Loading core/java/com/android/internal/util/LatencyTracker.java +10 −0 Original line number Diff line number Diff line Loading @@ -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, Loading @@ -160,6 +165,7 @@ public class LatencyTracker { ACTION_UDFPS_ILLUMINATE, ACTION_SHOW_BACK_ARROW, ACTION_LOAD_SHARE_SHEET, ACTION_FOLD_TO_AOD, }; /** @hide */ Loading @@ -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 { Loading @@ -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; Loading Loading @@ -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"); } Loading packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +28 −20 Original line number Diff line number Diff line Loading @@ -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(); } /** Loading packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt +55 −11 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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) { Loading @@ -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 Loading @@ -97,6 +103,7 @@ constructor( override fun onStartedWakingUp() { if (isAnimationPlaying) { foldToAodLatencyTracker.cancel() handler.removeCallbacks(startAnimationRunnable) mCentralSurfaces.notificationPanelViewController.cancelFoldToAodAnimation() } Loading Loading @@ -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 Loading Loading @@ -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) } } } packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt 0 → 100644 +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) } } Loading
core/java/com/android/internal/util/LatencyTracker.java +10 −0 Original line number Diff line number Diff line Loading @@ -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, Loading @@ -160,6 +165,7 @@ public class LatencyTracker { ACTION_UDFPS_ILLUMINATE, ACTION_SHOW_BACK_ARROW, ACTION_LOAD_SHARE_SHEET, ACTION_FOLD_TO_AOD, }; /** @hide */ Loading @@ -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 { Loading @@ -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; Loading Loading @@ -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"); } Loading
packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +28 −20 Original line number Diff line number Diff line Loading @@ -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(); } /** Loading
packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt +55 −11 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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) { Loading @@ -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 Loading @@ -97,6 +103,7 @@ constructor( override fun onStartedWakingUp() { if (isAnimationPlaying) { foldToAodLatencyTracker.cancel() handler.removeCallbacks(startAnimationRunnable) mCentralSurfaces.notificationPanelViewController.cancelFoldToAodAnimation() } Loading Loading @@ -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 Loading Loading @@ -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) } } }
packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt 0 → 100644 +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) } }