• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * 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.server.wm.traces.common.windowmanager
18 
19 import com.android.server.wm.traces.common.ITraceEntry
20 import com.android.server.wm.traces.common.prettyTimestamp
21 import com.android.server.wm.traces.common.windowmanager.windows.Activity
22 import com.android.server.wm.traces.common.windowmanager.windows.DisplayContent
23 import com.android.server.wm.traces.common.windowmanager.windows.KeyguardControllerState
24 import com.android.server.wm.traces.common.windowmanager.windows.RootWindowContainer
25 import com.android.server.wm.traces.common.windowmanager.windows.Task
26 import com.android.server.wm.traces.common.windowmanager.windows.TaskFragment
27 import com.android.server.wm.traces.common.windowmanager.windows.WindowContainer
28 import com.android.server.wm.traces.common.windowmanager.windows.WindowManagerPolicy
29 import com.android.server.wm.traces.common.windowmanager.windows.WindowState
30 
31 /**
32  * Represents a single WindowManager trace entry.
33  *
34  * This is a generic object that is reused by both Flicker and Winscope and cannot
35  * access internal Java/Android functionality
36  *
37  * The timestamp constructor must be a string due to lack of Kotlin/KotlinJS Long compatibility
38  *
39  **/
40 open class WindowManagerState(
41     val where: String,
42     val policy: WindowManagerPolicy?,
43     val focusedApp: String,
44     val focusedDisplayId: Int,
45     val focusedWindow: String,
46     val inputMethodWindowAppToken: String,
47     val isHomeRecentsComponent: Boolean,
48     val isDisplayFrozen: Boolean,
49     val pendingActivities: Array<String>,
50     val root: RootWindowContainer,
51     val keyguardControllerState: KeyguardControllerState,
52     _timestamp: String = "0"
53 ) : ITraceEntry {
54     override val timestamp: Long = _timestamp.toLong()
55     val isVisible: Boolean = true
56     val stableId: String get() = this::class.simpleName ?: error("Unable to determine class")
57     val name: String get() = prettyTimestamp(timestamp)
58 
59     val windowContainers: Array<WindowContainer>
60         get() = root.collectDescendants()
61 
62     val children: Array<WindowContainer>
63         get() = root.children.reversedArray()
64 
65     // Displays in z-order with the top most at the front of the list, starting with primary.
66     val displays: Array<DisplayContent>
67         get() = windowContainers.filterIsInstance<DisplayContent>().toTypedArray()
68 
69     // Root tasks in z-order with the top most at the front of the list, starting with primary display.
70     val rootTasks: Array<Task>
71         get() = displays.flatMap { it.rootTasks.toList() }.toTypedArray()
72 
73     // TaskFragments  in z-order with the top most at the front of the list.
74     val taskFragments: Array<TaskFragment>
75         get() = windowContainers.filterIsInstance<TaskFragment>().toTypedArray()
76 
77     // Windows in z-order with the top most at the front of the list.
78     val windowStates: Array<WindowState>
79         get() = windowContainers.filterIsInstance<WindowState>().toTypedArray()
80 
81     @Deprecated("Please use windowStates instead", replaceWith = ReplaceWith("windowStates"))
82     val windows: Array<WindowState>
83         get() = windowStates
84 
85     val appWindows: Array<WindowState>
86         get() = windowStates.filter { it.isAppWindow }.toTypedArray()
87     val nonAppWindows: Array<WindowState>
88         get() = windowStates.filterNot { it.isAppWindow }.toTypedArray()
89     val aboveAppWindows: Array<WindowState>
90         get() = windowStates.takeWhile { !appWindows.contains(it) }.toTypedArray()
91     val belowAppWindows: Array<WindowState>
92         get() = windowStates
93             .dropWhile { !appWindows.contains(it) }.drop(appWindows.size).toTypedArray()
94     val visibleWindows: Array<WindowState>
95         get() = windowStates
96             .filter { it.isVisible }
97             .filter { window ->
98                 val activities = getActivitiesForWindow(window.title)
99                 val activity = activities.firstOrNull { it.children.contains(window) }
100                 activity?.isVisible ?: true
101             }
102             .toTypedArray()
103     val visibleAppWindows: Array<WindowState>
104         get() = visibleWindows.filter { it.isAppWindow }.toTypedArray()
105     val topVisibleAppWindow: String
106         get() = visibleAppWindows
107             .map { it.title }
108             .firstOrNull() ?: ""
109     val pinnedWindows: Array<WindowState>
110         get() = visibleWindows
111             .filter { it.windowingMode == WINDOWING_MODE_PINNED }
112             .toTypedArray()
113 
114     /**
115      * Checks if the device state supports rotation, i.e., if the rotation sensor is
116      * enabled (e.g., launcher) and if the rotation not fixed
117      */
118     val canRotate: Boolean
119         get() = policy?.isFixedOrientation != true && policy?.isOrientationNoSensor != true
120     val focusedDisplay: DisplayContent? get() = getDisplay(focusedDisplayId)
121     val focusedStackId: Int get() = focusedDisplay?.focusedRootTaskId ?: -1
122     val focusedActivity: String get() {
123         val focusedDisplay = focusedDisplay
124         return if (focusedDisplay != null && focusedDisplay.resumedActivity.isNotEmpty()) {
125             focusedDisplay.resumedActivity
126         } else {
127             getActivitiesForWindow(focusedWindow, focusedDisplayId).firstOrNull()?.name ?: ""
128         }
129     }
130     val resumedActivities: Array<String>
131         get() = rootTasks.flatMap { it.resumedActivities.toList() }.toTypedArray()
132     val resumedActivitiesCount: Int get() = resumedActivities.size
133     val stackCount: Int get() = rootTasks.size
134     val homeTask: Task? get() = getStackByActivityType(ACTIVITY_TYPE_HOME)?.topTask
135     val recentsTask: Task? get() = getStackByActivityType(ACTIVITY_TYPE_RECENTS)?.topTask
136     val homeActivity: Activity? get() = homeTask?.activities?.lastOrNull()
137     val recentsActivity: Activity? get() = recentsTask?.activities?.lastOrNull()
138     val isRecentsActivityVisible: Boolean get() = recentsActivity?.isVisible ?: false
139     val frontWindow: String? get() = windowStates.map { it.title }.firstOrNull()
140     val inputMethodWindowState: WindowState?
141         get() = getWindowStateForAppToken(inputMethodWindowAppToken)
142 
143     fun getDefaultDisplay(): DisplayContent? =
144         displays.firstOrNull { it.id == DEFAULT_DISPLAY }
145 
146     fun getDisplay(displayId: Int): DisplayContent? =
147         displays.firstOrNull { it.id == displayId }
148 
149     fun countStacks(windowingMode: Int, activityType: Int): Int {
150         var count = 0
151         for (stack in rootTasks) {
152             if (activityType != ACTIVITY_TYPE_UNDEFINED && activityType != stack.activityType) {
153                 continue
154             }
155             if (windowingMode != WINDOWING_MODE_UNDEFINED && windowingMode != stack.windowingMode) {
156                 continue
157             }
158             ++count
159         }
160         return count
161     }
162 
163     fun getRootTask(taskId: Int): Task? =
164         rootTasks.firstOrNull { it.rootTaskId == taskId }
165 
166     fun getRotation(displayId: Int): Int =
167             getDisplay(displayId)?.rotation ?: error("Default display not found")
168 
169     fun getOrientation(displayId: Int): Int =
170             getDisplay(displayId)?.lastOrientation ?: error("Default display not found")
171 
172     fun getStackByActivityType(activityType: Int): Task? =
173         rootTasks.firstOrNull { it.activityType == activityType }
174 
175     fun getStandardStackByWindowingMode(windowingMode: Int): Task? =
176         rootTasks.firstOrNull {
177             it.activityType == ACTIVITY_TYPE_STANDARD &&
178                 it.windowingMode == windowingMode
179         }
180 
181     /**
182      * Get the all activities on display with id [displayId], containing a window whose title
183      * contains [partialWindowTitle]
184      *
185      * @param partialWindowTitle window title to search
186      * @param displayId display where to search the activity
187      */
188     fun getActivitiesForWindow(
189         partialWindowTitle: String,
190         displayId: Int = DEFAULT_DISPLAY
191     ): List<Activity> {
192         return displays.firstOrNull { it.id == displayId }?.rootTasks?.mapNotNull { stack ->
193             stack.getActivity { activity ->
194                 activity.hasWindow(partialWindowTitle)
195             }
196         } ?: emptyList()
197     }
198 
199     fun containsActivity(activityName: String): Boolean =
200         rootTasks.any { it.containsActivity(activityName) }
201 
202     fun isActivityVisible(activityName: String): Boolean {
203         val activity = rootTasks.mapNotNull { it.getActivity(activityName) }.firstOrNull()
204         return activity?.isVisible ?: false
205     }
206 
207     fun hasActivityState(activityName: String, activityState: String): Boolean =
208         rootTasks.any { it.getActivity(activityName)?.state == activityState }
209 
210     fun pendingActivityContain(activityName: String): Boolean {
211         return pendingActivities.contains(activityName)
212     }
213 
214     fun getMatchingVisibleWindowState(windowName: String): Array<WindowState> {
215         return windowStates.filter { it.isSurfaceShown && it.title.contains(windowName) }
216                 .toTypedArray()
217     }
218 
219     fun getNavBarWindow(displayId: Int): WindowState? {
220         val navWindow = windowStates.filter { it.isValidNavBarType && it.displayId == displayId }
221 
222         // We may need some time to wait for nav bar showing.
223         // It's Ok to get 0 nav bar here.
224         if (navWindow.size > 1) {
225             throw IllegalStateException("There should be at most one navigation bar on a display")
226         }
227         return navWindow.firstOrNull()
228     }
229 
230     fun getWindowStateForAppToken(appToken: String): WindowState? =
231         windowStates.firstOrNull { it.token == appToken }
232 
233     /**
234      * Check if there exists a window record with matching windowName.
235      */
236     fun containsWindow(windowName: String): Boolean =
237         windowStates.any { it.title.contains(windowName) }
238 
239     /**
240      * Check if at least one window which matches the specified name has shown it's surface.
241      */
242     fun isWindowSurfaceShown(windowName: String): Boolean =
243             getMatchingVisibleWindowState(windowName).isNotEmpty()
244 
245     /**
246      * Check if at least one window which matches provided window name is visible.
247      */
248     fun isWindowVisible(windowName: String): Boolean =
249         visibleWindows.any { it.title == windowName }
250 
251     /**
252      * Checks if the state has any window in PIP mode
253      */
254     fun hasPipWindow(): Boolean = pinnedWindows.isNotEmpty()
255 
256     /**
257      * Checks that an activity [windowName] is in PIP mode
258      */
259     fun isInPipMode(windowName: String): Boolean {
260         return pinnedWindows.any { it.title.contains(windowName) }
261     }
262 
263     fun getZOrder(w: WindowState): Int = windowStates.size - windowStates.indexOf(w)
264 
265     fun defaultMinimalTaskSize(displayId: Int): Int =
266         dpToPx(DEFAULT_RESIZABLE_TASK_SIZE_DP.toFloat(), getDisplay(displayId)!!.dpi)
267 
268     fun defaultMinimalDisplaySizeForSplitScreen(displayId: Int): Int {
269         return dpToPx(DEFAULT_MINIMAL_SPLIT_SCREEN_DISPLAY_SIZE_DP.toFloat(),
270             getDisplay(displayId)!!.dpi)
271     }
272 
273     fun getIsIncompleteReason(): String {
274         return buildString {
275             if (rootTasks.isEmpty()) {
276                 append("No stacks found...")
277             }
278             if (focusedStackId == -1) {
279                 append("No focused stack found...")
280             }
281             if (focusedActivity.isEmpty()) {
282                 append("No focused activity found...")
283             }
284             if (resumedActivities.isEmpty()) {
285                 append("No resumed activities found...")
286             }
287             if (windowStates.isEmpty()) {
288                 append("No Windows found...")
289             }
290             if (focusedWindow.isEmpty()) {
291                 append("No Focused Window...")
292             }
293             if (focusedApp.isEmpty()) {
294                 append("No Focused App...")
295             }
296             if (keyguardControllerState.isKeyguardShowing) {
297                 append("Keyguard showing...")
298             }
299         }
300     }
301 
302     fun isComplete(): Boolean = !isIncomplete()
303     fun isIncomplete(): Boolean {
304         return rootTasks.isEmpty() || focusedStackId == -1 || windowStates.isEmpty() ||
305             // overview screen has no focused window
306             ((focusedApp.isEmpty() || focusedWindow.isEmpty()) && homeActivity == null) ||
307             (focusedActivity.isEmpty() || resumedActivities.isEmpty()) &&
308             !keyguardControllerState.isKeyguardShowing
309     }
310 
311     fun asTrace(): WindowManagerTrace = WindowManagerTrace(arrayOf(this))
312 
313     override fun toString(): String {
314         return "${prettyTimestamp(timestamp)} (timestamp=$timestamp)"
315     }
316 
317     companion object {
318         const val STATE_INITIALIZING = "INITIALIZING"
319         const val STATE_RESUMED = "RESUMED"
320         const val STATE_PAUSED = "PAUSED"
321         const val STATE_STOPPED = "STOPPED"
322         const val STATE_DESTROYED = "DESTROYED"
323         const val APP_STATE_IDLE = "APP_STATE_IDLE"
324         internal const val ACTIVITY_TYPE_UNDEFINED = 0
325         internal const val ACTIVITY_TYPE_STANDARD = 1
326         internal const val DEFAULT_DISPLAY = 0
327         internal const val DEFAULT_MINIMAL_SPLIT_SCREEN_DISPLAY_SIZE_DP = 440
328         internal const val ACTIVITY_TYPE_HOME = 2
329         internal const val ACTIVITY_TYPE_RECENTS = 3
330         internal const val WINDOWING_MODE_UNDEFINED = 0
331         private const val DENSITY_DEFAULT = 160
332         /**
333          * @see android.app.WindowConfiguration.WINDOWING_MODE_PINNED
334          */
335         private const val WINDOWING_MODE_PINNED = 2
336 
337         /**
338          * @see android.view.WindowManager.LayoutParams
339          */
340         internal const val TYPE_NAVIGATION_BAR_PANEL = 2024
341 
342         // Default minimal size of resizable task, used if none is set explicitly.
343         // Must be kept in sync with 'default_minimal_size_resizable_task'
344         // dimen from frameworks/base.
345         internal const val DEFAULT_RESIZABLE_TASK_SIZE_DP = 220
346 
347         fun dpToPx(dp: Float, densityDpi: Int): Int {
348             return (dp * densityDpi / DENSITY_DEFAULT + 0.5f).toInt()
349         }
350     }
351     override fun equals(other: Any?): Boolean {
352         return other is WindowManagerState && other.timestamp == this.timestamp
353     }
354 }
355