• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2023 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.Context
20 import android.util.Log
21 import com.android.app.tracing.TraceUtils.traceAsync
22 import com.android.app.tracing.instantForTrack
23 import com.android.systemui.CoreStartable
24 import com.android.systemui.dagger.SysUISingleton
25 import com.android.systemui.dagger.qualifiers.Application
26 import com.android.systemui.display.data.repository.DeviceStateRepository
27 import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState
28 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
29 import com.android.systemui.power.domain.interactor.PowerInteractor
30 import com.android.systemui.power.shared.model.ScreenPowerState
31 import com.android.systemui.power.shared.model.WakeSleepReason
32 import com.android.systemui.power.shared.model.WakefulnessState
33 import com.android.systemui.shared.system.SysUiStatsLog
34 import com.android.systemui.unfold.DisplaySwitchLatencyTracker.DisplaySwitchLatencyEvent
35 import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg
36 import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
37 import com.android.systemui.util.Compile
38 import com.android.systemui.util.Utils.isDeviceFoldable
39 import com.android.systemui.util.animation.data.repository.AnimationStatusRepository
40 import com.android.systemui.util.kotlin.pairwise
41 import com.android.systemui.util.kotlin.race
42 import com.android.systemui.util.time.SystemClock
43 import com.android.systemui.util.time.measureTimeMillis
44 import java.time.Duration
45 import java.util.concurrent.Executor
46 import javax.inject.Inject
47 import kotlinx.coroutines.CoroutineScope
48 import kotlinx.coroutines.ExperimentalCoroutinesApi
49 import kotlinx.coroutines.TimeoutCancellationException
50 import kotlinx.coroutines.asCoroutineDispatcher
51 import kotlinx.coroutines.flow.filter
52 import kotlinx.coroutines.flow.first
53 import kotlinx.coroutines.flow.flatMapLatest
54 import kotlinx.coroutines.flow.flow
55 import kotlinx.coroutines.launch
56 import kotlinx.coroutines.withTimeout
57 
58 /**
59  * [DisplaySwitchLatencyTracker] tracks latency and related fields for display switch of a foldable
60  * device. This class populates [DisplaySwitchLatencyEvent] while an ongoing display switch event
61  */
62 @SysUISingleton
63 class DisplaySwitchLatencyTracker
64 @Inject
65 constructor(
66     private val context: Context,
67     private val deviceStateRepository: DeviceStateRepository,
68     private val powerInteractor: PowerInteractor,
69     private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
70     private val animationStatusRepository: AnimationStatusRepository,
71     private val keyguardInteractor: KeyguardInteractor,
72     @UnfoldSingleThreadBg private val singleThreadBgExecutor: Executor,
73     @Application private val applicationScope: CoroutineScope,
74     private val displaySwitchLatencyLogger: DisplaySwitchLatencyLogger,
75     private val systemClock: SystemClock
76 ) : CoreStartable {
77 
78     private val backgroundDispatcher = singleThreadBgExecutor.asCoroutineDispatcher()
79     private val isAodEnabled: Boolean
80         get() = keyguardInteractor.isAodAvailable.value
81 
82     @OptIn(ExperimentalCoroutinesApi::class)
83     override fun start() {
84         if (!isDeviceFoldable(context)) {
85             return
86         }
87         applicationScope.launch(backgroundDispatcher) {
88             deviceStateRepository.state
89                 .pairwise()
90                 .filter {
91                     // Start tracking only when the foldable device is
92                     // folding(UNFOLDED/HALF_FOLDED -> FOLDED) or
93                     // unfolding(FOLDED -> HALF_FOLD/UNFOLDED)
94                     foldableDeviceState ->
95                     foldableDeviceState.previousValue == DeviceState.FOLDED ||
96                         foldableDeviceState.newValue == DeviceState.FOLDED
97                 }
98                 .flatMapLatest { foldableDeviceState ->
99                     flow {
100                         var displaySwitchLatencyEvent = DisplaySwitchLatencyEvent()
101                         val toFoldableDeviceState = foldableDeviceState.newValue.toStatsInt()
102                         displaySwitchLatencyEvent =
103                             displaySwitchLatencyEvent.withBeforeFields(
104                                 foldableDeviceState.previousValue.toStatsInt()
105                             )
106 
107                         val displaySwitchTimeMs =
108                             measureTimeMillis(systemClock) {
109                                 try {
110                                     withTimeout(SCREEN_EVENT_TIMEOUT) {
111                                         traceAsync(TAG, "displaySwitch") {
112                                             waitForDisplaySwitch(toFoldableDeviceState)
113                                         }
114                                     }
115                                 } catch (e: TimeoutCancellationException) {
116                                     Log.e(TAG, "Wait for display switch timed out")
117                                 }
118                             }
119 
120                         displaySwitchLatencyEvent =
121                             displaySwitchLatencyEvent.withAfterFields(
122                                 toFoldableDeviceState,
123                                 displaySwitchTimeMs.toInt(),
124                                 getCurrentState()
125                             )
126                         emit(displaySwitchLatencyEvent)
127                     }
128                 }
129                 .collect { displaySwitchLatencyLogger.log(it) }
130         }
131     }
132 
133     private fun DeviceState.toStatsInt(): Int =
134         when (this) {
135             DeviceState.FOLDED -> FOLDABLE_DEVICE_STATE_CLOSED
136             DeviceState.HALF_FOLDED -> FOLDABLE_DEVICE_STATE_HALF_OPEN
137             DeviceState.UNFOLDED -> FOLDABLE_DEVICE_STATE_OPEN
138             DeviceState.CONCURRENT_DISPLAY -> FOLDABLE_DEVICE_STATE_FLIPPED
139             else -> FOLDABLE_DEVICE_STATE_UNKNOWN
140         }
141 
142     private suspend fun waitForDisplaySwitch(toFoldableDeviceState: Int) {
143         val isTransitionEnabled =
144             unfoldTransitionInteractor.isAvailable &&
145                 animationStatusRepository.areAnimationsEnabled().first()
146         if (shouldWaitForTransitionStart(toFoldableDeviceState, isTransitionEnabled)) {
147             traceAsync(TAG, "waitForTransitionStart()") {
148                 unfoldTransitionInteractor.waitForTransitionStart()
149             }
150         } else {
151             race({ waitForScreenTurnedOn() }, { waitForGoToSleepWithScreenOff() })
152         }
153     }
154 
155     private fun shouldWaitForTransitionStart(
156         toFoldableDeviceState: Int,
157         isTransitionEnabled: Boolean
158     ): Boolean = (toFoldableDeviceState != FOLDABLE_DEVICE_STATE_CLOSED && isTransitionEnabled)
159 
160     private suspend fun waitForScreenTurnedOn() {
161         traceAsync(TAG, "waitForScreenTurnedOn()") {
162             powerInteractor.screenPowerState.filter { it == ScreenPowerState.SCREEN_ON }.first()
163         }
164     }
165 
166     private suspend fun waitForGoToSleepWithScreenOff() {
167         traceAsync(TAG, "waitForGoToSleepWithScreenOff()") {
168             powerInteractor.detailedWakefulness
169                 .filter { it.internalWakefulnessState == WakefulnessState.ASLEEP && !isAodEnabled }
170                 .first()
171         }
172     }
173 
174     private fun getCurrentState(): Int =
175         when {
176             isStateAod() -> SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__AOD
177             isStateScreenOff() -> SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__SCREEN_OFF
178             else -> SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__UNKNOWN
179         }
180 
181     private fun isStateAod(): Boolean = (isAsleepDueToFold() && isAodEnabled)
182 
183     private fun isStateScreenOff(): Boolean = (isAsleepDueToFold() && !isAodEnabled)
184 
185     private fun isAsleepDueToFold(): Boolean {
186         val lastWakefulnessEvent = powerInteractor.detailedWakefulness.value
187 
188         return (lastWakefulnessEvent.isAsleep() &&
189             (lastWakefulnessEvent.lastSleepReason == WakeSleepReason.FOLD))
190     }
191 
192     private inline fun log(msg: () -> String) {
193         if (DEBUG) Log.d(TAG, msg())
194     }
195 
196     private fun DisplaySwitchLatencyEvent.withBeforeFields(
197         fromFoldableDeviceState: Int
198     ): DisplaySwitchLatencyEvent {
199         log { "fromFoldableDeviceState=$fromFoldableDeviceState" }
200         instantForTrack(TAG) { "fromFoldableDeviceState=$fromFoldableDeviceState" }
201 
202         return copy(fromFoldableDeviceState = fromFoldableDeviceState)
203     }
204 
205     private fun DisplaySwitchLatencyEvent.withAfterFields(
206         toFoldableDeviceState: Int,
207         displaySwitchTimeMs: Int,
208         toState: Int
209     ): DisplaySwitchLatencyEvent {
210         log {
211             "toFoldableDeviceState=$toFoldableDeviceState, " +
212                 "toState=$toState, " +
213                 "latencyMs=$displaySwitchTimeMs"
214         }
215         instantForTrack(TAG) { "toFoldableDeviceState=$toFoldableDeviceState, toState=$toState" }
216 
217         return copy(
218             toFoldableDeviceState = toFoldableDeviceState,
219             latencyMs = displaySwitchTimeMs,
220             toState = toState
221         )
222     }
223 
224     /**
225      * Stores values corresponding to all respective [DisplaySwitchLatencyTrackedField] in a single
226      * event of display switch for foldable devices.
227      *
228      * Once the data is captured in this data class and appropriate to log, it is logged through
229      * [DisplaySwitchLatencyLogger]
230      */
231     data class DisplaySwitchLatencyEvent(
232         val latencyMs: Int = VALUE_UNKNOWN,
233         val fromFoldableDeviceState: Int = FOLDABLE_DEVICE_STATE_UNKNOWN,
234         val fromState: Int = SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_STATE__UNKNOWN,
235         val fromFocusedAppUid: Int = VALUE_UNKNOWN,
236         val fromPipAppUid: Int = VALUE_UNKNOWN,
237         val fromVisibleAppsUid: Set<Int> = setOf(),
238         val fromDensityDpi: Int = VALUE_UNKNOWN,
239         val toFoldableDeviceState: Int = FOLDABLE_DEVICE_STATE_UNKNOWN,
240         val toState: Int = SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_STATE__UNKNOWN,
241         val toFocusedAppUid: Int = VALUE_UNKNOWN,
242         val toPipAppUid: Int = VALUE_UNKNOWN,
243         val toVisibleAppsUid: Set<Int> = setOf(),
244         val toDensityDpi: Int = VALUE_UNKNOWN,
245         val notificationCount: Int = VALUE_UNKNOWN,
246         val externalDisplayCount: Int = VALUE_UNKNOWN,
247         val throttlingLevel: Int =
248             SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__THROTTLING_LEVEL__NONE,
249         val vskinTemperatureC: Int = VALUE_UNKNOWN,
250         val hallSensorToFirstHingeAngleChangeMs: Int = VALUE_UNKNOWN,
251         val hallSensorToDeviceStateChangeMs: Int = VALUE_UNKNOWN,
252         val onScreenTurningOnToOnDrawnMs: Int = VALUE_UNKNOWN,
253         val onDrawnToOnScreenTurnedOnMs: Int = VALUE_UNKNOWN
254     )
255 
256     companion object {
257         private const val VALUE_UNKNOWN = -1
258         private const val TAG = "DisplaySwitchLatency"
259         private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE)
260         private val SCREEN_EVENT_TIMEOUT = Duration.ofMillis(15000).toMillis()
261 
262         private const val FOLDABLE_DEVICE_STATE_UNKNOWN =
263             SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_UNKNOWN
264         const val FOLDABLE_DEVICE_STATE_CLOSED =
265             SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_CLOSED
266         const val FOLDABLE_DEVICE_STATE_HALF_OPEN =
267             SysUiStatsLog
268                 .DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_HALF_OPENED
269         private const val FOLDABLE_DEVICE_STATE_OPEN =
270             SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_OPENED
271         private const val FOLDABLE_DEVICE_STATE_FLIPPED =
272             SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_FLIPPED
273     }
274 }
275