diff options
4 files changed, 103 insertions, 30 deletions
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java index 23bd64dd1068..a52d33ab7c42 100644 --- a/core/java/com/android/internal/jank/Cuj.java +++ b/core/java/com/android/internal/jank/Cuj.java @@ -147,8 +147,16 @@ public class Cuj { */ public static final int CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW = 104; + /** + * Track fade-in animation when in SystemUI process fold + * + * <p>Tracking starts after the screen turns on and finish when the animation is over {@link + * com.android.systemui.unfold.FoldLightRevealOverlayAnimation#playFoldLightRevealOverlayAnimation} for the span + */ + public static final int CUJ_FOLD_ANIM = 105; + // When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE. - @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW; + @VisibleForTesting static final int LAST_CUJ = CUJ_FOLD_ANIM; /** @hide */ @IntDef({ @@ -244,7 +252,8 @@ public class Cuj { CUJ_LAUNCHER_WIDGET_BOTTOM_SHEET_CLOSE_BACK, CUJ_LAUNCHER_PRIVATE_SPACE_LOCK, CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK, - CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW + CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, + CUJ_FOLD_ANIM }) @Retention(RetentionPolicy.SOURCE) public @interface CujType {} @@ -351,6 +360,7 @@ public class Cuj { CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_PRIVATE_SPACE_LOCK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_PRIVATE_SPACE_LOCK; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_PRIVATE_SPACE_UNLOCK; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_MAXIMIZE_WINDOW; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_FOLD_ANIM] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__FOLD_ANIM; } private Cuj() { @@ -555,6 +565,8 @@ public class Cuj { return "LAUNCHER_PRIVATE_SPACE_UNLOCK"; case CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW: return "DESKTOP_MODE_MAXIMIZE_WINDOW"; + case CUJ_FOLD_ANIM: + return "FOLD_ANIM"; } return "UNKNOWN"; } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt index aea739dcb6cc..9faa84ea9120 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt @@ -22,9 +22,12 @@ import android.animation.ValueAnimator import android.annotation.BinderThread import android.os.SystemProperties import android.util.Log +import android.view.View import android.view.animation.DecelerateInterpolator import com.android.app.tracing.TraceUtils.traceAsync import com.android.internal.foldables.FoldLockSettingAvailabilityProvider +import com.android.internal.jank.Cuj.CUJ_FOLD_ANIM +import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.display.data.repository.DeviceStateRepository import com.android.systemui.power.domain.interactor.PowerInteractor @@ -65,7 +68,8 @@ constructor( @Background private val applicationScope: CoroutineScope, private val animationStatusRepository: AnimationStatusRepository, private val controllerFactory: FullscreenLightRevealAnimationController.Factory, - private val foldLockSettingAvailabilityProvider: FoldLockSettingAvailabilityProvider + private val foldLockSettingAvailabilityProvider: FoldLockSettingAvailabilityProvider, + private val interactionJankMonitor: InteractionJankMonitor ) : FullscreenLightRevealAnimation { private val revealProgressValueAnimator: ValueAnimator = @@ -149,13 +153,23 @@ constructor( private suspend fun waitForGoToSleep() = traceAsync(TAG, "waitForGoToSleep()") { powerInteractor.isAsleep.filter { it }.first() } - private suspend fun playFoldLightRevealOverlayAnimation() { - revealProgressValueAnimator.duration = ANIMATION_DURATION - revealProgressValueAnimator.interpolator = DecelerateInterpolator() - revealProgressValueAnimator.addUpdateListener { animation -> - controller.updateRevealAmount(animation.animatedFraction) + private suspend fun playFoldLightRevealOverlayAnimation() = + trackCuj(CUJ_FOLD_ANIM, controller.scrimView) { + revealProgressValueAnimator.duration = ANIMATION_DURATION + revealProgressValueAnimator.interpolator = DecelerateInterpolator() + revealProgressValueAnimator.addUpdateListener { animation -> + controller.updateRevealAmount(animation.animatedFraction) + } + revealProgressValueAnimator.startAndAwaitCompletion() + } + + private suspend fun trackCuj(cuj: Int, view: View?, block: suspend () -> Unit) { + view?.let { interactionJankMonitor.begin(it, cuj) } + try { + block() + } finally { + if (view != null) interactionJankMonitor.end(cuj) } - revealProgressValueAnimator.startAndAwaitCompletion() } private suspend fun ValueAnimator.startAndAwaitCompletion(): Unit = diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FullscreenLightRevealAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/FullscreenLightRevealAnimation.kt index 135edfcb6a42..f368cac53dbb 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/FullscreenLightRevealAnimation.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/FullscreenLightRevealAnimation.kt @@ -81,7 +81,10 @@ constructor( private var currentRotation: Int = context.display.rotation private var root: SurfaceControlViewHost? = null - private var scrimView: LightRevealScrim? = null + + /** The scrim view that is used to reveal the screen. */ + var scrimView: LightRevealScrim? = null + private set private val rotationWatcher = RotationWatcher() private val internalDisplayInfos: List<DisplayInfo> = diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt index dddc71216da1..a56507af6575 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt @@ -22,7 +22,10 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.internal.foldables.FoldLockSettingAvailabilityProvider +import com.android.internal.jank.Cuj.CUJ_FOLD_ANIM +import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.AnimatorTestRule import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState import com.android.systemui.display.data.repository.fakeDeviceStateRepository import com.android.systemui.kosmos.Kosmos @@ -32,27 +35,34 @@ import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.se import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setScreenPowerState import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.power.shared.model.ScreenPowerState +import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.util.animation.data.repository.fakeAnimationStatusRepository -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.whenever import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runTest import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.atLeast import org.mockito.Mockito.never +import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.clearInvocations +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever @SmallTest @TestableLooper.RunWithLooper(setAsMainLooper = true) @RunWith(AndroidTestingRunner::class) @OptIn(ExperimentalCoroutinesApi::class) class FoldLightRevealOverlayAnimationTest : SysuiTestCase() { + @get:Rule val animatorTestRule = AnimatorTestRule(this) + private val kosmos = Kosmos() private val testScope: TestScope = kosmos.testScope private val fakeDeviceStateRepository = kosmos.fakeDeviceStateRepository @@ -63,6 +73,8 @@ class FoldLightRevealOverlayAnimationTest : SysuiTestCase() { private val mockFoldLockSettingAvailabilityProvider = mock<FoldLockSettingAvailabilityProvider>() private val onOverlayReady = mock<Runnable>() + private val mockJankMonitor = mock<InteractionJankMonitor>() + private val mockScrimView = mock<LightRevealScrim>() private lateinit var foldLightRevealAnimation: FoldLightRevealOverlayAnimation @Before @@ -71,6 +83,8 @@ class FoldLightRevealOverlayAnimationTest : SysuiTestCase() { whenever(mockFoldLockSettingAvailabilityProvider.isFoldLockBehaviorAvailable) .thenReturn(true) fakeAnimationStatusRepository.onAnimationStatusChanged(true) + whenever(mockFullScreenController.scrimView).thenReturn(mockScrimView) + whenever(mockJankMonitor.begin(any(), eq(CUJ_FOLD_ANIM))).thenReturn(true) foldLightRevealAnimation = FoldLightRevealOverlayAnimation( @@ -80,7 +94,8 @@ class FoldLightRevealOverlayAnimationTest : SysuiTestCase() { testScope.backgroundScope, fakeAnimationStatusRepository, mockControllerFactory, - mockFoldLockSettingAvailabilityProvider + mockFoldLockSettingAvailabilityProvider, + mockJankMonitor ) foldLightRevealAnimation.init() } @@ -99,9 +114,9 @@ class FoldLightRevealOverlayAnimationTest : SysuiTestCase() { testScope.runTest { foldDeviceToScreenOff() emitLastWakefulnessEventStartingToSleep() - advanceTimeBy(SHORT_DELAY_MS) + advanceTime(SHORT_DELAY_MS) turnScreenOn() - advanceTimeBy(ANIMATION_DURATION) + advanceTime(ANIMATION_DURATION) verifyFoldAnimationDidNotPlay() } @@ -111,8 +126,8 @@ class FoldLightRevealOverlayAnimationTest : SysuiTestCase() { testScope.runTest { foldDeviceToScreenOff() emitLastWakefulnessEventStartingToSleep() - advanceTimeBy(SHORT_DELAY_MS) - advanceTimeBy(ANIMATION_DURATION) + advanceTime(SHORT_DELAY_MS) + advanceTime(ANIMATION_DURATION) verifyFoldAnimationDidNotPlay() } @@ -122,10 +137,10 @@ class FoldLightRevealOverlayAnimationTest : SysuiTestCase() { testScope.runTest { foldDeviceToScreenOff() foldLightRevealAnimation.onScreenTurningOn {} - advanceTimeBy(WAIT_FOR_ANIMATION_TIMEOUT_MS) + advanceTime(WAIT_FOR_ANIMATION_TIMEOUT_MS) powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON) - advanceTimeBy(SHORT_DELAY_MS) - advanceTimeBy(ANIMATION_DURATION) + advanceTime(SHORT_DELAY_MS) + advanceTime(ANIMATION_DURATION) verifyFoldAnimationDidNotPlay() } @@ -135,10 +150,12 @@ class FoldLightRevealOverlayAnimationTest : SysuiTestCase() { testScope.runTest { foldDeviceToScreenOff() foldLightRevealAnimation.onScreenTurningOn {} - advanceTimeBy(SHORT_DELAY_MS) - advanceTimeBy(ANIMATION_DURATION) + advanceTime(SHORT_DELAY_MS) + clearInvocations(mockFullScreenController) + + // Unfold the device fakeDeviceStateRepository.emit(DeviceState.HALF_FOLDED) - advanceTimeBy(SHORT_DELAY_MS) + advanceTime(SHORT_DELAY_MS) powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON) verifyOverlayWasRemoved() @@ -149,12 +166,38 @@ class FoldLightRevealOverlayAnimationTest : SysuiTestCase() { testScope.runTest { foldDeviceToScreenOff() turnScreenOn() - advanceTimeBy(ANIMATION_DURATION) + clearInvocations(mockFullScreenController) + advanceTime(ANIMATION_DURATION) verifyOverlayWasRemoved() } @Test + fun foldToScreenOn_jankCujIsStarted() = + testScope.runTest { + foldDeviceToScreenOff() + turnScreenOn() + // Cuj has started but not ended + verify(mockJankMonitor, times(1)).begin(any(), eq(CUJ_FOLD_ANIM)) + verify(mockJankMonitor, never()).end(eq(CUJ_FOLD_ANIM)) + } + + @Test + fun foldToScreenOn_animationFinished_jankCujIsFinished() = + testScope.runTest { + foldDeviceToScreenOff() + turnScreenOn() + + advanceTime(ANIMATION_DURATION) + verify(mockJankMonitor, times(1)).end(eq(CUJ_FOLD_ANIM)) + } + + private fun TestScope.advanceTime(timeMs: Long) { + animatorTestRule.advanceTimeBy(timeMs) + advanceTimeBy(timeMs) + } + + @Test fun unfold_immediatelyRunRunnable() = testScope.runTest { foldLightRevealAnimation.onScreenTurningOn(onOverlayReady) @@ -165,18 +208,18 @@ class FoldLightRevealOverlayAnimationTest : SysuiTestCase() { private suspend fun TestScope.foldDeviceToScreenOff() { fakeDeviceStateRepository.emit(DeviceState.HALF_FOLDED) powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON) - advanceTimeBy(SHORT_DELAY_MS) + advanceTime(SHORT_DELAY_MS) fakeDeviceStateRepository.emit(DeviceState.FOLDED) - advanceTimeBy(SHORT_DELAY_MS) + advanceTime(SHORT_DELAY_MS) powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_OFF) - advanceTimeBy(SHORT_DELAY_MS) + advanceTime(SHORT_DELAY_MS) } private fun TestScope.turnScreenOn() { foldLightRevealAnimation.onScreenTurningOn {} - advanceTimeBy(SHORT_DELAY_MS) + advanceTime(SHORT_DELAY_MS) powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON) - advanceTimeBy(SHORT_DELAY_MS) + advanceTime(SHORT_DELAY_MS) } private fun emitLastWakefulnessEventStartingToSleep() = @@ -195,6 +238,7 @@ class FoldLightRevealOverlayAnimationTest : SysuiTestCase() { const val WAIT_FOR_ANIMATION_TIMEOUT_MS = 2000L val ANIMATION_DURATION: Long get() = SystemProperties.getLong("persist.fold_animation_duration", 200L) + const val SHORT_DELAY_MS = 50L } } |