From 1a1976c329346e4c023c4c66078254ca2c5cb3c1 Mon Sep 17 00:00:00 2001 From: Nicolo' Mazzucato Date: Wed, 29 Jun 2022 12:54:30 +0000 Subject: [PATCH] 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 --- .../android/internal/util/LatencyTracker.java | 10 ++ .../NotificationPanelViewController.java | 48 ++--- .../unfold/FoldAodAnimationController.kt | 66 +++++-- .../unfold/FoldAodAnimationControllerTest.kt | 166 ++++++++++++++++++ 4 files changed, 259 insertions(+), 31 deletions(-) create mode 100644 packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java index ca40a4052ca10..14a6d5e4d08cb 100644 --- a/core/java/com/android/internal/util/LatencyTracker.java +++ b/core/java/com/android/internal/util/LatencyTracker.java @@ -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"); } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 4e74540ff761e..58e9bb85503a5 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -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 onAnimationCancel(Animator animation) { - endAction.run(); - } + .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 onAnimationEnd(Animator animation) { - endAction.run(); - } - }) - .setUpdateListener(anim -> { - mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction()); - }) - .start(); + @Override + public void onAnimationCancel(Animator animation) { + cancelAction.run(); + } + + @Override + public void onAnimationEnd(Animator animation) { + endAction.run(); + } + }).setUpdateListener(anim -> { + mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction()); + }).start(); } /** diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt index d6dfceac7a63a..8f127fd1fcb4a 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt @@ -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, ScreenOffAnimation, WakefulnessLifecycle.Observer { private lateinit var mCentralSurfaces: CentralSurfaces @@ -64,12 +66,14 @@ constructor( private var isAnimationPlaying = false private val statusListeners = arrayListOf() + 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) + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt new file mode 100644 index 0000000000000..f51f78315dbda --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt @@ -0,0 +1,166 @@ +/* + * 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 + + 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) + } +}