summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt28
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java72
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt260
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java41
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpManagerPhone.java772
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpModule.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java605
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt13
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt6
11 files changed, 752 insertions, 1064 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index 9613f76c2b48..2c488e3a7242 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -28,7 +28,6 @@ import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.statusbar.NotificationRemoteInputManager
import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
-import com.android.systemui.statusbar.notification.HeadsUpManagerPhone
import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -52,6 +51,7 @@ import com.android.systemui.statusbar.notification.interruption.NotificationInte
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider
import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback
import com.android.systemui.statusbar.phone.NotificationGroupTestHelper
+import com.android.systemui.statusbar.policy.BaseHeadsUpManager
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.FakeExecutor
@@ -101,7 +101,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
private val notifPipeline: NotifPipeline = mock()
private val logger = HeadsUpCoordinatorLogger(logcatLogBuffer(), verbose = true)
- private val headsUpManager: HeadsUpManagerPhone = mock()
+ private val headsUpManager: BaseHeadsUpManager = mock()
private val headsUpViewBinder: HeadsUpViewBinder = mock()
private val visualInterruptionDecisionProvider: VisualInterruptionDecisionProvider = mock()
private val remoteInputManager: NotificationRemoteInputManager = mock()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
index 9a862fc6a18f..c5eed7365d92 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
@@ -24,11 +24,19 @@ import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
+import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.provider.visualStabilityProvider
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
+import com.android.systemui.statusbar.phone.keyguardBypassController
import com.android.systemui.statusbar.policy.HeadsUpManagerTestUtil.createFullScreenIntentEntry
+import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.util.settings.FakeGlobalSettings
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -48,6 +56,7 @@ import org.mockito.junit.MockitoRule
@RunWith(AndroidJUnit4::class)
@EnableFlags(NotificationThrottleHun.FLAG_NAME)
class AvalancheControllerTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
// For creating mocks
@get:Rule var rule: MockitoRule = MockitoJUnit.rule()
@@ -61,7 +70,6 @@ class AvalancheControllerTest : SysuiTestCase() {
@Mock private val mAccessibilityMgr: AccessibilityManagerWrapper? = null
private val mUiEventLoggerFake = UiEventLoggerFake()
@Mock private lateinit var mHeadsUpManagerLogger: HeadsUpManagerLogger
-
@Mock private lateinit var mBgHandler: Handler
private val mLogger = Mockito.spy(HeadsUpManagerLogger(logcatLogBuffer()))
@@ -76,26 +84,33 @@ class AvalancheControllerTest : SysuiTestCase() {
Mockito.`when`(
mAccessibilityMgr!!.getRecommendedTimeoutMillis(
ArgumentMatchers.anyInt(),
- ArgumentMatchers.anyInt()
+ ArgumentMatchers.anyInt(),
)
)
.then { i: InvocationOnMock -> i.getArgument(0) }
// Initialize AvalancheController and TestableHeadsUpManager during setUp instead of
// declaration, where mocks are null
- mAvalancheController = AvalancheController(dumpManager, mUiEventLoggerFake,
- mHeadsUpManagerLogger, mBgHandler)
+ mAvalancheController =
+ AvalancheController(dumpManager, mUiEventLoggerFake, mHeadsUpManagerLogger, mBgHandler)
testableHeadsUpManager =
TestableHeadsUpManager(
mContext,
mLogger,
+ kosmos.statusBarStateController,
+ kosmos.keyguardBypassController,
+ GroupMembershipManagerImpl(),
+ kosmos.visualStabilityProvider,
+ kosmos.configurationController,
mExecutor,
mGlobalSettings,
mSystemClock,
mAccessibilityMgr,
mUiEventLoggerFake,
- mAvalancheController
+ JavaAdapter(kosmos.testScope),
+ kosmos.shadeInteractor,
+ mAvalancheController,
)
}
@@ -270,7 +285,6 @@ class AvalancheControllerTest : SysuiTestCase() {
assertThat(mAvalancheController.headsUpEntryShowing).isEqualTo(nextEntry)
}
-
@Test
fun testDelete_deleteSecondToLastEntry_showingEntryKeyBecomesPreviousHunKey() {
mAvalancheController.previousHunKey = ""
@@ -305,7 +319,7 @@ class AvalancheControllerTest : SysuiTestCase() {
mAvalancheController.delete(showingEntry, runnableMock!!, "testLabel")
// Next entry is shown
- assertThat(mAvalancheController.previousHunKey).isEqualTo("");
+ assertThat(mAvalancheController.previousHunKey).isEqualTo("")
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
index 89aa670e5f0c..abb3e6e0c1f2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
@@ -35,6 +35,8 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
+
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Person;
@@ -49,15 +51,21 @@ import androidx.test.filters.SmallTest;
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.res.R;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
+import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.FakeGlobalSettings;
import com.android.systemui.util.time.FakeSystemClock;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -73,7 +81,10 @@ import java.util.List;
@SmallTest
@TestableLooper.RunWithLooper
@RunWith(ParameterizedAndroidJunit4.class)
+// TODO(b/378142453): Merge this with BaseHeadsUpManagerTest.
public class BaseHeadsUpManagerTest extends SysuiTestCase {
+ protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+
@Rule
public MockitoRule rule = MockitoJUnit.rule();
@@ -85,6 +96,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
@Mock private Handler mBgHandler;
@Mock private DumpManager dumpManager;
+ @Mock private ShadeInteractor mShadeInteractor;
private AvalancheController mAvalancheController;
@Mock private AccessibilityManagerWrapper mAccessibilityMgr;
@@ -108,8 +120,22 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
}
private BaseHeadsUpManager createHeadsUpManager() {
- return new TestableHeadsUpManager(mContext, mLogger, mExecutor, mGlobalSettings,
- mSystemClock, mAccessibilityMgr, mUiEventLoggerFake, mAvalancheController);
+ return new TestableHeadsUpManager(
+ mContext,
+ mLogger,
+ mKosmos.getStatusBarStateController(),
+ mKosmos.getKeyguardBypassController(),
+ new GroupMembershipManagerImpl(),
+ mKosmos.getVisualStabilityProvider(),
+ mKosmos.getConfigurationController(),
+ mExecutor,
+ mGlobalSettings,
+ mSystemClock,
+ mAccessibilityMgr,
+ mUiEventLoggerFake,
+ new JavaAdapter(mKosmos.getTestScope()),
+ mShadeInteractor,
+ mAvalancheController);
}
private NotificationEntry createStickyEntry(int id) {
@@ -152,6 +178,8 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
super.SysuiSetup();
mAvalancheController = new AvalancheController(dumpManager, mUiEventLoggerFake, mLogger,
mBgHandler);
+ when(mShadeInteractor.isAnyExpanded()).thenReturn(MutableStateFlow(true));
+ when(mKosmos.getKeyguardBypassController().getBypassEnabled()).thenReturn(false);
}
@Test
@@ -298,46 +326,6 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
verify(mLogger, times(1)).logNotificationActuallyRemoved(eq(notifEntry));
}
- @Test
- public void testShouldHeadsUpBecomePinned_hasFSI_notUnpinned_true() {
- final BaseHeadsUpManager hum = createHeadsUpManager();
- final NotificationEntry notifEntry =
- HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext);
-
- // Add notifEntry to ANM mAlertEntries map and make it NOT unpinned
- hum.showNotification(notifEntry);
-
- final BaseHeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(
- notifEntry.getKey());
- headsUpEntry.mWasUnpinned = false;
-
- assertTrue(hum.shouldHeadsUpBecomePinned(notifEntry));
- }
-
- @Test
- public void testShouldHeadsUpBecomePinned_wasUnpinned_false() {
- final BaseHeadsUpManager hum = createHeadsUpManager();
- final NotificationEntry notifEntry =
- HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext);
-
- // Add notifEntry to ANM mAlertEntries map and make it unpinned
- hum.showNotification(notifEntry);
-
- final BaseHeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(
- notifEntry.getKey());
- headsUpEntry.mWasUnpinned = true;
-
- assertFalse(hum.shouldHeadsUpBecomePinned(notifEntry));
- }
-
- @Test
- public void testShouldHeadsUpBecomePinned_noFSI_false() {
- final BaseHeadsUpManager hum = createHeadsUpManager();
- final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-
- assertFalse(hum.shouldHeadsUpBecomePinned(entry));
- }
-
@Test
public void testShowNotification_autoDismissesIncludingTouchAcceptanceDelay() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
index 1915e8ede7e7..8ebdbaaa6bf0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
@@ -15,7 +15,6 @@
*/
package com.android.systemui.statusbar.policy
-import android.content.Context
import android.os.Handler
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
@@ -26,27 +25,19 @@ import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.FakeStatusBarStateController
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
-import com.android.systemui.statusbar.notification.HeadsUpManagerPhone
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.testKosmos
-import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.concurrency.mockExecutorHandler
import com.android.systemui.util.kotlin.JavaAdapter
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.settings.GlobalSettings
-import com.android.systemui.util.time.SystemClock
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -78,7 +69,7 @@ class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManager
@Mock private lateinit var mVSProvider: VisualStabilityProvider
- @Mock private lateinit var mStatusBarStateController: StatusBarStateController
+ val statusBarStateController = FakeStatusBarStateController()
@Mock private lateinit var mBypassController: KeyguardBypassController
@@ -97,61 +88,16 @@ class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManager
@Mock private lateinit var mBgHandler: Handler
- private class TestableHeadsUpManagerPhone(
- context: Context,
- headsUpManagerLogger: HeadsUpManagerLogger,
- groupManager: GroupMembershipManager,
- visualStabilityProvider: VisualStabilityProvider,
- statusBarStateController: StatusBarStateController,
- keyguardBypassController: KeyguardBypassController,
- configurationController: ConfigurationController,
- globalSettings: GlobalSettings,
- systemClock: SystemClock,
- executor: DelayableExecutor,
- accessibilityManagerWrapper: AccessibilityManagerWrapper,
- uiEventLogger: UiEventLogger,
- javaAdapter: JavaAdapter,
- shadeInteractor: ShadeInteractor,
- avalancheController: AvalancheController
- ) :
- HeadsUpManagerPhone(
- context,
- headsUpManagerLogger,
- statusBarStateController,
- keyguardBypassController,
- groupManager,
- visualStabilityProvider,
- configurationController,
- mockExecutorHandler(executor),
- globalSettings,
- systemClock,
- executor,
- accessibilityManagerWrapper,
- uiEventLogger,
- javaAdapter,
- shadeInteractor,
- avalancheController
- ) {
- init {
- mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME
- mAutoDismissTime = TEST_AUTO_DISMISS_TIME
- }
-
- /** Wrapper for [BaseHeadsUpManager.shouldHeadsUpBecomePinned] for testing */
- fun shouldHeadsUpBecomePinnedWrapper(entry: NotificationEntry): Boolean {
- return shouldHeadsUpBecomePinned(entry)
- }
- }
-
- private fun createHeadsUpManagerPhone(): HeadsUpManagerPhone {
- return TestableHeadsUpManagerPhone(
+ private fun createHeadsUpManagerPhone(): BaseHeadsUpManager {
+ return BaseHeadsUpManager(
mContext,
mHeadsUpManagerLogger,
+ statusBarStateController,
+ mBypassController,
mGroupManager,
mVSProvider,
- mStatusBarStateController,
- mBypassController,
mConfigurationController,
+ mockExecutorHandler(mExecutor),
mGlobalSettings,
mSystemClock,
mExecutor,
@@ -159,20 +105,22 @@ class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManager
mUiEventLogger,
mJavaAdapter,
mShadeInteractor,
- mAvalancheController
+ mAvalancheController,
)
}
@Before
fun setUp() {
whenever(mShadeInteractor.isAnyExpanded).thenReturn(MutableStateFlow(false))
+ whenever(mShadeInteractor.isQsExpanded).thenReturn(MutableStateFlow(false))
+ whenever(mBypassController.bypassEnabled).thenReturn(false)
whenever(mVSProvider.isReorderingAllowed).thenReturn(true)
val accessibilityMgr =
mDependency.injectMockDependency(AccessibilityManagerWrapper::class.java)
whenever(
accessibilityMgr.getRecommendedTimeoutMillis(
ArgumentMatchers.anyInt(),
- ArgumentMatchers.anyInt()
+ ArgumentMatchers.anyInt(),
)
)
.thenReturn(TEST_AUTO_DISMISS_TIME)
@@ -205,7 +153,7 @@ class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManager
hmp.removeNotification(
entry.key,
/* releaseImmediately= */ false,
- /* reason= */ "swipe out"
+ /* reason= */ "swipe out",
)
Assert.assertTrue(removedImmediately)
Assert.assertFalse(hmp.isHeadsUpEntry(entry.key))
@@ -245,6 +193,7 @@ class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManager
mSystemClock.advanceTime((TEST_AUTO_DISMISS_TIME + hmp.mExtensionTime / 2).toLong())
Assert.assertTrue(hmp.isHeadsUpEntry(entry.key))
}
+
@Test
@EnableFlags(NotificationThrottleHun.FLAG_NAME)
fun testShowNotification_removeWhenReorderingAllowedTrue() {
@@ -253,7 +202,7 @@ class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManager
val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
hmp.showNotification(notifEntry)
- assertThat(hmp.mEntriesToRemoveWhenReorderingAllowed.contains(notifEntry)).isTrue();
+ assertThat(hmp.mEntriesToRemoveWhenReorderingAllowed.contains(notifEntry)).isTrue()
}
@Test
@@ -264,7 +213,7 @@ class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManager
val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
hmp.showNotification(notifEntry)
- assertThat(notifEntry.isSeenInShade).isTrue();
+ assertThat(notifEntry.isSeenInShade).isTrue()
}
@Test
@@ -275,197 +224,136 @@ class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManager
val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
hmp.showNotification(notifEntry)
- assertThat(notifEntry.isSeenInShade).isFalse();
+ assertThat(notifEntry.isSeenInShade).isFalse()
}
@Test
+ fun testShouldHeadsUpBecomePinned_noFSI_false() =
+ testScope.runTest {
+ val hum = createHeadsUpManagerPhone()
+ statusBarStateController.setState(StatusBarState.KEYGUARD)
+
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+
+ Assert.assertFalse(hum.shouldHeadsUpBecomePinned(entry))
+ }
+
+ @Test
+ fun testShouldHeadsUpBecomePinned_hasFSI_notUnpinned_true() =
+ testScope.runTest {
+ val hum = createHeadsUpManagerPhone()
+ statusBarStateController.setState(StatusBarState.KEYGUARD)
+
+ val notifEntry =
+ HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext)
+
+ // Add notifEntry to ANM mAlertEntries map and make it NOT unpinned
+ hum.showNotification(notifEntry)
+
+ val headsUpEntry = hum.getHeadsUpEntry(notifEntry.key)
+ headsUpEntry!!.mWasUnpinned = false
+
+ Assert.assertTrue(hum.shouldHeadsUpBecomePinned(notifEntry))
+ }
+
+ @Test
+ fun testShouldHeadsUpBecomePinned_wasUnpinned_false() =
+ testScope.runTest {
+ val hum = createHeadsUpManagerPhone()
+ statusBarStateController.setState(StatusBarState.KEYGUARD)
+
+ val notifEntry =
+ HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext)
+
+ // Add notifEntry to ANM mAlertEntries map and make it unpinned
+ hum.showNotification(notifEntry)
+
+ val headsUpEntry = hum.getHeadsUpEntry(notifEntry.key)
+ headsUpEntry!!.mWasUnpinned = true
+
+ Assert.assertFalse(hum.shouldHeadsUpBecomePinned(notifEntry))
+ }
+
+ @Test
fun shouldHeadsUpBecomePinned_shadeNotExpanded_true() =
testScope.runTest {
// GIVEN
- val statusBarStateController = FakeStatusBarStateController()
whenever(mShadeInteractor.isAnyFullyExpanded).thenReturn(MutableStateFlow(false))
- val hmp =
- TestableHeadsUpManagerPhone(
- mContext,
- mHeadsUpManagerLogger,
- mGroupManager,
- mVSProvider,
- statusBarStateController,
- mBypassController,
- mConfigurationController,
- mGlobalSettings,
- mSystemClock,
- mExecutor,
- mAccessibilityManagerWrapper,
- mUiEventLogger,
- mJavaAdapter,
- mShadeInteractor,
- mAvalancheController
- )
+ val hmp = createHeadsUpManagerPhone()
val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
statusBarStateController.setState(StatusBarState.SHADE)
runCurrent()
// THEN
- Assert.assertTrue(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+ Assert.assertTrue(hmp.shouldHeadsUpBecomePinned(entry))
}
@Test
fun shouldHeadsUpBecomePinned_shadeLocked_false() =
testScope.runTest {
// GIVEN
- val statusBarStateController = FakeStatusBarStateController()
- val hmp =
- TestableHeadsUpManagerPhone(
- mContext,
- mHeadsUpManagerLogger,
- mGroupManager,
- mVSProvider,
- statusBarStateController,
- mBypassController,
- mConfigurationController,
- mGlobalSettings,
- mSystemClock,
- mExecutor,
- mAccessibilityManagerWrapper,
- mUiEventLogger,
- mJavaAdapter,
- mShadeInteractor,
- mAvalancheController
- )
+ val hmp = createHeadsUpManagerPhone()
val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
statusBarStateController.setState(StatusBarState.SHADE_LOCKED)
runCurrent()
// THEN
- Assert.assertFalse(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+ Assert.assertFalse(hmp.shouldHeadsUpBecomePinned(entry))
}
@Test
fun shouldHeadsUpBecomePinned_shadeUnknown_false() =
testScope.runTest {
// GIVEN
- val statusBarStateController = FakeStatusBarStateController()
- val hmp =
- TestableHeadsUpManagerPhone(
- mContext,
- mHeadsUpManagerLogger,
- mGroupManager,
- mVSProvider,
- statusBarStateController,
- mBypassController,
- mConfigurationController,
- mGlobalSettings,
- mSystemClock,
- mExecutor,
- mAccessibilityManagerWrapper,
- mUiEventLogger,
- mJavaAdapter,
- mShadeInteractor,
- mAvalancheController
- )
+ val hmp = createHeadsUpManagerPhone()
val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
statusBarStateController.setState(1207)
runCurrent()
// THEN
- Assert.assertFalse(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+ Assert.assertFalse(hmp.shouldHeadsUpBecomePinned(entry))
}
@Test
fun shouldHeadsUpBecomePinned_keyguardWithBypassOn_true() =
testScope.runTest {
// GIVEN
- val statusBarStateController = FakeStatusBarStateController()
whenever(mBypassController.bypassEnabled).thenReturn(true)
- val hmp =
- TestableHeadsUpManagerPhone(
- mContext,
- mHeadsUpManagerLogger,
- mGroupManager,
- mVSProvider,
- statusBarStateController,
- mBypassController,
- mConfigurationController,
- mGlobalSettings,
- mSystemClock,
- mExecutor,
- mAccessibilityManagerWrapper,
- mUiEventLogger,
- mJavaAdapter,
- mShadeInteractor,
- mAvalancheController
- )
+ val hmp = createHeadsUpManagerPhone()
val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
statusBarStateController.setState(StatusBarState.KEYGUARD)
runCurrent()
// THEN
- Assert.assertTrue(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+ Assert.assertTrue(hmp.shouldHeadsUpBecomePinned(entry))
}
@Test
fun shouldHeadsUpBecomePinned_keyguardWithBypassOff_false() =
testScope.runTest {
// GIVEN
- val statusBarStateController = FakeStatusBarStateController()
whenever(mBypassController.bypassEnabled).thenReturn(false)
- val hmp =
- TestableHeadsUpManagerPhone(
- mContext,
- mHeadsUpManagerLogger,
- mGroupManager,
- mVSProvider,
- statusBarStateController,
- mBypassController,
- mConfigurationController,
- mGlobalSettings,
- mSystemClock,
- mExecutor,
- mAccessibilityManagerWrapper,
- mUiEventLogger,
- mJavaAdapter,
- mShadeInteractor,
- mAvalancheController
- )
+ val hmp = createHeadsUpManagerPhone()
val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
statusBarStateController.setState(StatusBarState.KEYGUARD)
runCurrent()
// THEN
- Assert.assertFalse(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+ Assert.assertFalse(hmp.shouldHeadsUpBecomePinned(entry))
}
@Test
fun shouldHeadsUpBecomePinned_shadeExpanded_false() =
testScope.runTest {
// GIVEN
- val statusBarStateController = FakeStatusBarStateController()
whenever(mShadeInteractor.isAnyExpanded).thenReturn(MutableStateFlow(true))
- val hmp =
- TestableHeadsUpManagerPhone(
- mContext,
- mHeadsUpManagerLogger,
- mGroupManager,
- mVSProvider,
- statusBarStateController,
- mBypassController,
- mConfigurationController,
- mGlobalSettings,
- mSystemClock,
- mExecutor,
- mAccessibilityManagerWrapper,
- mUiEventLogger,
- mJavaAdapter,
- mShadeInteractor,
- mAvalancheController
- )
+ val hmp = createHeadsUpManagerPhone()
val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
statusBarStateController.setState(StatusBarState.SHADE)
runCurrent()
// THEN
- Assert.assertFalse(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+ Assert.assertFalse(hmp.shouldHeadsUpBecomePinned(entry))
}
companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
index 3efabd743141..59987f413ad6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.policy;
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler;
import static org.mockito.Mockito.spy;
@@ -28,8 +27,14 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.GlobalSettings;
import com.android.systemui.util.time.SystemClock;
@@ -37,16 +42,39 @@ class TestableHeadsUpManager extends BaseHeadsUpManager {
private HeadsUpEntry mLastCreatedEntry;
- TestableHeadsUpManager(Context context,
+ TestableHeadsUpManager(
+ Context context,
HeadsUpManagerLogger logger,
+ StatusBarStateController statusBarStateController,
+ KeyguardBypassController bypassController,
+ GroupMembershipManager groupMembershipManager,
+ VisualStabilityProvider visualStabilityProvider,
+ ConfigurationController configurationController,
DelayableExecutor executor,
GlobalSettings globalSettings,
SystemClock systemClock,
AccessibilityManagerWrapper accessibilityManagerWrapper,
UiEventLogger uiEventLogger,
+ JavaAdapter javaAdapter,
+ ShadeInteractor shadeInteractor,
AvalancheController avalancheController) {
- super(context, logger, mockExecutorHandler(executor), globalSettings, systemClock,
- executor, accessibilityManagerWrapper, uiEventLogger, avalancheController);
+ super(
+ context,
+ logger,
+ statusBarStateController,
+ bypassController,
+ groupMembershipManager,
+ visualStabilityProvider,
+ configurationController,
+ mockExecutorHandler(executor),
+ globalSettings,
+ systemClock,
+ executor,
+ accessibilityManagerWrapper,
+ uiEventLogger,
+ javaAdapter,
+ shadeInteractor,
+ avalancheController);
mTouchAcceptanceDelay = BaseHeadsUpManagerTest.TEST_TOUCH_ACCEPTANCE_TIME;
mMinimumDisplayTime = BaseHeadsUpManagerTest.TEST_MINIMUM_DISPLAY_TIME;
@@ -61,11 +89,6 @@ class TestableHeadsUpManager extends BaseHeadsUpManager {
return mLastCreatedEntry;
}
- @Override
- public int getContentFlag() {
- return FLAG_CONTENT_VIEW_CONTRACTED;
- }
-
// The following are only implemented by HeadsUpManagerPhone. If you need them, use that.
@Override
public void addHeadsUpPhoneListener(@NonNull OnHeadsUpPhoneListenerChange listener) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpManagerPhone.java
deleted file mode 100644
index 0299ebcdf043..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpManagerPhone.java
+++ /dev/null
@@ -1,772 +0,0 @@
-/*
- * Copyright (C) 2024 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.statusbar.notification;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Region;
-import android.os.Handler;
-import android.util.ArrayMap;
-import android.util.Pools;
-
-import androidx.collection.ArraySet;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.policy.SystemBarUtils;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlag;
-import com.android.systemui.shade.domain.interactor.ShadeInteractor;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener;
-import com.android.systemui.statusbar.notification.collection.provider.OnReorderingBannedListener;
-import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
-import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
-import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository;
-import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
-import com.android.systemui.statusbar.phone.ExpandHeadsUpOnInlineReply;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
-import com.android.systemui.statusbar.policy.AnimationStateHandler;
-import com.android.systemui.statusbar.policy.AvalancheController;
-import com.android.systemui.statusbar.policy.BaseHeadsUpManager;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
-import com.android.systemui.statusbar.policy.OnHeadsUpPhoneListenerChange;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.kotlin.JavaAdapter;
-import com.android.systemui.util.settings.GlobalSettings;
-import com.android.systemui.util.time.SystemClock;
-
-import kotlinx.coroutines.flow.Flow;
-import kotlinx.coroutines.flow.MutableStateFlow;
-import kotlinx.coroutines.flow.StateFlow;
-import kotlinx.coroutines.flow.StateFlowKt;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-import java.util.Stack;
-
-import javax.inject.Inject;
-
-/** A implementation of HeadsUpManager for phone. */
-@SysUISingleton
-public class HeadsUpManagerPhone extends BaseHeadsUpManager implements
- HeadsUpRepository, OnHeadsUpChangedListener {
- private static final String TAG = "HeadsUpManagerPhone";
-
- @VisibleForTesting
- public final int mExtensionTime;
- private final KeyguardBypassController mBypassController;
- private final GroupMembershipManager mGroupMembershipManager;
- private final List<OnHeadsUpPhoneListenerChange> mHeadsUpPhoneListeners = new ArrayList<>();
- private final VisualStabilityProvider mVisualStabilityProvider;
-
- private AvalancheController mAvalancheController;
-
- // TODO(b/328393698) move the topHeadsUpRow logic to an interactor
- private final MutableStateFlow<HeadsUpRowRepository> mTopHeadsUpRow =
- StateFlowKt.MutableStateFlow(null);
- private final MutableStateFlow<Set<HeadsUpRowRepository>> mHeadsUpNotificationRows =
- StateFlowKt.MutableStateFlow(new HashSet<>());
- private final MutableStateFlow<Boolean> mHeadsUpAnimatingAway =
- StateFlowKt.MutableStateFlow(false);
- private boolean mReleaseOnExpandFinish;
- private boolean mTrackingHeadsUp;
- private final HashSet<String> mSwipedOutKeys = new HashSet<>();
- private final HashSet<NotificationEntry> mEntriesToRemoveAfterExpand = new HashSet<>();
- @VisibleForTesting
- public final ArraySet<NotificationEntry> mEntriesToRemoveWhenReorderingAllowed
- = new ArraySet<>();
- private boolean mIsShadeOrQsExpanded;
- private boolean mIsQsExpanded;
- private int mStatusBarState;
- private AnimationStateHandler mAnimationStateHandler;
-
- private int mHeadsUpInset;
-
- // Used for determining the region for touch interaction
- private final Region mTouchableRegion = new Region();
-
- private final Pools.Pool<HeadsUpEntryPhone> mEntryPool = new Pools.Pool<HeadsUpEntryPhone>() {
- private Stack<HeadsUpEntryPhone> mPoolObjects = new Stack<>();
-
- @Override
- public HeadsUpEntryPhone acquire() {
- NotificationThrottleHun.assertInLegacyMode();
- if (!mPoolObjects.isEmpty()) {
- return mPoolObjects.pop();
- }
- return new HeadsUpEntryPhone();
- }
-
- @Override
- public boolean release(@NonNull HeadsUpEntryPhone instance) {
- NotificationThrottleHun.assertInLegacyMode();
- mPoolObjects.push(instance);
- return true;
- }
- };
-
- ///////////////////////////////////////////////////////////////////////////////////////////////
- // Constructor:
- @Inject
- public HeadsUpManagerPhone(
- @NonNull final Context context,
- HeadsUpManagerLogger logger,
- StatusBarStateController statusBarStateController,
- KeyguardBypassController bypassController,
- GroupMembershipManager groupMembershipManager,
- VisualStabilityProvider visualStabilityProvider,
- ConfigurationController configurationController,
- @Main Handler handler,
- GlobalSettings globalSettings,
- SystemClock systemClock,
- @Main DelayableExecutor executor,
- AccessibilityManagerWrapper accessibilityManagerWrapper,
- UiEventLogger uiEventLogger,
- JavaAdapter javaAdapter,
- ShadeInteractor shadeInteractor,
- AvalancheController avalancheController) {
- super(context, logger, handler, globalSettings, systemClock, executor,
- accessibilityManagerWrapper, uiEventLogger, avalancheController);
- Resources resources = mContext.getResources();
- mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time);
- statusBarStateController.addCallback(mStatusBarStateListener);
- mBypassController = bypassController;
- mGroupMembershipManager = groupMembershipManager;
- mVisualStabilityProvider = visualStabilityProvider;
- mAvalancheController = avalancheController;
- updateResources();
- configurationController.addCallback(new ConfigurationController.ConfigurationListener() {
- @Override
- public void onDensityOrFontScaleChanged() {
- updateResources();
- }
-
- @Override
- public void onThemeChanged() {
- updateResources();
- }
- });
- javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(),
- this::onShadeOrQsExpanded);
- if (SceneContainerFlag.isEnabled()) {
- javaAdapter.alwaysCollectFlow(shadeInteractor.isQsExpanded(),
- this::onQsExpanded);
- }
- if (NotificationThrottleHun.isEnabled()) {
- mVisualStabilityProvider.addPersistentReorderingBannedListener(
- mOnReorderingBannedListener);
- mVisualStabilityProvider.addPersistentReorderingAllowedListener(
- mOnReorderingAllowedListener);
- }
- }
-
- public void setAnimationStateHandler(AnimationStateHandler handler) {
- mAnimationStateHandler = handler;
- }
-
- private void updateResources() {
- Resources resources = mContext.getResources();
- mHeadsUpInset = SystemBarUtils.getStatusBarHeight(mContext)
- + resources.getDimensionPixelSize(R.dimen.heads_up_status_bar_padding);
- }
-
- ///////////////////////////////////////////////////////////////////////////////////////////////
- // Public methods:
-
- /**
- * Add a listener to receive callbacks {@link #setHeadsUpAnimatingAway(boolean)}
- */
- @Override
- public void addHeadsUpPhoneListener(OnHeadsUpPhoneListenerChange listener) {
- mHeadsUpPhoneListeners.add(listener);
- }
-
- /**
- * Gets the touchable region needed for heads up notifications. Returns null if no touchable
- * region is required (ie: no heads up notification currently exists).
- */
- // TODO(b/347007367): With scene container enabled this method may report outdated regions
- @Override
- public @Nullable Region getTouchableRegion() {
- NotificationEntry topEntry = getTopEntry();
-
- // This call could be made in an inconsistent state while the pinnedMode hasn't been
- // updated yet, but callbacks leading out of the headsUp manager, querying it. Let's
- // therefore also check if the topEntry is null.
- if (!hasPinnedHeadsUp() || topEntry == null) {
- return null;
- } else {
- if (topEntry.rowIsChildInGroup()) {
- final NotificationEntry groupSummary =
- mGroupMembershipManager.getGroupSummary(topEntry);
- if (groupSummary != null) {
- topEntry = groupSummary;
- }
- }
- ExpandableNotificationRow topRow = topEntry.getRow();
- int[] tmpArray = new int[2];
- topRow.getLocationOnScreen(tmpArray);
- int minX = tmpArray[0];
- int maxX = tmpArray[0] + topRow.getWidth();
- int height = topRow.getIntrinsicHeight();
- final boolean stretchToTop = tmpArray[1] <= mHeadsUpInset;
- mTouchableRegion.set(minX, stretchToTop ? 0 : tmpArray[1], maxX, tmpArray[1] + height);
- return mTouchableRegion;
- }
- }
-
- /**
- * Decides whether a click is invalid for a notification, i.e it has not been shown long enough
- * that a user might have consciously clicked on it.
- *
- * @param key the key of the touched notification
- * @return whether the touch is invalid and should be discarded
- */
- @Override
- public boolean shouldSwallowClick(@NonNull String key) {
- BaseHeadsUpManager.HeadsUpEntry entry = getHeadsUpEntry(key);
- return entry != null && mSystemClock.elapsedRealtime() < entry.mPostTime;
- }
-
- @Override
- public void releaseAfterExpansion() {
- if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
- onExpandingFinished();
- }
-
- public void onExpandingFinished() {
- if (mReleaseOnExpandFinish) {
- releaseAllImmediately();
- mReleaseOnExpandFinish = false;
- } else {
- for (NotificationEntry entry: getAllEntries().toList()) {
- entry.setSeenInShade(true);
- }
- for (NotificationEntry entry : mEntriesToRemoveAfterExpand) {
- if (isHeadsUpEntry(entry.getKey())) {
- // Maybe the heads-up was removed already
- removeEntry(entry.getKey(), "onExpandingFinished");
- }
- }
- }
- mEntriesToRemoveAfterExpand.clear();
- }
-
- /**
- * Sets the tracking-heads-up flag. If the flag is true, HeadsUpManager doesn't remove the entry
- * from the list even after a Heads Up Notification is gone.
- */
- public void setTrackingHeadsUp(boolean trackingHeadsUp) {
- mTrackingHeadsUp = trackingHeadsUp;
- }
-
- private void onShadeOrQsExpanded(Boolean isExpanded) {
- if (isExpanded != mIsShadeOrQsExpanded) {
- mIsShadeOrQsExpanded = isExpanded;
- if (!SceneContainerFlag.isEnabled() && isExpanded) {
- mHeadsUpAnimatingAway.setValue(false);
- }
- }
- }
-
- private void onQsExpanded(Boolean isQsExpanded) {
- if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
- if (isQsExpanded != mIsQsExpanded) mIsQsExpanded = isQsExpanded;
- }
-
- /**
- * Set that we are exiting the headsUp pinned mode, but some notifications might still be
- * animating out. This is used to keep the touchable regions in a reasonable state.
- */
- @Override
- public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
- if (headsUpAnimatingAway != mHeadsUpAnimatingAway.getValue()) {
- for (OnHeadsUpPhoneListenerChange listener : mHeadsUpPhoneListeners) {
- listener.onHeadsUpAnimatingAwayStateChanged(headsUpAnimatingAway);
- }
- mHeadsUpAnimatingAway.setValue(headsUpAnimatingAway);
- }
- }
-
- @Override
- public void unpinAll(boolean userUnPinned) {
- super.unpinAll(userUnPinned);
- }
-
- /**
- * Notifies that a remote input textbox in notification gets active or inactive.
- *
- * @param entry The entry of the target notification.
- * @param remoteInputActive True to notify active, False to notify inactive.
- */
- public void setRemoteInputActive(
- @NonNull NotificationEntry entry, boolean remoteInputActive) {
- HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(entry.getKey());
- if (headsUpEntry != null && headsUpEntry.mRemoteInputActive != remoteInputActive) {
- headsUpEntry.mRemoteInputActive = remoteInputActive;
- if (ExpandHeadsUpOnInlineReply.isEnabled() && remoteInputActive) {
- headsUpEntry.mRemoteInputActivatedAtLeastOnce = true;
- }
- if (remoteInputActive) {
- headsUpEntry.cancelAutoRemovalCallbacks("setRemoteInputActive(true)");
- } else {
- headsUpEntry.updateEntry(false /* updatePostTime */, "setRemoteInputActive(false)");
- }
- onEntryUpdated(headsUpEntry);
- }
- }
-
- /**
- * Sets whether an entry's guts are exposed and therefore it should stick in the heads up
- * area if it's pinned until it's hidden again.
- */
- public void setGutsShown(@NonNull NotificationEntry entry, boolean gutsShown) {
- HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.getKey());
- if (!(headsUpEntry instanceof HeadsUpEntryPhone)) return;
- HeadsUpEntryPhone headsUpEntryPhone = (HeadsUpEntryPhone)headsUpEntry;
- if (entry.isRowPinned() || !gutsShown) {
- headsUpEntryPhone.setGutsShownPinned(gutsShown);
- }
- }
-
- /**
- * Extends the lifetime of the currently showing pulsing notification so that the pulse lasts
- * longer.
- */
- public void extendHeadsUp() {
- HeadsUpEntryPhone topEntry = getTopHeadsUpEntryPhone();
- if (topEntry == null) {
- return;
- }
- topEntry.extendPulse();
- }
-
- ///////////////////////////////////////////////////////////////////////////////////////////////
- // HeadsUpManager public methods overrides and overloads:
-
- @Override
- public boolean isTrackingHeadsUp() {
- return mTrackingHeadsUp;
- }
-
- @Override
- public void snooze() {
- super.snooze();
- mReleaseOnExpandFinish = true;
- }
-
- public void addSwipedOutNotification(@NonNull String key) {
- mSwipedOutKeys.add(key);
- }
-
- @Override
- public boolean removeNotification(@NonNull String key, boolean releaseImmediately,
- boolean animate, @NonNull String reason) {
- if (animate) {
- return removeNotification(key, releaseImmediately,
- "removeNotification(animate: true), reason: " + reason);
- } else {
- mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
- final boolean removed = removeNotification(key, releaseImmediately,
- "removeNotification(animate: false), reason: " + reason);
- mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(true);
- return removed;
- }
- }
-
- ///////////////////////////////////////////////////////////////////////////////////////////////
- // Dumpable overrides:
-
- @Override
- public void dump(PrintWriter pw, String[] args) {
- pw.println("HeadsUpManagerPhone state:");
- dumpInternal(pw, args);
- }
-
- ///////////////////////////////////////////////////////////////////////////////////////////////
- // OnReorderingAllowedListener:
-
- private final OnReorderingAllowedListener mOnReorderingAllowedListener = () -> {
- if (NotificationThrottleHun.isEnabled()) {
- mAvalancheController.setEnableAtRuntime(true);
- if (mEntriesToRemoveWhenReorderingAllowed.isEmpty()) {
- return;
- }
- }
- mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
- for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) {
- if (entry != null && isHeadsUpEntry(entry.getKey())) {
- // Maybe the heads-up was removed already
- removeEntry(entry.getKey(), "mOnReorderingAllowedListener");
- }
- }
- mEntriesToRemoveWhenReorderingAllowed.clear();
- mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(true);
- };
-
- private final OnReorderingBannedListener mOnReorderingBannedListener = () -> {
- if (mAvalancheController != null) {
- // In open shade the first HUN is pinned, and visual stability logic prevents us from
- // unpinning this first HUN as long as the shade remains open. AvalancheController only
- // shows the next HUN when the currently showing HUN is unpinned, so we must disable
- // throttling here so that the incoming HUN stream is not forever paused. This is reset
- // when reorder becomes allowed.
- mAvalancheController.setEnableAtRuntime(false);
-
- // Note that we cannot do the above when
- // 1) The remove runnable runs because its delay means it may not run before shade close
- // 2) Reordering is allowed again (when shade closes) because the HUN appear animation
- // will have started by then
- }
- };
-
- ///////////////////////////////////////////////////////////////////////////////////////////////
- // HeadsUpManager utility (protected) methods overrides:
-
- @NonNull
- @Override
- protected HeadsUpEntry createHeadsUpEntry(NotificationEntry entry) {
- if (NotificationThrottleHun.isEnabled()) {
- return new HeadsUpEntryPhone(entry);
- } else {
- HeadsUpEntryPhone headsUpEntry = mEntryPool.acquire();
- headsUpEntry.setEntry(entry);
- return headsUpEntry;
- }
- }
-
- @Override
- protected void onEntryAdded(HeadsUpEntry headsUpEntry) {
- super.onEntryAdded(headsUpEntry);
- updateTopHeadsUpFlow();
- updateHeadsUpFlow();
- }
-
- @Override
- protected void onEntryUpdated(HeadsUpEntry headsUpEntry) {
- super.onEntryUpdated(headsUpEntry);
- // no need to update the list here
- updateTopHeadsUpFlow();
- }
-
- @Override
- protected void onEntryRemoved(HeadsUpEntry headsUpEntry) {
- super.onEntryRemoved(headsUpEntry);
- if (!NotificationThrottleHun.isEnabled()) {
- mEntryPool.release((HeadsUpEntryPhone) headsUpEntry);
- }
- updateTopHeadsUpFlow();
- updateHeadsUpFlow();
- if (NotificationThrottleHun.isEnabled()) {
- if (headsUpEntry.mEntry != null) {
- if (mEntriesToRemoveWhenReorderingAllowed.contains(headsUpEntry.mEntry)) {
- mEntriesToRemoveWhenReorderingAllowed.remove(headsUpEntry.mEntry);
- }
- }
- }
- }
-
- private void updateTopHeadsUpFlow() {
- mTopHeadsUpRow.setValue((HeadsUpRowRepository) getTopHeadsUpEntry());
- }
-
- private void updateHeadsUpFlow() {
- mHeadsUpNotificationRows.setValue(new HashSet<>(getHeadsUpEntryPhoneMap().values()));
- }
-
- @Override
- protected boolean shouldHeadsUpBecomePinned(NotificationEntry entry) {
- boolean pin = mStatusBarState == StatusBarState.SHADE && !mIsShadeOrQsExpanded;
- if (SceneContainerFlag.isEnabled()) {
- pin |= mIsQsExpanded;
- }
- if (mBypassController.getBypassEnabled()) {
- pin |= mStatusBarState == StatusBarState.KEYGUARD;
- }
- return pin || super.shouldHeadsUpBecomePinned(entry);
- }
-
- @Override
- protected void dumpInternal(PrintWriter pw, String[] args) {
- super.dumpInternal(pw, args);
- pw.print(" mBarState=");
- pw.println(mStatusBarState);
- pw.print(" mTouchableRegion=");
- pw.println(mTouchableRegion);
- }
-
- ///////////////////////////////////////////////////////////////////////////////////////////////
- // Private utility methods:
-
- @NonNull
- private ArrayMap<String, HeadsUpEntryPhone> getHeadsUpEntryPhoneMap() {
- //noinspection unchecked
- return (ArrayMap<String, HeadsUpEntryPhone>) ((ArrayMap) mHeadsUpEntryMap);
- }
-
- @Nullable
- private HeadsUpEntryPhone getHeadsUpEntryPhone(@NonNull String key) {
- return (HeadsUpEntryPhone) mHeadsUpEntryMap.get(key);
- }
-
- @Nullable
- private HeadsUpEntryPhone getTopHeadsUpEntryPhone() {
- if (SceneContainerFlag.isEnabled()) {
- return (HeadsUpEntryPhone) mTopHeadsUpRow.getValue();
- } else {
- return (HeadsUpEntryPhone) getTopHeadsUpEntry();
- }
- }
-
- @Override
- public boolean canRemoveImmediately(@NonNull String key) {
- if (mSwipedOutKeys.contains(key)) {
- // We always instantly dismiss views being manually swiped out.
- mSwipedOutKeys.remove(key);
- return true;
- }
-
- HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(key);
- HeadsUpEntryPhone topEntry = getTopHeadsUpEntryPhone();
-
- return headsUpEntry == null || headsUpEntry != topEntry || super.canRemoveImmediately(key);
- }
-
- @Override
- @NonNull
- public Flow<HeadsUpRowRepository> getTopHeadsUpRow() {
- return mTopHeadsUpRow;
- }
-
- @Override
- @NonNull
- public Flow<Set<HeadsUpRowRepository>> getActiveHeadsUpRows() {
- return mHeadsUpNotificationRows;
- }
-
- @Override
- @NonNull
- public StateFlow<Boolean> isHeadsUpAnimatingAway() {
- return mHeadsUpAnimatingAway;
- }
-
- @Override
- public boolean isHeadsUpAnimatingAwayValue() {
- return mHeadsUpAnimatingAway.getValue();
- }
-
- ///////////////////////////////////////////////////////////////////////////////////////////////
- // HeadsUpEntryPhone:
-
- protected class HeadsUpEntryPhone extends BaseHeadsUpManager.HeadsUpEntry implements
- HeadsUpRowRepository {
-
- private boolean mGutsShownPinned;
- private final MutableStateFlow<Boolean> mIsPinned = StateFlowKt.MutableStateFlow(false);
-
- /**
- * If the time this entry has been on was extended
- */
- private boolean extended;
-
- @Override
- public boolean isSticky() {
- return super.isSticky() || mGutsShownPinned;
- }
-
- public HeadsUpEntryPhone() {
- super();
- }
-
- public HeadsUpEntryPhone(NotificationEntry entry) {
- super(entry);
- }
-
- @Override
- @NonNull
- public String getKey() {
- return requireEntry().getKey();
- }
-
- @Override
- @NonNull
- public StateFlow<Boolean> isPinned() {
- return mIsPinned;
- }
-
- @Override
- protected void setRowPinned(boolean pinned) {
- // TODO(b/327624082): replace this super call with a ViewBinder
- super.setRowPinned(pinned);
- mIsPinned.setValue(pinned);
- }
-
- @Override
- protected void setEntry(@androidx.annotation.NonNull NotificationEntry entry,
- @androidx.annotation.Nullable Runnable removeRunnable) {
- super.setEntry(entry, removeRunnable);
-
- if (NotificationThrottleHun.isEnabled()) {
- mEntriesToRemoveWhenReorderingAllowed.add(entry);
- if (!mVisualStabilityProvider.isReorderingAllowed()) {
- entry.setSeenInShade(true);
- }
- }
- }
-
- @Override
- protected Runnable createRemoveRunnable(NotificationEntry entry) {
- return () -> {
- if (!NotificationThrottleHun.isEnabled()
- && !mVisualStabilityProvider.isReorderingAllowed()
- // We don't want to allow reordering while pulsing, but headsup need to
- // time out anyway
- && !entry.showingPulsing()) {
- mEntriesToRemoveWhenReorderingAllowed.add(entry);
- mVisualStabilityProvider.addTemporaryReorderingAllowedListener(
- mOnReorderingAllowedListener);
- } else if (mTrackingHeadsUp) {
- mEntriesToRemoveAfterExpand.add(entry);
- mLogger.logRemoveEntryAfterExpand(entry);
- } else if (mVisualStabilityProvider.isReorderingAllowed()
- || entry.showingPulsing()) {
- removeEntry(entry.getKey(), "createRemoveRunnable");
- }
- };
- }
-
- @Override
- public void updateEntry(boolean updatePostTime, String reason) {
- super.updateEntry(updatePostTime, reason);
-
- if (mEntriesToRemoveAfterExpand.contains(mEntry)) {
- mEntriesToRemoveAfterExpand.remove(mEntry);
- }
- if (!NotificationThrottleHun.isEnabled()) {
- if (mEntriesToRemoveWhenReorderingAllowed.contains(mEntry)) {
- mEntriesToRemoveWhenReorderingAllowed.remove(mEntry);
- }
- }
- }
-
- @Override
- public void setExpanded(boolean expanded) {
- if (this.mExpanded == expanded) {
- return;
- }
-
- this.mExpanded = expanded;
- if (expanded) {
- cancelAutoRemovalCallbacks("setExpanded(true)");
- } else {
- updateEntry(false /* updatePostTime */, "setExpanded(false)");
- }
- }
-
- public void setGutsShownPinned(boolean gutsShownPinned) {
- if (mGutsShownPinned == gutsShownPinned) {
- return;
- }
-
- mGutsShownPinned = gutsShownPinned;
- if (gutsShownPinned) {
- cancelAutoRemovalCallbacks("setGutsShownPinned(true)");
- } else {
- updateEntry(false /* updatePostTime */, "setGutsShownPinned(false)");
- }
- }
-
- @Override
- public void reset() {
- super.reset();
- mGutsShownPinned = false;
- extended = false;
- }
-
- private void extendPulse() {
- if (!extended) {
- extended = true;
- updateEntry(false, "extendPulse()");
- }
- }
-
- @Override
- protected long calculateFinishTime() {
- return super.calculateFinishTime() + (extended ? mExtensionTime : 0);
- }
-
- @Override
- @NonNull
- public Object getElementKey() {
- return requireEntry().getRow();
- }
-
- private NotificationEntry requireEntry() {
- /* check if */ SceneContainerFlag.isUnexpectedlyInLegacyMode();
- return Objects.requireNonNull(mEntry);
- }
- }
-
- private final StateListener mStatusBarStateListener = new StateListener() {
- @Override
- public void onStateChanged(int newState) {
- boolean wasKeyguard = mStatusBarState == StatusBarState.KEYGUARD;
- boolean isKeyguard = newState == StatusBarState.KEYGUARD;
- mStatusBarState = newState;
-
- if (wasKeyguard && !isKeyguard && mBypassController.getBypassEnabled()) {
- ArrayList<String> keysToRemove = new ArrayList<>();
- for (HeadsUpEntry entry : getHeadsUpEntryList()) {
- if (entry.mEntry != null && entry.mEntry.isBubble() && !entry.isSticky()) {
- keysToRemove.add(entry.mEntry.getKey());
- }
- }
- for (String key : keysToRemove) {
- removeEntry(key, "mStatusBarStateListener");
- }
- }
- }
-
- @Override
- public void onDozingChanged(boolean isDozing) {
- if (!isDozing) {
- // Let's make sure all huns we got while dozing time out within the normal timeout
- // duration. Otherwise they could get stuck for a very long time
- for (HeadsUpEntry entry : getHeadsUpEntryList()) {
- entry.updateEntry(true /* updatePostTime */, "onDozingChanged(false)");
- }
- }
- }
- };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
index 63c9e8be9ead..cf4fb25f638e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
@@ -16,16 +16,11 @@
package com.android.systemui.statusbar.notification.data
import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository
-import com.android.systemui.statusbar.notification.HeadsUpManagerPhone
+import com.android.systemui.statusbar.policy.BaseHeadsUpManager
import dagger.Binds
import dagger.Module
-@Module(
- includes =
- [
- NotificationSettingsRepositoryModule::class,
- ]
-)
+@Module(includes = [NotificationSettingsRepositoryModule::class])
interface NotificationDataLayerModule {
- @Binds fun bindHeadsUpNotificationRepository(impl: HeadsUpManagerPhone): HeadsUpRepository
+ @Binds fun bindHeadsUpNotificationRepository(impl: BaseHeadsUpManager): HeadsUpRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpModule.kt
index 7919c84152b2..83551e9b8294 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpModule.kt
@@ -17,12 +17,12 @@
package com.android.systemui.statusbar.phone
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.notification.HeadsUpManagerPhone
+import com.android.systemui.statusbar.policy.BaseHeadsUpManager
import com.android.systemui.statusbar.policy.HeadsUpManager
import dagger.Binds
import dagger.Module
@Module
interface HeadsUpModule {
- @Binds @SysUISingleton fun bindsHeadsUpManager(hum: HeadsUpManagerPhone): HeadsUpManager
+ @Binds @SysUISingleton fun bindsHeadsUpManager(hum: BaseHeadsUpManager): HeadsUpManager
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index 4284c1911b33..f6f567f17077 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -16,33 +16,48 @@
package com.android.systemui.statusbar.policy;
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
import android.content.Context;
import android.content.res.Resources;
import android.database.ContentObserver;
+import android.graphics.Region;
import android.os.Handler;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
+import android.util.Pools;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
+import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.EventLogTags;
+import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
+import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener;
+import com.android.systemui.statusbar.notification.collection.provider.OnReorderingBannedListener;
+import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository;
+import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
import com.android.systemui.statusbar.phone.ExpandHeadsUpOnInlineReply;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.util.ListenerSet;
import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.GlobalSettings;
import com.android.systemui.util.time.SystemClock;
@@ -50,14 +65,27 @@ import org.jetbrains.annotations.NotNull;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.Stack;
import java.util.stream.Stream;
+import javax.inject.Inject;
+
+import kotlinx.coroutines.flow.Flow;
+import kotlinx.coroutines.flow.MutableStateFlow;
+import kotlinx.coroutines.flow.StateFlow;
+import kotlinx.coroutines.flow.StateFlowKt;
+
/**
* A manager which handles heads up notifications which is a special mode where
* they simply peek from the top of the screen.
*/
-public abstract class BaseHeadsUpManager implements HeadsUpManager {
+@SysUISingleton
+public class BaseHeadsUpManager
+ implements HeadsUpManager, HeadsUpRepository, OnHeadsUpChangedListener {
private static final String TAG = "BaseHeadsUpManager";
private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms";
@@ -74,7 +102,11 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
private final AccessibilityManagerWrapper mAccessibilityMgr;
private final UiEventLogger mUiEventLogger;
- private final AvalancheController mAvalancheController;
+ private AvalancheController mAvalancheController;
+ private final KeyguardBypassController mBypassController;
+ private final GroupMembershipManager mGroupMembershipManager;
+ private final List<OnHeadsUpPhoneListenerChange> mHeadsUpPhoneListeners = new ArrayList<>();
+ private final VisualStabilityProvider mVisualStabilityProvider;
protected final SystemClock mSystemClock;
protected final ArrayMap<String, HeadsUpEntry> mHeadsUpEntryMap = new ArrayMap<>();
@@ -84,6 +116,53 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
protected int mAutoDismissTime;
protected DelayableExecutor mExecutor;
+ @VisibleForTesting
+ public final int mExtensionTime;
+
+ // TODO(b/328393698) move the topHeadsUpRow logic to an interactor
+ private final MutableStateFlow<HeadsUpRowRepository> mTopHeadsUpRow =
+ StateFlowKt.MutableStateFlow(null);
+ private final MutableStateFlow<Set<HeadsUpRowRepository>> mHeadsUpNotificationRows =
+ StateFlowKt.MutableStateFlow(new HashSet<>());
+ private final MutableStateFlow<Boolean> mHeadsUpAnimatingAway =
+ StateFlowKt.MutableStateFlow(false);
+ private final HashSet<String> mSwipedOutKeys = new HashSet<>();
+ private final HashSet<NotificationEntry> mEntriesToRemoveAfterExpand = new HashSet<>();
+ @VisibleForTesting
+ public final ArraySet<NotificationEntry> mEntriesToRemoveWhenReorderingAllowed
+ = new ArraySet<>();
+
+ private boolean mReleaseOnExpandFinish;
+ private boolean mTrackingHeadsUp;
+ private boolean mIsShadeOrQsExpanded;
+ private boolean mIsQsExpanded;
+ private int mStatusBarState;
+ private AnimationStateHandler mAnimationStateHandler;
+ private int mHeadsUpInset;
+
+ // Used for determining the region for touch interaction
+ private final Region mTouchableRegion = new Region();
+
+ private final Pools.Pool<HeadsUpEntry> mEntryPool = new Pools.Pool<>() {
+ private final Stack<HeadsUpEntry> mPoolObjects = new Stack<>();
+
+ @Override
+ public HeadsUpEntry acquire() {
+ NotificationThrottleHun.assertInLegacyMode();
+ if (!mPoolObjects.isEmpty()) {
+ return mPoolObjects.pop();
+ }
+ return new HeadsUpEntry();
+ }
+
+ @Override
+ public boolean release(@NonNull HeadsUpEntry instance) {
+ NotificationThrottleHun.assertInLegacyMode();
+ mPoolObjects.push(instance);
+ return true;
+ }
+ };
+
/**
* Enum entry for notification peek logged from this class.
*/
@@ -100,14 +179,23 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
}
}
- public BaseHeadsUpManager(@NonNull final Context context,
+ @Inject
+ public BaseHeadsUpManager(
+ @NonNull final Context context,
HeadsUpManagerLogger logger,
+ StatusBarStateController statusBarStateController,
+ KeyguardBypassController bypassController,
+ GroupMembershipManager groupMembershipManager,
+ VisualStabilityProvider visualStabilityProvider,
+ ConfigurationController configurationController,
@Main Handler handler,
GlobalSettings globalSettings,
SystemClock systemClock,
@Main DelayableExecutor executor,
AccessibilityManagerWrapper accessibilityManagerWrapper,
UiEventLogger uiEventLogger,
+ JavaAdapter javaAdapter,
+ ShadeInteractor shadeInteractor,
AvalancheController avalancheController) {
mLogger = logger;
mExecutor = executor;
@@ -117,12 +205,16 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
mUiEventLogger = uiEventLogger;
mAvalancheController = avalancheController;
mAvalancheController.setBaseEntryMapStr(this::getEntryMapStr);
+ mBypassController = bypassController;
+ mGroupMembershipManager = groupMembershipManager;
+ mVisualStabilityProvider = visualStabilityProvider;
Resources resources = context.getResources();
mMinimumDisplayTime = NotificationThrottleHun.isEnabled()
? 500 : resources.getInteger(R.integer.heads_up_notification_minimum_time);
mStickyForSomeTimeAutoDismissTime = resources.getInteger(
R.integer.sticky_heads_up_notification_time);
mAutoDismissTime = resources.getInteger(R.integer.heads_up_notification_decay);
+ mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time);
mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay);
mSnoozedPackages = new ArrayMap<>();
int defaultSnoozeLengthMs =
@@ -145,11 +237,38 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
globalSettings.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS),
/* notifyForDescendants = */ false,
settingsObserver);
+
+ statusBarStateController.addCallback(mStatusBarStateListener);
+ updateResources();
+ configurationController.addCallback(new ConfigurationController.ConfigurationListener() {
+ @Override
+ public void onDensityOrFontScaleChanged() {
+ updateResources();
+ }
+
+ @Override
+ public void onThemeChanged() {
+ updateResources();
+ }
+ });
+ javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(),
+ this::onShadeOrQsExpanded);
+ if (SceneContainerFlag.isEnabled()) {
+ javaAdapter.alwaysCollectFlow(shadeInteractor.isQsExpanded(),
+ this::onQsExpanded);
+ }
+ if (NotificationThrottleHun.isEnabled()) {
+ mVisualStabilityProvider.addPersistentReorderingBannedListener(
+ mOnReorderingBannedListener);
+ mVisualStabilityProvider.addPersistentReorderingAllowedListener(
+ mOnReorderingAllowedListener);
+ }
}
/**
* Adds an OnHeadUpChangedListener to observe events.
*/
+ @Override
public void addListener(@NonNull OnHeadsUpChangedListener listener) {
mListeners.addIfAbsent(listener);
}
@@ -157,11 +276,31 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
/**
* Removes the OnHeadUpChangedListener from the observer list.
*/
+ @Override
public void removeListener(@NonNull OnHeadsUpChangedListener listener) {
mListeners.remove(listener);
}
/**
+ * Add a listener to receive callbacks {@link #setHeadsUpAnimatingAway(boolean)}
+ */
+ @Override
+ public void addHeadsUpPhoneListener(@NonNull OnHeadsUpPhoneListenerChange listener) {
+ mHeadsUpPhoneListeners.add(listener);
+ }
+
+ @Override
+ public void setAnimationStateHandler(@NonNull AnimationStateHandler handler) {
+ mAnimationStateHandler = handler;
+ }
+
+ private void updateResources() {
+ Resources resources = mContext.getResources();
+ mHeadsUpInset = SystemBarUtils.getStatusBarHeight(mContext)
+ + resources.getDimensionPixelSize(R.dimen.heads_up_status_bar_padding);
+ }
+
+ /**
* Called when posting a new notification that should appear on screen.
* Adds the notification to be managed.
* @param entry entry to show
@@ -188,14 +327,24 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
mAvalancheController.update(headsUpEntry, runnable, "showNotification");
}
- /**
- * Try to remove the notification. May not succeed if the notification has not been shown long
- * enough and needs to be kept around.
- * @param key the key of the notification to remove
- * @param releaseImmediately force a remove regardless of earliest removal time
- * @param reason reason for removing the notification
- * @return true if notification is removed, false otherwise
- */
+ @Override
+ public boolean removeNotification(
+ @NonNull String key,
+ boolean releaseImmediately,
+ boolean animate,
+ @NonNull String reason) {
+ if (animate) {
+ return removeNotification(key, releaseImmediately,
+ "removeNotification(animate: true), reason: " + reason);
+ } else {
+ mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
+ final boolean removed = removeNotification(key, releaseImmediately,
+ "removeNotification(animate: false), reason: " + reason);
+ mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(true);
+ return removed;
+ }
+ }
+
@Override
public boolean removeNotification(@NotNull String key, boolean releaseImmediately,
@NonNull String reason) {
@@ -261,6 +410,42 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
}
}
+ @Override
+ public void setTrackingHeadsUp(boolean trackingHeadsUp) {
+ mTrackingHeadsUp = trackingHeadsUp;
+ }
+
+ @Override
+ public boolean shouldSwallowClick(@NonNull String key) {
+ BaseHeadsUpManager.HeadsUpEntry entry = getHeadsUpEntry(key);
+ return entry != null && mSystemClock.elapsedRealtime() < entry.mPostTime;
+ }
+
+ @Override
+ public void releaseAfterExpansion() {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+ onExpandingFinished();
+ }
+
+ @Override
+ public void onExpandingFinished() {
+ if (mReleaseOnExpandFinish) {
+ releaseAllImmediately();
+ mReleaseOnExpandFinish = false;
+ } else {
+ for (NotificationEntry entry : getAllEntries().toList()) {
+ entry.setSeenInShade(true);
+ }
+ for (NotificationEntry entry : mEntriesToRemoveAfterExpand) {
+ if (isHeadsUpEntry(entry.getKey())) {
+ // Maybe the heads-up was removed already
+ removeEntry(entry.getKey(), "onExpandingFinished");
+ }
+ }
+ }
+ mEntriesToRemoveAfterExpand.clear();
+ }
+
/**
* Clears all managed notifications.
*/
@@ -339,10 +524,19 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
return 0;
}
+ @VisibleForTesting
protected boolean shouldHeadsUpBecomePinned(@NonNull NotificationEntry entry) {
- if (entry == null) {
- return false;
+ boolean pin = mStatusBarState == StatusBarState.SHADE && !mIsShadeOrQsExpanded;
+ if (SceneContainerFlag.isEnabled()) {
+ pin |= mIsQsExpanded;
+ }
+ if (mBypassController.getBypassEnabled()) {
+ pin |= mStatusBarState == StatusBarState.KEYGUARD;
+ }
+ if (pin) {
+ return true;
}
+
final HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.getKey());
if (headsUpEntry == null) {
// This should not happen since shouldHeadsUpBecomePinned is always called after adding
@@ -392,10 +586,6 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
}
}
- public @InflationFlag int getContentFlag() {
- return FLAG_CONTENT_VIEW_HEADS_UP;
- }
-
/**
* Manager-specific logic that should occur when an entry is added.
* @param headsUpEntry entry added
@@ -410,6 +600,8 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
for (OnHeadsUpChangedListener listener : mListeners) {
listener.onHeadsUpStateChanged(entry, true);
}
+ updateTopHeadsUpFlow();
+ updateHeadsUpFlow();
}
/**
@@ -466,11 +658,60 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
for (OnHeadsUpChangedListener listener : mListeners) {
listener.onHeadsUpStateChanged(entry, false);
}
+ if (!NotificationThrottleHun.isEnabled()) {
+ mEntryPool.release(headsUpEntry);
+ }
+ updateTopHeadsUpFlow();
+ updateHeadsUpFlow();
+ if (NotificationThrottleHun.isEnabled()) {
+ if (headsUpEntry.mEntry != null) {
+ if (mEntriesToRemoveWhenReorderingAllowed.contains(headsUpEntry.mEntry)) {
+ mEntriesToRemoveWhenReorderingAllowed.remove(headsUpEntry.mEntry);
+ }
+ }
+ }
+ }
+
+ private void updateTopHeadsUpFlow() {
+ mTopHeadsUpRow.setValue((HeadsUpRowRepository) getTopHeadsUpEntry());
+ }
+
+ private void updateHeadsUpFlow() {
+ mHeadsUpNotificationRows.setValue(new HashSet<>(getHeadsUpEntryPhoneMap().values()));
+ }
+
+ @Override
+ @NonNull
+ public Flow<HeadsUpRowRepository> getTopHeadsUpRow() {
+ return mTopHeadsUpRow;
+ }
+
+ @Override
+ @NonNull
+ public Flow<Set<HeadsUpRowRepository>> getActiveHeadsUpRows() {
+ return mHeadsUpNotificationRows;
+ }
+
+ @Override
+ @NonNull
+ public StateFlow<Boolean> isHeadsUpAnimatingAway() {
+ return mHeadsUpAnimatingAway;
+ }
+
+ @Override
+ public boolean isHeadsUpAnimatingAwayValue() {
+ return mHeadsUpAnimatingAway.getValue();
+ }
+
+ @NonNull
+ private ArrayMap<String, HeadsUpEntry> getHeadsUpEntryPhoneMap() {
+ return mHeadsUpEntryMap;
}
/**
* Called to notify the listeners that the HUN animating away animation has ended.
*/
+ @Override
public void onEntryAnimatingAwayEnded(@NonNull NotificationEntry entry) {
for (OnHeadsUpChangedListener listener : mListeners) {
listener.onHeadsUpAnimatingAwayEnded(entry);
@@ -484,6 +725,8 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
* @param headsUpEntry entry updated
*/
protected void onEntryUpdated(HeadsUpEntry headsUpEntry) {
+ // no need to update the list here
+ updateTopHeadsUpFlow();
}
protected void updatePinnedMode() {
@@ -521,6 +764,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
/**
* Snoozes all current Heads Up Notifications.
*/
+ @Override
public void snooze() {
List<String> keySet = new ArrayList<>(mHeadsUpEntryMap.keySet());
keySet.addAll(mAvalancheController.getWaitingKeys());
@@ -534,6 +778,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
mLogger.logPackageSnoozed(snoozeKey);
mSnoozedPackages.put(snoozeKey, mSystemClock.elapsedRealtime() + mSnoozeLengthMs);
}
+ mReleaseOnExpandFinish = true;
}
@NonNull
@@ -541,6 +786,11 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
return user + "," + packageName;
}
+ @Override
+ public void addSwipedOutNotification(@NonNull String key) {
+ mSwipedOutKeys.add(key);
+ }
+
@Nullable
protected HeadsUpEntry getHeadsUpEntry(@NonNull String key) {
if (mHeadsUpEntryMap.containsKey(key)) {
@@ -597,6 +847,59 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
}
@Override
+ public @Nullable Region getTouchableRegion() {
+ NotificationEntry topEntry = getTopEntry();
+
+ // This call could be made in an inconsistent state while the pinnedMode hasn't been
+ // updated yet, but callbacks leading out of the headsUp manager, querying it. Let's
+ // therefore also check if the topEntry is null.
+ if (!hasPinnedHeadsUp() || topEntry == null) {
+ return null;
+ } else {
+ if (topEntry.rowIsChildInGroup()) {
+ final NotificationEntry groupSummary =
+ mGroupMembershipManager.getGroupSummary(topEntry);
+ if (groupSummary != null) {
+ topEntry = groupSummary;
+ }
+ }
+ ExpandableNotificationRow topRow = topEntry.getRow();
+ int[] tmpArray = new int[2];
+ topRow.getLocationOnScreen(tmpArray);
+ int minX = tmpArray[0];
+ int maxX = tmpArray[0] + topRow.getWidth();
+ int height = topRow.getIntrinsicHeight();
+ final boolean stretchToTop = tmpArray[1] <= mHeadsUpInset;
+ mTouchableRegion.set(minX, stretchToTop ? 0 : tmpArray[1], maxX, tmpArray[1] + height);
+ return mTouchableRegion;
+ }
+ }
+
+ @Override
+ public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
+ if (headsUpAnimatingAway != mHeadsUpAnimatingAway.getValue()) {
+ for (OnHeadsUpPhoneListenerChange listener : mHeadsUpPhoneListeners) {
+ listener.onHeadsUpAnimatingAwayStateChanged(headsUpAnimatingAway);
+ }
+ mHeadsUpAnimatingAway.setValue(headsUpAnimatingAway);
+ }
+ }
+
+ private void onShadeOrQsExpanded(Boolean isExpanded) {
+ if (isExpanded != mIsShadeOrQsExpanded) {
+ mIsShadeOrQsExpanded = isExpanded;
+ if (!SceneContainerFlag.isEnabled() && isExpanded) {
+ mHeadsUpAnimatingAway.setValue(false);
+ }
+ }
+ }
+
+ private void onQsExpanded(Boolean isQsExpanded) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+ if (isQsExpanded != mIsQsExpanded) mIsQsExpanded = isQsExpanded;
+ }
+
+ @Override
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("HeadsUpManager state:");
dumpInternal(pw, args);
@@ -616,6 +919,10 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
pw.print(" "); pw.print(mSnoozedPackages.valueAt(i));
pw.print(", "); pw.println(mSnoozedPackages.keyAt(i));
}
+ pw.print(" mBarState=");
+ pw.println(mStatusBarState);
+ pw.print(" mTouchableRegion=");
+ pw.println(mTouchableRegion);
}
/**
@@ -639,6 +946,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
* Unpins all pinned Heads Up Notifications.
* @param userUnPinned The unpinned action is trigger by user real operation.
*/
+ @Override
public void unpinAll(boolean userUnPinned) {
for (String key : mHeadsUpEntryMap.keySet()) {
HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
@@ -662,13 +970,59 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
}
}
- /**
- * Returns the value of the tracking-heads-up flag. See the doc of {@code setTrackingHeadsUp} as
- * well.
- */
+ @Override
+ public void setRemoteInputActive(
+ @NonNull NotificationEntry entry, boolean remoteInputActive) {
+ HeadsUpEntry headsUpEntry = getHeadsUpEntryPhone(entry.getKey());
+ if (headsUpEntry != null && headsUpEntry.mRemoteInputActive != remoteInputActive) {
+ headsUpEntry.mRemoteInputActive = remoteInputActive;
+ if (ExpandHeadsUpOnInlineReply.isEnabled() && remoteInputActive) {
+ headsUpEntry.mRemoteInputActivatedAtLeastOnce = true;
+ }
+ if (remoteInputActive) {
+ headsUpEntry.cancelAutoRemovalCallbacks("setRemoteInputActive(true)");
+ } else {
+ headsUpEntry.updateEntry(false /* updatePostTime */, "setRemoteInputActive(false)");
+ }
+ onEntryUpdated(headsUpEntry);
+ }
+ }
+
+ @Nullable
+ private HeadsUpEntry getHeadsUpEntryPhone(@NonNull String key) {
+ return mHeadsUpEntryMap.get(key);
+ }
+
+ @Override
+ public void setGutsShown(@NonNull NotificationEntry entry, boolean gutsShown) {
+ HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.getKey());
+ if (headsUpEntry == null) return;
+ if (entry.isRowPinned() || !gutsShown) {
+ headsUpEntry.setGutsShownPinned(gutsShown);
+ }
+ }
+
+ @Override
+ public void extendHeadsUp() {
+ HeadsUpEntry topEntry = getTopHeadsUpEntryPhone();
+ if (topEntry == null) {
+ return;
+ }
+ topEntry.extendPulse();
+ }
+
+ @Nullable
+ private HeadsUpEntry getTopHeadsUpEntryPhone() {
+ if (SceneContainerFlag.isEnabled()) {
+ return (HeadsUpEntry) mTopHeadsUpRow.getValue();
+ } else {
+ return getTopHeadsUpEntry();
+ }
+ }
+
+ @Override
public boolean isTrackingHeadsUp() {
- // Might be implemented in subclass.
- return false;
+ return mTrackingHeadsUp;
}
/**
@@ -724,11 +1078,23 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
*/
@Override
public boolean canRemoveImmediately(@NonNull String key) {
- HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
- if (headsUpEntry != null && headsUpEntry.mUserActionMayIndirectlyRemove) {
+ if (mSwipedOutKeys.contains(key)) {
+ // We always instantly dismiss views being manually swiped out.
+ mSwipedOutKeys.remove(key);
+ return true;
+ }
+
+ HeadsUpEntry headsUpEntry = getHeadsUpEntryPhone(key);
+ HeadsUpEntry topEntry = getTopHeadsUpEntryPhone();
+
+ if (headsUpEntry == null || headsUpEntry != topEntry) {
return true;
}
- return headsUpEntry == null || headsUpEntry.wasShownLongEnough()
+
+ if (headsUpEntry.mUserActionMayIndirectlyRemove) {
+ return true;
+ }
+ return headsUpEntry.wasShownLongEnough()
|| (headsUpEntry.mEntry != null && headsUpEntry.mEntry.isRowDismissed());
}
@@ -747,7 +1113,13 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
@NonNull
protected HeadsUpEntry createHeadsUpEntry(NotificationEntry entry) {
- return new HeadsUpEntry(entry);
+ if (NotificationThrottleHun.isEnabled()) {
+ return new HeadsUpEntry(entry);
+ } else {
+ HeadsUpEntry headsUpEntry = mEntryPool.acquire();
+ headsUpEntry.setEntry(entry);
+ return headsUpEntry;
+ }
}
/**
@@ -763,12 +1135,79 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
&& Notification.CATEGORY_CALL.equals(n.category));
}
+ private final OnReorderingAllowedListener mOnReorderingAllowedListener = () -> {
+ if (NotificationThrottleHun.isEnabled()) {
+ mAvalancheController.setEnableAtRuntime(true);
+ if (mEntriesToRemoveWhenReorderingAllowed.isEmpty()) {
+ return;
+ }
+ }
+ mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
+ for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) {
+ if (entry != null && isHeadsUpEntry(entry.getKey())) {
+ // Maybe the heads-up was removed already
+ removeEntry(entry.getKey(), "mOnReorderingAllowedListener");
+ }
+ }
+ mEntriesToRemoveWhenReorderingAllowed.clear();
+ mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(true);
+ };
+
+ private final OnReorderingBannedListener mOnReorderingBannedListener = () -> {
+ if (mAvalancheController != null) {
+ // In open shade the first HUN is pinned, and visual stability logic prevents us from
+ // unpinning this first HUN as long as the shade remains open. AvalancheController only
+ // shows the next HUN when the currently showing HUN is unpinned, so we must disable
+ // throttling here so that the incoming HUN stream is not forever paused. This is reset
+ // when reorder becomes allowed.
+ mAvalancheController.setEnableAtRuntime(false);
+
+ // Note that we cannot do the above when
+ // 1) The remove runnable runs because its delay means it may not run before shade close
+ // 2) Reordering is allowed again (when shade closes) because the HUN appear animation
+ // will have started by then
+ }
+ };
+
+ private final StatusBarStateController.StateListener
+ mStatusBarStateListener = new StatusBarStateController.StateListener() {
+ @Override
+ public void onStateChanged(int newState) {
+ boolean wasKeyguard = mStatusBarState == StatusBarState.KEYGUARD;
+ boolean isKeyguard = newState == StatusBarState.KEYGUARD;
+ mStatusBarState = newState;
+
+ if (wasKeyguard && !isKeyguard && mBypassController.getBypassEnabled()) {
+ ArrayList<String> keysToRemove = new ArrayList<>();
+ for (HeadsUpEntry entry : getHeadsUpEntryList()) {
+ if (entry.mEntry != null && entry.mEntry.isBubble() && !entry.isSticky()) {
+ keysToRemove.add(entry.mEntry.getKey());
+ }
+ }
+ for (String key : keysToRemove) {
+ removeEntry(key, "mStatusBarStateListener");
+ }
+ }
+ }
+
+ @Override
+ public void onDozingChanged(boolean isDozing) {
+ if (!isDozing) {
+ // Let's make sure all huns we got while dozing time out within the normal timeout
+ // duration. Otherwise they could get stuck for a very long time
+ for (HeadsUpEntry entry : getHeadsUpEntryList()) {
+ entry.updateEntry(true /* updatePostTime */, "onDozingChanged(false)");
+ }
+ }
+ }
+ };
+
/**
* This represents a notification and how long it is in a heads up mode. It also manages its
* lifecycle automatically when created. This class is public because it is exposed by methods
* of AvalancheController that take it as param.
*/
- public class HeadsUpEntry implements Comparable<HeadsUpEntry> {
+ public class HeadsUpEntry implements Comparable<HeadsUpEntry>, HeadsUpRowRepository {
public boolean mRemoteInputActivatedAtLeastOnce;
public boolean mRemoteInputActive;
public boolean mUserActionMayIndirectlyRemove;
@@ -784,6 +1223,14 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
@Nullable private Runnable mCancelRemoveRunnable;
+ private boolean mGutsShownPinned;
+ private final MutableStateFlow<Boolean> mIsPinned = StateFlowKt.MutableStateFlow(false);
+
+ /**
+ * If the time this entry has been on was extended
+ */
+ private boolean extended;
+
public HeadsUpEntry() {
NotificationThrottleHun.assertInLegacyMode();
}
@@ -794,19 +1241,50 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
setEntry(entry, createRemoveRunnable(entry));
}
+ @Override
+ @NonNull
+ public String getKey() {
+ return requireEntry().getKey();
+ }
+
+ @Override
+ @NonNull
+ public Object getElementKey() {
+ return requireEntry().getRow();
+ }
+
+ private NotificationEntry requireEntry() {
+ /* check if */ SceneContainerFlag.isUnexpectedlyInLegacyMode();
+ return Objects.requireNonNull(mEntry);
+ }
+
+ @Override
+ @NonNull
+ public StateFlow<Boolean> isPinned() {
+ return mIsPinned;
+ }
+
/** Attach a NotificationEntry. */
public void setEntry(@NonNull final NotificationEntry entry) {
NotificationThrottleHun.assertInLegacyMode();
setEntry(entry, createRemoveRunnable(entry));
}
- protected void setEntry(@NonNull final NotificationEntry entry,
+ protected void setEntry(
+ @NonNull final NotificationEntry entry,
@Nullable Runnable removeRunnable) {
mEntry = entry;
mRemoveRunnable = removeRunnable;
mPostTime = calculatePostTime();
updateEntry(true /* updatePostTime */, "setEntry");
+
+ if (NotificationThrottleHun.isEnabled()) {
+ mEntriesToRemoveWhenReorderingAllowed.add(entry);
+ if (!mVisualStabilityProvider.isReorderingAllowed()) {
+ entry.setSeenInShade(true);
+ }
+ }
}
protected boolean isRowPinned() {
@@ -815,6 +1293,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
protected void setRowPinned(boolean pinned) {
if (mEntry != null) mEntry.setRowPinned(pinned);
+ mIsPinned.setValue(pinned);
}
/**
@@ -870,6 +1349,22 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
// Notify the manager, that the posted time has changed.
onEntryUpdated(this);
+
+ if (mEntriesToRemoveAfterExpand.contains(mEntry)) {
+ mEntriesToRemoveAfterExpand.remove(mEntry);
+ }
+ if (!NotificationThrottleHun.isEnabled()) {
+ if (mEntriesToRemoveWhenReorderingAllowed.contains(mEntry)) {
+ mEntriesToRemoveWhenReorderingAllowed.remove(mEntry);
+ }
+ }
+ }
+
+ private void extendPulse() {
+ if (!extended) {
+ extended = true;
+ updateEntry(false, "extendPulse()");
+ }
}
/**
@@ -878,6 +1373,8 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
* @return true if the notification is sticky
*/
public boolean isSticky() {
+ if (mGutsShownPinned) return true;
+
if (mEntry == null) return false;
if (ExpandHeadsUpOnInlineReply.isEnabled()) {
@@ -989,7 +1486,29 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
}
public void setExpanded(boolean expanded) {
+ if (this.mExpanded == expanded) {
+ return;
+ }
+
this.mExpanded = expanded;
+ if (expanded) {
+ cancelAutoRemovalCallbacks("setExpanded(true)");
+ } else {
+ updateEntry(false /* updatePostTime */, "setExpanded(false)");
+ }
+ }
+
+ public void setGutsShownPinned(boolean gutsShownPinned) {
+ if (mGutsShownPinned == gutsShownPinned) {
+ return;
+ }
+
+ mGutsShownPinned = gutsShownPinned;
+ if (gutsShownPinned) {
+ cancelAutoRemovalCallbacks("setGutsShownPinned(true)");
+ } else {
+ updateEntry(false /* updatePostTime */, "setGutsShownPinned(false)");
+ }
}
public void reset() {
@@ -999,6 +1518,8 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
mRemoveRunnable = null;
mExpanded = false;
mRemoteInputActive = false;
+ mGutsShownPinned = false;
+ extended = false;
}
/**
@@ -1074,7 +1595,23 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
/** Creates a runnable to remove this notification from the alerting entries. */
protected Runnable createRemoveRunnable(NotificationEntry entry) {
- return () -> removeEntry(entry.getKey(), "createRemoveRunnable");
+ return () -> {
+ if (!NotificationThrottleHun.isEnabled()
+ && !mVisualStabilityProvider.isReorderingAllowed()
+ // We don't want to allow reordering while pulsing, but headsup need to
+ // time out anyway
+ && !entry.showingPulsing()) {
+ mEntriesToRemoveWhenReorderingAllowed.add(entry);
+ mVisualStabilityProvider.addTemporaryReorderingAllowedListener(
+ mOnReorderingAllowedListener);
+ } else if (mTrackingHeadsUp) {
+ mEntriesToRemoveAfterExpand.add(entry);
+ mLogger.logRemoveEntryAfterExpand(entry);
+ } else if (mVisualStabilityProvider.isReorderingAllowed()
+ || entry.showingPulsing()) {
+ removeEntry(entry.getKey(), "createRemoveRunnable");
+ }
+ };
}
/**
@@ -1098,7 +1635,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
requestedTimeOutMs = mAvalancheController.getDurationMs(this, mAutoDismissTime);
}
final long duration = getRecommendedHeadsUpTimeoutMs(requestedTimeOutMs);
- return mPostTime + duration;
+ return mPostTime + duration + (extended ? mExtensionTime : 0);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
index 04fe6b3e9eb7..b37194b8b7a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
@@ -59,6 +59,7 @@ interface HeadsUpManager : Dumpable {
* Gets the touchable region needed for heads up notifications. Returns null if no touchable
* region is required (ie: no heads up notification currently exists).
*/
+ // TODO(b/347007367): With scene container enabled this method may report outdated regions
fun getTouchableRegion(): Region?
/**
@@ -83,6 +84,10 @@ interface HeadsUpManager : Dumpable {
/** Returns whether the entry is (pinned and expanded) or (has an active remote input). */
fun isSticky(key: String?): Boolean
+ /**
+ * Returns the value of the tracking-heads-up flag. See the doc of {@code setTrackingHeadsUp} as
+ * well.
+ */
fun isTrackingHeadsUp(): Boolean
fun onExpandingFinished()
@@ -115,7 +120,7 @@ interface HeadsUpManager : Dumpable {
key: String,
releaseImmediately: Boolean,
animate: Boolean,
- reason: String
+ reason: String,
): Boolean
/** Clears all managed notifications. */
@@ -149,6 +154,10 @@ interface HeadsUpManager : Dumpable {
*/
fun setRemoteInputActive(entry: NotificationEntry, remoteInputActive: Boolean)
+ /**
+ * Sets the tracking-heads-up flag. If the flag is true, HeadsUpManager doesn't remove the entry
+ * from the list even after a Heads Up Notification is gone.
+ */
fun setTrackingHeadsUp(tracking: Boolean)
/** Sets the current user. */
@@ -260,7 +269,7 @@ class HeadsUpManagerEmptyImpl @Inject constructor() : HeadsUpManager {
key: String,
releaseImmediately: Boolean,
animate: Boolean,
- reason: String
+ reason: String,
) = false
override fun setAnimationStateHandler(handler: AnimationStateHandler) {}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 63e6eb6c0ef5..3cd613b21f4b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -71,14 +71,17 @@ import com.android.systemui.shade.shadeController
import com.android.systemui.shade.ui.viewmodel.notificationShadeWindowModel
import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
import com.android.systemui.statusbar.data.repository.fakeStatusBarModePerDisplayRepository
+import com.android.systemui.statusbar.notification.collection.provider.visualStabilityProvider
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
+import com.android.systemui.statusbar.phone.keyguardBypassController
import com.android.systemui.statusbar.phone.scrimController
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.fakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.wifiInteractor
+import com.android.systemui.statusbar.policy.configurationController
import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
import com.android.systemui.statusbar.ui.viewmodel.keyguardStatusBarViewModel
@@ -105,6 +108,7 @@ class KosmosJavaAdapter() {
val testScope by lazy { kosmos.testScope }
val fakeExecutor by lazy { kosmos.fakeExecutor }
val fakeExecutorHandler by lazy { kosmos.fakeExecutorHandler }
+ val configurationController by lazy { kosmos.configurationController }
val configurationRepository by lazy { kosmos.fakeConfigurationRepository }
val configurationInteractor by lazy { kosmos.configurationInteractor }
val bouncerRepository by lazy { kosmos.bouncerRepository }
@@ -115,6 +119,7 @@ class KosmosJavaAdapter() {
val seenNotificationsInteractor by lazy { kosmos.seenNotificationsInteractor }
val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
val keyguardBouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
+ val keyguardBypassController by lazy { kosmos.keyguardBypassController }
val keyguardInteractor by lazy { kosmos.keyguardInteractor }
val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
val keyguardTransitionInteractor by lazy { kosmos.keyguardTransitionInteractor }
@@ -158,6 +163,7 @@ class KosmosJavaAdapter() {
val shadeRepository by lazy { kosmos.shadeRepository }
val shadeInteractor by lazy { kosmos.shadeInteractor }
val notificationShadeWindowModel by lazy { kosmos.notificationShadeWindowModel }
+ val visualStabilityProvider by lazy { kosmos.visualStabilityProvider }
val wifiInteractor by lazy { kosmos.wifiInteractor }
val fakeWifiRepository by lazy { kosmos.fakeWifiRepository }
val volumeDialogInteractor by lazy { kosmos.volumeDialogInteractor }