Loading packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +58 −1 Original line number Diff line number Diff line Loading @@ -100,11 +100,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; Loading Loading @@ -526,10 +529,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; Loading Loading @@ -653,6 +663,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. Loading Loading @@ -2751,9 +2767,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)"); } } } } Loading Loading @@ -3464,6 +3509,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. */ Loading Loading @@ -3781,6 +3832,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 Loading packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +62 −3 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -168,6 +173,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; Loading Loading @@ -279,6 +285,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); Loading Loading @@ -368,10 +383,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); Loading Loading @@ -460,7 +475,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), Loading Loading @@ -738,6 +760,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); Loading Loading
packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +58 −1 Original line number Diff line number Diff line Loading @@ -100,11 +100,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; Loading Loading @@ -526,10 +529,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; Loading Loading @@ -653,6 +663,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. Loading Loading @@ -2751,9 +2767,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)"); } } } } Loading Loading @@ -3464,6 +3509,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. */ Loading Loading @@ -3781,6 +3832,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 Loading
packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +62 −3 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -168,6 +173,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; Loading Loading @@ -279,6 +285,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); Loading Loading @@ -368,10 +383,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); Loading Loading @@ -460,7 +475,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), Loading Loading @@ -738,6 +760,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); Loading