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