Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 401e3d4c authored by Yohei Yukawa's avatar Yohei Yukawa
Browse files

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
parent 329bc82c
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -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
+7 −5
Original line number Diff line number Diff line
@@ -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();
+73 −2
Original line number Diff line number Diff line
@@ -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();
@@ -10900,6 +10920,55 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                : mCurrentSpellCheckerLocaleCache;
    }

    /**
     * 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
@@ -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) {