diff options
2 files changed, 120 insertions, 4 deletions
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 7da1d6c085b0..7d2928b12c72 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -101,11 +101,14 @@ import android.view.MotionEvent; import android.view.ThreadedRenderer; import android.view.View; import android.view.ViewGroup; +import android.view.ViewRootImpl; import android.view.WindowInsetsController.Appearance; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityManager; import android.widget.DateTimeView; +import android.window.OnBackInvokedCallback; +import android.window.OnBackInvokedDispatcher; import androidx.annotation.NonNull; import androidx.lifecycle.Lifecycle; @@ -528,10 +531,17 @@ public class CentralSurfacesImpl extends CoreStartable implements private CentralSurfacesComponent mCentralSurfacesComponent; // Flags for disabling the status bar - // Two variables becaseu the first one evidently ran out of room for new flags. + // Two variables because the first one evidently ran out of room for new flags. private int mDisabled1 = 0; private int mDisabled2 = 0; + /** + * This keeps track of whether we have (or haven't) registered the predictive back callback. + * Since we can have visible -> visible transitions, we need to avoid + * double-registering (or double-unregistering) our callback. + */ + private boolean mIsBackCallbackRegistered = false; + /** @see android.view.WindowInsetsController#setSystemBarsAppearance(int, int) */ private @Appearance int mAppearance; @@ -657,6 +667,12 @@ public class CentralSurfacesImpl extends CoreStartable implements private final InteractionJankMonitor mJankMonitor; + private final OnBackInvokedCallback mOnBackInvokedCallback = () -> { + if (DEBUG) { + Log.d(TAG, "mOnBackInvokedCallback() called"); + } + onBackPressed(); + }; /** * Public constructor for CentralSurfaces. @@ -2775,9 +2791,38 @@ public class CentralSurfacesImpl extends CoreStartable implements if (visibleToUser) { handleVisibleToUserChangedImpl(visibleToUser); mNotificationLogger.startNotificationLogging(); + + if (!mIsBackCallbackRegistered) { + ViewRootImpl viewRootImpl = getViewRootImpl(); + if (viewRootImpl != null) { + viewRootImpl.getOnBackInvokedDispatcher() + .registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, + mOnBackInvokedCallback); + mIsBackCallbackRegistered = true; + if (DEBUG) Log.d(TAG, "is now VISIBLE to user AND callback registered"); + } + } else { + if (DEBUG) Log.d(TAG, "is now VISIBLE to user, BUT callback ALREADY unregistered"); + } } else { mNotificationLogger.stopNotificationLogging(); handleVisibleToUserChangedImpl(visibleToUser); + + if (mIsBackCallbackRegistered) { + ViewRootImpl viewRootImpl = getViewRootImpl(); + if (viewRootImpl != null) { + viewRootImpl.getOnBackInvokedDispatcher() + .unregisterOnBackInvokedCallback(mOnBackInvokedCallback); + mIsBackCallbackRegistered = false; + if (DEBUG) Log.d(TAG, "is NOT VISIBLE to user, AND callback unregistered"); + } + } else { + if (DEBUG) { + Log.d(TAG, + "is NOT VISIBLE to user, BUT NO callback (or callback ALREADY " + + "unregistered)"); + } + } } } @@ -3503,6 +3548,12 @@ public class CentralSurfacesImpl extends CoreStartable implements return mNotificationPanelViewController.getKeyguardBottomAreaView(); } + protected ViewRootImpl getViewRootImpl() { + NotificationShadeWindowView nswv = getNotificationShadeWindowView(); + if (nswv != null) return nswv.getViewRootImpl(); + + return null; + } /** * Propagation of the bouncer state, indicating that it's fully visible. */ @@ -3821,6 +3872,12 @@ public class CentralSurfacesImpl extends CoreStartable implements updateScrimController(); } + @VisibleForTesting + public void setNotificationShadeWindowViewController( + NotificationShadeWindowViewController nswvc) { + mNotificationShadeWindowViewController = nswvc; + } + /** * Set the amount of progress we are currently in if we're transitioning to the full shade. * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index a0f7087ddb9e..ced2dbd751c1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -35,6 +35,7 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -69,7 +70,11 @@ import android.util.DisplayMetrics; import android.util.SparseArray; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; +import android.view.ViewRootImpl; import android.view.WindowManager; +import android.window.OnBackInvokedCallback; +import android.window.OnBackInvokedDispatcher; +import android.window.WindowOnBackInvokedDispatcher; import androidx.test.filters.SmallTest; @@ -170,6 +175,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.MockitoAnnotations; @@ -283,6 +289,15 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private InteractionJankMonitor mJankMonitor; @Mock private DeviceStateManager mDeviceStateManager; @Mock private WiredChargingRippleController mWiredChargingRippleController; + /** + * The process of registering/unregistering a predictive back callback requires a + * ViewRootImpl, which is present IRL, but may be missing during a Mockito unit test. + * To prevent an NPE during test execution, we explicitly craft and provide a fake ViewRootImpl. + */ + @Mock private ViewRootImpl mViewRootImpl; + @Mock private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher; + @Captor private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback; + private ShadeController mShadeController; private final FakeSystemClock mFakeSystemClock = new FakeSystemClock(); private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock); @@ -372,10 +387,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { return null; }).when(mNotificationShadeWindowController).batchApplyWindowLayoutParams(any()); - mShadeController = new ShadeControllerImpl(mCommandQueue, + mShadeController = spy(new ShadeControllerImpl(mCommandQueue, mStatusBarStateController, mNotificationShadeWindowController, mStatusBarKeyguardViewManager, mContext.getSystemService(WindowManager.class), - () -> Optional.of(mCentralSurfaces), () -> mAssistManager); + () -> Optional.of(mCentralSurfaces), () -> mAssistManager)); when(mOperatorNameViewControllerFactory.create(any())) .thenReturn(mOperatorNameViewController); @@ -465,7 +480,14 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mActivityLaunchAnimator, mJankMonitor, mDeviceStateManager, - mWiredChargingRippleController, mDreamManager); + mWiredChargingRippleController, mDreamManager) { + @Override + protected ViewRootImpl getViewRootImpl() { + return mViewRootImpl; + } + }; + when(mViewRootImpl.getOnBackInvokedDispatcher()) + .thenReturn(mOnBackInvokedDispatcher); when(mKeyguardViewMediator.registerCentralSurfaces( any(CentralSurfacesImpl.class), any(NotificationPanelViewController.class), @@ -743,6 +765,43 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } } + /** + * Do the following: + * 1. verify that a predictive back callback is registered when CSurf becomes visible + * 2. verify that the same callback is unregistered when CSurf becomes invisible + */ + @Test + public void testPredictiveBackCallback_registration() { + mCentralSurfaces.handleVisibleToUserChanged(true); + verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback( + eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), + mOnBackInvokedCallback.capture()); + + mCentralSurfaces.handleVisibleToUserChanged(false); + verify(mOnBackInvokedDispatcher).unregisterOnBackInvokedCallback( + eq(mOnBackInvokedCallback.getValue())); + } + + /** + * Do the following: + * 1. capture the predictive back callback during registration + * 2. call the callback directly + * 3. verify that the ShadeController's panel collapse animation is invoked + */ + @Test + public void testPredictiveBackCallback_invocationCollapsesPanel() { + mCentralSurfaces.setNotificationShadeWindowViewController( + mNotificationShadeWindowViewController); + mCentralSurfaces.handleVisibleToUserChanged(true); + verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback( + eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), + mOnBackInvokedCallback.capture()); + + when(mNotificationPanelViewController.canPanelBeCollapsed()).thenReturn(true); + mOnBackInvokedCallback.getValue().onBackInvoked(); + verify(mShadeController).animateCollapsePanels(); + } + @Test public void testPanelOpenForHeadsUp() { when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true); |