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
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index ca40a40..14a6d5e 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -142,6 +142,11 @@
*/
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 @@
ACTION_UDFPS_ILLUMINATE,
ACTION_SHOW_BACK_ARROW,
ACTION_LOAD_SHARE_SHEET,
+ ACTION_FOLD_TO_AOD,
};
/** @hide */
@@ -181,6 +187,7 @@
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 @@
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 @@
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 4e74540f..58e9bb8 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 @@
}
/**
- * 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 d6dfcea..8f127fd 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.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 @@
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 @@
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 @@
/** 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 @@
override fun onStartedWakingUp() {
if (isAnimationPlaying) {
+ foldToAodLatencyTracker.cancel()
handler.removeCallbacks(startAnimationRunnable)
mCentralSurfaces.notificationPanelViewController.cancelFoldToAodAnimation()
}
@@ -137,7 +144,8 @@
// 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 @@
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 0000000..f51f783
--- /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<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)
+ }
+}