From 293bdf360a59b32ce9a89620757913df71fd7ad1 Mon Sep 17 00:00:00 2001 From: Tony Mak Date: Tue, 18 Feb 2020 11:33:43 +0000 Subject: [PATCH] Remove local text classifier and related tests. 1. libtextclassifier and libtextclassifier-java are no longer built into framework/base. 2. Removed local text classifier code 3. Removed local text classifier test code. All of them should be already moved to libtextclassifier/tcs side. 4. Unify all the TC related log tags to "androidtc". BUG: 147412216 Test: mts-tradefed run mts-extservices Test: atest frameworks/base/core/java/android/view/textclassifier Test: Sanity test: Smart selection Change-Id: Icb1076153f51d5674c8a6c58681ffed5aa772149 --- Android.bp | 4 - .../TextClassificationManagerPerfTest.java | 2 - .../textclassifier/TextClassifierService.java | 29 +- .../ActionsModelParamsSupplier.java | 210 ---- .../ActionsSuggestionsHelper.java | 234 ----- .../view/textclassifier/ExtrasUtils.java | 202 +--- .../textclassifier/GenerateLinksLogger.java | 160 --- .../java/android/view/textclassifier/Log.java | 2 +- .../view/textclassifier/ModelFileManager.java | 301 ------ .../view/textclassifier/SelectionEvent.java | 4 +- .../SelectionSessionLogger.java | 261 +---- .../textclassifier/SystemTextClassifier.java | 2 +- .../textclassifier/TextClassification.java | 54 +- .../TextClassificationConstants.java | 279 +----- .../TextClassificationManager.java | 101 +- .../view/textclassifier/TextClassifier.java | 21 +- .../TextClassifierEventTronLogger.java | 187 ---- .../textclassifier/TextClassifierImpl.java | 911 ------------------ .../intent/ClassificationIntentFactory.java | 58 -- .../textclassifier/intent/LabeledIntent.java | 219 ----- .../LegacyClassificationIntentFactory.java | 280 ------ .../TemplateClassificationIntentFactory.java | 81 -- .../intent/TemplateIntentFactory.java | 156 --- .../logging/SmartSelectionEventTracker.java | 599 ------------ .../widget/SelectionActionModeHelper.java | 3 +- .../ActionsModelParamsSupplierTest.java | 95 -- .../ActionsSuggestionsHelperTest.java | 331 ------- .../textclassifier/ModelFileManagerTest.java | 350 ------- .../TextClassificationConstantsTest.java | 68 +- .../TextClassificationManagerTest.java | 52 +- .../textclassifier/TextClassifierTest.java | 719 -------------- .../intent/LabeledIntentTest.java | 200 ---- ...LegacyIntentClassificationFactoryTest.java | 122 --- ...mplateClassificationIntentFactoryTest.java | 243 ----- .../intent/TemplateIntentFactoryTest.java | 270 ------ .../logging/GenerateLinksLoggerTest.java | 116 --- .../SmartSelectionEventTrackerTest.java | 43 - .../TextClassifierEventTronLoggerTest.java | 107 -- 38 files changed, 61 insertions(+), 7015 deletions(-) delete mode 100644 core/java/android/view/textclassifier/ActionsModelParamsSupplier.java delete mode 100644 core/java/android/view/textclassifier/ActionsSuggestionsHelper.java delete mode 100644 core/java/android/view/textclassifier/GenerateLinksLogger.java delete mode 100644 core/java/android/view/textclassifier/ModelFileManager.java delete mode 100644 core/java/android/view/textclassifier/TextClassifierEventTronLogger.java delete mode 100644 core/java/android/view/textclassifier/TextClassifierImpl.java delete mode 100644 core/java/android/view/textclassifier/intent/ClassificationIntentFactory.java delete mode 100644 core/java/android/view/textclassifier/intent/LabeledIntent.java delete mode 100644 core/java/android/view/textclassifier/intent/LegacyClassificationIntentFactory.java delete mode 100644 core/java/android/view/textclassifier/intent/TemplateClassificationIntentFactory.java delete mode 100644 core/java/android/view/textclassifier/intent/TemplateIntentFactory.java delete mode 100644 core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.java delete mode 100644 core/tests/coretests/src/android/view/textclassifier/ActionsModelParamsSupplierTest.java delete mode 100644 core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java delete mode 100644 core/tests/coretests/src/android/view/textclassifier/ModelFileManagerTest.java delete mode 100644 core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java delete mode 100644 core/tests/coretests/src/android/view/textclassifier/intent/LabeledIntentTest.java delete mode 100644 core/tests/coretests/src/android/view/textclassifier/intent/LegacyIntentClassificationFactoryTest.java delete mode 100644 core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java delete mode 100644 core/tests/coretests/src/android/view/textclassifier/intent/TemplateIntentFactoryTest.java delete mode 100644 core/tests/coretests/src/android/view/textclassifier/logging/GenerateLinksLoggerTest.java delete mode 100644 core/tests/coretests/src/android/view/textclassifier/logging/SmartSelectionEventTrackerTest.java delete mode 100644 core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java diff --git a/Android.bp b/Android.bp index 8adf48dc49b6d..da5d62429035c 100644 --- a/Android.bp +++ b/Android.bp @@ -791,10 +791,6 @@ java_library { "libphonenumber-platform", "tagsoup", "rappor", - "libtextclassifier-java", - ], - required: [ - "libtextclassifier", ], dxflags: ["--core-library"], } diff --git a/apct-tests/perftests/textclassifier/src/android/view/textclassifier/TextClassificationManagerPerfTest.java b/apct-tests/perftests/textclassifier/src/android/view/textclassifier/TextClassificationManagerPerfTest.java index f61ea85492364..46250d74a4e3f 100644 --- a/apct-tests/perftests/textclassifier/src/android/view/textclassifier/TextClassificationManagerPerfTest.java +++ b/apct-tests/perftests/textclassifier/src/android/view/textclassifier/TextClassificationManagerPerfTest.java @@ -77,7 +77,6 @@ public class TextClassificationManagerPerfTest { BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { textClassificationManager.getTextClassifier(); - textClassificationManager.invalidateForTesting(); } } @@ -90,7 +89,6 @@ public class TextClassificationManagerPerfTest { BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { textClassificationManager.getTextClassifier(); - textClassificationManager.invalidateForTesting(); } } diff --git a/core/java/android/service/textclassifier/TextClassifierService.java b/core/java/android/service/textclassifier/TextClassifierService.java index 3ff6f549e3377..9dfbc285c381c 100644 --- a/core/java/android/service/textclassifier/TextClassifierService.java +++ b/core/java/android/service/textclassifier/TextClassifierService.java @@ -41,7 +41,6 @@ import android.util.Slog; import android.view.textclassifier.ConversationActions; import android.view.textclassifier.SelectionEvent; import android.view.textclassifier.TextClassification; -import android.view.textclassifier.TextClassificationConstants; import android.view.textclassifier.TextClassificationContext; import android.view.textclassifier.TextClassificationManager; import android.view.textclassifier.TextClassificationSessionId; @@ -405,27 +404,19 @@ public abstract class TextClassifierService extends Service { */ @NonNull public static TextClassifier getDefaultTextClassifierImplementation(@NonNull Context context) { - final TextClassificationManager tcm = - context.getSystemService(TextClassificationManager.class); - if (tcm == null) { + final String defaultTextClassifierPackageName = + context.getPackageManager().getDefaultTextClassifierPackageName(); + if (TextUtils.isEmpty(defaultTextClassifierPackageName)) { return TextClassifier.NO_OP; } - TextClassificationConstants settings = new TextClassificationConstants(); - if (settings.getUseDefaultTextClassifierAsDefaultImplementation()) { - final String defaultTextClassifierPackageName = - context.getPackageManager().getDefaultTextClassifierPackageName(); - if (TextUtils.isEmpty(defaultTextClassifierPackageName)) { - return TextClassifier.NO_OP; - } - if (defaultTextClassifierPackageName.equals(context.getPackageName())) { - throw new RuntimeException( - "The default text classifier itself should not call the" - + "getDefaultTextClassifierImplementation() method."); - } - return tcm.getTextClassifier(TextClassifier.DEFAULT_SERVICE); - } else { - return tcm.getTextClassifier(TextClassifier.LOCAL); + if (defaultTextClassifierPackageName.equals(context.getPackageName())) { + throw new RuntimeException( + "The default text classifier itself should not call the" + + "getDefaultTextClassifierImplementation() method."); } + final TextClassificationManager tcm = + context.getSystemService(TextClassificationManager.class); + return tcm.getTextClassifier(TextClassifier.DEFAULT_SYSTEM); } /** @hide **/ diff --git a/core/java/android/view/textclassifier/ActionsModelParamsSupplier.java b/core/java/android/view/textclassifier/ActionsModelParamsSupplier.java deleted file mode 100644 index 31645672f0499..0000000000000 --- a/core/java/android/view/textclassifier/ActionsModelParamsSupplier.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.view.textclassifier; - -import android.annotation.Nullable; -import android.content.ContentResolver; -import android.content.Context; -import android.database.ContentObserver; -import android.provider.Settings; -import android.text.TextUtils; -import android.util.Base64; -import android.util.KeyValueListParser; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.Preconditions; - -import java.lang.ref.WeakReference; -import java.util.Objects; -import java.util.function.Supplier; - -/** - * Parses the {@link Settings.Global#TEXT_CLASSIFIER_ACTION_MODEL_PARAMS} flag. - * - * @hide - */ -public final class ActionsModelParamsSupplier implements - Supplier { - private static final String TAG = TextClassifier.DEFAULT_LOG_TAG; - - @VisibleForTesting - static final String KEY_REQUIRED_MODEL_VERSION = "required_model_version"; - @VisibleForTesting - static final String KEY_REQUIRED_LOCALES = "required_locales"; - @VisibleForTesting - static final String KEY_SERIALIZED_PRECONDITIONS = "serialized_preconditions"; - - private final Context mAppContext; - private final SettingsObserver mSettingsObserver; - - private final Object mLock = new Object(); - private final Runnable mOnChangedListener; - @Nullable - @GuardedBy("mLock") - private ActionsModelParams mActionsModelParams; - @GuardedBy("mLock") - private boolean mParsed = true; - - public ActionsModelParamsSupplier(Context context, @Nullable Runnable onChangedListener) { - final Context appContext = Preconditions.checkNotNull(context).getApplicationContext(); - // Some contexts don't have an app context. - mAppContext = appContext != null ? appContext : context; - mOnChangedListener = onChangedListener == null ? () -> {} : onChangedListener; - mSettingsObserver = new SettingsObserver(mAppContext, () -> { - synchronized (mLock) { - Log.v(TAG, "Settings.Global.TEXT_CLASSIFIER_ACTION_MODEL_PARAMS is updated"); - mParsed = true; - mOnChangedListener.run(); - } - }); - } - - /** - * Returns the parsed actions params or {@link ActionsModelParams#INVALID} if the value is - * invalid. - */ - @Override - public ActionsModelParams get() { - synchronized (mLock) { - if (mParsed) { - mActionsModelParams = parse(mAppContext.getContentResolver()); - mParsed = false; - } - } - return mActionsModelParams; - } - - private ActionsModelParams parse(ContentResolver contentResolver) { - String settingStr = Settings.Global.getString(contentResolver, - Settings.Global.TEXT_CLASSIFIER_ACTION_MODEL_PARAMS); - if (TextUtils.isEmpty(settingStr)) { - return ActionsModelParams.INVALID; - } - try { - KeyValueListParser keyValueListParser = new KeyValueListParser(','); - keyValueListParser.setString(settingStr); - int version = keyValueListParser.getInt(KEY_REQUIRED_MODEL_VERSION, -1); - if (version == -1) { - Log.w(TAG, "ActionsModelParams.Parse, invalid model version"); - return ActionsModelParams.INVALID; - } - String locales = keyValueListParser.getString(KEY_REQUIRED_LOCALES, null); - if (locales == null) { - Log.w(TAG, "ActionsModelParams.Parse, invalid locales"); - return ActionsModelParams.INVALID; - } - String serializedPreconditionsStr = - keyValueListParser.getString(KEY_SERIALIZED_PRECONDITIONS, null); - if (serializedPreconditionsStr == null) { - Log.w(TAG, "ActionsModelParams.Parse, invalid preconditions"); - return ActionsModelParams.INVALID; - } - byte[] serializedPreconditions = - Base64.decode(serializedPreconditionsStr, Base64.NO_WRAP); - return new ActionsModelParams(version, locales, serializedPreconditions); - } catch (Throwable t) { - Log.e(TAG, "Invalid TEXT_CLASSIFIER_ACTION_MODEL_PARAMS, ignore", t); - } - return ActionsModelParams.INVALID; - } - - @Override - protected void finalize() throws Throwable { - try { - mAppContext.getContentResolver().unregisterContentObserver(mSettingsObserver); - } finally { - super.finalize(); - } - } - - /** - * Represents the parsed result. - */ - public static final class ActionsModelParams { - - public static final ActionsModelParams INVALID = - new ActionsModelParams(-1, "", new byte[0]); - - /** - * The required model version to apply {@code mSerializedPreconditions}. - */ - private final int mRequiredModelVersion; - - /** - * The required model locales to apply {@code mSerializedPreconditions}. - */ - private final String mRequiredModelLocales; - - /** - * The serialized params that will be applied to the model file, if all requirements are - * met. Do not modify. - */ - private final byte[] mSerializedPreconditions; - - public ActionsModelParams(int requiredModelVersion, String requiredModelLocales, - byte[] serializedPreconditions) { - mRequiredModelVersion = requiredModelVersion; - mRequiredModelLocales = Preconditions.checkNotNull(requiredModelLocales); - mSerializedPreconditions = Preconditions.checkNotNull(serializedPreconditions); - } - - /** - * Returns the serialized preconditions. Returns {@code null} if the the model in use does - * not meet all the requirements listed in the {@code ActionsModelParams} or the params - * are invalid. - */ - @Nullable - public byte[] getSerializedPreconditions(ModelFileManager.ModelFile modelInUse) { - if (this == INVALID) { - return null; - } - if (modelInUse.getVersion() != mRequiredModelVersion) { - Log.w(TAG, String.format( - "Not applying mSerializedPreconditions, required version=%d, actual=%d", - mRequiredModelVersion, modelInUse.getVersion())); - return null; - } - if (!Objects.equals(modelInUse.getSupportedLocalesStr(), mRequiredModelLocales)) { - Log.w(TAG, String.format( - "Not applying mSerializedPreconditions, required locales=%s, actual=%s", - mRequiredModelLocales, modelInUse.getSupportedLocalesStr())); - return null; - } - return mSerializedPreconditions; - } - } - - private static final class SettingsObserver extends ContentObserver { - - private final WeakReference mOnChangedListener; - - SettingsObserver(Context appContext, Runnable listener) { - super(null); - mOnChangedListener = new WeakReference<>(listener); - appContext.getContentResolver().registerContentObserver( - Settings.Global.getUriFor(Settings.Global.TEXT_CLASSIFIER_ACTION_MODEL_PARAMS), - false /* notifyForDescendants */, - this); - } - - public void onChange(boolean selfChange) { - if (mOnChangedListener.get() != null) { - mOnChangedListener.get().run(); - } - } - } -} diff --git a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java deleted file mode 100644 index 3ed48f655d471..0000000000000 --- a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.textclassifier; - -import android.annotation.Nullable; -import android.app.Person; -import android.app.RemoteAction; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.text.TextUtils; -import android.util.ArrayMap; -import android.util.Pair; -import android.view.textclassifier.intent.LabeledIntent; -import android.view.textclassifier.intent.TemplateIntentFactory; - -import com.android.internal.annotations.VisibleForTesting; - -import com.google.android.textclassifier.ActionsSuggestionsModel; -import com.google.android.textclassifier.RemoteActionTemplate; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Deque; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.StringJoiner; -import java.util.function.Function; -import java.util.stream.Collectors; - -/** - * Helper class for action suggestions. - * - * @hide - */ -@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) -public final class ActionsSuggestionsHelper { - private static final String TAG = "ActionsSuggestions"; - private static final int USER_LOCAL = 0; - private static final int FIRST_NON_LOCAL_USER = 1; - - private ActionsSuggestionsHelper() {} - - /** - * Converts the messages to a list of native messages object that the model can understand. - *

- * User id encoding - local user is represented as 0, Other users are numbered according to - * how far before they spoke last time in the conversation. For example, considering this - * conversation: - *

    - *
  • User A: xxx - *
  • Local user: yyy - *
  • User B: zzz - *
- * User A will be encoded as 2, user B will be encoded as 1 and local user will be encoded as 0. - */ - public static ActionsSuggestionsModel.ConversationMessage[] toNativeMessages( - List messages, - Function languageDetector) { - List messagesWithText = - messages.stream() - .filter(message -> !TextUtils.isEmpty(message.getText())) - .collect(Collectors.toCollection(ArrayList::new)); - if (messagesWithText.isEmpty()) { - return new ActionsSuggestionsModel.ConversationMessage[0]; - } - Deque nativeMessages = new ArrayDeque<>(); - PersonEncoder personEncoder = new PersonEncoder(); - int size = messagesWithText.size(); - for (int i = size - 1; i >= 0; i--) { - ConversationActions.Message message = messagesWithText.get(i); - long referenceTime = message.getReferenceTime() == null - ? 0 - : message.getReferenceTime().toInstant().toEpochMilli(); - String timeZone = message.getReferenceTime() == null - ? null - : message.getReferenceTime().getZone().getId(); - nativeMessages.push(new ActionsSuggestionsModel.ConversationMessage( - personEncoder.encode(message.getAuthor()), - message.getText().toString(), referenceTime, timeZone, - languageDetector.apply(message.getText()))); - } - return nativeMessages.toArray( - new ActionsSuggestionsModel.ConversationMessage[nativeMessages.size()]); - } - - /** - * Returns the result id for logging. - */ - public static String createResultId( - Context context, - List messages, - int modelVersion, - List modelLocales) { - final StringJoiner localesJoiner = new StringJoiner(","); - for (Locale locale : modelLocales) { - localesJoiner.add(locale.toLanguageTag()); - } - final String modelName = String.format( - Locale.US, "%s_v%d", localesJoiner.toString(), modelVersion); - final int hash = Objects.hash( - messages.stream().mapToInt(ActionsSuggestionsHelper::hashMessage), - context.getPackageName(), - System.currentTimeMillis()); - return SelectionSessionLogger.SignatureParser.createSignature( - SelectionSessionLogger.CLASSIFIER_ID, modelName, hash); - } - - /** - * Generated labeled intent from an action suggestion and return the resolved result. - */ - @Nullable - public static LabeledIntent.Result createLabeledIntentResult( - Context context, - TemplateIntentFactory templateIntentFactory, - ActionsSuggestionsModel.ActionSuggestion nativeSuggestion) { - RemoteActionTemplate[] remoteActionTemplates = - nativeSuggestion.getRemoteActionTemplates(); - if (remoteActionTemplates == null) { - Log.w(TAG, "createRemoteAction: Missing template for type " - + nativeSuggestion.getActionType()); - return null; - } - List labeledIntents = templateIntentFactory.create(remoteActionTemplates); - if (labeledIntents.isEmpty()) { - return null; - } - // Given that we only support implicit intent here, we should expect there is just one - // intent for each action type. - LabeledIntent.TitleChooser titleChooser = - ActionsSuggestionsHelper.createTitleChooser(nativeSuggestion.getActionType()); - return labeledIntents.get(0).resolve(context, titleChooser, null); - } - - /** - * Returns a {@link LabeledIntent.TitleChooser} for conversation actions use case. - */ - @Nullable - public static LabeledIntent.TitleChooser createTitleChooser(String actionType) { - if (ConversationAction.TYPE_OPEN_URL.equals(actionType)) { - return (labeledIntent, resolveInfo) -> { - if (resolveInfo.handleAllWebDataURI) { - return labeledIntent.titleWithEntity; - } - if ("android".equals(resolveInfo.activityInfo.packageName)) { - return labeledIntent.titleWithEntity; - } - return labeledIntent.titleWithoutEntity; - }; - } - return null; - } - - /** - * Returns a list of {@link ConversationAction}s that have 0 duplicates. Two actions are - * duplicates if they may look the same to users. This function assumes every - * ConversationActions with a non-null RemoteAction also have a non-null intent in the extras. - */ - public static List removeActionsWithDuplicates( - List conversationActions) { - // Ideally, we should compare title and icon here, but comparing icon is expensive and thus - // we use the component name of the target handler as the heuristic. - Map, Integer> counter = new ArrayMap<>(); - for (ConversationAction conversationAction : conversationActions) { - Pair representation = getRepresentation(conversationAction); - if (representation == null) { - continue; - } - Integer existingCount = counter.getOrDefault(representation, 0); - counter.put(representation, existingCount + 1); - } - List result = new ArrayList<>(); - for (ConversationAction conversationAction : conversationActions) { - Pair representation = getRepresentation(conversationAction); - if (representation == null || counter.getOrDefault(representation, 0) == 1) { - result.add(conversationAction); - } - } - return result; - } - - @Nullable - private static Pair getRepresentation( - ConversationAction conversationAction) { - RemoteAction remoteAction = conversationAction.getAction(); - if (remoteAction == null) { - return null; - } - Intent actionIntent = ExtrasUtils.getActionIntent(conversationAction.getExtras()); - ComponentName componentName = actionIntent.getComponent(); - // Action without a component name will be considered as from the same app. - String packageName = componentName == null ? null : componentName.getPackageName(); - return new Pair<>( - conversationAction.getAction().getTitle().toString(), packageName); - } - - private static final class PersonEncoder { - private final Map mMapping = new ArrayMap<>(); - private int mNextUserId = FIRST_NON_LOCAL_USER; - - private int encode(Person person) { - if (ConversationActions.Message.PERSON_USER_SELF.equals(person)) { - return USER_LOCAL; - } - Integer result = mMapping.get(person); - if (result == null) { - mMapping.put(person, mNextUserId); - result = mNextUserId; - mNextUserId++; - } - return result; - } - } - - private static int hashMessage(ConversationActions.Message message) { - return Objects.hash(message.getAuthor(), message.getText(), message.getReferenceTime()); - } -} diff --git a/core/java/android/view/textclassifier/ExtrasUtils.java b/core/java/android/view/textclassifier/ExtrasUtils.java index 11e0e2ca072c0..9e2b6427eaeac 100644 --- a/core/java/android/view/textclassifier/ExtrasUtils.java +++ b/core/java/android/view/textclassifier/ExtrasUtils.java @@ -19,15 +19,9 @@ package android.view.textclassifier; import android.annotation.Nullable; import android.app.RemoteAction; import android.content.Intent; -import android.icu.util.ULocale; import android.os.Bundle; -import com.android.internal.util.ArrayUtils; - -import com.google.android.textclassifier.AnnotatorModel; - import java.util.ArrayList; -import java.util.List; /** * Utility class for inserting and retrieving data in TextClassifier request/response extras. @@ -37,52 +31,19 @@ import java.util.List; public final class ExtrasUtils { // Keys for response objects. - private static final String SERIALIZED_ENTITIES_DATA = "serialized-entities-data"; - private static final String ENTITIES_EXTRAS = "entities-extras"; private static final String ACTION_INTENT = "action-intent"; private static final String ACTIONS_INTENTS = "actions-intents"; private static final String FOREIGN_LANGUAGE = "foreign-language"; private static final String ENTITY_TYPE = "entity-type"; private static final String SCORE = "score"; - private static final String MODEL_VERSION = "model-version"; private static final String MODEL_NAME = "model-name"; - private static final String TEXT_LANGUAGES = "text-languages"; - private static final String ENTITIES = "entities"; - // Keys for request objects. - private static final String IS_SERIALIZED_ENTITY_DATA_ENABLED = - "is-serialized-entity-data-enabled"; - - private ExtrasUtils() {} - - /** - * Bundles and returns foreign language detection information for TextClassifier responses. - */ - static Bundle createForeignLanguageExtra( - String language, float score, int modelVersion) { - final Bundle bundle = new Bundle(); - bundle.putString(ENTITY_TYPE, language); - bundle.putFloat(SCORE, score); - bundle.putInt(MODEL_VERSION, modelVersion); - bundle.putString(MODEL_NAME, "langId_v" + modelVersion); - return bundle; - } - - /** - * Stores {@code extra} as foreign language information in TextClassifier response object's - * extras {@code container}. - * - * @see #getForeignLanguageExtra(TextClassification) - */ - static void putForeignLanguageExtra(Bundle container, Bundle extra) { - container.putParcelable(FOREIGN_LANGUAGE, extra); + private ExtrasUtils() { } /** * Returns foreign language detection information contained in the TextClassification object. * responses. - * - * @see #putForeignLanguageExtra(Bundle, Bundle) */ @Nullable public static Bundle getForeignLanguageExtra(@Nullable TextClassification classification) { @@ -92,72 +53,6 @@ public final class ExtrasUtils { return classification.getExtras().getBundle(FOREIGN_LANGUAGE); } - /** - * @see #getTopLanguage(Intent) - */ - static void putTopLanguageScores(Bundle container, EntityConfidence languageScores) { - final int maxSize = Math.min(3, languageScores.getEntities().size()); - final String[] languages = languageScores.getEntities().subList(0, maxSize) - .toArray(new String[0]); - final float[] scores = new float[languages.length]; - for (int i = 0; i < languages.length; i++) { - scores[i] = languageScores.getConfidenceScore(languages[i]); - } - container.putStringArray(ENTITY_TYPE, languages); - container.putFloatArray(SCORE, scores); - } - - /** - * @see #putTopLanguageScores(Bundle, EntityConfidence) - */ - @Nullable - public static ULocale getTopLanguage(@Nullable Intent intent) { - if (intent == null) { - return null; - } - final Bundle tcBundle = intent.getBundleExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER); - if (tcBundle == null) { - return null; - } - final Bundle textLanguagesExtra = tcBundle.getBundle(TEXT_LANGUAGES); - if (textLanguagesExtra == null) { - return null; - } - final String[] languages = textLanguagesExtra.getStringArray(ENTITY_TYPE); - final float[] scores = textLanguagesExtra.getFloatArray(SCORE); - if (languages == null || scores == null - || languages.length == 0 || languages.length != scores.length) { - return null; - } - int highestScoringIndex = 0; - for (int i = 1; i < languages.length; i++) { - if (scores[highestScoringIndex] < scores[i]) { - highestScoringIndex = i; - } - } - return ULocale.forLanguageTag(languages[highestScoringIndex]); - } - - public static void putTextLanguagesExtra(Bundle container, Bundle extra) { - container.putBundle(TEXT_LANGUAGES, extra); - } - - /** - * Stores {@code actionIntents} information in TextClassifier response object's extras - * {@code container}. - */ - static void putActionsIntents(Bundle container, ArrayList actionsIntents) { - container.putParcelableArrayList(ACTIONS_INTENTS, actionsIntents); - } - - /** - * Stores {@code actionIntents} information in TextClassifier response object's extras - * {@code container}. - */ - public static void putActionIntent(Bundle container, @Nullable Intent actionIntent) { - container.putParcelable(ACTION_INTENT, actionIntent); - } - /** * Returns {@code actionIntent} information contained in a TextClassifier response object. */ @@ -166,48 +61,6 @@ public final class ExtrasUtils { return container.getParcelable(ACTION_INTENT); } - /** - * Stores serialized entity data information in TextClassifier response object's extras - * {@code container}. - */ - public static void putSerializedEntityData( - Bundle container, @Nullable byte[] serializedEntityData) { - container.putByteArray(SERIALIZED_ENTITIES_DATA, serializedEntityData); - } - - /** - * Returns serialized entity data information contained in a TextClassifier response - * object. - */ - @Nullable - public static byte[] getSerializedEntityData(Bundle container) { - return container.getByteArray(SERIALIZED_ENTITIES_DATA); - } - - /** - * Stores {@code entities} information in TextClassifier response object's extras - * {@code container}. - * - * @see {@link #getCopyText(Bundle)} - */ - public static void putEntitiesExtras(Bundle container, @Nullable Bundle entitiesExtras) { - container.putParcelable(ENTITIES_EXTRAS, entitiesExtras); - } - - /** - * Returns {@code entities} information contained in a TextClassifier response object. - * - * @see {@link #putEntitiesExtras(Bundle, Bundle)} - */ - @Nullable - public static String getCopyText(Bundle container) { - Bundle entitiesExtras = container.getParcelable(ENTITIES_EXTRAS); - if (entitiesExtras == null) { - return null; - } - return entitiesExtras.getString("text"); - } - /** * Returns {@code actionIntents} information contained in the TextClassification object. */ @@ -224,7 +77,7 @@ public final class ExtrasUtils { * action string, {@code intentAction}. */ @Nullable - public static RemoteAction findAction( + private static RemoteAction findAction( @Nullable TextClassification classification, @Nullable String intentAction) { if (classification == null || intentAction == null) { return null; @@ -283,53 +136,4 @@ public final class ExtrasUtils { } return extra.getString(MODEL_NAME); } - - /** - * Stores the entities from {@link AnnotatorModel.ClassificationResult} in {@code container}. - */ - public static void putEntities( - Bundle container, - @Nullable AnnotatorModel.ClassificationResult[] classifications) { - if (ArrayUtils.isEmpty(classifications)) { - return; - } - ArrayList entitiesBundle = new ArrayList<>(); - for (AnnotatorModel.ClassificationResult classification : classifications) { - if (classification == null) { - continue; - } - Bundle entityBundle = new Bundle(); - entityBundle.putString(ENTITY_TYPE, classification.getCollection()); - entityBundle.putByteArray( - SERIALIZED_ENTITIES_DATA, - classification.getSerializedEntityData()); - entitiesBundle.add(entityBundle); - } - if (!entitiesBundle.isEmpty()) { - container.putParcelableArrayList(ENTITIES, entitiesBundle); - } - } - - /** - * Returns a list of entities contained in the {@code extra}. - */ - @Nullable - public static List getEntities(Bundle container) { - return container.getParcelableArrayList(ENTITIES); - } - - /** - * Whether the annotator should populate serialized entity data into the result object. - */ - public static boolean isSerializedEntityDataEnabled(TextLinks.Request request) { - return request.getExtras().getBoolean(IS_SERIALIZED_ENTITY_DATA_ENABLED); - } - - /** - * To indicate whether the annotator should populate serialized entity data in the result - * object. - */ - public static void putIsSerializedEntityDataEnabled(Bundle bundle, boolean isEnabled) { - bundle.putBoolean(IS_SERIALIZED_ENTITY_DATA_ENABLED, isEnabled); - } -} +} \ No newline at end of file diff --git a/core/java/android/view/textclassifier/GenerateLinksLogger.java b/core/java/android/view/textclassifier/GenerateLinksLogger.java deleted file mode 100644 index 17ec73ad395f8..0000000000000 --- a/core/java/android/view/textclassifier/GenerateLinksLogger.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.textclassifier; - -import android.annotation.Nullable; -import android.metrics.LogMaker; -import android.util.ArrayMap; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; - -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.Random; -import java.util.UUID; - -/** - * A helper for logging calls to generateLinks. - * @hide - */ -@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) -public final class GenerateLinksLogger { - - private static final String LOG_TAG = "GenerateLinksLogger"; - private static final String ZERO = "0"; - - private final MetricsLogger mMetricsLogger; - private final Random mRng; - private final int mSampleRate; - - /** - * @param sampleRate the rate at which log events are written. (e.g. 100 means there is a 0.01 - * chance that a call to logGenerateLinks results in an event being written). - * To write all events, pass 1. - */ - public GenerateLinksLogger(int sampleRate) { - mSampleRate = sampleRate; - mRng = new Random(System.nanoTime()); - mMetricsLogger = new MetricsLogger(); - } - - @VisibleForTesting - public GenerateLinksLogger(int sampleRate, MetricsLogger metricsLogger) { - mSampleRate = sampleRate; - mRng = new Random(System.nanoTime()); - mMetricsLogger = metricsLogger; - } - - /** Logs statistics about a call to generateLinks. */ - public void logGenerateLinks(CharSequence text, TextLinks links, String callingPackageName, - long latencyMs) { - Objects.requireNonNull(text); - Objects.requireNonNull(links); - Objects.requireNonNull(callingPackageName); - if (!shouldLog()) { - return; - } - - // Always populate the total stats, and per-entity stats for each entity type detected. - final LinkifyStats totalStats = new LinkifyStats(); - final Map perEntityTypeStats = new ArrayMap<>(); - for (TextLinks.TextLink link : links.getLinks()) { - if (link.getEntityCount() == 0) continue; - final String entityType = link.getEntity(0); - if (entityType == null - || TextClassifier.TYPE_OTHER.equals(entityType) - || TextClassifier.TYPE_UNKNOWN.equals(entityType)) { - continue; - } - totalStats.countLink(link); - perEntityTypeStats.computeIfAbsent(entityType, k -> new LinkifyStats()).countLink(link); - } - - final String callId = UUID.randomUUID().toString(); - writeStats(callId, callingPackageName, null, totalStats, text, latencyMs); - for (Map.Entry entry : perEntityTypeStats.entrySet()) { - writeStats(callId, callingPackageName, entry.getKey(), entry.getValue(), text, - latencyMs); - } - } - - /** - * Returns whether this particular event should be logged. - * - * Sampling is used to reduce the amount of logging data generated. - **/ - private boolean shouldLog() { - if (mSampleRate <= 1) { - return true; - } else { - return mRng.nextInt(mSampleRate) == 0; - } - } - - /** Writes a log event for the given stats. */ - private void writeStats(String callId, String callingPackageName, @Nullable String entityType, - LinkifyStats stats, CharSequence text, long latencyMs) { - final LogMaker log = new LogMaker(MetricsEvent.TEXT_CLASSIFIER_GENERATE_LINKS) - .setPackageName(callingPackageName) - .addTaggedData(MetricsEvent.FIELD_LINKIFY_CALL_ID, callId) - .addTaggedData(MetricsEvent.FIELD_LINKIFY_NUM_LINKS, stats.mNumLinks) - .addTaggedData(MetricsEvent.FIELD_LINKIFY_LINK_LENGTH, stats.mNumLinksTextLength) - .addTaggedData(MetricsEvent.FIELD_LINKIFY_TEXT_LENGTH, text.length()) - .addTaggedData(MetricsEvent.FIELD_LINKIFY_LATENCY, latencyMs); - if (entityType != null) { - log.addTaggedData(MetricsEvent.FIELD_LINKIFY_ENTITY_TYPE, entityType); - } - mMetricsLogger.write(log); - debugLog(log); - } - - private static void debugLog(LogMaker log) { - if (!Log.ENABLE_FULL_LOGGING) { - return; - } - final String callId = Objects.toString( - log.getTaggedData(MetricsEvent.FIELD_LINKIFY_CALL_ID), ""); - final String entityType = Objects.toString( - log.getTaggedData(MetricsEvent.FIELD_LINKIFY_ENTITY_TYPE), "ANY_ENTITY"); - final int numLinks = Integer.parseInt( - Objects.toString(log.getTaggedData(MetricsEvent.FIELD_LINKIFY_NUM_LINKS), ZERO)); - final int linkLength = Integer.parseInt( - Objects.toString(log.getTaggedData(MetricsEvent.FIELD_LINKIFY_LINK_LENGTH), ZERO)); - final int textLength = Integer.parseInt( - Objects.toString(log.getTaggedData(MetricsEvent.FIELD_LINKIFY_TEXT_LENGTH), ZERO)); - final int latencyMs = Integer.parseInt( - Objects.toString(log.getTaggedData(MetricsEvent.FIELD_LINKIFY_LATENCY), ZERO)); - - Log.v(LOG_TAG, - String.format(Locale.US, "%s:%s %d links (%d/%d chars) %dms %s", callId, entityType, - numLinks, linkLength, textLength, latencyMs, log.getPackageName())); - } - - /** Helper class for storing per-entity type statistics. */ - private static final class LinkifyStats { - int mNumLinks; - int mNumLinksTextLength; - - void countLink(TextLinks.TextLink link) { - mNumLinks += 1; - mNumLinksTextLength += link.getEnd() - link.getStart(); - } - } -} diff --git a/core/java/android/view/textclassifier/Log.java b/core/java/android/view/textclassifier/Log.java index 03ed4967e7b6d..98ee09ce29c4e 100644 --- a/core/java/android/view/textclassifier/Log.java +++ b/core/java/android/view/textclassifier/Log.java @@ -32,7 +32,7 @@ public final class Log { * false: Limits logging to debug level. */ static final boolean ENABLE_FULL_LOGGING = - android.util.Log.isLoggable(TextClassifier.DEFAULT_LOG_TAG, android.util.Log.VERBOSE); + android.util.Log.isLoggable(TextClassifier.LOG_TAG, android.util.Log.VERBOSE); private Log() { } diff --git a/core/java/android/view/textclassifier/ModelFileManager.java b/core/java/android/view/textclassifier/ModelFileManager.java deleted file mode 100644 index 0a4ff5d559ab6..0000000000000 --- a/core/java/android/view/textclassifier/ModelFileManager.java +++ /dev/null @@ -1,301 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.view.textclassifier; - -import static android.view.textclassifier.TextClassifier.DEFAULT_LOG_TAG; - -import android.annotation.Nullable; -import android.os.LocaleList; -import android.os.ParcelFileDescriptor; -import android.text.TextUtils; - -import com.android.internal.annotations.VisibleForTesting; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.Objects; -import java.util.StringJoiner; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Manages model files that are listed by the model files supplier. - * @hide - */ -@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) -public final class ModelFileManager { - private final Object mLock = new Object(); - private final Supplier> mModelFileSupplier; - - private List mModelFiles; - - public ModelFileManager(Supplier> modelFileSupplier) { - mModelFileSupplier = Objects.requireNonNull(modelFileSupplier); - } - - /** - * Returns an unmodifiable list of model files listed by the given model files supplier. - *

- * The result is cached. - */ - public List listModelFiles() { - synchronized (mLock) { - if (mModelFiles == null) { - mModelFiles = Collections.unmodifiableList(mModelFileSupplier.get()); - } - return mModelFiles; - } - } - - /** - * Returns the best model file for the given localelist, {@code null} if nothing is found. - * - * @param localeList the required locales, use {@code null} if there is no preference. - */ - public ModelFile findBestModelFile(@Nullable LocaleList localeList) { - final String languages = localeList == null || localeList.isEmpty() - ? LocaleList.getDefault().toLanguageTags() - : localeList.toLanguageTags(); - final List languageRangeList = Locale.LanguageRange.parse(languages); - - ModelFile bestModel = null; - for (ModelFile model : listModelFiles()) { - if (model.isAnyLanguageSupported(languageRangeList)) { - if (model.isPreferredTo(bestModel)) { - bestModel = model; - } - } - } - return bestModel; - } - - /** - * Default implementation of the model file supplier. - */ - public static final class ModelFileSupplierImpl implements Supplier> { - private final File mUpdatedModelFile; - private final File mFactoryModelDir; - private final Pattern mModelFilenamePattern; - private final Function mVersionSupplier; - private final Function mSupportedLocalesSupplier; - - public ModelFileSupplierImpl( - File factoryModelDir, - String factoryModelFileNameRegex, - File updatedModelFile, - Function versionSupplier, - Function supportedLocalesSupplier) { - mUpdatedModelFile = Objects.requireNonNull(updatedModelFile); - mFactoryModelDir = Objects.requireNonNull(factoryModelDir); - mModelFilenamePattern = Pattern.compile( - Objects.requireNonNull(factoryModelFileNameRegex)); - mVersionSupplier = Objects.requireNonNull(versionSupplier); - mSupportedLocalesSupplier = Objects.requireNonNull(supportedLocalesSupplier); - } - - @Override - public List get() { - final List modelFiles = new ArrayList<>(); - // The update model has the highest precedence. - if (mUpdatedModelFile.exists()) { - final ModelFile updatedModel = createModelFile(mUpdatedModelFile); - if (updatedModel != null) { - modelFiles.add(updatedModel); - } - } - // Factory models should never have overlapping locales, so the order doesn't matter. - if (mFactoryModelDir.exists() && mFactoryModelDir.isDirectory()) { - final File[] files = mFactoryModelDir.listFiles(); - for (File file : files) { - final Matcher matcher = mModelFilenamePattern.matcher(file.getName()); - if (matcher.matches() && file.isFile()) { - final ModelFile model = createModelFile(file); - if (model != null) { - modelFiles.add(model); - } - } - } - } - return modelFiles; - } - - /** Returns null if the path did not point to a compatible model. */ - @Nullable - private ModelFile createModelFile(File file) { - if (!file.exists()) { - return null; - } - ParcelFileDescriptor modelFd = null; - try { - modelFd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); - if (modelFd == null) { - return null; - } - final int modelFdInt = modelFd.getFd(); - final int version = mVersionSupplier.apply(modelFdInt); - final String supportedLocalesStr = mSupportedLocalesSupplier.apply(modelFdInt); - if (supportedLocalesStr.isEmpty()) { - Log.d(DEFAULT_LOG_TAG, "Ignoring " + file.getAbsolutePath()); - return null; - } - final List supportedLocales = new ArrayList<>(); - for (String langTag : supportedLocalesStr.split(",")) { - supportedLocales.add(Locale.forLanguageTag(langTag)); - } - return new ModelFile( - file, - version, - supportedLocales, - supportedLocalesStr, - ModelFile.LANGUAGE_INDEPENDENT.equals(supportedLocalesStr)); - } catch (FileNotFoundException e) { - Log.e(DEFAULT_LOG_TAG, "Failed to find " + file.getAbsolutePath(), e); - return null; - } finally { - maybeCloseAndLogError(modelFd); - } - } - - /** - * Closes the ParcelFileDescriptor, if non-null, and logs any errors that occur. - */ - private static void maybeCloseAndLogError(@Nullable ParcelFileDescriptor fd) { - if (fd == null) { - return; - } - try { - fd.close(); - } catch (IOException e) { - Log.e(DEFAULT_LOG_TAG, "Error closing file.", e); - } - } - - } - - /** - * Describes TextClassifier model files on disk. - */ - public static final class ModelFile { - public static final String LANGUAGE_INDEPENDENT = "*"; - - private final File mFile; - private final int mVersion; - private final List mSupportedLocales; - private final String mSupportedLocalesStr; - private final boolean mLanguageIndependent; - - public ModelFile(File file, int version, List supportedLocales, - String supportedLocalesStr, - boolean languageIndependent) { - mFile = Objects.requireNonNull(file); - mVersion = version; - mSupportedLocales = Objects.requireNonNull(supportedLocales); - mSupportedLocalesStr = Objects.requireNonNull(supportedLocalesStr); - mLanguageIndependent = languageIndependent; - } - - /** Returns the absolute path to the model file. */ - public String getPath() { - return mFile.getAbsolutePath(); - } - - /** Returns a name to use for id generation, effectively the name of the model file. */ - public String getName() { - return mFile.getName(); - } - - /** Returns the version tag in the model's metadata. */ - public int getVersion() { - return mVersion; - } - - /** Returns whether the language supports any language in the given ranges. */ - public boolean isAnyLanguageSupported(List languageRanges) { - Objects.requireNonNull(languageRanges); - return mLanguageIndependent || Locale.lookup(languageRanges, mSupportedLocales) != null; - } - - /** Returns an immutable lists of supported locales. */ - public List getSupportedLocales() { - return Collections.unmodifiableList(mSupportedLocales); - } - - /** Returns the original supported locals string read from the model file. */ - public String getSupportedLocalesStr() { - return mSupportedLocalesStr; - } - - /** - * Returns if this model file is preferred to the given one. - */ - public boolean isPreferredTo(@Nullable ModelFile model) { - // A model is preferred to no model. - if (model == null) { - return true; - } - - // A language-specific model is preferred to a language independent - // model. - if (!mLanguageIndependent && model.mLanguageIndependent) { - return true; - } - if (mLanguageIndependent && !model.mLanguageIndependent) { - return false; - } - - // A higher-version model is preferred. - if (mVersion > model.getVersion()) { - return true; - } - return false; - } - - @Override - public int hashCode() { - return Objects.hash(getPath()); - } - - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } - if (other instanceof ModelFile) { - final ModelFile otherModel = (ModelFile) other; - return TextUtils.equals(getPath(), otherModel.getPath()); - } - return false; - } - - @Override - public String toString() { - final StringJoiner localesJoiner = new StringJoiner(","); - for (Locale locale : mSupportedLocales) { - localesJoiner.add(locale.toLanguageTag()); - } - return String.format(Locale.US, - "ModelFile { path=%s name=%s version=%d locales=%s }", - getPath(), getName(), mVersion, localesJoiner.toString()); - } - } -} diff --git a/core/java/android/view/textclassifier/SelectionEvent.java b/core/java/android/view/textclassifier/SelectionEvent.java index 9a54544720761..6f9556b6a96e9 100644 --- a/core/java/android/view/textclassifier/SelectionEvent.java +++ b/core/java/android/view/textclassifier/SelectionEvent.java @@ -158,7 +158,6 @@ public final class SelectionEvent implements Parcelable { mEventType = in.readInt(); mEntityType = in.readString(); mWidgetVersion = in.readInt() > 0 ? in.readString() : null; - // TODO: remove mPackageName once aiai does not need it mPackageName = in.readString(); mWidgetType = in.readString(); mInvocationMethod = in.readInt(); @@ -186,7 +185,6 @@ public final class SelectionEvent implements Parcelable { if (mWidgetVersion != null) { dest.writeString(mWidgetVersion); } - // TODO: remove mPackageName once aiai does not need it dest.writeString(mPackageName); dest.writeString(mWidgetType); dest.writeInt(mInvocationMethod); @@ -406,7 +404,7 @@ public final class SelectionEvent implements Parcelable { */ @NonNull public String getPackageName() { - return mSystemTcMetadata != null ? mSystemTcMetadata.getCallingPackageName() : ""; + return mPackageName; } /** diff --git a/core/java/android/view/textclassifier/SelectionSessionLogger.java b/core/java/android/view/textclassifier/SelectionSessionLogger.java index ae9f65ba61291..e7d896eb3f23e 100644 --- a/core/java/android/view/textclassifier/SelectionSessionLogger.java +++ b/core/java/android/view/textclassifier/SelectionSessionLogger.java @@ -16,251 +16,24 @@ package android.view.textclassifier; -import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.Context; -import android.metrics.LogMaker; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; - -import java.text.BreakIterator; -import java.util.List; -import java.util.Locale; -import java.util.Objects; -import java.util.StringJoiner; /** * A helper for logging selection session events. + * * @hide */ public final class SelectionSessionLogger { - - private static final String LOG_TAG = "SelectionSessionLogger"; - static final String CLASSIFIER_ID = "androidtc"; - - private static final int START_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_START; - private static final int PREV_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_PREVIOUS; - private static final int INDEX = MetricsEvent.FIELD_SELECTION_SESSION_INDEX; - private static final int WIDGET_TYPE = MetricsEvent.FIELD_SELECTION_WIDGET_TYPE; - private static final int WIDGET_VERSION = MetricsEvent.FIELD_SELECTION_WIDGET_VERSION; - private static final int MODEL_NAME = MetricsEvent.FIELD_TEXTCLASSIFIER_MODEL; - private static final int ENTITY_TYPE = MetricsEvent.FIELD_SELECTION_ENTITY_TYPE; - private static final int SMART_START = MetricsEvent.FIELD_SELECTION_SMART_RANGE_START; - private static final int SMART_END = MetricsEvent.FIELD_SELECTION_SMART_RANGE_END; - private static final int EVENT_START = MetricsEvent.FIELD_SELECTION_RANGE_START; - private static final int EVENT_END = MetricsEvent.FIELD_SELECTION_RANGE_END; - private static final int SESSION_ID = MetricsEvent.FIELD_SELECTION_SESSION_ID; - - private static final String ZERO = "0"; - private static final String UNKNOWN = "unknown"; - - private final MetricsLogger mMetricsLogger; - - public SelectionSessionLogger() { - mMetricsLogger = new MetricsLogger(); - } - - @VisibleForTesting - public SelectionSessionLogger(@NonNull MetricsLogger metricsLogger) { - mMetricsLogger = Objects.requireNonNull(metricsLogger); - } - - /** Emits a selection event to the logs. */ - public void writeEvent(@NonNull SelectionEvent event) { - Objects.requireNonNull(event); - final LogMaker log = new LogMaker(MetricsEvent.TEXT_SELECTION_SESSION) - .setType(getLogType(event)) - .setSubtype(getLogSubType(event)) - .setPackageName(event.getPackageName()) - .addTaggedData(START_EVENT_DELTA, event.getDurationSinceSessionStart()) - .addTaggedData(PREV_EVENT_DELTA, event.getDurationSincePreviousEvent()) - .addTaggedData(INDEX, event.getEventIndex()) - .addTaggedData(WIDGET_TYPE, event.getWidgetType()) - .addTaggedData(WIDGET_VERSION, event.getWidgetVersion()) - .addTaggedData(ENTITY_TYPE, event.getEntityType()) - .addTaggedData(EVENT_START, event.getStart()) - .addTaggedData(EVENT_END, event.getEnd()); - if (isPlatformLocalTextClassifierSmartSelection(event.getResultId())) { - // Ensure result id and smart indices are only set for events with smart selection from - // the platform's textclassifier. - log.addTaggedData(MODEL_NAME, SignatureParser.getModelName(event.getResultId())) - .addTaggedData(SMART_START, event.getSmartStart()) - .addTaggedData(SMART_END, event.getSmartEnd()); - } - if (event.getSessionId() != null) { - log.addTaggedData(SESSION_ID, event.getSessionId().getValue()); - } - mMetricsLogger.write(log); - debugLog(log); - } - - private static int getLogType(SelectionEvent event) { - switch (event.getEventType()) { - case SelectionEvent.ACTION_OVERTYPE: - return MetricsEvent.ACTION_TEXT_SELECTION_OVERTYPE; - case SelectionEvent.ACTION_COPY: - return MetricsEvent.ACTION_TEXT_SELECTION_COPY; - case SelectionEvent.ACTION_PASTE: - return MetricsEvent.ACTION_TEXT_SELECTION_PASTE; - case SelectionEvent.ACTION_CUT: - return MetricsEvent.ACTION_TEXT_SELECTION_CUT; - case SelectionEvent.ACTION_SHARE: - return MetricsEvent.ACTION_TEXT_SELECTION_SHARE; - case SelectionEvent.ACTION_SMART_SHARE: - return MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE; - case SelectionEvent.ACTION_DRAG: - return MetricsEvent.ACTION_TEXT_SELECTION_DRAG; - case SelectionEvent.ACTION_ABANDON: - return MetricsEvent.ACTION_TEXT_SELECTION_ABANDON; - case SelectionEvent.ACTION_OTHER: - return MetricsEvent.ACTION_TEXT_SELECTION_OTHER; - case SelectionEvent.ACTION_SELECT_ALL: - return MetricsEvent.ACTION_TEXT_SELECTION_SELECT_ALL; - case SelectionEvent.ACTION_RESET: - return MetricsEvent.ACTION_TEXT_SELECTION_RESET; - case SelectionEvent.EVENT_SELECTION_STARTED: - return MetricsEvent.ACTION_TEXT_SELECTION_START; - case SelectionEvent.EVENT_SELECTION_MODIFIED: - return MetricsEvent.ACTION_TEXT_SELECTION_MODIFY; - case SelectionEvent.EVENT_SMART_SELECTION_SINGLE: - return MetricsEvent.ACTION_TEXT_SELECTION_SMART_SINGLE; - case SelectionEvent.EVENT_SMART_SELECTION_MULTI: - return MetricsEvent.ACTION_TEXT_SELECTION_SMART_MULTI; - case SelectionEvent.EVENT_AUTO_SELECTION: - return MetricsEvent.ACTION_TEXT_SELECTION_AUTO; - default: - return MetricsEvent.VIEW_UNKNOWN; - } - } - - private static int getLogSubType(SelectionEvent event) { - switch (event.getInvocationMethod()) { - case SelectionEvent.INVOCATION_MANUAL: - return MetricsEvent.TEXT_SELECTION_INVOCATION_MANUAL; - case SelectionEvent.INVOCATION_LINK: - return MetricsEvent.TEXT_SELECTION_INVOCATION_LINK; - default: - return MetricsEvent.TEXT_SELECTION_INVOCATION_UNKNOWN; - } - } - - private static String getLogTypeString(int logType) { - switch (logType) { - case MetricsEvent.ACTION_TEXT_SELECTION_OVERTYPE: - return "OVERTYPE"; - case MetricsEvent.ACTION_TEXT_SELECTION_COPY: - return "COPY"; - case MetricsEvent.ACTION_TEXT_SELECTION_PASTE: - return "PASTE"; - case MetricsEvent.ACTION_TEXT_SELECTION_CUT: - return "CUT"; - case MetricsEvent.ACTION_TEXT_SELECTION_SHARE: - return "SHARE"; - case MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE: - return "SMART_SHARE"; - case MetricsEvent.ACTION_TEXT_SELECTION_DRAG: - return "DRAG"; - case MetricsEvent.ACTION_TEXT_SELECTION_ABANDON: - return "ABANDON"; - case MetricsEvent.ACTION_TEXT_SELECTION_OTHER: - return "OTHER"; - case MetricsEvent.ACTION_TEXT_SELECTION_SELECT_ALL: - return "SELECT_ALL"; - case MetricsEvent.ACTION_TEXT_SELECTION_RESET: - return "RESET"; - case MetricsEvent.ACTION_TEXT_SELECTION_START: - return "SELECTION_STARTED"; - case MetricsEvent.ACTION_TEXT_SELECTION_MODIFY: - return "SELECTION_MODIFIED"; - case MetricsEvent.ACTION_TEXT_SELECTION_SMART_SINGLE: - return "SMART_SELECTION_SINGLE"; - case MetricsEvent.ACTION_TEXT_SELECTION_SMART_MULTI: - return "SMART_SELECTION_MULTI"; - case MetricsEvent.ACTION_TEXT_SELECTION_AUTO: - return "AUTO_SELECTION"; - default: - return UNKNOWN; - } - } - - private static String getLogSubTypeString(int logSubType) { - switch (logSubType) { - case MetricsEvent.TEXT_SELECTION_INVOCATION_MANUAL: - return "MANUAL"; - case MetricsEvent.TEXT_SELECTION_INVOCATION_LINK: - return "LINK"; - default: - return UNKNOWN; - } - } + // Keep this in sync with the ResultIdUtils in libtextclassifier. + private static final String CLASSIFIER_ID = "androidtc"; static boolean isPlatformLocalTextClassifierSmartSelection(String signature) { return SelectionSessionLogger.CLASSIFIER_ID.equals( SelectionSessionLogger.SignatureParser.getClassifierId(signature)); } - private static void debugLog(LogMaker log) { - if (!Log.ENABLE_FULL_LOGGING) { - return; - } - final String widgetType = Objects.toString(log.getTaggedData(WIDGET_TYPE), UNKNOWN); - final String widgetVersion = Objects.toString(log.getTaggedData(WIDGET_VERSION), ""); - final String widget = widgetVersion.isEmpty() - ? widgetType : widgetType + "-" + widgetVersion; - final int index = Integer.parseInt(Objects.toString(log.getTaggedData(INDEX), ZERO)); - if (log.getType() == MetricsEvent.ACTION_TEXT_SELECTION_START) { - String sessionId = Objects.toString(log.getTaggedData(SESSION_ID), ""); - sessionId = sessionId.substring(sessionId.lastIndexOf("-") + 1); - Log.d(LOG_TAG, String.format("New selection session: %s (%s)", widget, sessionId)); - } - - final String model = Objects.toString(log.getTaggedData(MODEL_NAME), UNKNOWN); - final String entity = Objects.toString(log.getTaggedData(ENTITY_TYPE), UNKNOWN); - final String type = getLogTypeString(log.getType()); - final String subType = getLogSubTypeString(log.getSubtype()); - final int smartStart = Integer.parseInt( - Objects.toString(log.getTaggedData(SMART_START), ZERO)); - final int smartEnd = Integer.parseInt( - Objects.toString(log.getTaggedData(SMART_END), ZERO)); - final int eventStart = Integer.parseInt( - Objects.toString(log.getTaggedData(EVENT_START), ZERO)); - final int eventEnd = Integer.parseInt( - Objects.toString(log.getTaggedData(EVENT_END), ZERO)); - - Log.v(LOG_TAG, - String.format(Locale.US, "%2d: %s/%s/%s, range=%d,%d - smart_range=%d,%d (%s/%s)", - index, type, subType, entity, eventStart, eventEnd, smartStart, smartEnd, - widget, model)); - } - - /** - * Returns a token iterator for tokenizing text for logging purposes. - */ - public static BreakIterator getTokenIterator(@NonNull Locale locale) { - return BreakIterator.getWordInstance(Objects.requireNonNull(locale)); - } - - /** - * Creates a string id that may be used to identify a TextClassifier result. - */ - public static String createId( - String text, int start, int end, Context context, int modelVersion, - List locales) { - Objects.requireNonNull(text); - Objects.requireNonNull(context); - Objects.requireNonNull(locales); - final StringJoiner localesJoiner = new StringJoiner(","); - for (Locale locale : locales) { - localesJoiner.add(locale.toLanguageTag()); - } - final String modelName = String.format(Locale.US, "%s_v%d", localesJoiner.toString(), - modelVersion); - final int hash = Objects.hash(text, start, end, context.getPackageName()); - return SignatureParser.createSignature(CLASSIFIER_ID, modelName, hash); - } - /** * Helper for creating and parsing string ids for * {@link android.view.textclassifier.TextClassifierImpl}. @@ -268,10 +41,6 @@ public final class SelectionSessionLogger { @VisibleForTesting public static final class SignatureParser { - static String createSignature(String classifierId, String modelName, int hash) { - return String.format(Locale.US, "%s|%s|%d", classifierId, modelName, hash); - } - static String getClassifierId(@Nullable String signature) { if (signature == null) { return ""; @@ -282,29 +51,5 @@ public final class SelectionSessionLogger { } return ""; } - - static String getModelName(@Nullable String signature) { - if (signature == null) { - return ""; - } - final int start = signature.indexOf("|") + 1; - final int end = signature.indexOf("|", start); - if (start >= 1 && end >= start) { - return signature.substring(start, end); - } - return ""; - } - - static int getHash(@Nullable String signature) { - if (signature == null) { - return 0; - } - final int index1 = signature.indexOf("|"); - final int index2 = signature.indexOf("|", index1); - if (index2 > 0) { - return Integer.parseInt(signature.substring(index2)); - } - return 0; - } } } diff --git a/core/java/android/view/textclassifier/SystemTextClassifier.java b/core/java/android/view/textclassifier/SystemTextClassifier.java index 86ef4e1039907..8eac1c1520c08 100644 --- a/core/java/android/view/textclassifier/SystemTextClassifier.java +++ b/core/java/android/view/textclassifier/SystemTextClassifier.java @@ -45,7 +45,7 @@ import java.util.concurrent.TimeUnit; @VisibleForTesting(visibility = Visibility.PACKAGE) public final class SystemTextClassifier implements TextClassifier { - private static final String LOG_TAG = "SystemTextClassifier"; + private static final String LOG_TAG = TextClassifier.LOG_TAG; private final ITextClassifierService mManagerService; private final TextClassificationConstants mSettings; diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java index 8b9d12916595e..3aed32adea1cd 100644 --- a/core/java/android/view/textclassifier/TextClassification.java +++ b/core/java/android/view/textclassifier/TextClassification.java @@ -44,8 +44,6 @@ import android.view.textclassifier.TextClassifier.Utils; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; -import com.google.android.textclassifier.AnnotatorModel; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.time.ZonedDateTime; @@ -327,9 +325,6 @@ public final class TextClassification implements Parcelable { @NonNull private List mActions = new ArrayList<>(); @NonNull private final Map mTypeScoreMap = new ArrayMap<>(); - @NonNull - private final Map mClassificationResults = - new ArrayMap<>(); @Nullable private String mText; @Nullable private Drawable mLegacyIcon; @Nullable private String mLegacyLabel; @@ -362,36 +357,7 @@ public final class TextClassification implements Parcelable { public Builder setEntityType( @NonNull @EntityType String type, @FloatRange(from = 0.0, to = 1.0) float confidenceScore) { - setEntityType(type, confidenceScore, null); - return this; - } - - /** - * @see #setEntityType(String, float) - * - * @hide - */ - @NonNull - public Builder setEntityType(AnnotatorModel.ClassificationResult classificationResult) { - setEntityType( - classificationResult.getCollection(), - classificationResult.getScore(), - classificationResult); - return this; - } - - /** - * @see #setEntityType(String, float) - * - * @hide - */ - @NonNull - private Builder setEntityType( - @NonNull @EntityType String type, - @FloatRange(from = 0.0, to = 1.0) float confidenceScore, - @Nullable AnnotatorModel.ClassificationResult classificationResult) { mTypeScoreMap.put(type, confidenceScore); - mClassificationResults.put(type, classificationResult); return this; } @@ -517,25 +483,7 @@ public final class TextClassification implements Parcelable { EntityConfidence entityConfidence = new EntityConfidence(mTypeScoreMap); return new TextClassification(mText, mLegacyIcon, mLegacyLabel, mLegacyIntent, mLegacyOnClickListener, mActions, entityConfidence, mId, - buildExtras(entityConfidence)); - } - - private Bundle buildExtras(EntityConfidence entityConfidence) { - final Bundle extras = mExtras == null ? new Bundle() : mExtras; - if (mActionIntents.stream().anyMatch(Objects::nonNull)) { - ExtrasUtils.putActionsIntents(extras, mActionIntents); - } - if (mForeignLanguageExtra != null) { - ExtrasUtils.putForeignLanguageExtra(extras, mForeignLanguageExtra); - } - List sortedTypes = entityConfidence.getEntities(); - ArrayList sortedEntities = new ArrayList<>(); - for (String type : sortedTypes) { - sortedEntities.add(mClassificationResults.get(type)); - } - ExtrasUtils.putEntities( - extras, sortedEntities.toArray(new AnnotatorModel.ClassificationResult[0])); - return extras.isEmpty() ? Bundle.EMPTY : extras; + mExtras == null ? Bundle.EMPTY : mExtras); } } diff --git a/core/java/android/view/textclassifier/TextClassificationConstants.java b/core/java/android/view/textclassifier/TextClassificationConstants.java index 3d5ac58e77048..adb6fea91799e 100644 --- a/core/java/android/view/textclassifier/TextClassificationConstants.java +++ b/core/java/android/view/textclassifier/TextClassificationConstants.java @@ -17,16 +17,11 @@ package android.view.textclassifier; import android.annotation.Nullable; -import android.content.Context; import android.provider.DeviceConfig; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - /** * TextClassifier specific settings. * @@ -41,8 +36,6 @@ import java.util.List; */ // TODO: Rename to TextClassifierSettings. public final class TextClassificationConstants { - private static final String DELIMITER = ":"; - /** * Whether the smart linkify feature is enabled. */ @@ -60,7 +53,6 @@ public final class TextClassificationConstants { * Enable smart selection without a visible UI changes. */ private static final String MODEL_DARK_LAUNCH_ENABLED = "model_dark_launch_enabled"; - /** * Whether the smart selection feature is enabled. */ @@ -74,89 +66,11 @@ public final class TextClassificationConstants { */ private static final String SMART_SELECT_ANIMATION_ENABLED = "smart_select_animation_enabled"; - /** - * Max length of text that suggestSelection can accept. - */ - @VisibleForTesting - static final String SUGGEST_SELECTION_MAX_RANGE_LENGTH = - "suggest_selection_max_range_length"; - /** - * Max length of text that classifyText can accept. - */ - private static final String CLASSIFY_TEXT_MAX_RANGE_LENGTH = "classify_text_max_range_length"; /** * Max length of text that generateLinks can accept. */ - private static final String GENERATE_LINKS_MAX_TEXT_LENGTH = "generate_links_max_text_length"; - /** - * Sampling rate for generateLinks logging. - */ - private static final String GENERATE_LINKS_LOG_SAMPLE_RATE = - "generate_links_log_sample_rate"; - /** - * A colon(:) separated string that specifies the default entities types for - * generateLinks when hint is not given. - */ @VisibleForTesting - static final String ENTITY_LIST_DEFAULT = "entity_list_default"; - /** - * A colon(:) separated string that specifies the default entities types for - * generateLinks when the text is in a not editable UI widget. - */ - private static final String ENTITY_LIST_NOT_EDITABLE = "entity_list_not_editable"; - /** - * A colon(:) separated string that specifies the default entities types for - * generateLinks when the text is in an editable UI widget. - */ - private static final String ENTITY_LIST_EDITABLE = "entity_list_editable"; - /** - * A colon(:) separated string that specifies the default action types for - * suggestConversationActions when the suggestions are used in an app. - */ - private static final String IN_APP_CONVERSATION_ACTION_TYPES_DEFAULT = - "in_app_conversation_action_types_default"; - /** - * A colon(:) separated string that specifies the default action types for - * suggestConversationActions when the suggestions are used in a notification. - */ - private static final String NOTIFICATION_CONVERSATION_ACTION_TYPES_DEFAULT = - "notification_conversation_action_types_default"; - /** - * Threshold to accept a suggested language from LangID model. - */ - @VisibleForTesting - static final String LANG_ID_THRESHOLD_OVERRIDE = "lang_id_threshold_override"; - /** - * Whether to enable {@link android.view.textclassifier.TemplateIntentFactory}. - */ - private static final String TEMPLATE_INTENT_FACTORY_ENABLED = "template_intent_factory_enabled"; - /** - * Whether to enable "translate" action in classifyText. - */ - private static final String TRANSLATE_IN_CLASSIFICATION_ENABLED = - "translate_in_classification_enabled"; - /** - * Whether to detect the languages of the text in request by using langId for the native - * model. - */ - private static final String DETECT_LANGUAGES_FROM_TEXT_ENABLED = - "detect_languages_from_text_enabled"; - /** - * A colon(:) separated string that specifies the configuration to use when including - * surrounding context text in language detection queries. - *

- * Format= minimumTextSize:penalizeRatio:textScoreRatio - *

- * e.g. 20:1.0:0.4 - *

- * Accept all text lengths with minimumTextSize=0 - *

- * Reject all text less than minimumTextSize with penalizeRatio=0 - * @see {@code TextClassifierImpl#detectLanguages(String, int, int)} for reference. - */ - @VisibleForTesting - static final String LANG_ID_CONTEXT_SETTINGS = "lang_id_context_settings"; - + static final String GENERATE_LINKS_MAX_TEXT_LENGTH = "generate_links_max_text_length"; /** * The TextClassifierService which would like to use. Example of setting the package: *

@@ -168,16 +82,6 @@ public final class TextClassificationConstants {
     static final String TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE =
             "textclassifier_service_package_override";
 
-    /**
-     * Whether to use the default system text classifier as the default text classifier
-     * implementation. The local text classifier is used if it is {@code false}.
-     *
-     * @see android.service.textclassifier.TextClassifierService#getDefaultTextClassifierImplementation(Context)
-     */
-    // TODO: Once the system health experiment is done, remove this together with local TC.
-    private static final String USE_DEFAULT_SYSTEM_TEXT_CLASSIFIER_AS_DEFAULT_IMPL =
-            "use_default_system_text_classifier_as_default_impl";
-
     private static final String DEFAULT_TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE = null;
     private static final boolean LOCAL_TEXT_CLASSIFIER_ENABLED_DEFAULT = true;
     private static final boolean SYSTEM_TEXT_CLASSIFIER_ENABLED_DEFAULT = true;
@@ -186,42 +90,7 @@ public final class TextClassificationConstants {
     private static final boolean SMART_TEXT_SHARE_ENABLED_DEFAULT = true;
     private static final boolean SMART_LINKIFY_ENABLED_DEFAULT = true;
     private static final boolean SMART_SELECT_ANIMATION_ENABLED_DEFAULT = true;
-    private static final int SUGGEST_SELECTION_MAX_RANGE_LENGTH_DEFAULT = 10 * 1000;
-    private static final int CLASSIFY_TEXT_MAX_RANGE_LENGTH_DEFAULT = 10 * 1000;
     private static final int GENERATE_LINKS_MAX_TEXT_LENGTH_DEFAULT = 100 * 1000;
-    private static final int GENERATE_LINKS_LOG_SAMPLE_RATE_DEFAULT = 100;
-    private static final List ENTITY_LIST_DEFAULT_VALUE = Arrays.asList(
-            TextClassifier.TYPE_ADDRESS,
-            TextClassifier.TYPE_EMAIL,
-            TextClassifier.TYPE_PHONE,
-            TextClassifier.TYPE_URL,
-            TextClassifier.TYPE_DATE,
-            TextClassifier.TYPE_DATE_TIME,
-            TextClassifier.TYPE_FLIGHT_NUMBER);
-    private static final List CONVERSATION_ACTIONS_TYPES_DEFAULT_VALUES = Arrays.asList(
-            ConversationAction.TYPE_TEXT_REPLY,
-            ConversationAction.TYPE_CREATE_REMINDER,
-            ConversationAction.TYPE_CALL_PHONE,
-            ConversationAction.TYPE_OPEN_URL,
-            ConversationAction.TYPE_SEND_EMAIL,
-            ConversationAction.TYPE_SEND_SMS,
-            ConversationAction.TYPE_TRACK_FLIGHT,
-            ConversationAction.TYPE_VIEW_CALENDAR,
-            ConversationAction.TYPE_VIEW_MAP,
-            ConversationAction.TYPE_ADD_CONTACT,
-            ConversationAction.TYPE_COPY);
-    /**
-     * < 0  : Not set. Use value from LangId model.
-     * 0 - 1: Override value in LangId model.
-     *
-     * @see EntityConfidence
-     */
-    private static final float LANG_ID_THRESHOLD_OVERRIDE_DEFAULT = -1f;
-    private static final boolean TEMPLATE_INTENT_FACTORY_ENABLED_DEFAULT = true;
-    private static final boolean TRANSLATE_IN_CLASSIFICATION_ENABLED_DEFAULT = true;
-    private static final boolean DETECT_LANGUAGES_FROM_TEXT_ENABLED_DEFAULT = true;
-    private static final float[] LANG_ID_CONTEXT_SETTINGS_DEFAULT = new float[]{20f, 1.0f, 0.4f};
-    private static final boolean USE_DEFAULT_SYSTEM_TEXT_CLASSIFIER_AS_DEFAULT_IMPL_DEFAULT = true;
 
     @Nullable
     public String getTextClassifierServicePackageOverride() {
@@ -266,119 +135,20 @@ public final class TextClassificationConstants {
                 SMART_SELECT_ANIMATION_ENABLED, SMART_SELECT_ANIMATION_ENABLED_DEFAULT);
     }
 
-    public int getSuggestSelectionMaxRangeLength() {
-        return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
-                SUGGEST_SELECTION_MAX_RANGE_LENGTH, SUGGEST_SELECTION_MAX_RANGE_LENGTH_DEFAULT);
-    }
-
-    public int getClassifyTextMaxRangeLength() {
-        return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
-                CLASSIFY_TEXT_MAX_RANGE_LENGTH, CLASSIFY_TEXT_MAX_RANGE_LENGTH_DEFAULT);
-    }
-
     public int getGenerateLinksMaxTextLength() {
         return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
                 GENERATE_LINKS_MAX_TEXT_LENGTH, GENERATE_LINKS_MAX_TEXT_LENGTH_DEFAULT);
     }
 
-    public int getGenerateLinksLogSampleRate() {
-        return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
-                GENERATE_LINKS_LOG_SAMPLE_RATE, GENERATE_LINKS_LOG_SAMPLE_RATE_DEFAULT);
-    }
-
-    public List getEntityListDefault() {
-        return getDeviceConfigStringList(ENTITY_LIST_DEFAULT, ENTITY_LIST_DEFAULT_VALUE);
-    }
-
-    public List getEntityListNotEditable() {
-        return getDeviceConfigStringList(ENTITY_LIST_NOT_EDITABLE, ENTITY_LIST_DEFAULT_VALUE);
-    }
-
-    public List getEntityListEditable() {
-        return getDeviceConfigStringList(ENTITY_LIST_EDITABLE, ENTITY_LIST_DEFAULT_VALUE);
-    }
-
-    public List getInAppConversationActionTypes() {
-        return getDeviceConfigStringList(
-                IN_APP_CONVERSATION_ACTION_TYPES_DEFAULT,
-                CONVERSATION_ACTIONS_TYPES_DEFAULT_VALUES);
-    }
-
-    public List getNotificationConversationActionTypes() {
-        return getDeviceConfigStringList(
-                NOTIFICATION_CONVERSATION_ACTION_TYPES_DEFAULT,
-                CONVERSATION_ACTIONS_TYPES_DEFAULT_VALUES);
-    }
-
-    public float getLangIdThresholdOverride() {
-        return DeviceConfig.getFloat(
-                DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
-                LANG_ID_THRESHOLD_OVERRIDE,
-                LANG_ID_THRESHOLD_OVERRIDE_DEFAULT);
-    }
-
-    public boolean isTemplateIntentFactoryEnabled() {
-        return DeviceConfig.getBoolean(
-                DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
-                TEMPLATE_INTENT_FACTORY_ENABLED,
-                TEMPLATE_INTENT_FACTORY_ENABLED_DEFAULT);
-    }
-
-    public boolean isTranslateInClassificationEnabled() {
-        return DeviceConfig.getBoolean(
-                DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
-                TRANSLATE_IN_CLASSIFICATION_ENABLED,
-                TRANSLATE_IN_CLASSIFICATION_ENABLED_DEFAULT);
-    }
-
-    public boolean isDetectLanguagesFromTextEnabled() {
-        return DeviceConfig.getBoolean(
-                DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
-                DETECT_LANGUAGES_FROM_TEXT_ENABLED,
-                DETECT_LANGUAGES_FROM_TEXT_ENABLED_DEFAULT);
-    }
-
-    public float[] getLangIdContextSettings() {
-        return getDeviceConfigFloatArray(
-                LANG_ID_CONTEXT_SETTINGS, LANG_ID_CONTEXT_SETTINGS_DEFAULT);
-    }
-
-    public boolean getUseDefaultTextClassifierAsDefaultImplementation() {
-        return DeviceConfig.getBoolean(
-                DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
-                USE_DEFAULT_SYSTEM_TEXT_CLASSIFIER_AS_DEFAULT_IMPL,
-                USE_DEFAULT_SYSTEM_TEXT_CLASSIFIER_AS_DEFAULT_IMPL_DEFAULT);
-    }
-
     void dump(IndentingPrintWriter pw) {
         pw.println("TextClassificationConstants:");
         pw.increaseIndent();
-        pw.printPair("classify_text_max_range_length", getClassifyTextMaxRangeLength())
-                .println();
-        pw.printPair("detect_languages_from_text_enabled", isDetectLanguagesFromTextEnabled())
-                .println();
-        pw.printPair("entity_list_default", getEntityListDefault())
-                .println();
-        pw.printPair("entity_list_editable", getEntityListEditable())
-                .println();
-        pw.printPair("entity_list_not_editable", getEntityListNotEditable())
-                .println();
-        pw.printPair("generate_links_log_sample_rate", getGenerateLinksLogSampleRate())
-                .println();
         pw.printPair("generate_links_max_text_length", getGenerateLinksMaxTextLength())
                 .println();
-        pw.printPair("in_app_conversation_action_types_default", getInAppConversationActionTypes())
-                .println();
-        pw.printPair("lang_id_context_settings", Arrays.toString(getLangIdContextSettings()))
-                .println();
-        pw.printPair("lang_id_threshold_override", getLangIdThresholdOverride())
-                .println();
         pw.printPair("local_textclassifier_enabled", isLocalTextClassifierEnabled())
                 .println();
         pw.printPair("model_dark_launch_enabled", isModelDarkLaunchEnabled())
                 .println();
-        pw.printPair("notification_conversation_action_types_default",
-                getNotificationConversationActionTypes()).println();
         pw.printPair("smart_linkify_enabled", isSmartLinkifyEnabled())
                 .println();
         pw.printPair("smart_select_animation_enabled", isSmartSelectionAnimationEnabled())
@@ -387,57 +157,10 @@ public final class TextClassificationConstants {
                 .println();
         pw.printPair("smart_text_share_enabled", isSmartTextShareEnabled())
                 .println();
-        pw.printPair("suggest_selection_max_range_length", getSuggestSelectionMaxRangeLength())
-                .println();
         pw.printPair("system_textclassifier_enabled", isSystemTextClassifierEnabled())
                 .println();
-        pw.printPair("template_intent_factory_enabled", isTemplateIntentFactoryEnabled())
-                .println();
-        pw.printPair("translate_in_classification_enabled", isTranslateInClassificationEnabled())
-                .println();
         pw.printPair("textclassifier_service_package_override",
                 getTextClassifierServicePackageOverride()).println();
-        pw.printPair("use_default_system_text_classifier_as_default_impl",
-                getUseDefaultTextClassifierAsDefaultImplementation()).println();
         pw.decreaseIndent();
     }
-
-    private static List getDeviceConfigStringList(String key, List defaultValue) {
-        return parse(
-                DeviceConfig.getString(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, key, null),
-                defaultValue);
-    }
-
-    private static float[] getDeviceConfigFloatArray(String key, float[] defaultValue) {
-        return parse(
-                DeviceConfig.getString(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, key, null),
-                defaultValue);
-    }
-
-    private static List parse(@Nullable String listStr, List defaultValue) {
-        if (listStr != null) {
-            return Collections.unmodifiableList(Arrays.asList(listStr.split(DELIMITER)));
-        }
-        return defaultValue;
-    }
-
-    private static float[] parse(@Nullable String arrayStr, float[] defaultValue) {
-        if (arrayStr != null) {
-            final String[] split = arrayStr.split(DELIMITER);
-            if (split.length != defaultValue.length) {
-                return defaultValue;
-            }
-            final float[] result = new float[split.length];
-            for (int i = 0; i < split.length; i++) {
-                try {
-                    result[i] = Float.parseFloat(split[i]);
-                } catch (NumberFormatException e) {
-                    return defaultValue;
-                }
-            }
-            return result;
-        } else {
-            return defaultValue;
-        }
-    }
 }
\ No newline at end of file
diff --git a/core/java/android/view/textclassifier/TextClassificationManager.java b/core/java/android/view/textclassifier/TextClassificationManager.java
index dfbec9b67027e..fa4f7d6d32da5 100644
--- a/core/java/android/view/textclassifier/TextClassificationManager.java
+++ b/core/java/android/view/textclassifier/TextClassificationManager.java
@@ -19,21 +19,15 @@ package android.view.textclassifier;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemService;
-import android.app.ActivityThread;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.ServiceManager;
-import android.provider.DeviceConfig;
-import android.provider.DeviceConfig.Properties;
 import android.view.textclassifier.TextClassifier.TextClassifierType;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
 
-import java.lang.ref.WeakReference;
 import java.util.Objects;
-import java.util.Set;
 
 /**
  * Interface to the text classification service.
@@ -41,7 +35,7 @@ import java.util.Set;
 @SystemService(Context.TEXT_CLASSIFICATION_SERVICE)
 public final class TextClassificationManager {
 
-    private static final String LOG_TAG = "TextClassificationManager";
+    private static final String LOG_TAG = TextClassifier.LOG_TAG;
 
     private static final TextClassificationConstants sDefaultSettings =
             new TextClassificationConstants();
@@ -52,15 +46,11 @@ public final class TextClassificationManager {
                     classificationContext, getTextClassifier());
 
     private final Context mContext;
-    private final SettingsObserver mSettingsObserver;
 
     @GuardedBy("mLock")
     @Nullable
     private TextClassifier mCustomTextClassifier;
     @GuardedBy("mLock")
-    @Nullable
-    private TextClassifier mLocalTextClassifier;
-    @GuardedBy("mLock")
     private TextClassificationSessionFactory mSessionFactory;
     @GuardedBy("mLock")
     private TextClassificationConstants mSettings;
@@ -69,7 +59,6 @@ public final class TextClassificationManager {
     public TextClassificationManager(Context context) {
         mContext = Objects.requireNonNull(context);
         mSessionFactory = mDefaultSessionFactory;
-        mSettingsObserver = new SettingsObserver(this);
     }
 
     /**
@@ -112,7 +101,7 @@ public final class TextClassificationManager {
      *
      * @see TextClassifier#LOCAL
      * @see TextClassifier#SYSTEM
-     * @see TextClassifier#DEFAULT_SERVICE
+     * @see TextClassifier#DEFAULT_SYSTEM
      * @hide
      */
     @UnsupportedAppUsage
@@ -189,28 +178,17 @@ public final class TextClassificationManager {
         }
     }
 
-    @Override
-    protected void finalize() throws Throwable {
-        try {
-            // Note that fields could be null if the constructor threw.
-            if (mSettingsObserver != null) {
-                DeviceConfig.removeOnPropertiesChangedListener(mSettingsObserver);
-            }
-        } finally {
-            super.finalize();
-        }
-    }
-
     /** @hide */
     private TextClassifier getSystemTextClassifier(@TextClassifierType int type) {
         synchronized (mLock) {
             if (getSettings().isSystemTextClassifierEnabled()) {
                 try {
-                    Log.d(LOG_TAG, "Initializing SystemTextClassifier, type = " + type);
+                    Log.d(LOG_TAG, "Initializing SystemTextClassifier, type = "
+                            + TextClassifier.typeToString(type));
                     return new SystemTextClassifier(
                             mContext,
                             getSettings(),
-                            /* useDefault= */ type == TextClassifier.DEFAULT_SERVICE);
+                            /* useDefault= */ type == TextClassifier.DEFAULT_SYSTEM);
                 } catch (ServiceManager.ServiceNotFoundException e) {
                     Log.e(LOG_TAG, "Could not initialize SystemTextClassifier", e);
                 }
@@ -224,49 +202,13 @@ public final class TextClassificationManager {
      */
     @NonNull
     private TextClassifier getLocalTextClassifier() {
-        synchronized (mLock) {
-            if (mLocalTextClassifier == null) {
-                if (getSettings().isLocalTextClassifierEnabled()) {
-                    mLocalTextClassifier =
-                            new TextClassifierImpl(mContext, getSettings(), TextClassifier.NO_OP);
-                } else {
-                    Log.d(LOG_TAG, "Local TextClassifier disabled");
-                    mLocalTextClassifier = TextClassifier.NO_OP;
-                }
-            }
-            return mLocalTextClassifier;
-        }
-    }
-
-    /** @hide */
-    @VisibleForTesting
-    public void invalidateForTesting() {
-        invalidate();
-    }
-
-    private void invalidate() {
-        synchronized (mLock) {
-            mSettings = null;
-            invalidateTextClassifiers();
-        }
-    }
-
-    private void invalidateTextClassifiers() {
-        synchronized (mLock) {
-            mLocalTextClassifier = null;
-        }
-    }
-
-    Context getApplicationContext() {
-        return mContext.getApplicationContext() != null
-                ? mContext.getApplicationContext()
-                : mContext;
+        Log.d(LOG_TAG, "Local text-classifier not supported. Returning a no-op text-classifier.");
+        return TextClassifier.NO_OP;
     }
 
     /** @hide **/
     public void dump(IndentingPrintWriter pw) {
-        getLocalTextClassifier().dump(pw);
-        getSystemTextClassifier(TextClassifier.DEFAULT_SERVICE).dump(pw);
+        getSystemTextClassifier(TextClassifier.DEFAULT_SYSTEM).dump(pw);
         getSystemTextClassifier(TextClassifier.SYSTEM).dump(pw);
         getSettings().dump(pw);
     }
@@ -283,31 +225,4 @@ public final class TextClassificationManager {
             return sDefaultSettings;
         }
     }
-
-    private static final class SettingsObserver implements
-            DeviceConfig.OnPropertiesChangedListener {
-
-        private final WeakReference mTcm;
-
-        SettingsObserver(TextClassificationManager tcm) {
-            mTcm = new WeakReference<>(tcm);
-            DeviceConfig.addOnPropertiesChangedListener(
-                    DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
-                    ActivityThread.currentApplication().getMainExecutor(),
-                    this);
-        }
-
-        @Override
-        public void onPropertiesChanged(Properties properties) {
-            final TextClassificationManager tcm = mTcm.get();
-            if (tcm != null) {
-                final Set keys = properties.getKeyset();
-                if (keys.contains(TextClassificationConstants.SYSTEM_TEXT_CLASSIFIER_ENABLED)
-                        || keys.contains(
-                        TextClassificationConstants.LOCAL_TEXT_CLASSIFIER_ENABLED)) {
-                    tcm.invalidateTextClassifiers();
-                }
-            }
-        }
-    }
 }
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index 2cc226d5a6019..6d5077a99c62d 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -61,19 +61,32 @@ import java.util.Set;
 public interface TextClassifier {
 
     /** @hide */
-    String DEFAULT_LOG_TAG = "androidtc";
+    String LOG_TAG = "androidtc";
 
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(value = {LOCAL, SYSTEM, DEFAULT_SERVICE})
+    @IntDef(value = {LOCAL, SYSTEM, DEFAULT_SYSTEM})
     @interface TextClassifierType {}  // TODO: Expose as system APIs.
     /** Specifies a TextClassifier that runs locally in the app's process. @hide */
     int LOCAL = 0;
     /** Specifies a TextClassifier that runs in the system process and serves all apps. @hide */
     int SYSTEM = 1;
     /** Specifies the default TextClassifier that runs in the system process. @hide */
-    int DEFAULT_SERVICE = 2;
+    int DEFAULT_SYSTEM = 2;
+
+    /** @hide */
+    static String typeToString(@TextClassifierType int type) {
+        switch (type) {
+            case LOCAL:
+                return "Local";
+            case SYSTEM:
+                return "System";
+            case DEFAULT_SYSTEM:
+                return "Default system";
+        }
+        return "Unknown";
+    }
 
     /** The TextClassifier failed to run. */
     String TYPE_UNKNOWN = "";
@@ -776,7 +789,7 @@ public interface TextClassifier {
 
         static void checkMainThread() {
             if (Looper.myLooper() == Looper.getMainLooper()) {
-                Log.w(DEFAULT_LOG_TAG, "TextClassifier called on main thread");
+                Log.w(LOG_TAG, "TextClassifier called on main thread");
             }
         }
     }
diff --git a/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java b/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java
deleted file mode 100644
index 8162699a74c68..0000000000000
--- a/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.view.textclassifier;
-
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXTCLASSIFIER_MODEL;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_SCORE;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_SECOND_ENTITY_TYPE;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_SESSION_ID;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_THIRD_ENTITY_TYPE;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_WIDGET_TYPE;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_WIDGET_VERSION;
-
-import android.metrics.LogMaker;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import java.util.Objects;
-
-
-/**
- * Log {@link TextClassifierEvent} by using Tron, only support language detection and
- * conversation actions.
- *
- * @hide
- */
-public final class TextClassifierEventTronLogger {
-
-    private static final String TAG = "TCEventTronLogger";
-
-    private final MetricsLogger mMetricsLogger;
-
-    public TextClassifierEventTronLogger() {
-        this(new MetricsLogger());
-    }
-
-    @VisibleForTesting
-    public TextClassifierEventTronLogger(MetricsLogger metricsLogger) {
-        mMetricsLogger = Objects.requireNonNull(metricsLogger);
-    }
-
-    /** Emits a text classifier event to the logs. */
-    public void writeEvent(TextClassifierEvent event) {
-        Objects.requireNonNull(event);
-
-        int category = getCategory(event);
-        if (category == -1) {
-            Log.w(TAG, "Unknown category: " + event.getEventCategory());
-            return;
-        }
-        final LogMaker log = new LogMaker(category)
-                .setSubtype(getLogType(event))
-                .addTaggedData(FIELD_TEXT_CLASSIFIER_SESSION_ID, event.getResultId())
-                .addTaggedData(FIELD_TEXTCLASSIFIER_MODEL, getModelName(event));
-        if (event.getScores().length >= 1) {
-            log.addTaggedData(FIELD_TEXT_CLASSIFIER_SCORE, event.getScores()[0]);
-        }
-        String[] entityTypes = event.getEntityTypes();
-        // The old logger does not support a field of list type, and thus workaround by store them
-        // in three separate fields. This is not an issue with the new logger.
-        if (entityTypes.length >= 1) {
-            log.addTaggedData(FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE, entityTypes[0]);
-        }
-        if (entityTypes.length >= 2) {
-            log.addTaggedData(FIELD_TEXT_CLASSIFIER_SECOND_ENTITY_TYPE, entityTypes[1]);
-        }
-        if (entityTypes.length >= 3) {
-            log.addTaggedData(FIELD_TEXT_CLASSIFIER_THIRD_ENTITY_TYPE, entityTypes[2]);
-        }
-        TextClassificationContext eventContext = event.getEventContext();
-        if (eventContext != null) {
-            log.addTaggedData(FIELD_TEXT_CLASSIFIER_WIDGET_TYPE, eventContext.getWidgetType());
-            log.addTaggedData(FIELD_TEXT_CLASSIFIER_WIDGET_VERSION,
-                    eventContext.getWidgetVersion());
-            log.setPackageName(eventContext.getPackageName());
-        }
-        mMetricsLogger.write(log);
-        debugLog(log);
-    }
-
-    private static String getModelName(TextClassifierEvent event) {
-        if (event.getModelName() != null) {
-            return event.getModelName();
-        }
-        return SelectionSessionLogger.SignatureParser.getModelName(event.getResultId());
-    }
-
-    private static int getCategory(TextClassifierEvent event) {
-        switch (event.getEventCategory()) {
-            case TextClassifierEvent.CATEGORY_CONVERSATION_ACTIONS:
-                return MetricsEvent.CONVERSATION_ACTIONS;
-            case TextClassifierEvent.CATEGORY_LANGUAGE_DETECTION:
-                return MetricsEvent.LANGUAGE_DETECTION;
-        }
-        return -1;
-    }
-
-    private static int getLogType(TextClassifierEvent event) {
-        switch (event.getEventType()) {
-            case TextClassifierEvent.TYPE_SMART_ACTION:
-                return MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE;
-            case TextClassifierEvent.TYPE_ACTIONS_SHOWN:
-                return MetricsEvent.ACTION_TEXT_CLASSIFIER_ACTIONS_SHOWN;
-            case TextClassifierEvent.TYPE_MANUAL_REPLY:
-                return MetricsEvent.ACTION_TEXT_CLASSIFIER_MANUAL_REPLY;
-            case TextClassifierEvent.TYPE_ACTIONS_GENERATED:
-                return MetricsEvent.ACTION_TEXT_CLASSIFIER_ACTIONS_GENERATED;
-            default:
-                return MetricsEvent.VIEW_UNKNOWN;
-        }
-    }
-
-    private String toCategoryName(int category) {
-        switch (category) {
-            case MetricsEvent.CONVERSATION_ACTIONS:
-                return "conversation_actions";
-            case MetricsEvent.LANGUAGE_DETECTION:
-                return "language_detection";
-        }
-        return "unknown";
-    }
-
-    private String toEventName(int logType) {
-        switch (logType) {
-            case MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE:
-                return "smart_share";
-            case MetricsEvent.ACTION_TEXT_CLASSIFIER_ACTIONS_SHOWN:
-                return "actions_shown";
-            case MetricsEvent.ACTION_TEXT_CLASSIFIER_MANUAL_REPLY:
-                return "manual_reply";
-            case MetricsEvent.ACTION_TEXT_CLASSIFIER_ACTIONS_GENERATED:
-                return "actions_generated";
-        }
-        return "unknown";
-    }
-
-    private void debugLog(LogMaker log) {
-        if (!Log.ENABLE_FULL_LOGGING) {
-            return;
-        }
-        final String id = String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_SESSION_ID));
-        final String categoryName = toCategoryName(log.getCategory());
-        final String eventName = toEventName(log.getSubtype());
-        final String widgetType =
-                String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_WIDGET_TYPE));
-        final String widgetVersion =
-                String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_WIDGET_VERSION));
-        final String model = String.valueOf(log.getTaggedData(FIELD_TEXTCLASSIFIER_MODEL));
-        final String firstEntityType =
-                String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE));
-        final String secondEntityType =
-                String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_SECOND_ENTITY_TYPE));
-        final String thirdEntityType =
-                String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_THIRD_ENTITY_TYPE));
-        final String score =
-                String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_SCORE));
-
-        StringBuilder builder = new StringBuilder();
-        builder.append("writeEvent: ");
-        builder.append("id=").append(id);
-        builder.append(", category=").append(categoryName);
-        builder.append(", eventName=").append(eventName);
-        builder.append(", widgetType=").append(widgetType);
-        builder.append(", widgetVersion=").append(widgetVersion);
-        builder.append(", model=").append(model);
-        builder.append(", firstEntityType=").append(firstEntityType);
-        builder.append(", secondEntityType=").append(secondEntityType);
-        builder.append(", thirdEntityType=").append(thirdEntityType);
-        builder.append(", score=").append(score);
-
-        Log.v(TAG, builder.toString());
-    }
-}
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
deleted file mode 100644
index d7149ee05b575..0000000000000
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ /dev/null
@@ -1,911 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.textclassifier;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.WorkerThread;
-import android.app.RemoteAction;
-import android.content.Context;
-import android.content.Intent;
-import android.icu.util.ULocale;
-import android.os.Bundle;
-import android.os.LocaleList;
-import android.os.ParcelFileDescriptor;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Pair;
-import android.view.textclassifier.ActionsModelParamsSupplier.ActionsModelParams;
-import android.view.textclassifier.intent.ClassificationIntentFactory;
-import android.view.textclassifier.intent.LabeledIntent;
-import android.view.textclassifier.intent.LegacyClassificationIntentFactory;
-import android.view.textclassifier.intent.TemplateClassificationIntentFactory;
-import android.view.textclassifier.intent.TemplateIntentFactory;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.Preconditions;
-
-import com.google.android.textclassifier.ActionsSuggestionsModel;
-import com.google.android.textclassifier.AnnotatorModel;
-import com.google.android.textclassifier.LangIdModel;
-import com.google.android.textclassifier.LangIdModel.LanguageResult;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.time.Instant;
-import java.time.ZonedDateTime;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.function.Supplier;
-
-/**
- * Default implementation of the {@link TextClassifier} interface.
- *
- * 

This class uses machine learning to recognize entities in text. - * Unless otherwise stated, methods of this class are blocking operations and should most - * likely not be called on the UI thread. - * - * @hide - */ -public final class TextClassifierImpl implements TextClassifier { - - private static final String LOG_TAG = DEFAULT_LOG_TAG; - - private static final boolean DEBUG = false; - - private static final File FACTORY_MODEL_DIR = new File("/etc/textclassifier/"); - // Annotator - private static final String ANNOTATOR_FACTORY_MODEL_FILENAME_REGEX = - "textclassifier\\.(.*)\\.model"; - private static final File ANNOTATOR_UPDATED_MODEL_FILE = - new File("/data/misc/textclassifier/textclassifier.model"); - - // LangID - private static final String LANG_ID_FACTORY_MODEL_FILENAME_REGEX = "lang_id.model"; - private static final File UPDATED_LANG_ID_MODEL_FILE = - new File("/data/misc/textclassifier/lang_id.model"); - - // Actions - private static final String ACTIONS_FACTORY_MODEL_FILENAME_REGEX = - "actions_suggestions\\.(.*)\\.model"; - private static final File UPDATED_ACTIONS_MODEL = - new File("/data/misc/textclassifier/actions_suggestions.model"); - - private final Context mContext; - private final TextClassifier mFallback; - private final GenerateLinksLogger mGenerateLinksLogger; - - private final Object mLock = new Object(); - - @GuardedBy("mLock") - private ModelFileManager.ModelFile mAnnotatorModelInUse; - @GuardedBy("mLock") - private AnnotatorModel mAnnotatorImpl; - - @GuardedBy("mLock") - private ModelFileManager.ModelFile mLangIdModelInUse; - @GuardedBy("mLock") - private LangIdModel mLangIdImpl; - - @GuardedBy("mLock") - private ModelFileManager.ModelFile mActionModelInUse; - @GuardedBy("mLock") - private ActionsSuggestionsModel mActionsImpl; - - private final SelectionSessionLogger mSessionLogger = new SelectionSessionLogger(); - private final TextClassifierEventTronLogger mTextClassifierEventTronLogger = - new TextClassifierEventTronLogger(); - - private final TextClassificationConstants mSettings; - - private final ModelFileManager mAnnotatorModelFileManager; - private final ModelFileManager mLangIdModelFileManager; - private final ModelFileManager mActionsModelFileManager; - - private final ClassificationIntentFactory mClassificationIntentFactory; - private final TemplateIntentFactory mTemplateIntentFactory; - private final Supplier mActionsModelParamsSupplier; - - public TextClassifierImpl( - Context context, TextClassificationConstants settings, TextClassifier fallback) { - mContext = Objects.requireNonNull(context); - mFallback = Objects.requireNonNull(fallback); - mSettings = Objects.requireNonNull(settings); - mGenerateLinksLogger = new GenerateLinksLogger(mSettings.getGenerateLinksLogSampleRate()); - mAnnotatorModelFileManager = new ModelFileManager( - new ModelFileManager.ModelFileSupplierImpl( - FACTORY_MODEL_DIR, - ANNOTATOR_FACTORY_MODEL_FILENAME_REGEX, - ANNOTATOR_UPDATED_MODEL_FILE, - AnnotatorModel::getVersion, - AnnotatorModel::getLocales)); - mLangIdModelFileManager = new ModelFileManager( - new ModelFileManager.ModelFileSupplierImpl( - FACTORY_MODEL_DIR, - LANG_ID_FACTORY_MODEL_FILENAME_REGEX, - UPDATED_LANG_ID_MODEL_FILE, - LangIdModel::getVersion, - fd -> ModelFileManager.ModelFile.LANGUAGE_INDEPENDENT)); - mActionsModelFileManager = new ModelFileManager( - new ModelFileManager.ModelFileSupplierImpl( - FACTORY_MODEL_DIR, - ACTIONS_FACTORY_MODEL_FILENAME_REGEX, - UPDATED_ACTIONS_MODEL, - ActionsSuggestionsModel::getVersion, - ActionsSuggestionsModel::getLocales)); - - mTemplateIntentFactory = new TemplateIntentFactory(); - mClassificationIntentFactory = mSettings.isTemplateIntentFactoryEnabled() - ? new TemplateClassificationIntentFactory( - mTemplateIntentFactory, new LegacyClassificationIntentFactory()) - : new LegacyClassificationIntentFactory(); - mActionsModelParamsSupplier = new ActionsModelParamsSupplier(mContext, - () -> { - synchronized (mLock) { - // Clear mActionsImpl here, so that we will create a new - // ActionsSuggestionsModel object with the new flag in the next request. - mActionsImpl = null; - mActionModelInUse = null; - } - }); - } - - public TextClassifierImpl(Context context, TextClassificationConstants settings) { - this(context, settings, TextClassifier.NO_OP); - } - - /** @inheritDoc */ - @Override - @WorkerThread - public TextSelection suggestSelection(TextSelection.Request request) { - Objects.requireNonNull(request); - Utils.checkMainThread(); - try { - final int rangeLength = request.getEndIndex() - request.getStartIndex(); - final String string = request.getText().toString(); - if (string.length() > 0 - && rangeLength <= mSettings.getSuggestSelectionMaxRangeLength()) { - final String localesString = concatenateLocales(request.getDefaultLocales()); - final String detectLanguageTags = detectLanguageTagsFromText(request.getText()); - final ZonedDateTime refTime = ZonedDateTime.now(); - final AnnotatorModel annotatorImpl = getAnnotatorImpl(request.getDefaultLocales()); - final int start; - final int end; - if (mSettings.isModelDarkLaunchEnabled() && !request.isDarkLaunchAllowed()) { - start = request.getStartIndex(); - end = request.getEndIndex(); - } else { - final int[] startEnd = annotatorImpl.suggestSelection( - string, request.getStartIndex(), request.getEndIndex(), - new AnnotatorModel.SelectionOptions(localesString, detectLanguageTags)); - start = startEnd[0]; - end = startEnd[1]; - } - if (start < end - && start >= 0 && end <= string.length() - && start <= request.getStartIndex() && end >= request.getEndIndex()) { - final TextSelection.Builder tsBuilder = new TextSelection.Builder(start, end); - final AnnotatorModel.ClassificationResult[] results = - annotatorImpl.classifyText( - string, start, end, - new AnnotatorModel.ClassificationOptions( - refTime.toInstant().toEpochMilli(), - refTime.getZone().getId(), - localesString, - detectLanguageTags), - // Passing null here to suppress intent generation - // TODO: Use an explicit flag to suppress it. - /* appContext */ null, - /* deviceLocales */null); - final int size = results.length; - for (int i = 0; i < size; i++) { - tsBuilder.setEntityType(results[i].getCollection(), results[i].getScore()); - } - return tsBuilder.setId(createId( - string, request.getStartIndex(), request.getEndIndex())) - .build(); - } else { - // We can not trust the result. Log the issue and ignore the result. - Log.d(LOG_TAG, "Got bad indices for input text. Ignoring result."); - } - } - } catch (Throwable t) { - // Avoid throwing from this method. Log the error. - Log.e(LOG_TAG, - "Error suggesting selection for text. No changes to selection suggested.", - t); - } - // Getting here means something went wrong, return a NO_OP result. - return mFallback.suggestSelection(request); - } - - /** @inheritDoc */ - @Override - @WorkerThread - public TextClassification classifyText(TextClassification.Request request) { - Objects.requireNonNull(request); - Utils.checkMainThread(); - try { - final int rangeLength = request.getEndIndex() - request.getStartIndex(); - final String string = request.getText().toString(); - if (string.length() > 0 && rangeLength <= mSettings.getClassifyTextMaxRangeLength()) { - final String localesString = concatenateLocales(request.getDefaultLocales()); - final String detectLanguageTags = detectLanguageTagsFromText(request.getText()); - final ZonedDateTime refTime = request.getReferenceTime() != null - ? request.getReferenceTime() : ZonedDateTime.now(); - final AnnotatorModel.ClassificationResult[] results = - getAnnotatorImpl(request.getDefaultLocales()) - .classifyText( - string, request.getStartIndex(), request.getEndIndex(), - new AnnotatorModel.ClassificationOptions( - refTime.toInstant().toEpochMilli(), - refTime.getZone().getId(), - localesString, - detectLanguageTags), - mContext, - getResourceLocalesString() - ); - if (results.length > 0) { - return createClassificationResult( - results, string, - request.getStartIndex(), request.getEndIndex(), refTime.toInstant()); - } - } - } catch (Throwable t) { - // Avoid throwing from this method. Log the error. - Log.e(LOG_TAG, "Error getting text classification info.", t); - } - // Getting here means something went wrong, return a NO_OP result. - return mFallback.classifyText(request); - } - - /** @inheritDoc */ - @Override - @WorkerThread - public TextLinks generateLinks(@NonNull TextLinks.Request request) { - Objects.requireNonNull(request); - Utils.checkMainThread(); - if (!Utils.checkTextLength(request.getText(), getMaxGenerateLinksTextLength())) { - return mFallback.generateLinks(request); - } - - if (!mSettings.isSmartLinkifyEnabled() && request.isLegacyFallback()) { - return Utils.generateLegacyLinks(request); - } - - final String textString = request.getText().toString(); - final TextLinks.Builder builder = new TextLinks.Builder(textString); - - try { - final long startTimeMs = System.currentTimeMillis(); - final ZonedDateTime refTime = ZonedDateTime.now(); - final Collection entitiesToIdentify = request.getEntityConfig() != null - ? request.getEntityConfig().resolveEntityListModifications( - getEntitiesForHints(request.getEntityConfig().getHints())) - : mSettings.getEntityListDefault(); - final String localesString = concatenateLocales(request.getDefaultLocales()); - final String detectLanguageTags = detectLanguageTagsFromText(request.getText()); - final AnnotatorModel annotatorImpl = - getAnnotatorImpl(request.getDefaultLocales()); - final boolean isSerializedEntityDataEnabled = - ExtrasUtils.isSerializedEntityDataEnabled(request); - final AnnotatorModel.AnnotatedSpan[] annotations = - annotatorImpl.annotate( - textString, - new AnnotatorModel.AnnotationOptions( - refTime.toInstant().toEpochMilli(), - refTime.getZone().getId(), - localesString, - detectLanguageTags, - entitiesToIdentify, - AnnotatorModel.AnnotationUsecase.SMART.getValue(), - isSerializedEntityDataEnabled)); - for (AnnotatorModel.AnnotatedSpan span : annotations) { - final AnnotatorModel.ClassificationResult[] results = - span.getClassification(); - if (results.length == 0 - || !entitiesToIdentify.contains(results[0].getCollection())) { - continue; - } - final Map entityScores = new ArrayMap<>(); - for (int i = 0; i < results.length; i++) { - entityScores.put(results[i].getCollection(), results[i].getScore()); - } - Bundle extras = new Bundle(); - if (isSerializedEntityDataEnabled) { - ExtrasUtils.putEntities(extras, results); - } - builder.addLink(span.getStartIndex(), span.getEndIndex(), entityScores, extras); - } - final TextLinks links = builder.build(); - final long endTimeMs = System.currentTimeMillis(); - final String callingPackageName = request.getCallingPackageName() == null - ? mContext.getPackageName() // local (in process) TC. - : request.getCallingPackageName(); - mGenerateLinksLogger.logGenerateLinks( - request.getText(), links, callingPackageName, endTimeMs - startTimeMs); - return links; - } catch (Throwable t) { - // Avoid throwing from this method. Log the error. - Log.e(LOG_TAG, "Error getting links info.", t); - } - return mFallback.generateLinks(request); - } - - /** @inheritDoc */ - @Override - public int getMaxGenerateLinksTextLength() { - return mSettings.getGenerateLinksMaxTextLength(); - } - - private Collection getEntitiesForHints(Collection hints) { - final boolean editable = hints.contains(HINT_TEXT_IS_EDITABLE); - final boolean notEditable = hints.contains(HINT_TEXT_IS_NOT_EDITABLE); - - // Use the default if there is no hint, or conflicting ones. - final boolean useDefault = editable == notEditable; - if (useDefault) { - return mSettings.getEntityListDefault(); - } else if (editable) { - return mSettings.getEntityListEditable(); - } else { // notEditable - return mSettings.getEntityListNotEditable(); - } - } - - /** @inheritDoc */ - @Override - public void onSelectionEvent(SelectionEvent event) { - mSessionLogger.writeEvent(event); - } - - @Override - public void onTextClassifierEvent(TextClassifierEvent event) { - if (DEBUG) { - Log.d(DEFAULT_LOG_TAG, "onTextClassifierEvent() called with: event = [" + event + "]"); - } - try { - final SelectionEvent selEvent = event.toSelectionEvent(); - if (selEvent != null) { - mSessionLogger.writeEvent(selEvent); - } else { - mTextClassifierEventTronLogger.writeEvent(event); - } - } catch (Exception e) { - Log.e(LOG_TAG, "Error writing event", e); - } - } - - /** @inheritDoc */ - @Override - public TextLanguage detectLanguage(@NonNull TextLanguage.Request request) { - Objects.requireNonNull(request); - Utils.checkMainThread(); - try { - final TextLanguage.Builder builder = new TextLanguage.Builder(); - final LangIdModel.LanguageResult[] langResults = - getLangIdImpl().detectLanguages(request.getText().toString()); - for (int i = 0; i < langResults.length; i++) { - builder.putLocale( - ULocale.forLanguageTag(langResults[i].getLanguage()), - langResults[i].getScore()); - } - return builder.build(); - } catch (Throwable t) { - // Avoid throwing from this method. Log the error. - Log.e(LOG_TAG, "Error detecting text language.", t); - } - return mFallback.detectLanguage(request); - } - - @Override - public ConversationActions suggestConversationActions(ConversationActions.Request request) { - Objects.requireNonNull(request); - Utils.checkMainThread(); - try { - ActionsSuggestionsModel actionsImpl = getActionsImpl(); - if (actionsImpl == null) { - // Actions model is optional, fallback if it is not available. - return mFallback.suggestConversationActions(request); - } - ActionsSuggestionsModel.ConversationMessage[] nativeMessages = - ActionsSuggestionsHelper.toNativeMessages( - request.getConversation(), this::detectLanguageTagsFromText); - if (nativeMessages.length == 0) { - return mFallback.suggestConversationActions(request); - } - ActionsSuggestionsModel.Conversation nativeConversation = - new ActionsSuggestionsModel.Conversation(nativeMessages); - - ActionsSuggestionsModel.ActionSuggestion[] nativeSuggestions = - actionsImpl.suggestActionsWithIntents( - nativeConversation, - null, - mContext, - getResourceLocalesString(), - getAnnotatorImpl(LocaleList.getDefault())); - return createConversationActionResult(request, nativeSuggestions); - } catch (Throwable t) { - // Avoid throwing from this method. Log the error. - Log.e(LOG_TAG, "Error suggesting conversation actions.", t); - } - return mFallback.suggestConversationActions(request); - } - - /** - * Returns the {@link ConversationAction} result, with a non-null extras. - *

- * Whenever the RemoteAction is non-null, you can expect its corresponding intent - * with a non-null component name is in the extras. - */ - private ConversationActions createConversationActionResult( - ConversationActions.Request request, - ActionsSuggestionsModel.ActionSuggestion[] nativeSuggestions) { - Collection expectedTypes = resolveActionTypesFromRequest(request); - List conversationActions = new ArrayList<>(); - for (ActionsSuggestionsModel.ActionSuggestion nativeSuggestion : nativeSuggestions) { - String actionType = nativeSuggestion.getActionType(); - if (!expectedTypes.contains(actionType)) { - continue; - } - LabeledIntent.Result labeledIntentResult = - ActionsSuggestionsHelper.createLabeledIntentResult( - mContext, - mTemplateIntentFactory, - nativeSuggestion); - RemoteAction remoteAction = null; - Bundle extras = new Bundle(); - if (labeledIntentResult != null) { - remoteAction = labeledIntentResult.remoteAction; - ExtrasUtils.putActionIntent(extras, labeledIntentResult.resolvedIntent); - } - ExtrasUtils.putSerializedEntityData(extras, nativeSuggestion.getSerializedEntityData()); - ExtrasUtils.putEntitiesExtras( - extras, - TemplateIntentFactory.nameVariantsToBundle(nativeSuggestion.getEntityData())); - conversationActions.add( - new ConversationAction.Builder(actionType) - .setConfidenceScore(nativeSuggestion.getScore()) - .setTextReply(nativeSuggestion.getResponseText()) - .setAction(remoteAction) - .setExtras(extras) - .build()); - } - conversationActions = - ActionsSuggestionsHelper.removeActionsWithDuplicates(conversationActions); - if (request.getMaxSuggestions() >= 0 - && conversationActions.size() > request.getMaxSuggestions()) { - conversationActions = conversationActions.subList(0, request.getMaxSuggestions()); - } - String resultId = ActionsSuggestionsHelper.createResultId( - mContext, - request.getConversation(), - mActionModelInUse.getVersion(), - mActionModelInUse.getSupportedLocales()); - return new ConversationActions(conversationActions, resultId); - } - - @Nullable - private String detectLanguageTagsFromText(CharSequence text) { - if (!mSettings.isDetectLanguagesFromTextEnabled()) { - return null; - } - final float threshold = getLangIdThreshold(); - if (threshold < 0 || threshold > 1) { - Log.w(LOG_TAG, - "[detectLanguageTagsFromText] unexpected threshold is found: " + threshold); - return null; - } - TextLanguage.Request request = new TextLanguage.Request.Builder(text).build(); - TextLanguage textLanguage = detectLanguage(request); - int localeHypothesisCount = textLanguage.getLocaleHypothesisCount(); - List languageTags = new ArrayList<>(); - for (int i = 0; i < localeHypothesisCount; i++) { - ULocale locale = textLanguage.getLocale(i); - if (textLanguage.getConfidenceScore(locale) < threshold) { - break; - } - languageTags.add(locale.toLanguageTag()); - } - if (languageTags.isEmpty()) { - return null; - } - return String.join(",", languageTags); - } - - private Collection resolveActionTypesFromRequest(ConversationActions.Request request) { - List defaultActionTypes = - request.getHints().contains(ConversationActions.Request.HINT_FOR_NOTIFICATION) - ? mSettings.getNotificationConversationActionTypes() - : mSettings.getInAppConversationActionTypes(); - return request.getTypeConfig().resolveEntityListModifications(defaultActionTypes); - } - - private AnnotatorModel getAnnotatorImpl(LocaleList localeList) - throws FileNotFoundException { - synchronized (mLock) { - localeList = localeList == null ? LocaleList.getDefault() : localeList; - final ModelFileManager.ModelFile bestModel = - mAnnotatorModelFileManager.findBestModelFile(localeList); - if (bestModel == null) { - throw new FileNotFoundException( - "No annotator model for " + localeList.toLanguageTags()); - } - if (mAnnotatorImpl == null || !Objects.equals(mAnnotatorModelInUse, bestModel)) { - Log.d(DEFAULT_LOG_TAG, "Loading " + bestModel); - final ParcelFileDescriptor pfd = ParcelFileDescriptor.open( - new File(bestModel.getPath()), ParcelFileDescriptor.MODE_READ_ONLY); - try { - if (pfd != null) { - // The current annotator model may be still used by another thread / model. - // Do not call close() here, and let the GC to clean it up when no one else - // is using it. - mAnnotatorImpl = new AnnotatorModel(pfd.getFd()); - mAnnotatorModelInUse = bestModel; - } - } finally { - maybeCloseAndLogError(pfd); - } - } - return mAnnotatorImpl; - } - } - - private LangIdModel getLangIdImpl() throws FileNotFoundException { - synchronized (mLock) { - final ModelFileManager.ModelFile bestModel = - mLangIdModelFileManager.findBestModelFile(null); - if (bestModel == null) { - throw new FileNotFoundException("No LangID model is found"); - } - if (mLangIdImpl == null || !Objects.equals(mLangIdModelInUse, bestModel)) { - Log.d(DEFAULT_LOG_TAG, "Loading " + bestModel); - final ParcelFileDescriptor pfd = ParcelFileDescriptor.open( - new File(bestModel.getPath()), ParcelFileDescriptor.MODE_READ_ONLY); - try { - if (pfd != null) { - mLangIdImpl = new LangIdModel(pfd.getFd()); - mLangIdModelInUse = bestModel; - } - } finally { - maybeCloseAndLogError(pfd); - } - } - return mLangIdImpl; - } - } - - @Nullable - private ActionsSuggestionsModel getActionsImpl() throws FileNotFoundException { - synchronized (mLock) { - // TODO: Use LangID to determine the locale we should use here? - final ModelFileManager.ModelFile bestModel = - mActionsModelFileManager.findBestModelFile(LocaleList.getDefault()); - if (bestModel == null) { - return null; - } - if (mActionsImpl == null || !Objects.equals(mActionModelInUse, bestModel)) { - Log.d(DEFAULT_LOG_TAG, "Loading " + bestModel); - final ParcelFileDescriptor pfd = ParcelFileDescriptor.open( - new File(bestModel.getPath()), ParcelFileDescriptor.MODE_READ_ONLY); - try { - if (pfd == null) { - Log.d(LOG_TAG, "Failed to read the model file: " + bestModel.getPath()); - return null; - } - ActionsModelParams params = mActionsModelParamsSupplier.get(); - mActionsImpl = new ActionsSuggestionsModel( - pfd.getFd(), params.getSerializedPreconditions(bestModel)); - mActionModelInUse = bestModel; - } finally { - maybeCloseAndLogError(pfd); - } - } - return mActionsImpl; - } - } - - private String createId(String text, int start, int end) { - synchronized (mLock) { - return SelectionSessionLogger.createId(text, start, end, mContext, - mAnnotatorModelInUse.getVersion(), - mAnnotatorModelInUse.getSupportedLocales()); - } - } - - private static String concatenateLocales(@Nullable LocaleList locales) { - return (locales == null) ? "" : locales.toLanguageTags(); - } - - private TextClassification createClassificationResult( - AnnotatorModel.ClassificationResult[] classifications, - String text, int start, int end, @Nullable Instant referenceTime) { - final String classifiedText = text.substring(start, end); - final TextClassification.Builder builder = new TextClassification.Builder() - .setText(classifiedText); - - final int typeCount = classifications.length; - AnnotatorModel.ClassificationResult highestScoringResult = - typeCount > 0 ? classifications[0] : null; - for (int i = 0; i < typeCount; i++) { - builder.setEntityType(classifications[i]); - if (classifications[i].getScore() > highestScoringResult.getScore()) { - highestScoringResult = classifications[i]; - } - } - - final Pair languagesBundles = generateLanguageBundles(text, start, end); - final Bundle textLanguagesBundle = languagesBundles.first; - final Bundle foreignLanguageBundle = languagesBundles.second; - builder.setForeignLanguageExtra(foreignLanguageBundle); - - boolean isPrimaryAction = true; - final List labeledIntents = mClassificationIntentFactory.create( - mContext, - classifiedText, - foreignLanguageBundle != null, - referenceTime, - highestScoringResult); - final LabeledIntent.TitleChooser titleChooser = - (labeledIntent, resolveInfo) -> labeledIntent.titleWithoutEntity; - - for (LabeledIntent labeledIntent : labeledIntents) { - final LabeledIntent.Result result = - labeledIntent.resolve(mContext, titleChooser, textLanguagesBundle); - if (result == null) { - continue; - } - - final Intent intent = result.resolvedIntent; - final RemoteAction action = result.remoteAction; - if (isPrimaryAction) { - // For O backwards compatibility, the first RemoteAction is also written to the - // legacy API fields. - builder.setIcon(action.getIcon().loadDrawable(mContext)); - builder.setLabel(action.getTitle().toString()); - builder.setIntent(intent); - builder.setOnClickListener(TextClassification.createIntentOnClickListener( - TextClassification.createPendingIntent( - mContext, intent, labeledIntent.requestCode))); - isPrimaryAction = false; - } - builder.addAction(action, intent); - } - return builder.setId(createId(text, start, end)).build(); - } - - /** - * Returns a bundle pair with language detection information for extras. - *

- * Pair.first = textLanguagesBundle - A bundle containing information about all detected - * languages in the text. May be null if language detection fails or is disabled. This is - * typically expected to be added to a textClassifier generated remote action intent. - * See {@link ExtrasUtils#putTextLanguagesExtra(Bundle, Bundle)}. - * See {@link ExtrasUtils#getTopLanguage(Intent)}. - *

- * Pair.second = foreignLanguageBundle - A bundle with the language and confidence score if the - * system finds the text to be in a foreign language. Otherwise is null. - * See {@link TextClassification.Builder#setForeignLanguageExtra(Bundle)}. - * - * @param context the context of the text to detect languages for - * @param start the start index of the text - * @param end the end index of the text - */ - // TODO: Revisit this algorithm. - // TODO: Consider making this public API. - private Pair generateLanguageBundles(String context, int start, int end) { - if (!mSettings.isTranslateInClassificationEnabled()) { - return null; - } - try { - final float threshold = getLangIdThreshold(); - if (threshold < 0 || threshold > 1) { - Log.w(LOG_TAG, - "[detectForeignLanguage] unexpected threshold is found: " + threshold); - return Pair.create(null, null); - } - - final EntityConfidence languageScores = detectLanguages(context, start, end); - if (languageScores.getEntities().isEmpty()) { - return Pair.create(null, null); - } - - final Bundle textLanguagesBundle = new Bundle(); - ExtrasUtils.putTopLanguageScores(textLanguagesBundle, languageScores); - - final String language = languageScores.getEntities().get(0); - final float score = languageScores.getConfidenceScore(language); - if (score < threshold) { - return Pair.create(textLanguagesBundle, null); - } - - Log.v(LOG_TAG, String.format( - Locale.US, "Language detected: <%s:%.2f>", language, score)); - - final Locale detected = new Locale(language); - final LocaleList deviceLocales = LocaleList.getDefault(); - final int size = deviceLocales.size(); - for (int i = 0; i < size; i++) { - if (deviceLocales.get(i).getLanguage().equals(detected.getLanguage())) { - return Pair.create(textLanguagesBundle, null); - } - } - final Bundle foreignLanguageBundle = ExtrasUtils.createForeignLanguageExtra( - detected.getLanguage(), score, getLangIdImpl().getVersion()); - return Pair.create(textLanguagesBundle, foreignLanguageBundle); - } catch (Throwable t) { - Log.e(LOG_TAG, "Error generating language bundles.", t); - } - return Pair.create(null, null); - } - - /** - * Detect the language of a piece of text by taking surrounding text into consideration. - * - * @param text text providing context for the text for which its language is to be detected - * @param start the start index of the text to detect its language - * @param end the end index of the text to detect its language - */ - // TODO: Revisit this algorithm. - private EntityConfidence detectLanguages(String text, int start, int end) - throws FileNotFoundException { - Preconditions.checkArgument(start >= 0); - Preconditions.checkArgument(end <= text.length()); - Preconditions.checkArgument(start <= end); - - final float[] langIdContextSettings = mSettings.getLangIdContextSettings(); - // The minimum size of text to prefer for detection. - final int minimumTextSize = (int) langIdContextSettings[0]; - // For reducing the score when text is less than the preferred size. - final float penalizeRatio = langIdContextSettings[1]; - // Original detection score to surrounding text detection score ratios. - final float subjectTextScoreRatio = langIdContextSettings[2]; - final float moreTextScoreRatio = 1f - subjectTextScoreRatio; - Log.v(LOG_TAG, - String.format(Locale.US, "LangIdContextSettings: " - + "minimumTextSize=%d, penalizeRatio=%.2f, " - + "subjectTextScoreRatio=%.2f, moreTextScoreRatio=%.2f", - minimumTextSize, penalizeRatio, subjectTextScoreRatio, moreTextScoreRatio)); - - if (end - start < minimumTextSize && penalizeRatio <= 0) { - return new EntityConfidence(Collections.emptyMap()); - } - - final String subject = text.substring(start, end); - final EntityConfidence scores = detectLanguages(subject); - - if (subject.length() >= minimumTextSize - || subject.length() == text.length() - || subjectTextScoreRatio * penalizeRatio >= 1) { - return scores; - } - - final EntityConfidence moreTextScores; - if (moreTextScoreRatio >= 0) { - // Attempt to grow the detection text to be at least minimumTextSize long. - final String moreText = Utils.getSubString(text, start, end, minimumTextSize); - moreTextScores = detectLanguages(moreText); - } else { - moreTextScores = new EntityConfidence(Collections.emptyMap()); - } - - // Combine the original detection scores with the those returned after including more text. - final Map newScores = new ArrayMap<>(); - final Set languages = new ArraySet<>(); - languages.addAll(scores.getEntities()); - languages.addAll(moreTextScores.getEntities()); - for (String language : languages) { - final float score = (subjectTextScoreRatio * scores.getConfidenceScore(language) - + moreTextScoreRatio * moreTextScores.getConfidenceScore(language)) - * penalizeRatio; - newScores.put(language, score); - } - return new EntityConfidence(newScores); - } - - /** - * Detect languages for the specified text. - */ - private EntityConfidence detectLanguages(String text) throws FileNotFoundException { - final LangIdModel langId = getLangIdImpl(); - final LangIdModel.LanguageResult[] langResults = langId.detectLanguages(text); - final Map languagesMap = new ArrayMap<>(); - for (LanguageResult langResult : langResults) { - languagesMap.put(langResult.getLanguage(), langResult.getScore()); - } - return new EntityConfidence(languagesMap); - } - - private float getLangIdThreshold() { - try { - return mSettings.getLangIdThresholdOverride() >= 0 - ? mSettings.getLangIdThresholdOverride() - : getLangIdImpl().getLangIdThreshold(); - } catch (FileNotFoundException e) { - final float defaultThreshold = 0.5f; - Log.v(LOG_TAG, "Using default foreign language threshold: " + defaultThreshold); - return defaultThreshold; - } - } - - @Override - public void dump(@NonNull IndentingPrintWriter printWriter) { - synchronized (mLock) { - printWriter.println("TextClassifierImpl:"); - printWriter.increaseIndent(); - printWriter.println("Annotator model file(s):"); - printWriter.increaseIndent(); - for (ModelFileManager.ModelFile modelFile : - mAnnotatorModelFileManager.listModelFiles()) { - printWriter.println(modelFile.toString()); - } - printWriter.decreaseIndent(); - printWriter.println("LangID model file(s):"); - printWriter.increaseIndent(); - for (ModelFileManager.ModelFile modelFile : - mLangIdModelFileManager.listModelFiles()) { - printWriter.println(modelFile.toString()); - } - printWriter.decreaseIndent(); - printWriter.println("Actions model file(s):"); - printWriter.increaseIndent(); - for (ModelFileManager.ModelFile modelFile : - mActionsModelFileManager.listModelFiles()) { - printWriter.println(modelFile.toString()); - } - printWriter.decreaseIndent(); - printWriter.printPair("mFallback", mFallback); - printWriter.decreaseIndent(); - printWriter.println(); - } - } - - /** - * Closes the ParcelFileDescriptor, if non-null, and logs any errors that occur. - */ - private static void maybeCloseAndLogError(@Nullable ParcelFileDescriptor fd) { - if (fd == null) { - return; - } - - try { - fd.close(); - } catch (IOException e) { - Log.e(LOG_TAG, "Error closing file.", e); - } - } - - /** - * Returns the locales string for the current resources configuration. - */ - private String getResourceLocalesString() { - try { - return mContext.getResources().getConfiguration().getLocales().toLanguageTags(); - } catch (NullPointerException e) { - // NPE is unexpected. Erring on the side of caution. - return LocaleList.getDefault().toLanguageTags(); - } - } -} diff --git a/core/java/android/view/textclassifier/intent/ClassificationIntentFactory.java b/core/java/android/view/textclassifier/intent/ClassificationIntentFactory.java deleted file mode 100644 index 22e374f2b38f2..0000000000000 --- a/core/java/android/view/textclassifier/intent/ClassificationIntentFactory.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.view.textclassifier.intent; - -import android.annotation.Nullable; -import android.content.Context; -import android.content.Intent; - -import com.google.android.textclassifier.AnnotatorModel; - -import java.time.Instant; -import java.util.List; - -/** - * @hide - */ -public interface ClassificationIntentFactory { - - /** - * Return a list of LabeledIntent from the classification result. - */ - List create( - Context context, - String text, - boolean foreignText, - @Nullable Instant referenceTime, - @Nullable AnnotatorModel.ClassificationResult classification); - - /** - * Inserts translate action to the list if it is a foreign text. - */ - static void insertTranslateAction( - List actions, Context context, String text) { - actions.add(new LabeledIntent( - context.getString(com.android.internal.R.string.translate), - /* titleWithEntity */ null, - context.getString(com.android.internal.R.string.translate_desc), - /* descriptionWithAppName */ null, - new Intent(Intent.ACTION_TRANSLATE) - // TODO: Probably better to introduce a "translate" scheme instead of - // using EXTRA_TEXT. - .putExtra(Intent.EXTRA_TEXT, text), - text.hashCode())); - } -} diff --git a/core/java/android/view/textclassifier/intent/LabeledIntent.java b/core/java/android/view/textclassifier/intent/LabeledIntent.java deleted file mode 100644 index cbd9d1a522f64..0000000000000 --- a/core/java/android/view/textclassifier/intent/LabeledIntent.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.view.textclassifier.intent; - -import android.annotation.Nullable; -import android.app.PendingIntent; -import android.app.RemoteAction; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.graphics.drawable.Icon; -import android.os.Bundle; -import android.text.TextUtils; -import android.view.textclassifier.ExtrasUtils; -import android.view.textclassifier.Log; -import android.view.textclassifier.TextClassification; -import android.view.textclassifier.TextClassifier; - -import com.android.internal.annotations.VisibleForTesting; - -import java.util.Objects; - -/** - * Helper class to store the information from which RemoteActions are built. - * - * @hide - */ -@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) -public final class LabeledIntent { - private static final String TAG = "LabeledIntent"; - public static final int DEFAULT_REQUEST_CODE = 0; - private static final TitleChooser DEFAULT_TITLE_CHOOSER = - (labeledIntent, resolveInfo) -> { - if (!TextUtils.isEmpty(labeledIntent.titleWithEntity)) { - return labeledIntent.titleWithEntity; - } - return labeledIntent.titleWithoutEntity; - }; - - @Nullable - public final String titleWithoutEntity; - @Nullable - public final String titleWithEntity; - public final String description; - @Nullable - public final String descriptionWithAppName; - // Do not update this intent. - public final Intent intent; - public final int requestCode; - - /** - * Initializes a LabeledIntent. - * - *

NOTE: {@code requestCode} is required to not be {@link #DEFAULT_REQUEST_CODE} - * if distinguishing info (e.g. the classified text) is represented in intent extras only. - * In such circumstances, the request code should represent the distinguishing info - * (e.g. by generating a hashcode) so that the generated PendingIntent is (somewhat) - * unique. To be correct, the PendingIntent should be definitely unique but we try a - * best effort approach that avoids spamming the system with PendingIntents. - */ - // TODO: Fix the issue mentioned above so the behaviour is correct. - public LabeledIntent( - @Nullable String titleWithoutEntity, - @Nullable String titleWithEntity, - String description, - @Nullable String descriptionWithAppName, - Intent intent, - int requestCode) { - if (TextUtils.isEmpty(titleWithEntity) && TextUtils.isEmpty(titleWithoutEntity)) { - throw new IllegalArgumentException( - "titleWithEntity and titleWithoutEntity should not be both null"); - } - this.titleWithoutEntity = titleWithoutEntity; - this.titleWithEntity = titleWithEntity; - this.description = Objects.requireNonNull(description); - this.descriptionWithAppName = descriptionWithAppName; - this.intent = Objects.requireNonNull(intent); - this.requestCode = requestCode; - } - - /** - * Return the resolved result. - * - * @param context the context to resolve the result's intent and action - * @param titleChooser for choosing an action title - * @param textLanguagesBundle containing language detection information - */ - @Nullable - public Result resolve( - Context context, - @Nullable TitleChooser titleChooser, - @Nullable Bundle textLanguagesBundle) { - final PackageManager pm = context.getPackageManager(); - final ResolveInfo resolveInfo = pm.resolveActivity(intent, 0); - - if (resolveInfo == null || resolveInfo.activityInfo == null) { - Log.w(TAG, "resolveInfo or activityInfo is null"); - return null; - } - final String packageName = resolveInfo.activityInfo.packageName; - final String className = resolveInfo.activityInfo.name; - if (packageName == null || className == null) { - Log.w(TAG, "packageName or className is null"); - return null; - } - Intent resolvedIntent = new Intent(intent); - resolvedIntent.putExtra( - TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER, - getFromTextClassifierExtra(textLanguagesBundle)); - boolean shouldShowIcon = false; - Icon icon = null; - if (!"android".equals(packageName)) { - // We only set the component name when the package name is not resolved to "android" - // to workaround a bug that explicit intent with component name == ResolverActivity - // can't be launched on keyguard. - resolvedIntent.setComponent(new ComponentName(packageName, className)); - if (resolveInfo.activityInfo.getIconResource() != 0) { - icon = Icon.createWithResource( - packageName, resolveInfo.activityInfo.getIconResource()); - shouldShowIcon = true; - } - } - if (icon == null) { - // RemoteAction requires that there be an icon. - icon = Icon.createWithResource( - "android", com.android.internal.R.drawable.ic_more_items); - } - final PendingIntent pendingIntent = - TextClassification.createPendingIntent(context, resolvedIntent, requestCode); - titleChooser = titleChooser == null ? DEFAULT_TITLE_CHOOSER : titleChooser; - CharSequence title = titleChooser.chooseTitle(this, resolveInfo); - if (TextUtils.isEmpty(title)) { - Log.w(TAG, "Custom titleChooser return null, fallback to the default titleChooser"); - title = DEFAULT_TITLE_CHOOSER.chooseTitle(this, resolveInfo); - } - final RemoteAction action = - new RemoteAction(icon, title, resolveDescription(resolveInfo, pm), pendingIntent); - action.setShouldShowIcon(shouldShowIcon); - return new Result(resolvedIntent, action); - } - - private String resolveDescription(ResolveInfo resolveInfo, PackageManager packageManager) { - if (!TextUtils.isEmpty(descriptionWithAppName)) { - // Example string format of descriptionWithAppName: "Use %1$s to open map". - String applicationName = getApplicationName(resolveInfo, packageManager); - if (!TextUtils.isEmpty(applicationName)) { - return String.format(descriptionWithAppName, applicationName); - } - } - return description; - } - - @Nullable - private String getApplicationName( - ResolveInfo resolveInfo, PackageManager packageManager) { - if (resolveInfo.activityInfo == null) { - return null; - } - if ("android".equals(resolveInfo.activityInfo.packageName)) { - return null; - } - if (resolveInfo.activityInfo.applicationInfo == null) { - return null; - } - return (String) packageManager.getApplicationLabel( - resolveInfo.activityInfo.applicationInfo); - } - - private Bundle getFromTextClassifierExtra(@Nullable Bundle textLanguagesBundle) { - if (textLanguagesBundle != null) { - final Bundle bundle = new Bundle(); - ExtrasUtils.putTextLanguagesExtra(bundle, textLanguagesBundle); - return bundle; - } else { - return Bundle.EMPTY; - } - } - - /** - * Data class that holds the result. - */ - public static final class Result { - public final Intent resolvedIntent; - public final RemoteAction remoteAction; - - public Result(Intent resolvedIntent, RemoteAction remoteAction) { - this.resolvedIntent = Objects.requireNonNull(resolvedIntent); - this.remoteAction = Objects.requireNonNull(remoteAction); - } - } - - /** - * An object to choose a title from resolved info. If {@code null} is returned, - * {@link #titleWithEntity} will be used if it exists, {@link #titleWithoutEntity} otherwise. - */ - public interface TitleChooser { - /** - * Picks a title from a {@link LabeledIntent} by looking into resolved info. - * {@code resolveInfo} is guaranteed to have a non-null {@code activityInfo}. - */ - @Nullable - CharSequence chooseTitle(LabeledIntent labeledIntent, ResolveInfo resolveInfo); - } -} diff --git a/core/java/android/view/textclassifier/intent/LegacyClassificationIntentFactory.java b/core/java/android/view/textclassifier/intent/LegacyClassificationIntentFactory.java deleted file mode 100644 index 8d60ad850afa2..0000000000000 --- a/core/java/android/view/textclassifier/intent/LegacyClassificationIntentFactory.java +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.view.textclassifier.intent; - -import static java.time.temporal.ChronoUnit.MILLIS; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.SearchManager; -import android.content.ContentUris; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.os.UserManager; -import android.provider.Browser; -import android.provider.CalendarContract; -import android.provider.ContactsContract; -import android.view.textclassifier.Log; -import android.view.textclassifier.TextClassifier; - -import com.google.android.textclassifier.AnnotatorModel; - -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.concurrent.TimeUnit; - -/** - * Creates intents based on the classification type. - * @hide - */ -// TODO: Consider to support {@code descriptionWithAppName}. -public final class LegacyClassificationIntentFactory implements ClassificationIntentFactory { - - private static final String TAG = "LegacyClassificationIntentFactory"; - private static final long MIN_EVENT_FUTURE_MILLIS = TimeUnit.MINUTES.toMillis(5); - private static final long DEFAULT_EVENT_DURATION = TimeUnit.HOURS.toMillis(1); - - @NonNull - @Override - public List create(Context context, String text, boolean foreignText, - @Nullable Instant referenceTime, - AnnotatorModel.ClassificationResult classification) { - final String type = classification != null - ? classification.getCollection().trim().toLowerCase(Locale.ENGLISH) - : ""; - text = text.trim(); - final List actions; - switch (type) { - case TextClassifier.TYPE_EMAIL: - actions = createForEmail(context, text); - break; - case TextClassifier.TYPE_PHONE: - actions = createForPhone(context, text); - break; - case TextClassifier.TYPE_ADDRESS: - actions = createForAddress(context, text); - break; - case TextClassifier.TYPE_URL: - actions = createForUrl(context, text); - break; - case TextClassifier.TYPE_DATE: // fall through - case TextClassifier.TYPE_DATE_TIME: - if (classification.getDatetimeResult() != null) { - final Instant parsedTime = Instant.ofEpochMilli( - classification.getDatetimeResult().getTimeMsUtc()); - actions = createForDatetime(context, type, referenceTime, parsedTime); - } else { - actions = new ArrayList<>(); - } - break; - case TextClassifier.TYPE_FLIGHT_NUMBER: - actions = createForFlight(context, text); - break; - case TextClassifier.TYPE_DICTIONARY: - actions = createForDictionary(context, text); - break; - default: - actions = new ArrayList<>(); - break; - } - if (foreignText) { - ClassificationIntentFactory.insertTranslateAction(actions, context, text); - } - return actions; - } - - @NonNull - private static List createForEmail(Context context, String text) { - final List actions = new ArrayList<>(); - actions.add(new LabeledIntent( - context.getString(com.android.internal.R.string.email), - /* titleWithEntity */ null, - context.getString(com.android.internal.R.string.email_desc), - /* descriptionWithAppName */ null, - new Intent(Intent.ACTION_SENDTO) - .setData(Uri.parse(String.format("mailto:%s", text))), - LabeledIntent.DEFAULT_REQUEST_CODE)); - actions.add(new LabeledIntent( - context.getString(com.android.internal.R.string.add_contact), - /* titleWithEntity */ null, - context.getString(com.android.internal.R.string.add_contact_desc), - /* descriptionWithAppName */ null, - new Intent(Intent.ACTION_INSERT_OR_EDIT) - .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE) - .putExtra(ContactsContract.Intents.Insert.EMAIL, text), - text.hashCode())); - return actions; - } - - @NonNull - private static List createForPhone(Context context, String text) { - final List actions = new ArrayList<>(); - final UserManager userManager = context.getSystemService(UserManager.class); - final Bundle userRestrictions = userManager != null - ? userManager.getUserRestrictions() : new Bundle(); - if (!userRestrictions.getBoolean(UserManager.DISALLOW_OUTGOING_CALLS, false)) { - actions.add(new LabeledIntent( - context.getString(com.android.internal.R.string.dial), - /* titleWithEntity */ null, - context.getString(com.android.internal.R.string.dial_desc), - /* descriptionWithAppName */ null, - new Intent(Intent.ACTION_DIAL).setData( - Uri.parse(String.format("tel:%s", text))), - LabeledIntent.DEFAULT_REQUEST_CODE)); - } - actions.add(new LabeledIntent( - context.getString(com.android.internal.R.string.add_contact), - /* titleWithEntity */ null, - context.getString(com.android.internal.R.string.add_contact_desc), - /* descriptionWithAppName */ null, - new Intent(Intent.ACTION_INSERT_OR_EDIT) - .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE) - .putExtra(ContactsContract.Intents.Insert.PHONE, text), - text.hashCode())); - if (!userRestrictions.getBoolean(UserManager.DISALLOW_SMS, false)) { - actions.add(new LabeledIntent( - context.getString(com.android.internal.R.string.sms), - /* titleWithEntity */ null, - context.getString(com.android.internal.R.string.sms_desc), - /* descriptionWithAppName */ null, - new Intent(Intent.ACTION_SENDTO) - .setData(Uri.parse(String.format("smsto:%s", text))), - LabeledIntent.DEFAULT_REQUEST_CODE)); - } - return actions; - } - - @NonNull - private static List createForAddress(Context context, String text) { - final List actions = new ArrayList<>(); - try { - final String encText = URLEncoder.encode(text, "UTF-8"); - actions.add(new LabeledIntent( - context.getString(com.android.internal.R.string.map), - /* titleWithEntity */ null, - context.getString(com.android.internal.R.string.map_desc), - /* descriptionWithAppName */ null, - new Intent(Intent.ACTION_VIEW) - .setData(Uri.parse(String.format("geo:0,0?q=%s", encText))), - LabeledIntent.DEFAULT_REQUEST_CODE)); - } catch (UnsupportedEncodingException e) { - Log.e(TAG, "Could not encode address", e); - } - return actions; - } - - @NonNull - private static List createForUrl(Context context, String text) { - if (Uri.parse(text).getScheme() == null) { - text = "http://" + text; - } - final List actions = new ArrayList<>(); - actions.add(new LabeledIntent( - context.getString(com.android.internal.R.string.browse), - /* titleWithEntity */ null, - context.getString(com.android.internal.R.string.browse_desc), - /* descriptionWithAppName */ null, - new Intent(Intent.ACTION_VIEW) - .setDataAndNormalize(Uri.parse(text)) - .putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()), - LabeledIntent.DEFAULT_REQUEST_CODE)); - return actions; - } - - @NonNull - private static List createForDatetime( - Context context, String type, @Nullable Instant referenceTime, - Instant parsedTime) { - if (referenceTime == null) { - // If no reference time was given, use now. - referenceTime = Instant.now(); - } - List actions = new ArrayList<>(); - actions.add(createCalendarViewIntent(context, parsedTime)); - final long millisUntilEvent = referenceTime.until(parsedTime, MILLIS); - if (millisUntilEvent > MIN_EVENT_FUTURE_MILLIS) { - actions.add(createCalendarCreateEventIntent(context, parsedTime, type)); - } - return actions; - } - - @NonNull - private static List createForFlight(Context context, String text) { - final List actions = new ArrayList<>(); - actions.add(new LabeledIntent( - context.getString(com.android.internal.R.string.view_flight), - /* titleWithEntity */ null, - context.getString(com.android.internal.R.string.view_flight_desc), - /* descriptionWithAppName */ null, - new Intent(Intent.ACTION_WEB_SEARCH) - .putExtra(SearchManager.QUERY, text), - text.hashCode())); - return actions; - } - - @NonNull - private static LabeledIntent createCalendarViewIntent(Context context, Instant parsedTime) { - Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon(); - builder.appendPath("time"); - ContentUris.appendId(builder, parsedTime.toEpochMilli()); - return new LabeledIntent( - context.getString(com.android.internal.R.string.view_calendar), - /* titleWithEntity */ null, - context.getString(com.android.internal.R.string.view_calendar_desc), - /* descriptionWithAppName */ null, - new Intent(Intent.ACTION_VIEW).setData(builder.build()), - LabeledIntent.DEFAULT_REQUEST_CODE); - } - - @NonNull - private static LabeledIntent createCalendarCreateEventIntent( - Context context, Instant parsedTime, @TextClassifier.EntityType String type) { - final boolean isAllDay = TextClassifier.TYPE_DATE.equals(type); - return new LabeledIntent( - context.getString(com.android.internal.R.string.add_calendar_event), - /* titleWithEntity */ null, - context.getString(com.android.internal.R.string.add_calendar_event_desc), - /* descriptionWithAppName */ null, - new Intent(Intent.ACTION_INSERT) - .setData(CalendarContract.Events.CONTENT_URI) - .putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, isAllDay) - .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, - parsedTime.toEpochMilli()) - .putExtra(CalendarContract.EXTRA_EVENT_END_TIME, - parsedTime.toEpochMilli() + DEFAULT_EVENT_DURATION), - parsedTime.hashCode()); - } - - @NonNull - private static List createForDictionary(Context context, String text) { - final List actions = new ArrayList<>(); - actions.add(new LabeledIntent( - context.getString(com.android.internal.R.string.define), - /* titleWithEntity */ null, - context.getString(com.android.internal.R.string.define_desc), - /* descriptionWithAppName */ null, - new Intent(Intent.ACTION_DEFINE) - .putExtra(Intent.EXTRA_TEXT, text), - text.hashCode())); - return actions; - } -} diff --git a/core/java/android/view/textclassifier/intent/TemplateClassificationIntentFactory.java b/core/java/android/view/textclassifier/intent/TemplateClassificationIntentFactory.java deleted file mode 100644 index aef4bd6c73510..0000000000000 --- a/core/java/android/view/textclassifier/intent/TemplateClassificationIntentFactory.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.view.textclassifier.intent; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.Context; -import android.view.textclassifier.Log; -import android.view.textclassifier.TextClassifier; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.Preconditions; - -import com.google.android.textclassifier.AnnotatorModel; -import com.google.android.textclassifier.RemoteActionTemplate; - -import java.time.Instant; -import java.util.Collections; -import java.util.List; -import java.util.Objects; - -/** - * Creates intents based on {@link RemoteActionTemplate} objects for a ClassificationResult. - * - * @hide - */ -@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) -public final class TemplateClassificationIntentFactory implements ClassificationIntentFactory { - private static final String TAG = TextClassifier.DEFAULT_LOG_TAG; - private final TemplateIntentFactory mTemplateIntentFactory; - private final ClassificationIntentFactory mFallback; - - public TemplateClassificationIntentFactory(TemplateIntentFactory templateIntentFactory, - ClassificationIntentFactory fallback) { - mTemplateIntentFactory = Objects.requireNonNull(templateIntentFactory); - mFallback = Objects.requireNonNull(fallback); - } - - /** - * Returns a list of {@link LabeledIntent} - * that are constructed from the classification result. - */ - @NonNull - @Override - public List create( - Context context, - String text, - boolean foreignText, - @Nullable Instant referenceTime, - @Nullable AnnotatorModel.ClassificationResult classification) { - if (classification == null) { - return Collections.emptyList(); - } - RemoteActionTemplate[] remoteActionTemplates = classification.getRemoteActionTemplates(); - if (remoteActionTemplates == null) { - // RemoteActionTemplate is missing, fallback. - Log.w(TAG, "RemoteActionTemplate is missing, fallback to" - + " LegacyClassificationIntentFactory."); - return mFallback.create(context, text, foreignText, referenceTime, classification); - } - final List labeledIntents = - mTemplateIntentFactory.create(remoteActionTemplates); - if (foreignText) { - ClassificationIntentFactory.insertTranslateAction(labeledIntents, context, text.trim()); - } - return labeledIntents; - } -} diff --git a/core/java/android/view/textclassifier/intent/TemplateIntentFactory.java b/core/java/android/view/textclassifier/intent/TemplateIntentFactory.java deleted file mode 100644 index 7a39569969728..0000000000000 --- a/core/java/android/view/textclassifier/intent/TemplateIntentFactory.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.view.textclassifier.intent; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.text.TextUtils; -import android.view.textclassifier.Log; -import android.view.textclassifier.TextClassifier; - -import com.android.internal.annotations.VisibleForTesting; - -import com.google.android.textclassifier.NamedVariant; -import com.google.android.textclassifier.RemoteActionTemplate; - -import java.util.ArrayList; -import java.util.List; - -/** - * Creates intents based on {@link RemoteActionTemplate} objects. - * - * @hide - */ -@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) -public final class TemplateIntentFactory { - private static final String TAG = TextClassifier.DEFAULT_LOG_TAG; - - /** - * Constructs and returns a list of {@link LabeledIntent} based on the given templates. - */ - @Nullable - public List create( - @NonNull RemoteActionTemplate[] remoteActionTemplates) { - if (remoteActionTemplates.length == 0) { - return new ArrayList<>(); - } - final List labeledIntents = new ArrayList<>(); - for (RemoteActionTemplate remoteActionTemplate : remoteActionTemplates) { - if (!isValidTemplate(remoteActionTemplate)) { - Log.w(TAG, "Invalid RemoteActionTemplate skipped."); - continue; - } - labeledIntents.add( - new LabeledIntent( - remoteActionTemplate.titleWithoutEntity, - remoteActionTemplate.titleWithEntity, - remoteActionTemplate.description, - remoteActionTemplate.descriptionWithAppName, - createIntent(remoteActionTemplate), - remoteActionTemplate.requestCode == null - ? LabeledIntent.DEFAULT_REQUEST_CODE - : remoteActionTemplate.requestCode)); - } - return labeledIntents; - } - - private static boolean isValidTemplate(@Nullable RemoteActionTemplate remoteActionTemplate) { - if (remoteActionTemplate == null) { - Log.w(TAG, "Invalid RemoteActionTemplate: is null"); - return false; - } - if (TextUtils.isEmpty(remoteActionTemplate.titleWithEntity) - && TextUtils.isEmpty(remoteActionTemplate.titleWithoutEntity)) { - Log.w(TAG, "Invalid RemoteActionTemplate: title is null"); - return false; - } - if (TextUtils.isEmpty(remoteActionTemplate.description)) { - Log.w(TAG, "Invalid RemoteActionTemplate: description is null"); - return false; - } - if (!TextUtils.isEmpty(remoteActionTemplate.packageName)) { - Log.w(TAG, "Invalid RemoteActionTemplate: package name is set"); - return false; - } - if (TextUtils.isEmpty(remoteActionTemplate.action)) { - Log.w(TAG, "Invalid RemoteActionTemplate: intent action not set"); - return false; - } - return true; - } - - private static Intent createIntent(RemoteActionTemplate remoteActionTemplate) { - final Intent intent = new Intent(remoteActionTemplate.action); - final Uri uri = TextUtils.isEmpty(remoteActionTemplate.data) - ? null : Uri.parse(remoteActionTemplate.data).normalizeScheme(); - final String type = TextUtils.isEmpty(remoteActionTemplate.type) - ? null : Intent.normalizeMimeType(remoteActionTemplate.type); - intent.setDataAndType(uri, type); - intent.setFlags(remoteActionTemplate.flags == null ? 0 : remoteActionTemplate.flags); - if (remoteActionTemplate.category != null) { - for (String category : remoteActionTemplate.category) { - if (category != null) { - intent.addCategory(category); - } - } - } - intent.putExtras(nameVariantsToBundle(remoteActionTemplate.extras)); - return intent; - } - - /** - * Converts an array of {@link NamedVariant} to a Bundle and returns it. - */ - public static Bundle nameVariantsToBundle(@Nullable NamedVariant[] namedVariants) { - if (namedVariants == null) { - return Bundle.EMPTY; - } - Bundle bundle = new Bundle(); - for (NamedVariant namedVariant : namedVariants) { - if (namedVariant == null) { - continue; - } - switch (namedVariant.getType()) { - case NamedVariant.TYPE_INT: - bundle.putInt(namedVariant.getName(), namedVariant.getInt()); - break; - case NamedVariant.TYPE_LONG: - bundle.putLong(namedVariant.getName(), namedVariant.getLong()); - break; - case NamedVariant.TYPE_FLOAT: - bundle.putFloat(namedVariant.getName(), namedVariant.getFloat()); - break; - case NamedVariant.TYPE_DOUBLE: - bundle.putDouble(namedVariant.getName(), namedVariant.getDouble()); - break; - case NamedVariant.TYPE_BOOL: - bundle.putBoolean(namedVariant.getName(), namedVariant.getBool()); - break; - case NamedVariant.TYPE_STRING: - bundle.putString(namedVariant.getName(), namedVariant.getString()); - break; - default: - Log.w(TAG, - "Unsupported type found in nameVariantsToBundle : " - + namedVariant.getType()); - } - } - return bundle; - } -} diff --git a/core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.java b/core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.java deleted file mode 100644 index 28cb80d0c74cb..0000000000000 --- a/core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.java +++ /dev/null @@ -1,599 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.textclassifier.logging; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.Context; -import android.metrics.LogMaker; -import android.os.Build; -import android.util.Log; -import android.view.textclassifier.TextClassification; -import android.view.textclassifier.TextClassifier; -import android.view.textclassifier.TextSelection; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.internal.util.Preconditions; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Objects; -import java.util.UUID; - -/** - * A selection event tracker. - * @hide - */ -//TODO: Do not allow any crashes from this class. -public final class SmartSelectionEventTracker { - - private static final String LOG_TAG = "SmartSelectEventTracker"; - private static final boolean DEBUG_LOG_ENABLED = true; - - private static final int START_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_START; - private static final int PREV_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_PREVIOUS; - private static final int INDEX = MetricsEvent.FIELD_SELECTION_SESSION_INDEX; - private static final int WIDGET_TYPE = MetricsEvent.FIELD_SELECTION_WIDGET_TYPE; - private static final int WIDGET_VERSION = MetricsEvent.FIELD_SELECTION_WIDGET_VERSION; - private static final int MODEL_NAME = MetricsEvent.FIELD_TEXTCLASSIFIER_MODEL; - private static final int ENTITY_TYPE = MetricsEvent.FIELD_SELECTION_ENTITY_TYPE; - private static final int SMART_START = MetricsEvent.FIELD_SELECTION_SMART_RANGE_START; - private static final int SMART_END = MetricsEvent.FIELD_SELECTION_SMART_RANGE_END; - private static final int EVENT_START = MetricsEvent.FIELD_SELECTION_RANGE_START; - private static final int EVENT_END = MetricsEvent.FIELD_SELECTION_RANGE_END; - private static final int SESSION_ID = MetricsEvent.FIELD_SELECTION_SESSION_ID; - - private static final String ZERO = "0"; - private static final String TEXTVIEW = "textview"; - private static final String EDITTEXT = "edittext"; - private static final String UNSELECTABLE_TEXTVIEW = "nosel-textview"; - private static final String WEBVIEW = "webview"; - private static final String EDIT_WEBVIEW = "edit-webview"; - private static final String CUSTOM_TEXTVIEW = "customview"; - private static final String CUSTOM_EDITTEXT = "customedit"; - private static final String CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview"; - private static final String UNKNOWN = "unknown"; - - @Retention(RetentionPolicy.SOURCE) - @IntDef({WidgetType.UNSPECIFIED, WidgetType.TEXTVIEW, WidgetType.WEBVIEW, - WidgetType.EDITTEXT, WidgetType.EDIT_WEBVIEW}) - public @interface WidgetType { - int UNSPECIFIED = 0; - int TEXTVIEW = 1; - int WEBVIEW = 2; - int EDITTEXT = 3; - int EDIT_WEBVIEW = 4; - int UNSELECTABLE_TEXTVIEW = 5; - int CUSTOM_TEXTVIEW = 6; - int CUSTOM_EDITTEXT = 7; - int CUSTOM_UNSELECTABLE_TEXTVIEW = 8; - } - - private final MetricsLogger mMetricsLogger = new MetricsLogger(); - private final int mWidgetType; - @Nullable private final String mWidgetVersion; - private final Context mContext; - - @Nullable private String mSessionId; - private final int[] mSmartIndices = new int[2]; - private final int[] mPrevIndices = new int[2]; - private int mOrigStart; - private int mIndex; - private long mSessionStartTime; - private long mLastEventTime; - private boolean mSmartSelectionTriggered; - private String mModelName; - - @UnsupportedAppUsage(trackingBug = 136637107, maxTargetSdk = Build.VERSION_CODES.Q, - publicAlternatives = "See {@link android.view.textclassifier.TextClassifier}.") - public SmartSelectionEventTracker(@NonNull Context context, @WidgetType int widgetType) { - mWidgetType = widgetType; - mWidgetVersion = null; - mContext = Objects.requireNonNull(context); - } - - public SmartSelectionEventTracker( - @NonNull Context context, @WidgetType int widgetType, @Nullable String widgetVersion) { - mWidgetType = widgetType; - mWidgetVersion = widgetVersion; - mContext = Objects.requireNonNull(context); - } - - /** - * Logs a selection event. - * - * @param event the selection event - */ - @UnsupportedAppUsage(trackingBug = 136637107, maxTargetSdk = Build.VERSION_CODES.Q, - publicAlternatives = "See {@link android.view.textclassifier.TextClassifier}.") - public void logEvent(@NonNull SelectionEvent event) { - Objects.requireNonNull(event); - - if (event.mEventType != SelectionEvent.EventType.SELECTION_STARTED && mSessionId == null - && DEBUG_LOG_ENABLED) { - Log.d(LOG_TAG, "Selection session not yet started. Ignoring event"); - return; - } - - final long now = System.currentTimeMillis(); - switch (event.mEventType) { - case SelectionEvent.EventType.SELECTION_STARTED: - mSessionId = startNewSession(); - Preconditions.checkArgument(event.mEnd == event.mStart + 1); - mOrigStart = event.mStart; - mSessionStartTime = now; - break; - case SelectionEvent.EventType.SMART_SELECTION_SINGLE: // fall through - case SelectionEvent.EventType.SMART_SELECTION_MULTI: - mSmartSelectionTriggered = true; - mModelName = getModelName(event); - mSmartIndices[0] = event.mStart; - mSmartIndices[1] = event.mEnd; - break; - case SelectionEvent.EventType.SELECTION_MODIFIED: // fall through - case SelectionEvent.EventType.AUTO_SELECTION: - if (mPrevIndices[0] == event.mStart && mPrevIndices[1] == event.mEnd) { - // Selection did not change. Ignore event. - return; - } - } - writeEvent(event, now); - - if (event.isTerminal()) { - endSession(); - } - } - - private void writeEvent(SelectionEvent event, long now) { - final long prevEventDelta = mLastEventTime == 0 ? 0 : now - mLastEventTime; - final LogMaker log = new LogMaker(MetricsEvent.TEXT_SELECTION_SESSION) - .setType(getLogType(event)) - .setSubtype(MetricsEvent.TEXT_SELECTION_INVOCATION_MANUAL) - .setPackageName(mContext.getPackageName()) - .addTaggedData(START_EVENT_DELTA, now - mSessionStartTime) - .addTaggedData(PREV_EVENT_DELTA, prevEventDelta) - .addTaggedData(INDEX, mIndex) - .addTaggedData(WIDGET_TYPE, getWidgetTypeName()) - .addTaggedData(WIDGET_VERSION, mWidgetVersion) - .addTaggedData(MODEL_NAME, mModelName) - .addTaggedData(ENTITY_TYPE, event.mEntityType) - .addTaggedData(SMART_START, getSmartRangeDelta(mSmartIndices[0])) - .addTaggedData(SMART_END, getSmartRangeDelta(mSmartIndices[1])) - .addTaggedData(EVENT_START, getRangeDelta(event.mStart)) - .addTaggedData(EVENT_END, getRangeDelta(event.mEnd)) - .addTaggedData(SESSION_ID, mSessionId); - mMetricsLogger.write(log); - debugLog(log); - mLastEventTime = now; - mPrevIndices[0] = event.mStart; - mPrevIndices[1] = event.mEnd; - mIndex++; - } - - private String startNewSession() { - endSession(); - mSessionId = createSessionId(); - return mSessionId; - } - - private void endSession() { - // Reset fields. - mOrigStart = 0; - mSmartIndices[0] = mSmartIndices[1] = 0; - mPrevIndices[0] = mPrevIndices[1] = 0; - mIndex = 0; - mSessionStartTime = 0; - mLastEventTime = 0; - mSmartSelectionTriggered = false; - mModelName = getModelName(null); - mSessionId = null; - } - - private static int getLogType(SelectionEvent event) { - switch (event.mEventType) { - case SelectionEvent.ActionType.OVERTYPE: - return MetricsEvent.ACTION_TEXT_SELECTION_OVERTYPE; - case SelectionEvent.ActionType.COPY: - return MetricsEvent.ACTION_TEXT_SELECTION_COPY; - case SelectionEvent.ActionType.PASTE: - return MetricsEvent.ACTION_TEXT_SELECTION_PASTE; - case SelectionEvent.ActionType.CUT: - return MetricsEvent.ACTION_TEXT_SELECTION_CUT; - case SelectionEvent.ActionType.SHARE: - return MetricsEvent.ACTION_TEXT_SELECTION_SHARE; - case SelectionEvent.ActionType.SMART_SHARE: - return MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE; - case SelectionEvent.ActionType.DRAG: - return MetricsEvent.ACTION_TEXT_SELECTION_DRAG; - case SelectionEvent.ActionType.ABANDON: - return MetricsEvent.ACTION_TEXT_SELECTION_ABANDON; - case SelectionEvent.ActionType.OTHER: - return MetricsEvent.ACTION_TEXT_SELECTION_OTHER; - case SelectionEvent.ActionType.SELECT_ALL: - return MetricsEvent.ACTION_TEXT_SELECTION_SELECT_ALL; - case SelectionEvent.ActionType.RESET: - return MetricsEvent.ACTION_TEXT_SELECTION_RESET; - case SelectionEvent.EventType.SELECTION_STARTED: - return MetricsEvent.ACTION_TEXT_SELECTION_START; - case SelectionEvent.EventType.SELECTION_MODIFIED: - return MetricsEvent.ACTION_TEXT_SELECTION_MODIFY; - case SelectionEvent.EventType.SMART_SELECTION_SINGLE: - return MetricsEvent.ACTION_TEXT_SELECTION_SMART_SINGLE; - case SelectionEvent.EventType.SMART_SELECTION_MULTI: - return MetricsEvent.ACTION_TEXT_SELECTION_SMART_MULTI; - case SelectionEvent.EventType.AUTO_SELECTION: - return MetricsEvent.ACTION_TEXT_SELECTION_AUTO; - default: - return MetricsEvent.VIEW_UNKNOWN; - } - } - - private static String getLogTypeString(int logType) { - switch (logType) { - case MetricsEvent.ACTION_TEXT_SELECTION_OVERTYPE: - return "OVERTYPE"; - case MetricsEvent.ACTION_TEXT_SELECTION_COPY: - return "COPY"; - case MetricsEvent.ACTION_TEXT_SELECTION_PASTE: - return "PASTE"; - case MetricsEvent.ACTION_TEXT_SELECTION_CUT: - return "CUT"; - case MetricsEvent.ACTION_TEXT_SELECTION_SHARE: - return "SHARE"; - case MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE: - return "SMART_SHARE"; - case MetricsEvent.ACTION_TEXT_SELECTION_DRAG: - return "DRAG"; - case MetricsEvent.ACTION_TEXT_SELECTION_ABANDON: - return "ABANDON"; - case MetricsEvent.ACTION_TEXT_SELECTION_OTHER: - return "OTHER"; - case MetricsEvent.ACTION_TEXT_SELECTION_SELECT_ALL: - return "SELECT_ALL"; - case MetricsEvent.ACTION_TEXT_SELECTION_RESET: - return "RESET"; - case MetricsEvent.ACTION_TEXT_SELECTION_START: - return "SELECTION_STARTED"; - case MetricsEvent.ACTION_TEXT_SELECTION_MODIFY: - return "SELECTION_MODIFIED"; - case MetricsEvent.ACTION_TEXT_SELECTION_SMART_SINGLE: - return "SMART_SELECTION_SINGLE"; - case MetricsEvent.ACTION_TEXT_SELECTION_SMART_MULTI: - return "SMART_SELECTION_MULTI"; - case MetricsEvent.ACTION_TEXT_SELECTION_AUTO: - return "AUTO_SELECTION"; - default: - return UNKNOWN; - } - } - - private int getRangeDelta(int offset) { - return offset - mOrigStart; - } - - private int getSmartRangeDelta(int offset) { - return mSmartSelectionTriggered ? getRangeDelta(offset) : 0; - } - - private String getWidgetTypeName() { - switch (mWidgetType) { - case WidgetType.TEXTVIEW: - return TEXTVIEW; - case WidgetType.WEBVIEW: - return WEBVIEW; - case WidgetType.EDITTEXT: - return EDITTEXT; - case WidgetType.EDIT_WEBVIEW: - return EDIT_WEBVIEW; - case WidgetType.UNSELECTABLE_TEXTVIEW: - return UNSELECTABLE_TEXTVIEW; - case WidgetType.CUSTOM_TEXTVIEW: - return CUSTOM_TEXTVIEW; - case WidgetType.CUSTOM_EDITTEXT: - return CUSTOM_EDITTEXT; - case WidgetType.CUSTOM_UNSELECTABLE_TEXTVIEW: - return CUSTOM_UNSELECTABLE_TEXTVIEW; - default: - return UNKNOWN; - } - } - - private String getModelName(@Nullable SelectionEvent event) { - return event == null - ? SelectionEvent.NO_VERSION_TAG - : Objects.toString(event.mVersionTag, SelectionEvent.NO_VERSION_TAG); - } - - private static String createSessionId() { - return UUID.randomUUID().toString(); - } - - private static void debugLog(LogMaker log) { - if (!DEBUG_LOG_ENABLED) return; - - final String widgetType = Objects.toString(log.getTaggedData(WIDGET_TYPE), UNKNOWN); - final String widgetVersion = Objects.toString(log.getTaggedData(WIDGET_VERSION), ""); - final String widget = widgetVersion.isEmpty() - ? widgetType : widgetType + "-" + widgetVersion; - final int index = Integer.parseInt(Objects.toString(log.getTaggedData(INDEX), ZERO)); - if (log.getType() == MetricsEvent.ACTION_TEXT_SELECTION_START) { - String sessionId = Objects.toString(log.getTaggedData(SESSION_ID), ""); - sessionId = sessionId.substring(sessionId.lastIndexOf("-") + 1); - Log.d(LOG_TAG, String.format("New selection session: %s (%s)", widget, sessionId)); - } - - final String model = Objects.toString(log.getTaggedData(MODEL_NAME), UNKNOWN); - final String entity = Objects.toString(log.getTaggedData(ENTITY_TYPE), UNKNOWN); - final String type = getLogTypeString(log.getType()); - final int smartStart = Integer.parseInt( - Objects.toString(log.getTaggedData(SMART_START), ZERO)); - final int smartEnd = Integer.parseInt( - Objects.toString(log.getTaggedData(SMART_END), ZERO)); - final int eventStart = Integer.parseInt( - Objects.toString(log.getTaggedData(EVENT_START), ZERO)); - final int eventEnd = Integer.parseInt( - Objects.toString(log.getTaggedData(EVENT_END), ZERO)); - - Log.d(LOG_TAG, String.format("%2d: %s/%s, range=%d,%d - smart_range=%d,%d (%s/%s)", - index, type, entity, eventStart, eventEnd, smartStart, smartEnd, widget, model)); - } - - /** - * A selection event. - * Specify index parameters as word token indices. - */ - public static final class SelectionEvent { - - /** - * Use this to specify an indeterminate positive index. - */ - public static final int OUT_OF_BOUNDS = Integer.MAX_VALUE; - - /** - * Use this to specify an indeterminate negative index. - */ - public static final int OUT_OF_BOUNDS_NEGATIVE = Integer.MIN_VALUE; - - private static final String NO_VERSION_TAG = ""; - - @Retention(RetentionPolicy.SOURCE) - @IntDef({ActionType.OVERTYPE, ActionType.COPY, ActionType.PASTE, ActionType.CUT, - ActionType.SHARE, ActionType.SMART_SHARE, ActionType.DRAG, ActionType.ABANDON, - ActionType.OTHER, ActionType.SELECT_ALL, ActionType.RESET}) - public @interface ActionType { - /** User typed over the selection. */ - int OVERTYPE = 100; - /** User copied the selection. */ - int COPY = 101; - /** User pasted over the selection. */ - int PASTE = 102; - /** User cut the selection. */ - int CUT = 103; - /** User shared the selection. */ - int SHARE = 104; - /** User clicked the textAssist menu item. */ - int SMART_SHARE = 105; - /** User dragged+dropped the selection. */ - int DRAG = 106; - /** User abandoned the selection. */ - int ABANDON = 107; - /** User performed an action on the selection. */ - int OTHER = 108; - - /* Non-terminal actions. */ - /** User activated Select All */ - int SELECT_ALL = 200; - /** User reset the smart selection. */ - int RESET = 201; - } - - @Retention(RetentionPolicy.SOURCE) - @IntDef({ActionType.OVERTYPE, ActionType.COPY, ActionType.PASTE, ActionType.CUT, - ActionType.SHARE, ActionType.SMART_SHARE, ActionType.DRAG, ActionType.ABANDON, - ActionType.OTHER, ActionType.SELECT_ALL, ActionType.RESET, - EventType.SELECTION_STARTED, EventType.SELECTION_MODIFIED, - EventType.SMART_SELECTION_SINGLE, EventType.SMART_SELECTION_MULTI, - EventType.AUTO_SELECTION}) - private @interface EventType { - /** User started a new selection. */ - int SELECTION_STARTED = 1; - /** User modified an existing selection. */ - int SELECTION_MODIFIED = 2; - /** Smart selection triggered for a single token (word). */ - int SMART_SELECTION_SINGLE = 3; - /** Smart selection triggered spanning multiple tokens (words). */ - int SMART_SELECTION_MULTI = 4; - /** Something else other than User or the default TextClassifier triggered a selection. */ - int AUTO_SELECTION = 5; - } - - private final int mStart; - private final int mEnd; - private @EventType int mEventType; - private final @TextClassifier.EntityType String mEntityType; - private final String mVersionTag; - - private SelectionEvent( - int start, int end, int eventType, - @TextClassifier.EntityType String entityType, String versionTag) { - Preconditions.checkArgument(end >= start, "end cannot be less than start"); - mStart = start; - mEnd = end; - mEventType = eventType; - mEntityType = Objects.requireNonNull(entityType); - mVersionTag = Objects.requireNonNull(versionTag); - } - - /** - * Creates a "selection started" event. - * - * @param start the word index of the selected word - */ - @UnsupportedAppUsage(trackingBug = 136637107, maxTargetSdk = Build.VERSION_CODES.Q, - publicAlternatives = "See {@link android.view.textclassifier.TextClassifier}.") - public static SelectionEvent selectionStarted(int start) { - return new SelectionEvent( - start, start + 1, EventType.SELECTION_STARTED, - TextClassifier.TYPE_UNKNOWN, NO_VERSION_TAG); - } - - /** - * Creates a "selection modified" event. - * Use when the user modifies the selection. - * - * @param start the start word (inclusive) index of the selection - * @param end the end word (exclusive) index of the selection - */ - @UnsupportedAppUsage(trackingBug = 136637107, maxTargetSdk = Build.VERSION_CODES.Q, - publicAlternatives = "See {@link android.view.textclassifier.TextClassifier}.") - public static SelectionEvent selectionModified(int start, int end) { - return new SelectionEvent( - start, end, EventType.SELECTION_MODIFIED, - TextClassifier.TYPE_UNKNOWN, NO_VERSION_TAG); - } - - /** - * Creates a "selection modified" event. - * Use when the user modifies the selection and the selection's entity type is known. - * - * @param start the start word (inclusive) index of the selection - * @param end the end word (exclusive) index of the selection - * @param classification the TextClassification object returned by the TextClassifier that - * classified the selected text - */ - @UnsupportedAppUsage(trackingBug = 136637107, maxTargetSdk = Build.VERSION_CODES.Q, - publicAlternatives = "See {@link android.view.textclassifier.TextClassifier}.") - public static SelectionEvent selectionModified( - int start, int end, @NonNull TextClassification classification) { - final String entityType = classification.getEntityCount() > 0 - ? classification.getEntity(0) - : TextClassifier.TYPE_UNKNOWN; - final String versionTag = getVersionInfo(classification.getId()); - return new SelectionEvent( - start, end, EventType.SELECTION_MODIFIED, entityType, versionTag); - } - - /** - * Creates a "selection modified" event. - * Use when a TextClassifier modifies the selection. - * - * @param start the start word (inclusive) index of the selection - * @param end the end word (exclusive) index of the selection - * @param selection the TextSelection object returned by the TextClassifier for the - * specified selection - */ - @UnsupportedAppUsage(trackingBug = 136637107, maxTargetSdk = Build.VERSION_CODES.Q, - publicAlternatives = "See {@link android.view.textclassifier.TextClassifier}.") - public static SelectionEvent selectionModified( - int start, int end, @NonNull TextSelection selection) { - final boolean smartSelection = getSourceClassifier(selection.getId()) - .equals(TextClassifier.DEFAULT_LOG_TAG); - final int eventType; - if (smartSelection) { - eventType = end - start > 1 - ? EventType.SMART_SELECTION_MULTI - : EventType.SMART_SELECTION_SINGLE; - - } else { - eventType = EventType.AUTO_SELECTION; - } - final String entityType = selection.getEntityCount() > 0 - ? selection.getEntity(0) - : TextClassifier.TYPE_UNKNOWN; - final String versionTag = getVersionInfo(selection.getId()); - return new SelectionEvent(start, end, eventType, entityType, versionTag); - } - - /** - * Creates an event specifying an action taken on a selection. - * Use when the user clicks on an action to act on the selected text. - * - * @param start the start word (inclusive) index of the selection - * @param end the end word (exclusive) index of the selection - * @param actionType the action that was performed on the selection - */ - @UnsupportedAppUsage(trackingBug = 136637107, maxTargetSdk = Build.VERSION_CODES.Q, - publicAlternatives = "See {@link android.view.textclassifier.TextClassifier}.") - public static SelectionEvent selectionAction( - int start, int end, @ActionType int actionType) { - return new SelectionEvent( - start, end, actionType, TextClassifier.TYPE_UNKNOWN, NO_VERSION_TAG); - } - - /** - * Creates an event specifying an action taken on a selection. - * Use when the user clicks on an action to act on the selected text and the selection's - * entity type is known. - * - * @param start the start word (inclusive) index of the selection - * @param end the end word (exclusive) index of the selection - * @param actionType the action that was performed on the selection - * @param classification the TextClassification object returned by the TextClassifier that - * classified the selected text - */ - @UnsupportedAppUsage(trackingBug = 136637107, maxTargetSdk = Build.VERSION_CODES.Q, - publicAlternatives = "See {@link android.view.textclassifier.TextClassifier}.") - public static SelectionEvent selectionAction( - int start, int end, @ActionType int actionType, - @NonNull TextClassification classification) { - final String entityType = classification.getEntityCount() > 0 - ? classification.getEntity(0) - : TextClassifier.TYPE_UNKNOWN; - final String versionTag = getVersionInfo(classification.getId()); - return new SelectionEvent(start, end, actionType, entityType, versionTag); - } - - @VisibleForTesting - public static String getVersionInfo(String signature) { - final int start = signature.indexOf("|") + 1; - final int end = signature.indexOf("|", start); - if (start >= 1 && end >= start) { - return signature.substring(start, end); - } - return ""; - } - - private static String getSourceClassifier(String signature) { - final int end = signature.indexOf("|"); - if (end >= 0) { - return signature.substring(0, end); - } - return ""; - } - - private boolean isTerminal() { - switch (mEventType) { - case ActionType.OVERTYPE: // fall through - case ActionType.COPY: // fall through - case ActionType.PASTE: // fall through - case ActionType.CUT: // fall through - case ActionType.SHARE: // fall through - case ActionType.SMART_SHARE: // fall through - case ActionType.DRAG: // fall through - case ActionType.ABANDON: // fall through - case ActionType.OTHER: // fall through - return true; - default: - return false; - } - } - } -} diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java index 4ef3f61b5280f..45943f512c22f 100644 --- a/core/java/android/widget/SelectionActionModeHelper.java +++ b/core/java/android/widget/SelectionActionModeHelper.java @@ -39,7 +39,6 @@ import android.view.ActionMode; import android.view.textclassifier.ExtrasUtils; import android.view.textclassifier.SelectionEvent; import android.view.textclassifier.SelectionEvent.InvocationMethod; -import android.view.textclassifier.SelectionSessionLogger; import android.view.textclassifier.TextClassification; import android.view.textclassifier.TextClassificationConstants; import android.view.textclassifier.TextClassificationContext; @@ -705,7 +704,7 @@ public final class SelectionActionModeHelper { SelectionMetricsLogger(TextView textView) { Objects.requireNonNull(textView); mEditTextLogger = textView.isTextEditable(); - mTokenIterator = SelectionSessionLogger.getTokenIterator(textView.getTextLocale()); + mTokenIterator = BreakIterator.getWordInstance(textView.getTextLocale()); } public void logSelectionStarted( diff --git a/core/tests/coretests/src/android/view/textclassifier/ActionsModelParamsSupplierTest.java b/core/tests/coretests/src/android/view/textclassifier/ActionsModelParamsSupplierTest.java deleted file mode 100644 index 87449979704d4..0000000000000 --- a/core/tests/coretests/src/android/view/textclassifier/ActionsModelParamsSupplierTest.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.view.textclassifier; - -import static com.google.common.truth.Truth.assertThat; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.File; -import java.util.Collections; -import java.util.Locale; - -@SmallTest -@RunWith(AndroidJUnit4.class) -public class ActionsModelParamsSupplierTest { - - @Test - public void getSerializedPreconditions_validActionsModelParams() { - ModelFileManager.ModelFile modelFile = new ModelFileManager.ModelFile( - new File("/model/file"), - 200 /* version */, - Collections.singletonList(Locale.forLanguageTag("en")), - "en", - false); - byte[] serializedPreconditions = new byte[]{0x12, 0x24, 0x36}; - ActionsModelParamsSupplier.ActionsModelParams params = - new ActionsModelParamsSupplier.ActionsModelParams( - 200 /* version */, - "en", - serializedPreconditions); - - byte[] actual = params.getSerializedPreconditions(modelFile); - - assertThat(actual).isEqualTo(serializedPreconditions); - } - - @Test - public void getSerializedPreconditions_invalidVersion() { - ModelFileManager.ModelFile modelFile = new ModelFileManager.ModelFile( - new File("/model/file"), - 201 /* version */, - Collections.singletonList(Locale.forLanguageTag("en")), - "en", - false); - byte[] serializedPreconditions = new byte[]{0x12, 0x24, 0x36}; - ActionsModelParamsSupplier.ActionsModelParams params = - new ActionsModelParamsSupplier.ActionsModelParams( - 200 /* version */, - "en", - serializedPreconditions); - - byte[] actual = params.getSerializedPreconditions(modelFile); - - assertThat(actual).isNull(); - } - - @Test - public void getSerializedPreconditions_invalidLocales() { - final String LANGUAGE_TAG = "zh"; - ModelFileManager.ModelFile modelFile = new ModelFileManager.ModelFile( - new File("/model/file"), - 200 /* version */, - Collections.singletonList(Locale.forLanguageTag(LANGUAGE_TAG)), - LANGUAGE_TAG, - false); - byte[] serializedPreconditions = new byte[]{0x12, 0x24, 0x36}; - ActionsModelParamsSupplier.ActionsModelParams params = - new ActionsModelParamsSupplier.ActionsModelParams( - 200 /* version */, - "en", - serializedPreconditions); - - byte[] actual = params.getSerializedPreconditions(modelFile); - - assertThat(actual).isNull(); - } - -} diff --git a/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java b/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java deleted file mode 100644 index ec7e83f1f2439..0000000000000 --- a/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java +++ /dev/null @@ -1,331 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.textclassifier; - -import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_OTHERS; -import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_SELF; - -import static com.google.common.truth.Truth.assertThat; - -import android.app.PendingIntent; -import android.app.Person; -import android.app.RemoteAction; -import android.content.ComponentName; -import android.content.Intent; -import android.graphics.drawable.Icon; -import android.net.Uri; -import android.os.Bundle; -import android.view.textclassifier.intent.LabeledIntent; -import android.view.textclassifier.intent.TemplateIntentFactory; - -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import com.google.android.textclassifier.ActionsSuggestionsModel; -import com.google.android.textclassifier.RemoteActionTemplate; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.time.Instant; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.function.Function; - -@SmallTest -@RunWith(AndroidJUnit4.class) -public class ActionsSuggestionsHelperTest { - private static final String LOCALE_TAG = Locale.US.toLanguageTag(); - private static final Function LANGUAGE_DETECTOR = - charSequence -> LOCALE_TAG; - - @Test - public void testToNativeMessages_emptyInput() { - ActionsSuggestionsModel.ConversationMessage[] conversationMessages = - ActionsSuggestionsHelper.toNativeMessages( - Collections.emptyList(), LANGUAGE_DETECTOR); - - assertThat(conversationMessages).isEmpty(); - } - - @Test - public void testToNativeMessages_noTextMessages() { - ConversationActions.Message messageWithoutText = - new ConversationActions.Message.Builder(PERSON_USER_OTHERS).build(); - - ActionsSuggestionsModel.ConversationMessage[] conversationMessages = - ActionsSuggestionsHelper.toNativeMessages( - Collections.singletonList(messageWithoutText), LANGUAGE_DETECTOR); - - assertThat(conversationMessages).isEmpty(); - } - - @Test - public void testToNativeMessages_userIdEncoding() { - Person userA = new Person.Builder().setName("userA").build(); - Person userB = new Person.Builder().setName("userB").build(); - - ConversationActions.Message firstMessage = - new ConversationActions.Message.Builder(userB) - .setText("first") - .build(); - ConversationActions.Message secondMessage = - new ConversationActions.Message.Builder(userA) - .setText("second") - .build(); - ConversationActions.Message thirdMessage = - new ConversationActions.Message.Builder(PERSON_USER_SELF) - .setText("third") - .build(); - ConversationActions.Message fourthMessage = - new ConversationActions.Message.Builder(userA) - .setText("fourth") - .build(); - - ActionsSuggestionsModel.ConversationMessage[] conversationMessages = - ActionsSuggestionsHelper.toNativeMessages( - Arrays.asList(firstMessage, secondMessage, thirdMessage, fourthMessage), - LANGUAGE_DETECTOR); - - assertThat(conversationMessages).hasLength(4); - assertNativeMessage(conversationMessages[0], firstMessage.getText(), 2, 0); - assertNativeMessage(conversationMessages[1], secondMessage.getText(), 1, 0); - assertNativeMessage(conversationMessages[2], thirdMessage.getText(), 0, 0); - assertNativeMessage(conversationMessages[3], fourthMessage.getText(), 1, 0); - } - - @Test - public void testToNativeMessages_referenceTime() { - ConversationActions.Message firstMessage = - new ConversationActions.Message.Builder(PERSON_USER_OTHERS) - .setText("first") - .setReferenceTime(createZonedDateTimeFromMsUtc(1000)) - .build(); - ConversationActions.Message secondMessage = - new ConversationActions.Message.Builder(PERSON_USER_OTHERS) - .setText("second") - .build(); - ConversationActions.Message thirdMessage = - new ConversationActions.Message.Builder(PERSON_USER_OTHERS) - .setText("third") - .setReferenceTime(createZonedDateTimeFromMsUtc(2000)) - .build(); - - ActionsSuggestionsModel.ConversationMessage[] conversationMessages = - ActionsSuggestionsHelper.toNativeMessages( - Arrays.asList(firstMessage, secondMessage, thirdMessage), - LANGUAGE_DETECTOR); - - assertThat(conversationMessages).hasLength(3); - assertNativeMessage(conversationMessages[0], firstMessage.getText(), 1, 1000); - assertNativeMessage(conversationMessages[1], secondMessage.getText(), 1, 0); - assertNativeMessage(conversationMessages[2], thirdMessage.getText(), 1, 2000); - } - - @Test - public void testDeduplicateActions() { - Bundle phoneExtras = new Bundle(); - Intent phoneIntent = new Intent(); - phoneIntent.setComponent(new ComponentName("phone", "intent")); - ExtrasUtils.putActionIntent(phoneExtras, phoneIntent); - - Bundle anotherPhoneExtras = new Bundle(); - Intent anotherPhoneIntent = new Intent(); - anotherPhoneIntent.setComponent(new ComponentName("phone", "another.intent")); - ExtrasUtils.putActionIntent(anotherPhoneExtras, anotherPhoneIntent); - - Bundle urlExtras = new Bundle(); - Intent urlIntent = new Intent(); - urlIntent.setComponent(new ComponentName("url", "intent")); - ExtrasUtils.putActionIntent(urlExtras, urlIntent); - - PendingIntent pendingIntent = PendingIntent.getActivity( - InstrumentationRegistry.getTargetContext(), - 0, - phoneIntent, - 0); - Icon icon = Icon.createWithData(new byte[0], 0, 0); - ConversationAction action = - new ConversationAction.Builder(ConversationAction.TYPE_CALL_PHONE) - .setAction(new RemoteAction(icon, "label", "1", pendingIntent)) - .setExtras(phoneExtras) - .build(); - ConversationAction actionWithSameLabel = - new ConversationAction.Builder(ConversationAction.TYPE_CALL_PHONE) - .setAction(new RemoteAction( - icon, "label", "2", pendingIntent)) - .setExtras(phoneExtras) - .build(); - ConversationAction actionWithSamePackageButDifferentClass = - new ConversationAction.Builder(ConversationAction.TYPE_CALL_PHONE) - .setAction(new RemoteAction( - icon, "label", "3", pendingIntent)) - .setExtras(anotherPhoneExtras) - .build(); - ConversationAction actionWithDifferentLabel = - new ConversationAction.Builder(ConversationAction.TYPE_CALL_PHONE) - .setAction(new RemoteAction( - icon, "another_label", "4", pendingIntent)) - .setExtras(phoneExtras) - .build(); - ConversationAction actionWithDifferentPackage = - new ConversationAction.Builder(ConversationAction.TYPE_OPEN_URL) - .setAction(new RemoteAction(icon, "label", "5", pendingIntent)) - .setExtras(urlExtras) - .build(); - ConversationAction actionWithoutRemoteAction = - new ConversationAction.Builder(ConversationAction.TYPE_CREATE_REMINDER) - .build(); - - List conversationActions = - ActionsSuggestionsHelper.removeActionsWithDuplicates( - Arrays.asList(action, actionWithSameLabel, - actionWithSamePackageButDifferentClass, actionWithDifferentLabel, - actionWithDifferentPackage, actionWithoutRemoteAction)); - - assertThat(conversationActions).hasSize(3); - assertThat(conversationActions.get(0).getAction().getContentDescription()).isEqualTo("4"); - assertThat(conversationActions.get(1).getAction().getContentDescription()).isEqualTo("5"); - assertThat(conversationActions.get(2).getAction()).isNull(); - } - - @Test - public void testDeduplicateActions_nullComponent() { - Bundle phoneExtras = new Bundle(); - Intent phoneIntent = new Intent(Intent.ACTION_DIAL); - ExtrasUtils.putActionIntent(phoneExtras, phoneIntent); - PendingIntent pendingIntent = PendingIntent.getActivity( - InstrumentationRegistry.getTargetContext(), - 0, - phoneIntent, - 0); - Icon icon = Icon.createWithData(new byte[0], 0, 0); - ConversationAction action = - new ConversationAction.Builder(ConversationAction.TYPE_CALL_PHONE) - .setAction(new RemoteAction(icon, "label", "1", pendingIntent)) - .setExtras(phoneExtras) - .build(); - ConversationAction actionWithSameLabel = - new ConversationAction.Builder(ConversationAction.TYPE_CALL_PHONE) - .setAction(new RemoteAction( - icon, "label", "2", pendingIntent)) - .setExtras(phoneExtras) - .build(); - - List conversationActions = - ActionsSuggestionsHelper.removeActionsWithDuplicates( - Arrays.asList(action, actionWithSameLabel)); - - assertThat(conversationActions).isEmpty(); - } - - @Test - public void createLabeledIntentResult_null() { - ActionsSuggestionsModel.ActionSuggestion nativeSuggestion = - new ActionsSuggestionsModel.ActionSuggestion( - "text", - ConversationAction.TYPE_OPEN_URL, - 1.0f, - null, - null, - null - ); - - LabeledIntent.Result labeledIntentResult = - ActionsSuggestionsHelper.createLabeledIntentResult( - InstrumentationRegistry.getTargetContext(), - new TemplateIntentFactory(), - nativeSuggestion); - - assertThat(labeledIntentResult).isNull(); - } - - @Test - public void createLabeledIntentResult_emptyList() { - ActionsSuggestionsModel.ActionSuggestion nativeSuggestion = - new ActionsSuggestionsModel.ActionSuggestion( - "text", - ConversationAction.TYPE_OPEN_URL, - 1.0f, - null, - null, - new RemoteActionTemplate[0] - ); - - LabeledIntent.Result labeledIntentResult = - ActionsSuggestionsHelper.createLabeledIntentResult( - InstrumentationRegistry.getTargetContext(), - new TemplateIntentFactory(), - nativeSuggestion); - - assertThat(labeledIntentResult).isNull(); - } - - @Test - public void createLabeledIntentResult() { - ActionsSuggestionsModel.ActionSuggestion nativeSuggestion = - new ActionsSuggestionsModel.ActionSuggestion( - "text", - ConversationAction.TYPE_OPEN_URL, - 1.0f, - null, - null, - new RemoteActionTemplate[]{ - new RemoteActionTemplate( - "title", - null, - "description", - null, - Intent.ACTION_VIEW, - Uri.parse("http://www.android.com").toString(), - null, - 0, - null, - null, - null, - 0)}); - - LabeledIntent.Result labeledIntentResult = - ActionsSuggestionsHelper.createLabeledIntentResult( - InstrumentationRegistry.getTargetContext(), - new TemplateIntentFactory(), - nativeSuggestion); - - assertThat(labeledIntentResult.remoteAction.getTitle()).isEqualTo("title"); - assertThat(labeledIntentResult.resolvedIntent.getAction()).isEqualTo(Intent.ACTION_VIEW); - } - - private ZonedDateTime createZonedDateTimeFromMsUtc(long msUtc) { - return ZonedDateTime.ofInstant(Instant.ofEpochMilli(msUtc), ZoneId.of("UTC")); - } - - private static void assertNativeMessage( - ActionsSuggestionsModel.ConversationMessage nativeMessage, - CharSequence text, - int userId, - long referenceTimeInMsUtc) { - assertThat(nativeMessage.getText()).isEqualTo(text.toString()); - assertThat(nativeMessage.getUserId()).isEqualTo(userId); - assertThat(nativeMessage.getDetectedTextLanguageTags()).isEqualTo(LOCALE_TAG); - assertThat(nativeMessage.getReferenceTimeMsUtc()).isEqualTo(referenceTimeInMsUtc); - } -} diff --git a/core/tests/coretests/src/android/view/textclassifier/ModelFileManagerTest.java b/core/tests/coretests/src/android/view/textclassifier/ModelFileManagerTest.java deleted file mode 100644 index 79e1406f6ae69..0000000000000 --- a/core/tests/coretests/src/android/view/textclassifier/ModelFileManagerTest.java +++ /dev/null @@ -1,350 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.textclassifier; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.when; - -import android.os.LocaleList; - -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.io.File; -import java.io.IOException; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -@SmallTest -@RunWith(AndroidJUnit4.class) -public class ModelFileManagerTest { - private static final Locale DEFAULT_LOCALE = Locale.forLanguageTag("en-US"); - @Mock - private Supplier> mModelFileSupplier; - private ModelFileManager.ModelFileSupplierImpl mModelFileSupplierImpl; - private ModelFileManager mModelFileManager; - private File mRootTestDir; - private File mFactoryModelDir; - private File mUpdatedModelFile; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - mModelFileManager = new ModelFileManager(mModelFileSupplier); - mRootTestDir = InstrumentationRegistry.getContext().getCacheDir(); - mFactoryModelDir = new File(mRootTestDir, "factory"); - mUpdatedModelFile = new File(mRootTestDir, "updated.model"); - - mModelFileSupplierImpl = - new ModelFileManager.ModelFileSupplierImpl( - mFactoryModelDir, - "test\\d.model", - mUpdatedModelFile, - fd -> 1, - fd -> ModelFileManager.ModelFile.LANGUAGE_INDEPENDENT - ); - - mRootTestDir.mkdirs(); - mFactoryModelDir.mkdirs(); - - Locale.setDefault(DEFAULT_LOCALE); - } - - @After - public void removeTestDir() { - recursiveDelete(mRootTestDir); - } - - @Test - public void get() { - ModelFileManager.ModelFile modelFile = - new ModelFileManager.ModelFile( - new File("/path/a"), 1, Collections.emptyList(), "", true); - when(mModelFileSupplier.get()).thenReturn(Collections.singletonList(modelFile)); - - List modelFiles = mModelFileManager.listModelFiles(); - - assertThat(modelFiles).hasSize(1); - assertThat(modelFiles.get(0)).isEqualTo(modelFile); - } - - @Test - public void findBestModel_versionCode() { - ModelFileManager.ModelFile olderModelFile = - new ModelFileManager.ModelFile( - new File("/path/a"), 1, - Collections.emptyList(), "", true); - - ModelFileManager.ModelFile newerModelFile = - new ModelFileManager.ModelFile( - new File("/path/b"), 2, - Collections.emptyList(), "", true); - when(mModelFileSupplier.get()) - .thenReturn(Arrays.asList(olderModelFile, newerModelFile)); - - ModelFileManager.ModelFile bestModelFile = - mModelFileManager.findBestModelFile(LocaleList.getEmptyLocaleList()); - - assertThat(bestModelFile).isEqualTo(newerModelFile); - } - - @Test - public void findBestModel_languageDependentModelIsPreferred() { - Locale locale = Locale.forLanguageTag("ja"); - ModelFileManager.ModelFile languageIndependentModelFile = - new ModelFileManager.ModelFile( - new File("/path/a"), 1, - Collections.emptyList(), "", true); - - ModelFileManager.ModelFile languageDependentModelFile = - new ModelFileManager.ModelFile( - new File("/path/b"), 1, - Collections.singletonList(locale), locale.toLanguageTag(), false); - when(mModelFileSupplier.get()) - .thenReturn( - Arrays.asList(languageIndependentModelFile, languageDependentModelFile)); - - ModelFileManager.ModelFile bestModelFile = - mModelFileManager.findBestModelFile( - LocaleList.forLanguageTags(locale.toLanguageTag())); - assertThat(bestModelFile).isEqualTo(languageDependentModelFile); - } - - @Test - public void findBestModel_noMatchedLanguageModel() { - Locale locale = Locale.forLanguageTag("ja"); - ModelFileManager.ModelFile languageIndependentModelFile = - new ModelFileManager.ModelFile( - new File("/path/a"), 1, - Collections.emptyList(), "", true); - - ModelFileManager.ModelFile languageDependentModelFile = - new ModelFileManager.ModelFile( - new File("/path/b"), 1, - Collections.singletonList(locale), locale.toLanguageTag(), false); - - when(mModelFileSupplier.get()) - .thenReturn( - Arrays.asList(languageIndependentModelFile, languageDependentModelFile)); - - ModelFileManager.ModelFile bestModelFile = - mModelFileManager.findBestModelFile( - LocaleList.forLanguageTags("zh-hk")); - assertThat(bestModelFile).isEqualTo(languageIndependentModelFile); - } - - @Test - public void findBestModel_noMatchedLanguageModel_defaultLocaleModelExists() { - ModelFileManager.ModelFile languageIndependentModelFile = - new ModelFileManager.ModelFile( - new File("/path/a"), 1, - Collections.emptyList(), "", true); - - ModelFileManager.ModelFile languageDependentModelFile = - new ModelFileManager.ModelFile( - new File("/path/b"), 1, - Collections.singletonList( - DEFAULT_LOCALE), DEFAULT_LOCALE.toLanguageTag(), false); - - when(mModelFileSupplier.get()) - .thenReturn( - Arrays.asList(languageIndependentModelFile, languageDependentModelFile)); - - ModelFileManager.ModelFile bestModelFile = - mModelFileManager.findBestModelFile( - LocaleList.forLanguageTags("zh-hk")); - assertThat(bestModelFile).isEqualTo(languageIndependentModelFile); - } - - @Test - public void findBestModel_languageIsMoreImportantThanVersion() { - ModelFileManager.ModelFile matchButOlderModel = - new ModelFileManager.ModelFile( - new File("/path/a"), 1, - Collections.singletonList(Locale.forLanguageTag("fr")), "fr", false); - - ModelFileManager.ModelFile mismatchButNewerModel = - new ModelFileManager.ModelFile( - new File("/path/b"), 2, - Collections.singletonList(Locale.forLanguageTag("ja")), "ja", false); - - when(mModelFileSupplier.get()) - .thenReturn( - Arrays.asList(matchButOlderModel, mismatchButNewerModel)); - - ModelFileManager.ModelFile bestModelFile = - mModelFileManager.findBestModelFile( - LocaleList.forLanguageTags("fr")); - assertThat(bestModelFile).isEqualTo(matchButOlderModel); - } - - @Test - public void findBestModel_languageIsMoreImportantThanVersion_bestModelComesFirst() { - ModelFileManager.ModelFile matchLocaleModel = - new ModelFileManager.ModelFile( - new File("/path/b"), 1, - Collections.singletonList(Locale.forLanguageTag("ja")), "ja", false); - - ModelFileManager.ModelFile languageIndependentModel = - new ModelFileManager.ModelFile( - new File("/path/a"), 2, - Collections.emptyList(), "", true); - when(mModelFileSupplier.get()) - .thenReturn( - Arrays.asList(matchLocaleModel, languageIndependentModel)); - - ModelFileManager.ModelFile bestModelFile = - mModelFileManager.findBestModelFile( - LocaleList.forLanguageTags("ja")); - - assertThat(bestModelFile).isEqualTo(matchLocaleModel); - } - - @Test - public void modelFileEquals() { - ModelFileManager.ModelFile modelA = - new ModelFileManager.ModelFile( - new File("/path/a"), 1, - Collections.singletonList(Locale.forLanguageTag("ja")), "ja", false); - - ModelFileManager.ModelFile modelB = - new ModelFileManager.ModelFile( - new File("/path/a"), 1, - Collections.singletonList(Locale.forLanguageTag("ja")), "ja", false); - - assertThat(modelA).isEqualTo(modelB); - } - - @Test - public void modelFile_different() { - ModelFileManager.ModelFile modelA = - new ModelFileManager.ModelFile( - new File("/path/a"), 1, - Collections.singletonList(Locale.forLanguageTag("ja")), "ja", false); - - ModelFileManager.ModelFile modelB = - new ModelFileManager.ModelFile( - new File("/path/b"), 1, - Collections.singletonList(Locale.forLanguageTag("ja")), "ja", false); - - assertThat(modelA).isNotEqualTo(modelB); - } - - - @Test - public void modelFile_getPath() { - ModelFileManager.ModelFile modelA = - new ModelFileManager.ModelFile( - new File("/path/a"), 1, - Collections.singletonList(Locale.forLanguageTag("ja")), "ja", false); - - assertThat(modelA.getPath()).isEqualTo("/path/a"); - } - - @Test - public void modelFile_getName() { - ModelFileManager.ModelFile modelA = - new ModelFileManager.ModelFile( - new File("/path/a"), 1, - Collections.singletonList(Locale.forLanguageTag("ja")), "ja", false); - - assertThat(modelA.getName()).isEqualTo("a"); - } - - @Test - public void modelFile_isPreferredTo_languageDependentIsBetter() { - ModelFileManager.ModelFile modelA = - new ModelFileManager.ModelFile( - new File("/path/a"), 1, - Collections.singletonList(Locale.forLanguageTag("ja")), "ja", false); - - ModelFileManager.ModelFile modelB = - new ModelFileManager.ModelFile( - new File("/path/b"), 2, - Collections.emptyList(), "", true); - - assertThat(modelA.isPreferredTo(modelB)).isTrue(); - } - - @Test - public void modelFile_isPreferredTo_version() { - ModelFileManager.ModelFile modelA = - new ModelFileManager.ModelFile( - new File("/path/a"), 2, - Collections.singletonList(Locale.forLanguageTag("ja")), "ja", false); - - ModelFileManager.ModelFile modelB = - new ModelFileManager.ModelFile( - new File("/path/b"), 1, - Collections.emptyList(), "", false); - - assertThat(modelA.isPreferredTo(modelB)).isTrue(); - } - - @Test - public void testFileSupplierImpl_updatedFileOnly() throws IOException { - mUpdatedModelFile.createNewFile(); - File model1 = new File(mFactoryModelDir, "test1.model"); - model1.createNewFile(); - File model2 = new File(mFactoryModelDir, "test2.model"); - model2.createNewFile(); - new File(mFactoryModelDir, "not_match_regex.model").createNewFile(); - - List modelFiles = mModelFileSupplierImpl.get(); - List modelFilePaths = - modelFiles - .stream() - .map(modelFile -> modelFile.getPath()) - .collect(Collectors.toList()); - - assertThat(modelFiles).hasSize(3); - assertThat(modelFilePaths).containsExactly( - mUpdatedModelFile.getAbsolutePath(), - model1.getAbsolutePath(), - model2.getAbsolutePath()); - } - - @Test - public void testFileSupplierImpl_empty() { - mFactoryModelDir.delete(); - List modelFiles = mModelFileSupplierImpl.get(); - - assertThat(modelFiles).hasSize(0); - } - - private static void recursiveDelete(File f) { - if (f.isDirectory()) { - for (File innerFile : f.listFiles()) { - recursiveDelete(innerFile); - } - } - f.delete(); - } -} diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java index 82fa73f762079..2f21b7f0c75a5 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java @@ -16,7 +16,6 @@ package android.view.textclassifier; -import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import android.provider.DeviceConfig; @@ -24,8 +23,6 @@ import android.provider.DeviceConfig; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.google.common.primitives.Floats; - import org.junit.Test; import org.junit.runner.RunWith; @@ -57,17 +54,17 @@ public class TextClassificationConstantsTest { public void testLoadFromDeviceConfig_IntValue() throws Exception { // Saves config original value. final String originalValue = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, - TextClassificationConstants.SUGGEST_SELECTION_MAX_RANGE_LENGTH); + TextClassificationConstants.GENERATE_LINKS_MAX_TEXT_LENGTH); final TextClassificationConstants constants = new TextClassificationConstants(); try { // Sets and checks different value. - setDeviceConfig(TextClassificationConstants.SUGGEST_SELECTION_MAX_RANGE_LENGTH, "8"); - assertWithMessage(TextClassificationConstants.SUGGEST_SELECTION_MAX_RANGE_LENGTH) - .that(constants.getSuggestSelectionMaxRangeLength()).isEqualTo(8); + setDeviceConfig(TextClassificationConstants.GENERATE_LINKS_MAX_TEXT_LENGTH, "8"); + assertWithMessage(TextClassificationConstants.GENERATE_LINKS_MAX_TEXT_LENGTH) + .that(constants.getGenerateLinksMaxTextLength()).isEqualTo(8); } finally { // Restores config original value. - setDeviceConfig(TextClassificationConstants.SUGGEST_SELECTION_MAX_RANGE_LENGTH, + setDeviceConfig(TextClassificationConstants.GENERATE_LINKS_MAX_TEXT_LENGTH, originalValue); } } @@ -94,61 +91,6 @@ public class TextClassificationConstantsTest { } } - @Test - public void testLoadFromDeviceConfig_FloatValue() throws Exception { - // Saves config original value. - final String originalValue = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, - TextClassificationConstants.LANG_ID_THRESHOLD_OVERRIDE); - - final TextClassificationConstants constants = new TextClassificationConstants(); - try { - // Sets and checks different value. - setDeviceConfig(TextClassificationConstants.LANG_ID_THRESHOLD_OVERRIDE, "2"); - assertWithMessage(TextClassificationConstants.LANG_ID_THRESHOLD_OVERRIDE) - .that(constants.getLangIdThresholdOverride()).isWithin(EPSILON).of(2f); - } finally { - // Restores config original value. - setDeviceConfig(TextClassificationConstants.LANG_ID_THRESHOLD_OVERRIDE, originalValue); - } - } - - @Test - public void testLoadFromDeviceConfig_StringList() throws Exception { - // Saves config original value. - final String originalValue = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, - TextClassificationConstants.ENTITY_LIST_DEFAULT); - - final TextClassificationConstants constants = new TextClassificationConstants(); - try { - // Sets and checks different value. - setDeviceConfig(TextClassificationConstants.ENTITY_LIST_DEFAULT, "email:url"); - assertWithMessage(TextClassificationConstants.ENTITY_LIST_DEFAULT) - .that(constants.getEntityListDefault()) - .containsExactly("email", "url"); - } finally { - // Restores config original value. - setDeviceConfig(TextClassificationConstants.ENTITY_LIST_DEFAULT, originalValue); - } - } - - @Test - public void testLoadFromDeviceConfig_FloatList() throws Exception { - // Saves config original value. - final String originalValue = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, - TextClassificationConstants.LANG_ID_CONTEXT_SETTINGS); - - final TextClassificationConstants constants = new TextClassificationConstants(); - try { - // Sets and checks different value. - setDeviceConfig(TextClassificationConstants.LANG_ID_CONTEXT_SETTINGS, "30:0.5:0.3"); - assertThat(Floats.asList(constants.getLangIdContextSettings())).containsExactly(30f, - 0.5f, 0.3f).inOrder(); - } finally { - // Restores config original value. - setDeviceConfig(TextClassificationConstants.LANG_ID_CONTEXT_SETTINGS, originalValue); - } - } - private void setDeviceConfig(String key, String value) { DeviceConfig.setProperty(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, key, value, /* makeDefault */ false); diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java index 1ca46491b363b..628252d8ca6cc 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java @@ -16,19 +16,15 @@ package android.view.textclassifier; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertTrue; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Mockito.mock; import android.content.Context; -import android.content.Intent; -import android.os.LocaleList; -import androidx.test.InstrumentationRegistry; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; @@ -38,14 +34,12 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class TextClassificationManagerTest { - private static final LocaleList LOCALES = LocaleList.forLanguageTags("en-US"); - private Context mContext; private TextClassificationManager mTcm; @Before public void setup() { - mContext = InstrumentationRegistry.getTargetContext(); + mContext = ApplicationProvider.getApplicationContext(); mTcm = mContext.getSystemService(TextClassificationManager.class); } @@ -53,45 +47,17 @@ public class TextClassificationManagerTest { public void testSetTextClassifier() { TextClassifier classifier = mock(TextClassifier.class); mTcm.setTextClassifier(classifier); - assertEquals(classifier, mTcm.getTextClassifier()); + assertThat(mTcm.getTextClassifier()).isEqualTo(classifier); } @Test public void testGetLocalTextClassifier() { - assertTrue(mTcm.getTextClassifier(TextClassifier.LOCAL) instanceof TextClassifierImpl); + assertThat(mTcm.getTextClassifier(TextClassifier.LOCAL)).isSameAs(TextClassifier.NO_OP); } @Test public void testGetSystemTextClassifier() { - assertTrue(mTcm.getTextClassifier(TextClassifier.SYSTEM) instanceof SystemTextClassifier); - } - - @Test - public void testCannotResolveIntent() { - Context fakeContext = new FakeContextBuilder() - .setAllIntentComponent(FakeContextBuilder.DEFAULT_COMPONENT) - .setIntentComponent(Intent.ACTION_INSERT_OR_EDIT, null) - .build(); - - TextClassifier fallback = TextClassifier.NO_OP; - TextClassifier classifier = new TextClassifierImpl( - fakeContext, new TextClassificationConstants(), fallback); - - String text = "Contact me at +12122537077"; - String classifiedText = "+12122537077"; - int startIndex = text.indexOf(classifiedText); - int endIndex = startIndex + classifiedText.length(); - TextClassification.Request request = new TextClassification.Request.Builder( - text, startIndex, endIndex) - .setDefaultLocales(LOCALES) - .build(); - - TextClassification result = classifier.classifyText(request); - TextClassification fallbackResult = fallback.classifyText(request); - - // classifier should not totally fail in which case it returns a fallback result. - // It should skip the failing intent and return a result for non-failing intents. - assertFalse(result.getActions().isEmpty()); - assertNotSame(result, fallbackResult); + assertThat(mTcm.getTextClassifier(TextClassifier.SYSTEM)) + .isInstanceOf(SystemTextClassifier.class); } } diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java deleted file mode 100644 index 372a4787cf1a1..0000000000000 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java +++ /dev/null @@ -1,719 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.textclassifier; - -import static org.hamcrest.CoreMatchers.not; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; - -import android.app.RemoteAction; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.os.LocaleList; -import android.service.textclassifier.TextClassifierService; -import android.text.Spannable; -import android.text.SpannableString; - -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; - -import com.google.common.truth.Truth; - -import org.hamcrest.BaseMatcher; -import org.hamcrest.Description; -import org.hamcrest.Matcher; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -/** - * Testing {@link TextClassifierTest} APIs on local and system textclassifier. - *

- * Tests are skipped if such a textclassifier does not exist. - */ -@SmallTest -@RunWith(Parameterized.class) -public class TextClassifierTest { - private static final String LOCAL = "local"; - private static final String SESSION = "session"; - private static final String DEFAULT = "default"; - - // TODO: Add SYSTEM, which tests TextClassifier.SYSTEM. - @Parameterized.Parameters(name = "{0}") - public static Iterable textClassifierTypes() { - return Arrays.asList(LOCAL, SESSION, DEFAULT); - } - - @Parameterized.Parameter - public String mTextClassifierType; - - private static final TextClassificationConstants TC_CONSTANTS = - new TextClassificationConstants(); - private static final LocaleList LOCALES = LocaleList.forLanguageTags("en-US"); - private static final String NO_TYPE = null; - - private Context mContext; - private TextClassificationManager mTcm; - private TextClassifier mClassifier; - - @Before - public void setup() { - mContext = InstrumentationRegistry.getTargetContext(); - mTcm = mContext.getSystemService(TextClassificationManager.class); - - if (mTextClassifierType.equals(LOCAL)) { - mClassifier = mTcm.getTextClassifier(TextClassifier.LOCAL); - } else if (mTextClassifierType.equals(SESSION)) { - mClassifier = mTcm.createTextClassificationSession( - new TextClassificationContext.Builder( - "android", - TextClassifier.WIDGET_TYPE_NOTIFICATION) - .build(), - mTcm.getTextClassifier(TextClassifier.LOCAL)); - } else { - mClassifier = TextClassifierService.getDefaultTextClassifierImplementation(mContext); - } - } - - @Test - public void testSuggestSelection() { - if (isTextClassifierDisabled()) return; - - String text = "Contact me at droid@android.com"; - String selected = "droid"; - String suggested = "droid@android.com"; - int startIndex = text.indexOf(selected); - int endIndex = startIndex + selected.length(); - int smartStartIndex = text.indexOf(suggested); - int smartEndIndex = smartStartIndex + suggested.length(); - TextSelection.Request request = new TextSelection.Request.Builder( - text, startIndex, endIndex) - .setDefaultLocales(LOCALES) - .build(); - - TextSelection selection = mClassifier.suggestSelection(request); - assertThat(selection, - isTextSelection(smartStartIndex, smartEndIndex, TextClassifier.TYPE_EMAIL)); - } - - @Test - public void testSuggestSelection_url() { - if (isTextClassifierDisabled()) return; - - String text = "Visit http://www.android.com for more information"; - String selected = "http"; - String suggested = "http://www.android.com"; - int startIndex = text.indexOf(selected); - int endIndex = startIndex + selected.length(); - int smartStartIndex = text.indexOf(suggested); - int smartEndIndex = smartStartIndex + suggested.length(); - TextSelection.Request request = new TextSelection.Request.Builder( - text, startIndex, endIndex) - .setDefaultLocales(LOCALES) - .build(); - - TextSelection selection = mClassifier.suggestSelection(request); - assertThat(selection, - isTextSelection(smartStartIndex, smartEndIndex, TextClassifier.TYPE_URL)); - } - - @Test - public void testSmartSelection_withEmoji() { - if (isTextClassifierDisabled()) return; - - String text = "\uD83D\uDE02 Hello."; - String selected = "Hello"; - int startIndex = text.indexOf(selected); - int endIndex = startIndex + selected.length(); - TextSelection.Request request = new TextSelection.Request.Builder( - text, startIndex, endIndex) - .setDefaultLocales(LOCALES) - .build(); - - TextSelection selection = mClassifier.suggestSelection(request); - assertThat(selection, - isTextSelection(startIndex, endIndex, NO_TYPE)); - } - - @Test - public void testClassifyText() { - if (isTextClassifierDisabled()) return; - - String text = "Contact me at droid@android.com"; - String classifiedText = "droid@android.com"; - int startIndex = text.indexOf(classifiedText); - int endIndex = startIndex + classifiedText.length(); - TextClassification.Request request = new TextClassification.Request.Builder( - text, startIndex, endIndex) - .setDefaultLocales(LOCALES) - .build(); - - TextClassification classification = mClassifier.classifyText(request); - assertThat(classification, isTextClassification(classifiedText, TextClassifier.TYPE_EMAIL)); - } - - @Test - public void testClassifyText_url() { - if (isTextClassifierDisabled()) return; - - String text = "Visit www.android.com for more information"; - String classifiedText = "www.android.com"; - int startIndex = text.indexOf(classifiedText); - int endIndex = startIndex + classifiedText.length(); - TextClassification.Request request = new TextClassification.Request.Builder( - text, startIndex, endIndex) - .setDefaultLocales(LOCALES) - .build(); - - TextClassification classification = mClassifier.classifyText(request); - assertThat(classification, isTextClassification(classifiedText, TextClassifier.TYPE_URL)); - assertThat(classification, containsIntentWithAction(Intent.ACTION_VIEW)); - } - - @Test - public void testClassifyText_address() { - if (isTextClassifierDisabled()) return; - - String text = "Brandschenkestrasse 110, Zürich, Switzerland"; - TextClassification.Request request = new TextClassification.Request.Builder( - text, 0, text.length()) - .setDefaultLocales(LOCALES) - .build(); - - TextClassification classification = mClassifier.classifyText(request); - assertThat(classification, isTextClassification(text, TextClassifier.TYPE_ADDRESS)); - } - - @Test - public void testClassifyText_url_inCaps() { - if (isTextClassifierDisabled()) return; - - String text = "Visit HTTP://ANDROID.COM for more information"; - String classifiedText = "HTTP://ANDROID.COM"; - int startIndex = text.indexOf(classifiedText); - int endIndex = startIndex + classifiedText.length(); - TextClassification.Request request = new TextClassification.Request.Builder( - text, startIndex, endIndex) - .setDefaultLocales(LOCALES) - .build(); - - TextClassification classification = mClassifier.classifyText(request); - assertThat(classification, isTextClassification(classifiedText, TextClassifier.TYPE_URL)); - assertThat(classification, containsIntentWithAction(Intent.ACTION_VIEW)); - } - - @Test - public void testClassifyText_date() { - if (isTextClassifierDisabled()) return; - - String text = "Let's meet on January 9, 2018."; - String classifiedText = "January 9, 2018"; - int startIndex = text.indexOf(classifiedText); - int endIndex = startIndex + classifiedText.length(); - TextClassification.Request request = new TextClassification.Request.Builder( - text, startIndex, endIndex) - .setDefaultLocales(LOCALES) - .build(); - - TextClassification classification = mClassifier.classifyText(request); - assertThat(classification, isTextClassification(classifiedText, TextClassifier.TYPE_DATE)); - Bundle extras = classification.getExtras(); - List entities = ExtrasUtils.getEntities(extras); - Truth.assertThat(entities).hasSize(1); - Bundle entity = entities.get(0); - Truth.assertThat(ExtrasUtils.getEntityType(entity)).isEqualTo(TextClassifier.TYPE_DATE); - } - - @Test - public void testClassifyText_datetime() { - if (isTextClassifierDisabled()) return; - - String text = "Let's meet 2018/01/01 10:30:20."; - String classifiedText = "2018/01/01 10:30:20"; - int startIndex = text.indexOf(classifiedText); - int endIndex = startIndex + classifiedText.length(); - TextClassification.Request request = new TextClassification.Request.Builder( - text, startIndex, endIndex) - .setDefaultLocales(LOCALES) - .build(); - - TextClassification classification = mClassifier.classifyText(request); - assertThat(classification, - isTextClassification(classifiedText, TextClassifier.TYPE_DATE_TIME)); - } - - @Test - public void testClassifyText_foreignText() { - LocaleList originalLocales = LocaleList.getDefault(); - LocaleList.setDefault(LocaleList.forLanguageTags("en")); - String japaneseText = "これは日本語のテキストです"; - - Context context = new FakeContextBuilder() - .setIntentComponent(Intent.ACTION_TRANSLATE, FakeContextBuilder.DEFAULT_COMPONENT) - .build(); - TextClassifier classifier = new TextClassifierImpl(context, TC_CONSTANTS); - TextClassification.Request request = new TextClassification.Request.Builder( - japaneseText, 0, japaneseText.length()) - .setDefaultLocales(LOCALES) - .build(); - - TextClassification classification = classifier.classifyText(request); - RemoteAction translateAction = classification.getActions().get(0); - assertEquals(1, classification.getActions().size()); - assertEquals( - context.getString(com.android.internal.R.string.translate), - translateAction.getTitle()); - - assertEquals(translateAction, ExtrasUtils.findTranslateAction(classification)); - Intent intent = ExtrasUtils.getActionsIntents(classification).get(0); - assertEquals(Intent.ACTION_TRANSLATE, intent.getAction()); - Bundle foreignLanguageInfo = ExtrasUtils.getForeignLanguageExtra(classification); - assertEquals("ja", ExtrasUtils.getEntityType(foreignLanguageInfo)); - assertTrue(ExtrasUtils.getScore(foreignLanguageInfo) >= 0); - assertTrue(ExtrasUtils.getScore(foreignLanguageInfo) <= 1); - assertTrue(intent.hasExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER)); - assertEquals("ja", ExtrasUtils.getTopLanguage(intent).getLanguage()); - - LocaleList.setDefault(originalLocales); - } - - @Test - public void testGenerateLinks_phone() { - if (isTextClassifierDisabled()) return; - String text = "The number is +12122537077. See you tonight!"; - TextLinks.Request request = new TextLinks.Request.Builder(text).build(); - assertThat(mClassifier.generateLinks(request), - isTextLinksContaining(text, "+12122537077", TextClassifier.TYPE_PHONE)); - } - - @Test - public void testGenerateLinks_exclude() { - if (isTextClassifierDisabled()) return; - String text = "You want apple@banana.com. See you tonight!"; - List hints = Collections.EMPTY_LIST; - List included = Collections.EMPTY_LIST; - List excluded = Arrays.asList(TextClassifier.TYPE_EMAIL); - TextLinks.Request request = new TextLinks.Request.Builder(text) - .setEntityConfig(TextClassifier.EntityConfig.create(hints, included, excluded)) - .setDefaultLocales(LOCALES) - .build(); - assertThat(mClassifier.generateLinks(request), - not(isTextLinksContaining(text, "apple@banana.com", TextClassifier.TYPE_EMAIL))); - } - - @Test - public void testGenerateLinks_explicit_address() { - if (isTextClassifierDisabled()) return; - String text = "The address is 1600 Amphitheater Parkway, Mountain View, CA. See you!"; - List explicit = Arrays.asList(TextClassifier.TYPE_ADDRESS); - TextLinks.Request request = new TextLinks.Request.Builder(text) - .setEntityConfig(TextClassifier.EntityConfig.createWithExplicitEntityList(explicit)) - .setDefaultLocales(LOCALES) - .build(); - assertThat(mClassifier.generateLinks(request), - isTextLinksContaining(text, "1600 Amphitheater Parkway, Mountain View, CA", - TextClassifier.TYPE_ADDRESS)); - } - - @Test - public void testGenerateLinks_exclude_override() { - if (isTextClassifierDisabled()) return; - String text = "You want apple@banana.com. See you tonight!"; - List hints = Collections.EMPTY_LIST; - List included = Arrays.asList(TextClassifier.TYPE_EMAIL); - List excluded = Arrays.asList(TextClassifier.TYPE_EMAIL); - TextLinks.Request request = new TextLinks.Request.Builder(text) - .setEntityConfig(TextClassifier.EntityConfig.create(hints, included, excluded)) - .setDefaultLocales(LOCALES) - .build(); - assertThat(mClassifier.generateLinks(request), - not(isTextLinksContaining(text, "apple@banana.com", TextClassifier.TYPE_EMAIL))); - } - - @Test - public void testGenerateLinks_maxLength() { - if (isTextClassifierDisabled()) return; - char[] manySpaces = new char[mClassifier.getMaxGenerateLinksTextLength()]; - Arrays.fill(manySpaces, ' '); - TextLinks.Request request = new TextLinks.Request.Builder(new String(manySpaces)).build(); - TextLinks links = mClassifier.generateLinks(request); - assertTrue(links.getLinks().isEmpty()); - } - - @Test - public void testApplyLinks_unsupportedCharacter() { - if (isTextClassifierDisabled()) return; - Spannable url = new SpannableString("\u202Emoc.diordna.com"); - TextLinks.Request request = new TextLinks.Request.Builder(url).build(); - assertEquals( - TextLinks.STATUS_UNSUPPORTED_CHARACTER, - mClassifier.generateLinks(request).apply(url, 0, null)); - } - - @Test - public void testGenerateLinks_tooLong() { - if (isTextClassifierDisabled()) return; - char[] manySpaces = new char[mClassifier.getMaxGenerateLinksTextLength() + 1]; - Arrays.fill(manySpaces, ' '); - TextLinks.Request request = new TextLinks.Request.Builder(new String(manySpaces)).build(); - TextLinks links = mClassifier.generateLinks(request); - assertTrue(links.getLinks().isEmpty()); - } - - @Test - public void testGenerateLinks_entityData() { - if (isTextClassifierDisabled()) return; - String text = "The number is +12122537077."; - Bundle extras = new Bundle(); - ExtrasUtils.putIsSerializedEntityDataEnabled(extras, true); - TextLinks.Request request = new TextLinks.Request.Builder(text).setExtras(extras).build(); - - TextLinks textLinks = mClassifier.generateLinks(request); - - Truth.assertThat(textLinks.getLinks()).hasSize(1); - TextLinks.TextLink textLink = textLinks.getLinks().iterator().next(); - List entities = ExtrasUtils.getEntities(textLink.getExtras()); - Truth.assertThat(entities).hasSize(1); - Bundle entity = entities.get(0); - Truth.assertThat(ExtrasUtils.getEntityType(entity)).isEqualTo(TextClassifier.TYPE_PHONE); - } - - @Test - public void testGenerateLinks_entityData_disabled() { - if (isTextClassifierDisabled()) return; - String text = "The number is +12122537077."; - TextLinks.Request request = new TextLinks.Request.Builder(text).build(); - - TextLinks textLinks = mClassifier.generateLinks(request); - - Truth.assertThat(textLinks.getLinks()).hasSize(1); - TextLinks.TextLink textLink = textLinks.getLinks().iterator().next(); - List entities = ExtrasUtils.getEntities(textLink.getExtras()); - Truth.assertThat(entities).isNull(); - } - - @Test - public void testDetectLanguage() { - if (isTextClassifierDisabled()) return; - String text = "This is English text"; - TextLanguage.Request request = new TextLanguage.Request.Builder(text).build(); - TextLanguage textLanguage = mClassifier.detectLanguage(request); - assertThat(textLanguage, isTextLanguage("en")); - } - - @Test - public void testDetectLanguage_japanese() { - if (isTextClassifierDisabled()) return; - String text = "これは日本語のテキストです"; - TextLanguage.Request request = new TextLanguage.Request.Builder(text).build(); - TextLanguage textLanguage = mClassifier.detectLanguage(request); - assertThat(textLanguage, isTextLanguage("ja")); - } - - @Ignore // Doesn't work without a language-based model. - @Test - public void testSuggestConversationActions_textReplyOnly_maxOne() { - if (isTextClassifierDisabled()) return; - ConversationActions.Message message = - new ConversationActions.Message.Builder( - ConversationActions.Message.PERSON_USER_OTHERS) - .setText("Where are you?") - .build(); - TextClassifier.EntityConfig typeConfig = - new TextClassifier.EntityConfig.Builder().includeTypesFromTextClassifier(false) - .setIncludedTypes( - Collections.singletonList(ConversationAction.TYPE_TEXT_REPLY)) - .build(); - ConversationActions.Request request = - new ConversationActions.Request.Builder(Collections.singletonList(message)) - .setMaxSuggestions(1) - .setTypeConfig(typeConfig) - .build(); - - ConversationActions conversationActions = mClassifier.suggestConversationActions(request); - Truth.assertThat(conversationActions.getConversationActions()).hasSize(1); - ConversationAction conversationAction = conversationActions.getConversationActions().get(0); - Truth.assertThat(conversationAction.getType()).isEqualTo( - ConversationAction.TYPE_TEXT_REPLY); - Truth.assertThat(conversationAction.getTextReply()).isNotNull(); - } - - @Ignore // Doesn't work without a language-based model. - @Test - public void testSuggestConversationActions_textReplyOnly_noMax() { - if (isTextClassifierDisabled()) return; - ConversationActions.Message message = - new ConversationActions.Message.Builder( - ConversationActions.Message.PERSON_USER_OTHERS) - .setText("Where are you?") - .build(); - TextClassifier.EntityConfig typeConfig = - new TextClassifier.EntityConfig.Builder().includeTypesFromTextClassifier(false) - .setIncludedTypes( - Collections.singletonList(ConversationAction.TYPE_TEXT_REPLY)) - .build(); - ConversationActions.Request request = - new ConversationActions.Request.Builder(Collections.singletonList(message)) - .setTypeConfig(typeConfig) - .build(); - - ConversationActions conversationActions = mClassifier.suggestConversationActions(request); - assertTrue(conversationActions.getConversationActions().size() > 1); - for (ConversationAction conversationAction : - conversationActions.getConversationActions()) { - assertThat(conversationAction, - isConversationAction(ConversationAction.TYPE_TEXT_REPLY)); - } - } - - @Test - public void testSuggestConversationActions_openUrl() { - if (isTextClassifierDisabled()) return; - ConversationActions.Message message = - new ConversationActions.Message.Builder( - ConversationActions.Message.PERSON_USER_OTHERS) - .setText("Check this out: https://www.android.com") - .build(); - TextClassifier.EntityConfig typeConfig = - new TextClassifier.EntityConfig.Builder().includeTypesFromTextClassifier(false) - .setIncludedTypes( - Collections.singletonList(ConversationAction.TYPE_OPEN_URL)) - .build(); - ConversationActions.Request request = - new ConversationActions.Request.Builder(Collections.singletonList(message)) - .setMaxSuggestions(1) - .setTypeConfig(typeConfig) - .build(); - - ConversationActions conversationActions = mClassifier.suggestConversationActions(request); - Truth.assertThat(conversationActions.getConversationActions()).hasSize(1); - ConversationAction conversationAction = conversationActions.getConversationActions().get(0); - Truth.assertThat(conversationAction.getType()).isEqualTo(ConversationAction.TYPE_OPEN_URL); - Intent actionIntent = ExtrasUtils.getActionIntent(conversationAction.getExtras()); - Truth.assertThat(actionIntent.getAction()).isEqualTo(Intent.ACTION_VIEW); - Truth.assertThat(actionIntent.getData()).isEqualTo(Uri.parse("https://www.android.com")); - } - - @Ignore // Doesn't work without a language-based model. - @Test - public void testSuggestConversationActions_copy() { - if (isTextClassifierDisabled()) return; - ConversationActions.Message message = - new ConversationActions.Message.Builder( - ConversationActions.Message.PERSON_USER_OTHERS) - .setText("Authentication code: 12345") - .build(); - TextClassifier.EntityConfig typeConfig = - new TextClassifier.EntityConfig.Builder().includeTypesFromTextClassifier(false) - .setIncludedTypes( - Collections.singletonList(ConversationAction.TYPE_COPY)) - .build(); - ConversationActions.Request request = - new ConversationActions.Request.Builder(Collections.singletonList(message)) - .setMaxSuggestions(1) - .setTypeConfig(typeConfig) - .build(); - - ConversationActions conversationActions = mClassifier.suggestConversationActions(request); - Truth.assertThat(conversationActions.getConversationActions()).hasSize(1); - ConversationAction conversationAction = conversationActions.getConversationActions().get(0); - Truth.assertThat(conversationAction.getType()).isEqualTo(ConversationAction.TYPE_COPY); - Truth.assertThat(conversationAction.getTextReply()).isAnyOf(null, ""); - Truth.assertThat(conversationAction.getAction()).isNull(); - String code = ExtrasUtils.getCopyText(conversationAction.getExtras()); - Truth.assertThat(code).isEqualTo("12345"); - Truth.assertThat( - ExtrasUtils.getSerializedEntityData(conversationAction.getExtras())).isNotEmpty(); - } - - @Test - public void testSuggestConversationActions_deduplicate() { - Context context = new FakeContextBuilder() - .setIntentComponent(Intent.ACTION_SENDTO, FakeContextBuilder.DEFAULT_COMPONENT) - .build(); - ConversationActions.Message message = - new ConversationActions.Message.Builder( - ConversationActions.Message.PERSON_USER_OTHERS) - .setText("a@android.com b@android.com") - .build(); - ConversationActions.Request request = - new ConversationActions.Request.Builder(Collections.singletonList(message)) - .setMaxSuggestions(3) - .build(); - - TextClassifier classifier = new TextClassifierImpl(context, TC_CONSTANTS); - ConversationActions conversationActions = classifier.suggestConversationActions(request); - - Truth.assertThat(conversationActions.getConversationActions()).isEmpty(); - } - - private boolean isTextClassifierDisabled() { - return mClassifier == null || mClassifier == TextClassifier.NO_OP; - } - - private static Matcher isTextSelection( - final int startIndex, final int endIndex, final String type) { - return new BaseMatcher() { - @Override - public boolean matches(Object o) { - if (o instanceof TextSelection) { - TextSelection selection = (TextSelection) o; - return startIndex == selection.getSelectionStartIndex() - && endIndex == selection.getSelectionEndIndex() - && typeMatches(selection, type); - } - return false; - } - - private boolean typeMatches(TextSelection selection, String type) { - return type == null - || (selection.getEntityCount() > 0 - && type.trim().equalsIgnoreCase(selection.getEntity(0))); - } - - @Override - public void describeTo(Description description) { - description.appendValue( - String.format("%d, %d, %s", startIndex, endIndex, type)); - } - }; - } - - private static Matcher isTextLinksContaining( - final String text, final String substring, final String type) { - return new BaseMatcher() { - - @Override - public void describeTo(Description description) { - description.appendText("text=").appendValue(text) - .appendText(", substring=").appendValue(substring) - .appendText(", type=").appendValue(type); - } - - @Override - public boolean matches(Object o) { - if (o instanceof TextLinks) { - for (TextLinks.TextLink link : ((TextLinks) o).getLinks()) { - if (text.subSequence(link.getStart(), link.getEnd()).equals(substring)) { - return type.equals(link.getEntity(0)); - } - } - } - return false; - } - }; - } - - private static Matcher isTextClassification( - final String text, final String type) { - return new BaseMatcher() { - @Override - public boolean matches(Object o) { - if (o instanceof TextClassification) { - TextClassification result = (TextClassification) o; - return text.equals(result.getText()) - && result.getEntityCount() > 0 - && type.equals(result.getEntity(0)); - } - return false; - } - - @Override - public void describeTo(Description description) { - description.appendText("text=").appendValue(text) - .appendText(", type=").appendValue(type); - } - }; - } - - private static Matcher containsIntentWithAction(final String action) { - return new BaseMatcher() { - @Override - public boolean matches(Object o) { - if (o instanceof TextClassification) { - TextClassification result = (TextClassification) o; - return ExtrasUtils.findAction(result, action) != null; - } - return false; - } - - @Override - public void describeTo(Description description) { - description.appendText("intent action=").appendValue(action); - } - }; - } - - private static Matcher isTextLanguage(final String languageTag) { - return new BaseMatcher() { - @Override - public boolean matches(Object o) { - if (o instanceof TextLanguage) { - TextLanguage result = (TextLanguage) o; - return result.getLocaleHypothesisCount() > 0 - && languageTag.equals(result.getLocale(0).toLanguageTag()); - } - return false; - } - - @Override - public void describeTo(Description description) { - description.appendText("locale=").appendValue(languageTag); - } - }; - } - - private static Matcher isConversationAction(String actionType) { - return new BaseMatcher() { - @Override - public boolean matches(Object o) { - if (!(o instanceof ConversationAction)) { - return false; - } - ConversationAction conversationAction = - (ConversationAction) o; - if (!actionType.equals(conversationAction.getType())) { - return false; - } - if (ConversationAction.TYPE_TEXT_REPLY.equals(actionType)) { - if (conversationAction.getTextReply() == null) { - return false; - } - } - if (conversationAction.getConfidenceScore() < 0 - || conversationAction.getConfidenceScore() > 1) { - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("actionType=").appendValue(actionType); - } - }; - } -} diff --git a/core/tests/coretests/src/android/view/textclassifier/intent/LabeledIntentTest.java b/core/tests/coretests/src/android/view/textclassifier/intent/LabeledIntentTest.java deleted file mode 100644 index 3ad26f5d51088..0000000000000 --- a/core/tests/coretests/src/android/view/textclassifier/intent/LabeledIntentTest.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.textclassifier.intent; - -import static com.google.common.truth.Truth.assertThat; - -import static org.testng.Assert.assertThrows; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.view.textclassifier.FakeContextBuilder; -import android.view.textclassifier.TextClassifier; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -@SmallTest -@RunWith(AndroidJUnit4.class) -public final class LabeledIntentTest { - private static final String TITLE_WITHOUT_ENTITY = "Map"; - private static final String TITLE_WITH_ENTITY = "Map NW14D1"; - private static final String DESCRIPTION = "Check the map"; - private static final String DESCRIPTION_WITH_APP_NAME = "Use %1$s to open map"; - private static final Intent INTENT = - new Intent(Intent.ACTION_VIEW).setDataAndNormalize(Uri.parse("http://www.android.com")); - private static final int REQUEST_CODE = 42; - private static final Bundle TEXT_LANGUAGES_BUNDLE = Bundle.EMPTY; - private static final String APP_LABEL = "fake"; - - private Context mContext; - - @Before - public void setup() { - final ComponentName component = FakeContextBuilder.DEFAULT_COMPONENT; - mContext = new FakeContextBuilder() - .setIntentComponent(Intent.ACTION_VIEW, component) - .setAppLabel(component.getPackageName(), APP_LABEL) - .build(); - } - - @Test - public void resolve_preferTitleWithEntity() { - LabeledIntent labeledIntent = new LabeledIntent( - TITLE_WITHOUT_ENTITY, - TITLE_WITH_ENTITY, - DESCRIPTION, - null, - INTENT, - REQUEST_CODE - ); - - LabeledIntent.Result result = labeledIntent.resolve( - mContext, /*titleChooser*/ null, TEXT_LANGUAGES_BUNDLE); - - assertThat(result).isNotNull(); - assertThat(result.remoteAction.getTitle()).isEqualTo(TITLE_WITH_ENTITY); - assertThat(result.remoteAction.getContentDescription()).isEqualTo(DESCRIPTION); - Intent intent = result.resolvedIntent; - assertThat(intent.getAction()).isEqualTo(intent.getAction()); - assertThat(intent.getComponent()).isNotNull(); - assertThat(intent.hasExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER)).isTrue(); - } - - @Test - public void resolve_useAvailableTitle() { - LabeledIntent labeledIntent = new LabeledIntent( - TITLE_WITHOUT_ENTITY, - null, - DESCRIPTION, - null, - INTENT, - REQUEST_CODE - ); - - LabeledIntent.Result result = labeledIntent.resolve( - mContext, /*titleChooser*/ null, TEXT_LANGUAGES_BUNDLE); - - assertThat(result).isNotNull(); - assertThat(result.remoteAction.getTitle()).isEqualTo(TITLE_WITHOUT_ENTITY); - assertThat(result.remoteAction.getContentDescription()).isEqualTo(DESCRIPTION); - Intent intent = result.resolvedIntent; - assertThat(intent.getAction()).isEqualTo(intent.getAction()); - assertThat(intent.getComponent()).isNotNull(); - } - - @Test - public void resolve_titleChooser() { - LabeledIntent labeledIntent = new LabeledIntent( - TITLE_WITHOUT_ENTITY, - null, - DESCRIPTION, - null, - INTENT, - REQUEST_CODE - ); - - LabeledIntent.Result result = labeledIntent.resolve( - mContext, (labeledIntent1, resolveInfo) -> "chooser", TEXT_LANGUAGES_BUNDLE); - - assertThat(result).isNotNull(); - assertThat(result.remoteAction.getTitle()).isEqualTo("chooser"); - assertThat(result.remoteAction.getContentDescription()).isEqualTo(DESCRIPTION); - Intent intent = result.resolvedIntent; - assertThat(intent.getAction()).isEqualTo(intent.getAction()); - assertThat(intent.getComponent()).isNotNull(); - } - - @Test - public void resolve_titleChooserReturnsNull() { - LabeledIntent labeledIntent = new LabeledIntent( - TITLE_WITHOUT_ENTITY, - null, - DESCRIPTION, - null, - INTENT, - REQUEST_CODE - ); - - LabeledIntent.Result result = labeledIntent.resolve( - mContext, (labeledIntent1, resolveInfo) -> null, TEXT_LANGUAGES_BUNDLE); - - assertThat(result).isNotNull(); - assertThat(result.remoteAction.getTitle()).isEqualTo(TITLE_WITHOUT_ENTITY); - assertThat(result.remoteAction.getContentDescription()).isEqualTo(DESCRIPTION); - Intent intent = result.resolvedIntent; - assertThat(intent.getAction()).isEqualTo(intent.getAction()); - assertThat(intent.getComponent()).isNotNull(); - } - - @Test - public void resolve_missingTitle() { - assertThrows( - IllegalArgumentException.class, - () -> - new LabeledIntent( - null, - null, - DESCRIPTION, - null, - INTENT, - REQUEST_CODE - )); - } - - @Test - public void resolve_noIntentHandler() { - // See setup(). mContext can only resolve Intent.ACTION_VIEW. - Intent unresolvableIntent = new Intent(Intent.ACTION_TRANSLATE); - LabeledIntent labeledIntent = new LabeledIntent( - TITLE_WITHOUT_ENTITY, - null, - DESCRIPTION, - null, - unresolvableIntent, - REQUEST_CODE); - - LabeledIntent.Result result = labeledIntent.resolve(mContext, null, null); - - assertThat(result).isNull(); - } - - @Test - public void resolve_descriptionWithAppName() { - LabeledIntent labeledIntent = new LabeledIntent( - TITLE_WITHOUT_ENTITY, - TITLE_WITH_ENTITY, - DESCRIPTION, - DESCRIPTION_WITH_APP_NAME, - INTENT, - REQUEST_CODE - ); - - LabeledIntent.Result result = labeledIntent.resolve( - mContext, /*titleChooser*/ null, TEXT_LANGUAGES_BUNDLE); - - assertThat(result).isNotNull(); - assertThat(result.remoteAction.getContentDescription()).isEqualTo("Use fake to open map"); - } -} diff --git a/core/tests/coretests/src/android/view/textclassifier/intent/LegacyIntentClassificationFactoryTest.java b/core/tests/coretests/src/android/view/textclassifier/intent/LegacyIntentClassificationFactoryTest.java deleted file mode 100644 index 8891d3fd2dca7..0000000000000 --- a/core/tests/coretests/src/android/view/textclassifier/intent/LegacyIntentClassificationFactoryTest.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.textclassifier.intent; - -import static com.google.common.truth.Truth.assertThat; - -import android.content.Intent; -import android.view.textclassifier.TextClassifier; - -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import com.google.android.textclassifier.AnnotatorModel; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.List; - -@SmallTest -@RunWith(AndroidJUnit4.class) -public class LegacyIntentClassificationFactoryTest { - - private static final String TEXT = "text"; - - private LegacyClassificationIntentFactory mLegacyIntentClassificationFactory; - - @Before - public void setup() { - mLegacyIntentClassificationFactory = new LegacyClassificationIntentFactory(); - } - - @Test - public void create_typeDictionary() { - AnnotatorModel.ClassificationResult classificationResult = - new AnnotatorModel.ClassificationResult( - TextClassifier.TYPE_DICTIONARY, - 1.0f, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 0L, - 0L, - 0d); - - List intents = mLegacyIntentClassificationFactory.create( - InstrumentationRegistry.getContext(), - TEXT, - /* foreignText */ false, - null, - classificationResult); - - assertThat(intents).hasSize(1); - LabeledIntent labeledIntent = intents.get(0); - Intent intent = labeledIntent.intent; - assertThat(intent.getAction()).isEqualTo(Intent.ACTION_DEFINE); - assertThat(intent.getStringExtra(Intent.EXTRA_TEXT)).isEqualTo(TEXT); - } - - @Test - public void create_translateAndDictionary() { - AnnotatorModel.ClassificationResult classificationResult = - new AnnotatorModel.ClassificationResult( - TextClassifier.TYPE_DICTIONARY, - 1.0f, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 0L, - 0L, - 0d); - - List intents = mLegacyIntentClassificationFactory.create( - InstrumentationRegistry.getContext(), - TEXT, - /* foreignText */ true, - null, - classificationResult); - - assertThat(intents).hasSize(2); - assertThat(intents.get(0).intent.getAction()).isEqualTo(Intent.ACTION_DEFINE); - assertThat(intents.get(1).intent.getAction()).isEqualTo(Intent.ACTION_TRANSLATE); - } -} diff --git a/core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java b/core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java deleted file mode 100644 index bcea5fea6a13c..0000000000000 --- a/core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.textclassifier.intent; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.same; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - -import android.content.Context; -import android.content.Intent; -import android.view.textclassifier.TextClassifier; - -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import com.google.android.textclassifier.AnnotatorModel; -import com.google.android.textclassifier.RemoteActionTemplate; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.List; - -@SmallTest -@RunWith(AndroidJUnit4.class) -public class TemplateClassificationIntentFactoryTest { - - private static final String TEXT = "text"; - private static final String TITLE_WITHOUT_ENTITY = "Map"; - private static final String DESCRIPTION = "Opens in Maps"; - private static final String DESCRIPTION_WITH_APP_NAME = "Use %1$s to open Map"; - private static final String ACTION = Intent.ACTION_VIEW; - - @Mock - private ClassificationIntentFactory mFallback; - private TemplateClassificationIntentFactory mTemplateClassificationIntentFactory; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - mTemplateClassificationIntentFactory = new TemplateClassificationIntentFactory( - new TemplateIntentFactory(), - mFallback); - } - - @Test - public void create_foreignText() { - AnnotatorModel.ClassificationResult classificationResult = - new AnnotatorModel.ClassificationResult( - TextClassifier.TYPE_ADDRESS, - 1.0f, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - createRemoteActionTemplates(), - 0L, - 0L, - 0d); - - List intents = - mTemplateClassificationIntentFactory.create( - InstrumentationRegistry.getContext(), - TEXT, - /* foreignText */ true, - null, - classificationResult); - - assertThat(intents).hasSize(2); - LabeledIntent labeledIntent = intents.get(0); - assertThat(labeledIntent.titleWithoutEntity).isEqualTo(TITLE_WITHOUT_ENTITY); - Intent intent = labeledIntent.intent; - assertThat(intent.getAction()).isEqualTo(ACTION); - - labeledIntent = intents.get(1); - intent = labeledIntent.intent; - assertThat(intent.getAction()).isEqualTo(Intent.ACTION_TRANSLATE); - } - - @Test - public void create_notForeignText() { - AnnotatorModel.ClassificationResult classificationResult = - new AnnotatorModel.ClassificationResult( - TextClassifier.TYPE_ADDRESS, - 1.0f, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - createRemoteActionTemplates(), - 0L, - 0L, - 0d); - - List intents = - mTemplateClassificationIntentFactory.create( - InstrumentationRegistry.getContext(), - TEXT, - /* foreignText */ false, - null, - classificationResult); - - assertThat(intents).hasSize(1); - LabeledIntent labeledIntent = intents.get(0); - assertThat(labeledIntent.titleWithoutEntity).isEqualTo(TITLE_WITHOUT_ENTITY); - Intent intent = labeledIntent.intent; - assertThat(intent.getAction()).isEqualTo(ACTION); - } - - @Test - public void create_nullTemplate() { - AnnotatorModel.ClassificationResult classificationResult = - new AnnotatorModel.ClassificationResult( - TextClassifier.TYPE_ADDRESS, - 1.0f, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 0L, - 0L, - 0d); - - mTemplateClassificationIntentFactory.create( - InstrumentationRegistry.getContext(), - TEXT, - /* foreignText */ false, - null, - classificationResult); - - - verify(mFallback).create( - same(InstrumentationRegistry.getContext()), eq(TEXT), eq(false), eq(null), - same(classificationResult)); - } - - @Test - public void create_emptyResult() { - AnnotatorModel.ClassificationResult classificationResult = - new AnnotatorModel.ClassificationResult( - TextClassifier.TYPE_ADDRESS, - 1.0f, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - new RemoteActionTemplate[0], - 0L, - 0L, - 0d); - - mTemplateClassificationIntentFactory.create( - InstrumentationRegistry.getContext(), - TEXT, - /* foreignText */ false, - null, - classificationResult); - - - verify(mFallback, never()).create( - any(Context.class), eq(TEXT), eq(false), eq(null), - any(AnnotatorModel.ClassificationResult.class)); - } - - - private static RemoteActionTemplate[] createRemoteActionTemplates() { - return new RemoteActionTemplate[]{ - new RemoteActionTemplate( - TITLE_WITHOUT_ENTITY, - null, - DESCRIPTION, - DESCRIPTION_WITH_APP_NAME, - ACTION, - null, - null, - null, - null, - null, - null, - null - ) - }; - } -} diff --git a/core/tests/coretests/src/android/view/textclassifier/intent/TemplateIntentFactoryTest.java b/core/tests/coretests/src/android/view/textclassifier/intent/TemplateIntentFactoryTest.java deleted file mode 100644 index a33c35811276c..0000000000000 --- a/core/tests/coretests/src/android/view/textclassifier/intent/TemplateIntentFactoryTest.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.view.textclassifier.intent; - -import static com.google.common.truth.Truth.assertThat; - -import android.content.Intent; -import android.net.Uri; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import com.google.android.textclassifier.NamedVariant; -import com.google.android.textclassifier.RemoteActionTemplate; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.MockitoAnnotations; - -import java.util.List; - -@SmallTest -@RunWith(AndroidJUnit4.class) -public class TemplateIntentFactoryTest { - - private static final String TEXT = "text"; - private static final String TITLE_WITHOUT_ENTITY = "Map"; - private static final String TITLE_WITH_ENTITY = "Map NW14D1"; - private static final String DESCRIPTION = "Check the map"; - private static final String DESCRIPTION_WITH_APP_NAME = "Use %1$s to open map"; - private static final String ACTION = Intent.ACTION_VIEW; - private static final String DATA = Uri.parse("http://www.android.com").toString(); - private static final String TYPE = "text/html"; - private static final Integer FLAG = Intent.FLAG_ACTIVITY_NEW_TASK; - private static final String[] CATEGORY = - new String[]{Intent.CATEGORY_DEFAULT, Intent.CATEGORY_APP_BROWSER}; - private static final String PACKAGE_NAME = "pkg.name"; - private static final String KEY_ONE = "key1"; - private static final String VALUE_ONE = "value1"; - private static final String KEY_TWO = "key2"; - private static final int VALUE_TWO = 42; - - private static final NamedVariant[] NAMED_VARIANTS = new NamedVariant[]{ - new NamedVariant(KEY_ONE, VALUE_ONE), - new NamedVariant(KEY_TWO, VALUE_TWO) - }; - private static final Integer REQUEST_CODE = 10; - - private TemplateIntentFactory mTemplateIntentFactory; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - mTemplateIntentFactory = new TemplateIntentFactory(); - } - - @Test - public void create_full() { - RemoteActionTemplate remoteActionTemplate = new RemoteActionTemplate( - TITLE_WITHOUT_ENTITY, - TITLE_WITH_ENTITY, - DESCRIPTION, - DESCRIPTION_WITH_APP_NAME, - ACTION, - DATA, - TYPE, - FLAG, - CATEGORY, - /* packageName */ null, - NAMED_VARIANTS, - REQUEST_CODE - ); - - List intents = - mTemplateIntentFactory.create(new RemoteActionTemplate[]{remoteActionTemplate}); - - assertThat(intents).hasSize(1); - LabeledIntent labeledIntent = intents.get(0); - assertThat(labeledIntent.titleWithoutEntity).isEqualTo(TITLE_WITHOUT_ENTITY); - assertThat(labeledIntent.titleWithEntity).isEqualTo(TITLE_WITH_ENTITY); - assertThat(labeledIntent.description).isEqualTo(DESCRIPTION); - assertThat(labeledIntent.descriptionWithAppName).isEqualTo(DESCRIPTION_WITH_APP_NAME); - assertThat(labeledIntent.requestCode).isEqualTo(REQUEST_CODE); - Intent intent = labeledIntent.intent; - assertThat(intent.getAction()).isEqualTo(ACTION); - assertThat(intent.getData().toString()).isEqualTo(DATA); - assertThat(intent.getType()).isEqualTo(TYPE); - assertThat(intent.getFlags()).isEqualTo(FLAG); - assertThat(intent.getCategories()).containsExactly((Object[]) CATEGORY); - assertThat(intent.getPackage()).isNull(); - assertThat(intent.getStringExtra(KEY_ONE)).isEqualTo(VALUE_ONE); - assertThat(intent.getIntExtra(KEY_TWO, 0)).isEqualTo(VALUE_TWO); - } - - @Test - public void normalizesScheme() { - RemoteActionTemplate remoteActionTemplate = new RemoteActionTemplate( - TITLE_WITHOUT_ENTITY, - TITLE_WITH_ENTITY, - DESCRIPTION, - DESCRIPTION_WITH_APP_NAME, - ACTION, - "HTTp://www.android.com", - TYPE, - FLAG, - CATEGORY, - /* packageName */ null, - NAMED_VARIANTS, - REQUEST_CODE - ); - - List intents = - mTemplateIntentFactory.create(new RemoteActionTemplate[] {remoteActionTemplate}); - - String data = intents.get(0).intent.getData().toString(); - assertThat(data).isEqualTo("http://www.android.com"); - } - - @Test - public void create_minimal() { - RemoteActionTemplate remoteActionTemplate = new RemoteActionTemplate( - TITLE_WITHOUT_ENTITY, - null, - DESCRIPTION, - null, - ACTION, - null, - null, - null, - null, - null, - null, - null - ); - - List intents = - mTemplateIntentFactory.create(new RemoteActionTemplate[]{remoteActionTemplate}); - - assertThat(intents).hasSize(1); - LabeledIntent labeledIntent = intents.get(0); - assertThat(labeledIntent.titleWithoutEntity).isEqualTo(TITLE_WITHOUT_ENTITY); - assertThat(labeledIntent.titleWithEntity).isNull(); - assertThat(labeledIntent.description).isEqualTo(DESCRIPTION); - assertThat(labeledIntent.requestCode).isEqualTo( - LabeledIntent.DEFAULT_REQUEST_CODE); - Intent intent = labeledIntent.intent; - assertThat(intent.getAction()).isEqualTo(ACTION); - assertThat(intent.getData()).isNull(); - assertThat(intent.getType()).isNull(); - assertThat(intent.getFlags()).isEqualTo(0); - assertThat(intent.getCategories()).isNull(); - assertThat(intent.getPackage()).isNull(); - } - - @Test - public void invalidTemplate_nullTemplate() { - RemoteActionTemplate remoteActionTemplate = null; - - List intents = - mTemplateIntentFactory.create(new RemoteActionTemplate[] {remoteActionTemplate}); - - assertThat(intents).isEmpty(); - } - - @Test - public void invalidTemplate_nonEmptyPackageName() { - RemoteActionTemplate remoteActionTemplate = new RemoteActionTemplate( - TITLE_WITHOUT_ENTITY, - TITLE_WITH_ENTITY, - DESCRIPTION, - DESCRIPTION_WITH_APP_NAME, - ACTION, - DATA, - TYPE, - FLAG, - CATEGORY, - PACKAGE_NAME, - NAMED_VARIANTS, - REQUEST_CODE - ); - - List intents = - mTemplateIntentFactory.create(new RemoteActionTemplate[] {remoteActionTemplate}); - - assertThat(intents).isEmpty(); - } - - @Test - public void invalidTemplate_emptyTitle() { - RemoteActionTemplate remoteActionTemplate = new RemoteActionTemplate( - null, - null, - DESCRIPTION, - DESCRIPTION_WITH_APP_NAME, - ACTION, - null, - null, - null, - null, - null, - null, - null - ); - - List intents = - mTemplateIntentFactory.create(new RemoteActionTemplate[] {remoteActionTemplate}); - - assertThat(intents).isEmpty(); - } - - @Test - public void invalidTemplate_emptyDescription() { - RemoteActionTemplate remoteActionTemplate = new RemoteActionTemplate( - TITLE_WITHOUT_ENTITY, - TITLE_WITH_ENTITY, - null, - null, - ACTION, - null, - null, - null, - null, - null, - null, - null - ); - - List intents = - mTemplateIntentFactory.create(new RemoteActionTemplate[] {remoteActionTemplate}); - - assertThat(intents).isEmpty(); - } - - @Test - public void invalidTemplate_emptyIntentAction() { - RemoteActionTemplate remoteActionTemplate = new RemoteActionTemplate( - TITLE_WITHOUT_ENTITY, - TITLE_WITH_ENTITY, - DESCRIPTION, - DESCRIPTION_WITH_APP_NAME, - null, - null, - null, - null, - null, - null, - null, - null - ); - - List intents = - mTemplateIntentFactory.create(new RemoteActionTemplate[] {remoteActionTemplate}); - - assertThat(intents).isEmpty(); - } -} diff --git a/core/tests/coretests/src/android/view/textclassifier/logging/GenerateLinksLoggerTest.java b/core/tests/coretests/src/android/view/textclassifier/logging/GenerateLinksLoggerTest.java deleted file mode 100644 index 5e8e5823eefa6..0000000000000 --- a/core/tests/coretests/src/android/view/textclassifier/logging/GenerateLinksLoggerTest.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.textclassifier.logging; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.mock; - -import android.metrics.LogMaker; -import android.util.ArrayMap; -import android.view.textclassifier.GenerateLinksLogger; -import android.view.textclassifier.TextClassifier; -import android.view.textclassifier.TextLinks; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; - -import java.util.List; -import java.util.Map; -import java.util.Objects; - -@SmallTest -@RunWith(AndroidJUnit4.class) -public class GenerateLinksLoggerTest { - - private static final String PACKAGE_NAME = "packageName"; - private static final String ZERO = "0"; - private static final int LATENCY_MS = 123; - - @Test - public void testLogGenerateLinks() { - final String phoneText = "+12122537077"; - final String addressText = "1600 Amphitheater Parkway, Mountain View, CA"; - final String testText = "The number is " + phoneText + ", the address is " + addressText; - final int phoneOffset = testText.indexOf(phoneText); - final int addressOffset = testText.indexOf(addressText); - - final Map phoneEntityScores = new ArrayMap<>(); - phoneEntityScores.put(TextClassifier.TYPE_PHONE, 0.9f); - phoneEntityScores.put(TextClassifier.TYPE_OTHER, 0.1f); - final Map addressEntityScores = new ArrayMap<>(); - addressEntityScores.put(TextClassifier.TYPE_ADDRESS, 1f); - - TextLinks links = new TextLinks.Builder(testText) - .addLink(phoneOffset, phoneOffset + phoneText.length(), phoneEntityScores) - .addLink(addressOffset, addressOffset + addressText.length(), addressEntityScores) - .build(); - - // Set up mock. - MetricsLogger metricsLogger = mock(MetricsLogger.class); - ArgumentCaptor logMakerCapture = ArgumentCaptor.forClass(LogMaker.class); - doNothing().when(metricsLogger).write(logMakerCapture.capture()); - - // Generate the log. - GenerateLinksLogger logger = new GenerateLinksLogger(1 /* sampleRate */, metricsLogger); - logger.logGenerateLinks(testText, links, PACKAGE_NAME, LATENCY_MS); - - // Validate. - List logs = logMakerCapture.getAllValues(); - assertEquals(3, logs.size()); - assertHasLog(logs, "" /* entityType */, 2, phoneText.length() + addressText.length(), - testText.length()); - assertHasLog(logs, TextClassifier.TYPE_ADDRESS, 1, addressText.length(), - testText.length()); - assertHasLog(logs, TextClassifier.TYPE_PHONE, 1, phoneText.length(), - testText.length()); - } - - private void assertHasLog(List logs, String entityType, int numLinks, - int linkTextLength, int textLength) { - for (LogMaker log : logs) { - if (!entityType.equals(getEntityType(log))) { - continue; - } - assertEquals(PACKAGE_NAME, log.getPackageName()); - assertNotNull(Objects.toString(log.getTaggedData(MetricsEvent.FIELD_LINKIFY_CALL_ID))); - assertEquals(numLinks, getIntValue(log, MetricsEvent.FIELD_LINKIFY_NUM_LINKS)); - assertEquals(linkTextLength, getIntValue(log, MetricsEvent.FIELD_LINKIFY_LINK_LENGTH)); - assertEquals(textLength, getIntValue(log, MetricsEvent.FIELD_LINKIFY_TEXT_LENGTH)); - assertEquals(LATENCY_MS, getIntValue(log, MetricsEvent.FIELD_LINKIFY_LATENCY)); - return; - } - fail("No log for entity type \"" + entityType + "\""); - } - - private static String getEntityType(LogMaker log) { - return Objects.toString(log.getTaggedData(MetricsEvent.FIELD_LINKIFY_ENTITY_TYPE), ""); - } - - private static int getIntValue(LogMaker log, int eventField) { - return Integer.parseInt(Objects.toString(log.getTaggedData(eventField), ZERO)); - } -} diff --git a/core/tests/coretests/src/android/view/textclassifier/logging/SmartSelectionEventTrackerTest.java b/core/tests/coretests/src/android/view/textclassifier/logging/SmartSelectionEventTrackerTest.java deleted file mode 100644 index 321a7f21c3078..0000000000000 --- a/core/tests/coretests/src/android/view/textclassifier/logging/SmartSelectionEventTrackerTest.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package android.view.textclassifier.logging; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; - -import com.google.common.truth.Truth; -import org.junit.Test; -import org.junit.runner.RunWith; - -@SmallTest -@RunWith(AndroidJUnit4.class) -public class SmartSelectionEventTrackerTest { - - @Test - public void getVersionInfo_valid() { - String signature = "a|702|b"; - String versionInfo = SmartSelectionEventTracker.SelectionEvent.getVersionInfo(signature); - Truth.assertThat(versionInfo).isEqualTo("702"); - } - - @Test - public void getVersionInfo_invalid() { - String signature = "|702"; - String versionInfo = SmartSelectionEventTracker.SelectionEvent.getVersionInfo(signature); - Truth.assertThat(versionInfo).isEmpty(); - } -} diff --git a/core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java b/core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java deleted file mode 100644 index 2c540e560f6b4..0000000000000 --- a/core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.textclassifier.logging; - -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.CONVERSATION_ACTIONS; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_EVENT_TIME; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_SCORE; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_WIDGET_TYPE; - -import static com.google.common.truth.Truth.assertThat; - -import android.metrics.LogMaker; -import android.view.textclassifier.ConversationAction; -import android.view.textclassifier.TextClassificationContext; -import android.view.textclassifier.TextClassifierEvent; -import android.view.textclassifier.TextClassifierEventTronLogger; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.internal.logging.MetricsLogger; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; - -@SmallTest -@RunWith(AndroidJUnit4.class) -public class TextClassifierEventTronLoggerTest { - private static final String WIDGET_TYPE = "notification"; - private static final String PACKAGE_NAME = "pkg"; - - @Mock - private MetricsLogger mMetricsLogger; - private TextClassifierEventTronLogger mTextClassifierEventTronLogger; - - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - mTextClassifierEventTronLogger = new TextClassifierEventTronLogger(mMetricsLogger); - } - - @Test - public void testWriteEvent() { - TextClassificationContext textClassificationContext = - new TextClassificationContext.Builder(PACKAGE_NAME, WIDGET_TYPE) - .build(); - TextClassifierEvent.ConversationActionsEvent textClassifierEvent = - new TextClassifierEvent.ConversationActionsEvent.Builder( - TextClassifierEvent.TYPE_SMART_ACTION) - .setEntityTypes(ConversationAction.TYPE_CALL_PHONE) - .setScores(0.5f) - .setEventContext(textClassificationContext) - .build(); - - mTextClassifierEventTronLogger.writeEvent(textClassifierEvent); - - ArgumentCaptor captor = ArgumentCaptor.forClass(LogMaker.class); - Mockito.verify(mMetricsLogger).write(captor.capture()); - LogMaker logMaker = captor.getValue(); - assertThat(logMaker.getCategory()).isEqualTo(CONVERSATION_ACTIONS); - assertThat(logMaker.getSubtype()).isEqualTo(ACTION_TEXT_SELECTION_SMART_SHARE); - assertThat(logMaker.getTaggedData(FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE)) - .isEqualTo(ConversationAction.TYPE_CALL_PHONE); - assertThat((float) logMaker.getTaggedData(FIELD_TEXT_CLASSIFIER_SCORE)) - .isWithin(0.00001f).of(0.5f); - // Never write event time. - assertThat(logMaker.getTaggedData(FIELD_TEXT_CLASSIFIER_EVENT_TIME)).isNull(); - assertThat(logMaker.getPackageName()).isEqualTo(PACKAGE_NAME); - assertThat(logMaker.getTaggedData(FIELD_TEXT_CLASSIFIER_WIDGET_TYPE)) - .isEqualTo(WIDGET_TYPE); - - } - - @Test - public void testWriteEvent_unsupportedCategory() { - TextClassifierEvent.TextSelectionEvent textClassifierEvent = - new TextClassifierEvent.TextSelectionEvent.Builder( - TextClassifierEvent.TYPE_SMART_ACTION) - .build(); - - mTextClassifierEventTronLogger.writeEvent(textClassifierEvent); - - Mockito.verify(mMetricsLogger, Mockito.never()).write(Mockito.any(LogMaker.class)); - } -}