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.hardware.devicestate.DeviceStateManager 21 import android.util.Log 22 import androidx.annotation.VisibleForTesting 23 import com.android.app.tracing.TraceUtils.traceAsync 24 import com.android.app.tracing.instantForTrack 25 import com.android.internal.util.LatencyTracker 26 import com.android.internal.util.LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD 27 import com.android.systemui.CoreStartable 28 import com.android.systemui.dagger.SysUISingleton 29 import com.android.systemui.dagger.qualifiers.Application 30 import com.android.systemui.display.data.repository.DeviceStateRepository 31 import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState 32 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor 33 import com.android.systemui.power.domain.interactor.PowerInteractor 34 import com.android.systemui.power.shared.model.ScreenPowerState 35 import com.android.systemui.power.shared.model.WakeSleepReason 36 import com.android.systemui.power.shared.model.WakefulnessModel 37 import com.android.systemui.power.shared.model.WakefulnessState 38 import com.android.systemui.shared.system.SysUiStatsLog 39 import com.android.systemui.unfold.DisplaySwitchLatencyTracker.TrackingResult.CORRUPTED 40 import com.android.systemui.unfold.DisplaySwitchLatencyTracker.TrackingResult.SUCCESS 41 import com.android.systemui.unfold.DisplaySwitchLatencyTracker.TrackingResult.TIMED_OUT 42 import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg 43 import com.android.systemui.unfold.data.repository.ScreenTimeoutPolicyRepository 44 import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionStarted 45 import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor 46 import com.android.systemui.util.Compile 47 import com.android.systemui.util.Utils.isDeviceFoldable 48 import com.android.systemui.util.animation.data.repository.AnimationStatusRepository 49 import com.android.systemui.util.kotlin.WithPrev 50 import com.android.systemui.util.kotlin.pairwise 51 import com.android.systemui.util.kotlin.race 52 import com.android.systemui.util.time.SystemClock 53 import com.android.systemui.util.time.measureTimeMillis 54 import java.util.concurrent.Executor 55 import javax.inject.Inject 56 import kotlin.coroutines.cancellation.CancellationException 57 import kotlin.time.Duration.Companion.seconds 58 import kotlinx.coroutines.CoroutineScope 59 import kotlinx.coroutines.FlowPreview 60 import kotlinx.coroutines.TimeoutCancellationException 61 import kotlinx.coroutines.asCoroutineDispatcher 62 import kotlinx.coroutines.flow.Flow 63 import kotlinx.coroutines.flow.collectLatest 64 import kotlinx.coroutines.flow.drop 65 import kotlinx.coroutines.flow.filter 66 import kotlinx.coroutines.flow.first 67 import kotlinx.coroutines.flow.merge 68 import kotlinx.coroutines.flow.timeout 69 import kotlinx.coroutines.launch 70 import kotlinx.coroutines.withTimeout 71 72 /** 73 * [DisplaySwitchLatencyTracker] tracks latency and related fields for display switch of a foldable 74 * device. This class populates [DisplaySwitchLatencyEvent] while an ongoing display switch event 75 */ 76 @SysUISingleton 77 class DisplaySwitchLatencyTracker 78 @Inject 79 constructor( 80 private val context: Context, 81 private val deviceStateRepository: DeviceStateRepository, 82 private val powerInteractor: PowerInteractor, 83 private val screenTimeoutPolicyRepository: ScreenTimeoutPolicyRepository, 84 private val unfoldTransitionInteractor: UnfoldTransitionInteractor, 85 private val animationStatusRepository: AnimationStatusRepository, 86 private val keyguardInteractor: KeyguardInteractor, 87 @UnfoldSingleThreadBg private val singleThreadBgExecutor: Executor, 88 @Application private val applicationScope: CoroutineScope, 89 private val displaySwitchLatencyLogger: DisplaySwitchLatencyLogger, 90 private val systemClock: SystemClock, 91 private val deviceStateManager: DeviceStateManager, 92 private val latencyTracker: LatencyTracker, 93 ) : CoreStartable { 94 95 private val backgroundDispatcher = singleThreadBgExecutor.asCoroutineDispatcher() 96 private val isAodEnabled: Boolean 97 get() = keyguardInteractor.isAodAvailable.value 98 99 private val displaySwitchStarted = 100 deviceStateRepository.state.pairwise().filter { 101 // Start tracking only when the foldable device is 102 // folding(UNFOLDED/HALF_FOLDED -> FOLDED) or unfolding(FOLDED -> HALF_FOLD/UNFOLDED) 103 foldableDeviceState -> 104 foldableDeviceState.previousValue == DeviceState.FOLDED || 105 foldableDeviceState.newValue == DeviceState.FOLDED 106 } 107 108 private var startOrEndEvent: Flow<Any> = merge(displaySwitchStarted, anyEndEventFlow()) 109 110 private var isCoolingDown = false 111 112 override fun start() { 113 if (!isDeviceFoldable(context.resources, deviceStateManager)) { 114 return 115 } 116 applicationScope.launch(context = backgroundDispatcher) { 117 displaySwitchStarted.collectLatest { (previousState, newState) -> 118 if (isCoolingDown) return@collectLatest 119 if (previousState == DeviceState.FOLDED) { 120 latencyTracker.onActionStart(ACTION_SWITCH_DISPLAY_UNFOLD) 121 instantForTrack(TAG) { "unfold latency tracking started" } 122 } 123 val event = DisplaySwitchLatencyEvent().withBeforeFields(previousState.toStatsInt()) 124 try { 125 withTimeout(SCREEN_EVENT_TIMEOUT) { 126 val displaySwitchTimeMs = 127 measureTimeMillis(systemClock) { 128 traceAsync(TAG, "displaySwitch") { 129 waitForDisplaySwitch(newState.toStatsInt()) 130 } 131 } 132 if (previousState == DeviceState.FOLDED) { 133 latencyTracker.onActionEnd(ACTION_SWITCH_DISPLAY_UNFOLD) 134 } 135 logDisplaySwitchEvent(event, newState, displaySwitchTimeMs) 136 } 137 } catch (e: TimeoutCancellationException) { 138 instantForTrack(TAG) { "tracking timed out" } 139 latencyTracker.onActionCancel(ACTION_SWITCH_DISPLAY_UNFOLD) 140 logDisplaySwitchEvent( 141 event = event, 142 toFoldableDeviceState = newState, 143 displaySwitchTimeMs = SCREEN_EVENT_TIMEOUT.inWholeMilliseconds, 144 trackingResult = TIMED_OUT, 145 ) 146 } catch (e: CancellationException) { 147 instantForTrack(TAG) { "new state interrupted, entering cool down" } 148 latencyTracker.onActionCancel(ACTION_SWITCH_DISPLAY_UNFOLD) 149 startCoolDown(event) 150 } 151 } 152 } 153 } 154 155 @OptIn(FlowPreview::class) 156 private fun startCoolDown(event: DisplaySwitchLatencyEvent) { 157 if (isCoolingDown) return 158 isCoolingDown = true 159 applicationScope.launch(context = backgroundDispatcher) { 160 val startTime = systemClock.elapsedRealtime() 161 var lastState: DeviceState? = null 162 try { 163 startOrEndEvent.timeout(COOL_DOWN_DURATION).collect { 164 if (it is WithPrev<*, *>) { 165 lastState = it.newValue as? DeviceState 166 } 167 } 168 } catch (e: TimeoutCancellationException) { 169 val totalCooldownTime = systemClock.elapsedRealtime() - startTime 170 logDisplaySwitchEvent( 171 event = event, 172 toFoldableDeviceState = lastState ?: DeviceState.UNKNOWN, 173 displaySwitchTimeMs = totalCooldownTime, 174 trackingResult = CORRUPTED, 175 ) 176 instantForTrack(TAG) { "cool down finished, lasted $totalCooldownTime ms" } 177 isCoolingDown = false 178 } 179 } 180 } 181 182 private fun logDisplaySwitchEvent( 183 event: DisplaySwitchLatencyEvent, 184 toFoldableDeviceState: DeviceState, 185 displaySwitchTimeMs: Long, 186 trackingResult: TrackingResult = SUCCESS, 187 ) { 188 displaySwitchLatencyLogger.log( 189 event.withAfterFields( 190 toFoldableDeviceState, 191 displaySwitchTimeMs, 192 getCurrentState(), 193 trackingResult, 194 ) 195 ) 196 } 197 198 private fun DeviceState.toStatsInt(): Int = 199 when (this) { 200 DeviceState.FOLDED -> FOLDABLE_DEVICE_STATE_CLOSED 201 DeviceState.HALF_FOLDED -> FOLDABLE_DEVICE_STATE_HALF_OPEN 202 DeviceState.UNFOLDED -> FOLDABLE_DEVICE_STATE_OPEN 203 DeviceState.CONCURRENT_DISPLAY -> FOLDABLE_DEVICE_STATE_FLIPPED 204 else -> FOLDABLE_DEVICE_STATE_UNKNOWN 205 } 206 207 private fun TrackingResult.toStatsInt(): Int = 208 when (this) { 209 SUCCESS -> SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TRACKING_RESULT__SUCCESS 210 CORRUPTED -> SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TRACKING_RESULT__CORRUPTED 211 TIMED_OUT -> SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TRACKING_RESULT__TIMED_OUT 212 } 213 214 private suspend fun waitForDisplaySwitch(toFoldableDeviceState: Int) { 215 val isTransitionEnabled = 216 unfoldTransitionInteractor.isAvailable && 217 animationStatusRepository.areAnimationsEnabled().first() 218 if (shouldWaitForTransitionStart(toFoldableDeviceState, isTransitionEnabled)) { 219 traceAsync(TAG, "waitForTransitionStart()") { 220 unfoldTransitionInteractor.waitForTransitionStart() 221 } 222 } else { 223 race({ waitForScreenTurnedOn() }, { waitForGoToSleepWithScreenOff() }) 224 } 225 } 226 227 private fun anyEndEventFlow(): Flow<Any> { 228 val unfoldStatus = 229 unfoldTransitionInteractor.unfoldTransitionStatus.filter { it is TransitionStarted } 230 // dropping first emission as we're only interested in new emissions, not current state 231 val screenOn = 232 powerInteractor.screenPowerState.drop(1).filter { it == ScreenPowerState.SCREEN_ON } 233 val goToSleep = 234 powerInteractor.detailedWakefulness.drop(1).filter { sleepWithScreenOff(it) } 235 return merge(screenOn, goToSleep, unfoldStatus) 236 } 237 238 private fun shouldWaitForTransitionStart( 239 toFoldableDeviceState: Int, 240 isTransitionEnabled: Boolean, 241 ): Boolean = (toFoldableDeviceState != FOLDABLE_DEVICE_STATE_CLOSED && isTransitionEnabled) 242 243 private suspend fun waitForScreenTurnedOn() { 244 traceAsync(TAG, "waitForScreenTurnedOn()") { 245 // dropping first as it's stateFlow and will always emit latest value but we're 246 // only interested in new states 247 powerInteractor.screenPowerState 248 .drop(1) 249 .filter { it == ScreenPowerState.SCREEN_ON } 250 .first() 251 } 252 } 253 254 private suspend fun waitForGoToSleepWithScreenOff() { 255 traceAsync(TAG, "waitForGoToSleepWithScreenOff()") { 256 powerInteractor.detailedWakefulness.filter { sleepWithScreenOff(it) }.first() 257 } 258 } 259 260 private fun sleepWithScreenOff(model: WakefulnessModel) = 261 model.internalWakefulnessState == WakefulnessState.ASLEEP && !isAodEnabled 262 263 private fun getCurrentState(): Int = 264 when { 265 isStateAod() -> SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__AOD 266 isStateScreenOff() -> SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__SCREEN_OFF 267 else -> SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__UNKNOWN 268 } 269 270 private fun isStateAod(): Boolean = (isAsleepDueToFold() && isAodEnabled) 271 272 private fun isStateScreenOff(): Boolean = (isAsleepDueToFold() && !isAodEnabled) 273 274 private fun isAsleepDueToFold(): Boolean { 275 val lastWakefulnessEvent = powerInteractor.detailedWakefulness.value 276 277 return (lastWakefulnessEvent.isAsleep() && 278 (lastWakefulnessEvent.lastSleepReason == WakeSleepReason.FOLD)) 279 } 280 281 private inline fun log(msg: () -> String) { 282 if (DEBUG) Log.d(TAG, msg()) 283 } 284 285 private fun DisplaySwitchLatencyEvent.withBeforeFields( 286 fromFoldableDeviceState: Int 287 ): DisplaySwitchLatencyEvent { 288 log { "fromFoldableDeviceState=$fromFoldableDeviceState" } 289 instantForTrack(TAG) { "fromFoldableDeviceState=$fromFoldableDeviceState" } 290 291 val screenTimeoutActive = screenTimeoutPolicyRepository.screenTimeoutActive.value 292 val screenWakelockStatus = 293 if (screenTimeoutActive) { 294 NO_SCREEN_WAKELOCKS 295 } else { 296 HAS_SCREEN_WAKELOCKS 297 } 298 299 return copy( 300 fromFoldableDeviceState = fromFoldableDeviceState, 301 screenWakelockStatus = screenWakelockStatus 302 ) 303 } 304 305 private fun DisplaySwitchLatencyEvent.withAfterFields( 306 toFoldableDeviceState: DeviceState, 307 displaySwitchTimeMs: Long, 308 toState: Int, 309 trackingResult: TrackingResult, 310 ): DisplaySwitchLatencyEvent { 311 log { 312 "trackingResult=$trackingResult, " + 313 "toFoldableDeviceState=$toFoldableDeviceState, " + 314 "toState=$toState, " + 315 "latencyMs=$displaySwitchTimeMs" 316 } 317 instantForTrack(TAG) { "toFoldableDeviceState=$toFoldableDeviceState, toState=$toState" } 318 319 return copy( 320 toFoldableDeviceState = toFoldableDeviceState.toStatsInt(), 321 latencyMs = displaySwitchTimeMs.toInt(), 322 toState = toState, 323 trackingResult = trackingResult.toStatsInt(), 324 ) 325 } 326 327 /** 328 * Stores values corresponding to all respective [DisplaySwitchLatencyTrackedField] in a single 329 * event of display switch for foldable devices. 330 * 331 * Once the data is captured in this data class and appropriate to log, it is logged through 332 * [DisplaySwitchLatencyLogger] 333 */ 334 data class DisplaySwitchLatencyEvent( 335 val latencyMs: Int = VALUE_UNKNOWN, 336 val fromFoldableDeviceState: Int = FOLDABLE_DEVICE_STATE_UNKNOWN, 337 val fromState: Int = SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_STATE__UNKNOWN, 338 val fromFocusedAppUid: Int = VALUE_UNKNOWN, 339 val fromPipAppUid: Int = VALUE_UNKNOWN, 340 val fromVisibleAppsUid: Set<Int> = setOf(), 341 val fromDensityDpi: Int = VALUE_UNKNOWN, 342 val toFoldableDeviceState: Int = FOLDABLE_DEVICE_STATE_UNKNOWN, 343 val toState: Int = SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_STATE__UNKNOWN, 344 val toFocusedAppUid: Int = VALUE_UNKNOWN, 345 val toPipAppUid: Int = VALUE_UNKNOWN, 346 val toVisibleAppsUid: Set<Int> = setOf(), 347 val toDensityDpi: Int = VALUE_UNKNOWN, 348 val notificationCount: Int = VALUE_UNKNOWN, 349 val externalDisplayCount: Int = VALUE_UNKNOWN, 350 val throttlingLevel: Int = 351 SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__THROTTLING_LEVEL__NONE, 352 val vskinTemperatureC: Int = VALUE_UNKNOWN, 353 val hallSensorToFirstHingeAngleChangeMs: Int = VALUE_UNKNOWN, 354 val hallSensorToDeviceStateChangeMs: Int = VALUE_UNKNOWN, 355 val onScreenTurningOnToOnDrawnMs: Int = VALUE_UNKNOWN, 356 val onDrawnToOnScreenTurnedOnMs: Int = VALUE_UNKNOWN, 357 val trackingResult: Int = 358 SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TRACKING_RESULT__UNKNOWN_RESULT, 359 val screenWakelockStatus: Int = 360 SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__SCREEN_WAKELOCK_STATUS__SCREEN_WAKELOCK_STATUS_UNKNOWN, 361 ) 362 363 enum class TrackingResult { 364 SUCCESS, 365 CORRUPTED, 366 TIMED_OUT, 367 } 368 369 companion object { 370 private const val VALUE_UNKNOWN = -1 371 private const val TAG = "DisplaySwitchLatency" 372 private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE) 373 @VisibleForTesting val SCREEN_EVENT_TIMEOUT = 15.seconds 374 @VisibleForTesting val COOL_DOWN_DURATION = 2.seconds 375 376 private const val FOLDABLE_DEVICE_STATE_UNKNOWN = 377 SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_UNKNOWN 378 const val FOLDABLE_DEVICE_STATE_CLOSED = 379 SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_CLOSED 380 const val FOLDABLE_DEVICE_STATE_HALF_OPEN = 381 SysUiStatsLog 382 .DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_HALF_OPENED 383 private const val FOLDABLE_DEVICE_STATE_OPEN = 384 SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_OPENED 385 private const val FOLDABLE_DEVICE_STATE_FLIPPED = 386 SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_FLIPPED 387 388 private const val HAS_SCREEN_WAKELOCKS = 389 SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__SCREEN_WAKELOCK_STATUS__SCREEN_WAKELOCK_STATUS_HAS_SCREEN_WAKELOCKS 390 private const val NO_SCREEN_WAKELOCKS = 391 SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__SCREEN_WAKELOCK_STATUS__SCREEN_WAKELOCK_STATUS_NO_WAKELOCKS 392 } 393 } 394