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
This commit is contained in:
@@ -142,6 +142,11 @@ public class LatencyTracker {
|
|||||||
*/
|
*/
|
||||||
public static final int ACTION_LOAD_SHARE_SHEET = 16;
|
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 = {
|
private static final int[] ACTIONS_ALL = {
|
||||||
ACTION_EXPAND_PANEL,
|
ACTION_EXPAND_PANEL,
|
||||||
ACTION_TOGGLE_RECENTS,
|
ACTION_TOGGLE_RECENTS,
|
||||||
@@ -160,6 +165,7 @@ public class LatencyTracker {
|
|||||||
ACTION_UDFPS_ILLUMINATE,
|
ACTION_UDFPS_ILLUMINATE,
|
||||||
ACTION_SHOW_BACK_ARROW,
|
ACTION_SHOW_BACK_ARROW,
|
||||||
ACTION_LOAD_SHARE_SHEET,
|
ACTION_LOAD_SHARE_SHEET,
|
||||||
|
ACTION_FOLD_TO_AOD,
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @hide */
|
/** @hide */
|
||||||
@@ -181,6 +187,7 @@ public class LatencyTracker {
|
|||||||
ACTION_UDFPS_ILLUMINATE,
|
ACTION_UDFPS_ILLUMINATE,
|
||||||
ACTION_SHOW_BACK_ARROW,
|
ACTION_SHOW_BACK_ARROW,
|
||||||
ACTION_LOAD_SHARE_SHEET,
|
ACTION_LOAD_SHARE_SHEET,
|
||||||
|
ACTION_FOLD_TO_AOD
|
||||||
})
|
})
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
public @interface Action {
|
public @interface Action {
|
||||||
@@ -204,6 +211,7 @@ public class LatencyTracker {
|
|||||||
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_UDFPS_ILLUMINATE,
|
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_UDFPS_ILLUMINATE,
|
||||||
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_BACK_ARROW,
|
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_BACK_ARROW,
|
||||||
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOAD_SHARE_SHEET,
|
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOAD_SHARE_SHEET,
|
||||||
|
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD
|
||||||
};
|
};
|
||||||
|
|
||||||
private static LatencyTracker sLatencyTracker;
|
private static LatencyTracker sLatencyTracker;
|
||||||
@@ -297,6 +305,8 @@ public class LatencyTracker {
|
|||||||
return "ACTION_SHOW_BACK_ARROW";
|
return "ACTION_SHOW_BACK_ARROW";
|
||||||
case 17:
|
case 17:
|
||||||
return "ACTION_LOAD_SHARE_SHEET";
|
return "ACTION_LOAD_SHARE_SHEET";
|
||||||
|
case 19:
|
||||||
|
return "ACTION_FOLD_TO_AOD";
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Invalid action");
|
throw new IllegalArgumentException("Invalid action");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
mView.animate()
|
||||||
.translationX(0)
|
.translationX(0)
|
||||||
.alpha(1f)
|
.alpha(1f)
|
||||||
.setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
|
.setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
|
||||||
.setInterpolator(EMPHASIZED_DECELERATE)
|
.setInterpolator(EMPHASIZED_DECELERATE)
|
||||||
.setListener(new AnimatorListenerAdapter() {
|
.setListener(new AnimatorListenerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationStart(Animator animation) {
|
||||||
|
startAction.run();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAnimationCancel(Animator animation) {
|
public void onAnimationCancel(Animator animation) {
|
||||||
endAction.run();
|
cancelAction.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAnimationEnd(Animator animation) {
|
public void onAnimationEnd(Animator animation) {
|
||||||
endAction.run();
|
endAction.run();
|
||||||
}
|
}
|
||||||
})
|
}).setUpdateListener(anim -> {
|
||||||
.setUpdateListener(anim -> {
|
|
||||||
mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
|
mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
|
||||||
})
|
}).start();
|
||||||
.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -22,11 +22,12 @@ import android.os.Handler
|
|||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import androidx.core.view.OneShotPreDrawListener
|
import androidx.core.view.OneShotPreDrawListener
|
||||||
|
import com.android.internal.util.LatencyTracker
|
||||||
import com.android.systemui.dagger.qualifiers.Main
|
import com.android.systemui.dagger.qualifiers.Main
|
||||||
import com.android.systemui.keyguard.WakefulnessLifecycle
|
import com.android.systemui.keyguard.WakefulnessLifecycle
|
||||||
import com.android.systemui.statusbar.LightRevealScrim
|
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.CentralSurfaces
|
||||||
|
import com.android.systemui.statusbar.phone.ScreenOffAnimation
|
||||||
import com.android.systemui.statusbar.policy.CallbackController
|
import com.android.systemui.statusbar.policy.CallbackController
|
||||||
import com.android.systemui.unfold.FoldAodAnimationController.FoldAodAnimationStatus
|
import com.android.systemui.unfold.FoldAodAnimationController.FoldAodAnimationStatus
|
||||||
import com.android.systemui.util.settings.GlobalSettings
|
import com.android.systemui.util.settings.GlobalSettings
|
||||||
@@ -47,7 +48,8 @@ constructor(
|
|||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val deviceStateManager: DeviceStateManager,
|
private val deviceStateManager: DeviceStateManager,
|
||||||
private val wakefulnessLifecycle: WakefulnessLifecycle,
|
private val wakefulnessLifecycle: WakefulnessLifecycle,
|
||||||
private val globalSettings: GlobalSettings
|
private val globalSettings: GlobalSettings,
|
||||||
|
private val latencyTracker: LatencyTracker,
|
||||||
) : CallbackController<FoldAodAnimationStatus>, ScreenOffAnimation, WakefulnessLifecycle.Observer {
|
) : CallbackController<FoldAodAnimationStatus>, ScreenOffAnimation, WakefulnessLifecycle.Observer {
|
||||||
|
|
||||||
private lateinit var mCentralSurfaces: CentralSurfaces
|
private lateinit var mCentralSurfaces: CentralSurfaces
|
||||||
@@ -64,12 +66,14 @@ constructor(
|
|||||||
private var isAnimationPlaying = false
|
private var isAnimationPlaying = false
|
||||||
|
|
||||||
private val statusListeners = arrayListOf<FoldAodAnimationStatus>()
|
private val statusListeners = arrayListOf<FoldAodAnimationStatus>()
|
||||||
|
private val foldToAodLatencyTracker = FoldToAodLatencyTracker()
|
||||||
|
|
||||||
private val startAnimationRunnable = Runnable {
|
private val startAnimationRunnable = Runnable {
|
||||||
mCentralSurfaces.notificationPanelViewController.startFoldToAodAnimation {
|
mCentralSurfaces.notificationPanelViewController.startFoldToAodAnimation(
|
||||||
// End action
|
/* startAction= */ { foldToAodLatencyTracker.onAnimationStarted() },
|
||||||
setAnimationState(playing = false)
|
/* endAction= */ { setAnimationState(playing = false) },
|
||||||
}
|
/* cancelAction= */ { setAnimationState(playing = false) },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initialize(centralSurfaces: CentralSurfaces, lightRevealScrim: LightRevealScrim) {
|
override fun initialize(centralSurfaces: CentralSurfaces, lightRevealScrim: LightRevealScrim) {
|
||||||
@@ -82,11 +86,13 @@ constructor(
|
|||||||
/** Returns true if we should run fold to AOD animation */
|
/** Returns true if we should run fold to AOD animation */
|
||||||
override fun shouldPlayAnimation(): Boolean = shouldPlayAnimation
|
override fun shouldPlayAnimation(): Boolean = shouldPlayAnimation
|
||||||
|
|
||||||
override fun startAnimation(): Boolean =
|
private fun shouldStartAnimation(): Boolean =
|
||||||
if (alwaysOnEnabled &&
|
alwaysOnEnabled &&
|
||||||
wakefulnessLifecycle.lastSleepReason == PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD &&
|
wakefulnessLifecycle.lastSleepReason == PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD &&
|
||||||
globalSettings.getString(Settings.Global.ANIMATOR_DURATION_SCALE) != "0"
|
globalSettings.getString(Settings.Global.ANIMATOR_DURATION_SCALE) != "0"
|
||||||
) {
|
|
||||||
|
override fun startAnimation(): Boolean =
|
||||||
|
if (shouldStartAnimation()) {
|
||||||
setAnimationState(playing = true)
|
setAnimationState(playing = true)
|
||||||
mCentralSurfaces.notificationPanelViewController.prepareFoldToAodAnimation()
|
mCentralSurfaces.notificationPanelViewController.prepareFoldToAodAnimation()
|
||||||
true
|
true
|
||||||
@@ -97,6 +103,7 @@ constructor(
|
|||||||
|
|
||||||
override fun onStartedWakingUp() {
|
override fun onStartedWakingUp() {
|
||||||
if (isAnimationPlaying) {
|
if (isAnimationPlaying) {
|
||||||
|
foldToAodLatencyTracker.cancel()
|
||||||
handler.removeCallbacks(startAnimationRunnable)
|
handler.removeCallbacks(startAnimationRunnable)
|
||||||
mCentralSurfaces.notificationPanelViewController.cancelFoldToAodAnimation()
|
mCentralSurfaces.notificationPanelViewController.cancelFoldToAodAnimation()
|
||||||
}
|
}
|
||||||
@@ -137,7 +144,8 @@ constructor(
|
|||||||
// but we should wait for the initial animation preparations to be drawn
|
// but we should wait for the initial animation preparations to be drawn
|
||||||
// (setting initial alpha/translation)
|
// (setting initial alpha/translation)
|
||||||
OneShotPreDrawListener.add(
|
OneShotPreDrawListener.add(
|
||||||
mCentralSurfaces.notificationPanelViewController.view, onReady
|
mCentralSurfaces.notificationPanelViewController.view,
|
||||||
|
onReady
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// No animation, call ready callback immediately
|
// No animation, call ready callback immediately
|
||||||
@@ -209,5 +217,41 @@ constructor(
|
|||||||
isFoldHandled = false
|
isFoldHandled = false
|
||||||
}
|
}
|
||||||
this.isFolded = isFolded
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user