Loading core/java/com/android/internal/util/LatencyTracker.java +25 −0 Original line number Diff line number Diff line Loading @@ -26,6 +26,8 @@ import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPOR import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FACE_WAKE_AND_UNLOCK; import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FINGERPRINT_WAKE_AND_UNLOCK; import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD; import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE; import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN; import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME; import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOAD_SHARE_SHEET; import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK; Loading Loading @@ -234,6 +236,19 @@ public class LatencyTracker { */ public static final int ACTION_BACK_SYSTEM_ANIMATION = 25; /** * Time notifications spent in hidden state for performance reasons. We might temporary * hide notifications after display size changes (e.g. fold/unfold of a foldable device) * and measure them while they are hidden to unblock rendering of the rest of the UI. */ public static final int ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE = 26; /** * The same as {@link ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE} but tracks time only * when the notifications are hidden and when the shade is open or keyguard is visible. */ public static final int ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN = 27; private static final int[] ACTIONS_ALL = { ACTION_EXPAND_PANEL, ACTION_TOGGLE_RECENTS, Loading Loading @@ -261,6 +276,8 @@ public class LatencyTracker { ACTION_NOTIFICATION_BIG_PICTURE_LOADED, ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME, ACTION_BACK_SYSTEM_ANIMATION, ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE, ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN, }; /** @hide */ Loading Loading @@ -291,6 +308,8 @@ public class LatencyTracker { ACTION_NOTIFICATION_BIG_PICTURE_LOADED, ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME, ACTION_BACK_SYSTEM_ANIMATION, ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE, ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN, }) @Retention(RetentionPolicy.SOURCE) public @interface Action { Loading Loading @@ -324,6 +343,8 @@ public class LatencyTracker { UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATION_BIG_PICTURE_LOADED, UIACTION_LATENCY_REPORTED__ACTION__ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME, UIACTION_LATENCY_REPORTED__ACTION__ACTION_BACK_SYSTEM_ANIMATION, UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE, UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN, }; private final Object mLock = new Object(); Loading Loading @@ -514,6 +535,10 @@ public class LatencyTracker { return "ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME"; case UIACTION_LATENCY_REPORTED__ACTION__ACTION_BACK_SYSTEM_ANIMATION: return "ACTION_BACK_SYSTEM_ANIMATION"; case UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE: return "ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE"; case UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN: return "ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN"; default: throw new IllegalArgumentException("Invalid action"); } Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotificationsHiderTracker.kt 0 → 100644 +64 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.statusbar.notification.stack import com.android.internal.util.LatencyTracker import com.android.internal.util.LatencyTracker.ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE import com.android.internal.util.LatencyTracker.ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN import com.android.systemui.shade.domain.interactor.ShadeInteractor import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import javax.inject.Inject /** * Tracks latencies related to temporary hiding notifications while measuring * them, which is an optimization to show some content as early as possible * and perform notifications measurement later. * See [HideNotificationsInteractor]. */ class DisplaySwitchNotificationsHiderTracker @Inject constructor( private val notificationsInteractor: ShadeInteractor, private val latencyTracker: LatencyTracker ) { suspend fun trackNotificationHideTime(shouldHideNotifications: Flow<Boolean>) { shouldHideNotifications .collect { shouldHide -> if (shouldHide) { latencyTracker.onActionStart(ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE) } else { latencyTracker.onActionEnd(ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE) } } } suspend fun trackNotificationHideTimeWhenVisible(shouldHideNotifications: Flow<Boolean>) { combine(shouldHideNotifications, notificationsInteractor.isAnyExpanded) { hidden, shadeExpanded -> hidden && shadeExpanded } .distinctUntilChanged() .collect { hiddenButShouldBeVisible -> if (hiddenButShouldBeVisible) { latencyTracker.onActionStart( ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN) } else { latencyTracker.onActionEnd( ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN) } } } } No newline at end of file packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/HideNotificationsBinder.kt +18 −4 Original line number Diff line number Diff line Loading @@ -16,24 +16,38 @@ package com.android.systemui.statusbar.notification.stack.ui.viewbinder import androidx.core.view.doOnDetach import com.android.systemui.statusbar.notification.stack.DisplaySwitchNotificationsHiderTracker import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.launch /** * Binds a [NotificationStackScrollLayoutController] to its [view model][NotificationListViewModel]. */ object HideNotificationsBinder { suspend fun bindHideList( fun CoroutineScope.bindHideList( viewController: NotificationStackScrollLayoutController, viewModel: NotificationListViewModel viewModel: NotificationListViewModel, hiderTracker: DisplaySwitchNotificationsHiderTracker ) { viewController.view.doOnDetach { viewController.bindHideState(shouldHide = false) } viewModel.hideListViewModel.shouldHideListForPerformance.collect { shouldHide -> val hideListFlow = viewModel.hideListViewModel.shouldHideListForPerformance .shareIn(this, started = Lazily) launch { hideListFlow.collect { shouldHide -> viewController.bindHideState(shouldHide) } } launch { hiderTracker.trackNotificationHideTime(hideListFlow) } launch { hiderTracker.trackNotificationHideTimeWhenVisible(hideListFlow) } } private fun NotificationStackScrollLayoutController.bindHideState(shouldHide: Boolean) { if (shouldHide) { updateNotificationsContainerVisibility(/* visible= */ false, /* animate=*/ false) Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt +3 −1 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterVi import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerShelfViewBinder import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinder import com.android.systemui.statusbar.notification.stack.DisplaySwitchNotificationsHiderTracker import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.notification.stack.ui.view.NotificationStatsLogger Loading @@ -53,6 +54,7 @@ class NotificationListViewBinder @Inject constructor( @Background private val backgroundDispatcher: CoroutineDispatcher, private val hiderTracker: DisplaySwitchNotificationsHiderTracker, private val configuration: ConfigurationState, private val falsingManager: FalsingManager, private val iconAreaController: NotificationIconAreaController, Loading @@ -74,7 +76,7 @@ constructor( view.repeatWhenAttached { lifecycleScope.launch { launch { bindShelf(shelf) } launch { bindHideList(viewController, viewModel) } bindHideList(viewController, viewModel, hiderTracker) if (FooterViewRefactor.isEnabled) { launch { bindFooter(view) } Loading packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotificationsHiderTrackerTest.kt 0 → 100644 +163 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.statusbar.notification.stack import androidx.test.filters.SmallTest import com.android.internal.util.LatencyTracker import com.android.internal.util.LatencyTracker.ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE import com.android.internal.util.LatencyTracker.ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN import com.android.systemui.SysuiTestCase import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.never import org.mockito.Mockito.verify @SmallTest @OptIn(ExperimentalCoroutinesApi::class) class DisplaySwitchNotificationsHiderTrackerTest : SysuiTestCase() { private val testScope = TestScope() private val shadeInteractor = mock<ShadeInteractor>() private val latencyTracker = mock<LatencyTracker>() private val shouldHideFlow = MutableStateFlow(false) private val shadeExpandedFlow = MutableStateFlow(false) private val tracker = DisplaySwitchNotificationsHiderTracker(shadeInteractor, latencyTracker) @Before fun setup() { whenever(shadeInteractor.isAnyExpanded).thenReturn(shadeExpandedFlow) } @Test fun notificationsBecomeHidden_tracksHideActionStart() = testScope.runTest { startTracking() shouldHideFlow.value = true runCurrent() verify(latencyTracker).onActionStart(HIDE_NOTIFICATIONS_ACTION) } @Test fun notificationsBecomeVisibleAfterHidden_tracksHideActionEnd() = testScope.runTest { startTracking() shouldHideFlow.value = true runCurrent() clearInvocations(latencyTracker) shouldHideFlow.value = false runCurrent() verify(latencyTracker).onActionEnd(HIDE_NOTIFICATIONS_ACTION) } @Test fun notificationsBecomeHiddenWhenShadeIsClosed_doesNotTrackHideWhenVisibleActionStart() = testScope.runTest { shouldHideFlow.value = false shadeExpandedFlow.value = false startTracking() shouldHideFlow.value = true runCurrent() verify(latencyTracker, never()) .onActionStart(HIDE_NOTIFICATIONS_WHEN_VISIBLE_ACTION) } @Test fun notificationsBecomeHiddenWhenShadeIsOpen_tracksHideWhenVisibleActionStart() = testScope.runTest { shouldHideFlow.value = false shadeExpandedFlow.value = false startTracking() shouldHideFlow.value = true shadeExpandedFlow.value = true runCurrent() verify(latencyTracker).onActionStart(HIDE_NOTIFICATIONS_WHEN_VISIBLE_ACTION) } @Test fun shadeBecomesOpenWhenNotificationsHidden_tracksHideWhenVisibleActionStart() = testScope.runTest { shouldHideFlow.value = true shadeExpandedFlow.value = false startTracking() shadeExpandedFlow.value = true runCurrent() verify(latencyTracker).onActionStart(HIDE_NOTIFICATIONS_WHEN_VISIBLE_ACTION) } @Test fun notificationsBecomeVisibleWhenShadeIsOpen_tracksHideWhenVisibleActionEnd() = testScope.runTest { shouldHideFlow.value = false shadeExpandedFlow.value = false startTracking() shouldHideFlow.value = true shadeExpandedFlow.value = true runCurrent() clearInvocations(latencyTracker) shouldHideFlow.value = false runCurrent() verify(latencyTracker).onActionEnd(HIDE_NOTIFICATIONS_WHEN_VISIBLE_ACTION) } @Test fun shadeBecomesClosedWhenNotificationsHidden_tracksHideWhenVisibleActionEnd() = testScope.runTest { shouldHideFlow.value = false shadeExpandedFlow.value = false startTracking() shouldHideFlow.value = true shadeExpandedFlow.value = true runCurrent() clearInvocations(latencyTracker) shadeExpandedFlow.value = false runCurrent() verify(latencyTracker).onActionEnd(HIDE_NOTIFICATIONS_WHEN_VISIBLE_ACTION) } private fun TestScope.startTracking() { backgroundScope.launch { tracker.trackNotificationHideTime(shouldHideFlow) } backgroundScope.launch { tracker.trackNotificationHideTimeWhenVisible(shouldHideFlow) } runCurrent() clearInvocations(latencyTracker) } private companion object { const val HIDE_NOTIFICATIONS_ACTION = ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE const val HIDE_NOTIFICATIONS_WHEN_VISIBLE_ACTION = ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN } } Loading
core/java/com/android/internal/util/LatencyTracker.java +25 −0 Original line number Diff line number Diff line Loading @@ -26,6 +26,8 @@ import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPOR import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FACE_WAKE_AND_UNLOCK; import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FINGERPRINT_WAKE_AND_UNLOCK; import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD; import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE; import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN; import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME; import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOAD_SHARE_SHEET; import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK; Loading Loading @@ -234,6 +236,19 @@ public class LatencyTracker { */ public static final int ACTION_BACK_SYSTEM_ANIMATION = 25; /** * Time notifications spent in hidden state for performance reasons. We might temporary * hide notifications after display size changes (e.g. fold/unfold of a foldable device) * and measure them while they are hidden to unblock rendering of the rest of the UI. */ public static final int ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE = 26; /** * The same as {@link ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE} but tracks time only * when the notifications are hidden and when the shade is open or keyguard is visible. */ public static final int ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN = 27; private static final int[] ACTIONS_ALL = { ACTION_EXPAND_PANEL, ACTION_TOGGLE_RECENTS, Loading Loading @@ -261,6 +276,8 @@ public class LatencyTracker { ACTION_NOTIFICATION_BIG_PICTURE_LOADED, ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME, ACTION_BACK_SYSTEM_ANIMATION, ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE, ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN, }; /** @hide */ Loading Loading @@ -291,6 +308,8 @@ public class LatencyTracker { ACTION_NOTIFICATION_BIG_PICTURE_LOADED, ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME, ACTION_BACK_SYSTEM_ANIMATION, ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE, ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN, }) @Retention(RetentionPolicy.SOURCE) public @interface Action { Loading Loading @@ -324,6 +343,8 @@ public class LatencyTracker { UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATION_BIG_PICTURE_LOADED, UIACTION_LATENCY_REPORTED__ACTION__ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME, UIACTION_LATENCY_REPORTED__ACTION__ACTION_BACK_SYSTEM_ANIMATION, UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE, UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN, }; private final Object mLock = new Object(); Loading Loading @@ -514,6 +535,10 @@ public class LatencyTracker { return "ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME"; case UIACTION_LATENCY_REPORTED__ACTION__ACTION_BACK_SYSTEM_ANIMATION: return "ACTION_BACK_SYSTEM_ANIMATION"; case UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE: return "ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE"; case UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN: return "ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN"; default: throw new IllegalArgumentException("Invalid action"); } Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotificationsHiderTracker.kt 0 → 100644 +64 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.statusbar.notification.stack import com.android.internal.util.LatencyTracker import com.android.internal.util.LatencyTracker.ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE import com.android.internal.util.LatencyTracker.ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN import com.android.systemui.shade.domain.interactor.ShadeInteractor import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import javax.inject.Inject /** * Tracks latencies related to temporary hiding notifications while measuring * them, which is an optimization to show some content as early as possible * and perform notifications measurement later. * See [HideNotificationsInteractor]. */ class DisplaySwitchNotificationsHiderTracker @Inject constructor( private val notificationsInteractor: ShadeInteractor, private val latencyTracker: LatencyTracker ) { suspend fun trackNotificationHideTime(shouldHideNotifications: Flow<Boolean>) { shouldHideNotifications .collect { shouldHide -> if (shouldHide) { latencyTracker.onActionStart(ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE) } else { latencyTracker.onActionEnd(ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE) } } } suspend fun trackNotificationHideTimeWhenVisible(shouldHideNotifications: Flow<Boolean>) { combine(shouldHideNotifications, notificationsInteractor.isAnyExpanded) { hidden, shadeExpanded -> hidden && shadeExpanded } .distinctUntilChanged() .collect { hiddenButShouldBeVisible -> if (hiddenButShouldBeVisible) { latencyTracker.onActionStart( ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN) } else { latencyTracker.onActionEnd( ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN) } } } } No newline at end of file
packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/HideNotificationsBinder.kt +18 −4 Original line number Diff line number Diff line Loading @@ -16,24 +16,38 @@ package com.android.systemui.statusbar.notification.stack.ui.viewbinder import androidx.core.view.doOnDetach import com.android.systemui.statusbar.notification.stack.DisplaySwitchNotificationsHiderTracker import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.launch /** * Binds a [NotificationStackScrollLayoutController] to its [view model][NotificationListViewModel]. */ object HideNotificationsBinder { suspend fun bindHideList( fun CoroutineScope.bindHideList( viewController: NotificationStackScrollLayoutController, viewModel: NotificationListViewModel viewModel: NotificationListViewModel, hiderTracker: DisplaySwitchNotificationsHiderTracker ) { viewController.view.doOnDetach { viewController.bindHideState(shouldHide = false) } viewModel.hideListViewModel.shouldHideListForPerformance.collect { shouldHide -> val hideListFlow = viewModel.hideListViewModel.shouldHideListForPerformance .shareIn(this, started = Lazily) launch { hideListFlow.collect { shouldHide -> viewController.bindHideState(shouldHide) } } launch { hiderTracker.trackNotificationHideTime(hideListFlow) } launch { hiderTracker.trackNotificationHideTimeWhenVisible(hideListFlow) } } private fun NotificationStackScrollLayoutController.bindHideState(shouldHide: Boolean) { if (shouldHide) { updateNotificationsContainerVisibility(/* visible= */ false, /* animate=*/ false) Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt +3 −1 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterVi import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerShelfViewBinder import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinder import com.android.systemui.statusbar.notification.stack.DisplaySwitchNotificationsHiderTracker import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.notification.stack.ui.view.NotificationStatsLogger Loading @@ -53,6 +54,7 @@ class NotificationListViewBinder @Inject constructor( @Background private val backgroundDispatcher: CoroutineDispatcher, private val hiderTracker: DisplaySwitchNotificationsHiderTracker, private val configuration: ConfigurationState, private val falsingManager: FalsingManager, private val iconAreaController: NotificationIconAreaController, Loading @@ -74,7 +76,7 @@ constructor( view.repeatWhenAttached { lifecycleScope.launch { launch { bindShelf(shelf) } launch { bindHideList(viewController, viewModel) } bindHideList(viewController, viewModel, hiderTracker) if (FooterViewRefactor.isEnabled) { launch { bindFooter(view) } Loading
packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotificationsHiderTrackerTest.kt 0 → 100644 +163 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.statusbar.notification.stack import androidx.test.filters.SmallTest import com.android.internal.util.LatencyTracker import com.android.internal.util.LatencyTracker.ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE import com.android.internal.util.LatencyTracker.ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN import com.android.systemui.SysuiTestCase import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.never import org.mockito.Mockito.verify @SmallTest @OptIn(ExperimentalCoroutinesApi::class) class DisplaySwitchNotificationsHiderTrackerTest : SysuiTestCase() { private val testScope = TestScope() private val shadeInteractor = mock<ShadeInteractor>() private val latencyTracker = mock<LatencyTracker>() private val shouldHideFlow = MutableStateFlow(false) private val shadeExpandedFlow = MutableStateFlow(false) private val tracker = DisplaySwitchNotificationsHiderTracker(shadeInteractor, latencyTracker) @Before fun setup() { whenever(shadeInteractor.isAnyExpanded).thenReturn(shadeExpandedFlow) } @Test fun notificationsBecomeHidden_tracksHideActionStart() = testScope.runTest { startTracking() shouldHideFlow.value = true runCurrent() verify(latencyTracker).onActionStart(HIDE_NOTIFICATIONS_ACTION) } @Test fun notificationsBecomeVisibleAfterHidden_tracksHideActionEnd() = testScope.runTest { startTracking() shouldHideFlow.value = true runCurrent() clearInvocations(latencyTracker) shouldHideFlow.value = false runCurrent() verify(latencyTracker).onActionEnd(HIDE_NOTIFICATIONS_ACTION) } @Test fun notificationsBecomeHiddenWhenShadeIsClosed_doesNotTrackHideWhenVisibleActionStart() = testScope.runTest { shouldHideFlow.value = false shadeExpandedFlow.value = false startTracking() shouldHideFlow.value = true runCurrent() verify(latencyTracker, never()) .onActionStart(HIDE_NOTIFICATIONS_WHEN_VISIBLE_ACTION) } @Test fun notificationsBecomeHiddenWhenShadeIsOpen_tracksHideWhenVisibleActionStart() = testScope.runTest { shouldHideFlow.value = false shadeExpandedFlow.value = false startTracking() shouldHideFlow.value = true shadeExpandedFlow.value = true runCurrent() verify(latencyTracker).onActionStart(HIDE_NOTIFICATIONS_WHEN_VISIBLE_ACTION) } @Test fun shadeBecomesOpenWhenNotificationsHidden_tracksHideWhenVisibleActionStart() = testScope.runTest { shouldHideFlow.value = true shadeExpandedFlow.value = false startTracking() shadeExpandedFlow.value = true runCurrent() verify(latencyTracker).onActionStart(HIDE_NOTIFICATIONS_WHEN_VISIBLE_ACTION) } @Test fun notificationsBecomeVisibleWhenShadeIsOpen_tracksHideWhenVisibleActionEnd() = testScope.runTest { shouldHideFlow.value = false shadeExpandedFlow.value = false startTracking() shouldHideFlow.value = true shadeExpandedFlow.value = true runCurrent() clearInvocations(latencyTracker) shouldHideFlow.value = false runCurrent() verify(latencyTracker).onActionEnd(HIDE_NOTIFICATIONS_WHEN_VISIBLE_ACTION) } @Test fun shadeBecomesClosedWhenNotificationsHidden_tracksHideWhenVisibleActionEnd() = testScope.runTest { shouldHideFlow.value = false shadeExpandedFlow.value = false startTracking() shouldHideFlow.value = true shadeExpandedFlow.value = true runCurrent() clearInvocations(latencyTracker) shadeExpandedFlow.value = false runCurrent() verify(latencyTracker).onActionEnd(HIDE_NOTIFICATIONS_WHEN_VISIBLE_ACTION) } private fun TestScope.startTracking() { backgroundScope.launch { tracker.trackNotificationHideTime(shouldHideFlow) } backgroundScope.launch { tracker.trackNotificationHideTimeWhenVisible(shouldHideFlow) } runCurrent() clearInvocations(latencyTracker) } private companion object { const val HIDE_NOTIFICATIONS_ACTION = ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE const val HIDE_NOTIFICATIONS_WHEN_VISIBLE_ACTION = ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN } }