diff options
4 files changed, 173 insertions, 26 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index f37243adfa9e..fa7bfaeb6c4d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -416,6 +416,9 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn void endAffordanceLaunch(); + /** Should the keyguard be hidden immediately in response to a back press/gesture. */ + boolean shouldKeyguardHideImmediately(); + boolean onBackPressed(); boolean onSpacePressed(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 0b63bbfec877..14ae008abc66 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -3312,19 +3312,23 @@ public class CentralSurfacesImpl extends CoreStartable implements mNotificationPanelViewController.onAffordanceLaunchEnded(); } + /** + * Returns whether the keyguard should hide immediately (as opposed to via an animation). + * Non-scrimmed bouncers have a special animation tied to the notification panel expansion. + * @return whether the keyguard should be immediately hidden. + */ @Override - public boolean onBackPressed() { + public boolean shouldKeyguardHideImmediately() { final boolean isScrimmedBouncer = mScrimController.getState() == ScrimState.BOUNCER_SCRIMMED; final boolean isBouncerOverDream = isBouncerShowingOverDream(); + return (isScrimmedBouncer || isBouncerOverDream); + } - if (mStatusBarKeyguardViewManager.onBackPressed( - isScrimmedBouncer || isBouncerOverDream /* hideImmediately */)) { - if (isScrimmedBouncer || isBouncerOverDream) { - mStatusBarStateController.setLeaveOpenOnKeyguardHide(false); - } else { - mNotificationPanelViewController.expandWithoutQs(); - } + @Override + public boolean onBackPressed() { + if (mStatusBarKeyguardViewManager.canHandleBackPressed()) { + mStatusBarKeyguardViewManager.onBackPressed(false /* unused */); return true; } if (mNotificationPanelViewController.isQsCustomizing()) { @@ -3339,7 +3343,7 @@ public class CentralSurfacesImpl extends CoreStartable implements return true; } if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED - && !isBouncerOverDream) { + && !isBouncerShowingOverDream()) { if (mNotificationPanelViewController.canPanelBeCollapsed()) { mShadeController.animateCollapsePanels(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index a0578fac2033..46fc7d7c9461 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -31,12 +31,15 @@ import android.hardware.biometrics.BiometricSourceType; import android.os.Bundle; import android.os.SystemClock; import android.os.Trace; +import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewRootImpl; import android.view.WindowManagerGlobal; +import android.window.OnBackInvokedCallback; +import android.window.OnBackInvokedDispatcher; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -119,6 +122,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private static final long KEYGUARD_DISMISS_DURATION_LOCKED = 2000; private static String TAG = "StatusBarKeyguardViewManager"; + private static final boolean DEBUG = false; protected final Context mContext; private final ConfigurationController mConfigurationController; @@ -184,8 +188,25 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb if (mAlternateAuthInterceptor != null) { mAlternateAuthInterceptor.onBouncerVisibilityChanged(); } + + /* Register predictive back callback when keyguard becomes visible, and unregister + when it's hidden. */ + if (isVisible) { + registerBackCallback(); + } else { + unregisterBackCallback(); + } + } + }; + + private final OnBackInvokedCallback mOnBackInvokedCallback = () -> { + if (DEBUG) { + Log.d(TAG, "onBackInvokedCallback() called, invoking onBackPressed()"); } + onBackPressed(false /* unused */); }; + private boolean mIsBackCallbackRegistered = false; + private final DockManager.DockEventListener mDockEventListener = new DockManager.DockEventListener() { @Override @@ -378,6 +399,46 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } } + /** Register a callback, to be invoked by the Predictive Back system. */ + private void registerBackCallback() { + if (!mIsBackCallbackRegistered) { + ViewRootImpl viewRoot = getViewRootImpl(); + if (viewRoot != null) { + viewRoot.getOnBackInvokedDispatcher().registerOnBackInvokedCallback( + OnBackInvokedDispatcher.PRIORITY_OVERLAY, mOnBackInvokedCallback); + mIsBackCallbackRegistered = true; + } else { + if (DEBUG) { + Log.d(TAG, "view root was null, could not register back callback"); + } + } + } else { + if (DEBUG) { + Log.d(TAG, "prevented registering back callback twice"); + } + } + } + + /** Unregister the callback formerly registered with the Predictive Back system. */ + private void unregisterBackCallback() { + if (mIsBackCallbackRegistered) { + ViewRootImpl viewRoot = getViewRootImpl(); + if (viewRoot != null) { + viewRoot.getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback( + mOnBackInvokedCallback); + mIsBackCallbackRegistered = false; + } else { + if (DEBUG) { + Log.d(TAG, "view root was null, could not unregister back callback"); + } + } + } else { + if (DEBUG) { + Log.d(TAG, "prevented unregistering back callback twice"); + } + } + } + @Override public void onDensityOrFontScaleChanged() { hideBouncer(true /* destroyView */); @@ -1015,25 +1076,47 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } /** - * Notifies this manager that the back button has been pressed. + * Returns whether a back invocation can be handled, which depends on whether the keyguard + * is currently showing (which itself is derived from multiple states). * - * @param hideImmediately Hide bouncer when {@code true}, keep it around otherwise. - * Non-scrimmed bouncers have a special animation tied to the expansion - * of the notification panel. - * @return whether the back press has been handled + * @return whether a back press can be handled right now. */ - public boolean onBackPressed(boolean hideImmediately) { - if (bouncerIsShowing()) { - mCentralSurfaces.endAffordanceLaunch(); - // The second condition is for SIM card locked bouncer - if (bouncerIsScrimmed() - && !needsFullscreenBouncer()) { - hideBouncer(false); - updateStates(); + public boolean canHandleBackPressed() { + return mBouncer.isShowing(); + } + + /** + * Notifies this manager that the back button has been pressed. + */ + // TODO(b/244635782): This "accept boolean and ignore it, and always return false" was done + // to make it possible to check this in *and* allow merging to master, + // where ArcStatusBarKeyguardViewManager inherits this class, and its + // build will break if we change this interface. + // So, overall, while this function refactors the behavior of onBackPressed, + // (it now handles the back press, and no longer returns *whether* it did so) + // its interface is not changing right now (but will, in a follow-up CL). + public boolean onBackPressed(boolean ignored) { + if (!canHandleBackPressed()) { + return false; + } + + mCentralSurfaces.endAffordanceLaunch(); + // The second condition is for SIM card locked bouncer + if (bouncerIsScrimmed() && needsFullscreenBouncer()) { + hideBouncer(false); + updateStates(); + } else { + /* Non-scrimmed bouncers have a special animation tied to the expansion + * of the notification panel. We decide whether to kick this animation off + * by computing the hideImmediately boolean. + */ + boolean hideImmediately = mCentralSurfaces.shouldKeyguardHideImmediately(); + reset(hideImmediately); + if (hideImmediately) { + mStatusBarStateController.setLeaveOpenOnKeyguardHide(false); } else { - reset(hideImmediately); + mNotificationPanelViewController.expandWithoutQs(); } - return true; } return false; } @@ -1304,7 +1387,15 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Override public ViewRootImpl getViewRootImpl() { - return mNotificationShadeWindowController.getNotificationShadeView().getViewRootImpl(); + ViewGroup viewGroup = mNotificationShadeWindowController.getNotificationShadeView(); + if (viewGroup != null) { + return viewGroup.getViewRootImpl(); + } else { + if (DEBUG) { + Log.d(TAG, "ViewGroup was null, cannot get ViewRootImpl"); + } + return null; + } } public void launchPendingWakeupAction() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index f52bfcaaaa44..fe3555fd4ec8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -35,6 +35,10 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; import android.view.ViewGroup; +import android.view.ViewRootImpl; +import android.window.OnBackInvokedCallback; +import android.window.OnBackInvokedDispatcher; +import android.window.WindowOnBackInvokedDispatcher; import androidx.test.filters.SmallTest; @@ -73,6 +77,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @@ -119,6 +124,12 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private KeyguardBouncer.BouncerExpansionCallback mBouncerExpansionCallback; + @Mock private ViewRootImpl mViewRootImpl; + @Mock private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher; + @Captor + private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback; + + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -154,7 +165,14 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mFeatureFlags, mBouncerCallbackInteractor, mBouncerInteractor, - mBouncerView); + mBouncerView) { + @Override + public ViewRootImpl getViewRootImpl() { + return mViewRootImpl; + } + }; + when(mViewRootImpl.getOnBackInvokedDispatcher()) + .thenReturn(mOnBackInvokedDispatcher); mStatusBarKeyguardViewManager.registerCentralSurfaces( mCentralSurfaces, mNotificationPanelView, @@ -509,6 +527,37 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test + public void testPredictiveBackCallback_registration() { + /* verify that a predictive back callback is registered when the bouncer becomes visible */ + mBouncerExpansionCallback.onVisibilityChanged(true); + verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback( + eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY), + mOnBackInvokedCallback.capture()); + + /* verify that the same callback is unregistered when the bouncer becomes invisible */ + mBouncerExpansionCallback.onVisibilityChanged(false); + verify(mOnBackInvokedDispatcher).unregisterOnBackInvokedCallback( + eq(mOnBackInvokedCallback.getValue())); + } + + @Test + public void testPredictiveBackCallback_invocationHidesBouncer() { + mBouncerExpansionCallback.onVisibilityChanged(true); + /* capture the predictive back callback during registration */ + verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback( + eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY), + mOnBackInvokedCallback.capture()); + + when(mBouncer.isShowing()).thenReturn(true); + when(mCentralSurfaces.shouldKeyguardHideImmediately()).thenReturn(true); + /* invoke the back callback directly */ + mOnBackInvokedCallback.getValue().onBackInvoked(); + + /* verify that the bouncer will be hidden as a result of the invocation */ + verify(mCentralSurfaces).setBouncerShowing(eq(false)); + } + + @Test public void testReportBouncerOnDreamWhenVisible() { mBouncerExpansionCallback.onVisibilityChanged(true); verify(mCentralSurfaces).setBouncerShowingOverDream(false); |