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

Commit 7276946b authored by Yohei Yukawa's avatar Yohei Yukawa
Browse files

Use setTextOperationUser() in KeyguardPasswordView and RemoteInputView

Android implements many complex text-related operations as per-user
out-of-process services. Notable examples are:

  * InputMethodServices
  * SpellCheckerService
  * TextClassifierService
  * AutofillService

For service client processes to connect to the right user's services
often the system assumes that a process that runs as user X should
connect to services that run as user X.  While this approach works for
most of cases, there is at least one exception, System UI, which
always runs as the primary user (unless the device enables special
mode e.g. UserManager.isSplitSystemUser()) no matter who is the
current foreground user.

In order to deal with this special case, we recently introduced an
@hide API TextView#setTextOperationUser() [1] to centralize cross-user
special logic inside TextView.  With this CL, KeyguardPasswordView and
RemoteInputView actually start calling it.

Note that TextView#setTextOperationUser() already takes care of IMEs
and SpellCheckers hence those two operations start working as expected
with this CL.  Once TextView#setTextOperationUser() start taking care
of other operations such as text classification and clipboard,
KeyguardPasswordView and RemoteInputView will be automatically taken
care of with no code change.

 [1]: I6d11e4d6a84570bc2991a8552349e8b216b0d139
      401e3d4c

Fix: 120744418
Fix: 123043618
Test: atest SystemUITests
Test: Manually tested Bug 120744418 scenario as follows.
  1. Build and flash an AOSP build
  2. adb root
  3. adb shell setprop persist.debug.per_profile_ime 1
  4. adb reboot
  5. Install Test DPC
  6. Enable managed profile with Test DPC
  7. make -j SoftKeyboard
  8. adb install -r $OUT/system/app/SoftKeyboard/SoftKeyboard.apk
  9. adb shell ime enable com.example.android.softkeyboard/.SoftKeyboard
 10. adb shell ime set com.example.android.softkeyboard/.SoftKeyboard
 11. make -j EditTextVariations
 12. adb install -r $ANDROID_TARGET_OUT_TESTCASES/EditTextVariations/EditTextVariations.apk
 13. adb shell am start --user 0 \
     -n com.android.inputmethod.tools.edittextvariations/.EditTextVariations
 14. Tap the menu icon on the EditTextVariations then click "Direct Reply"
 15. adb shell am start --user 10 \
     -n com.android.inputmethod.tools.edittextvariations/.EditTextVariations
 16. Tap the menu icon on the EditTextVariations then click "Direct Reply"
 17. Open notification shade.
 18. Tap reply icon on "Message from UserHandle{0}".
 19. Make sure that SoftKeyboard Sample IME is shown.
 20. Tap reply icon on "Message from UserHandle{10}".
 21. Make sure that AOSP Keyboard is shown.
 22. Tap reply icon on "Message from UserHandle{0}" again.
 23. Make sure that SoftKeyboard Sample IME is shown again.
Test: Manually tested Bug 123043618 scenario as follows.
  1. Build and flash an AOSP build
  2. Create a secondary user then switch to it.
  3. make -j SoftKeyboard
  4. adb install -r $OUT/system/app/SoftKeyboard/SoftKeyboard.apk
  5. adb shell ime enable com.example.android.softkeyboard/.SoftKeyboard
  6. adb shell ime set com.example.android.softkeyboard/.SoftKeyboard
  7. make -j EditTextVariations
  8. adb install -r $ANDROID_TARGET_OUT_TESTCASES/EditTextVariations/EditTextVariations.apk
  9. adb shell am start \
     -n com.android.inputmethod.tools.edittextvariations/.EditTextVariations
 10. Tap the menu icon on the EditTextVariations then click "Direct Reply"
 11. Tap reply icon on "Message from UserHandle{10}".
 12. Make sure that SoftKeyboard Sample IME is shown.
 13. Type "ggggg" then hit the space key.
 14. Make sure that red underline is drawn under "ggggg"
 15. Make sure that AndroidSpellCheckerService runs as user 10.
Test: Manually tested multi-user scenario with per-profile mode as follows.
  1. Build and flash an AOSP build
  2. adb root
  3. adb shell setprop persist.debug.per_profile_ime 1
  4. adb reboot
  5. Create a secondary user then switch to it.
  6. make -j SoftKeyboard
  7. adb install -r $OUT/system/app/SoftKeyboard/SoftKeyboard.apk
  8. adb shell ime enable com.example.android.softkeyboard/.SoftKeyboard
  9. adb shell ime set com.example.android.softkeyboard/.SoftKeyboard
 10. make -j EditTextVariations
 11. adb install -r $ANDROID_TARGET_OUT_TESTCASES/EditTextVariations/EditTextVariations.apk
 12. adb shell am start \
     -n com.android.inputmethod.tools.edittextvariations/.EditTextVariations
 13. Tap the menu icon on the EditTextVariations then click "Direct Reply"
 14. Tap reply icon on "Message from UserHandle{10}".
 15. Make sure that SoftKeyboard Sample IME is shown.
 16. Type "ggggg" then hit the space key.
 17. Make sure that red underline is drawn under "ggggg"
 18. Make sure that AndroidSpellCheckerService runs as user 10.
 19. Set up a text screenlock.
 20. Turn off the display.
 21. Turn on the display.
 22. Make sure that SoftKeyboard Sample IME is shown.
Change-Id: Ibabe13e5b85e5bb91f9f8af6ec07c395c25c4393
parent 03c5726d
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.keyguard;

import android.content.Context;
import android.graphics.Rect;
import android.os.UserHandle;
import android.text.Editable;
import android.text.InputType;
import android.text.TextUtils;
@@ -79,6 +80,7 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView

    @Override
    protected void resetState() {
        mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
        mSecurityMessageDisplay.setMessage("");
        final boolean wasDisabled = mPasswordEntry.isEnabled();
        // Don't set enabled password entry & showSoftInput when PasswordEntry is invisible or in
@@ -174,6 +176,7 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView
                Context.INPUT_METHOD_SERVICE);

        mPasswordEntry = findViewById(getPasswordTextViewId());
        mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
        mPasswordEntryDisabler = new TextViewInputDisabler(mPasswordEntry);
        mPasswordEntry.setKeyListener(TextKeyListener.getInstance());
        mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT
+9 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.policy;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.app.ActivityManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.RemoteInput;
@@ -28,6 +29,7 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.SystemClock;
import android.os.UserHandle;
import android.text.Editable;
import android.text.SpannedString;
import android.text.TextWatcher;
@@ -283,6 +285,11 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
        focus();
    }

    private static UserHandle computeTextOperationUser(UserHandle notificationUser) {
        return UserHandle.ALL.equals(notificationUser)
                ? UserHandle.of(ActivityManager.getCurrentUser()) : notificationUser;
    }

    public void focus() {
        MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_REMOTE_INPUT_OPEN,
                mEntry.notification.getPackageName());
@@ -291,6 +298,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
        if (mWrapper != null) {
            mWrapper.setRemoteInputVisible(true);
        }
        mEditText.setTextOperationUser(computeTextOperationUser(mEntry.notification.getUser()));
        mEditText.setInnerFocusable(true);
        mEditText.mShowImeOnInputConnection = true;
        mEditText.setText(mEntry.remoteInputText);
@@ -320,6 +328,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
        mResetting = true;
        mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText());

        mEditText.setTextOperationUser(null);
        mEditText.getText().clear();
        mEditText.setEnabled(true);
        mSendButton.setVisibility(VISIBLE);
+18 −12
Original line number Diff line number Diff line
@@ -54,6 +54,8 @@ public class NotificationTestHelper {
    public static final String PKG = "com.android.systemui";
    /** System UI id for testing purposes. */
    public static final int UID = 1000;
    /** Current {@link UserHandle} of the system. */
    public static final UserHandle USER_HANDLE = UserHandle.of(ActivityManager.getCurrentUser());

    private static final String GROUP_KEY = "gruKey";

@@ -78,7 +80,7 @@ public class NotificationTestHelper {
     * @throws Exception
     */
    public ExpandableNotificationRow createRow() throws Exception {
        return createRow(PKG, UID);
        return createRow(PKG, UID, USER_HANDLE);
    }

    /**
@@ -89,8 +91,9 @@ public class NotificationTestHelper {
     * @return a row with a notification using the package and user id
     * @throws Exception
     */
    public ExpandableNotificationRow createRow(String pkg, int uid) throws Exception {
        return createRow(pkg, uid, false /* isGroupSummary */, null /* groupKey */);
    public ExpandableNotificationRow createRow(String pkg, int uid, UserHandle userHandle)
            throws Exception {
        return createRow(pkg, uid, userHandle, false /* isGroupSummary */, null /* groupKey */);
    }

    /**
@@ -101,7 +104,7 @@ public class NotificationTestHelper {
     * @throws Exception
     */
    public ExpandableNotificationRow createRow(Notification notification) throws Exception {
        return generateRow(notification, PKG, UID, 0 /* extraInflationFlags */);
        return generateRow(notification, PKG, UID, USER_HANDLE, 0 /* extraInflationFlags */);
    }

    /**
@@ -114,7 +117,7 @@ public class NotificationTestHelper {
     */
    public ExpandableNotificationRow createRow(@InflationFlag int extraInflationFlags)
            throws Exception {
        return generateRow(createNotification(), PKG, UID, extraInflationFlags);
        return generateRow(createNotification(), PKG, UID, USER_HANDLE, extraInflationFlags);
    }

    /**
@@ -136,11 +139,11 @@ public class NotificationTestHelper {
    }

    private ExpandableNotificationRow createGroupSummary(String groupkey) throws Exception {
        return createRow(PKG, UID, true /* isGroupSummary */, groupkey);
        return createRow(PKG, UID, USER_HANDLE, true /* isGroupSummary */, groupkey);
    }

    private ExpandableNotificationRow createGroupChild(String groupkey) throws Exception {
        return createRow(PKG, UID, false /* isGroupSummary */, groupkey);
        return createRow(PKG, UID, USER_HANDLE, false /* isGroupSummary */, groupkey);
    }

    /**
@@ -149,7 +152,7 @@ public class NotificationTestHelper {
    public ExpandableNotificationRow createBubble() throws Exception {
        Notification n = createNotification(false /* isGroupSummary */,
                null /* groupKey */, true /* isBubble */);
        return generateRow(n, PKG, UID, 0 /* extraInflationFlags */, IMPORTANCE_HIGH);
        return generateRow(n, PKG, UID, USER_HANDLE, 0 /* extraInflationFlags */, IMPORTANCE_HIGH);
    }

    /**
@@ -166,11 +169,12 @@ public class NotificationTestHelper {
    private ExpandableNotificationRow createRow(
            String pkg,
            int uid,
            UserHandle userHandle,
            boolean isGroupSummary,
            @Nullable String groupKey)
            throws Exception {
        Notification notif = createNotification(isGroupSummary, groupKey);
        return generateRow(notif, pkg, uid, 0 /* inflationFlags */);
        return generateRow(notif, pkg, uid, userHandle, 0 /* inflationFlags */);
    }

    /**
@@ -230,15 +234,18 @@ public class NotificationTestHelper {
            Notification notification,
            String pkg,
            int uid,
            UserHandle userHandle,
            @InflationFlag int extraInflationFlags)
            throws Exception {
        return generateRow(notification, pkg, uid, extraInflationFlags, IMPORTANCE_DEFAULT);
        return generateRow(notification, pkg, uid, userHandle, extraInflationFlags,
                IMPORTANCE_DEFAULT);
    }

    private ExpandableNotificationRow generateRow(
            Notification notification,
            String pkg,
            int uid,
            UserHandle userHandle,
            @InflationFlag int extraInflationFlags,
            int importance)
            throws Exception {
@@ -252,7 +259,6 @@ public class NotificationTestHelper {
        row.setGroupManager(mGroupManager);
        row.setHeadsUpManager(mHeadsUpManager);
        row.setAboveShelfChangedListener(aboveShelf -> {});
        UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser());
        StatusBarNotification sbn = new StatusBarNotification(
                pkg,
                pkg,
@@ -261,7 +267,7 @@ public class NotificationTestHelper {
                uid,
                2000 /* initialPid */,
                notification,
                mUser,
                userHandle,
                null /* overrideGroupKey */,
                System.currentTimeMillis());
        NotificationEntry entry = new NotificationEntry(sbn);
+3 −1
Original line number Diff line number Diff line
@@ -44,6 +44,7 @@ import android.content.pm.PackageManager;
import android.graphics.drawable.Icon;
import android.media.session.MediaSession;
import android.os.Bundle;
import android.os.Process;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.SnoozeCriterion;
@@ -140,7 +141,8 @@ public class NotificationDataTest extends SysuiTestCase {
        ExpandableNotificationRow row2 = new NotificationTestHelper(getContext()).createRow();
        mNotificationData.add(row2.getEntry());
        ExpandableNotificationRow diffPkg =
                new NotificationTestHelper(getContext()).createRow("pkg", 4000);
                new NotificationTestHelper(getContext()).createRow("pkg", 4000,
                        Process.myUserHandle());
        mNotificationData.add(diffPkg.getEntry());

        ArraySet<Integer> expectedOps = new ArraySet<>();
+74 −14
Original line number Diff line number Diff line
@@ -15,17 +15,23 @@
package com.android.systemui.statusbar.policy;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;

import android.app.ActivityManager;
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ShortcutManager;
import android.os.Handler;
import android.os.Process;
import android.os.UserHandle;
import android.support.test.filters.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.widget.EditText;
import android.widget.ImageButton;

@@ -53,6 +59,10 @@ public class RemoteInputViewTest extends SysuiTestCase {
    private static final String TEST_REPLY = "hello";
    private static final String TEST_ACTION = "com.android.REMOTE_INPUT_VIEW_ACTION";

    private static final String DUMMY_MESSAGE_APP_PKG =
            "com.android.sysuitest.dummynotificationsender";
    private static final int DUMMY_MESSAGE_APP_ID = Process.LAST_APPLICATION_UID - 1;

    @Mock private RemoteInputController mController;
    @Mock private ShortcutManager mShortcutManager;
    @Mock private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
@@ -73,9 +83,6 @@ public class RemoteInputViewTest extends SysuiTestCase {

        // Avoid SecurityException RemoteInputView#sendRemoteInput().
        mContext.addMockSystemService(ShortcutManager.class, mShortcutManager);

        ExpandableNotificationRow row = new NotificationTestHelper(mContext).createRow();
        mView = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
    }

    @After
@@ -83,19 +90,27 @@ public class RemoteInputViewTest extends SysuiTestCase {
        mContext.unregisterReceiver(mReceiver);
    }

    @Test
    public void testSendRemoteInput_intentContainsResultsAndSource() throws InterruptedException {
    private void setTestPendingIntent(RemoteInputView view) {
        PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
                new Intent(TEST_ACTION), 0);
        RemoteInput input = new RemoteInput.Builder(TEST_RESULT_KEY).build();

        mView.setPendingIntent(pendingIntent);
        mView.setRemoteInput(new RemoteInput[]{input}, input);
        mView.focus();
        view.setPendingIntent(pendingIntent);
        view.setRemoteInput(new RemoteInput[]{input}, input);
    }

    @Test
    public void testSendRemoteInput_intentContainsResultsAndSource() throws Exception {
        ExpandableNotificationRow row = new NotificationTestHelper(mContext).createRow();
        RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);

        setTestPendingIntent(view);

        EditText editText = mView.findViewById(R.id.remote_input_text);
        view.focus();

        EditText editText = view.findViewById(R.id.remote_input_text);
        editText.setText(TEST_REPLY);
        ImageButton sendButton = mView.findViewById(R.id.remote_input_send);
        ImageButton sendButton = view.findViewById(R.id.remote_input_send);
        sendButton.performClick();

        Intent resultIntent = mReceiver.waitForIntent();
@@ -105,10 +120,55 @@ public class RemoteInputViewTest extends SysuiTestCase {
                RemoteInput.getResultsSource(resultIntent));
    }

    private UserHandle getTargetInputMethodUser(UserHandle fromUser, UserHandle toUser)
            throws Exception {
        ExpandableNotificationRow row = new NotificationTestHelper(mContext).createRow(
                DUMMY_MESSAGE_APP_PKG,
                UserHandle.getUid(fromUser.getIdentifier(), DUMMY_MESSAGE_APP_ID),
                toUser);
        RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);

        setTestPendingIntent(view);

        view.focus();

        EditText editText = view.findViewById(R.id.remote_input_text);
        EditorInfo editorInfo = new EditorInfo();
        editorInfo.packageName = DUMMY_MESSAGE_APP_PKG;
        editorInfo.fieldId = editText.getId();
        InputConnection ic = editText.onCreateInputConnection(editorInfo);
        assertNotNull(ic);
        return editorInfo.targetInputMethodUser;
    }

    @Test
    public void testNoCrashWithoutVisibilityListener() {
        mView.setOnVisibilityChangedListener(null);
        mView.setVisibility(View.INVISIBLE);
        mView.setVisibility(View.VISIBLE);
    public void testEditorInfoTargetInputMethodUserForCallingUser() throws Exception {
        UserHandle callingUser = Process.myUserHandle();
        assertEquals(callingUser, getTargetInputMethodUser(callingUser, callingUser));
    }

    @Test
    public void testEditorInfoTargetInputMethodUserForDifferentUser() throws Exception {
        UserHandle differentUser = UserHandle.of(UserHandle.getCallingUserId() + 1);
        assertEquals(differentUser, getTargetInputMethodUser(differentUser, differentUser));
    }

    @Test
    public void testEditorInfoTargetInputMethodUserForAllUser() throws Exception {
        // For the special pseudo user UserHandle.ALL, EditorInfo#targetInputMethodUser must be
        // resolved as the current user.
        UserHandle callingUser = Process.myUserHandle();
        assertEquals(UserHandle.of(ActivityManager.getCurrentUser()),
                getTargetInputMethodUser(callingUser, UserHandle.ALL));
    }

    @Test
    public void testNoCrashWithoutVisibilityListener() throws Exception {
        ExpandableNotificationRow row = new NotificationTestHelper(mContext).createRow();
        RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);

        view.setOnVisibilityChangedListener(null);
        view.setVisibility(View.INVISIBLE);
        view.setVisibility(View.VISIBLE);
    }
}