diff options
author | Yohei Yukawa <yukawa@google.com> | 2019-01-19 11:49:37 -0800 |
---|---|---|
committer | Yohei Yukawa <yukawa@google.com> | 2019-01-19 11:49:37 -0800 |
commit | 401e3d4c842ce86569de4477138137dc07a6aa6f (patch) | |
tree | 6fd66c984826e4b5b96c2d7b4fd092d6c0a978e7 | |
parent | 329bc82c0fd2c68e5d17d6262518ab0062a9b706 (diff) |
Introduce @hide TextView#setTextOperationUser()
This CL introduces a unified way for framework developers to specify
whose components should be interacting with the given TextView. An
important use case is the direct-reply UI notification hosted in
System UI, which always runs as user 0 no matter who is the current
user.
For instance, to let the given EditText interact with user 10's input
methods and spell checkers, you can call setTextOperationUser() as
follows.
editText.setTextOperationUser(UserHandle.of(10));
In this way we can easily propergate the same user ID to other
components such as autofill and text classifer as necessary in the
future.
No one calls TextView#setTextOperationUser() yet hence there should be
no user-visible behavior change.
Bug: 120744418
Bug: 123043618
Test: spell checker still works
Test: atest CtsInputMethodTestCases CtsInputMethodServiceHostTestCases
Change-Id: I6d11e4d6a84570bc2991a8552349e8b216b0d139
-rw-r--r-- | core/java/android/widget/Editor.java | 11 | ||||
-rw-r--r-- | core/java/android/widget/SpellChecker.java | 12 | ||||
-rw-r--r-- | core/java/android/widget/TextView.java | 75 |
3 files changed, 91 insertions, 7 deletions
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 3c32bb29cf66..55364ec1017d 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -2394,6 +2394,17 @@ public class Editor { } } + /** + * Called when {@link TextView#mTextOperationUser} has changed. + * + * <p>Any user-specific resources need to be refreshed here.</p> + */ + final void onTextOperationUserChanged() { + if (mSpellChecker != null) { + mSpellChecker.resetSession(); + } + } + protected void stopTextActionMode() { if (mTextActionMode != null) { // This will hide the mSelectionModifierCursorController diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java index fc1172e0457a..d4aad752daa0 100644 --- a/core/java/android/widget/SpellChecker.java +++ b/core/java/android/widget/SpellChecker.java @@ -16,7 +16,7 @@ package android.widget; -import android.content.Context; +import android.annotation.Nullable; import android.text.Editable; import android.text.Selection; import android.text.Spanned; @@ -93,6 +93,7 @@ public class SpellChecker implements SpellCheckerSessionListener { // concurrently due to the asynchronous nature of onGetSuggestions. private WordIterator mWordIterator; + @Nullable private TextServicesManager mTextServicesManager; private Runnable mSpellRunnable; @@ -114,12 +115,12 @@ public class SpellChecker implements SpellCheckerSessionListener { mCookie = hashCode(); } - private void resetSession() { + void resetSession() { closeSession(); - mTextServicesManager = (TextServicesManager) mTextView.getContext(). - getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + mTextServicesManager = mTextView.getTextServicesManagerForUser(); if (mCurrentLocale == null + || mTextServicesManager == null || mTextView.length() == 0 || !mTextServicesManager.isSpellCheckerEnabled() || mTextServicesManager.getCurrentSpellCheckerSubtype(true) == null) { @@ -226,7 +227,8 @@ public class SpellChecker implements SpellCheckerSessionListener { start = 0; end = mTextView.getText().length(); } else { - final boolean spellCheckerActivated = mTextServicesManager.isSpellCheckerEnabled(); + final boolean spellCheckerActivated = + mTextServicesManager != null && mTextServicesManager.isSpellCheckerEnabled(); if (isSessionActive != spellCheckerActivated) { // Spell checker has been turned of or off since last spellCheck resetSession(); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 4a6095464b42..1085e5dadb42 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -16,6 +16,7 @@ package android.widget; +import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH; import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX; import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY; @@ -31,6 +32,7 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Px; +import android.annotation.RequiresPermission; import android.annotation.Size; import android.annotation.StringRes; import android.annotation.StyleRes; @@ -45,6 +47,7 @@ import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; import android.content.UndoManager; +import android.content.pm.PackageManager; import android.content.res.ColorStateList; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; @@ -73,7 +76,9 @@ import android.os.LocaleList; import android.os.Parcel; import android.os.Parcelable; import android.os.ParcelableParcel; +import android.os.Process; import android.os.SystemClock; +import android.os.UserHandle; import android.provider.Settings; import android.text.BoringLayout; import android.text.DynamicLayout; @@ -194,6 +199,7 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Locale; +import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; @@ -785,6 +791,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private InputFilter[] mFilters = NO_FILTERS; + /** + * {@link UserHandle} that represents the logical owner of the text. {@code null} when it is + * the same as {@link Process#myUserHandle()}. + * + * <p>Most of applications should not worry about this. Some privileged apps that host UI for + * other apps may need to set this so that the system can use right user's resources and + * services such as input methods and spell checkers.</p> + * + * @see #setTextOperationUser(UserHandle) + */ + @Nullable + private UserHandle mTextOperationUser; + private volatile Locale mCurrentSpellCheckerLocaleCache; // It is possible to have a selection even when mEditor is null (programmatically set, like when @@ -8324,6 +8343,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; } outAttrs.hintText = mHint; + outAttrs.targetInputMethodUser = mTextOperationUser; if (mText instanceof Editable) { InputConnection ic = new EditableInputConnection(this); outAttrs.initialSelStart = getSelectionStart(); @@ -10901,6 +10921,55 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** + * Associate {@link UserHandle} who is considered to be the logical owner of the text shown in + * this {@link TextView}. + * + * <p>Most of applications should not worry about this. Some privileged apps that host UI for + * other apps may need to set this so that the system can user right user's resources and + * services such as input methods and spell checkers.</p> + * + * @param user {@link UserHandle} who is considered to be the owner of the text shown in this + * {@link TextView}. {@code null} to reset {@link #mTextOperationUser}. + * @hide + */ + @RequiresPermission(INTERACT_ACROSS_USERS_FULL) + public final void setTextOperationUser(@Nullable UserHandle user) { + if (Objects.equals(mTextOperationUser, user)) { + return; + } + if (user != null && !Process.myUserHandle().equals(user)) { + // Just for preventing people from accidentally using this hidden API without + // the required permission. The same permission is also checked in the system server. + if (getContext().checkSelfPermission(INTERACT_ACROSS_USERS_FULL) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("INTERACT_ACROSS_USERS_FULL is required." + + " userId=" + user.getIdentifier() + + " callingUserId" + UserHandle.myUserId()); + } + } + mTextOperationUser = user; + // Invalidate some resources + mCurrentSpellCheckerLocaleCache = null; + if (mEditor != null) { + mEditor.onTextOperationUserChanged(); + } + } + + @Nullable + final TextServicesManager getTextServicesManagerForUser() { + if (mTextOperationUser == null) { + return getContext().getSystemService(TextServicesManager.class); + } + try { + return getContext().createPackageContextAsUser( + "android", 0 /* flags */, mTextOperationUser) + .getSystemService(TextServicesManager.class); + } catch (PackageManager.NameNotFoundException e) { + return null; + } + } + + /** * This is a temporary method. Future versions may support multi-locale text. * Caveat: This method may not return the latest text services locale, but this should be * acceptable and it's more important to make this method asynchronous. @@ -10972,8 +11041,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @UnsupportedAppUsage private void updateTextServicesLocaleLocked() { - final TextServicesManager textServicesManager = (TextServicesManager) - mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + final TextServicesManager textServicesManager = getTextServicesManagerForUser(); + if (textServicesManager == null) { + return; + } final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true); final Locale locale; if (subtype != null) { |