1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.view.textclassifier;
18 
19 import android.annotation.Nullable;
20 import android.app.RemoteAction;
21 import android.content.Intent;
22 import android.icu.util.ULocale;
23 import android.os.Bundle;
24 
25 import com.android.internal.util.ArrayUtils;
26 
27 import com.google.android.textclassifier.AnnotatorModel;
28 
29 import java.util.ArrayList;
30 import java.util.List;
31 
32 /**
33  * Utility class for inserting and retrieving data in TextClassifier request/response extras.
34  * @hide
35  */
36 // TODO: Make this a TestApi for CTS testing.
37 public final class ExtrasUtils {
38 
39     // Keys for response objects.
40     private static final String SERIALIZED_ENTITIES_DATA = "serialized-entities-data";
41     private static final String ENTITIES_EXTRAS = "entities-extras";
42     private static final String ACTION_INTENT = "action-intent";
43     private static final String ACTIONS_INTENTS = "actions-intents";
44     private static final String FOREIGN_LANGUAGE = "foreign-language";
45     private static final String ENTITY_TYPE = "entity-type";
46     private static final String SCORE = "score";
47     private static final String MODEL_VERSION = "model-version";
48     private static final String MODEL_NAME = "model-name";
49     private static final String TEXT_LANGUAGES = "text-languages";
50     private static final String ENTITIES = "entities";
51 
52     // Keys for request objects.
53     private static final String IS_SERIALIZED_ENTITY_DATA_ENABLED =
54             "is-serialized-entity-data-enabled";
55 
ExtrasUtils()56     private ExtrasUtils() {}
57 
58     /**
59      * Bundles and returns foreign language detection information for TextClassifier responses.
60      */
createForeignLanguageExtra( String language, float score, int modelVersion)61     static Bundle createForeignLanguageExtra(
62             String language, float score, int modelVersion) {
63         final Bundle bundle = new Bundle();
64         bundle.putString(ENTITY_TYPE, language);
65         bundle.putFloat(SCORE, score);
66         bundle.putInt(MODEL_VERSION, modelVersion);
67         bundle.putString(MODEL_NAME, "langId_v" + modelVersion);
68         return bundle;
69     }
70 
71     /**
72      * Stores {@code extra} as foreign language information in TextClassifier response object's
73      * extras {@code container}.
74      *
75      * @see #getForeignLanguageExtra(TextClassification)
76      */
putForeignLanguageExtra(Bundle container, Bundle extra)77     static void putForeignLanguageExtra(Bundle container, Bundle extra) {
78         container.putParcelable(FOREIGN_LANGUAGE, extra);
79     }
80 
81     /**
82      * Returns foreign language detection information contained in the TextClassification object.
83      * responses.
84      *
85      * @see #putForeignLanguageExtra(Bundle, Bundle)
86      */
87     @Nullable
getForeignLanguageExtra(@ullable TextClassification classification)88     public static Bundle getForeignLanguageExtra(@Nullable TextClassification classification) {
89         if (classification == null) {
90             return null;
91         }
92         return classification.getExtras().getBundle(FOREIGN_LANGUAGE);
93     }
94 
95     /**
96      * @see #getTopLanguage(Intent)
97      */
putTopLanguageScores(Bundle container, EntityConfidence languageScores)98     static void putTopLanguageScores(Bundle container, EntityConfidence languageScores) {
99         final int maxSize = Math.min(3, languageScores.getEntities().size());
100         final String[] languages = languageScores.getEntities().subList(0, maxSize)
101                 .toArray(new String[0]);
102         final float[] scores = new float[languages.length];
103         for (int i = 0; i < languages.length; i++) {
104             scores[i] = languageScores.getConfidenceScore(languages[i]);
105         }
106         container.putStringArray(ENTITY_TYPE, languages);
107         container.putFloatArray(SCORE, scores);
108     }
109 
110     /**
111      * @see #putTopLanguageScores(Bundle, EntityConfidence)
112      */
113     @Nullable
getTopLanguage(@ullable Intent intent)114     public static ULocale getTopLanguage(@Nullable Intent intent) {
115         if (intent == null) {
116             return null;
117         }
118         final Bundle tcBundle = intent.getBundleExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER);
119         if (tcBundle == null) {
120             return null;
121         }
122         final Bundle textLanguagesExtra = tcBundle.getBundle(TEXT_LANGUAGES);
123         if (textLanguagesExtra == null) {
124             return null;
125         }
126         final String[] languages = textLanguagesExtra.getStringArray(ENTITY_TYPE);
127         final float[] scores = textLanguagesExtra.getFloatArray(SCORE);
128         if (languages == null || scores == null
129                 || languages.length == 0 || languages.length != scores.length) {
130             return null;
131         }
132         int highestScoringIndex = 0;
133         for (int i = 1; i < languages.length; i++) {
134             if (scores[highestScoringIndex] < scores[i]) {
135                 highestScoringIndex = i;
136             }
137         }
138         return ULocale.forLanguageTag(languages[highestScoringIndex]);
139     }
140 
putTextLanguagesExtra(Bundle container, Bundle extra)141     public static void putTextLanguagesExtra(Bundle container, Bundle extra) {
142         container.putBundle(TEXT_LANGUAGES, extra);
143     }
144 
145     /**
146      * Stores {@code actionIntents} information in TextClassifier response object's extras
147      * {@code container}.
148      */
putActionsIntents(Bundle container, ArrayList<Intent> actionsIntents)149     static void putActionsIntents(Bundle container, ArrayList<Intent> actionsIntents) {
150         container.putParcelableArrayList(ACTIONS_INTENTS, actionsIntents);
151     }
152 
153     /**
154      * Stores {@code actionIntents} information in TextClassifier response object's extras
155      * {@code container}.
156      */
putActionIntent(Bundle container, @Nullable Intent actionIntent)157     public static void putActionIntent(Bundle container, @Nullable Intent actionIntent) {
158         container.putParcelable(ACTION_INTENT, actionIntent);
159     }
160 
161     /**
162      * Returns {@code actionIntent} information contained in a TextClassifier response object.
163      */
164     @Nullable
getActionIntent(Bundle container)165     public static Intent getActionIntent(Bundle container) {
166         return container.getParcelable(ACTION_INTENT);
167     }
168 
169     /**
170      * Stores serialized entity data information in TextClassifier response object's extras
171      * {@code container}.
172      */
putSerializedEntityData( Bundle container, @Nullable byte[] serializedEntityData)173     public static void putSerializedEntityData(
174             Bundle container, @Nullable byte[] serializedEntityData) {
175         container.putByteArray(SERIALIZED_ENTITIES_DATA, serializedEntityData);
176     }
177 
178     /**
179      * Returns serialized entity data information contained in a TextClassifier response
180      * object.
181      */
182     @Nullable
getSerializedEntityData(Bundle container)183     public static byte[] getSerializedEntityData(Bundle container) {
184         return container.getByteArray(SERIALIZED_ENTITIES_DATA);
185     }
186 
187     /**
188      * Stores {@code entities} information in TextClassifier response object's extras
189      * {@code container}.
190      *
191      * @see {@link #getCopyText(Bundle)}
192      */
putEntitiesExtras(Bundle container, @Nullable Bundle entitiesExtras)193     public static void putEntitiesExtras(Bundle container, @Nullable Bundle entitiesExtras) {
194         container.putParcelable(ENTITIES_EXTRAS, entitiesExtras);
195     }
196 
197     /**
198      * Returns {@code entities} information contained in a TextClassifier response object.
199      *
200      * @see {@link #putEntitiesExtras(Bundle, Bundle)}
201      */
202     @Nullable
getCopyText(Bundle container)203     public static String getCopyText(Bundle container) {
204         Bundle entitiesExtras = container.getParcelable(ENTITIES_EXTRAS);
205         if (entitiesExtras == null) {
206             return null;
207         }
208         return entitiesExtras.getString("text");
209     }
210 
211     /**
212      * Returns {@code actionIntents} information contained in the TextClassification object.
213      */
214     @Nullable
getActionsIntents(@ullable TextClassification classification)215     public static ArrayList<Intent> getActionsIntents(@Nullable TextClassification classification) {
216         if (classification == null) {
217             return null;
218         }
219         return classification.getExtras().getParcelableArrayList(ACTIONS_INTENTS);
220     }
221 
222     /**
223      * Returns the first action found in the {@code classification} object with an intent
224      * action string, {@code intentAction}.
225      */
226     @Nullable
findAction( @ullable TextClassification classification, @Nullable String intentAction)227     public static RemoteAction findAction(
228             @Nullable TextClassification classification, @Nullable String intentAction) {
229         if (classification == null || intentAction == null) {
230             return null;
231         }
232         final ArrayList<Intent> actionIntents = getActionsIntents(classification);
233         if (actionIntents != null) {
234             final int size = actionIntents.size();
235             for (int i = 0; i < size; i++) {
236                 final Intent intent = actionIntents.get(i);
237                 if (intent != null && intentAction.equals(intent.getAction())) {
238                     return classification.getActions().get(i);
239                 }
240             }
241         }
242         return null;
243     }
244 
245     /**
246      * Returns the first "translate" action found in the {@code classification} object.
247      */
248     @Nullable
findTranslateAction(@ullable TextClassification classification)249     public static RemoteAction findTranslateAction(@Nullable TextClassification classification) {
250         return findAction(classification, Intent.ACTION_TRANSLATE);
251     }
252 
253     /**
254      * Returns the entity type contained in the {@code extra}.
255      */
256     @Nullable
getEntityType(@ullable Bundle extra)257     public static String getEntityType(@Nullable Bundle extra) {
258         if (extra == null) {
259             return null;
260         }
261         return extra.getString(ENTITY_TYPE);
262     }
263 
264     /**
265      * Returns the score contained in the {@code extra}.
266      */
267     @Nullable
getScore(Bundle extra)268     public static float getScore(Bundle extra) {
269         final int defaultValue = -1;
270         if (extra == null) {
271             return defaultValue;
272         }
273         return extra.getFloat(SCORE, defaultValue);
274     }
275 
276     /**
277      * Returns the model name contained in the {@code extra}.
278      */
279     @Nullable
getModelName(@ullable Bundle extra)280     public static String getModelName(@Nullable Bundle extra) {
281         if (extra == null) {
282             return null;
283         }
284         return extra.getString(MODEL_NAME);
285     }
286 
287     /**
288      * Stores the entities from {@link AnnotatorModel.ClassificationResult} in {@code container}.
289      */
putEntities( Bundle container, @Nullable AnnotatorModel.ClassificationResult[] classifications)290     public static void putEntities(
291             Bundle container,
292             @Nullable AnnotatorModel.ClassificationResult[] classifications) {
293         if (ArrayUtils.isEmpty(classifications)) {
294             return;
295         }
296         ArrayList<Bundle> entitiesBundle = new ArrayList<>();
297         for (AnnotatorModel.ClassificationResult classification : classifications) {
298             if (classification == null) {
299                 continue;
300             }
301             Bundle entityBundle = new Bundle();
302             entityBundle.putString(ENTITY_TYPE, classification.getCollection());
303             entityBundle.putByteArray(
304                     SERIALIZED_ENTITIES_DATA,
305                     classification.getSerializedEntityData());
306             entitiesBundle.add(entityBundle);
307         }
308         if (!entitiesBundle.isEmpty()) {
309             container.putParcelableArrayList(ENTITIES, entitiesBundle);
310         }
311     }
312 
313     /**
314      * Returns a list of entities contained in the {@code extra}.
315      */
316     @Nullable
getEntities(Bundle container)317     public static List<Bundle> getEntities(Bundle container) {
318         return container.getParcelableArrayList(ENTITIES);
319     }
320 
321     /**
322      * Whether the annotator should populate serialized entity data into the result object.
323      */
isSerializedEntityDataEnabled(TextLinks.Request request)324     public static boolean isSerializedEntityDataEnabled(TextLinks.Request request) {
325         return request.getExtras().getBoolean(IS_SERIALIZED_ENTITY_DATA_ENABLED);
326     }
327 
328     /**
329      * To indicate whether the annotator should populate serialized entity data in the result
330      * object.
331      */
putIsSerializedEntityDataEnabled(Bundle bundle, boolean isEnabled)332     public static void putIsSerializedEntityDataEnabled(Bundle bundle, boolean isEnabled) {
333         bundle.putBoolean(IS_SERIALIZED_ENTITY_DATA_ENABLED, isEnabled);
334     }
335 }
336