• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.unfold
18 
19 import android.content.ContentResolver
20 import android.content.Context
21 import android.hardware.devicestate.DeviceStateManager
22 import android.os.Trace
23 import android.util.Log
24 import com.android.internal.util.LatencyTracker
25 import com.android.systemui.Flags.unfoldLatencyTrackingFix
26 import com.android.systemui.dagger.qualifiers.UiBackground
27 import com.android.systemui.keyguard.ScreenLifecycle
28 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
29 import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider.Companion.areAnimationsEnabled
30 import com.android.systemui.util.Compile
31 import com.android.systemui.util.Utils.isDeviceFoldable
32 import java.util.Optional
33 import java.util.concurrent.Executor
34 import javax.inject.Inject
35 
36 /**
37  * Logs performance metrics regarding time to turn the inner screen on.
38  *
39  * This class assumes that [onFoldEvent] is always called before [onScreenTurnedOn].
40  *
41  * This should be used from only one process.
42  *
43  * For now, the focus is on the time the inner display is visible, but in the future, it is easily
44  * possible to monitor the time to go from the inner screen to the outer.
45  */
46 @SysUIUnfoldScope
47 class UnfoldLatencyTracker
48 @Inject
49 constructor(
50     private val latencyTracker: LatencyTracker,
51     private val deviceStateManager: DeviceStateManager,
52     private val transitionProgressProvider: Optional<UnfoldTransitionProgressProvider>,
53     @UiBackground private val uiBgExecutor: Executor,
54     private val context: Context,
55     private val contentResolver: ContentResolver,
56     private val screenLifecycle: ScreenLifecycle,
57 ) : ScreenLifecycle.Observer, TransitionProgressListener {
58 
59     private var folded: Boolean? = null
60     private var isTransitionEnabled: Boolean? = null
61     private val foldStateListener = FoldStateListener(context)
62     private var unfoldInProgress = false
63     private val isFoldable: Boolean = isDeviceFoldable(context.resources, deviceStateManager)
64 
65     /** Registers for relevant events only if the device is foldable. */
initnull66     fun init() {
67         if (unfoldLatencyTrackingFix() || !isFoldable) {
68             return
69         }
70         deviceStateManager.registerCallback(uiBgExecutor, foldStateListener)
71         screenLifecycle.addObserver(this)
72         if (transitionProgressProvider.isPresent) {
73             // Might not be present if the device is not a foldable device or unfold transition
74             // is disabled in the device configuration
75             transitionProgressProvider.get().addCallback(this)
76         }
77     }
78 
79     /**
80      * To be called when the screen becomes visible.
81      *
82      * This is safe to call also when unsure whether the device is not a foldable, as it emits the
83      * end action event only if we previously received a fold state.
84      */
onScreenTurnedOnnull85     override fun onScreenTurnedOn() {
86         if (DEBUG) {
87             Log.d(
88                 TAG,
89                 "onScreenTurnedOn: folded = $folded, isTransitionEnabled = $isTransitionEnabled",
90             )
91         }
92 
93         // We use onScreenTurnedOn event to finish tracking only if we are not playing
94         // the unfold animation (e.g. it could be disabled because of battery saver).
95         // When animation is enabled finishing of the tracking will be done in onTransitionStarted.
96         if (folded == false && isTransitionEnabled == false) {
97             onUnfoldEnded()
98 
99             if (DEBUG) {
100                 Log.d(TAG, "onScreenTurnedOn: ending ACTION_SWITCH_DISPLAY_UNFOLD")
101             }
102         }
103     }
104 
105     /**
106      * This callback is used to end the metric when the unfold animation is enabled because it could
107      * add an additional delay to synchronize with launcher.
108      */
onTransitionStartednull109     override fun onTransitionStarted() {
110         if (DEBUG) {
111             Log.d(
112                 TAG,
113                 "onTransitionStarted: folded = $folded, isTransitionEnabled = $isTransitionEnabled",
114             )
115         }
116 
117         if (folded == false && isTransitionEnabled == true) {
118             onUnfoldEnded()
119 
120             if (DEBUG) {
121                 Log.d(TAG, "onTransitionStarted: ending ACTION_SWITCH_DISPLAY_UNFOLD")
122             }
123         }
124     }
125 
onUnfoldStartednull126     private fun onUnfoldStarted() {
127         if (unfoldInProgress) return
128         unfoldInProgress = true
129         // As LatencyTracker might be disabled, let's also log a parallel slice to the trace to be
130         // able to debug all cases.
131         latencyTracker.onActionStart(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
132         Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, UNFOLD_IN_PROGRESS_TRACE_NAME, /* cookie= */ 0)
133     }
134 
onUnfoldEndednull135     private fun onUnfoldEnded() {
136         if (!unfoldInProgress) return
137         unfoldInProgress = false
138         latencyTracker.onActionEnd(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
139         Trace.endAsyncSection(UNFOLD_IN_PROGRESS_TRACE_NAME, 0)
140     }
141 
onFoldEventnull142     private fun onFoldEvent(folded: Boolean) {
143         val oldFolded = this.folded
144 
145         if (oldFolded != folded) {
146             this.folded = folded
147 
148             if (DEBUG) {
149                 Log.d(TAG, "Received onFoldEvent = $folded")
150             }
151 
152             // Do not start tracking when oldFolded is null, this means that this is the first
153             // onFoldEvent after booting the device or starting SystemUI and not actual folding or
154             // unfolding the device.
155             if (oldFolded != null && !folded) {
156                 // Unfolding started
157                 onUnfoldStarted()
158                 isTransitionEnabled =
159                     transitionProgressProvider.isPresent && contentResolver.areAnimationsEnabled()
160 
161                 if (DEBUG) {
162                     Log.d(
163                         TAG,
164                         "Starting ACTION_SWITCH_DISPLAY_UNFOLD, " +
165                             "isTransitionEnabled = $isTransitionEnabled",
166                     )
167                 }
168             }
169         }
170     }
171 
172     private inner class FoldStateListener(context: Context) :
<lambda>null173         DeviceStateManager.FoldStateListener(context, { onFoldEvent(it) })
174 }
175 
176 private const val TAG = "UnfoldLatencyTracker"
177 private const val UNFOLD_IN_PROGRESS_TRACE_NAME = "Switch displays during unfold"
178 private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE)
179