diff options
9 files changed, 328 insertions, 887 deletions
diff --git a/core/java/android/service/textclassifier/TextClassifierService.java b/core/java/android/service/textclassifier/TextClassifierService.java index 20dc2beeaa48..8dca69f856e5 100644 --- a/core/java/android/service/textclassifier/TextClassifierService.java +++ b/core/java/android/service/textclassifier/TextClassifierService.java @@ -37,7 +37,6 @@ import android.os.IBinder; import android.os.Looper; import android.os.Parcelable; import android.os.RemoteException; -import android.provider.Settings; import android.text.TextUtils; import android.util.Slog; import android.view.textclassifier.ConversationActions; @@ -437,28 +436,16 @@ public abstract class TextClassifierService extends Service { /** * Returns the component name of the system default textclassifier service if it can be found * on the system. Otherwise, returns null. - * @hide - */ - public static ComponentName getServiceComponentName(@NonNull Context context) { - return getServiceComponentName(context, new TextClassificationConstants( - () -> Settings.Global.getString( - context.getContentResolver(), - Settings.Global.TEXT_CLASSIFIER_CONSTANTS))); - } - - /** - * Returns the component name of the system default textclassifier service if it can be found - * on the system. Otherwise, returns null. - * @param context the text classification context - * @param settings TextClassifier specific settings. * + * @param context the text classification context * @hide */ @Nullable - public static ComponentName getServiceComponentName(@NonNull Context context, - @NonNull TextClassificationConstants settings) { + public static ComponentName getServiceComponentName(@NonNull Context context) { + final TextClassificationConstants settings = TextClassificationManager.getSettings(context); // get override TextClassifierService package name - String packageName = settings.getTextClassifierServiceName(); + String packageName = settings.getTextClassifierServicePackageOverride(); + ComponentName serviceComponent = null; final boolean isOverrideService = !TextUtils.isEmpty(packageName); if (isOverrideService) { @@ -468,7 +455,7 @@ public abstract class TextClassifierService extends Service { if (serviceComponent != null) { return serviceComponent; } - // If no TextClassifierService overrode or invalid override package name, read the first + // If no TextClassifierService override or invalid override package name, read the first // package defined in the config final String[] packages = context.getPackageManager().getSystemTextClassifierPackages(); if (packages.length == 0 || TextUtils.isEmpty(packages[0])) { diff --git a/core/java/android/view/textclassifier/ConfigParser.java b/core/java/android/view/textclassifier/ConfigParser.java deleted file mode 100644 index 9b51458d27d9..000000000000 --- a/core/java/android/view/textclassifier/ConfigParser.java +++ /dev/null @@ -1,264 +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.provider.DeviceConfig; -import android.util.ArrayMap; -import android.util.KeyValueListParser; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.annotations.VisibleForTesting.Visibility; -import com.android.internal.util.Preconditions; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.function.Supplier; - -/** - * Retrieves settings from {@link DeviceConfig} and {@link android.provider.Settings}. - * It will try DeviceConfig first and then Settings. - * - * @hide - */ -@VisibleForTesting(visibility = Visibility.PACKAGE) -public final class ConfigParser { - private static final String TAG = "ConfigParser"; - - public static final boolean ENABLE_DEVICE_CONFIG = true; - - private static final String STRING_LIST_DELIMITER = ":"; - - private final Supplier<String> mLegacySettingsSupplier; - private final Object mLock = new Object(); - @GuardedBy("mLock") - private final Map<String, Object> mCache = new ArrayMap<>(); - @GuardedBy("mLock") - private @Nullable KeyValueListParser mSettingsParser; // Call getLegacySettings() instead. - - public ConfigParser(Supplier<String> legacySettingsSupplier) { - mLegacySettingsSupplier = Preconditions.checkNotNull(legacySettingsSupplier); - } - - private KeyValueListParser getLegacySettings() { - synchronized (mLock) { - if (mSettingsParser == null) { - final String legacySettings = mLegacySettingsSupplier.get(); - try { - mSettingsParser = new KeyValueListParser(','); - mSettingsParser.setString(legacySettings); - } catch (IllegalArgumentException e) { - // Failed to parse the settings string, log this and move on with defaults. - Log.w(TAG, "Bad text_classifier_constants: " + legacySettings); - } - } - return mSettingsParser; - } - } - - /** - * Reads a boolean setting through the cache. - */ - public boolean getBoolean(String key, boolean defaultValue) { - synchronized (mLock) { - final Object cached = mCache.get(key); - if (cached instanceof Boolean) { - return (boolean) cached; - } - final boolean value; - if (ENABLE_DEVICE_CONFIG) { - value = DeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_TEXTCLASSIFIER, - key, - getLegacySettings().getBoolean(key, defaultValue)); - } else { - value = getLegacySettings().getBoolean(key, defaultValue); - } - mCache.put(key, value); - return value; - } - } - - /** - * Reads an integer setting through the cache. - */ - public int getInt(String key, int defaultValue) { - synchronized (mLock) { - final Object cached = mCache.get(key); - if (cached instanceof Integer) { - return (int) cached; - } - final int value; - if (ENABLE_DEVICE_CONFIG) { - value = DeviceConfig.getInt( - DeviceConfig.NAMESPACE_TEXTCLASSIFIER, - key, - getLegacySettings().getInt(key, defaultValue)); - } else { - value = getLegacySettings().getInt(key, defaultValue); - } - mCache.put(key, value); - return value; - } - } - - /** - * Reads a float setting through the cache. - */ - public float getFloat(String key, float defaultValue) { - synchronized (mLock) { - final Object cached = mCache.get(key); - if (cached instanceof Float) { - return (float) cached; - } - final float value; - if (ENABLE_DEVICE_CONFIG) { - value = DeviceConfig.getFloat( - DeviceConfig.NAMESPACE_TEXTCLASSIFIER, - key, - getLegacySettings().getFloat(key, defaultValue)); - } else { - value = getLegacySettings().getFloat(key, defaultValue); - } - mCache.put(key, value); - return value; - } - } - - /** - * Reads a string setting through the cache. - */ - public String getString(String key, String defaultValue) { - synchronized (mLock) { - final Object cached = mCache.get(key); - if (cached instanceof String) { - return (String) cached; - } - final String value; - if (ENABLE_DEVICE_CONFIG) { - value = DeviceConfig.getString( - DeviceConfig.NAMESPACE_TEXTCLASSIFIER, - key, - getLegacySettings().getString(key, defaultValue)); - } else { - value = getLegacySettings().getString(key, defaultValue); - } - mCache.put(key, value); - return value; - } - } - - /** - * Reads a string list setting through the cache. - */ - public List<String> getStringList(String key, List<String> defaultValue) { - synchronized (mLock) { - final Object cached = mCache.get(key); - if (cached instanceof List) { - final List asList = (List) cached; - if (asList.isEmpty()) { - return Collections.emptyList(); - } else if (asList.get(0) instanceof String) { - return (List<String>) cached; - } - } - final List<String> value; - if (ENABLE_DEVICE_CONFIG) { - value = getDeviceConfigStringList( - key, - getSettingsStringList(key, defaultValue)); - } else { - value = getSettingsStringList(key, defaultValue); - } - mCache.put(key, value); - return value; - } - } - - /** - * Reads a float array through the cache. The returned array should be expected to be of the - * same length as that of the defaultValue. - */ - public float[] getFloatArray(String key, float[] defaultValue) { - synchronized (mLock) { - final Object cached = mCache.get(key); - if (cached instanceof float[]) { - return (float[]) cached; - } - final float[] value; - if (ENABLE_DEVICE_CONFIG) { - value = getDeviceConfigFloatArray( - key, - getSettingsFloatArray(key, defaultValue)); - } else { - value = getSettingsFloatArray(key, defaultValue); - } - mCache.put(key, value); - return value; - } - } - - private List<String> getSettingsStringList(String key, List<String> defaultValue) { - return parse(mSettingsParser.getString(key, null), defaultValue); - } - - 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 float[] getSettingsFloatArray(String key, float[] defaultValue) { - return parse(mSettingsParser.getString(key, null), defaultValue); - } - - private static List<String> parse(@Nullable String listStr, List<String> defaultValue) { - if (listStr != null) { - return Collections.unmodifiableList( - Arrays.asList(listStr.split(STRING_LIST_DELIMITER))); - } - return defaultValue; - } - - private static float[] parse(@Nullable String arrayStr, float[] defaultValue) { - if (arrayStr != null) { - final String[] split = arrayStr.split(STRING_LIST_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; - } - } -} diff --git a/core/java/android/view/textclassifier/TextClassificationConstants.java b/core/java/android/view/textclassifier/TextClassificationConstants.java index 9f8671aad313..ed69513b7f69 100644 --- a/core/java/android/view/textclassifier/TextClassificationConstants.java +++ b/core/java/android/view/textclassifier/TextClassificationConstants.java @@ -16,38 +16,31 @@ package android.view.textclassifier; +import android.annotation.Nullable; +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; -import java.util.function.Supplier; /** * TextClassifier specific settings. - * This is encoded as a key=value list, separated by commas. - * <p> - * Example of setting the values for testing. - * <p> - * <pre> - * adb shell settings put global text_classifier_constants \ - * model_dark_launch_enabled=true,smart_selection_enabled=true, \ - * entity_list_default=phone:address, \ - * lang_id_context_settings=20:1.0:0.4 - * </pre> - * <p> - * Settings are also available in device config. These take precedence over those in settings - * global. - * <p> + * + * <p>Currently, this class does not guarantee co-diverted flags are updated atomically. + * * <pre> * adb shell cmd device_config put textclassifier system_textclassifier_enabled true * </pre> * - * @see android.provider.Settings.Global.TEXT_CLASSIFIER_CONSTANTS - * @see android.provider.DeviceConfig.NAMESPACE_TEXTCLASSIFIER + * @see android.provider.DeviceConfig#NAMESPACE_TEXTCLASSIFIER * @hide */ // TODO: Rename to TextClassifierSettings. public final class TextClassificationConstants { + private static final String DELIMITER = ":"; /** * Whether the smart linkify feature is enabled. @@ -56,11 +49,12 @@ public final class TextClassificationConstants { /** * Whether SystemTextClassifier is enabled. */ - private static final String SYSTEM_TEXT_CLASSIFIER_ENABLED = "system_textclassifier_enabled"; + static final String SYSTEM_TEXT_CLASSIFIER_ENABLED = "system_textclassifier_enabled"; /** * Whether TextClassifierImpl is enabled. */ - private static final String LOCAL_TEXT_CLASSIFIER_ENABLED = "local_textclassifier_enabled"; + @VisibleForTesting + static final String LOCAL_TEXT_CLASSIFIER_ENABLED = "local_textclassifier_enabled"; /** * Enable smart selection without a visible UI changes. */ @@ -82,7 +76,8 @@ public final class TextClassificationConstants { /** * Max length of text that suggestSelection can accept. */ - private static final String SUGGEST_SELECTION_MAX_RANGE_LENGTH = + @VisibleForTesting + static final String SUGGEST_SELECTION_MAX_RANGE_LENGTH = "suggest_selection_max_range_length"; /** * Max length of text that classifyText can accept. @@ -101,7 +96,8 @@ public final class TextClassificationConstants { * A colon(:) separated string that specifies the default entities types for * generateLinks when hint is not given. */ - private static final String ENTITY_LIST_DEFAULT = "entity_list_default"; + @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. @@ -127,7 +123,8 @@ public final class TextClassificationConstants { /** * Threshold to accept a suggested language from LangID model. */ - private static final String LANG_ID_THRESHOLD_OVERRIDE = "lang_id_threshold_override"; + @VisibleForTesting + static final String LANG_ID_THRESHOLD_OVERRIDE = "lang_id_threshold_override"; /** * Whether to enable {@link android.view.textclassifier.TemplateIntentFactory}. */ @@ -156,7 +153,8 @@ public final class TextClassificationConstants { * Reject all text less than minimumTextSize with penalizeRatio=0 * @see {@code TextClassifierImpl#detectLanguages(String, int, int)} for reference. */ - private static final String LANG_ID_CONTEXT_SETTINGS = "lang_id_context_settings"; + @VisibleForTesting + static final String LANG_ID_CONTEXT_SETTINGS = "lang_id_context_settings"; /** * The TextClassifierService which would like to use. Example of setting the package: @@ -164,9 +162,9 @@ public final class TextClassificationConstants { * adb shell cmd device_config put textclassifier textclassifier_service_package_override \ * com.android.textclassifier * </pre> - * */ - private static final String TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE = + @VisibleForTesting + static final String TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE = "textclassifier_service_package_override"; private static final String DEFAULT_TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE = null; @@ -213,142 +211,124 @@ public final class TextClassificationConstants { 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 final ConfigParser mConfigParser; - - public TextClassificationConstants(Supplier<String> legacySettingsSupplier) { - mConfigParser = new ConfigParser(legacySettingsSupplier); - } - - public String getTextClassifierServiceName() { - return mConfigParser.getString( + @Nullable + public String getTextClassifierServicePackageOverride() { + return DeviceConfig.getString(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE, DEFAULT_TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE); } public boolean isLocalTextClassifierEnabled() { - return mConfigParser.getBoolean( - LOCAL_TEXT_CLASSIFIER_ENABLED, - LOCAL_TEXT_CLASSIFIER_ENABLED_DEFAULT); + return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, + LOCAL_TEXT_CLASSIFIER_ENABLED, LOCAL_TEXT_CLASSIFIER_ENABLED_DEFAULT); } public boolean isSystemTextClassifierEnabled() { - return mConfigParser.getBoolean( + return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, SYSTEM_TEXT_CLASSIFIER_ENABLED, SYSTEM_TEXT_CLASSIFIER_ENABLED_DEFAULT); } public boolean isModelDarkLaunchEnabled() { - return mConfigParser.getBoolean( - MODEL_DARK_LAUNCH_ENABLED, - MODEL_DARK_LAUNCH_ENABLED_DEFAULT); + return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, + MODEL_DARK_LAUNCH_ENABLED, MODEL_DARK_LAUNCH_ENABLED_DEFAULT); } public boolean isSmartSelectionEnabled() { - return mConfigParser.getBoolean( - SMART_SELECTION_ENABLED, - SMART_SELECTION_ENABLED_DEFAULT); + return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, + SMART_SELECTION_ENABLED, SMART_SELECTION_ENABLED_DEFAULT); } public boolean isSmartTextShareEnabled() { - return mConfigParser.getBoolean( - SMART_TEXT_SHARE_ENABLED, - SMART_TEXT_SHARE_ENABLED_DEFAULT); + return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, + SMART_TEXT_SHARE_ENABLED, SMART_TEXT_SHARE_ENABLED_DEFAULT); } public boolean isSmartLinkifyEnabled() { - return mConfigParser.getBoolean( - SMART_LINKIFY_ENABLED, + return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, SMART_LINKIFY_ENABLED, SMART_LINKIFY_ENABLED_DEFAULT); } public boolean isSmartSelectionAnimationEnabled() { - return mConfigParser.getBoolean( - SMART_SELECT_ANIMATION_ENABLED, - SMART_SELECT_ANIMATION_ENABLED_DEFAULT); + return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, + SMART_SELECT_ANIMATION_ENABLED, SMART_SELECT_ANIMATION_ENABLED_DEFAULT); } public int getSuggestSelectionMaxRangeLength() { - return mConfigParser.getInt( - SUGGEST_SELECTION_MAX_RANGE_LENGTH, - SUGGEST_SELECTION_MAX_RANGE_LENGTH_DEFAULT); + return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, + SUGGEST_SELECTION_MAX_RANGE_LENGTH, SUGGEST_SELECTION_MAX_RANGE_LENGTH_DEFAULT); } public int getClassifyTextMaxRangeLength() { - return mConfigParser.getInt( - CLASSIFY_TEXT_MAX_RANGE_LENGTH, - CLASSIFY_TEXT_MAX_RANGE_LENGTH_DEFAULT); + return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, + CLASSIFY_TEXT_MAX_RANGE_LENGTH, CLASSIFY_TEXT_MAX_RANGE_LENGTH_DEFAULT); } public int getGenerateLinksMaxTextLength() { - return mConfigParser.getInt( - GENERATE_LINKS_MAX_TEXT_LENGTH, - GENERATE_LINKS_MAX_TEXT_LENGTH_DEFAULT); + return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, + GENERATE_LINKS_MAX_TEXT_LENGTH, GENERATE_LINKS_MAX_TEXT_LENGTH_DEFAULT); } public int getGenerateLinksLogSampleRate() { - return mConfigParser.getInt( - GENERATE_LINKS_LOG_SAMPLE_RATE, - GENERATE_LINKS_LOG_SAMPLE_RATE_DEFAULT); + return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, + GENERATE_LINKS_LOG_SAMPLE_RATE, GENERATE_LINKS_LOG_SAMPLE_RATE_DEFAULT); } public List<String> getEntityListDefault() { - return mConfigParser.getStringList( - ENTITY_LIST_DEFAULT, - ENTITY_LIST_DEFAULT_VALUE); + return getDeviceConfigStringList(ENTITY_LIST_DEFAULT, ENTITY_LIST_DEFAULT_VALUE); } public List<String> getEntityListNotEditable() { - return mConfigParser.getStringList( - ENTITY_LIST_NOT_EDITABLE, - ENTITY_LIST_DEFAULT_VALUE); + return getDeviceConfigStringList(ENTITY_LIST_NOT_EDITABLE, ENTITY_LIST_DEFAULT_VALUE); } public List<String> getEntityListEditable() { - return mConfigParser.getStringList( - ENTITY_LIST_EDITABLE, - ENTITY_LIST_DEFAULT_VALUE); + return getDeviceConfigStringList(ENTITY_LIST_EDITABLE, ENTITY_LIST_DEFAULT_VALUE); } public List<String> getInAppConversationActionTypes() { - return mConfigParser.getStringList( + return getDeviceConfigStringList( IN_APP_CONVERSATION_ACTION_TYPES_DEFAULT, CONVERSATION_ACTIONS_TYPES_DEFAULT_VALUES); } public List<String> getNotificationConversationActionTypes() { - return mConfigParser.getStringList( + return getDeviceConfigStringList( NOTIFICATION_CONVERSATION_ACTION_TYPES_DEFAULT, CONVERSATION_ACTIONS_TYPES_DEFAULT_VALUES); } public float getLangIdThresholdOverride() { - return mConfigParser.getFloat( + return DeviceConfig.getFloat( + DeviceConfig.NAMESPACE_TEXTCLASSIFIER, LANG_ID_THRESHOLD_OVERRIDE, LANG_ID_THRESHOLD_OVERRIDE_DEFAULT); } public boolean isTemplateIntentFactoryEnabled() { - return mConfigParser.getBoolean( + return DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_TEXTCLASSIFIER, TEMPLATE_INTENT_FACTORY_ENABLED, TEMPLATE_INTENT_FACTORY_ENABLED_DEFAULT); } public boolean isTranslateInClassificationEnabled() { - return mConfigParser.getBoolean( + return DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_TEXTCLASSIFIER, TRANSLATE_IN_CLASSIFICATION_ENABLED, TRANSLATE_IN_CLASSIFICATION_ENABLED_DEFAULT); } public boolean isDetectLanguagesFromTextEnabled() { - return mConfigParser.getBoolean( + return DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_TEXTCLASSIFIER, DETECT_LANGUAGES_FROM_TEXT_ENABLED, DETECT_LANGUAGES_FROM_TEXT_ENABLED_DEFAULT); } public float[] getLangIdContextSettings() { - return mConfigParser.getFloatArray( - LANG_ID_CONTEXT_SETTINGS, - LANG_ID_CONTEXT_SETTINGS_DEFAULT); + return getDeviceConfigFloatArray( + LANG_ID_CONTEXT_SETTINGS, LANG_ID_CONTEXT_SETTINGS_DEFAULT); } void dump(IndentingPrintWriter pw) { @@ -356,7 +336,7 @@ public final class TextClassificationConstants { pw.increaseIndent(); pw.printPair("classify_text_max_range_length", getClassifyTextMaxRangeLength()) .println(); - pw.printPair("detect_language_from_text_enabled", isDetectLanguagesFromTextEnabled()) + pw.printPair("detect_languages_from_text_enabled", isDetectLanguagesFromTextEnabled()) .println(); pw.printPair("entity_list_default", getEntityListDefault()) .println(); @@ -396,8 +376,47 @@ public final class TextClassificationConstants { .println(); pw.printPair("translate_in_classification_enabled", isTranslateInClassificationEnabled()) .println(); - pw.printPair("textclassifier_service_package_override", getTextClassifierServiceName()) - .println(); + pw.printPair("textclassifier_service_package_override", + getTextClassifierServicePackageOverride()).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 3e0f7eef33a7..7c25bf0f277d 100644 --- a/core/java/android/view/textclassifier/TextClassificationManager.java +++ b/core/java/android/view/textclassifier/TextClassificationManager.java @@ -22,11 +22,9 @@ import android.annotation.SystemService; import android.annotation.UnsupportedAppUsage; import android.app.ActivityThread; import android.content.Context; -import android.database.ContentObserver; import android.os.ServiceManager; import android.provider.DeviceConfig; import android.provider.DeviceConfig.Properties; -import android.provider.Settings; import android.service.textclassifier.TextClassifierService; import android.view.textclassifier.TextClassifier.TextClassifierType; @@ -36,6 +34,7 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import java.lang.ref.WeakReference; +import java.util.Set; /** * Interface to the text classification service. @@ -46,7 +45,7 @@ public final class TextClassificationManager { private static final String LOG_TAG = "TextClassificationManager"; private static final TextClassificationConstants sDefaultSettings = - new TextClassificationConstants(() -> null); + new TextClassificationConstants(); private final Object mLock = new Object(); private final TextClassificationSessionFactory mDefaultSessionFactory = @@ -132,10 +131,7 @@ public final class TextClassificationManager { private TextClassificationConstants getSettings() { synchronized (mLock) { if (mSettings == null) { - mSettings = new TextClassificationConstants( - () -> Settings.Global.getString( - getApplicationContext().getContentResolver(), - Settings.Global.TEXT_CLASSIFIER_CONSTANTS)); + mSettings = new TextClassificationConstants(); } return mSettings; } @@ -201,11 +197,7 @@ public final class TextClassificationManager { try { // Note that fields could be null if the constructor threw. if (mSettingsObserver != null) { - getApplicationContext().getContentResolver() - .unregisterContentObserver(mSettingsObserver); - if (ConfigParser.ENABLE_DEVICE_CONFIG) { - DeviceConfig.removeOnPropertiesChangedListener(mSettingsObserver); - } + DeviceConfig.removeOnPropertiesChangedListener(mSettingsObserver); } } finally { super.finalize(); @@ -250,7 +242,7 @@ public final class TextClassificationManager { private boolean isSystemTextClassifierEnabled() { return getSettings().isSystemTextClassifierEnabled() - && TextClassifierService.getServiceComponentName(mContext, getSettings()) != null; + && TextClassifierService.getServiceComponentName(mContext) != null; } /** @hide */ @@ -262,6 +254,12 @@ public final class TextClassificationManager { private void invalidate() { synchronized (mLock) { mSettings = null; + invalidateTextClassifiers(); + } + } + + private void invalidateTextClassifiers() { + synchronized (mLock) { mLocalTextClassifier = null; mSystemTextClassifier = null; } @@ -293,40 +291,29 @@ public final class TextClassificationManager { } } - private static final class SettingsObserver extends ContentObserver - implements DeviceConfig.OnPropertiesChangedListener { + private static final class SettingsObserver implements + DeviceConfig.OnPropertiesChangedListener { private final WeakReference<TextClassificationManager> mTcm; SettingsObserver(TextClassificationManager tcm) { - super(null); mTcm = new WeakReference<>(tcm); - tcm.getApplicationContext().getContentResolver().registerContentObserver( - Settings.Global.getUriFor(Settings.Global.TEXT_CLASSIFIER_CONSTANTS), - false /* notifyForDescendants */, + DeviceConfig.addOnPropertiesChangedListener( + DeviceConfig.NAMESPACE_TEXTCLASSIFIER, + ActivityThread.currentApplication().getMainExecutor(), this); - if (ConfigParser.ENABLE_DEVICE_CONFIG) { - DeviceConfig.addOnPropertiesChangedListener( - DeviceConfig.NAMESPACE_TEXTCLASSIFIER, - ActivityThread.currentApplication().getMainExecutor(), - this); - } - } - - @Override - public void onChange(boolean selfChange) { - invalidateSettings(); } @Override public void onPropertiesChanged(Properties properties) { - invalidateSettings(); - } - - private void invalidateSettings() { final TextClassificationManager tcm = mTcm.get(); if (tcm != null) { - tcm.invalidate(); + 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/tests/coretests/src/android/view/textclassifier/ConfigParserTest.java b/core/tests/coretests/src/android/view/textclassifier/ConfigParserTest.java deleted file mode 100644 index d54ce51c3c37..000000000000 --- a/core/tests/coretests/src/android/view/textclassifier/ConfigParserTest.java +++ /dev/null @@ -1,138 +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 android.provider.DeviceConfig; -import android.support.test.uiautomator.UiDevice; - -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 java.io.IOException; -import java.util.function.Supplier; - -@SmallTest -@RunWith(AndroidJUnit4.class) -public class ConfigParserTest { - private static final Supplier<String> SETTINGS = - () -> "int=42,float=12.3,boolean=true,string=abc"; - private static final String CLEAR_DEVICE_CONFIG_KEY_CMD = - "device_config delete " + DeviceConfig.NAMESPACE_TEXTCLASSIFIER; - private static final String[] DEVICE_CONFIG_KEYS = new String[]{ - "boolean", - "string", - "int", - "float" - }; - - private ConfigParser mConfigParser; - - @Before - public void setup() throws IOException { - mConfigParser = new ConfigParser(SETTINGS); - clearDeviceConfig(); - } - - @After - public void tearDown() throws IOException { - clearDeviceConfig(); - } - - @Test - public void getBoolean_deviceConfig() { - DeviceConfig.setProperty( - DeviceConfig.NAMESPACE_TEXTCLASSIFIER, - "boolean", - "false", - false); - boolean value = mConfigParser.getBoolean("boolean", true); - assertThat(value).isFalse(); - } - - @Test - public void getBoolean_settings() { - boolean value = mConfigParser.getBoolean( - "boolean", - false); - assertThat(value).isTrue(); - } - - @Test - public void getInt_deviceConfig() { - DeviceConfig.setProperty( - DeviceConfig.NAMESPACE_TEXTCLASSIFIER, - "int", - "1", - false); - int value = mConfigParser.getInt("int", 0); - assertThat(value).isEqualTo(1); - } - - @Test - public void getInt_settings() { - int value = mConfigParser.getInt("int", 0); - assertThat(value).isEqualTo(42); - } - - @Test - public void getFloat_deviceConfig() { - DeviceConfig.setProperty( - DeviceConfig.NAMESPACE_TEXTCLASSIFIER, - "float", - "3.14", - false); - float value = mConfigParser.getFloat("float", 0); - assertThat(value).isWithin(0.0001f).of(3.14f); - } - - @Test - public void getFloat_settings() { - float value = mConfigParser.getFloat("float", 0); - assertThat(value).isWithin(0.0001f).of(12.3f); - } - - @Test - public void getString_deviceConfig() { - DeviceConfig.setProperty( - DeviceConfig.NAMESPACE_TEXTCLASSIFIER, - "string", - "hello", - false); - String value = mConfigParser.getString("string", ""); - assertThat(value).isEqualTo("hello"); - } - - @Test - public void getString_settings() { - String value = mConfigParser.getString("string", ""); - assertThat(value).isEqualTo("abc"); - } - - private static void clearDeviceConfig() throws IOException { - UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); - for (String key : DEVICE_CONFIG_KEYS) { - uiDevice.executeShellCommand(CLEAR_DEVICE_CONFIG_KEY_CMD + " " + key); - } - } -} diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java index 64fb14190ce2..82fa73f76207 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java @@ -16,230 +16,141 @@ package android.view.textclassifier; +import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; +import android.provider.DeviceConfig; + import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import org.junit.Assert; +import com.google.common.primitives.Floats; + import org.junit.Test; import org.junit.runner.RunWith; @SmallTest @RunWith(AndroidJUnit4.class) public class TextClassificationConstantsTest { - private static final float EPSILON = 0.0001f; @Test - public void testLoadFromString() { - final String s = "local_textclassifier_enabled=true," - + "system_textclassifier_enabled=true," - + "model_dark_launch_enabled=true," - + "smart_selection_enabled=true," - + "smart_text_share_enabled=true," - + "smart_linkify_enabled=true," - + "smart_select_animation_enabled=true," - + "suggest_selection_max_range_length=10," - + "classify_text_max_range_length=11," - + "generate_links_max_text_length=12," - + "generate_links_log_sample_rate=13," - + "entity_list_default=phone," - + "entity_list_not_editable=address:flight," - + "entity_list_editable=date:datetime," - + "in_app_conversation_action_types_default=text_reply," - + "notification_conversation_action_types_default=send_email:call_phone," - + "lang_id_threshold_override=0.3," - + "lang_id_context_settings=10:1:0.5," - + "detect_language_from_text_enabled=true," - + "template_intent_factory_enabled=true," - + "translate_in_classification_enabled=true"; - final TextClassificationConstants constants = new TextClassificationConstants(() -> s); - - assertWithMessage("local_textclassifier_enabled") - .that(constants.isLocalTextClassifierEnabled()).isTrue(); - assertWithMessage("system_textclassifier_enabled") - .that(constants.isSystemTextClassifierEnabled()).isTrue(); - assertWithMessage("model_dark_launch_enabled") - .that(constants.isModelDarkLaunchEnabled()).isTrue(); - assertWithMessage("smart_selection_enabled") - .that(constants.isSmartSelectionEnabled()).isTrue(); - assertWithMessage("smart_text_share_enabled") - .that(constants.isSmartTextShareEnabled()).isTrue(); - assertWithMessage("smart_linkify_enabled") - .that(constants.isSmartLinkifyEnabled()).isTrue(); - assertWithMessage("smart_select_animation_enabled") - .that(constants.isSmartSelectionAnimationEnabled()).isTrue(); - assertWithMessage("suggest_selection_max_range_length") - .that(constants.getSuggestSelectionMaxRangeLength()).isEqualTo(10); - assertWithMessage("classify_text_max_range_length") - .that(constants.getClassifyTextMaxRangeLength()).isEqualTo(11); - assertWithMessage("generate_links_max_text_length") - .that(constants.getGenerateLinksMaxTextLength()).isEqualTo(12); - assertWithMessage("generate_links_log_sample_rate") - .that(constants.getGenerateLinksLogSampleRate()).isEqualTo(13); - assertWithMessage("entity_list_default") - .that(constants.getEntityListDefault()) - .containsExactly("phone"); - assertWithMessage("entity_list_not_editable") - .that(constants.getEntityListNotEditable()) - .containsExactly("address", "flight"); - assertWithMessage("entity_list_editable") - .that(constants.getEntityListEditable()) - .containsExactly("date", "datetime"); - assertWithMessage("in_app_conversation_action_types_default") - .that(constants.getInAppConversationActionTypes()) - .containsExactly("text_reply"); - assertWithMessage("notification_conversation_action_types_default") - .that(constants.getNotificationConversationActionTypes()) - .containsExactly("send_email", "call_phone"); - assertWithMessage("lang_id_threshold_override") - .that(constants.getLangIdThresholdOverride()).isWithin(EPSILON).of(0.3f); - Assert.assertArrayEquals("lang_id_context_settings", - constants.getLangIdContextSettings(), new float[]{10, 1, 0.5f}, EPSILON); - assertWithMessage("detect_language_from_text_enabled") - .that(constants.isLocalTextClassifierEnabled()).isTrue(); - assertWithMessage("template_intent_factory_enabled") - .that(constants.isLocalTextClassifierEnabled()).isTrue(); - assertWithMessage("translate_in_classification_enabled") - .that(constants.isLocalTextClassifierEnabled()).isTrue(); + public void testLoadFromDeviceConfig_booleanValue() throws Exception { + // Saves config original value. + final String originalValue = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, + TextClassificationConstants.LOCAL_TEXT_CLASSIFIER_ENABLED); + + final TextClassificationConstants constants = new TextClassificationConstants(); + try { + // Sets and checks different value. + setDeviceConfig(TextClassificationConstants.LOCAL_TEXT_CLASSIFIER_ENABLED, "false"); + assertWithMessage(TextClassificationConstants.LOCAL_TEXT_CLASSIFIER_ENABLED) + .that(constants.isLocalTextClassifierEnabled()).isFalse(); + } finally { + // Restores config original value. + setDeviceConfig(TextClassificationConstants.LOCAL_TEXT_CLASSIFIER_ENABLED, + originalValue); + } + } + + @Test + public void testLoadFromDeviceConfig_IntValue() throws Exception { + // Saves config original value. + final String originalValue = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, + TextClassificationConstants.SUGGEST_SELECTION_MAX_RANGE_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); + } finally { + // Restores config original value. + setDeviceConfig(TextClassificationConstants.SUGGEST_SELECTION_MAX_RANGE_LENGTH, + originalValue); + } + } + + @Test + public void testLoadFromDeviceConfig_StringValue() throws Exception { + // Saves config original value. + final String originalValue = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, + TextClassificationConstants.TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE); + + final TextClassificationConstants constants = new TextClassificationConstants(); + try { + // Sets and checks different value. + final String testTextClassifier = "com.example.textclassifier"; + setDeviceConfig(TextClassificationConstants.TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE, + testTextClassifier); + assertWithMessage(TextClassificationConstants.TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE) + .that(constants.getTextClassifierServicePackageOverride()).isEqualTo( + testTextClassifier); + } finally { + // Restores config original value. + setDeviceConfig(TextClassificationConstants.TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE, + originalValue); + } + } + + @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 testLoadFromString_differentValues() { - final String testTextClassifier = "com.example.textclassifier"; - final String s = "local_textclassifier_enabled=false," - + "system_textclassifier_enabled=false," - + "model_dark_launch_enabled=false," - + "smart_selection_enabled=false," - + "smart_text_share_enabled=false," - + "smart_linkify_enabled=false," - + "smart_select_animation_enabled=false," - + "suggest_selection_max_range_length=8," - + "classify_text_max_range_length=7," - + "generate_links_max_text_length=6," - + "generate_links_log_sample_rate=5," - + "entity_list_default=email:url," - + "entity_list_not_editable=date," - + "entity_list_editable=flight," - + "in_app_conversation_action_types_default=view_map:track_flight," - + "notification_conversation_action_types_default=share_location," - + "lang_id_threshold_override=2," - + "lang_id_context_settings=30:0.5:0.3," - + "detect_language_from_text_enabled=false," - + "template_intent_factory_enabled=false," - + "textclassifier_service_package_override=" + testTextClassifier + "," - + "translate_in_classification_enabled=false"; - final TextClassificationConstants constants = new TextClassificationConstants(() -> s); - - assertWithMessage("local_textclassifier_enabled") - .that(constants.isLocalTextClassifierEnabled()).isFalse(); - assertWithMessage("system_textclassifier_enabled") - .that(constants.isSystemTextClassifierEnabled()).isFalse(); - assertWithMessage("model_dark_launch_enabled") - .that(constants.isModelDarkLaunchEnabled()).isFalse(); - assertWithMessage("smart_selection_enabled") - .that(constants.isSmartSelectionEnabled()).isFalse(); - assertWithMessage("smart_text_share_enabled") - .that(constants.isSmartTextShareEnabled()).isFalse(); - assertWithMessage("smart_linkify_enabled") - .that(constants.isSmartLinkifyEnabled()).isFalse(); - assertWithMessage("smart_select_animation_enabled") - .that(constants.isSmartSelectionAnimationEnabled()).isFalse(); - assertWithMessage("suggest_selection_max_range_length") - .that(constants.getSuggestSelectionMaxRangeLength()).isEqualTo(8); - assertWithMessage("classify_text_max_range_length") - .that(constants.getClassifyTextMaxRangeLength()).isEqualTo(7); - assertWithMessage("generate_links_max_text_length") - .that(constants.getGenerateLinksMaxTextLength()).isEqualTo(6); - assertWithMessage("generate_links_log_sample_rate") - .that(constants.getGenerateLinksLogSampleRate()).isEqualTo(5); - assertWithMessage("entity_list_default") - .that(constants.getEntityListDefault()) - .containsExactly("email", "url"); - assertWithMessage("entity_list_not_editable") - .that(constants.getEntityListNotEditable()) - .containsExactly("date"); - assertWithMessage("entity_list_editable") - .that(constants.getEntityListEditable()) - .containsExactly("flight"); - assertWithMessage("in_app_conversation_action_types_default") - .that(constants.getInAppConversationActionTypes()) - .containsExactly("view_map", "track_flight"); - assertWithMessage("notification_conversation_action_types_default") - .that(constants.getNotificationConversationActionTypes()) - .containsExactly("share_location"); - assertWithMessage("lang_id_threshold_override") - .that(constants.getLangIdThresholdOverride()).isWithin(EPSILON).of(2f); - Assert.assertArrayEquals("lang_id_context_settings", - constants.getLangIdContextSettings(), new float[]{30, 0.5f, 0.3f}, EPSILON); - assertWithMessage("detect_language_from_text_enabled") - .that(constants.isLocalTextClassifierEnabled()).isFalse(); - assertWithMessage("template_intent_factory_enabled") - .that(constants.isLocalTextClassifierEnabled()).isFalse(); - assertWithMessage("translate_in_classification_enabled") - .that(constants.isLocalTextClassifierEnabled()).isFalse(); - assertWithMessage("textclassifier_service_package_override") - .that(constants.getTextClassifierServiceName()).isEqualTo( - testTextClassifier); + 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 testLoadFromString_defaultValues() { - final TextClassificationConstants constants = new TextClassificationConstants(() -> ""); - - assertWithMessage("local_textclassifier_enabled") - .that(constants.isLocalTextClassifierEnabled()).isTrue(); - assertWithMessage("system_textclassifier_enabled") - .that(constants.isSystemTextClassifierEnabled()).isTrue(); - assertWithMessage("model_dark_launch_enabled") - .that(constants.isModelDarkLaunchEnabled()).isFalse(); - assertWithMessage("smart_selection_enabled") - .that(constants.isSmartSelectionEnabled()).isTrue(); - assertWithMessage("smart_text_share_enabled") - .that(constants.isSmartTextShareEnabled()).isTrue(); - assertWithMessage("smart_linkify_enabled") - .that(constants.isSmartLinkifyEnabled()).isTrue(); - assertWithMessage("smart_select_animation_enabled") - .that(constants.isSmartSelectionAnimationEnabled()).isTrue(); - assertWithMessage("suggest_selection_max_range_length") - .that(constants.getSuggestSelectionMaxRangeLength()).isEqualTo(10 * 1000); - assertWithMessage("classify_text_max_range_length") - .that(constants.getClassifyTextMaxRangeLength()).isEqualTo(10 * 1000); - assertWithMessage("generate_links_max_text_length") - .that(constants.getGenerateLinksMaxTextLength()).isEqualTo(100 * 1000); - assertWithMessage("generate_links_log_sample_rate") - .that(constants.getGenerateLinksLogSampleRate()).isEqualTo(100); - assertWithMessage("entity_list_default") - .that(constants.getEntityListDefault()) - .containsExactly("address", "email", "url", "phone", "date", "datetime", "flight"); - assertWithMessage("entity_list_not_editable") - .that(constants.getEntityListNotEditable()) - .containsExactly("address", "email", "url", "phone", "date", "datetime", "flight"); - assertWithMessage("entity_list_editable") - .that(constants.getEntityListEditable()) - .containsExactly("address", "email", "url", "phone", "date", "datetime", "flight"); - assertWithMessage("in_app_conversation_action_types_default") - .that(constants.getInAppConversationActionTypes()) - .containsExactly("text_reply", "create_reminder", "call_phone", "open_url", - "send_email", "send_sms", "track_flight", "view_calendar", "view_map", - "add_contact", "copy"); - assertWithMessage("notification_conversation_action_types_default") - .that(constants.getNotificationConversationActionTypes()) - .containsExactly("text_reply", "create_reminder", "call_phone", "open_url", - "send_email", "send_sms", "track_flight", "view_calendar", "view_map", - "add_contact", "copy"); - assertWithMessage("lang_id_threshold_override") - .that(constants.getLangIdThresholdOverride()).isWithin(EPSILON).of(-1f); - Assert.assertArrayEquals("lang_id_context_settings", - constants.getLangIdContextSettings(), new float[]{20, 1, 0.4f}, EPSILON); - assertWithMessage("detect_language_from_text_enabled") - .that(constants.isLocalTextClassifierEnabled()).isTrue(); - assertWithMessage("template_intent_factory_enabled") - .that(constants.isLocalTextClassifierEnabled()).isTrue(); - assertWithMessage("translate_in_classification_enabled") - .that(constants.isLocalTextClassifierEnabled()).isTrue(); - assertWithMessage("textclassifier_service_package_override") - .that(constants.getTextClassifierServiceName()).isNull(); + 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 b3f2bbe92c07..8faf790549d5 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java @@ -78,7 +78,7 @@ public class TextClassificationManagerTest { TextClassifier fallback = TextClassifier.NO_OP; TextClassifier classifier = new TextClassifierImpl( - fakeContext, new TextClassificationConstants(() -> null), fallback); + fakeContext, new TextClassificationConstants(), fallback); String text = "Contact me at +12122537077"; String classifiedText = "+12122537077"; diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java index deb0f182156e..2304ba6f6da4 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java @@ -69,7 +69,7 @@ public class TextClassifierTest { public String mTextClassifierType; private static final TextClassificationConstants TC_CONSTANTS = - new TextClassificationConstants(() -> ""); + new TextClassificationConstants(); private static final LocaleList LOCALES = LocaleList.forLanguageTags("en-US"); private static final String NO_TYPE = null; diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java index 7d905bae4491..6a986b9da515 100644 --- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java +++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java @@ -24,15 +24,12 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.PackageManager; -import android.database.ContentObserver; -import android.net.Uri; import android.os.Binder; import android.os.IBinder; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.provider.DeviceConfig; -import android.provider.Settings; import android.service.textclassifier.ITextClassifierCallback; import android.service.textclassifier.ITextClassifierService; import android.service.textclassifier.TextClassifierService; @@ -41,7 +38,6 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.Slog; import android.util.SparseArray; -import android.view.textclassifier.ConfigParser; import android.view.textclassifier.ConversationActions; import android.view.textclassifier.SelectionEvent; import android.view.textclassifier.TextClassification; @@ -143,7 +139,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi private TextClassificationManagerService(Context context) { mContext = Preconditions.checkNotNull(context); mLock = new Object(); - mSettingsListener = new TextClassifierSettingsListener(mContext, this); + mSettingsListener = new TextClassifierSettingsListener(mContext); } private void startListenSettings() { @@ -171,10 +167,8 @@ public final class TextClassificationManagerService extends ITextClassifierServi Slog.d(LOG_TAG, "Unable to bind TextClassifierService at suggestSelection."); callback.onFailure(); } else if (userState.isBoundLocked()) { - if (!userState.isRequestAcceptedLocked(Binder.getCallingUid())) { - Slog.d(LOG_TAG, - "Only allow to see own content for non-default service at " - + "suggestSelection."); + if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(), + "suggestSelection")) { return; } userState.mService.onSuggestSelection(sessionId, request, callback); @@ -203,10 +197,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi Slog.d(LOG_TAG, "Unable to bind TextClassifierService at classifyText."); callback.onFailure(); } else if (userState.isBoundLocked()) { - if (!userState.isRequestAcceptedLocked(Binder.getCallingUid())) { - Slog.d(LOG_TAG, - "Only allow to see own content for non-default service at " - + "classifyText."); + if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(), "classifyText")) { return; } userState.mService.onClassifyText(sessionId, request, callback); @@ -235,10 +226,8 @@ public final class TextClassificationManagerService extends ITextClassifierServi Slog.d(LOG_TAG, "Unable to bind TextClassifierService at generateLinks."); callback.onFailure(); } else if (userState.isBoundLocked()) { - if (!userState.isRequestAcceptedLocked(Binder.getCallingUid())) { - Slog.d(LOG_TAG, - "Only allow to see own content for non-default service at " - + "generateLinks."); + if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(), + "generateLinks")) { return; } userState.mService.onGenerateLinks(sessionId, request, callback); @@ -262,10 +251,8 @@ public final class TextClassificationManagerService extends ITextClassifierServi synchronized (mLock) { UserState userState = getUserStateLocked(userId); if (userState.isBoundLocked()) { - if (!userState.isRequestAcceptedLocked(Binder.getCallingUid())) { - Slog.d(LOG_TAG, - "Only allow to see own content for non-default service at " - + "selectionEvent."); + if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(), + "selectionEvent")) { return; } userState.mService.onSelectionEvent(sessionId, event); @@ -293,10 +280,8 @@ public final class TextClassificationManagerService extends ITextClassifierServi synchronized (mLock) { UserState userState = getUserStateLocked(userId); if (userState.isBoundLocked()) { - if (!userState.isRequestAcceptedLocked(Binder.getCallingUid())) { - Slog.d(LOG_TAG, - "Only allow to see own content for non-default service at " - + "textClassifierEvent."); + if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(), + "textClassifierEvent")) { return; } userState.mService.onTextClassifierEvent(sessionId, event); @@ -325,10 +310,8 @@ public final class TextClassificationManagerService extends ITextClassifierServi Slog.d(LOG_TAG, "Unable to bind TextClassifierService at detectLanguage."); callback.onFailure(); } else if (userState.isBoundLocked()) { - if (!userState.isRequestAcceptedLocked(Binder.getCallingUid())) { - Slog.d(LOG_TAG, - "Only allow to see own content for non-default service at " - + "detectLanguage."); + if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(), + "detectLanguage")) { return; } userState.mService.onDetectLanguage(sessionId, request, callback); @@ -358,10 +341,8 @@ public final class TextClassificationManagerService extends ITextClassifierServi "Unable to bind TextClassifierService at suggestConversationActions."); callback.onFailure(); } else if (userState.isBoundLocked()) { - if (!userState.isRequestAcceptedLocked(Binder.getCallingUid())) { - Slog.d(LOG_TAG, - "Only allow to see own content for non-default service at " - + "suggestConversationActions."); + if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(), + "suggestConversationActions")) { return; } userState.mService.onSuggestConversationActions(sessionId, request, callback); @@ -387,10 +368,8 @@ public final class TextClassificationManagerService extends ITextClassifierServi synchronized (mLock) { UserState userState = getUserStateLocked(userId); if (userState.isBoundLocked()) { - if (!userState.isRequestAcceptedLocked(Binder.getCallingUid())) { - Slog.d(LOG_TAG, - "Only allow to see own content for non-default service at " - + "createTextClassificationSession."); + if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(), + "createTextClassificationSession")) { return; } userState.mService.onCreateTextClassificationSession( @@ -422,10 +401,8 @@ public final class TextClassificationManagerService extends ITextClassifierServi UserState userState = getUserStateLocked(userId); if (userState.isBoundLocked()) { - if (!userState.isRequestAcceptedLocked(Binder.getCallingUid())) { - Slog.d(LOG_TAG, - "Only allow to see own content for non-default service at " - + "destroyTextClassificationSession."); + if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(), + "destroyTextClassificationSession")) { return; } userState.mService.onDestroyTextClassificationSession(sessionId); @@ -488,6 +465,29 @@ public final class TextClassificationManagerService extends ITextClassifierServi } } + private void unbindServiceIfNecessary() { + final ComponentName serviceComponentName = + TextClassifierService.getServiceComponentName(mContext); + if (serviceComponentName == null) { + // It should not occur if we had defined default service names in config.xml + Slog.w(LOG_TAG, "No default configured system TextClassifierService."); + return; + } + synchronized (mLock) { + final int size = mUserStates.size(); + for (int i = 0; i < size; i++) { + UserState userState = mUserStates.valueAt(i); + // Only unbind for a new service + if (userState.isCurrentlyBoundToComponentLocked(serviceComponentName)) { + return; + } + if (userState.isBoundLocked()) { + userState.unbindLocked(); + } + } + } + } + private static final class PendingRequest implements IBinder.DeathRecipient { private final int mUid; @@ -582,48 +582,6 @@ public final class TextClassificationManagerService extends ITextClassifierServi } } - private TextClassificationConstants getTextClassifierSettings(Context context) { - synchronized (mLock) { - if (mSettings == null) { - mSettings = new TextClassificationConstants( - () -> Settings.Global.getString( - context.getContentResolver(), - Settings.Global.TEXT_CLASSIFIER_CONSTANTS)); - } - return mSettings; - } - } - - private void invalidateSettings() { - synchronized (mLock) { - mSettings = null; - } - } - - private void unbindServiceIfNeeded() { - final ComponentName serviceComponentName = - TextClassifierService.getServiceComponentName(mContext, - getTextClassifierSettings(mContext)); - if (serviceComponentName == null) { - // It should not occur if we had defined default service name in config - Slog.w(LOG_TAG, "No default configured system TextClassifierService."); - return; - } - synchronized (mLock) { - final int size = mUserStates.size(); - for (int i = 0; i < size; i++) { - UserState userState = mUserStates.valueAt(i); - // Only unbind for a new service - if (userState.isServiceCurrentBoundLocked(serviceComponentName)) { - return; - } - if (userState.isBoundLocked()) { - userState.unbindLocked(); - } - } - } - } - private final class UserState { @UserIdInt final int mUserId; @GuardedBy("mLock") @@ -635,9 +593,9 @@ public final class TextClassificationManagerService extends ITextClassifierServi @GuardedBy("mLock") boolean mBinding; @GuardedBy("mLock") - ComponentName mBoundServiceComponent = null; + ComponentName mBoundComponentName = null; @GuardedBy("mLock") - boolean mIsBoundToDefaultService; + boolean mBoundToDefaultTrustService; @GuardedBy("mLock") int mBoundServiceUid; @@ -660,10 +618,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi PendingRequest request; while ((request = mPendingRequests.poll()) != null) { if (isBoundLocked()) { - if (!isRequestAcceptedLocked(request.mUid)) { - Slog.d(LOG_TAG, - "Only allow to see own content for non-default service at " - + request.mName); + if (!checkRequestAcceptedLocked(request.mUid, request.mName)) { return; } request.mRequest.run(); @@ -687,15 +642,15 @@ public final class TextClassificationManagerService extends ITextClassifierServi } @GuardedBy("mLock") - private boolean isServiceCurrentBoundLocked(@NonNull ComponentName componentName) { - return (mBoundServiceComponent != null - && mBoundServiceComponent.getPackageName().equals( + private boolean isCurrentlyBoundToComponentLocked(@NonNull ComponentName componentName) { + return (mBoundComponentName != null + && mBoundComponentName.getPackageName().equals( componentName.getPackageName())); } @GuardedBy("mLock") private void unbindLocked() { - Slog.d(LOG_TAG, "unbinding to " + mBoundServiceComponent + " for " + mUserId); + Slog.v(LOG_TAG, "unbinding from " + mBoundComponentName + " for " + mUserId); mContext.unbindService(mConnection); mConnection.cleanupService(); mConnection = null; @@ -716,8 +671,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi final long identity = Binder.clearCallingIdentity(); try { final ComponentName componentName = - TextClassifierService.getServiceComponentName(mContext, - getTextClassifierSettings(mContext)); + TextClassifierService.getServiceComponentName(mContext); if (componentName == null) { // Might happen if the storage is encrypted and the user is not unlocked return false; @@ -742,8 +696,8 @@ public final class TextClassificationManagerService extends ITextClassifierServi pw.printPair("context", mContext); pw.printPair("userId", mUserId); synchronized (mLock) { - pw.printPair("BoundServiceComponent", mBoundServiceComponent); - pw.printPair("isBoundToDefaultService", mIsBoundToDefaultService); + pw.printPair("boundComponentName", mBoundComponentName); + pw.printPair("boundToDefaultTrustService", mBoundToDefaultTrustService); pw.printPair("boundServiceUid", mBoundServiceUid); pw.printPair("binding", mBinding); pw.printPair("numberRequests", mPendingRequests.size()); @@ -751,14 +705,17 @@ public final class TextClassificationManagerService extends ITextClassifierServi } @GuardedBy("mLock") - private boolean isRequestAcceptedLocked(int requestUid) { - if (mIsBoundToDefaultService) { + private boolean checkRequestAcceptedLocked(int requestUid, @NonNull String methodName) { + if (mBoundToDefaultTrustService || (requestUid == mBoundServiceUid)) { return true; } - return (requestUid == mBoundServiceUid); + Slog.w(LOG_TAG, String.format( + "[%s] Non-default TextClassifierServices may only see text from the same uid.", + methodName)); + return false; } - private boolean isDefaultService(@NonNull ComponentName currentService) { + private boolean isDefaultTrustService(@NonNull ComponentName currentService) { final String[] defaultServiceNames = mContext.getPackageManager().getSystemTextClassifierPackages(); final String servicePackageName = currentService.getPackageName(); @@ -789,16 +746,16 @@ public final class TextClassificationManagerService extends ITextClassifierServi } @GuardedBy("mLock") - private void updateServiceInfoLocked(@Nullable ComponentName componentName, int userId) { - mBoundServiceComponent = componentName; - mIsBoundToDefaultService = (mBoundServiceComponent != null && isDefaultService( - mBoundServiceComponent)); - mBoundServiceUid = getServiceUid(mBoundServiceComponent, userId); + private void updateServiceInfoLocked(int userId, @Nullable ComponentName componentName) { + mBoundComponentName = componentName; + mBoundToDefaultTrustService = (mBoundComponentName != null && isDefaultTrustService( + mBoundComponentName)); + mBoundServiceUid = getServiceUid(mBoundComponentName, userId); } private final class TextClassifierServiceConnection implements ServiceConnection { - @UserIdInt final int mUserId; + @UserIdInt private final int mUserId; TextClassifierServiceConnection(int userId) { mUserId = userId; @@ -840,60 +797,42 @@ public final class TextClassificationManagerService extends ITextClassifierServi synchronized (mLock) { mService = service; mBinding = false; - updateServiceInfoLocked(name, mUserId); + updateServiceInfoLocked(mUserId, name); handlePendingRequestsLocked(); } } } } - private final class TextClassifierSettingsListener extends ContentObserver - implements DeviceConfig.OnPropertiesChangedListener { + private final class TextClassifierSettingsListener implements + DeviceConfig.OnPropertiesChangedListener { @NonNull private final Context mContext; + @NonNull private final TextClassificationConstants mSettings; @Nullable private String mServicePackageName; - TextClassifierSettingsListener(Context context, TextClassificationManagerService service) { - super(null); + TextClassifierSettingsListener(Context context) { mContext = context; - mServicePackageName = - getTextClassifierSettings(mContext).getTextClassifierServiceName(); + mSettings = TextClassificationManager.getSettings(mContext); + mServicePackageName = mSettings.getTextClassifierServicePackageOverride(); } public void registerObserver() { - mContext.getContentResolver().registerContentObserver( - Settings.Global.getUriFor(Settings.Global.TEXT_CLASSIFIER_CONSTANTS), - false /* notifyForDescendants */, + DeviceConfig.addOnPropertiesChangedListener( + DeviceConfig.NAMESPACE_TEXTCLASSIFIER, + mContext.getMainExecutor(), this); - if (ConfigParser.ENABLE_DEVICE_CONFIG) { - DeviceConfig.addOnPropertiesChangedListener( - DeviceConfig.NAMESPACE_TEXTCLASSIFIER, - mContext.getMainExecutor(), - this); - } } - private void updateChange() { - final String overrideServiceName = getTextClassifierSettings( - mContext).getTextClassifierServiceName(); + @Override + public void onPropertiesChanged(DeviceConfig.Properties properties) { + final String overrideServiceName = mSettings.getTextClassifierServicePackageOverride(); + if (TextUtils.equals(overrideServiceName, mServicePackageName)) { return; } mServicePackageName = overrideServiceName; - unbindServiceIfNeeded(); - } - - @Override - public void onChange(boolean selfChange, Uri uri) { - super.onChange(selfChange, uri); - invalidateSettings(); - updateChange(); - } - - @Override - public void onPropertiesChanged(DeviceConfig.Properties properties) { - invalidateSettings(); - updateChange(); + unbindServiceIfNecessary(); } } } |