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.util.Log 23 import com.android.internal.util.LatencyTracker 24 import com.android.systemui.dagger.SysUISingleton 25 import com.android.systemui.dagger.qualifiers.UiBackground 26 import com.android.systemui.keyguard.ScreenLifecycle 27 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener 28 import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider.Companion.areAnimationsEnabled 29 import com.android.systemui.util.Compile 30 import java.util.Optional 31 import java.util.concurrent.Executor 32 import javax.inject.Inject 33 34 /** 35 * Logs performance metrics regarding time to turn the inner screen on. 36 * 37 * This class assumes that [onFoldEvent] is always called before [onScreenTurnedOn]. 38 * 39 * This should be used from only one process. 40 * 41 * For now, the focus is on the time the inner display is visible, but in the future, it is easily 42 * possible to monitor the time to go from the inner screen to the outer. 43 */ 44 @SysUISingleton 45 class UnfoldLatencyTracker 46 @Inject 47 constructor( 48 private val latencyTracker: LatencyTracker, 49 private val deviceStateManager: DeviceStateManager, 50 private val transitionProgressProvider: Optional<UnfoldTransitionProgressProvider>, 51 @UiBackground private val uiBgExecutor: Executor, 52 private val context: Context, 53 private val contentResolver: ContentResolver, 54 private val screenLifecycle: ScreenLifecycle 55 ) : ScreenLifecycle.Observer, TransitionProgressListener { 56 57 private var folded: Boolean? = null 58 private var isTransitionEnabled: Boolean? = null 59 private val foldStateListener = FoldStateListener(context) 60 private val isFoldable: Boolean 61 get() = 62 context.resources 63 .getIntArray(com.android.internal.R.array.config_foldedDeviceStates) 64 .isNotEmpty() 65 66 /** Registers for relevant events only if the device is foldable. */ initnull67 fun init() { 68 if (!isFoldable) { 69 return 70 } 71 deviceStateManager.registerCallback(uiBgExecutor, foldStateListener) 72 screenLifecycle.addObserver(this) 73 if (transitionProgressProvider.isPresent) { 74 // Might not be present if the device is not a foldable device or unfold transition 75 // is disabled in the device configuration 76 transitionProgressProvider.get().addCallback(this) 77 } 78 } 79 80 /** 81 * To be called when the screen becomes visible. 82 * 83 * This is safe to call also when unsure whether the device is not a foldable, as it emits the 84 * end action event only if we previously received a fold state. 85 */ onScreenTurnedOnnull86 override fun onScreenTurnedOn() { 87 if (DEBUG) { 88 Log.d( 89 TAG, 90 "onScreenTurnedOn: folded = $folded, isTransitionEnabled = $isTransitionEnabled" 91 ) 92 } 93 94 // We use onScreenTurnedOn event to finish tracking only if we are not playing 95 // the unfold animation (e.g. it could be disabled because of battery saver). 96 // When animation is enabled finishing of the tracking will be done in onTransitionStarted. 97 if (folded == false && isTransitionEnabled == false) { 98 latencyTracker.onActionEnd(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD) 99 100 if (DEBUG) { 101 Log.d(TAG, "onScreenTurnedOn: ending ACTION_SWITCH_DISPLAY_UNFOLD") 102 } 103 } 104 } 105 106 /** 107 * This callback is used to end the metric when the unfold animation is enabled because it could 108 * add an additional delay to synchronize with launcher. 109 */ onTransitionStartednull110 override fun onTransitionStarted() { 111 if (DEBUG) { 112 Log.d( 113 TAG, 114 "onTransitionStarted: folded = $folded, isTransitionEnabled = $isTransitionEnabled" 115 ) 116 } 117 118 if (folded == false && isTransitionEnabled == true) { 119 latencyTracker.onActionEnd(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD) 120 121 if (DEBUG) { 122 Log.d(TAG, "onTransitionStarted: ending ACTION_SWITCH_DISPLAY_UNFOLD") 123 } 124 } 125 } 126 onFoldEventnull127 private fun onFoldEvent(folded: Boolean) { 128 val oldFolded = this.folded 129 130 if (oldFolded != folded) { 131 this.folded = folded 132 133 if (DEBUG) { 134 Log.d(TAG, "Received onFoldEvent = $folded") 135 } 136 137 // Do not start tracking when oldFolded is null, this means that this is the first 138 // onFoldEvent after booting the device or starting SystemUI and not actual folding or 139 // unfolding the device. 140 if (oldFolded != null && !folded) { 141 // Unfolding started 142 latencyTracker.onActionStart(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD) 143 isTransitionEnabled = 144 transitionProgressProvider.isPresent && contentResolver.areAnimationsEnabled() 145 146 if (DEBUG) { 147 Log.d( 148 TAG, 149 "Starting ACTION_SWITCH_DISPLAY_UNFOLD, " + 150 "isTransitionEnabled = $isTransitionEnabled" 151 ) 152 } 153 } 154 } 155 } 156 157 private inner class FoldStateListener(context: Context) : <lambda>null158 DeviceStateManager.FoldStateListener(context, { onFoldEvent(it) }) 159 } 160 161 private const val TAG = "UnfoldLatencyTracker" 162 private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE) 163