Merge "Add RemoteInput auth bypass flow" into sc-dev

This commit is contained in:
Steve Elliott
2021-02-09 03:37:50 +00:00
committed by Android (Google) Code Review
4 changed files with 174 additions and 46 deletions

View File

@@ -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",

View File

@@ -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,40 +431,9 @@ 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);
return true;
}
final boolean deferBouncer = authBypassCheck != null;
if (!deferBouncer && showBouncerForRemoteInput(view, pendingIntent, row)) {
return true;
}
if (riv != null && !riv.isAttachedToWindow()) {
@@ -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,10 +484,62 @@ 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) {
return 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();
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
});
}