Merge "Add RemoteInput auth bypass flow" into sc-dev
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 9021864..7bfb42b 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -76,8 +76,8 @@
"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",
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index a816ecc..cfadcd7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -388,7 +388,28 @@
*/
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,40 +431,9 @@
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);
- return true;
- }
+ final boolean deferBouncer = authBypassCheck != null;
+ if (!deferBouncer && showBouncerForRemoteInput(view, pendingIntent, row)) {
+ return true;
}
if (riv != null && !riv.isAttachedToWindow()) {
@@ -461,7 +451,10 @@
&& !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,10 +484,62 @@
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) {
return null;
@@ -807,8 +852,11 @@
*
* @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 @@
*/
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();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index 36519ac..983b296e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -180,8 +180,8 @@
@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 @@
mGroupExpansionManager.toggleGroupExpansion(row.getEntry());
}
row.setUserExpanded(true);
- row.getPrivateLayout().setOnExpandedVisibleListener(clickedView::performClick);
+ row.getPrivateLayout().setOnExpandedVisibleListener(runnable);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 9380d91..8e833c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -73,6 +73,7 @@
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.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 @@
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 @@
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 @@
}
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 @@
}
}
+ /** 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 @@
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 @@
private RemoteInputView mRemoteInputView;
boolean mShowImeOnInputConnection;
private LightBarController mLightBarController;
+ private InputMethodManager mInputMethodManager;
UserHandle mUser;
public RemoteEditText(Context context, AttributeSet attrs) {
@@ -621,6 +669,12 @@
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 @@
@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 @@
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);
}
});
}