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

Commit 1a1976c3 authored by Nicolo' Mazzucato's avatar Nicolo' Mazzucato
Browse files

Monitor fold to AOD latency

This logs the latency from when we receive the device state change (device folded), to when the AOD animation starts.

Bug: 237270816
Test: atest FoldAodAnimationControllerTest
Change-Id: I67e0e04bbc24064640f46cdc8feb667814170529
Merged-In: I67e0e04bbc24064640f46cdc8feb667814170529
parent 4d334f45
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -142,6 +142,11 @@ public class LatencyTracker {
     */
    public static final int ACTION_LOAD_SHARE_SHEET = 16;

    /**
     * Time it takes to show AOD display after folding the device.
     */
    public static final int ACTION_FOLD_TO_AOD = 17;

    private static final int[] ACTIONS_ALL = {
        ACTION_EXPAND_PANEL,
        ACTION_TOGGLE_RECENTS,
@@ -160,6 +165,7 @@ public class LatencyTracker {
        ACTION_UDFPS_ILLUMINATE,
        ACTION_SHOW_BACK_ARROW,
        ACTION_LOAD_SHARE_SHEET,
        ACTION_FOLD_TO_AOD,
    };

    /** @hide */
@@ -181,6 +187,7 @@ public class LatencyTracker {
        ACTION_UDFPS_ILLUMINATE,
        ACTION_SHOW_BACK_ARROW,
        ACTION_LOAD_SHARE_SHEET,
        ACTION_FOLD_TO_AOD
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface Action {
@@ -204,6 +211,7 @@ public class LatencyTracker {
            FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_UDFPS_ILLUMINATE,
            FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_BACK_ARROW,
            FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOAD_SHARE_SHEET,
            FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD
    };

    private static LatencyTracker sLatencyTracker;
@@ -297,6 +305,8 @@ public class LatencyTracker {
                return "ACTION_SHOW_BACK_ARROW";
            case 17:
                return "ACTION_LOAD_SHARE_SHEET";
            case 19:
                return "ACTION_FOLD_TO_AOD";
            default:
                throw new IllegalArgumentException("Invalid action");
        }
+28 −20
Original line number Diff line number Diff line
@@ -3881,29 +3881,37 @@ public final class NotificationPanelViewController extends PanelViewController {
    }

    /**
     * Starts fold to AOD animation
     * Starts fold to AOD animation.
     *
     * @param startAction invoked when the animation starts.
     * @param endAction invoked when the animation finishes, also if it was cancelled.
     * @param cancelAction invoked when the animation is cancelled, before endAction.
     */
    public void startFoldToAodAnimation(Runnable endAction) {
    public void startFoldToAodAnimation(Runnable startAction, Runnable endAction,
            Runnable cancelAction) {
        mView.animate()
            .translationX(0)
            .alpha(1f)
            .setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
            .setInterpolator(EMPHASIZED_DECELERATE)
            .setListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationStart(Animator animation) {
                    startAction.run();
                }

                @Override
                public void onAnimationCancel(Animator animation) {
                        endAction.run();
                    cancelAction.run();
                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    endAction.run();
                }
                })
                .setUpdateListener(anim -> {
            }).setUpdateListener(anim -> {
                mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
                })
                .start();
            }).start();
    }

    /**
+55 −11
Original line number Diff line number Diff line
@@ -22,11 +22,12 @@ import android.os.Handler
import android.os.PowerManager
import android.provider.Settings
import androidx.core.view.OneShotPreDrawListener
import com.android.internal.util.LatencyTracker
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.phone.ScreenOffAnimation
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.phone.ScreenOffAnimation
import com.android.systemui.statusbar.policy.CallbackController
import com.android.systemui.unfold.FoldAodAnimationController.FoldAodAnimationStatus
import com.android.systemui.util.settings.GlobalSettings
@@ -47,7 +48,8 @@ constructor(
    private val context: Context,
    private val deviceStateManager: DeviceStateManager,
    private val wakefulnessLifecycle: WakefulnessLifecycle,
    private val globalSettings: GlobalSettings
    private val globalSettings: GlobalSettings,
    private val latencyTracker: LatencyTracker,
) : CallbackController<FoldAodAnimationStatus>, ScreenOffAnimation, WakefulnessLifecycle.Observer {

    private lateinit var mCentralSurfaces: CentralSurfaces
@@ -64,12 +66,14 @@ constructor(
    private var isAnimationPlaying = false

    private val statusListeners = arrayListOf<FoldAodAnimationStatus>()
    private val foldToAodLatencyTracker = FoldToAodLatencyTracker()

    private val startAnimationRunnable = Runnable {
        mCentralSurfaces.notificationPanelViewController.startFoldToAodAnimation {
            // End action
            setAnimationState(playing = false)
        }
        mCentralSurfaces.notificationPanelViewController.startFoldToAodAnimation(
            /* startAction= */ { foldToAodLatencyTracker.onAnimationStarted() },
            /* endAction= */ { setAnimationState(playing = false) },
            /* cancelAction= */ { setAnimationState(playing = false) },
        )
    }

    override fun initialize(centralSurfaces: CentralSurfaces, lightRevealScrim: LightRevealScrim) {
@@ -82,11 +86,13 @@ constructor(
    /** Returns true if we should run fold to AOD animation */
    override fun shouldPlayAnimation(): Boolean = shouldPlayAnimation

    override fun startAnimation(): Boolean =
        if (alwaysOnEnabled &&
    private fun shouldStartAnimation(): Boolean =
        alwaysOnEnabled &&
            wakefulnessLifecycle.lastSleepReason == PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD &&
            globalSettings.getString(Settings.Global.ANIMATOR_DURATION_SCALE) != "0"
        ) {

    override fun startAnimation(): Boolean =
        if (shouldStartAnimation()) {
            setAnimationState(playing = true)
            mCentralSurfaces.notificationPanelViewController.prepareFoldToAodAnimation()
            true
@@ -97,6 +103,7 @@ constructor(

    override fun onStartedWakingUp() {
        if (isAnimationPlaying) {
            foldToAodLatencyTracker.cancel()
            handler.removeCallbacks(startAnimationRunnable)
            mCentralSurfaces.notificationPanelViewController.cancelFoldToAodAnimation()
        }
@@ -137,7 +144,8 @@ constructor(
            // but we should wait for the initial animation preparations to be drawn
            // (setting initial alpha/translation)
            OneShotPreDrawListener.add(
                mCentralSurfaces.notificationPanelViewController.view, onReady
                mCentralSurfaces.notificationPanelViewController.view,
                onReady
            )
        } else {
            // No animation, call ready callback immediately
@@ -209,5 +217,41 @@ constructor(
                    isFoldHandled = false
                }
                this.isFolded = isFolded
            })
                if (isFolded) {
                    foldToAodLatencyTracker.onFolded()
                }
            }
        )

    /**
     * Tracks the latency of fold to AOD using [LatencyTracker].
     *
     * Events that trigger start and end are:
     *
     * - Start: Once [DeviceStateManager] sends the folded signal [FoldToAodLatencyTracker.onFolded]
     * is called and latency tracking starts.
     * - End: Once the fold -> AOD animation starts, [FoldToAodLatencyTracker.onAnimationStarted] is
     * called, and latency tracking stops.
     */
    private inner class FoldToAodLatencyTracker {

        /** Triggers the latency logging, if needed. */
        fun onFolded() {
            if (shouldStartAnimation()) {
                latencyTracker.onActionStart(LatencyTracker.ACTION_FOLD_TO_AOD)
            }
        }
        /**
         * Called once the Fold -> AOD animation is started.
         *
         * For latency tracking, this determines the end of the fold to aod action.
         */
        fun onAnimationStarted() {
            latencyTracker.onActionEnd(LatencyTracker.ACTION_FOLD_TO_AOD)
        }

        fun cancel() {
            latencyTracker.onActionCancel(LatencyTracker.ACTION_FOLD_TO_AOD)
        }
    }
}
+166 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.unfold

import android.hardware.devicestate.DeviceStateManager
import android.hardware.devicestate.DeviceStateManager.FoldStateListener
import android.os.Handler
import android.os.PowerManager
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
import android.view.ViewGroup
import android.view.ViewTreeObserver
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.shade.NotificationPanelViewController
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.unfold.util.FoldableDeviceStates
import com.android.systemui.unfold.util.FoldableTestUtils
import com.android.systemui.util.mockito.any
import com.android.systemui.util.settings.GlobalSettings
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations

@RunWith(AndroidTestingRunner::class)
@SmallTest
@RunWithLooper
class FoldAodAnimationControllerTest : SysuiTestCase() {

    @Mock lateinit var deviceStateManager: DeviceStateManager

    @Mock lateinit var wakefulnessLifecycle: WakefulnessLifecycle

    @Mock lateinit var globalSettings: GlobalSettings

    @Mock lateinit var latencyTracker: LatencyTracker

    @Mock lateinit var centralSurfaces: CentralSurfaces

    @Mock lateinit var lightRevealScrim: LightRevealScrim

    @Mock lateinit var notificationPanelViewController: NotificationPanelViewController

    @Mock lateinit var viewGroup: ViewGroup

    @Mock lateinit var viewTreeObserver: ViewTreeObserver

    @Captor private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener>

    private lateinit var deviceStates: FoldableDeviceStates

    private lateinit var testableLooper: TestableLooper

    lateinit var foldAodAnimationController: FoldAodAnimationController

    @Before
    fun setup() {
        MockitoAnnotations.initMocks(this)
        testableLooper = TestableLooper.get(this)

        foldAodAnimationController =
            FoldAodAnimationController(
                    Handler(testableLooper.looper),
                    context.mainExecutor,
                    context,
                    deviceStateManager,
                    wakefulnessLifecycle,
                    globalSettings,
                    latencyTracker,
                )
                .apply { initialize(centralSurfaces, lightRevealScrim) }
        deviceStates = FoldableTestUtils.findDeviceStates(context)

        whenever(notificationPanelViewController.view).thenReturn(viewGroup)
        whenever(viewGroup.viewTreeObserver).thenReturn(viewTreeObserver)
        whenever(wakefulnessLifecycle.lastSleepReason)
            .thenReturn(PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD)
        whenever(centralSurfaces.notificationPanelViewController)
            .thenReturn(notificationPanelViewController)
        whenever(notificationPanelViewController.startFoldToAodAnimation(any(), any(), any()))
            .then {
                val onActionStarted = it.arguments[0] as Runnable
                onActionStarted.run()
            }
        verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture())

        foldAodAnimationController.setIsDozing(dozing = true)
        setAodEnabled(enabled = true)
        sendFoldEvent(folded = false)
    }

    @Test
    fun onFolded_aodDisabled_doesNotLogLatency() {
        setAodEnabled(enabled = false)

        fold()
        simulateScreenTurningOn()

        verifyNoMoreInteractions(latencyTracker)
    }

    @Test
    fun onFolded_aodEnabled_logsLatency() {
        setAodEnabled(enabled = true)

        fold()
        simulateScreenTurningOn()

        verify(latencyTracker).onActionStart(any())
        verify(latencyTracker).onActionEnd(any())
    }

    @Test
    fun onFolded_animationCancelled_doesNotLogLatency() {
        setAodEnabled(enabled = true)

        fold()
        foldAodAnimationController.onScreenTurningOn({})
        foldAodAnimationController.onStartedWakingUp()
        testableLooper.processAllMessages()

        verify(latencyTracker).onActionStart(any())
        verify(latencyTracker).onActionCancel(any())
    }

    private fun simulateScreenTurningOn() {
        foldAodAnimationController.onScreenTurningOn({})
        foldAodAnimationController.onScreenTurnedOn()
        testableLooper.processAllMessages()
    }

    private fun fold() = sendFoldEvent(folded = true)

    private fun setAodEnabled(enabled: Boolean) =
        foldAodAnimationController.onAlwaysOnChanged(alwaysOn = enabled)

    private fun sendFoldEvent(folded: Boolean) {
        val state = if (folded) deviceStates.folded else deviceStates.unfolded
        foldStateListenerCaptor.value.onStateChanged(state)
    }
}