diff options
| author | 2025-01-09 09:17:29 -0800 | |
|---|---|---|
| committer | 2025-01-09 09:17:29 -0800 | |
| commit | 785e50429536b98e07aa057714bb67a3a7cb62fc (patch) | |
| tree | 0eee08d7a1418ba628c833874df22b6452b9ed0e | |
| parent | 991ac06a07fdf3b3f450e0b6360f0940d8622f9e (diff) | |
| parent | 3efa0fcd867859858f689943176bd7eae2f9919f (diff) | |
Merge changes from topic "caitlinshk-callchipflag-removal" into main
* changes:
[SB] Remove status_bar_call_chip_notification_icon flag.
[SB] Remove status_bar_use_repos_for_call_chip flag.
[SB][Notif] Only hide text in chip that was actually tapped.
[SB][Notif] Re-tapping notif chip hides the HUN.
22 files changed, 315 insertions, 1117 deletions
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 7d5fd903c01b..1d49e509e6a9 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -424,24 +424,6 @@ flag { } flag { - name: "status_bar_use_repos_for_call_chip" - namespace: "systemui" - description: "Use repositories as the source of truth for call notifications shown as a chip in" - "the status bar" - bug: "328584859" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { - name: "status_bar_call_chip_notification_icon" - namespace: "systemui" - description: "Use the small icon set on the notification for the status bar call chip" - bug: "354930838" -} - -flag { name: "status_bar_signal_policy_refactor" namespace: "systemui" description: "Use a settings observer for airplane mode and make StatusBarSignalPolicy startable" diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt index 0061c4142e8b..75d000b63d62 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt @@ -22,7 +22,6 @@ import android.platform.test.annotations.EnableFlags import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.Flags.FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue @@ -126,65 +125,8 @@ class CallChipViewModelTest : SysuiTestCase() { } @Test - @DisableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON) - fun chip_positiveStartTime_notifIconFlagOff_iconIsPhone() = - testScope.runTest { - val latest by collectLastValue(underTest.chip) - - repo.setOngoingCallState( - inCallModel(startTimeMs = 1000, notificationIcon = createStatusBarIconViewOrNull()) - ) - - assertThat((latest as OngoingActivityChipModel.Shown).icon) - .isInstanceOf(OngoingActivityChipModel.ChipIcon.SingleColorIcon::class.java) - val icon = - (((latest as OngoingActivityChipModel.Shown).icon) - as OngoingActivityChipModel.ChipIcon.SingleColorIcon) - .impl as Icon.Resource - assertThat(icon.res).isEqualTo(com.android.internal.R.drawable.ic_phone) - assertThat(icon.contentDescription).isNotNull() - } - - @Test - @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON) - @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME) - fun chip_positiveStartTime_notifIconFlagOn_iconIsNotifIcon() = - testScope.runTest { - val latest by collectLastValue(underTest.chip) - - val notifIcon = createStatusBarIconViewOrNull() - repo.setOngoingCallState(inCallModel(startTimeMs = 1000, notificationIcon = notifIcon)) - - assertThat((latest as OngoingActivityChipModel.Shown).icon) - .isInstanceOf(OngoingActivityChipModel.ChipIcon.StatusBarView::class.java) - val actualIcon = - (((latest as OngoingActivityChipModel.Shown).icon) - as OngoingActivityChipModel.ChipIcon.StatusBarView) - .impl - assertThat(actualIcon).isEqualTo(notifIcon) - } - - @Test - @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME) - fun chip_positiveStartTime_notifIconFlagOn_cdFlagOn_iconIsNotifKeyIcon() = - testScope.runTest { - val latest by collectLastValue(underTest.chip) - - repo.setOngoingCallState( - inCallModel( - startTimeMs = 1000, - notificationIcon = createStatusBarIconViewOrNull(), - notificationKey = "notifKey", - ) - ) - - assertThat((latest as OngoingActivityChipModel.Shown).icon) - .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon("notifKey")) - } - - @Test - @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME) - fun chip_positiveStartTime_notifIconAndConnectedDisplaysFlagOn_iconIsNotifIcon() = + @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) + fun chip_positiveStartTime_connectedDisplaysFlagOn_iconIsNotifIcon() = testScope.runTest { val latest by collectLastValue(underTest.chip) @@ -205,29 +147,8 @@ class CallChipViewModelTest : SysuiTestCase() { } @Test - @DisableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON) - fun chip_zeroStartTime_notifIconFlagOff_iconIsPhone() = - testScope.runTest { - val latest by collectLastValue(underTest.chip) - - repo.setOngoingCallState( - inCallModel(startTimeMs = 0, notificationIcon = createStatusBarIconViewOrNull()) - ) - - assertThat((latest as OngoingActivityChipModel.Shown).icon) - .isInstanceOf(OngoingActivityChipModel.ChipIcon.SingleColorIcon::class.java) - val icon = - (((latest as OngoingActivityChipModel.Shown).icon) - as OngoingActivityChipModel.ChipIcon.SingleColorIcon) - .impl as Icon.Resource - assertThat(icon.res).isEqualTo(com.android.internal.R.drawable.ic_phone) - assertThat(icon.contentDescription).isNotNull() - } - - @Test - @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON) @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME) - fun chip_zeroStartTime_notifIconFlagOn_cdFlagOff_iconIsNotifIcon() = + fun chip_zeroStartTime_cdFlagOff_iconIsNotifIcon() = testScope.runTest { val latest by collectLastValue(underTest.chip) @@ -244,8 +165,8 @@ class CallChipViewModelTest : SysuiTestCase() { } @Test - @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME) - fun chip_zeroStartTime_notifIconFlagOn_cdFlagOn_iconIsNotifKeyIcon() = + @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) + fun chip_zeroStartTime_cdFlagOn_iconIsNotifKeyIcon() = testScope.runTest { val latest by collectLastValue(underTest.chip) @@ -262,7 +183,6 @@ class CallChipViewModelTest : SysuiTestCase() { } @Test - @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON) @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME) fun chip_notifIconFlagOn_butNullNotifIcon_cdFlagOff_iconIsPhone() = testScope.runTest { @@ -281,7 +201,7 @@ class CallChipViewModelTest : SysuiTestCase() { } @Test - @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME) + @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) fun chip_notifIconFlagOn_butNullNotifIcon_iconNotifKey() = testScope.runTest { val latest by collectLastValue(underTest.chip) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt index e561e3ea27d7..902db5e10589 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt @@ -501,7 +501,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { @Test @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) - fun chips_hasHeadsUpByUser_onlyShowsIcon() = + fun chips_hasHeadsUpBySystem_showsTime() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -523,7 +523,99 @@ class NotifChipsViewModelTest : SysuiTestCase() { ) ) - // WHEN there's a HUN pinned by a user + // WHEN there's a HUN pinned by the system + kosmos.headsUpNotificationRepository.setNotifications( + UnconfinedFakeHeadsUpRowRepository( + key = "notif", + pinnedStatus = MutableStateFlow(PinnedStatus.PinnedBySystem), + ) + ) + + // THEN the chip keeps showing time + // (In real life the chip won't show at all, but that's handled in a different part of + // the system. What we know here is that the chip shouldn't shrink to icon only.) + assertThat(latest!![0]) + .isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java) + } + + @Test + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) + fun chips_hasHeadsUpByUser_forOtherNotif_showsTime() = + kosmos.runTest { + val latest by collectLastValue(underTest.chips) + + val promotedContentBuilder = + PromotedNotificationContentModel.Builder("notif").apply { + this.time = + PromotedNotificationContentModel.When( + time = 6543L, + mode = PromotedNotificationContentModel.When.Mode.BasicTime, + ) + } + val otherPromotedContentBuilder = + PromotedNotificationContentModel.Builder("other notif").apply { + this.time = + PromotedNotificationContentModel.When( + time = 654321L, + mode = PromotedNotificationContentModel.When.Mode.BasicTime, + ) + } + val icon = createStatusBarIconViewOrNull() + val otherIcon = createStatusBarIconViewOrNull() + setNotifs( + listOf( + activeNotificationModel( + key = "notif", + statusBarChipIcon = icon, + promotedContent = promotedContentBuilder.build(), + ), + activeNotificationModel( + key = "other notif", + statusBarChipIcon = otherIcon, + promotedContent = otherPromotedContentBuilder.build(), + ), + ) + ) + + // WHEN there's a HUN pinned for the "other notif" chip + kosmos.headsUpNotificationRepository.setNotifications( + UnconfinedFakeHeadsUpRowRepository( + key = "other notif", + pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser), + ) + ) + + // THEN the "notif" chip keeps showing time + val chip = latest!![0] + assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java) + assertIsNotifChip(chip, icon, "notif") + } + + @Test + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) + fun chips_hasHeadsUpByUser_forThisNotif_onlyShowsIcon() = + kosmos.runTest { + val latest by collectLastValue(underTest.chips) + + val promotedContentBuilder = + PromotedNotificationContentModel.Builder("notif").apply { + this.time = + PromotedNotificationContentModel.When( + time = 6543L, + mode = PromotedNotificationContentModel.When.Mode.BasicTime, + ) + } + setNotifs( + listOf( + activeNotificationModel( + key = "notif", + statusBarChipIcon = mock<StatusBarIconView>(), + promotedContent = promotedContentBuilder.build(), + ) + ) + ) + + // WHEN this notification is pinned by the user kosmos.headsUpNotificationRepository.setNotifications( UnconfinedFakeHeadsUpRowRepository( key = "notif", @@ -531,6 +623,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { ) ) + // THEN the chip shrinks to icon only assertThat(latest!![0]) .isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java) } 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 a3ffd91b1728..609885d0214b 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 @@ -458,7 +458,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { @Test @EnableFlags(StatusBarNotifChips.FLAG_NAME) - fun showPromotedNotification_hasNotifEntry_shownAsHUN() = + fun onPromotedNotificationChipTapped_hasNotifEntry_shownAsHUN() = testScope.runTest { whenever(notifCollection.getEntry(entry.key)).thenReturn(entry) @@ -473,7 +473,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { @Test @EnableFlags(StatusBarNotifChips.FLAG_NAME) - fun showPromotedNotification_noNotifEntry_noHUN() = + fun onPromotedNotificationChipTapped_noNotifEntry_noHUN() = testScope.runTest { whenever(notifCollection.getEntry(entry.key)).thenReturn(null) @@ -488,7 +488,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { @Test @EnableFlags(StatusBarNotifChips.FLAG_NAME) - fun showPromotedNotification_shownAsHUNEvenIfEntryShouldNot() = + fun onPromotedNotificationChipTapped_shownAsHUNEvenIfEntryShouldNot() = testScope.runTest { whenever(notifCollection.getEntry(entry.key)).thenReturn(entry) @@ -511,7 +511,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { @Test @EnableFlags(StatusBarNotifChips.FLAG_NAME) - fun showPromotedNotification_atSameTimeAsOnAdded_promotedShownAsHUN() = + fun onPromotedNotificationChipTapped_atSameTimeAsOnAdded_promotedShownAsHUN() = testScope.runTest { // First, the promoted notification appears as not heads up val promotedEntry = NotificationEntryBuilder().setPkg("promotedPackage").build() @@ -548,6 +548,33 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { } @Test + @EnableFlags(StatusBarNotifChips.FLAG_NAME) + fun onPromotedNotificationChipTapped_chipTappedTwice_hunHiddenOnSecondTap() = + testScope.runTest { + whenever(notifCollection.getEntry(entry.key)).thenReturn(entry) + + // WHEN chip tapped first + statusBarNotificationChipsInteractor.onPromotedNotificationChipTapped(entry.key) + executor.advanceClockToLast() + executor.runAllReady() + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry)) + + // THEN HUN is shown + finishBind(entry) + verify(headsUpManager).showNotification(entry, isPinnedByUser = true) + addHUN(entry) + + // WHEN chip is tapped again + statusBarNotificationChipsInteractor.onPromotedNotificationChipTapped(entry.key) + executor.advanceClockToLast() + executor.runAllReady() + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry)) + + // THEN HUN is hidden + verify(headsUpManager).removeNotification(eq(entry.key), eq(false), any()) + } + + @Test fun testTransferIsolatedChildAlert_withGroupAlertSummary() { setShouldHeadsUp(groupSummary) whenever(notifPipeline.allNotifs).thenReturn(listOf(groupSummary, groupSibling1)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt index 22ef408e266c..fae7d515d305 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt @@ -32,6 +32,7 @@ import com.android.systemui.shade.data.repository.fakeShadeRepository import com.android.systemui.shade.shadeTestUtil import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository +import com.android.systemui.statusbar.notification.domain.model.TopPinnedState import com.android.systemui.statusbar.notification.headsup.PinnedStatus import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor @@ -412,46 +413,53 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() { @Test fun statusBarHeadsUpState_pinnedBySystem() = testScope.runTest { - val statusBarHeadsUpState by collectLastValue(underTest.statusBarHeadsUpState) + val state by collectLastValue(underTest.statusBarHeadsUpState) + val status by collectLastValue(underTest.statusBarHeadsUpStatus) headsUpRepository.setNotifications( FakeHeadsUpRowRepository(key = "key 0", pinnedStatus = PinnedStatus.PinnedBySystem) ) runCurrent() - assertThat(statusBarHeadsUpState).isEqualTo(PinnedStatus.PinnedBySystem) + assertThat(state).isEqualTo(TopPinnedState.Pinned("key 0", PinnedStatus.PinnedBySystem)) + assertThat(status).isEqualTo(PinnedStatus.PinnedBySystem) } @Test fun statusBarHeadsUpState_pinnedByUser() = testScope.runTest { - val statusBarHeadsUpState by collectLastValue(underTest.statusBarHeadsUpState) + val state by collectLastValue(underTest.statusBarHeadsUpState) + val status by collectLastValue(underTest.statusBarHeadsUpStatus) headsUpRepository.setNotifications( FakeHeadsUpRowRepository(key = "key 0", pinnedStatus = PinnedStatus.PinnedByUser) ) runCurrent() - assertThat(statusBarHeadsUpState).isEqualTo(PinnedStatus.PinnedByUser) + assertThat(state).isEqualTo(TopPinnedState.Pinned("key 0", PinnedStatus.PinnedByUser)) + assertThat(status).isEqualTo(PinnedStatus.PinnedByUser) } @Test fun statusBarHeadsUpState_withoutPinnedNotifications_notPinned() = testScope.runTest { - val statusBarHeadsUpState by collectLastValue(underTest.statusBarHeadsUpState) + val state by collectLastValue(underTest.statusBarHeadsUpState) + val status by collectLastValue(underTest.statusBarHeadsUpStatus) headsUpRepository.setNotifications( FakeHeadsUpRowRepository(key = "key 0", PinnedStatus.NotPinned) ) runCurrent() - assertThat(statusBarHeadsUpState).isEqualTo(PinnedStatus.NotPinned) + assertThat(state).isEqualTo(TopPinnedState.NothingPinned) + assertThat(status).isEqualTo(PinnedStatus.NotPinned) } @Test fun statusBarHeadsUpState_whenShadeExpanded_false() = testScope.runTest { - val statusBarHeadsUpState by collectLastValue(underTest.statusBarHeadsUpState) + val state by collectLastValue(underTest.statusBarHeadsUpState) + val status by collectLastValue(underTest.statusBarHeadsUpStatus) // WHEN a row is pinned headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true)) @@ -463,13 +471,15 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() { // should emit `false`. kosmos.fakeShadeRepository.setLegacyShadeExpansion(1.0f) - assertThat(statusBarHeadsUpState!!.isPinned).isFalse() + assertThat(state).isEqualTo(TopPinnedState.NothingPinned) + assertThat(status!!.isPinned).isFalse() } @Test fun statusBarHeadsUpState_notificationsAreHidden_false() = testScope.runTest { - val statusBarHeadsUpState by collectLastValue(underTest.statusBarHeadsUpState) + val state by collectLastValue(underTest.statusBarHeadsUpState) + val status by collectLastValue(underTest.statusBarHeadsUpStatus) // WHEN a row is pinned headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true)) @@ -477,13 +487,15 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() { // AND the notifications are hidden keyguardViewStateRepository.areNotificationsFullyHidden.value = true - assertThat(statusBarHeadsUpState!!.isPinned).isFalse() + assertThat(state).isEqualTo(TopPinnedState.NothingPinned) + assertThat(status!!.isPinned).isFalse() } @Test fun statusBarHeadsUpState_onLockScreen_false() = testScope.runTest { - val statusBarHeadsUpState by collectLastValue(underTest.statusBarHeadsUpState) + val state by collectLastValue(underTest.statusBarHeadsUpState) + val status by collectLastValue(underTest.statusBarHeadsUpStatus) // WHEN a row is pinned headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true)) @@ -494,13 +506,15 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() { testSetup = true, ) - assertThat(statusBarHeadsUpState!!.isPinned).isFalse() + assertThat(state).isEqualTo(TopPinnedState.NothingPinned) + assertThat(status!!.isPinned).isFalse() } @Test fun statusBarHeadsUpState_onByPassLockScreen_true() = testScope.runTest { - val statusBarHeadsUpState by collectLastValue(underTest.statusBarHeadsUpState) + val state by collectLastValue(underTest.statusBarHeadsUpState) + val status by collectLastValue(underTest.statusBarHeadsUpStatus) // WHEN a row is pinned headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true)) @@ -513,13 +527,15 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() { // AND bypass is enabled faceAuthRepository.isBypassEnabled.value = true - assertThat(statusBarHeadsUpState!!.isPinned).isTrue() + assertThat(state).isInstanceOf(TopPinnedState.Pinned::class.java) + assertThat(status!!.isPinned).isTrue() } @Test fun statusBarHeadsUpState_onByPassLockScreen_withoutNotifications_false() = testScope.runTest { - val statusBarHeadsUpState by collectLastValue(underTest.statusBarHeadsUpState) + val state by collectLastValue(underTest.statusBarHeadsUpState) + val status by collectLastValue(underTest.statusBarHeadsUpStatus) // WHEN no pinned rows // AND the lock screen is shown @@ -530,7 +546,8 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() { // AND bypass is enabled faceAuthRepository.isBypassEnabled.value = true - assertThat(statusBarHeadsUpState!!.isPinned).isFalse() + assertThat(state).isEqualTo(TopPinnedState.NothingPinned) + assertThat(status!!.isPinned).isFalse() } private fun fakeHeadsUpRowRepository(key: String, isPinned: Boolean = false) = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt index cf512cdee800..b98409906f8d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt @@ -28,9 +28,7 @@ import android.view.View import android.widget.LinearLayout import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.Flags.FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON import com.android.systemui.Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS -import com.android.systemui.Flags.FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP import com.android.systemui.SysuiTestCase import com.android.systemui.concurrency.fakeExecutor import com.android.systemui.dump.DumpManager @@ -42,7 +40,6 @@ import com.android.systemui.res.R import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler -import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection import com.android.systemui.statusbar.notification.data.model.activeNotificationModel import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository @@ -76,9 +73,8 @@ import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper @OptIn(ExperimentalCoroutinesApi::class) -@EnableFlags(FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP) @DisableFlags(StatusBarChipsModernization.FLAG_NAME) -class OngoingCallControllerViaRepoTest : SysuiTestCase() { +class OngoingCallControllerTest : SysuiTestCase() { private val kosmos = Kosmos() private val clock = kosmos.fakeSystemClock @@ -114,7 +110,6 @@ class OngoingCallControllerViaRepoTest : SysuiTestCase() { testScope.backgroundScope, context, ongoingCallRepository, - mock<CommonNotifCollection>(), kosmos.activeNotificationsInteractor, clock, mockActivityStarter, @@ -162,28 +157,7 @@ class OngoingCallControllerViaRepoTest : SysuiTestCase() { } @Test - @DisableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON) - fun interactorHasOngoingCallNotif_notifIconFlagOff_repoHasNoNotifIcon() = - testScope.runTest { - val icon = mock<StatusBarIconView>() - setNotifOnRepo( - activeNotificationModel( - key = "ongoingNotif", - callType = CallType.Ongoing, - uid = CALL_UID, - statusBarChipIcon = icon, - whenTime = 567, - ) - ) - - val repoState = ongoingCallRepository.ongoingCallState.value - assertThat(repoState).isInstanceOf(OngoingCallModel.InCall::class.java) - assertThat((repoState as OngoingCallModel.InCall).notificationIconView).isNull() - } - - @Test - @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON) - fun interactorHasOngoingCallNotif_notifIconFlagOn_repoHasNotifIcon() = + fun interactorHasOngoingCallNotif_repoHasNotifIcon() = testScope.runTest { val icon = mock<StatusBarIconView>() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt deleted file mode 100644 index cd3539d6b9a5..000000000000 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt +++ /dev/null @@ -1,694 +0,0 @@ -/* - * Copyright (C) 2021 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.phone.ongoingcall - -import android.app.ActivityManager -import android.app.IActivityManager -import android.app.IUidObserver -import android.app.Notification -import android.app.PendingIntent -import android.app.Person -import android.platform.test.annotations.DisableFlags -import android.platform.test.annotations.EnableFlags -import android.service.notification.NotificationListenerService.REASON_USER_STOPPED -import android.testing.TestableLooper -import android.view.LayoutInflater -import android.view.View -import android.widget.LinearLayout -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.systemui.Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS -import com.android.systemui.Flags.FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP -import com.android.systemui.SysuiTestCase -import com.android.systemui.dump.DumpManager -import com.android.systemui.kosmos.Kosmos -import com.android.systemui.log.logcatLogBuffer -import com.android.systemui.plugins.ActivityStarter -import com.android.systemui.res.R -import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository -import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler -import com.android.systemui.statusbar.notification.collection.NotificationEntry -import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder -import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection -import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener -import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor -import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository -import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel -import com.android.systemui.statusbar.window.StatusBarWindowController -import com.android.systemui.statusbar.window.StatusBarWindowControllerStore -import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.mockito.any -import com.android.systemui.util.time.FakeSystemClock -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.runCurrent -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentCaptor -import org.mockito.ArgumentMatchers.anyBoolean -import org.mockito.ArgumentMatchers.anyString -import org.mockito.ArgumentMatchers.nullable -import org.mockito.Mock -import org.mockito.Mockito.eq -import org.mockito.Mockito.mock -import org.mockito.Mockito.never -import org.mockito.Mockito.reset -import org.mockito.Mockito.times -import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` -import org.mockito.MockitoAnnotations -import org.mockito.kotlin.whenever - -private const val CALL_UID = 900 - -// A process state that represents the process being visible to the user. -private const val PROC_STATE_VISIBLE = ActivityManager.PROCESS_STATE_TOP - -// A process state that represents the process being invisible to the user. -private const val PROC_STATE_INVISIBLE = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE - -@SmallTest -@RunWith(AndroidJUnit4::class) -@TestableLooper.RunWithLooper -@OptIn(ExperimentalCoroutinesApi::class) -@DisableFlags(FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP, StatusBarChipsModernization.FLAG_NAME) -class OngoingCallControllerViaListenerTest : SysuiTestCase() { - private val kosmos = Kosmos() - - private val clock = FakeSystemClock() - private val mainExecutor = FakeExecutor(clock) - private val testScope = TestScope() - private val statusBarModeRepository = FakeStatusBarModeRepository() - private val ongoingCallRepository = kosmos.ongoingCallRepository - - private lateinit var controller: OngoingCallController - private lateinit var notifCollectionListener: NotifCollectionListener - - @Mock - private lateinit var mockSwipeStatusBarAwayGestureHandler: SwipeStatusBarAwayGestureHandler - @Mock private lateinit var mockOngoingCallListener: OngoingCallListener - @Mock private lateinit var mockActivityStarter: ActivityStarter - @Mock private lateinit var mockIActivityManager: IActivityManager - @Mock private lateinit var mockStatusBarWindowController: StatusBarWindowController - @Mock private lateinit var mockStatusBarWindowControllerStore: StatusBarWindowControllerStore - - private lateinit var chipView: View - - @Before - fun setUp() { - allowTestableLooperAsMainThread() - TestableLooper.get(this).runWithLooper { - chipView = - LayoutInflater.from(mContext).inflate(R.layout.ongoing_activity_chip_primary, null) - } - - MockitoAnnotations.initMocks(this) - val notificationCollection = mock(CommonNotifCollection::class.java) - whenever(mockStatusBarWindowControllerStore.defaultDisplay) - .thenReturn(mockStatusBarWindowController) - - controller = - OngoingCallController( - testScope.backgroundScope, - context, - ongoingCallRepository, - notificationCollection, - kosmos.activeNotificationsInteractor, - clock, - mockActivityStarter, - mainExecutor, - mockIActivityManager, - DumpManager(), - mockStatusBarWindowControllerStore, - mockSwipeStatusBarAwayGestureHandler, - statusBarModeRepository, - logcatLogBuffer("OngoingCallControllerViaListenerTest"), - ) - controller.start() - controller.addCallback(mockOngoingCallListener) - controller.setChipView(chipView) - onTeardown { controller.tearDownChipView() } - - val collectionListenerCaptor = ArgumentCaptor.forClass(NotifCollectionListener::class.java) - verify(notificationCollection).addCollectionListener(collectionListenerCaptor.capture()) - notifCollectionListener = collectionListenerCaptor.value!! - - `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java))) - .thenReturn(PROC_STATE_INVISIBLE) - } - - @Test - fun onEntryUpdated_isOngoingCallNotif_listenerAndRepoNotified() { - val notification = NotificationEntryBuilder(createOngoingCallNotifEntry()) - notification.modifyNotification(context).setWhen(567) - notifCollectionListener.onEntryUpdated(notification.build()) - - verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean()) - val repoState = ongoingCallRepository.ongoingCallState.value - assertThat(repoState).isInstanceOf(OngoingCallModel.InCall::class.java) - assertThat((repoState as OngoingCallModel.InCall).startTimeMs).isEqualTo(567) - } - - @Test - fun onEntryUpdated_isOngoingCallNotif_windowControllerUpdated() { - notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) - - verify(mockStatusBarWindowController).setOngoingProcessRequiresStatusBarVisible(true) - } - - @Test - fun onEntryUpdated_notOngoingCallNotif_listenerAndRepoNotNotified() { - notifCollectionListener.onEntryUpdated(createNotCallNotifEntry()) - - verify(mockOngoingCallListener, never()).onOngoingCallStateChanged(anyBoolean()) - assertThat(ongoingCallRepository.ongoingCallState.value) - .isInstanceOf(OngoingCallModel.NoCall::class.java) - } - - @Test - fun onEntryUpdated_ongoingCallNotifThenScreeningCallNotif_listenerNotifiedTwice() { - notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) - notifCollectionListener.onEntryUpdated(createScreeningCallNotifEntry()) - - verify(mockOngoingCallListener, times(2)).onOngoingCallStateChanged(anyBoolean()) - } - - @Test - fun onEntryUpdated_ongoingCallNotifThenScreeningCallNotif_repoUpdated() { - notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) - assertThat(ongoingCallRepository.ongoingCallState.value) - .isInstanceOf(OngoingCallModel.InCall::class.java) - - notifCollectionListener.onEntryUpdated(createScreeningCallNotifEntry()) - - assertThat(ongoingCallRepository.ongoingCallState.value) - .isInstanceOf(OngoingCallModel.NoCall::class.java) - } - - /** Regression test for b/191472854. */ - @Test - fun onEntryUpdated_notifHasNullContentIntent_noCrash() { - notifCollectionListener.onEntryUpdated( - createCallNotifEntry(ongoingCallStyle, nullContentIntent = true) - ) - } - - /** Regression test for b/192379214. */ - @Test - @DisableFlags(android.app.Flags.FLAG_SORT_SECTION_BY_TIME, FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - fun onEntryUpdated_notificationWhenIsZero_timeHidden() { - val notification = NotificationEntryBuilder(createOngoingCallNotifEntry()) - notification.modifyNotification(context).setWhen(0) - - notifCollectionListener.onEntryUpdated(notification.build()) - chipView.measure( - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), - ) - - assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth) - .isEqualTo(0) - } - - @Test - @EnableFlags(android.app.Flags.FLAG_SORT_SECTION_BY_TIME) - @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - fun onEntryUpdated_notificationWhenIsZero_timeShown() { - val notification = NotificationEntryBuilder(createOngoingCallNotifEntry()) - notification.modifyNotification(context).setWhen(0) - - notifCollectionListener.onEntryUpdated(notification.build()) - chipView.measure( - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), - ) - - assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth) - .isGreaterThan(0) - } - - @Test - @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - fun onEntryUpdated_notificationWhenIsValid_timeShown() { - val notification = NotificationEntryBuilder(createOngoingCallNotifEntry()) - notification.modifyNotification(context).setWhen(clock.currentTimeMillis()) - - notifCollectionListener.onEntryUpdated(notification.build()) - chipView.measure( - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), - ) - - assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth) - .isGreaterThan(0) - } - - /** Regression test for b/194731244. */ - @Test - fun onEntryUpdated_calledManyTimes_uidObserverOnlyRegisteredOnce() { - for (i in 0 until 4) { - // Re-create the notification each time so that it's considered a different object and - // will re-trigger the whole flow. - notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) - } - - verify(mockIActivityManager, times(1)).registerUidObserver(any(), any(), any(), any()) - } - - /** Regression test for b/216248574. */ - @Test - fun entryUpdated_getUidProcessStateThrowsException_noCrash() { - `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java))) - .thenThrow(SecurityException()) - - // No assert required, just check no crash - notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) - } - - /** Regression test for b/216248574. */ - @Test - fun entryUpdated_registerUidObserverThrowsException_noCrash() { - `when`( - mockIActivityManager.registerUidObserver( - any(), - any(), - any(), - nullable(String::class.java), - ) - ) - .thenThrow(SecurityException()) - - // No assert required, just check no crash - notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) - } - - /** Regression test for b/216248574. */ - @Test - fun entryUpdated_packageNameProvidedToActivityManager() { - notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) - - val packageNameCaptor = ArgumentCaptor.forClass(String::class.java) - verify(mockIActivityManager) - .registerUidObserver(any(), any(), any(), packageNameCaptor.capture()) - assertThat(packageNameCaptor.value).isNotNull() - } - - /** - * If a call notification is never added before #onEntryRemoved is called, then the listener - * should never be notified. - */ - @Test - fun onEntryRemoved_callNotifNeverAddedBeforehand_listenerNotNotified() { - notifCollectionListener.onEntryRemoved(createOngoingCallNotifEntry(), REASON_USER_STOPPED) - - verify(mockOngoingCallListener, never()).onOngoingCallStateChanged(anyBoolean()) - } - - @Test - fun onEntryRemoved_callNotifAddedThenRemoved_listenerNotified() { - val ongoingCallNotifEntry = createOngoingCallNotifEntry() - notifCollectionListener.onEntryAdded(ongoingCallNotifEntry) - reset(mockOngoingCallListener) - - notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED) - - verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean()) - } - - @Test - fun onEntryRemoved_callNotifAddedThenRemoved_repoUpdated() { - val ongoingCallNotifEntry = createOngoingCallNotifEntry() - notifCollectionListener.onEntryAdded(ongoingCallNotifEntry) - assertThat(ongoingCallRepository.ongoingCallState.value) - .isInstanceOf(OngoingCallModel.InCall::class.java) - - notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED) - - assertThat(ongoingCallRepository.ongoingCallState.value) - .isInstanceOf(OngoingCallModel.NoCall::class.java) - } - - @Test - fun onEntryUpdated_callNotifAddedThenRemoved_windowControllerUpdated() { - val ongoingCallNotifEntry = createOngoingCallNotifEntry() - notifCollectionListener.onEntryAdded(ongoingCallNotifEntry) - - notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED) - - verify(mockStatusBarWindowController).setOngoingProcessRequiresStatusBarVisible(false) - } - - /** Regression test for b/188491504. */ - @Test - fun onEntryRemoved_removedNotifHasSameKeyAsAddedNotif_listenerNotified() { - val ongoingCallNotifEntry = createOngoingCallNotifEntry() - notifCollectionListener.onEntryAdded(ongoingCallNotifEntry) - reset(mockOngoingCallListener) - - // Create another notification based on the ongoing call one, but remove the features that - // made it a call notification. - val removedEntryBuilder = NotificationEntryBuilder(ongoingCallNotifEntry) - removedEntryBuilder.modifyNotification(context).style = null - - notifCollectionListener.onEntryRemoved(removedEntryBuilder.build(), REASON_USER_STOPPED) - - verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean()) - } - - /** Regression test for b/188491504. */ - @Test - fun onEntryRemoved_removedNotifHasSameKeyAsAddedNotif_repoUpdated() { - val ongoingCallNotifEntry = createOngoingCallNotifEntry() - notifCollectionListener.onEntryAdded(ongoingCallNotifEntry) - - // Create another notification based on the ongoing call one, but remove the features that - // made it a call notification. - val removedEntryBuilder = NotificationEntryBuilder(ongoingCallNotifEntry) - removedEntryBuilder.modifyNotification(context).style = null - - notifCollectionListener.onEntryRemoved(removedEntryBuilder.build(), REASON_USER_STOPPED) - - assertThat(ongoingCallRepository.ongoingCallState.value) - .isInstanceOf(OngoingCallModel.NoCall::class.java) - } - - @Test - fun onEntryRemoved_notifKeyDoesNotMatchOngoingCallNotif_listenerNotNotified() { - notifCollectionListener.onEntryAdded(createOngoingCallNotifEntry()) - reset(mockOngoingCallListener) - - notifCollectionListener.onEntryRemoved(createNotCallNotifEntry(), REASON_USER_STOPPED) - - verify(mockOngoingCallListener, never()).onOngoingCallStateChanged(anyBoolean()) - } - - @Test - fun onEntryRemoved_notifKeyDoesNotMatchOngoingCallNotif_repoNotUpdated() { - notifCollectionListener.onEntryAdded(createOngoingCallNotifEntry()) - - notifCollectionListener.onEntryRemoved(createNotCallNotifEntry(), REASON_USER_STOPPED) - - assertThat(ongoingCallRepository.ongoingCallState.value) - .isInstanceOf(OngoingCallModel.InCall::class.java) - } - - @Test - fun hasOngoingCall_noOngoingCallNotifSent_returnsFalse() { - assertThat(controller.hasOngoingCall()).isFalse() - } - - @Test - fun hasOngoingCall_unrelatedNotifSent_returnsFalse() { - notifCollectionListener.onEntryUpdated(createNotCallNotifEntry()) - - assertThat(controller.hasOngoingCall()).isFalse() - } - - @Test - fun hasOngoingCall_screeningCallNotifSent_returnsFalse() { - notifCollectionListener.onEntryUpdated(createScreeningCallNotifEntry()) - - assertThat(controller.hasOngoingCall()).isFalse() - } - - @Test - fun hasOngoingCall_ongoingCallNotifSentAndCallAppNotVisible_returnsTrue() { - `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java))) - .thenReturn(PROC_STATE_INVISIBLE) - - notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) - - assertThat(controller.hasOngoingCall()).isTrue() - } - - @Test - fun hasOngoingCall_ongoingCallNotifSentButCallAppVisible_returnsFalse() { - `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java))) - .thenReturn(PROC_STATE_VISIBLE) - - notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) - - assertThat(controller.hasOngoingCall()).isFalse() - } - - @Test - fun hasOngoingCall_ongoingCallNotifSentButInvalidChipView_returnsFalse() { - val invalidChipView = LinearLayout(context) - controller.setChipView(invalidChipView) - - notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) - - assertThat(controller.hasOngoingCall()).isFalse() - } - - @Test - fun hasOngoingCall_ongoingCallNotifSentThenRemoved_returnsFalse() { - val ongoingCallNotifEntry = createOngoingCallNotifEntry() - - notifCollectionListener.onEntryUpdated(ongoingCallNotifEntry) - notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED) - - assertThat(controller.hasOngoingCall()).isFalse() - } - - @Test - fun hasOngoingCall_ongoingCallNotifSentThenScreeningCallNotifSent_returnsFalse() { - notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) - notifCollectionListener.onEntryUpdated(createScreeningCallNotifEntry()) - - assertThat(controller.hasOngoingCall()).isFalse() - } - - @Test - fun hasOngoingCall_ongoingCallNotifSentThenUnrelatedNotifSent_returnsTrue() { - notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) - notifCollectionListener.onEntryUpdated(createNotCallNotifEntry()) - - assertThat(controller.hasOngoingCall()).isTrue() - } - - /** - * This test fakes a theme change during an ongoing call. - * - * When a theme change happens, [CollapsedStatusBarFragment] and its views get re-created, so - * [OngoingCallController.setChipView] gets called with a new view. If there's an active ongoing - * call when the theme changes, the new view needs to be updated with the call information. - */ - @Test - fun setChipView_whenHasOngoingCallIsTrue_listenerNotifiedWithNewView() { - // Start an ongoing call. - notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) - reset(mockOngoingCallListener) - - lateinit var newChipView: View - TestableLooper.get(this).runWithLooper { - newChipView = - LayoutInflater.from(mContext).inflate(R.layout.ongoing_activity_chip_primary, null) - } - - // Change the chip view associated with the controller. - controller.setChipView(newChipView) - - verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean()) - } - - @Test - fun callProcessChangesToVisible_listenerNotified() { - // Start the call while the process is invisible. - `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java))) - .thenReturn(PROC_STATE_INVISIBLE) - notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) - reset(mockOngoingCallListener) - - val captor = ArgumentCaptor.forClass(IUidObserver.Stub::class.java) - verify(mockIActivityManager) - .registerUidObserver(captor.capture(), any(), any(), nullable(String::class.java)) - val uidObserver = captor.value - - // Update the process to visible. - uidObserver.onUidStateChanged(CALL_UID, PROC_STATE_VISIBLE, 0, 0) - mainExecutor.advanceClockToLast() - mainExecutor.runAllReady() - - verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean()) - } - - @Test - fun callProcessChangesToInvisible_listenerNotified() { - // Start the call while the process is visible. - `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java))) - .thenReturn(PROC_STATE_VISIBLE) - notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) - reset(mockOngoingCallListener) - - val captor = ArgumentCaptor.forClass(IUidObserver.Stub::class.java) - verify(mockIActivityManager) - .registerUidObserver(captor.capture(), any(), any(), nullable(String::class.java)) - val uidObserver = captor.value - - // Update the process to invisible. - uidObserver.onUidStateChanged(CALL_UID, PROC_STATE_INVISIBLE, 0, 0) - mainExecutor.advanceClockToLast() - mainExecutor.runAllReady() - - verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean()) - } - - /** Regression test for b/212467440. */ - @Test - @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - fun chipClicked_activityStarterTriggeredWithUnmodifiedIntent() { - val notifEntry = createOngoingCallNotifEntry() - val pendingIntent = notifEntry.sbn.notification.contentIntent - notifCollectionListener.onEntryUpdated(notifEntry) - - chipView.performClick() - - // Ensure that the sysui didn't modify the notification's intent -- see b/212467440. - verify(mockActivityStarter).postStartActivityDismissingKeyguard(eq(pendingIntent), any()) - } - - @Test - @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - fun callNotificationAdded_chipIsClickable() { - notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) - - assertThat(chipView.hasOnClickListeners()).isTrue() - } - - @Test - @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - fun callNotificationAdded_newChipsEnabled_chipNotClickable() { - notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) - - assertThat(chipView.hasOnClickListeners()).isFalse() - } - - @Test - @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - fun fullscreenIsTrue_chipStillClickable() { - notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) - statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true - testScope.runCurrent() - - assertThat(chipView.hasOnClickListeners()).isTrue() - } - - // Swipe gesture tests - - @Test - fun callStartedInImmersiveMode_swipeGestureCallbackAdded() { - statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true - testScope.runCurrent() - - notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) - - verify(mockSwipeStatusBarAwayGestureHandler) - .addOnGestureDetectedCallback(anyString(), any()) - } - - @Test - fun callStartedNotInImmersiveMode_swipeGestureCallbackNotAdded() { - statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = false - testScope.runCurrent() - - notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) - - verify(mockSwipeStatusBarAwayGestureHandler, never()) - .addOnGestureDetectedCallback(anyString(), any()) - } - - @Test - fun transitionToImmersiveMode_swipeGestureCallbackAdded() { - notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) - - statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true - testScope.runCurrent() - - verify(mockSwipeStatusBarAwayGestureHandler) - .addOnGestureDetectedCallback(anyString(), any()) - } - - @Test - fun transitionOutOfImmersiveMode_swipeGestureCallbackRemoved() { - statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true - notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) - reset(mockSwipeStatusBarAwayGestureHandler) - - statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = false - testScope.runCurrent() - - verify(mockSwipeStatusBarAwayGestureHandler).removeOnGestureDetectedCallback(anyString()) - } - - @Test - fun callEndedWhileInImmersiveMode_swipeGestureCallbackRemoved() { - statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true - testScope.runCurrent() - val ongoingCallNotifEntry = createOngoingCallNotifEntry() - notifCollectionListener.onEntryAdded(ongoingCallNotifEntry) - reset(mockSwipeStatusBarAwayGestureHandler) - - notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED) - - verify(mockSwipeStatusBarAwayGestureHandler).removeOnGestureDetectedCallback(anyString()) - } - - // TODO(b/195839150): Add test - // swipeGesturedTriggeredPreviously_entersImmersiveModeAgain_callbackNotAdded(). That's - // difficult to add now because we have no way to trigger [SwipeStatusBarAwayGestureHandler]'s - // callbacks in test. - - // END swipe gesture tests - - private fun createOngoingCallNotifEntry() = createCallNotifEntry(ongoingCallStyle) - - private fun createScreeningCallNotifEntry() = createCallNotifEntry(screeningCallStyle) - - private fun createCallNotifEntry( - callStyle: Notification.CallStyle, - nullContentIntent: Boolean = false, - ): NotificationEntry { - val notificationEntryBuilder = NotificationEntryBuilder() - notificationEntryBuilder.modifyNotification(context).style = callStyle - notificationEntryBuilder.setUid(CALL_UID) - - if (nullContentIntent) { - notificationEntryBuilder.modifyNotification(context).setContentIntent(null) - } else { - val contentIntent = mock(PendingIntent::class.java) - notificationEntryBuilder.modifyNotification(context).setContentIntent(contentIntent) - } - - return notificationEntryBuilder.build() - } - - private fun createNotCallNotifEntry() = NotificationEntryBuilder().build() -} - -private val person = Person.Builder().setName("name").build() -private val hangUpIntent = mock(PendingIntent::class.java) - -private val ongoingCallStyle = Notification.CallStyle.forOngoingCall(person, hangUpIntent) -private val screeningCallStyle = - Notification.CallStyle.forScreeningCall( - person, - hangUpIntent, - /* answerIntent= */ mock(PendingIntent::class.java), - ) diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt index 129a6bb72996..2ed0671a570b 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt @@ -23,13 +23,7 @@ import com.android.server.notification.Flags.crossAppPoliteNotifications import com.android.server.notification.Flags.politeNotifications import com.android.server.notification.Flags.vibrateWhileUnlocked import com.android.systemui.Flags.FLAG_COMMUNAL_HUB -import com.android.systemui.Flags.FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON -import com.android.systemui.Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS -import com.android.systemui.Flags.FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP import com.android.systemui.Flags.communalHub -import com.android.systemui.Flags.statusBarCallChipNotificationIcon -import com.android.systemui.Flags.statusBarScreenSharingChips -import com.android.systemui.Flags.statusBarUseReposForCallChip import com.android.systemui.dagger.SysUISingleton import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.shared.flag.DualShade @@ -63,10 +57,6 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha // DualShade dependencies DualShade.token dependsOn SceneContainerFlag.getMainAconfigFlag() - - // Status bar chip dependencies - statusBarCallChipNotificationIconToken dependsOn statusBarUseReposForCallChipToken - statusBarCallChipNotificationIconToken dependsOn statusBarScreenSharingChipsToken } private inline val politeNotifications @@ -86,17 +76,4 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha private inline val communalHub get() = FlagToken(FLAG_COMMUNAL_HUB, communalHub()) - - private inline val statusBarCallChipNotificationIconToken - get() = - FlagToken( - FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, - statusBarCallChipNotificationIcon(), - ) - - private inline val statusBarScreenSharingChipsToken - get() = FlagToken(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, statusBarScreenSharingChips()) - - private inline val statusBarUseReposForCallChipToken - get() = FlagToken(FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP, statusBarUseReposForCallChip()) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt index de08e3891902..86954d569199 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.chips.call.ui.viewmodel import android.view.View import com.android.internal.jank.InteractionJankMonitor -import com.android.systemui.Flags import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon @@ -64,18 +63,12 @@ constructor( is OngoingCallModel.InCallWithVisibleApp -> OngoingActivityChipModel.Hidden() is OngoingCallModel.InCall -> { val icon = - if ( - Flags.statusBarCallChipNotificationIcon() && - state.notificationIconView != null - ) { + if (state.notificationIconView != null) { StatusBarConnectedDisplays.assertInLegacyMode() OngoingActivityChipModel.ChipIcon.StatusBarView( state.notificationIconView ) - } else if ( - StatusBarConnectedDisplays.isEnabled && - Flags.statusBarCallChipNotificationIcon() - ) { + } else if (StatusBarConnectedDisplays.isEnabled) { OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon( state.notificationKey ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt index 2f6431b05c8b..ec3a5b271e35 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt @@ -27,6 +27,7 @@ import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor +import com.android.systemui.statusbar.notification.domain.model.TopPinnedState import com.android.systemui.statusbar.notification.headsup.PinnedStatus import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel import javax.inject.Inject @@ -60,7 +61,7 @@ constructor( /** Converts the notification to the [OngoingActivityChipModel] object. */ private fun NotificationChipModel.toActivityChipModel( - headsUpState: PinnedStatus + headsUpState: TopPinnedState ): OngoingActivityChipModel.Shown { StatusBarNotifChips.assertInNewMode() val icon = @@ -87,8 +88,12 @@ constructor( } } - if (headsUpState == PinnedStatus.PinnedByUser) { - // If the user tapped the chip to show the HUN, we want to just show the icon because + val isShowingHeadsUpFromChipTap = + headsUpState is TopPinnedState.Pinned && + headsUpState.status == PinnedStatus.PinnedByUser && + headsUpState.key == this.key + if (isShowingHeadsUpFromChipTap) { + // If the user tapped this chip to show the HUN, we want to just show the icon because // the HUN will show the rest of the information. return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt index c81e8e211507..956d99e46766 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.chips.ui.model import android.view.View -import com.android.systemui.Flags import com.android.systemui.common.shared.model.Icon import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips @@ -130,10 +129,6 @@ sealed class OngoingActivityChipModel { */ data class StatusBarView(val impl: StatusBarIconView) : ChipIcon { init { - check(Flags.statusBarCallChipNotificationIcon()) { - "OngoingActivityChipModel.ChipIcon.StatusBarView created even though " + - "Flags.statusBarCallChipNotificationIcon is not enabled" - } StatusBarConnectedDisplays.assertInLegacyMode() } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt index c7535ec14d5d..eb5a3703bcfb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt @@ -112,20 +112,20 @@ constructor( if (StatusBarNotifChips.isEnabled) { applicationScope.launch { statusBarNotificationChipsInteractor.promotedNotificationChipTapEvent.collect { - showPromotedNotificationHeadsUp(it) + onPromotedNotificationChipTapEvent(it) } } } } /** - * Shows the promoted notification with the given [key] as heads-up. + * Updates the heads-up state based on which promoted notification with the given [key] was + * tapped. * * Must be run on the main thread. */ - private fun showPromotedNotificationHeadsUp(key: String) { + private fun onPromotedNotificationChipTapEvent(key: String) { StatusBarNotifChips.assertInNewMode() - mLogger.logShowPromotedNotificationHeadsUp(key) val entry = notifCollection.getEntry(key) if (entry == null) { @@ -135,22 +135,29 @@ constructor( // TODO(b/364653005): Validate that the given key indeed matches a promoted notification, // not just any notification. + val isCurrentlyHeadsUp = mHeadsUpManager.isHeadsUpEntry(entry.key) val posted = PostedEntry( entry, wasAdded = false, wasUpdated = false, - // Force-set this notification to show heads-up. - shouldHeadsUpEver = true, - shouldHeadsUpAgain = true, + // We want the chip to act as a toggle, so if the chip's notification is currently + // showing as heads up, then we should stop showing it. + shouldHeadsUpEver = !isCurrentlyHeadsUp, + shouldHeadsUpAgain = !isCurrentlyHeadsUp, isPinnedByUser = true, - isHeadsUpEntry = mHeadsUpManager.isHeadsUpEntry(entry.key), + isHeadsUpEntry = isCurrentlyHeadsUp, isBinding = isEntryBinding(entry), ) + if (isCurrentlyHeadsUp) { + mLogger.logHidePromotedNotificationHeadsUp(key) + } else { + mLogger.logShowPromotedNotificationHeadsUp(key) + } mExecutor.execute { mPostedEntries[entry.key] = posted - mNotifPromoter.invalidateList("showPromotedNotificationHeadsUp: ${entry.logKey}") + mNotifPromoter.invalidateList("onPromotedNotificationChipTapEvent: ${entry.logKey}") } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt index e443a0418ffd..5141aa35b041 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt @@ -148,6 +148,15 @@ class HeadsUpCoordinatorLogger(private val buffer: LogBuffer, private val verbos ) } + fun logHidePromotedNotificationHeadsUp(key: String) { + buffer.log( + TAG, + LogLevel.DEBUG, + { str1 = key }, + { "requesting promoted entry to hide heads up: $str1" }, + ) + } + fun logPromotedNotificationForHeadsUpNotFound(key: String) { buffer.log( TAG, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt index 75c7d2d5be98..6140c92369b3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt @@ -25,6 +25,7 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository +import com.android.systemui.statusbar.notification.domain.model.TopPinnedState import com.android.systemui.statusbar.notification.headsup.PinnedStatus import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey import javax.inject.Inject @@ -98,21 +99,39 @@ constructor( } } - /** What [PinnedStatus] does the top row have? */ - private val topPinnedStatus: Flow<PinnedStatus> = + /** What [PinnedStatus] and key does the top row have? */ + private val topPinnedState: Flow<TopPinnedState> = headsUpRepository.activeHeadsUpRows.flatMapLatest { rows -> if (rows.isNotEmpty()) { - combine(rows.map { it.pinnedStatus }) { pinnedStatus -> - pinnedStatus.firstOrNull { it.isPinned } ?: PinnedStatus.NotPinned + // For each row, emits a (key, pinnedStatus) pair each time any row's + // `pinnedStatus` changes + val toCombine: List<Flow<Pair<String, PinnedStatus>>> = + rows.map { row -> row.pinnedStatus.map { status -> row.key to status } } + combine(toCombine) { pairs -> + val topPinnedRow: Pair<String, PinnedStatus>? = + pairs.firstOrNull { it.second.isPinned } + if (topPinnedRow != null) { + TopPinnedState.Pinned( + key = topPinnedRow.first, + status = topPinnedRow.second, + ) + } else { + TopPinnedState.NothingPinned + } } } else { - // if the set is empty, there are no flows to combine - flowOf(PinnedStatus.NotPinned) + flowOf(TopPinnedState.NothingPinned) } } /** Are there any pinned heads up rows to display? */ - val hasPinnedRows: Flow<Boolean> = topPinnedStatus.map { it.isPinned } + val hasPinnedRows: Flow<Boolean> = + topPinnedState.map { + when (it) { + is TopPinnedState.Pinned -> it.status.isPinned + is TopPinnedState.NothingPinned -> false + } + } val isHeadsUpOrAnimatingAway: Flow<Boolean> by lazy { if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) { @@ -142,13 +161,25 @@ constructor( } } - /** Emits the pinned notification state as it relates to the status bar. */ - val statusBarHeadsUpState: Flow<PinnedStatus> = - combine(topPinnedStatus, canShowHeadsUp) { topPinnedStatus, canShowHeadsUp -> + /** + * Emits the pinned notification state as it relates to the status bar. Includes both the pinned + * status and key of the notification that's pinned (if there is a pinned notification). + */ + val statusBarHeadsUpState: Flow<TopPinnedState> = + combine(topPinnedState, canShowHeadsUp) { topPinnedState, canShowHeadsUp -> if (canShowHeadsUp) { - topPinnedStatus + topPinnedState } else { - PinnedStatus.NotPinned + TopPinnedState.NothingPinned + } + } + + /** Emits the pinned notification status as it relates to the status bar. */ + val statusBarHeadsUpStatus: Flow<PinnedStatus> = + statusBarHeadsUpState.map { + when (it) { + is TopPinnedState.Pinned -> it.status + is TopPinnedState.NothingPinned -> PinnedStatus.NotPinned } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt index 042389f7fde7..fd5973e0ab3b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt @@ -25,7 +25,6 @@ import android.graphics.drawable.Icon import android.service.notification.StatusBarNotification import android.util.ArrayMap import com.android.app.tracing.traceSection -import com.android.systemui.Flags import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.ListEntry @@ -132,12 +131,6 @@ private class ActiveNotificationsStoreBuilder( } private fun NotificationEntry.toModel(): ActiveNotificationModel { - val statusBarChipIcon = - if (Flags.statusBarCallChipNotificationIcon()) { - icons.statusBarChipIcon - } else { - null - } val promotedContent = if (PromotedNotificationContentModel.featureFlagEnabled()) { promotedNotificationContentModel @@ -158,7 +151,7 @@ private class ActiveNotificationsStoreBuilder( aodIcon = icons.aodIcon?.sourceIcon, shelfIcon = icons.shelfIcon?.sourceIcon, statusBarIcon = icons.statusBarIcon?.sourceIcon, - statusBarChipIconView = statusBarChipIcon, + statusBarChipIconView = icons.statusBarChipIcon, uid = sbn.uid, packageName = sbn.packageName, contentIntent = sbn.notification.contentIntent, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/model/TopPinnedState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/model/TopPinnedState.kt new file mode 100644 index 000000000000..51c448adf998 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/model/TopPinnedState.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2025 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.domain.model + +import com.android.systemui.statusbar.notification.headsup.PinnedStatus + +/** A class representing the state of the top pinned row. */ +sealed interface TopPinnedState { + /** Nothing is pinned. */ + data object NothingPinned : TopPinnedState + + /** + * The top pinned row is a notification with the given key and status. + * + * @property status must have [PinnedStatus.isPinned] as true. + */ + data class Pinned(val key: String, val status: PinnedStatus) : TopPinnedState { + init { + check(status.isPinned) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt index b56a838a80a5..31375cc4a03a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt @@ -162,9 +162,7 @@ constructor( val sbIcon = iconBuilder.createIconView(entry) sbIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE val sbChipIcon: StatusBarIconView? - if ( - Flags.statusBarCallChipNotificationIcon() && !StatusBarConnectedDisplays.isEnabled - ) { + if (!StatusBarConnectedDisplays.isEnabled) { sbChipIcon = iconBuilder.createIconView(entry) sbChipIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE } else { @@ -186,7 +184,7 @@ constructor( try { setIcon(entry, normalIconDescriptor, sbIcon) - if (Flags.statusBarCallChipNotificationIcon() && sbChipIcon != null) { + if (sbChipIcon != null) { setIcon(entry, normalIconDescriptor, sbChipIcon) } setIcon(entry, sensitiveIconDescriptor, shelfIcon) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt index c57cede754d3..f56c2d5dc5e8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt @@ -18,8 +18,6 @@ package com.android.systemui.statusbar.phone.ongoingcall import android.app.ActivityManager import android.app.IActivityManager -import android.app.Notification -import android.app.Notification.CallStyle.CALL_TYPE_ONGOING import android.app.PendingIntent import android.app.UidObserver import android.content.Context @@ -44,9 +42,6 @@ import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.chips.ui.view.ChipChronometer import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler -import com.android.systemui.statusbar.notification.collection.NotificationEntry -import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection -import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel import com.android.systemui.statusbar.notification.shared.CallType @@ -60,7 +55,9 @@ import java.util.concurrent.Executor import javax.inject.Inject import kotlinx.coroutines.CoroutineScope -/** A controller to handle the ongoing call chip in the collapsed status bar. +/** + * A controller to handle the ongoing call chip in the collapsed status bar. + * * @deprecated Use [OngoingCallInteractor] instead, which follows recommended architecture patterns */ @Deprecated("Use OngoingCallInteractor instead") @@ -71,7 +68,6 @@ constructor( @Application private val scope: CoroutineScope, private val context: Context, private val ongoingCallRepository: OngoingCallRepository, - private val notifCollection: CommonNotifCollection, private val activeNotificationsInteractor: ActiveNotificationsInteractor, private val systemClock: SystemClock, private val activityStarter: ActivityStarter, @@ -90,105 +86,24 @@ constructor( private val mListeners: MutableList<OngoingCallListener> = mutableListOf() private val uidObserver = CallAppUidObserver() - private val notifListener = - object : NotifCollectionListener { - // Temporary workaround for b/178406514 for testing purposes. - // - // b/178406514 means that posting an incoming call notif then updating it to an ongoing - // call notif does not work (SysUI never receives the update). This workaround allows us - // to trigger the ongoing call chip when an ongoing call notif is *added* rather than - // *updated*, allowing us to test the chip. - // - // TODO(b/183229367): Remove this function override when b/178406514 is fixed. - override fun onEntryAdded(entry: NotificationEntry) { - onEntryUpdated(entry, true) - } - - override fun onEntryUpdated(entry: NotificationEntry) { - StatusBarUseReposForCallChip.assertInLegacyMode() - // We have a new call notification or our existing call notification has been - // updated. - // TODO(b/183229367): This likely won't work if you take a call from one app then - // switch to a call from another app. - if ( - callNotificationInfo == null && isCallNotification(entry) || - (entry.sbn.key == callNotificationInfo?.key) - ) { - val newOngoingCallInfo = - CallNotificationInfo( - entry.sbn.key, - entry.sbn.notification.getWhen(), - // In this old listener pattern, we don't have access to the - // notification icon. - notificationIconView = null, - entry.sbn.notification.contentIntent, - entry.sbn.uid, - entry.sbn.notification.extras.getInt( - Notification.EXTRA_CALL_TYPE, - -1, - ) == CALL_TYPE_ONGOING, - statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false, - ) - if (newOngoingCallInfo == callNotificationInfo) { - return - } - - callNotificationInfo = newOngoingCallInfo - if (newOngoingCallInfo.isOngoing) { - logger.log( - TAG, - LogLevel.DEBUG, - { str1 = newOngoingCallInfo.key }, - { "Call notif *is* ongoing -> showing chip. key=$str1" }, - ) - updateChip() - } else { - logger.log( - TAG, - LogLevel.DEBUG, - { str1 = newOngoingCallInfo.key }, - { "Call notif not ongoing -> hiding chip. key=$str1" }, - ) - removeChip() - } - } - } - - override fun onEntryRemoved(entry: NotificationEntry, reason: Int) { - if (entry.sbn.key == callNotificationInfo?.key) { - logger.log( - TAG, - LogLevel.DEBUG, - { str1 = entry.sbn.key }, - { "Call notif removed -> hiding chip. key=$str1" }, - ) - removeChip() - } - } - } override fun start() { - if (StatusBarChipsModernization.isEnabled) - return + if (StatusBarChipsModernization.isEnabled) return dumpManager.registerDumpable(this) - if (Flags.statusBarUseReposForCallChip()) { - scope.launch { - // Listening to [ActiveNotificationsInteractor] instead of using - // [NotifCollectionListener#onEntryUpdated] is better for two reasons: - // 1. ActiveNotificationsInteractor automatically filters the notification list to - // just notifications for the current user, which ensures we don't show a call chip - // for User 1's call while User 2 is active (see b/328584859). - // 2. ActiveNotificationsInteractor only emits notifications that are currently - // present in the shade, which means we know we've already inflated the icon that we - // might use for the call chip (see b/354930838). - activeNotificationsInteractor.ongoingCallNotification.collect { - updateInfoFromNotifModel(it) - } + scope.launch { + // Listening to [ActiveNotificationsInteractor] instead of using + // [NotifCollectionListener#onEntryUpdated] is better for two reasons: + // 1. ActiveNotificationsInteractor automatically filters the notification list to + // just notifications for the current user, which ensures we don't show a call chip + // for User 1's call while User 2 is active (see b/328584859). + // 2. ActiveNotificationsInteractor only emits notifications that are currently + // present in the shade, which means we know we've already inflated the icon that we + // might use for the call chip (see b/354930838). + activeNotificationsInteractor.ongoingCallNotification.collect { + updateInfoFromNotifModel(it) } - } else { - notifCollection.addCollectionListener(notifListener) } scope.launch { @@ -244,21 +159,12 @@ constructor( logger.log( TAG, LogLevel.DEBUG, - { - bool1 = Flags.statusBarCallChipNotificationIcon() - bool2 = currentInfo.notificationIconView != null - }, - { "Creating OngoingCallModel.InCall. notifIconFlag=$bool1 hasIcon=$bool2" }, + { bool1 = currentInfo.notificationIconView != null }, + { "Creating OngoingCallModel.InCall. hasIcon=$bool1" }, ) - val icon = - if (Flags.statusBarCallChipNotificationIcon()) { - currentInfo.notificationIconView - } else { - null - } return OngoingCallModel.InCall( startTimeMs = currentInfo.callStartTime, - notificationIconView = icon, + notificationIconView = currentInfo.notificationIconView, intent = currentInfo.intent, notificationKey = currentInfo.key, ) @@ -597,8 +503,4 @@ constructor( } } -private fun isCallNotification(entry: NotificationEntry): Boolean { - return entry.sbn.notification.isStyle(Notification.CallStyle::class.java) -} - private const val TAG = OngoingCallRepository.TAG diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarUseReposForCallChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarUseReposForCallChip.kt deleted file mode 100644 index 4bdd90ebff3e..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarUseReposForCallChip.kt +++ /dev/null @@ -1,53 +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.phone.ongoingcall - -import com.android.systemui.Flags -import com.android.systemui.flags.FlagToken -import com.android.systemui.flags.RefactorFlagUtils - -/** Helper for reading or using the status bar use repos for call chip flag state. */ -@Suppress("NOTHING_TO_INLINE") -object StatusBarUseReposForCallChip { - /** The aconfig flag name */ - const val FLAG_NAME = Flags.FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP - - /** A token used for dependency declaration */ - val token: FlagToken - get() = FlagToken(FLAG_NAME, isEnabled) - - /** Is the refactor enabled */ - @JvmStatic - inline val isEnabled - get() = Flags.statusBarUseReposForCallChip() - - /** - * Called to ensure code is only run when the flag is enabled. This protects users from the - * unintended behaviors caused by accidentally running new logic, while also crashing on an eng - * build to ensure that the refactor author catches issues in testing. - */ - @JvmStatic - inline fun isUnexpectedlyInLegacyMode() = - RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) - - /** - * Called to ensure code is only run when the flag is disabled. This will throw an exception if - * the flag is enabled to ensure that the refactor author catches issues in testing. - */ - @JvmStatic - inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt index d731752ad5d5..d9d9a29ee2b6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt @@ -295,11 +295,12 @@ constructor( override val shouldShowOperatorNameView: Flow<Boolean> = combine( shouldHomeStatusBarBeVisible, - headsUpNotificationInteractor.statusBarHeadsUpState, + headsUpNotificationInteractor.statusBarHeadsUpStatus, homeStatusBarInteractor.visibilityViaDisableFlags, homeStatusBarInteractor.shouldShowOperatorName, - ) { shouldStatusBarBeVisible, headsUpState, visibilityViaDisableFlags, shouldShowOperator -> - val hideForHeadsUp = headsUpState == PinnedStatus.PinnedBySystem + ) { shouldStatusBarBeVisible, headsUpStatus, visibilityViaDisableFlags, shouldShowOperator + -> + val hideForHeadsUp = headsUpStatus == PinnedStatus.PinnedBySystem shouldStatusBarBeVisible && !hideForHeadsUp && visibilityViaDisableFlags.isSystemInfoAllowed && @@ -309,10 +310,10 @@ constructor( override val isClockVisible: Flow<VisibilityModel> = combine( shouldHomeStatusBarBeVisible, - headsUpNotificationInteractor.statusBarHeadsUpState, + headsUpNotificationInteractor.statusBarHeadsUpStatus, homeStatusBarInteractor.visibilityViaDisableFlags, - ) { shouldStatusBarBeVisible, headsUpState, visibilityViaDisableFlags -> - val hideClockForHeadsUp = headsUpState == PinnedStatus.PinnedBySystem + ) { shouldStatusBarBeVisible, headsUpStatus, visibilityViaDisableFlags -> + val hideClockForHeadsUp = headsUpStatus == PinnedStatus.PinnedBySystem val showClock = shouldStatusBarBeVisible && visibilityViaDisableFlags.isClockAllowed && diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt index 6175ea190697..a98a9e0c16d2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt @@ -60,7 +60,7 @@ constructor( private val showingHeadsUpStatusBar: Flow<Boolean> = if (SceneContainerFlag.isEnabled) { - headsUpNotificationInteractor.statusBarHeadsUpState.map { it.isPinned } + headsUpNotificationInteractor.statusBarHeadsUpStatus.map { it.isPinned } } else { flowOf(false) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt index 57a12df0cfee..c4ef4f978ff8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt @@ -35,7 +35,6 @@ import android.platform.test.annotations.EnableFlags import androidx.test.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.Flags.FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON import com.android.systemui.SysuiTestCase import com.android.systemui.controls.controller.AuxiliaryPersistenceWrapperTest.Companion.any import com.android.systemui.statusbar.core.StatusBarConnectedDisplays @@ -112,20 +111,8 @@ class IconManagerTest : SysuiTestCase() { } @Test - @DisableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON) - fun testCreateIcons_chipNotifIconFlagDisabled_statusBarChipIconIsNull() { - val entry = - notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = true) - entry?.let { iconManager.createIcons(it) } - testScope.runCurrent() - - assertThat(entry?.icons?.statusBarChipIcon).isNull() - } - - @Test - @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON) @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME) - fun testCreateIcons_chipNotifIconFlagEnabled_cdFlagDisabled_statusBarChipIconIsNotNull() { + fun testCreateIcons_cdFlagDisabled_statusBarChipIconIsNotNull() { val entry = notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = true) entry?.let { iconManager.createIcons(it) } @@ -135,8 +122,8 @@ class IconManagerTest : SysuiTestCase() { } @Test - @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME) - fun testCreateIcons_chipNotifIconFlagEnabled_cdFlagEnabled_statusBarChipIconIsNull() { + @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) + fun testCreateIcons_cdFlagEnabled_statusBarChipIconIsNull() { val entry = notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = true) entry?.let { iconManager.createIcons(it) } @@ -217,7 +204,6 @@ class IconManagerTest : SysuiTestCase() { } @Test - @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON) @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME) fun testCreateIcons_cdFlagDisabled_sensitiveImportantConversation() { val entry = @@ -233,7 +219,7 @@ class IconManagerTest : SysuiTestCase() { } @Test - @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME) + @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) fun testCreateIcons_cdFlagEnabled_sensitiveImportantConversation() { val entry = notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = false) @@ -248,7 +234,6 @@ class IconManagerTest : SysuiTestCase() { } @Test - @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON) @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME) fun testUpdateIcons_cdFlagDisabled_sensitiveImportantConversation() { val entry = @@ -266,7 +251,7 @@ class IconManagerTest : SysuiTestCase() { } @Test - @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME) + @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) fun testUpdateIcons_cdFlagEnabled_sensitiveImportantConversation() { val entry = notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = false) |