• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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