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

Commit bd3f1ff6 authored by Nick Chameyev's avatar Nick Chameyev Committed by Android (Google) Code Review
Browse files

Merge "Track notifications hidden time" into main

parents 6c63f33a 218de998
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