Merge "Add RemoteInput auth bypass flow" into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
174eb3ef2e
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user