diff options
5 files changed, 151 insertions, 40 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt index 458ed4059b3b..926002060da7 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt @@ -36,6 +36,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dreams.DreamOverlayStateController import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.shade.NotifPanelEvents import com.android.systemui.statusbar.CrossFadeHelper import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.StatusBarState @@ -85,13 +86,22 @@ class MediaHierarchyManager @Inject constructor( private val bypassController: KeyguardBypassController, private val mediaCarouselController: MediaCarouselController, private val notifLockscreenUserManager: NotificationLockscreenUserManager, + private val keyguardViewController: KeyguardViewController, + private val dreamOverlayStateController: DreamOverlayStateController, configurationController: ConfigurationController, wakefulnessLifecycle: WakefulnessLifecycle, - private val keyguardViewController: KeyguardViewController, - private val dreamOverlayStateController: DreamOverlayStateController + panelEventsEvents: NotifPanelEvents, ) { /** + * Whether we "skip" QQS during panel expansion. + * + * This means that when expanding the panel we go directly to QS. Also when we are on QS and + * start closing the panel, it fully collapses instead of going to QQS. + */ + private var skipQqsOnExpansion: Boolean = false + + /** * The root overlay of the hierarchy. This is where the media notification is attached to * whenever the view is transitioning from one host to another. It also make sure that the * view is always in its final state when it is attached to a view host. @@ -504,6 +514,13 @@ class MediaHierarchyManager @Inject constructor( mediaCarouselController.updateUserVisibility = { mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser() } + + panelEventsEvents.registerListener(object : NotifPanelEvents.Listener { + override fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) { + skipQqsOnExpansion = isExpandImmediateEnabled + updateDesiredLocation() + } + }) } private fun updateConfiguration() { @@ -701,6 +718,9 @@ class MediaHierarchyManager @Inject constructor( if (isCurrentlyInGuidedTransformation()) { return false } + if (skipQqsOnExpansion) { + return false + } // This is an invalid transition, and can happen when using the camera gesture from the // lock screen. Disallow. if (previousLocation == LOCATION_LOCKSCREEN && @@ -852,6 +872,9 @@ class MediaHierarchyManager @Inject constructor( * otherwise */ private fun getTransformationProgress(): Float { + if (skipQqsOnExpansion) { + return -1.0f + } val progress = getQSTransformationProgress() if (statusbarState != StatusBarState.KEYGUARD && progress >= 0) { return progress @@ -1042,6 +1065,10 @@ class MediaHierarchyManager @Inject constructor( // reattach it without an animation return LOCATION_LOCKSCREEN } + if (skipQqsOnExpansion) { + // When doing an immediate expand or collapse, we want to keep it in QS. + return LOCATION_QS + } return location } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt b/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt index ce9d89f89ae1..4558061de1a2 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt @@ -29,11 +29,25 @@ interface NotifPanelEvents { interface Listener { /** Invoked when the notification panel starts or stops collapsing. */ - fun onPanelCollapsingChanged(isCollapsing: Boolean) + @JvmDefault + fun onPanelCollapsingChanged(isCollapsing: Boolean) {} /** * Invoked when the notification panel starts or stops launching an [android.app.Activity]. */ - fun onLaunchingActivityChanged(isLaunchingActivity: Boolean) + @JvmDefault + fun onLaunchingActivityChanged(isLaunchingActivity: Boolean) {} + + /** + * Invoked when the "expand immediate" attribute changes. + * + * An example of expanding immediately is when swiping down from the top with two fingers. + * Instead of going to QQS, we immediately expand to full QS. + * + * Another example is when full QS is showing, and we swipe up from the bottom. Instead of + * going to QQS, the panel fully collapses. + */ + @JvmDefault + fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {} } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index fb8c35bd27e3..cf416c52bf3a 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -1745,12 +1745,17 @@ public final class NotificationPanelViewController extends PanelViewController { } if (mQsExpanded) { - mQsExpandImmediate = true; + setQsExpandImmediate(true); setShowShelfOnly(true); } super.collapse(delayed, speedUpFactor); } + private void setQsExpandImmediate(boolean expandImmediate) { + mQsExpandImmediate = expandImmediate; + mPanelEventsEmitter.notifyExpandImmediateChange(expandImmediate); + } + private void setShowShelfOnly(boolean shelfOnly) { mNotificationStackScrollLayoutController.setShouldShowShelfOnly( shelfOnly && !mSplitShadeEnabled); @@ -1803,7 +1808,7 @@ public final class NotificationPanelViewController extends PanelViewController { public void expandWithQs() { if (isQsExpansionEnabled()) { - mQsExpandImmediate = true; + setQsExpandImmediate(true); setShowShelfOnly(true); } if (mSplitShadeEnabled && isOnKeyguard()) { @@ -2132,7 +2137,7 @@ public final class NotificationPanelViewController extends PanelViewController { if (mTwoFingerQsExpandPossible && isOpenQsEvent(event) && event.getY(event.getActionIndex()) < mStatusBarMinHeight) { mMetricsLogger.count(COUNTER_PANEL_OPEN_QS, 1); - mQsExpandImmediate = true; + setQsExpandImmediate(true); setShowShelfOnly(true); requestPanelHeightUpdate(); @@ -3334,7 +3339,7 @@ public final class NotificationPanelViewController extends PanelViewController { } else { setListening(true); } - mQsExpandImmediate = false; + setQsExpandImmediate(false); setShowShelfOnly(false); mTwoFingerQsExpandPossible = false; updateTrackingHeadsUp(null); @@ -3392,7 +3397,7 @@ public final class NotificationPanelViewController extends PanelViewController { super.onTrackingStarted(); mScrimController.onTrackingStarted(); if (mQsFullyExpanded) { - mQsExpandImmediate = true; + setQsExpandImmediate(true); setShowShelfOnly(true); } mNotificationStackScrollLayoutController.onPanelTrackingStarted(); @@ -4959,7 +4964,7 @@ public final class NotificationPanelViewController extends PanelViewController { // to locked will trigger this event and we're not actually in the process of opening // the shade, lockscreen is just always expanded if (mSplitShadeEnabled && !isOnKeyguard()) { - mQsExpandImmediate = true; + setQsExpandImmediate(true); } mCentralSurfaces.makeExpandedVisible(false); } @@ -5026,5 +5031,11 @@ public final class NotificationPanelViewController extends PanelViewController { cb.onPanelCollapsingChanged(isCollapsing); } } + + private void notifyExpandImmediateChange(boolean expandImmediateEnabled) { + for (NotifPanelEvents.Listener cb : mListeners) { + cb.onExpandImmediateChanged(expandImmediateEnabled); + } + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt index d65b6b31a26d..369913d1ea73 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt @@ -16,7 +16,6 @@ package com.android.systemui.media -import org.mockito.Mockito.`when` as whenever import android.graphics.Rect import android.testing.AndroidTestingRunner import android.testing.TestableLooper @@ -30,6 +29,7 @@ import com.android.systemui.controls.controller.ControlsControllerImplTest.Compa import com.android.systemui.dreams.DreamOverlayStateController import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.shade.testing.FakeNotifPanelEvents import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.SysuiStatusBarStateController @@ -50,10 +50,11 @@ import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyLong import org.mockito.Captor import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.times import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` +import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit @SmallTest @@ -61,32 +62,19 @@ import org.mockito.junit.MockitoJUnit @TestableLooper.RunWithLooper class MediaHierarchyManagerTest : SysuiTestCase() { - @Mock - private lateinit var lockHost: MediaHost - @Mock - private lateinit var qsHost: MediaHost - @Mock - private lateinit var qqsHost: MediaHost - @Mock - private lateinit var bypassController: KeyguardBypassController - @Mock - private lateinit var keyguardStateController: KeyguardStateController - @Mock - private lateinit var statusBarStateController: SysuiStatusBarStateController - @Mock - private lateinit var notificationLockscreenUserManager: NotificationLockscreenUserManager - @Mock - private lateinit var mediaCarouselController: MediaCarouselController - @Mock - private lateinit var mediaCarouselScrollHandler: MediaCarouselScrollHandler - @Mock - private lateinit var wakefulnessLifecycle: WakefulnessLifecycle - @Mock - private lateinit var keyguardViewController: KeyguardViewController - @Mock - private lateinit var uniqueObjectHostView: UniqueObjectHostView - @Mock - private lateinit var dreamOverlayStateController: DreamOverlayStateController + @Mock private lateinit var lockHost: MediaHost + @Mock private lateinit var qsHost: MediaHost + @Mock private lateinit var qqsHost: MediaHost + @Mock private lateinit var bypassController: KeyguardBypassController + @Mock private lateinit var keyguardStateController: KeyguardStateController + @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController + @Mock private lateinit var notificationLockscreenUserManager: NotificationLockscreenUserManager + @Mock private lateinit var mediaCarouselController: MediaCarouselController + @Mock private lateinit var mediaCarouselScrollHandler: MediaCarouselScrollHandler + @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle + @Mock private lateinit var keyguardViewController: KeyguardViewController + @Mock private lateinit var uniqueObjectHostView: UniqueObjectHostView + @Mock private lateinit var dreamOverlayStateController: DreamOverlayStateController @Captor private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)> @Captor @@ -97,6 +85,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { private lateinit var mediaHiearchyManager: MediaHierarchyManager private lateinit var mediaFrame: ViewGroup private val configurationController = FakeConfigurationController() + private val notifPanelEvents = FakeNotifPanelEvents() @Before fun setup() { @@ -111,10 +100,12 @@ class MediaHierarchyManagerTest : SysuiTestCase() { bypassController, mediaCarouselController, notificationLockscreenUserManager, + keyguardViewController, + dreamOverlayStateController, configurationController, wakefulnessLifecycle, - keyguardViewController, - dreamOverlayStateController) + notifPanelEvents, + ) verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture()) verify(statusBarStateController).addCallback(statusBarCallback.capture()) setupHost(lockHost, MediaHierarchyManager.LOCATION_LOCKSCREEN, LOCKSCREEN_TOP) @@ -212,6 +203,25 @@ class MediaHierarchyManagerTest : SysuiTestCase() { } @Test + fun calculateTransformationType_notOnLockscreen_returnsTransition() { + expandQS() + + val transformType = mediaHiearchyManager.calculateTransformationType() + + assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_TRANSITION) + } + + @Test + fun calculateTransformationType_onLockscreen_returnsTransition() { + goToLockscreen() + expandQS() + + val transformType = mediaHiearchyManager.calculateTransformationType() + + assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_FADE) + } + + @Test fun calculateTransformationType_onLockShade_inSplitShade_goingToFullShade_returnsTransition() { enableSplitShade() goToLockscreen() @@ -295,6 +305,18 @@ class MediaHierarchyManagerTest : SysuiTestCase() { } @Test + fun isCurrentlyInGuidedTransformation_hostsVisible_expandImmediateEnabled_returnsFalse() { + notifPanelEvents.changeExpandImmediate(expandImmediate = true) + goToLockscreen() + enterGuidedTransformation() + whenever(lockHost.visible).thenReturn(true) + whenever(qsHost.visible).thenReturn(true) + whenever(qqsHost.visible).thenReturn(true) + + assertThat(mediaHiearchyManager.isCurrentlyInGuidedTransformation()).isFalse() + } + + @Test fun isCurrentlyInGuidedTransformation_hostNotVisible_returnsTrue() { goToLockscreen() enterGuidedTransformation() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/testing/FakeNotifPanelEvents.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/testing/FakeNotifPanelEvents.kt new file mode 100644 index 000000000000..d05213877232 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/testing/FakeNotifPanelEvents.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shade.testing + +import com.android.systemui.shade.NotifPanelEvents + +/** Fake implementation of [NotifPanelEvents] for testing. */ +class FakeNotifPanelEvents : NotifPanelEvents { + + private val listeners = mutableListOf<NotifPanelEvents.Listener>() + + override fun registerListener(listener: NotifPanelEvents.Listener) { + listeners.add(listener) + } + + override fun unregisterListener(listener: NotifPanelEvents.Listener) { + listeners.remove(listener) + } + + fun changeExpandImmediate(expandImmediate: Boolean) { + listeners.forEach { it.onExpandImmediateChanged(expandImmediate) } + } +} |