diff options
4 files changed, 151 insertions, 4 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java index 344d23341dc1..c1c6c88da822 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java @@ -23,6 +23,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewStub; +import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.LockIconView; import com.android.systemui.R; import com.android.systemui.battery.BatteryMeterView; @@ -67,6 +68,7 @@ import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.tuner.TunerService; import com.android.systemui.util.CarrierConfigTracker; import com.android.systemui.util.settings.SecureSettings; @@ -299,7 +301,9 @@ public abstract class StatusBarViewModule { OperatorNameViewController.Factory operatorNameViewControllerFactory, SecureSettings secureSettings, @Main Executor mainExecutor, - DumpManager dumpManager + DumpManager dumpManager, + StatusBarWindowStateController statusBarWindowStateController, + KeyguardUpdateMonitor keyguardUpdateMonitor ) { return new CollapsedStatusBarFragment(statusBarFragmentComponentFactory, ongoingCallController, @@ -320,7 +324,9 @@ public abstract class StatusBarViewModule { operatorNameViewControllerFactory, secureSettings, mainExecutor, - dumpManager); + dumpManager, + statusBarWindowStateController, + keyguardUpdateMonitor); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index 9354c5e1948d..00fd4ef9a1fc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -44,6 +44,7 @@ import android.widget.LinearLayout; import androidx.annotation.VisibleForTesting; +import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; @@ -72,6 +73,8 @@ import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentCom import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.statusbar.window.StatusBarWindowStateController; +import com.android.systemui.statusbar.window.StatusBarWindowStateListener; import com.android.systemui.util.CarrierConfigTracker; import com.android.systemui.util.CarrierConfigTracker.CarrierConfigChangedListener; import com.android.systemui.util.CarrierConfigTracker.DefaultDataSubscriptionChangedListener; @@ -129,6 +132,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private final SecureSettings mSecureSettings; private final Executor mMainExecutor; private final DumpManager mDumpManager; + private final StatusBarWindowStateController mStatusBarWindowStateController; + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private List<String> mBlockedIcons = new ArrayList<>(); private Map<Startable, Startable.State> mStartableStates = new ArrayMap<>(); @@ -164,6 +169,22 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } }; + /** + * Whether we've launched the secure camera over the lockscreen, but haven't yet received a + * status bar window state change afterward. + * + * We wait for this state change (which will tell us whether to show/hide the status bar icons) + * so that there is no flickering/jump cutting during the camera launch. + */ + private boolean mWaitingForWindowStateChangeAfterCameraLaunch = false; + + /** + * Listener that updates {@link #mWaitingForWindowStateChangeAfterCameraLaunch} when it receives + * a new status bar window state. + */ + private final StatusBarWindowStateListener mStatusBarWindowStateListener = state -> + mWaitingForWindowStateChangeAfterCameraLaunch = false; + @SuppressLint("ValidFragment") public CollapsedStatusBarFragment( StatusBarFragmentComponent.Factory statusBarFragmentComponentFactory, @@ -185,7 +206,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue OperatorNameViewController.Factory operatorNameViewControllerFactory, SecureSettings secureSettings, @Main Executor mainExecutor, - DumpManager dumpManager + DumpManager dumpManager, + StatusBarWindowStateController statusBarWindowStateController, + KeyguardUpdateMonitor keyguardUpdateMonitor ) { mStatusBarFragmentComponentFactory = statusBarFragmentComponentFactory; mOngoingCallController = ongoingCallController; @@ -207,6 +230,20 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mSecureSettings = secureSettings; mMainExecutor = mainExecutor; mDumpManager = dumpManager; + mStatusBarWindowStateController = statusBarWindowStateController; + mKeyguardUpdateMonitor = keyguardUpdateMonitor; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mStatusBarWindowStateController.addListener(mStatusBarWindowStateListener); + } + + @Override + public void onDestroy() { + super.onDestroy(); + mStatusBarWindowStateController.removeListener(mStatusBarWindowStateListener); } @Override @@ -254,6 +291,11 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mCarrierConfigTracker.addDefaultDataSubscriptionChangedListener(mDefaultDataListener); } + @Override + public void onCameraLaunchGestureDetected(int source) { + mWaitingForWindowStateChangeAfterCameraLaunch = true; + } + @VisibleForTesting void updateBlockedIcons() { mBlockedIcons.clear(); @@ -466,6 +508,27 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue && mNotificationPanelViewController.hideStatusBarIconsWhenExpanded()) { return true; } + + // When launching the camera over the lockscreen, the icons become visible momentarily + // before animating out, since we're not yet aware that the launching camera activity is + // fullscreen. Even once the activity finishes launching, it takes a short time before WM + // decides that the top app wants to hide the icons and tells us to hide them. To ensure + // that this high-visibility animation is smooth, keep the icons hidden during a camera + // launch until we receive a window state change which indicates that the activity is done + // launching and WM has decided to show/hide the icons. For extra safety (to ensure the + // icons don't remain hidden somehow) we double check that the camera is still showing, the + // status bar window isn't hidden, and we're still occluded as well, though these checks + // are typically unnecessary. + final boolean hideIconsForSecureCamera = + (mWaitingForWindowStateChangeAfterCameraLaunch || + !mStatusBarWindowStateController.windowIsShowing()) && + mKeyguardUpdateMonitor.isSecureCameraLaunchedOverKeyguard() && + mKeyguardStateController.isOccluded(); + + if (hideIconsForSecureCamera) { + return true; + } + return mStatusBarHideIconsForBouncerManager.getShouldHideStatusBarIconsForBouncer(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt index 60f6df66cb5a..8f424b2e251e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt @@ -61,6 +61,10 @@ class StatusBarWindowStateController @Inject constructor( listeners.add(listener) } + fun removeListener(listener: StatusBarWindowStateListener) { + listeners.remove(listener) + } + /** Returns true if the window is currently showing. */ fun windowIsShowing() = windowState == WINDOW_STATE_SHOWING diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index f230b876f05e..07e8d3c19518 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -23,10 +23,12 @@ import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedul import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -45,6 +47,7 @@ import android.widget.FrameLayout; import androidx.test.filters.SmallTest; +import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.R; import com.android.systemui.SysuiBaseFragmentTest; import com.android.systemui.dump.DumpManager; @@ -67,6 +70,8 @@ import com.android.systemui.statusbar.phone.StatusBarLocationPublisher; import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.statusbar.window.StatusBarWindowStateController; +import com.android.systemui.statusbar.window.StatusBarWindowStateListener; import com.android.systemui.util.CarrierConfigTracker; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.settings.SecureSettings; @@ -79,6 +84,9 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; +import java.util.List; + @RunWith(AndroidTestingRunner.class) @RunWithLooper(setAsMainLooper = true) @SmallTest @@ -118,6 +126,12 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; @Mock private DumpManager mDumpManager; + @Mock + private StatusBarWindowStateController mStatusBarWindowStateController; + @Mock + private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + + private List<StatusBarWindowStateListener> mStatusBarWindowStateListeners = new ArrayList<>(); public CollapsedStatusBarFragmentTest() { super(CollapsedStatusBarFragment.class); @@ -127,6 +141,14 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { public void setup() { injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES); mDependency.injectMockDependency(DarkIconDispatcher.class); + + // Keep the window state listeners so we can dispatch to them to test the status bar + // fragment's response. + doAnswer(invocation -> { + mStatusBarWindowStateListeners.add(invocation.getArgument(0)); + return null; + }).when(mStatusBarWindowStateController).addListener( + any(StatusBarWindowStateListener.class)); } @Test @@ -414,6 +436,27 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertFalse(contains); } + @Test + public void testStatusBarIcons_hiddenThroughoutCameraLaunch() { + final CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + + mockSecureCameraLaunch(fragment, true /* launched */); + + // Status icons should be invisible or gone, but certainly not VISIBLE. + assertNotEquals(View.VISIBLE, getEndSideContentView().getVisibility()); + assertNotEquals(View.VISIBLE, getClockView().getVisibility()); + + mockSecureCameraLaunchFinished(); + + assertNotEquals(View.VISIBLE, getEndSideContentView().getVisibility()); + assertNotEquals(View.VISIBLE, getClockView().getVisibility()); + + mockSecureCameraLaunch(fragment, false /* launched */); + + assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); + assertEquals(View.VISIBLE, getClockView().getVisibility()); + } + @Override protected Fragment instantiate(Context context, String className, Bundle arguments) { MockitoAnnotations.initMocks(this); @@ -455,7 +498,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { mOperatorNameViewControllerFactory, mSecureSettings, mExecutor, - mDumpManager); + mDumpManager, + mStatusBarWindowStateController, + mKeyguardUpdateMonitor); } private void setUpDaggerComponent() { @@ -478,6 +523,35 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { mNotificationAreaInner); } + /** + * Configure mocks to return values consistent with the secure camera animating itself launched + * over the keyguard. + */ + private void mockSecureCameraLaunch(CollapsedStatusBarFragment fragment, boolean launched) { + when(mKeyguardUpdateMonitor.isSecureCameraLaunchedOverKeyguard()).thenReturn(launched); + when(mKeyguardStateController.isOccluded()).thenReturn(launched); + + if (launched) { + fragment.onCameraLaunchGestureDetected(0 /* source */); + } else { + for (StatusBarWindowStateListener listener : mStatusBarWindowStateListeners) { + listener.onStatusBarWindowStateChanged(StatusBarManager.WINDOW_STATE_SHOWING); + } + } + + fragment.disable(DEFAULT_DISPLAY, 0, 0, false); + } + + /** + * Configure mocks to return values consistent with the secure camera showing over the keyguard + * with its launch animation finished. + */ + private void mockSecureCameraLaunchFinished() { + for (StatusBarWindowStateListener listener : mStatusBarWindowStateListeners) { + listener.onStatusBarWindowStateChanged(StatusBarManager.WINDOW_STATE_HIDDEN); + } + } + private CollapsedStatusBarFragment resumeAndGetFragment() { mFragments.dispatchResume(); processAllMessages(); |