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