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