Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 218de998 authored by Nick Chameyev's avatar Nick Chameyev
Browse files

Track notifications hidden time

Adds tracking for two actions:
* ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE
  tracks time notifications spent in 'hidden' state
* ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN
  tracks time notifications spent in 'hidden' state when
  otherwise they should be visible

Bug: 318342273
Test: atest DisplaySwitchNotificationsHiderTrackerTest
Test: manually check that LatencyTracker is invoked
  when notifications are hidden
Flag: ACONFIG notifications_hide_on_display_switch DISABLED
Change-Id: I847cc8959247ce41822f719f18fa910b3427f747
parent 8fa40be2
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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,
@@ -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 */
@@ -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 {
@@ -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();
@@ -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");
        }
+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
+18 −4
Original line number Diff line number Diff line
@@ -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)
+3 −1
Original line number Diff line number Diff line
@@ -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
@@ -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,
@@ -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) }
+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