Merge "[Unfold transition] Update latency tracker to listen for animation start" into tm-qpr-dev
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
index 0b04fb4..6063ce2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
@@ -17,6 +17,7 @@
 
 import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
 import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
+import com.android.systemui.util.traceSection
 import javax.inject.Inject
 import javax.inject.Singleton
 
@@ -39,14 +40,22 @@
     }
 
     override fun onScreenTurnedOn() {
-        listeners.forEach(ScreenListener::onScreenTurnedOn)
+        traceSection("$TRACE_TAG#onScreenTurnedOn") {
+            listeners.forEach(ScreenListener::onScreenTurnedOn)
+        }
     }
 
     override fun onScreenTurningOff() {
-        listeners.forEach(ScreenListener::onScreenTurningOff)
+        traceSection("$TRACE_TAG#onScreenTurningOff") {
+            listeners.forEach(ScreenListener::onScreenTurningOff)
+        }
     }
 
     override fun onScreenTurningOn() {
-        listeners.forEach(ScreenListener::onScreenTurningOn)
+        traceSection("$TRACE_TAG#onScreenTurningOn") {
+            listeners.forEach(ScreenListener::onScreenTurningOn)
+        }
     }
 }
+
+private const val TRACE_TAG = "LifecycleScreenStatusProvider"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt
index 79b42b8..9269df3 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt
@@ -16,12 +16,18 @@
 
 package com.android.systemui.unfold
 
+import android.content.ContentResolver
 import android.content.Context
 import android.hardware.devicestate.DeviceStateManager
+import android.util.Log
 import com.android.internal.util.LatencyTracker
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.UiBackground
 import com.android.systemui.keyguard.ScreenLifecycle
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider.Companion.areAnimationsEnabled
+import com.android.systemui.util.Compile
+import java.util.Optional
 import java.util.concurrent.Executor
 import javax.inject.Inject
 
@@ -41,17 +47,19 @@
 constructor(
     private val latencyTracker: LatencyTracker,
     private val deviceStateManager: DeviceStateManager,
+    private val transitionProgressProvider: Optional<UnfoldTransitionProgressProvider>,
     @UiBackground private val uiBgExecutor: Executor,
     private val context: Context,
+    private val contentResolver: ContentResolver,
     private val screenLifecycle: ScreenLifecycle
-) : ScreenLifecycle.Observer {
+) : ScreenLifecycle.Observer, TransitionProgressListener {
 
     private var folded: Boolean? = null
+    private var isTransitionEnabled: Boolean? = null
     private val foldStateListener = FoldStateListener(context)
     private val isFoldable: Boolean
         get() =
-            context
-                .resources
+            context.resources
                 .getIntArray(com.android.internal.R.array.config_foldedDeviceStates)
                 .isNotEmpty()
 
@@ -62,6 +70,11 @@
         }
         deviceStateManager.registerCallback(uiBgExecutor, foldStateListener)
         screenLifecycle.addObserver(this)
+        if (transitionProgressProvider.isPresent) {
+            // Might not be present if the device is not a foldable device or unfold transition
+            // is disabled in the device configuration
+            transitionProgressProvider.get().addCallback(this)
+        }
     }
 
     /**
@@ -71,16 +84,72 @@
      * end action event only if we previously received a fold state.
      */
     override fun onScreenTurnedOn() {
-        if (folded == false) {
+        if (DEBUG) {
+            Log.d(
+                TAG,
+                "onScreenTurnedOn: folded = $folded, isTransitionEnabled = $isTransitionEnabled"
+            )
+        }
+
+        // We use onScreenTurnedOn event to finish tracking only if we are not playing
+        // the unfold animation (e.g. it could be disabled because of battery saver).
+        // When animation is enabled finishing of the tracking will be done in onTransitionStarted.
+        if (folded == false && isTransitionEnabled == false) {
             latencyTracker.onActionEnd(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
+
+            if (DEBUG) {
+                Log.d(TAG, "onScreenTurnedOn: ending ACTION_SWITCH_DISPLAY_UNFOLD")
+            }
+        }
+    }
+
+    /**
+     * This callback is used to end the metric when the unfold animation is enabled because it could
+     * add an additional delay to synchronize with launcher.
+     */
+    override fun onTransitionStarted() {
+        if (DEBUG) {
+            Log.d(
+                TAG,
+                "onTransitionStarted: folded = $folded, isTransitionEnabled = $isTransitionEnabled"
+            )
+        }
+
+        if (folded == false && isTransitionEnabled == true) {
+            latencyTracker.onActionEnd(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
+
+            if (DEBUG) {
+                Log.d(TAG, "onTransitionStarted: ending ACTION_SWITCH_DISPLAY_UNFOLD")
+            }
         }
     }
 
     private fun onFoldEvent(folded: Boolean) {
-        if (this.folded != folded) {
+        val oldFolded = this.folded
+
+        if (oldFolded != folded) {
             this.folded = folded
-            if (!folded) { // unfolding started
+
+            if (DEBUG) {
+                Log.d(TAG, "Received onFoldEvent = $folded")
+            }
+
+            // Do not start tracking when oldFolded is null, this means that this is the first
+            // onFoldEvent after booting the device or starting SystemUI and not actual folding or
+            // unfolding the device.
+            if (oldFolded != null && !folded) {
+                // Unfolding started
                 latencyTracker.onActionStart(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
+                isTransitionEnabled =
+                    transitionProgressProvider.isPresent && contentResolver.areAnimationsEnabled()
+
+                if (DEBUG) {
+                    Log.d(
+                        TAG,
+                        "Starting ACTION_SWITCH_DISPLAY_UNFOLD, " +
+                            "isTransitionEnabled = $isTransitionEnabled"
+                    )
+                }
             }
         }
     }
@@ -88,3 +157,6 @@
     private inner class FoldStateListener(context: Context) :
         DeviceStateManager.FoldStateListener(context, { onFoldEvent(it) })
 }
+
+private const val TAG = "UnfoldLatencyTracker"
+private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
index 4e2736c7..c7dc0ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
@@ -18,6 +18,7 @@
 
 import android.hardware.devicestate.DeviceStateManager
 import android.hardware.devicestate.DeviceStateManager.FoldStateListener
+import android.provider.Settings
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.internal.util.LatencyTracker
@@ -32,9 +33,11 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
 import org.mockito.Mock
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.MockitoAnnotations
+import java.util.Optional
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
@@ -59,14 +62,18 @@
 
     private lateinit var unfoldLatencyTracker: UnfoldLatencyTracker
 
+    private val transitionProgressProvider = TestUnfoldTransitionProvider()
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         unfoldLatencyTracker = UnfoldLatencyTracker(
             latencyTracker,
             deviceStateManager,
+            Optional.of(transitionProgressProvider),
             context.mainExecutor,
             context,
+            context.contentResolver,
             screenLifecycle
         ).apply { init() }
         deviceStates = FoldableTestUtils.findDeviceStates(context)
@@ -76,8 +83,11 @@
     }
 
     @Test
-    fun unfold_eventPropagated() {
+    fun unfold_startedFolded_animationsDisabled_eventPropagatedOnScreenTurnedOnEvent() {
+        setAnimationsEnabled(false)
+        sendFoldEvent(folded = true)
         sendFoldEvent(folded = false)
+
         sendScreenTurnedOnEvent()
 
         verify(latencyTracker).onActionStart(any())
@@ -85,14 +95,77 @@
     }
 
     @Test
-    fun fold_eventNotPropagated() {
+    fun unfold_startedFolded_animationsEnabledOnScreenTurnedOn_eventNotFinished() {
+        setAnimationsEnabled(true)
         sendFoldEvent(folded = true)
+        sendFoldEvent(folded = false)
+
+        sendScreenTurnedOnEvent()
+
+        verify(latencyTracker).onActionStart(any())
+        verify(latencyTracker, never()).onActionEnd(any())
+    }
+
+    @Test
+    fun unfold_firstFoldEventAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventNotPropagated() {
+        setAnimationsEnabled(true)
+        sendFoldEvent(folded = false)
+
+        sendScreenTurnedOnEvent()
+        transitionProgressProvider.onTransitionStarted()
+
+        verifyNoMoreInteractions(latencyTracker)
+    }
+
+    @Test
+    fun unfold_secondFoldEventAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventPropagated() {
+        setAnimationsEnabled(true)
+        sendFoldEvent(folded = true)
+        sendFoldEvent(folded = false)
+
+        sendScreenTurnedOnEvent()
+        transitionProgressProvider.onTransitionStarted()
+
+        verify(latencyTracker).onActionStart(any())
+        verify(latencyTracker).onActionEnd(any())
+    }
+
+    @Test
+    fun unfold_unfoldFoldUnfoldAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventPropagated() {
+        setAnimationsEnabled(true)
+        sendFoldEvent(folded = false)
+        sendFoldEvent(folded = true)
+        sendFoldEvent(folded = false)
+
+        sendScreenTurnedOnEvent()
+        transitionProgressProvider.onTransitionStarted()
+
+        verify(latencyTracker).onActionStart(any())
+        verify(latencyTracker).onActionEnd(any())
+    }
+
+    @Test
+    fun fold_animationsDisabled_screenTurnedOn_eventNotPropagated() {
+        setAnimationsEnabled(false)
+        sendFoldEvent(folded = true)
+
         sendScreenTurnedOnEvent() // outer display on.
 
         verifyNoMoreInteractions(latencyTracker)
     }
 
     @Test
+    fun fold_animationsEnabled_screenTurnedOn_eventNotPropagated() {
+        setAnimationsEnabled(true)
+        sendFoldEvent(folded = true)
+
+        sendScreenTurnedOnEvent() // outer display on.
+        transitionProgressProvider.onTransitionStarted()
+
+        verifyNoMoreInteractions(latencyTracker)
+    }
+
+    @Test
     fun onScreenTurnedOn_stateNeverSet_eventNotPropagated() {
         sendScreenTurnedOnEvent()
 
@@ -107,4 +180,20 @@
     private fun sendScreenTurnedOnEvent() {
         screenLifecycleCaptor.value.onScreenTurnedOn()
     }
+
+    private fun setAnimationsEnabled(enabled: Boolean) {
+        val durationScale =
+            if (enabled) {
+                1f
+            } else {
+                0f
+            }
+
+        // It uses [TestableSettingsProvider] and it will be cleared after the test
+        Settings.Global.putString(
+            context.contentResolver,
+            Settings.Global.ANIMATOR_DURATION_SCALE,
+            durationScale.toString()
+        )
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
index ecc029d..074b1e1 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.unfold.progress
 
 import android.os.Trace
+import android.os.Trace.TRACE_TAG_APP
 import android.util.Log
 import androidx.dynamicanimation.animation.DynamicAnimation
 import androidx.dynamicanimation.animation.FloatPropertyCompat
@@ -157,7 +158,10 @@
     }
 
     private fun onStartTransition() {
+        Trace.beginSection( "$TAG#onStartTransition")
         listeners.forEach { it.onTransitionStarted() }
+        Trace.endSection()
+
         isTransitionRunning = true
 
         if (DEBUG) {