Merge "Add permission check functions to NetworkStatsService" into rvc-dev
diff --git a/Android.bp b/Android.bp
index 8adf48d..da5d624 100644
--- a/Android.bp
+++ b/Android.bp
@@ -791,10 +791,6 @@
"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 f61ea85..46250d7 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 @@
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
textClassificationManager.getTextClassifier();
- textClassificationManager.invalidateForTesting();
}
}
@@ -90,7 +89,6 @@
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
textClassificationManager.getTextClassifier();
- textClassificationManager.invalidateForTesting();
}
}
diff --git a/api/system-current.txt b/api/system-current.txt
index fd10d2a..ab3bcc1 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -252,9 +252,6 @@
public static final class R.array {
field public static final int config_keySystemUuidMapping = 17235973; // 0x1070005
- field public static final int config_restrictedPreinstalledCarrierApps = 17235975; // 0x1070007
- field public static final int config_sms_enabled_locking_shift_tables = 17235977; // 0x1070009
- field public static final int config_sms_enabled_single_shift_tables = 17235976; // 0x1070008
field public static final int simColors = 17235974; // 0x1070006
}
@@ -307,7 +304,6 @@
field public static final int config_helpPackageNameKey = 17039387; // 0x104001b
field public static final int config_helpPackageNameValue = 17039388; // 0x104001c
field public static final int config_systemGallery = 17039402; // 0x104002a
- field public static final int low_memory = 17039403; // 0x104002b
}
public static final class R.style {
diff --git a/api/system-lint-baseline.txt b/api/system-lint-baseline.txt
index 55333cf..10c96a3 100644
--- a/api/system-lint-baseline.txt
+++ b/api/system-lint-baseline.txt
@@ -246,12 +246,6 @@
-ResourceValueFieldName: android.R.array#config_sms_enabled_locking_shift_tables:
- Expected resource name in `android.R.array` to be in the `fooBarBaz` style, was `config_sms_enabled_locking_shift_tables`
-ResourceValueFieldName: android.R.array#config_sms_enabled_single_shift_tables:
- Expected resource name in `android.R.array` to be in the `fooBarBaz` style, was `config_sms_enabled_single_shift_tables`
-
-
SamShouldBeLast: android.accounts.AccountManager#addAccount(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
SamShouldBeLast: android.accounts.AccountManager#addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean):
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index bc8d05e..5b8ee71 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -5736,6 +5736,10 @@
throwIfParentInstance("isAlwaysOnVpnLockdownEnabled");
if (mService != null) {
try {
+ // Starting from Android R, the caller can pass the permission check in
+ // DevicePolicyManagerService if it holds android.permission.MAINLINE_NETWORK_STACK.
+ // Note that the android.permission.MAINLINE_NETWORK_STACK is a signature permission
+ // which is used by the NetworkStack mainline module.
return mService.isAlwaysOnVpnLockdownEnabled(admin);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -11991,7 +11995,8 @@
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with
* @param timeoutMillis Maximum time the profile is allowed to be off in milliseconds or 0 if
- * not limited.
+ * not limited. The minimum non-zero value corresponds to 72 hours. If an admin sets a
+ * smaller non-zero vaulue, 72 hours will be set instead.
* @throws IllegalStateException if the profile owner doesn't have an activity that handles
* {@link #ACTION_CHECK_POLICY_COMPLIANCE}
* @see #setPersonalAppsSuspended
diff --git a/core/java/android/service/textclassifier/TextClassifierService.java b/core/java/android/service/textclassifier/TextClassifierService.java
index 3ff6f54..9dfbc28 100644
--- a/core/java/android/service/textclassifier/TextClassifierService.java
+++ b/core/java/android/service/textclassifier/TextClassifierService.java
@@ -41,7 +41,6 @@
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 @@
*/
@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 3164567..0000000
--- 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<ActionsModelParamsSupplier.ActionsModelParams> {
- 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<Runnable> 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 3ed48f6..0000000
--- 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.
- * <p>
- * 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:
- * <ul>
- * <li> User A: xxx
- * <li> Local user: yyy
- * <li> User B: zzz
- * </ul>
- * 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<ConversationActions.Message> messages,
- Function<CharSequence, String> languageDetector) {
- List<ConversationActions.Message> messagesWithText =
- messages.stream()
- .filter(message -> !TextUtils.isEmpty(message.getText()))
- .collect(Collectors.toCollection(ArrayList::new));
- if (messagesWithText.isEmpty()) {
- return new ActionsSuggestionsModel.ConversationMessage[0];
- }
- Deque<ActionsSuggestionsModel.ConversationMessage> 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<ConversationActions.Message> messages,
- int modelVersion,
- List<Locale> 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<LabeledIntent> 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<ConversationAction> removeActionsWithDuplicates(
- List<ConversationAction> 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<Pair<String, String>, Integer> counter = new ArrayMap<>();
- for (ConversationAction conversationAction : conversationActions) {
- Pair<String, String> representation = getRepresentation(conversationAction);
- if (representation == null) {
- continue;
- }
- Integer existingCount = counter.getOrDefault(representation, 0);
- counter.put(representation, existingCount + 1);
- }
- List<ConversationAction> result = new ArrayList<>();
- for (ConversationAction conversationAction : conversationActions) {
- Pair<String, String> representation = getRepresentation(conversationAction);
- if (representation == null || counter.getOrDefault(representation, 0) == 1) {
- result.add(conversationAction);
- }
- }
- return result;
- }
-
- @Nullable
- private static Pair<String, String> 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<Person, Integer> 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 11e0e2c..9e2b642 100644
--- a/core/java/android/view/textclassifier/ExtrasUtils.java
+++ b/core/java/android/view/textclassifier/ExtrasUtils.java
@@ -19,15 +19,9 @@
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 @@
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) {
@@ -93,72 +54,6 @@
}
/**
- * @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<Intent> 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.
*/
@Nullable
@@ -167,48 +62,6 @@
}
/**
- * 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.
*/
@Nullable
@@ -224,7 +77,7 @@
* 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 @@
}
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<Bundle> 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<Bundle> 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 17ec73a..0000000
--- 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<String, LinkifyStats> 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<String, LinkifyStats> 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 03ed496..98ee09c 100644
--- a/core/java/android/view/textclassifier/Log.java
+++ b/core/java/android/view/textclassifier/Log.java
@@ -32,7 +32,7 @@
* 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 0a4ff5d..0000000
--- 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<List<ModelFile>> mModelFileSupplier;
-
- private List<ModelFile> mModelFiles;
-
- public ModelFileManager(Supplier<List<ModelFile>> modelFileSupplier) {
- mModelFileSupplier = Objects.requireNonNull(modelFileSupplier);
- }
-
- /**
- * Returns an unmodifiable list of model files listed by the given model files supplier.
- * <p>
- * The result is cached.
- */
- public List<ModelFile> 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<Locale.LanguageRange> 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<List<ModelFile>> {
- private final File mUpdatedModelFile;
- private final File mFactoryModelDir;
- private final Pattern mModelFilenamePattern;
- private final Function<Integer, Integer> mVersionSupplier;
- private final Function<Integer, String> mSupportedLocalesSupplier;
-
- public ModelFileSupplierImpl(
- File factoryModelDir,
- String factoryModelFileNameRegex,
- File updatedModelFile,
- Function<Integer, Integer> versionSupplier,
- Function<Integer, String> 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<ModelFile> get() {
- final List<ModelFile> 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<Locale> 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<Locale> mSupportedLocales;
- private final String mSupportedLocalesStr;
- private final boolean mLanguageIndependent;
-
- public ModelFile(File file, int version, List<Locale> 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<Locale.LanguageRange> languageRanges) {
- Objects.requireNonNull(languageRanges);
- return mLanguageIndependent || Locale.lookup(languageRanges, mSupportedLocales) != null;
- }
-
- /** Returns an immutable lists of supported locales. */
- public List<Locale> 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 9a54544..6f9556b 100644
--- a/core/java/android/view/textclassifier/SelectionEvent.java
+++ b/core/java/android/view/textclassifier/SelectionEvent.java
@@ -158,7 +158,6 @@
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 @@
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 @@
*/
@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 ae9f65b..e7d896e 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<Locale> 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 @@
@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 @@
}
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 86ef4e1..8eac1c1 100644
--- a/core/java/android/view/textclassifier/SystemTextClassifier.java
+++ b/core/java/android/view/textclassifier/SystemTextClassifier.java
@@ -45,7 +45,7 @@
@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 8b9d129..3aed32a 100644
--- a/core/java/android/view/textclassifier/TextClassification.java
+++ b/core/java/android/view/textclassifier/TextClassification.java
@@ -44,8 +44,6 @@
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 @@
@NonNull private List<RemoteAction> mActions = new ArrayList<>();
@NonNull private final Map<String, Float> mTypeScoreMap = new ArrayMap<>();
- @NonNull
- private final Map<String, AnnotatorModel.ClassificationResult> mClassificationResults =
- new ArrayMap<>();
@Nullable private String mText;
@Nullable private Drawable mLegacyIcon;
@Nullable private String mLegacyLabel;
@@ -362,36 +357,7 @@
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 @@
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<String> sortedTypes = entityConfidence.getEntities();
- ArrayList<AnnotatorModel.ClassificationResult> 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 3d5ac58..adb6fea 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 @@
*/
// TODO: Rename to TextClassifierSettings.
public final class TextClassificationConstants {
- private static final String DELIMITER = ":";
-
/**
* Whether the smart linkify feature is enabled.
*/
@@ -60,7 +53,6 @@
* 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.
*/
@@ -75,88 +67,10 @@
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.
- * <p>
- * Format= minimumTextSize<int>:penalizeRatio<float>:textScoreRatio<float>
- * <p>
- * e.g. 20:1.0:0.4
- * <p>
- * Accept all text lengths with minimumTextSize=0
- * <p>
- * 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:
* <pre>
@@ -168,16 +82,6 @@
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 @@
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<String> 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<String> 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 @@
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<String> getEntityListDefault() {
- return getDeviceConfigStringList(ENTITY_LIST_DEFAULT, ENTITY_LIST_DEFAULT_VALUE);
- }
-
- public List<String> getEntityListNotEditable() {
- return getDeviceConfigStringList(ENTITY_LIST_NOT_EDITABLE, ENTITY_LIST_DEFAULT_VALUE);
- }
-
- public List<String> getEntityListEditable() {
- return getDeviceConfigStringList(ENTITY_LIST_EDITABLE, ENTITY_LIST_DEFAULT_VALUE);
- }
-
- public List<String> getInAppConversationActionTypes() {
- return getDeviceConfigStringList(
- IN_APP_CONVERSATION_ACTION_TYPES_DEFAULT,
- CONVERSATION_ACTIONS_TYPES_DEFAULT_VALUES);
- }
-
- public List<String> 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 @@
.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<String> getDeviceConfigStringList(String key, List<String> 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<String> parse(@Nullable String listStr, List<String> 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 dfbec9b..fa4f7d6 100644
--- a/core/java/android/view/textclassifier/TextClassificationManager.java
+++ b/core/java/android/view/textclassifier/TextClassificationManager.java
@@ -19,21 +19,15 @@
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 @@
@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 @@
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 TextClassificationManager(Context context) {
mContext = Objects.requireNonNull(context);
mSessionFactory = mDefaultSessionFactory;
- mSettingsObserver = new SettingsObserver(this);
}
/**
@@ -112,7 +101,7 @@
*
* @see TextClassifier#LOCAL
* @see TextClassifier#SYSTEM
- * @see TextClassifier#DEFAULT_SERVICE
+ * @see TextClassifier#DEFAULT_SYSTEM
* @hide
*/
@UnsupportedAppUsage
@@ -189,28 +178,17 @@
}
}
- @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 @@
*/
@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 @@
return sDefaultSettings;
}
}
-
- private static final class SettingsObserver implements
- DeviceConfig.OnPropertiesChangedListener {
-
- private final WeakReference<TextClassificationManager> 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<String> 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 2cc226d..6d5077a 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -61,19 +61,32 @@
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 @@
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 8162699..0000000
--- 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 d7149ee..0000000
--- 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.
- *
- * <p>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<ActionsModelParams> 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<String> 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<String, Float> 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<String> getEntitiesForHints(Collection<String> 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.
- * <p>
- * 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<String> expectedTypes = resolveActionTypesFromRequest(request);
- List<ConversationAction> 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<String> 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<String> resolveActionTypesFromRequest(ConversationActions.Request request) {
- List<String> 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<Bundle, Bundle> languagesBundles = generateLanguageBundles(text, start, end);
- final Bundle textLanguagesBundle = languagesBundles.first;
- final Bundle foreignLanguageBundle = languagesBundles.second;
- builder.setForeignLanguageExtra(foreignLanguageBundle);
-
- boolean isPrimaryAction = true;
- final List<LabeledIntent> 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.
- * <p>
- * 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)}.
- * <p>
- * 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<Bundle, Bundle> 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<String, Float> newScores = new ArrayMap<>();
- final Set<String> 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<String, Float> 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 22e374f2..0000000
--- 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<LabeledIntent> 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<LabeledIntent> 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 cbd9d1a..0000000
--- 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.
- *
- * <p>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 8d60ad8..0000000
--- 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<LabeledIntent> 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<LabeledIntent> 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<LabeledIntent> createForEmail(Context context, String text) {
- final List<LabeledIntent> 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<LabeledIntent> createForPhone(Context context, String text) {
- final List<LabeledIntent> 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<LabeledIntent> createForAddress(Context context, String text) {
- final List<LabeledIntent> 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<LabeledIntent> createForUrl(Context context, String text) {
- if (Uri.parse(text).getScheme() == null) {
- text = "http://" + text;
- }
- final List<LabeledIntent> 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<LabeledIntent> 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<LabeledIntent> 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<LabeledIntent> createForFlight(Context context, String text) {
- final List<LabeledIntent> 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<LabeledIntent> createForDictionary(Context context, String text) {
- final List<LabeledIntent> 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 aef4bd6..0000000
--- 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<LabeledIntent> 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<LabeledIntent> 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 7a39569..0000000
--- 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<LabeledIntent> create(
- @NonNull RemoteActionTemplate[] remoteActionTemplates) {
- if (remoteActionTemplates.length == 0) {
- return new ArrayList<>();
- }
- final List<LabeledIntent> 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 28cb80d..0000000
--- 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 4ef3f61..45943f5 100644
--- a/core/java/android/widget/SelectionActionModeHelper.java
+++ b/core/java/android/widget/SelectionActionModeHelper.java
@@ -39,7 +39,6 @@
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 @@
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/res/res/values/config.xml b/core/res/res/values/config.xml
index a2aa492..6060d9a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4420,7 +4420,7 @@
<string name="config_customSessionPolicyProvider"></string>
<!-- The max scale for the wallpaper when it's zoomed in -->
- <item name="config_wallpaperMaxScale" format="float" type="dimen">1</item>
+ <item name="config_wallpaperMaxScale" format="float" type="dimen">1.15</item>
<!-- Package name that will receive an explicit manifest broadcast for
android.os.action.POWER_SAVE_MODE_CHANGED. -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 7230cc4..cf68aff 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3044,8 +3044,6 @@
<public name="config_defaultCallScreening" />
<!-- @hide @SystemApi @TestApi -->
<public name="config_systemGallery" />
- <!-- @hide @SystemApi -->
- <public name="low_memory" />
</public-group>
<public-group type="bool" first-id="0x01110005">
@@ -3073,12 +3071,6 @@
<public-group type="array" first-id="0x01070006">
<!-- @hide @SystemApi -->
<public name="simColors" />
- <!-- @hide @SystemApi -->
- <public name="config_restrictedPreinstalledCarrierApps" />
- <!-- @hide @SystemApi -->
- <public name="config_sms_enabled_single_shift_tables" />
- <!-- @hide @SystemApi -->
- <public name="config_sms_enabled_locking_shift_tables" />
</public-group>
<public-group type="string" first-id="0x0104002c">
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 8744997..0000000
--- 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 ec7e83f..0000000
--- 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<CharSequence, String> 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<ConversationAction> 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<ConversationAction> 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 79e1406..0000000
--- 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<List<ModelFileManager.ModelFile>> 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<ModelFileManager.ModelFile> 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<ModelFileManager.ModelFile> modelFiles = mModelFileSupplierImpl.get();
- List<String> 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<ModelFileManager.ModelFile> 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 82fa73f..2f21b7f 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 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 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 @@
}
}
- @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 1ca4649..628252d 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 @@
@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 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 372a478..0000000
--- 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.
- * <p>
- * 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<Object> 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<Bundle> 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<String> hints = Collections.EMPTY_LIST;
- List<String> included = Collections.EMPTY_LIST;
- List<String> 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<String> 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<String> hints = Collections.EMPTY_LIST;
- List<String> included = Arrays.asList(TextClassifier.TYPE_EMAIL);
- List<String> 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<Bundle> 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<Bundle> 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<TextSelection> isTextSelection(
- final int startIndex, final int endIndex, final String type) {
- return new BaseMatcher<TextSelection>() {
- @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<TextLinks> isTextLinksContaining(
- final String text, final String substring, final String type) {
- return new BaseMatcher<TextLinks>() {
-
- @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<TextClassification> isTextClassification(
- final String text, final String type) {
- return new BaseMatcher<TextClassification>() {
- @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<TextClassification> containsIntentWithAction(final String action) {
- return new BaseMatcher<TextClassification>() {
- @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<TextLanguage> isTextLanguage(final String languageTag) {
- return new BaseMatcher<TextLanguage>() {
- @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<ConversationAction> isConversationAction(String actionType) {
- return new BaseMatcher<ConversationAction>() {
- @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 3ad26f5..0000000
--- 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 8891d3f..0000000
--- 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<LabeledIntent> 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<LabeledIntent> 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 bcea5fe..0000000
--- 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<LabeledIntent> 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<LabeledIntent> 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 a33c358..0000000
--- 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<LabeledIntent> 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<LabeledIntent> 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<LabeledIntent> 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<LabeledIntent> 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<LabeledIntent> 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<LabeledIntent> 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<LabeledIntent> 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<LabeledIntent> 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 5e8e582..0000000
--- 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<String, Float> phoneEntityScores = new ArrayMap<>();
- phoneEntityScores.put(TextClassifier.TYPE_PHONE, 0.9f);
- phoneEntityScores.put(TextClassifier.TYPE_OTHER, 0.1f);
- final Map<String, Float> 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<LogMaker> 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<LogMaker> 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<LogMaker> 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 321a7f2..0000000
--- 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 2c540e5..0000000
--- 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<LogMaker> 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));
- }
-}
diff --git a/keystore/java/android/security/keystore/KeymasterUtils.java b/keystore/java/android/security/keystore/KeymasterUtils.java
index bc933ff..670ef5e 100644
--- a/keystore/java/android/security/keystore/KeymasterUtils.java
+++ b/keystore/java/android/security/keystore/KeymasterUtils.java
@@ -82,6 +82,63 @@
}
}
+ private static void addSids(KeymasterArguments args, UserAuthArgs spec) {
+ // If both biometric and credential are accepted, then just use the root sid from gatekeeper
+ if (spec.getUserAuthenticationType() == (KeyProperties.AUTH_BIOMETRIC_STRONG
+ | KeyProperties.AUTH_DEVICE_CREDENTIAL)) {
+ if (spec.getBoundToSpecificSecureUserId() != GateKeeper.INVALID_SECURE_USER_ID) {
+ args.addUnsignedLong(KeymasterDefs.KM_TAG_USER_SECURE_ID,
+ KeymasterArguments.toUint64(spec.getBoundToSpecificSecureUserId()));
+ } else {
+ // The key is authorized for use for the specified amount of time after the user has
+ // authenticated. Whatever unlocks the secure lock screen should authorize this key.
+ args.addUnsignedLong(KeymasterDefs.KM_TAG_USER_SECURE_ID,
+ KeymasterArguments.toUint64(getRootSid()));
+ }
+ } else {
+ List<Long> sids = new ArrayList<>();
+ if ((spec.getUserAuthenticationType() & KeyProperties.AUTH_BIOMETRIC_STRONG) != 0) {
+ final BiometricManager bm = KeyStore.getApplicationContext()
+ .getSystemService(BiometricManager.class);
+
+ // TODO: Restore permission check in getAuthenticatorIds once the ID is no longer
+ // needed here.
+
+ final long[] biometricSids = bm.getAuthenticatorIds();
+
+ if (biometricSids.length == 0) {
+ throw new IllegalStateException(
+ "At least one biometric must be enrolled to create keys requiring user"
+ + " authentication for every use");
+ }
+
+ if (spec.getBoundToSpecificSecureUserId() != GateKeeper.INVALID_SECURE_USER_ID) {
+ sids.add(spec.getBoundToSpecificSecureUserId());
+ } else if (spec.isInvalidatedByBiometricEnrollment()) {
+ // The biometric-only SIDs will change on biometric enrollment or removal of all
+ // enrolled templates, invalidating the key.
+ for (long sid : biometricSids) {
+ sids.add(sid);
+ }
+ } else {
+ // The root SID will *not* change on fingerprint enrollment, or removal of all
+ // enrolled fingerprints, allowing the key to remain valid.
+ sids.add(getRootSid());
+ }
+ } else if ((spec.getUserAuthenticationType() & KeyProperties.AUTH_DEVICE_CREDENTIAL)
+ != 0) {
+ sids.add(getRootSid());
+ } else {
+ throw new IllegalStateException("Invalid or no authentication type specified.");
+ }
+
+ for (int i = 0; i < sids.size(); i++) {
+ args.addUnsignedLong(KeymasterDefs.KM_TAG_USER_SECURE_ID,
+ KeymasterArguments.toUint64(sids.get(i)));
+ }
+ }
+ }
+
/**
* Adds keymaster arguments to express the key's authorization policy supported by user
* authentication.
@@ -114,40 +171,7 @@
if (spec.getUserAuthenticationValidityDurationSeconds() == 0) {
// Every use of this key needs to be authorized by the user.
- final BiometricManager bm = KeyStore.getApplicationContext()
- .getSystemService(BiometricManager.class);
-
- // TODO: Restore permission check in getAuthenticatorIds once the ID is no longer
- // needed here.
-
- final long[] biometricSids = bm.getAuthenticatorIds();
-
- if (biometricSids.length == 0) {
- throw new IllegalStateException(
- "At least one biometric must be enrolled to create keys requiring user"
- + " authentication for every use");
- }
-
- List<Long> sids = new ArrayList<>();
- if (spec.getBoundToSpecificSecureUserId() != GateKeeper.INVALID_SECURE_USER_ID) {
- sids.add(spec.getBoundToSpecificSecureUserId());
- } else if (spec.isInvalidatedByBiometricEnrollment()) {
- // The biometric-only SIDs will change on biometric enrollment or removal of all
- // enrolled templates, invalidating the key.
- for (long sid : biometricSids) {
- sids.add(sid);
- }
- } else {
- // The root SID will *not* change on fingerprint enrollment, or removal of all
- // enrolled fingerprints, allowing the key to remain valid.
- sids.add(getRootSid());
- }
-
- for (int i = 0; i < sids.size(); i++) {
- args.addUnsignedLong(KeymasterDefs.KM_TAG_USER_SECURE_ID,
- KeymasterArguments.toUint64(sids.get(i)));
- }
-
+ addSids(args, spec);
args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, spec.getUserAuthenticationType());
if (spec.isUserAuthenticationValidWhileOnBody()) {
@@ -155,16 +179,7 @@
+ "supported for keys requiring fingerprint authentication");
}
} else {
- long sid;
- if (spec.getBoundToSpecificSecureUserId() != GateKeeper.INVALID_SECURE_USER_ID) {
- sid = spec.getBoundToSpecificSecureUserId();
- } else {
- // The key is authorized for use for the specified amount of time after the user has
- // authenticated. Whatever unlocks the secure lock screen should authorize this key.
- sid = getRootSid();
- }
- args.addUnsignedLong(KeymasterDefs.KM_TAG_USER_SECURE_ID,
- KeymasterArguments.toUint64(sid));
+ addSids(args, spec);
args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, spec.getUserAuthenticationType());
args.addUnsignedInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT,
spec.getUserAuthenticationValidityDurationSeconds());
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java
index 8b8b9e5..48be0d9 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java
@@ -64,16 +64,26 @@
dest.writeInt(mTetheringType);
}
+ /**
+ * Get the MAC address used to identify the client.
+ */
@NonNull
public MacAddress getMacAddress() {
return mMacAddress;
}
+ /**
+ * Get information on the list of addresses that are associated with the client.
+ */
@NonNull
public List<AddressInfo> getAddresses() {
return new ArrayList<>(mAddresses);
}
+ /**
+ * Get the type of tethering used by the client.
+ * @return one of the {@code TetheringManager#TETHERING_*} constants.
+ */
public int getTetheringType() {
return mTetheringType;
}
@@ -115,45 +125,47 @@
private final LinkAddress mAddress;
@Nullable
private final String mHostname;
- // TODO: use LinkAddress expiration time once it is supported
- private final long mExpirationTime;
/** @hide */
public AddressInfo(@NonNull LinkAddress address, @Nullable String hostname) {
- this(address, hostname, 0);
- }
-
- /** @hide */
- public AddressInfo(@NonNull LinkAddress address, String hostname, long expirationTime) {
this.mAddress = address;
this.mHostname = hostname;
- this.mExpirationTime = expirationTime;
}
private AddressInfo(Parcel in) {
- this(in.readParcelable(null), in.readString(), in.readLong());
+ this(in.readParcelable(null), in.readString());
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeParcelable(mAddress, flags);
dest.writeString(mHostname);
- dest.writeLong(mExpirationTime);
}
+ /**
+ * Get the link address (including prefix length and lifetime) used by the client.
+ *
+ * This may be an IPv4 or IPv6 address.
+ */
@NonNull
public LinkAddress getAddress() {
return mAddress;
}
+ /**
+ * Get the hostname that was advertised by the client when obtaining its address, if any.
+ */
@Nullable
public String getHostname() {
return mHostname;
}
- /** @hide TODO: use expiration time in LinkAddress */
+ /**
+ * Get the expiration time of the address assigned to the client.
+ * @hide
+ */
public long getExpirationTime() {
- return mExpirationTime;
+ return mAddress.getExpirationTime();
}
@Override
@@ -163,7 +175,7 @@
@Override
public int hashCode() {
- return Objects.hash(mAddress, mHostname, mExpirationTime);
+ return Objects.hash(mAddress, mHostname);
}
@Override
@@ -173,8 +185,7 @@
// Use .equals() for addresses as all changes, including address expiry changes,
// should be included.
return other.mAddress.equals(mAddress)
- && Objects.equals(mHostname, other.mHostname)
- && mExpirationTime == other.mExpirationTime;
+ && Objects.equals(mHostname, other.mHostname);
}
@NonNull
diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java
index 38f8609..6c0c432 100644
--- a/packages/Tethering/src/android/net/ip/IpServer.java
+++ b/packages/Tethering/src/android/net/ip/IpServer.java
@@ -24,6 +24,7 @@
import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;
import static android.net.util.NetworkConstants.asByte;
import static android.net.util.TetheringMessageBase.BASE_IPSERVER;
+import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
import android.net.INetd;
import android.net.INetworkStackStatusCallback;
@@ -448,7 +449,9 @@
final ArrayList<TetheredClient> leases = new ArrayList<>();
for (DhcpLeaseParcelable lease : leaseParcelables) {
final LinkAddress address = new LinkAddress(
- intToInet4AddressHTH(lease.netAddr), lease.prefixLength);
+ intToInet4AddressHTH(lease.netAddr), lease.prefixLength,
+ 0 /* flags */, RT_SCOPE_UNIVERSE /* as per RFC6724#3.2 */,
+ lease.expTime /* deprecationTime */, lease.expTime /* expirationTime */);
final MacAddress macAddress;
try {
@@ -460,7 +463,7 @@
}
final TetheredClient.AddressInfo addressInfo = new TetheredClient.AddressInfo(
- address, lease.hostname, lease.expTime);
+ address, lease.hostname);
leases.add(new TetheredClient(
macAddress,
Collections.singletonList(addressInfo),
diff --git a/packages/Tethering/tests/unit/src/android/net/TetheredClientTest.kt b/packages/Tethering/tests/unit/src/android/net/TetheredClientTest.kt
index d85389a..a20a0df 100644
--- a/packages/Tethering/tests/unit/src/android/net/TetheredClientTest.kt
+++ b/packages/Tethering/tests/unit/src/android/net/TetheredClientTest.kt
@@ -20,6 +20,7 @@
import android.net.TetheredClient.AddressInfo
import android.net.TetheringManager.TETHERING_BLUETOOTH
import android.net.TetheringManager.TETHERING_USB
+import android.system.OsConstants.RT_SCOPE_UNIVERSE
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.testutils.assertParcelSane
@@ -30,11 +31,19 @@
private val TEST_MACADDR = MacAddress.fromBytes(byteArrayOf(12, 23, 34, 45, 56, 67))
private val TEST_OTHER_MACADDR = MacAddress.fromBytes(byteArrayOf(23, 34, 45, 56, 67, 78))
-private val TEST_ADDR1 = LinkAddress(parseNumericAddress("192.168.113.3"), 24)
-private val TEST_ADDR2 = LinkAddress(parseNumericAddress("fe80::1:2:3"), 64)
+private val TEST_ADDR1 = makeLinkAddress("192.168.113.3", prefixLength = 24, expTime = 123L)
+private val TEST_ADDR2 = makeLinkAddress("fe80::1:2:3", prefixLength = 64, expTime = 456L)
private val TEST_ADDRINFO1 = AddressInfo(TEST_ADDR1, "test_hostname")
private val TEST_ADDRINFO2 = AddressInfo(TEST_ADDR2, null)
+private fun makeLinkAddress(addr: String, prefixLength: Int, expTime: Long) = LinkAddress(
+ parseNumericAddress(addr),
+ prefixLength,
+ 0 /* flags */,
+ RT_SCOPE_UNIVERSE,
+ expTime /* deprecationTime */,
+ expTime /* expirationTime */)
+
@RunWith(AndroidJUnit4::class)
@SmallTest
class TetheredClientTest {
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/ConnectedClientsTrackerTest.kt b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/ConnectedClientsTrackerTest.kt
index 56f3e21..1cdc3bb 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/ConnectedClientsTrackerTest.kt
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/ConnectedClientsTrackerTest.kt
@@ -46,23 +46,28 @@
private val client1Addr = MacAddress.fromString("01:23:45:67:89:0A")
private val client1 = TetheredClient(client1Addr, listOf(
- AddressInfo(LinkAddress("192.168.43.44/32"), null /* hostname */, clock.time + 20)),
+ makeAddrInfo("192.168.43.44/32", null /* hostname */, clock.time + 20)),
TETHERING_WIFI)
private val wifiClient1 = makeWifiClient(client1Addr)
private val client2Addr = MacAddress.fromString("02:34:56:78:90:AB")
- private val client2Exp30AddrInfo = AddressInfo(
- LinkAddress("192.168.43.45/32"), "my_hostname", clock.time + 30)
+ private val client2Exp30AddrInfo = makeAddrInfo(
+ "192.168.43.45/32", "my_hostname", clock.time + 30)
private val client2 = TetheredClient(client2Addr, listOf(
client2Exp30AddrInfo,
- AddressInfo(LinkAddress("2001:db8:12::34/72"), "other_hostname", clock.time + 10)),
+ makeAddrInfo("2001:db8:12::34/72", "other_hostname", clock.time + 10)),
TETHERING_WIFI)
private val wifiClient2 = makeWifiClient(client2Addr)
private val client3Addr = MacAddress.fromString("03:45:67:89:0A:BC")
private val client3 = TetheredClient(client3Addr,
- listOf(AddressInfo(LinkAddress("2001:db8:34::34/72"), "other_other_hostname",
- clock.time + 10)),
+ listOf(makeAddrInfo("2001:db8:34::34/72", "other_other_hostname", clock.time + 10)),
TETHERING_USB)
+ private fun makeAddrInfo(addr: String, hostname: String?, expTime: Long) =
+ LinkAddress(addr).let {
+ AddressInfo(LinkAddress(it.address, it.prefixLength, it.flags, it.scope,
+ expTime /* deprecationTime */, expTime /* expirationTime */), hostname)
+ }
+
@Test
fun testUpdateConnectedClients() {
doReturn(emptyList<TetheredClient>()).`when`(server1).allLeases
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index d12e03d..c37ea8b 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -301,6 +301,14 @@
public abstract boolean destroyDeSnapshots(int rollbackId);
/**
+ * Deletes snapshots of the credential encrypted apex data directories for the specified user,
+ * where the rollback id is not included in {@code retainRollbackIds}.
+ *
+ * @return boolean true if the delete was successful
+ */
+ public abstract boolean destroyCeSnapshotsNotSpecified(int userId, int[] retainRollbackIds);
+
+ /**
* Dumps various state information to the provided {@link PrintWriter} object.
*
* @param pw the {@link PrintWriter} object to send information to.
@@ -745,6 +753,17 @@
}
}
+ @Override
+ public boolean destroyCeSnapshotsNotSpecified(int userId, int[] retainRollbackIds) {
+ try {
+ mApexService.destroyCeSnapshotsNotSpecified(userId, retainRollbackIds);
+ return true;
+ } catch (Exception e) {
+ Slog.e(TAG, e.getMessage(), e);
+ return false;
+ }
+ }
+
/**
* Dump information about the packages contained in a particular cache
* @param packagesCache the cache to print information about.
@@ -963,6 +982,11 @@
}
@Override
+ public boolean destroyCeSnapshotsNotSpecified(int userId, int[] retainRollbackIds) {
+ return true;
+ }
+
+ @Override
void dump(PrintWriter pw, String packageName) {
// No-op
}
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 42fada1..b50c22e 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -63,6 +63,7 @@
import com.android.server.PackageWatchdog;
import com.android.server.SystemConfig;
import com.android.server.Watchdog;
+import com.android.server.pm.ApexManager;
import com.android.server.pm.Installer;
import java.io.File;
@@ -485,6 +486,8 @@
}
latch.countDown();
+
+ destroyCeSnapshotsForExpiredRollbacks(userId);
});
try {
@@ -495,6 +498,15 @@
}
@WorkerThread
+ private void destroyCeSnapshotsForExpiredRollbacks(int userId) {
+ int[] rollbackIds = new int[mRollbacks.size()];
+ for (int i = 0; i < rollbackIds.length; i++) {
+ rollbackIds[i] = mRollbacks.get(i).info.getRollbackId();
+ }
+ ApexManager.getInstance().destroyCeSnapshotsNotSpecified(userId, rollbackIds);
+ }
+
+ @WorkerThread
private void updateRollbackLifetimeDurationInMillis() {
mRollbackLifetimeDurationInMillis = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_ROLLBACK_BOOT,
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f5d2c6a..fc9044a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -393,6 +393,7 @@
private static final long MS_PER_DAY = TimeUnit.DAYS.toMillis(1);
private static final long EXPIRATION_GRACE_PERIOD_MS = 5 * MS_PER_DAY; // 5 days, in ms
+ private static final long MANAGED_PROFILE_MAXIMUM_TIME_OFF_THRESHOLD = 3 * MS_PER_DAY;
private static final String ACTION_EXPIRED_PASSWORD_NOTIFICATION =
"com.android.server.ACTION_EXPIRED_PASSWORD_NOTIFICATION";
@@ -15849,6 +15850,12 @@
// DO shouldn't be able to use this method.
enforceProfileOwnerOfOrganizationOwnedDevice(admin);
enforceHandlesCheckPolicyComplianceIntent(userId, admin.info.getPackageName());
+ Preconditions.checkArgument(timeoutMillis >= 0, "Timeout must be non-negative.");
+ // Ensure the timeout is long enough to avoid having bad user experience.
+ if (timeoutMillis > 0 && timeoutMillis < MANAGED_PROFILE_MAXIMUM_TIME_OFF_THRESHOLD
+ && !isAdminTestOnlyLocked(who, userId)) {
+ timeoutMillis = MANAGED_PROFILE_MAXIMUM_TIME_OFF_THRESHOLD;
+ }
if (admin.mProfileMaximumTimeOffMillis == timeoutMillis) {
return;
}
diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
index d9ae48f..b3d7c0d 100644
--- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
+++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
@@ -30,6 +30,7 @@
import android.util.ArrayMap;
import android.util.Log;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.util.ArrayUtils;
@@ -162,7 +163,7 @@
for (ApplicationInfo ai : candidates) {
String packageName = ai.packageName;
String[] restrictedCarrierApps = Resources.getSystem().getStringArray(
- android.R.array.config_restrictedPreinstalledCarrierApps);
+ R.array.config_restrictedPreinstalledCarrierApps);
boolean hasPrivileges = telephonyManager != null
&& telephonyManager.checkCarrierPrivilegesForPackageAnyPhone(packageName)
== TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS
diff --git a/telephony/common/com/android/internal/telephony/GsmAlphabet.java b/telephony/common/com/android/internal/telephony/GsmAlphabet.java
index c62cec2..5c53f7e 100644
--- a/telephony/common/com/android/internal/telephony/GsmAlphabet.java
+++ b/telephony/common/com/android/internal/telephony/GsmAlphabet.java
@@ -19,10 +19,12 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.Resources;
import android.os.Build;
-import android.text.TextUtils;
import android.util.Log;
+import android.text.TextUtils;
import android.util.SparseIntArray;
+import com.android.internal.R;
+
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
@@ -1087,10 +1089,8 @@
private static void enableCountrySpecificEncodings() {
Resources r = Resources.getSystem();
// See comments in frameworks/base/core/res/res/values/config.xml for allowed values
- sEnabledSingleShiftTables = r.getIntArray(
- android.R.array.config_sms_enabled_single_shift_tables);
- sEnabledLockingShiftTables = r.getIntArray(
- android.R.array.config_sms_enabled_locking_shift_tables);
+ sEnabledSingleShiftTables = r.getIntArray(R.array.config_sms_enabled_single_shift_tables);
+ sEnabledLockingShiftTables = r.getIntArray(R.array.config_sms_enabled_locking_shift_tables);
if (sEnabledSingleShiftTables.length > 0) {
sHighestEnabledSingleShiftCode =
diff --git a/telephony/common/com/google/android/mms/util/SqliteWrapper.java b/telephony/common/com/google/android/mms/util/SqliteWrapper.java
index 4871434..31fe4d7 100644
--- a/telephony/common/com/google/android/mms/util/SqliteWrapper.java
+++ b/telephony/common/com/google/android/mms/util/SqliteWrapper.java
@@ -60,7 +60,8 @@
@UnsupportedAppUsage
public static void checkSQLiteException(Context context, SQLiteException e) {
if (isLowMemory(e)) {
- Toast.makeText(context, android.R.string.low_memory, Toast.LENGTH_SHORT).show();
+ Toast.makeText(context, com.android.internal.R.string.low_memory,
+ Toast.LENGTH_SHORT).show();
} else {
throw e;
}
diff --git a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
index 173dbd1..de65ba2 100644
--- a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
+++ b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
@@ -22,6 +22,9 @@
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.assertParcelSane
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -43,4 +46,27 @@
}.build()
assertParcelSane(config, 9)
}
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ fun testBuilder() {
+ val config = NetworkAgentConfig.Builder().apply {
+ setExplicitlySelected(true)
+ setLegacyType(ConnectivityManager.TYPE_ETHERNET)
+ setSubscriberId("MySubId")
+ setPartialConnectivityAcceptable(false)
+ setUnvalidatedConnectivityAcceptable(true)
+ setLegacyTypeName("TEST_NETWORK")
+ disableNat64Detection()
+ disableProvisioningNotification()
+ }.build()
+
+ assertTrue(config.isExplicitlySelected())
+ assertEquals(ConnectivityManager.TYPE_ETHERNET, config.getLegacyType())
+ assertEquals("MySubId", config.getSubscriberId())
+ assertFalse(config.isPartialConnectivityAcceptable())
+ assertTrue(config.isUnvalidatedConnectivityAcceptable())
+ assertEquals("TEST_NETWORK", config.getLegacyTypeName())
+ assertFalse(config.isNat64DetectionEnabled())
+ assertFalse(config.isProvisioningNotificationEnabled())
+ }
}