diff options
4 files changed, 251 insertions, 18 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt index 7a33cbe761c1..9442367e82a3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt @@ -404,7 +404,7 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { } fun assertIsCallChip(latest: OngoingActivityChipModel?, notificationKey: String) { - assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) if (StatusBarConnectedDisplays.isEnabled) { assertNotificationIcon(latest, notificationKey) return diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt index 78103a9906c4..a03500e5f08f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt @@ -18,15 +18,20 @@ package com.android.systemui.statusbar.chips.ui.viewmodel import android.content.DialogInterface import android.content.packageManager +import android.content.res.Configuration +import android.content.res.mainResources import android.graphics.Bitmap import android.graphics.drawable.BitmapDrawable +import android.platform.test.annotations.DisableFlags 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.SysuiTestCase import com.android.systemui.animation.Expandable +import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.display.data.repository.displayStateRepository import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.mediaprojection.data.model.MediaProjectionState @@ -48,21 +53,21 @@ import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsVie import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.assertIsScreenRecordChip import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.assertIsShareToAppChip import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog -import com.android.systemui.statusbar.commandline.commandRegistry +import com.android.systemui.statusbar.core.StatusBarRootModernization 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 import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel +import com.android.systemui.statusbar.notification.shared.CallType import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization 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.phone.ongoingcall.shared.model.inCallModel import com.android.systemui.testKosmos import com.android.systemui.util.time.fakeSystemClock import com.google.common.truth.Truth.assertThat -import java.io.PrintWriter -import java.io.StringWriter import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -85,15 +90,12 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val testScope = kosmos.testScope private val systemClock = kosmos.fakeSystemClock - private val commandRegistry = kosmos.commandRegistry private val screenRecordState = kosmos.screenRecordRepository.screenRecordState private val mediaProjectionState = kosmos.fakeMediaProjectionRepository.mediaProjectionState private val callRepo = kosmos.ongoingCallRepository private val activeNotificationListRepository = kosmos.activeNotificationListRepository - private val pw = PrintWriter(StringWriter()) - private val mockSystemUIDialog = mock<SystemUIDialog>() private val chipBackgroundView = mock<ChipBackgroundContainer>() private val chipView = @@ -202,6 +204,152 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { } @Test + fun chips_oneChip_notSquished() = + testScope.runTest { + callRepo.setOngoingCallState(inCallModel(startTimeMs = 34, notificationKey = "call")) + + val latest by collectLastValue(underTest.chips) + + // The call chip isn't squished (squished chips would be icon only) + assertThat(latest!!.primary) + .isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java) + } + + @Test + @DisableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME) + fun chips_twoTimerChips_isSmallPortrait_andChipsModernizationDisabled_bothSquished() = + testScope.runTest { + screenRecordState.value = ScreenRecordModel.Recording + callRepo.setOngoingCallState(inCallModel(startTimeMs = 34, notificationKey = "call")) + + val latest by collectLastValue(underTest.chips) + + // Squished chips are icon only + assertThat(latest!!.primary) + .isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java) + assertThat(latest!!.secondary) + .isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java) + } + + @Test + @DisableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME) + fun chips_countdownChipAndTimerChip_countdownNotSquished_butTimerSquished() = + testScope.runTest { + screenRecordState.value = ScreenRecordModel.Starting(millisUntilStarted = 2000) + callRepo.setOngoingCallState(inCallModel(startTimeMs = 34, notificationKey = "call")) + + val latest by collectLastValue(underTest.chips) + + // The screen record countdown isn't squished to icon-only + assertThat(latest!!.primary) + .isInstanceOf(OngoingActivityChipModel.Shown.Countdown::class.java) + // But the call chip *is* squished + assertThat(latest!!.secondary) + .isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java) + } + + @Test + @DisableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME) + fun chips_numberOfChipsChanges_chipsGetSquishedAndUnsquished() = + testScope.runTest { + val latest by collectLastValue(underTest.chips) + + // WHEN there's only one chip + screenRecordState.value = ScreenRecordModel.Recording + callRepo.setOngoingCallState(OngoingCallModel.NoCall) + + // The screen record isn't squished because it's the only one + assertThat(latest!!.primary) + .isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java) + assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) + + // WHEN there's 2 chips + callRepo.setOngoingCallState(inCallModel(startTimeMs = 34, notificationKey = "call")) + + // THEN they both become squished + assertThat(latest!!.primary) + .isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java) + // But the call chip *is* squished + assertThat(latest!!.secondary) + .isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java) + + // WHEN we go back down to 1 chip + screenRecordState.value = ScreenRecordModel.DoingNothing + + // THEN the remaining chip unsquishes + assertThat(latest!!.primary) + .isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java) + assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) + } + + @Test + @DisableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME) + fun chips_twoChips_isLandscape_notSquished() = + testScope.runTest { + screenRecordState.value = ScreenRecordModel.Recording + callRepo.setOngoingCallState(inCallModel(startTimeMs = 34, notificationKey = "call")) + + // WHEN we're in landscape + val config = + Configuration(kosmos.mainResources.configuration).apply { + orientation = Configuration.ORIENTATION_LANDSCAPE + } + kosmos.fakeConfigurationRepository.onConfigurationChange(config) + + val latest by collectLastValue(underTest.chips) + + // THEN the chips aren't squished (squished chips would be icon only) + assertThat(latest!!.primary) + .isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java) + assertThat(latest!!.secondary) + .isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java) + } + + @Test + @DisableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME) + fun chips_twoChips_isLargeScreen_notSquished() = + testScope.runTest { + screenRecordState.value = ScreenRecordModel.Recording + callRepo.setOngoingCallState(inCallModel(startTimeMs = 34, notificationKey = "call")) + + // WHEN we're on a large screen + kosmos.displayStateRepository.setIsLargeScreen(true) + + val latest by collectLastValue(underTest.chips) + + // THEN the chips aren't squished (squished chips would be icon only) + assertThat(latest!!.primary) + .isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java) + assertThat(latest!!.secondary) + .isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java) + } + + @Test + @EnableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME) + fun chips_twoChips_chipsModernizationEnabled_notSquished() = + testScope.runTest { + screenRecordState.value = ScreenRecordModel.Recording + setNotifs( + listOf( + activeNotificationModel( + key = "call", + statusBarChipIcon = createStatusBarIconViewOrNull(), + callType = CallType.Ongoing, + whenTime = 499, + ) + ) + ) + + val latest by collectLastValue(underTest.chips) + + // Squished chips would be icon only + assertThat(latest!!.primary) + .isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java) + assertThat(latest!!.secondary) + .isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java) + } + + @Test fun primaryChip_screenRecordShowAndShareToAppShow_screenRecordShown() = testScope.runTest { screenRecordState.value = ScreenRecordModel.Recording diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt index baa9d8b8b794..1451154d25ab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt @@ -16,6 +16,9 @@ package com.android.systemui.statusbar.chips.ui.viewmodel +import android.content.res.Configuration +import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor +import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.LogBuffer @@ -30,6 +33,7 @@ import com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel.ScreenReco import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.ShareToAppChipViewModel import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel +import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -37,7 +41,9 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn /** @@ -56,8 +62,30 @@ constructor( castToOtherDeviceChipViewModel: CastToOtherDeviceChipViewModel, callChipViewModel: CallChipViewModel, notifChipsViewModel: NotifChipsViewModel, + displayStateInteractor: DisplayStateInteractor, + configurationInteractor: ConfigurationInteractor, @StatusBarChipsLog private val logger: LogBuffer, ) { + private val isLandscape: Flow<Boolean> = + configurationInteractor.configurationValues + .map { it.isLandscape } + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + + private val isScreenReasonablyLarge: Flow<Boolean> = + combine(isLandscape, displayStateInteractor.isLargeScreen) { isLandscape, isLargeScreen -> + isLandscape || isLargeScreen + } + .distinctUntilChanged() + .onEach { + logger.log( + TAG, + LogLevel.DEBUG, + { bool1 = it }, + { "isScreenReasonablyLarge: $bool1" }, + ) + } + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + private enum class ChipType { ScreenRecord, ShareToApp, @@ -165,22 +193,72 @@ constructor( ) private val internalChips: Flow<InternalMultipleOngoingActivityChipsModel> = - incomingChipBundle.map { bundle -> + combine(incomingChipBundle, isScreenReasonablyLarge) { bundle, isScreenReasonablyLarge -> // First: Find the most important chip. val primaryChipResult = pickMostImportantChip(bundle) - val primaryChip = primaryChipResult.mostImportantChip - if (primaryChip is InternalChipModel.Hidden) { - // If the primary chip is hidden, the secondary chip will also be hidden, so just - // pass the same Hidden model for both. - InternalMultipleOngoingActivityChipsModel(primaryChip, primaryChip) - } else { - // Then: Find the next most important chip. - val secondaryChip = - pickMostImportantChip(primaryChipResult.remainingChips).mostImportantChip - InternalMultipleOngoingActivityChipsModel(primaryChip, secondaryChip) + when (val primaryChip = primaryChipResult.mostImportantChip) { + is InternalChipModel.Hidden -> { + // If the primary chip is hidden, the secondary chip will also be hidden, so + // just pass the same Hidden model for both. + InternalMultipleOngoingActivityChipsModel(primaryChip, primaryChip) + } + is InternalChipModel.Shown -> { + // Otherwise: Find the next most important chip. + val secondaryChip = + pickMostImportantChip(primaryChipResult.remainingChips).mostImportantChip + if ( + secondaryChip is InternalChipModel.Shown && + StatusBarNotifChips.isEnabled && + !StatusBarChipsModernization.isEnabled && + !isScreenReasonablyLarge + ) { + // If we have two showing chips and we don't have a ton of room + // (!isScreenReasonablyLarge), then we want to make both of them as small as + // possible so that we have the highest chance of showing both chips (as + // opposed to showing the primary chip with a lot of text and completely + // hiding the secondary chip). + // Also: If StatusBarChipsModernization is enabled, then we'll do the + // squishing in Compose instead. + InternalMultipleOngoingActivityChipsModel( + primaryChip.squish(), + secondaryChip.squish(), + ) + } else { + InternalMultipleOngoingActivityChipsModel(primaryChip, secondaryChip) + } + } } } + /** Squishes the chip down to the smallest content possible. */ + private fun InternalChipModel.Shown.squish(): InternalChipModel.Shown { + return when (model) { + // Icon-only is already maximum squished + is OngoingActivityChipModel.Shown.IconOnly -> this + // Countdown shows just a single digit, so already maximum squished + is OngoingActivityChipModel.Shown.Countdown -> this + // The other chips have icon+text, so we should hide the text + is OngoingActivityChipModel.Shown.Timer, + is OngoingActivityChipModel.Shown.ShortTimeDelta, + is OngoingActivityChipModel.Shown.Text -> + InternalChipModel.Shown(this.type, this.model.toIconOnly()) + } + } + + private fun OngoingActivityChipModel.Shown.toIconOnly(): OngoingActivityChipModel.Shown { + // If this chip doesn't have an icon, then it only has text and we should continue showing + // its text. (This is theoretically impossible because + // [OngoingActivityChipModel.Shown.Countdown] is the only chip without an icon, but protect + // against it just in case.) + val currentIcon = icon ?: return this + return OngoingActivityChipModel.Shown.IconOnly( + currentIcon, + colors, + onClickListenerLegacy, + clickBehavior, + ) + } + /** * A flow modeling the primary chip that should be shown in the status bar after accounting for * possibly multiple ongoing activities and animation requirements. @@ -327,6 +405,9 @@ constructor( } } + private val Configuration.isLandscape: Boolean + get() = orientation == Configuration.ORIENTATION_LANDSCAPE + companion object { private val TAG = "ChipsViewModel".pad() diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt index ee34aa642e51..212049a0ce52 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.chips.ui.viewmodel +import com.android.systemui.biometrics.domain.interactor.displayStateInteractor +import com.android.systemui.common.ui.domain.interactor.configurationInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.statusbar.chips.call.ui.viewmodel.callChipViewModel @@ -34,6 +36,8 @@ val Kosmos.ongoingActivityChipsViewModel: OngoingActivityChipsViewModel by castToOtherDeviceChipViewModel = castToOtherDeviceChipViewModel, callChipViewModel = callChipViewModel, notifChipsViewModel = notifChipsViewModel, + displayStateInteractor = displayStateInteractor, + configurationInteractor = configurationInteractor, logger = statusBarChipsLogger, ) } |