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

Commit 00f83da3 authored by Steve Elliott's avatar Steve Elliott Committed by Automerger Merge Worker
Browse files

Merge "Add RemoteInput auth bypass flow" into sc-dev am: 174eb3ef

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/13494453

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: If3aa0c26032ec105f33dc8144baf556e53e3f2ab
parents 5c517017 174eb3ef
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -76,8 +76,8 @@ android_library {
        "androidx.dynamicanimation_dynamicanimation",
        "androidx-constraintlayout_constraintlayout",
        "androidx.exifinterface_exifinterface",
        "kotlinx-coroutines-android",
        "kotlinx-coroutines-core",
        "kotlinx_coroutines_android",
        "kotlinx_coroutines",
        "iconloader_base",
        "SystemUI-tags",
        "SystemUI-proto",
+108 −36
Original line number Diff line number Diff line
@@ -388,7 +388,28 @@ public class NotificationRemoteInputManager implements Dumpable {
     */
    public boolean activateRemoteInput(View view, RemoteInput[] inputs, RemoteInput input,
            PendingIntent pendingIntent, @Nullable EditedSuggestionInfo editedSuggestionInfo) {
        return activateRemoteInput(view, inputs, input, pendingIntent, editedSuggestionInfo,
                null /* userMessageContent */, null /* authBypassCheck */);
    }

    /**
     * Activates a given {@link RemoteInput}
     *
     * @param view The view of the action button or suggestion chip that was tapped.
     * @param inputs The remote inputs that need to be sent to the app.
     * @param input The remote input that needs to be activated.
     * @param pendingIntent The pending intent to be sent to the app.
     * @param editedSuggestionInfo The smart reply that should be inserted in the remote input, or
     *         {@code null} if the user is not editing a smart reply.
     * @param userMessageContent User-entered text with which to initialize the remote input view.
     * @param authBypassCheck Optional auth bypass check associated with this remote input
     *         activation. If {@code null}, we never bypass.
     * @return Whether the {@link RemoteInput} was activated.
     */
    public boolean activateRemoteInput(View view, RemoteInput[] inputs, RemoteInput input,
            PendingIntent pendingIntent, @Nullable EditedSuggestionInfo editedSuggestionInfo,
            @Nullable String userMessageContent,
            @Nullable AuthBypassPredicate authBypassCheck) {
        ViewParent p = view.getParent();
        RemoteInputView riv = null;
        ExpandableNotificationRow row = null;
@@ -410,41 +431,10 @@ public class NotificationRemoteInputManager implements Dumpable {

        row.setUserExpanded(true);

        if (!mLockscreenUserManager.shouldAllowLockscreenRemoteInput()) {
            final int userId = pendingIntent.getCreatorUserHandle().getIdentifier();

            final boolean isLockedManagedProfile =
                    mUserManager.getUserInfo(userId).isManagedProfile()
                    && mKeyguardManager.isDeviceLocked(userId);

            final boolean isParentUserLocked;
            if (isLockedManagedProfile) {
                final UserInfo profileParent = mUserManager.getProfileParent(userId);
                isParentUserLocked = (profileParent != null)
                        && mKeyguardManager.isDeviceLocked(profileParent.id);
            } else {
                isParentUserLocked = false;
            }

            if (mLockscreenUserManager.isLockscreenPublicMode(userId)
                    || mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
                // If the parent user is no longer locked, and the user to which the remote input
                // is destined is a locked, managed profile, then onLockedWorkRemoteInput should be
                // called to unlock it.
                if (isLockedManagedProfile && !isParentUserLocked) {
                    mCallback.onLockedWorkRemoteInput(userId, row, view);
                } else {
                    // Even if we don't have security we should go through this flow, otherwise
                    // we won't go to the shade.
                    mCallback.onLockedRemoteInput(row, view);
                }
                return true;
            }
            if (isLockedManagedProfile) {
                mCallback.onLockedWorkRemoteInput(userId, row, view);
        final boolean deferBouncer = authBypassCheck != null;
        if (!deferBouncer && showBouncerForRemoteInput(view, pendingIntent, row)) {
            return true;
        }
        }

        if (riv != null && !riv.isAttachedToWindow()) {
            // the remoteInput isn't attached to the window anymore :/ Let's focus on the expanded
@@ -461,7 +451,10 @@ public class NotificationRemoteInputManager implements Dumpable {
                && !row.getPrivateLayout().getExpandedChild().isShown()) {
            // The expanded layout is selected, but it's not shown yet, let's wait on it to
            // show before we do the animation.
            mCallback.onMakeExpandedVisibleForRemoteInput(row, view);
            mCallback.onMakeExpandedVisibleForRemoteInput(row, view, deferBouncer, () -> {
                activateRemoteInput(view, inputs, input, pendingIntent, editedSuggestionInfo,
                        userMessageContent, authBypassCheck);
            });
            return true;
        }

@@ -491,9 +484,61 @@ public class NotificationRemoteInputManager implements Dumpable {
        riv.setPendingIntent(pendingIntent);
        riv.setRemoteInput(inputs, input, editedSuggestionInfo);
        riv.focusAnimated();
        if (userMessageContent != null) {
            riv.setEditTextContent(userMessageContent);
        }
        if (deferBouncer) {
            final ExpandableNotificationRow finalRow = row;
            riv.setBouncerChecker(() -> !authBypassCheck.canSendRemoteInputWithoutBouncer()
                    && showBouncerForRemoteInput(view, pendingIntent, finalRow));
        }

        return true;
    }

    private boolean showBouncerForRemoteInput(View view, PendingIntent pendingIntent,
            ExpandableNotificationRow row) {
        if (mLockscreenUserManager.shouldAllowLockscreenRemoteInput()) {
            return false;
        }

        final int userId = pendingIntent.getCreatorUserHandle().getIdentifier();

        final boolean isLockedManagedProfile =
                mUserManager.getUserInfo(userId).isManagedProfile()
                        && mKeyguardManager.isDeviceLocked(userId);

        final boolean isParentUserLocked;
        if (isLockedManagedProfile) {
            final UserInfo profileParent = mUserManager.getProfileParent(userId);
            isParentUserLocked = (profileParent != null)
                    && mKeyguardManager.isDeviceLocked(profileParent.id);
        } else {
            isParentUserLocked = false;
        }

        if ((mLockscreenUserManager.isLockscreenPublicMode(userId)
                || mStatusBarStateController.getState() == StatusBarState.KEYGUARD)) {
            // If the parent user is no longer locked, and the user to which the remote
            // input
            // is destined is a locked, managed profile, then onLockedWorkRemoteInput
            // should be
            // called to unlock it.
            if (isLockedManagedProfile && !isParentUserLocked) {
                mCallback.onLockedWorkRemoteInput(userId, row, view);
            } else {
                // Even if we don't have security we should go through this flow, otherwise
                // we won't go to the shade.
                mCallback.onLockedRemoteInput(row, view);
            }
            return true;
        }
        if (isLockedManagedProfile) {
            mCallback.onLockedWorkRemoteInput(userId, row, view);
            return true;
        }
        return false;
    }

    private RemoteInputView findRemoteInputView(View v) {
        if (v == null) {
@@ -807,8 +852,11 @@ public class NotificationRemoteInputManager implements Dumpable {
         *
         * @param row
         * @param clickedView
         * @param deferBouncer
         * @param runnable
         */
        void onMakeExpandedVisibleForRemoteInput(ExpandableNotificationRow row, View clickedView);
        void onMakeExpandedVisibleForRemoteInput(ExpandableNotificationRow row, View clickedView,
                boolean deferBouncer, Runnable runnable);

        /**
         * Return whether or not remote input should be handled for this view.
@@ -845,4 +893,28 @@ public class NotificationRemoteInputManager implements Dumpable {
         */
        boolean handleClick();
    }

    /**
     * Predicate that is associated with a specific {@link #activateRemoteInput(View, RemoteInput[],
     * RemoteInput, PendingIntent, EditedSuggestionInfo, String, AuthBypassPredicate)}
     * invocation that determines whether or not the bouncer can be bypassed when sending the
     * RemoteInput.
     */
    public interface AuthBypassPredicate {
        /**
         * Determines if the RemoteInput can be sent without the bouncer. Should be checked the
         * same frame that the RemoteInput is to be sent.
         */
        boolean canSendRemoteInputWithoutBouncer();
    }

    /** Shows the bouncer if necessary */
    public interface BouncerChecker {
        /**
         * Shows the bouncer if necessary in order to send a RemoteInput.
         *
         * @return {@code true} if the bouncer was shown, {@code false} otherwise
         */
        boolean showBouncerIfNecessary();
    }
}
+3 −3
Original line number Diff line number Diff line
@@ -180,8 +180,8 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks,

    @Override
    public void onMakeExpandedVisibleForRemoteInput(ExpandableNotificationRow row,
            View clickedView) {
        if (mKeyguardStateController.isShowing()) {
            View clickedView, boolean deferBouncer, Runnable runnable) {
        if (!deferBouncer && mKeyguardStateController.isShowing()) {
            onLockedRemoteInput(row, clickedView);
        } else {
            if (row.isChildInGroup() && !row.areChildrenExpanded()) {
@@ -189,7 +189,7 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks,
                mGroupExpansionManager.toggleGroupExpansion(row.getEntry());
            }
            row.setUserExpanded(true);
            row.getPrivateLayout().setOnExpandedVisibleListener(clickedView::performClick);
            row.getPrivateLayout().setOnExpandedVisibleListener(runnable);
        }
    }

+61 −5
Original line number Diff line number Diff line
@@ -73,6 +73,7 @@ import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntry.EditedSuggestionInfo;
@@ -80,6 +81,7 @@ import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewW
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.LightBarController;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
@@ -99,6 +101,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene

    private final SendButtonTextWatcher mTextWatcher;
    private final TextView.OnEditorActionListener mEditorActionHandler;
    private final NotificationRemoteInputManager mRemoteInputManager;
    private final List<OnFocusChangeListener> mEditTextFocusChangeListeners = new ArrayList<>();
    private RemoteEditText mEditText;
    private ImageButton mSendButton;
    private ProgressBar mProgressBar;
@@ -121,12 +125,14 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
    private boolean mResetting;
    private NotificationViewWrapper mWrapper;
    private Consumer<Boolean> mOnVisibilityChangedListener;
    private NotificationRemoteInputManager.BouncerChecker mBouncerChecker;

    public RemoteInputView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mTextWatcher = new SendButtonTextWatcher();
        mEditorActionHandler = new EditorActionHandler();
        mRemoteInputQuickSettingsDisabler = Dependency.get(RemoteInputQuickSettingsDisabler.class);
        mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
        mStatusBarManagerService = IStatusBarService.Stub.asInterface(
                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
    }
@@ -200,6 +206,11 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
    }

    private void sendRemoteInput(Intent intent) {
        if (mBouncerChecker != null && mBouncerChecker.showBouncerIfNecessary()) {
            mEditText.hideIme();
            return;
        }

        mEditText.setEnabled(false);
        mSendButton.setVisibility(INVISIBLE);
        mProgressBar.setVisibility(VISIBLE);
@@ -351,6 +362,11 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
        }
    }

    /** Populates the text field of the remote input with the given content. */
    public void setEditTextContent(@Nullable CharSequence editTextContent) {
        mEditText.setText(editTextContent);
    }

    public void focusAnimated() {
        if (getVisibility() != VISIBLE) {
            Animator animator = ViewAnimationUtils.createCircularReveal(
@@ -552,6 +568,37 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
        return getVisibility() == VISIBLE && mController.isSpinning(mEntry.getKey(), mToken);
    }

    /**
     * Sets a {@link com.android.systemui.statusbar.NotificationRemoteInputManager.BouncerChecker}
     * that will be used to determine if the device needs to be unlocked before sending the
     * RemoteInput.
     */
    public void setBouncerChecker(
            @Nullable NotificationRemoteInputManager.BouncerChecker bouncerChecker) {
        mBouncerChecker = bouncerChecker;
    }

    /** Registers a listener for focus-change events on the EditText */
    public void addOnEditTextFocusChangedListener(View.OnFocusChangeListener listener) {
        mEditTextFocusChangeListeners.add(listener);
    }

    /** Removes a previously-added listener for focus-change events on the EditText */
    public void removeOnEditTextFocusChangedListener(View.OnFocusChangeListener listener) {
        mEditTextFocusChangeListeners.remove(listener);
    }

    /** Determines if the EditText has focus. */
    public boolean editTextHasFocus() {
        return mEditText != null && mEditText.hasFocus();
    }

    private void onEditTextFocusChanged(RemoteEditText remoteEditText, boolean focused) {
        for (View.OnFocusChangeListener listener : mEditTextFocusChangeListeners) {
            listener.onFocusChange(remoteEditText, focused);
        }
    }

    /** Handler for button click on send action in IME. */
    private class EditorActionHandler implements TextView.OnEditorActionListener {

@@ -603,6 +650,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
        private RemoteInputView mRemoteInputView;
        boolean mShowImeOnInputConnection;
        private LightBarController mLightBarController;
        private InputMethodManager mInputMethodManager;
        UserHandle mUser;

        public RemoteEditText(Context context, AttributeSet attrs) {
@@ -621,6 +669,12 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
            setOnReceiveContentListener(types, listener);
        }

        private void hideIme() {
            if (mInputMethodManager != null) {
                mInputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
            }
        }

        private void defocusIfNeeded(boolean animate) {
            if (mRemoteInputView != null && mRemoteInputView.mEntry.getRow().isChangingPosition()
                    || isTemporarilyDetached()) {
@@ -654,6 +708,9 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
        @Override
        protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
            super.onFocusChanged(focused, direction, previouslyFocusedRect);
            if (mRemoteInputView != null) {
                mRemoteInputView.onEditTextFocusChanged(this, focused);
            }
            if (!focused) {
                defocusIfNeeded(true /* animate */);
            }
@@ -724,17 +781,16 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene

            if (mShowImeOnInputConnection && ic != null) {
                Context targetContext = userContext != null ? userContext : getContext();
                final InputMethodManager imm =
                        targetContext.getSystemService(InputMethodManager.class);
                if (imm != null) {
                mInputMethodManager = targetContext.getSystemService(InputMethodManager.class);
                if (mInputMethodManager != null) {
                    // onCreateInputConnection is called by InputMethodManager in the middle of
                    // setting up the connection to the IME; wait with requesting the IME until that
                    // work has completed.
                    post(new Runnable() {
                        @Override
                        public void run() {
                            imm.viewClicked(RemoteEditText.this);
                            imm.showSoftInput(RemoteEditText.this, 0);
                            mInputMethodManager.viewClicked(RemoteEditText.this);
                            mInputMethodManager.showSoftInput(RemoteEditText.this, 0);
                        }
                    });
                }