From ba60ab604c880699166cc5fbfad95aa0f2501141 Mon Sep 17 00:00:00 2001 From: Abodunrinwa Toki Date: Tue, 19 Sep 2017 23:18:40 +0100 Subject: [PATCH] Flags to enable/disable smart selection in EditText 1. smart_selection_enabled_for_edit_text Disables smart selection for editable text. 2. smart_selection_dark_launch Dark launch TextClassifierImpl.suggestSelection(...) In this mode we run the method, but never change the user's selection to measure the quality of the results based on what the user actually wanted. Bug: 65959640 Test: Manually tested flags turn on/off feature with 'adb shell settings put global' and GServices override. Test: bit FrameworksCoreTests:android.provider.SettingsBackupTest Merged-In: Iacc561c8ee004b0123de66ad1ee06b1f56b4e4b4 Change-Id: Iacc561c8ee004b0123de66ad1ee06b1f56b4e4b4 --- core/java/android/provider/Settings.java | 16 ++++ .../view/textclassifier/TextClassifier.java | 8 ++ .../TextClassifierConstants.java | 90 +++++++++++++++++++ .../textclassifier/TextClassifierImpl.java | 14 ++- .../widget/SelectionActionModeHelper.java | 30 ++++--- .../android/provider/SettingsBackupTest.java | 1 + 6 files changed, 148 insertions(+), 11 deletions(-) create mode 100644 core/java/android/view/textclassifier/TextClassifierConstants.java diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index c04e016bf4d5e..a248bf79f2a6b 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9529,6 +9529,22 @@ public final class Settings { */ public static final String DEVICE_POLICY_CONSTANTS = "device_policy_constants"; + /** + * TextClassifier specific settings. + * This is encoded as a key=value list, separated by commas. Ex: + * + *
+         * smart_selection_dark_launch              (boolean)
+         * smart_selection_enabled_for_edit_text    (boolean)
+         * 
+ * + *

+ * Type: string + * @hide + * see also android.view.textclassifier.TextClassifierConstants + */ + public static final String TEXT_CLASSIFIER_CONSTANTS = "text_classifier_constants"; + /** * Get the key that retrieves a bluetooth headset's priority. * @hide diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java index bb1e693fbf43f..c3601d9d32be3 100644 --- a/core/java/android/view/textclassifier/TextClassifier.java +++ b/core/java/android/view/textclassifier/TextClassifier.java @@ -152,4 +152,12 @@ public interface TextClassifier { */ @WorkerThread default void logEvent(String source, String event) {} + + /** + * Returns this TextClassifier's settings. + * @hide + */ + default TextClassifierConstants getSettings() { + return TextClassifierConstants.DEFAULT; + } } diff --git a/core/java/android/view/textclassifier/TextClassifierConstants.java b/core/java/android/view/textclassifier/TextClassifierConstants.java new file mode 100644 index 0000000000000..51e6168e9aa58 --- /dev/null +++ b/core/java/android/view/textclassifier/TextClassifierConstants.java @@ -0,0 +1,90 @@ +/* + * 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.util.KeyValueListParser; +import android.util.Slog; + +/** + * TextClassifier specific settings. + * This is encoded as a key=value list, separated by commas. Ex: + * + *

+ * smart_selection_dark_launch              (boolean)
+ * smart_selection_enabled_for_edit_text    (boolean)
+ * 
+ * + *

+ * Type: string + * see also android.provider.Settings.Global.TEXT_CLASSIFIER_CONSTANTS + * + * Example of setting the values for testing. + * adb shell settings put global text_classifier_constants smart_selection_dark_launch=true,smart_selection_enabled_for_edit_text=true + * @hide + */ +public final class TextClassifierConstants { + + private static final String LOG_TAG = "TextClassifierConstants"; + + private static final String SMART_SELECTION_DARK_LAUNCH = + "smart_selection_dark_launch"; + private static final String SMART_SELECTION_ENABLED_FOR_EDIT_TEXT = + "smart_selection_enabled_for_edit_text"; + + private static final boolean SMART_SELECTION_DARK_LAUNCH_DEFAULT = false; + private static final boolean SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT = true; + + /** Default settings. */ + static final TextClassifierConstants DEFAULT = new TextClassifierConstants(); + + private final boolean mDarkLaunch; + private final boolean mSuggestSelectionEnabledForEditableText; + + private TextClassifierConstants() { + mDarkLaunch = SMART_SELECTION_DARK_LAUNCH_DEFAULT; + mSuggestSelectionEnabledForEditableText = SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT; + } + + private TextClassifierConstants(@Nullable String settings) { + final KeyValueListParser parser = new KeyValueListParser(','); + try { + parser.setString(settings); + } catch (IllegalArgumentException e) { + // Failed to parse the settings string, log this and move on with defaults. + Slog.e(LOG_TAG, "Bad TextClassifier settings: " + settings); + } + mDarkLaunch = parser.getBoolean( + SMART_SELECTION_DARK_LAUNCH, + SMART_SELECTION_DARK_LAUNCH_DEFAULT); + mSuggestSelectionEnabledForEditableText = parser.getBoolean( + SMART_SELECTION_ENABLED_FOR_EDIT_TEXT, + SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT); + } + + static TextClassifierConstants loadFromString(String settings) { + return new TextClassifierConstants(settings); + } + + public boolean isDarkLaunch() { + return mDarkLaunch; + } + + public boolean isSuggestSelectionEnabledForEditableText() { + return mSuggestSelectionEnabledForEditableText; + } +} diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index 7e93b78c4809d..2e41404dde0aa 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -24,11 +24,11 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.graphics.drawable.Drawable; -import android.icu.text.BreakIterator; import android.net.Uri; import android.os.LocaleList; import android.os.ParcelFileDescriptor; import android.provider.Browser; +import android.provider.Settings; import android.text.Spannable; import android.text.TextUtils; import android.text.method.WordIterator; @@ -46,6 +46,7 @@ import com.android.internal.util.Preconditions; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.text.BreakIterator; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -90,6 +91,8 @@ final class TextClassifierImpl implements TextClassifier { @GuardedBy("mSmartSelectionLock") // Do not access outside this lock. private SmartSelection mSmartSelection; + private TextClassifierConstants mSettings; + TextClassifierImpl(Context context) { mContext = Preconditions.checkNotNull(context); } @@ -188,6 +191,15 @@ final class TextClassifierImpl implements TextClassifier { } } + @Override + public TextClassifierConstants getSettings() { + if (mSettings == null) { + mSettings = TextClassifierConstants.loadFromString(Settings.Global.getString( + mContext.getContentResolver(), Settings.Global.TEXT_CLASSIFIER_CONSTANTS)); + } + return mSettings; + } + private SmartSelection getSmartSelection(LocaleList localeList) throws FileNotFoundException { synchronized (mSmartSelectionLock) { localeList = localeList == null ? LocaleList.getEmptyLocaleList() : localeList; diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java index 513a18cdaf308..384e254e205fa 100644 --- a/core/java/android/widget/SelectionActionModeHelper.java +++ b/core/java/android/widget/SelectionActionModeHelper.java @@ -71,11 +71,15 @@ final class SelectionActionModeHelper { } public void startActionModeAsync(boolean adjustSelection) { + // Check if the smart selection should run for editable text. + adjustSelection &= !mTextView.isTextEditable() + || mTextView.getTextClassifier().getSettings() + .isSuggestSelectionEnabledForEditableText(); + mSelectionTracker.onOriginalSelection( getText(mTextView), mTextView.getSelectionStart(), - mTextView.getSelectionEnd(), - mTextView.isTextEditable()); + mTextView.getSelectionEnd()); cancelAsyncTask(); if (skipTextClassification()) { startActionMode(null); @@ -163,7 +167,10 @@ final class SelectionActionModeHelper { private void startActionMode(@Nullable SelectionResult result) { final CharSequence text = getText(mTextView); if (result != null && text instanceof Spannable) { - Selection.setSelection((Spannable) text, result.mStart, result.mEnd); + // Do not change the selection if TextClassifier should be dark launched. + if (!mTextView.getTextClassifier().getSettings().isDarkLaunch()) { + Selection.setSelection((Spannable) text, result.mStart, result.mEnd); + } mTextClassification = result.mClassification; } else { mTextClassification = null; @@ -193,7 +200,7 @@ final class SelectionActionModeHelper { } private void resetTextClassificationHelper() { - mTextClassificationHelper.reset( + mTextClassificationHelper.init( mTextView.getTextClassifier(), getText(mTextView), mTextView.getSelectionStart(), mTextView.getSelectionEnd(), @@ -225,8 +232,7 @@ final class SelectionActionModeHelper { /** * Called when the original selection happens, before smart selection is triggered. */ - public void onOriginalSelection( - CharSequence text, int selectionStart, int selectionEnd, boolean editableText) { + public void onOriginalSelection(CharSequence text, int selectionStart, int selectionEnd) { // If we abandoned a selection and created a new one very shortly after, we may still // have a pending request to log ABANDON, which we flush here. mDelayedLogAbandon.flush(); @@ -622,11 +628,11 @@ final class SelectionActionModeHelper { TextClassificationHelper(TextClassifier textClassifier, CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) { - reset(textClassifier, text, selectionStart, selectionEnd, locales); + init(textClassifier, text, selectionStart, selectionEnd, locales); } @UiThread - public void reset(TextClassifier textClassifier, + public void init(TextClassifier textClassifier, CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) { mTextClassifier = Preconditions.checkNotNull(textClassifier); mText = Preconditions.checkNotNull(text).toString(); @@ -649,8 +655,12 @@ final class SelectionActionModeHelper { trimText(); final TextSelection selection = mTextClassifier.suggestSelection( mTrimmedText, mRelativeStart, mRelativeEnd, mLocales); - mSelectionStart = Math.max(0, selection.getSelectionStartIndex() + mTrimStart); - mSelectionEnd = Math.min(mText.length(), selection.getSelectionEndIndex() + mTrimStart); + // Do not classify new selection boundaries if TextClassifier should be dark launched. + if (!mTextClassifier.getSettings().isDarkLaunch()) { + mSelectionStart = Math.max(0, selection.getSelectionStartIndex() + mTrimStart); + mSelectionEnd = Math.min( + mText.length(), selection.getSelectionEndIndex() + mTrimStart); + } return performClassification(selection); } diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index 7d9c50dc432e0..662f20e58a734 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -344,6 +344,7 @@ public class SettingsBackupTest { Settings.Global.TETHER_DUN_REQUIRED, Settings.Global.TETHER_OFFLOAD_DISABLED, Settings.Global.TETHER_SUPPORTED, + Settings.Global.TEXT_CLASSIFIER_CONSTANTS, Settings.Global.THEATER_MODE_ON, Settings.Global.TRANSITION_ANIMATION_SCALE, Settings.Global.TRUSTED_SOUND,