xref: /aosp_15_r20/platform_testing/libraries/flicker/utils/src/android/tools/traces/wm/WindowManagerState.kt (revision dd0948b35e70be4c0246aabd6c72554a5eb8b22a)
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 android.tools.traces.wm
18 
19 import android.tools.PlatformConsts
20 import android.tools.Rotation
21 import android.tools.Timestamps
22 import android.tools.TraceEntry
23 import android.tools.traces.component.IComponentMatcher
24 import android.tools.traces.wm.Utils.collectDescendants
25 
26 /**
27  * Represents a single WindowManager trace entry.
28  *
29  * This is a generic object that is reused by both Flicker and Winscope and cannot access internal
30  * Java/Android functionality
31  *
32  * The timestamp constructor must be a string due to lack of Kotlin/KotlinJS Long compatibility
33  */
34 class WindowManagerState(
35     val elapsedTimestamp: Long,
36     val clockTimestamp: Long?,
37     val where: String,
38     val policy: WindowManagerPolicy?,
39     val focusedApp: String,
40     val focusedDisplayId: Int,
41     private val _focusedWindow: String,
42     val inputMethodWindowAppToken: String,
43     val isHomeRecentsComponent: Boolean,
44     val isDisplayFrozen: Boolean,
45     private val _pendingActivities: Collection<String>,
46     val root: RootWindowContainer,
47     val keyguardControllerState: KeyguardControllerState,
48 ) : TraceEntry {
49     override val timestamp =
50         Timestamps.from(elapsedNanos = elapsedTimestamp, unixNanos = clockTimestamp)
51 
52     val isVisible: Boolean = true
53 
54     val stableId: String
55         get() = this::class.simpleName ?: error("Unable to determine class")
56 
57     val isTablet: Boolean
58         get() = displays.any { it.isTablet }
59 
60     val windowContainers: Collection<WindowContainer>
61         get() = root.collectDescendants()
62 
63     val children: Collection<WindowContainer>
64         get() = root.children.reversed()
65 
66     /** Displays in z-order with the top most at the front of the list, starting with primary. */
67     val displays: Collection<DisplayContent>
68         get() = windowContainers.filterIsInstance<DisplayContent>()
69 
70     /**
71      * Root tasks in z-order with the top most at the front of the list, starting with primary
72      * display.
73      */
74     val rootTasks: Collection<Task>
75         get() = displays.flatMap { it.rootTasks }
76 
77     /** TaskFragments in z-order with the top most at the front of the list. */
78     val taskFragments: Collection<TaskFragment>
79         get() = windowContainers.filterIsInstance<TaskFragment>()
80 
81     /** Windows in z-order with the top most at the front of the list. */
82     val windowStates: Collection<WindowState>
83         get() = windowContainers.filterIsInstance<WindowState>()
84 
85     @Deprecated("Please use windowStates instead", replaceWith = ReplaceWith("windowStates"))
86     val windows: Collection<WindowState>
87         get() = windowStates
88 
89     val appWindows: Collection<WindowState>
90         get() = windowStates.filter { it.isAppWindow }
91 
92     val nonAppWindows: Collection<WindowState>
93         get() = windowStates.filterNot { it.isAppWindow }
94 
95     val aboveAppWindows: Collection<WindowState>
96         get() = windowStates.takeWhile { !appWindows.contains(it) }
97 
98     val belowAppWindows: Collection<WindowState>
99         get() = windowStates.dropWhile { !appWindows.contains(it) }.drop(appWindows.size)
100 
101     val visibleWindows: Collection<WindowState>
102         get() =
103             windowStates.filter {
104                 val activities = getActivitiesForWindowState(it)
105                 val windowIsVisible = it.isVisible
106                 val activityIsVisible = activities.any { activity -> activity.isVisible }
107 
108                 // for invisible checks it suffices if activity or window is invisible
109                 windowIsVisible && (activityIsVisible || activities.isEmpty())
110             }
111 
112     val visibleAppWindows: Collection<WindowState>
113         get() = visibleWindows.filter { it.isAppWindow }
114 
115     val topVisibleAppWindow: WindowState?
116         get() = visibleAppWindows.firstOrNull()
117 
118     val pinnedWindows: Collection<WindowState>
119         get() = visibleWindows.filter { it.windowingMode == PlatformConsts.WINDOWING_MODE_PINNED }
120 
121     val pendingActivities: Collection<Activity>
122         get() = _pendingActivities.mapNotNull { getActivityByName(it) }
123 
124     val focusedWindow: WindowState?
125         get() = visibleWindows.firstOrNull { it.name == _focusedWindow }
126 
127     val isKeyguardShowing: Boolean
128         get() = keyguardControllerState.isKeyguardShowing
129 
130     val isAodShowing: Boolean
131         get() = keyguardControllerState.isAodShowing
132 
133     /**
134      * Checks if the device state supports rotation, i.e., if the rotation sensor is enabled (e.g.,
135      * launcher) and if the rotation not fixed
136      */
137     val canRotate: Boolean
138         get() = policy?.isFixedOrientation != true && policy?.isOrientationNoSensor != true
139 
140     val focusedDisplay: DisplayContent?
141         get() = getDisplay(focusedDisplayId)
142 
143     val focusedStackId: Int
144         get() = focusedDisplay?.focusedRootTaskId ?: -1
145 
146     val focusedActivity: Activity?
147         get() {
148             val focusedDisplay = focusedDisplay
149             val focusedWindow = focusedWindow
150             return when {
151                 focusedDisplay != null && focusedDisplay.resumedActivity.isNotEmpty() ->
152                     getActivityByName(focusedDisplay.resumedActivity)
153                 focusedWindow != null ->
154                     getActivitiesForWindowState(focusedWindow, focusedDisplayId).firstOrNull()
155                 else -> null
156             }
157         }
158 
159     val resumedActivities: Collection<Activity>
160         get() = rootTasks.flatMap { it.resumedActivities }.mapNotNull { getActivityByName(it) }
161 
162     val resumedActivitiesCount: Int
163         get() = resumedActivities.size
164 
165     val stackCount: Int
166         get() = rootTasks.size
167 
168     val homeTask: Task?
169         get() = getStackByActivityType(PlatformConsts.ACTIVITY_TYPE_HOME)?.topTask
170 
171     val recentsTask: Task?
172         get() = getStackByActivityType(PlatformConsts.ACTIVITY_TYPE_RECENTS)?.topTask
173 
174     val homeActivity: Activity?
175         get() = homeTask?.activities?.lastOrNull()
176 
177     val isHomeActivityVisible: Boolean
178         get() {
179             val activity = homeActivity
180             return activity != null && activity.isVisible
181         }
182 
183     val recentsActivity: Activity?
184         get() = recentsTask?.activities?.lastOrNull()
185 
186     val isRecentsActivityVisible: Boolean
187         get() {
188             return if (isHomeRecentsComponent) {
189                 isHomeActivityVisible
190             } else {
191                 val activity = recentsActivity
192                 activity != null && activity.isVisible
193             }
194         }
195 
196     val frontWindow: WindowState?
197         get() = windowStates.firstOrNull()
198 
199     val inputMethodWindowState: WindowState?
200         get() = getWindowStateForAppToken(inputMethodWindowAppToken)
201 
202     fun getDefaultDisplay(): DisplayContent? =
203         displays.firstOrNull { it.displayId == PlatformConsts.DEFAULT_DISPLAY }
204 
205     fun getDisplay(displayId: Int): DisplayContent? =
206         displays.firstOrNull { it.displayId == displayId }
207 
208     fun countStacks(windowingMode: Int, activityType: Int): Int {
209         var count = 0
210         for (stack in rootTasks) {
211             if (
212                 activityType != PlatformConsts.ACTIVITY_TYPE_UNDEFINED &&
213                     activityType != stack.activityType
214             ) {
215                 continue
216             }
217             if (
218                 windowingMode != PlatformConsts.WINDOWING_MODE_UNDEFINED &&
219                     windowingMode != stack.windowingMode
220             ) {
221                 continue
222             }
223             ++count
224         }
225         return count
226     }
227 
228     fun getRootTask(taskId: Int): Task? = rootTasks.firstOrNull { it.rootTaskId == taskId }
229 
230     fun getRotation(displayId: Int): Rotation =
231         getDisplay(displayId)?.rotation ?: error("Default display not found")
232 
233     fun getOrientation(displayId: Int): Int =
234         getDisplay(displayId)?.lastOrientation ?: error("Default display not found")
235 
236     fun getStackByActivityType(activityType: Int): Task? =
237         rootTasks.firstOrNull { it.activityType == activityType }
238 
239     fun getStandardStackByWindowingMode(windowingMode: Int): Task? =
240         rootTasks.firstOrNull {
241             it.activityType == PlatformConsts.ACTIVITY_TYPE_STANDARD &&
242                 it.windowingMode == windowingMode
243         }
244 
245     fun getActivitiesForWindowState(
246         windowState: WindowState,
247         displayId: Int = PlatformConsts.DEFAULT_DISPLAY,
248     ): Collection<Activity> {
249         return displays
250             .firstOrNull { it.displayId == displayId }
251             ?.rootTasks
252             ?.mapNotNull { stack ->
253                 stack.getActivity { activity -> activity.hasWindowState(windowState) }
254             } ?: emptyList()
255     }
256 
257     /**
258      * Get the all activities on display with id [displayId], containing a matching
259      * [componentMatcher]
260      *
261      * @param componentMatcher Components to search
262      * @param displayId display where to search the activity
263      */
264     fun getActivitiesForWindow(
265         componentMatcher: IComponentMatcher,
266         displayId: Int = PlatformConsts.DEFAULT_DISPLAY,
267     ): Collection<Activity> {
268         return displays
269             .firstOrNull { it.displayId == displayId }
270             ?.rootTasks
271             ?.mapNotNull { stack ->
272                 stack.getActivity { activity -> activity.hasWindow(componentMatcher) }
273             } ?: emptyList()
274     }
275 
276     /**
277      * @param componentMatcher Components to search
278      * @return if any activity matches [componentMatcher]
279      */
280     fun containsActivity(componentMatcher: IComponentMatcher): Boolean =
281         rootTasks.any { it.containsActivity(componentMatcher) }
282 
283     /**
284      * @param componentMatcher Components to search
285      * @return the first [Activity] matching [componentMatcher], or null otherwise
286      */
287     fun getActivity(componentMatcher: IComponentMatcher): Activity? =
288         rootTasks.firstNotNullOfOrNull { it.getActivity(componentMatcher) }
289 
290     private fun getActivityByName(activityName: String): Activity? =
291         rootTasks.firstNotNullOfOrNull { task ->
292             task.getActivity { activity -> activity.title.contains(activityName) }
293         }
294 
295     /**
296      * @param componentMatcher Components to search
297      * @return if any activity matching [componentMatcher] is visible
298      */
299     fun isActivityVisible(componentMatcher: IComponentMatcher): Boolean =
300         getActivity(componentMatcher)?.isVisible ?: false
301 
302     /**
303      * @param componentMatcher Components to search
304      * @param activityState expected activity state
305      * @return if any activity matching [componentMatcher] has state of [activityState]
306      */
307     fun hasActivityState(componentMatcher: IComponentMatcher, activityState: String): Boolean =
308         rootTasks.any { it.getActivity(componentMatcher)?.state == activityState }
309 
310     /**
311      * @param componentMatcher Components to search
312      * @return if any pending activities match [componentMatcher]
313      */
314     fun pendingActivityContain(componentMatcher: IComponentMatcher): Boolean =
315         componentMatcher.activityMatchesAnyOf(pendingActivities)
316 
317     /**
318      * @param componentMatcher Components to search
319      * @return the visible [WindowState]s matching [componentMatcher]
320      */
321     fun getMatchingVisibleWindowState(
322         componentMatcher: IComponentMatcher
323     ): Collection<WindowState> {
324         return windowStates.filter { it.isSurfaceShown && componentMatcher.windowMatchesAnyOf(it) }
325     }
326 
327     /** @return the [WindowState] for the nav bar in the display with id [displayId] */
328     fun getNavBarWindow(displayId: Int): WindowState? {
329         val navWindow = windowStates.filter { it.isValidNavBarType && it.displayId == displayId }
330 
331         // We may need some time to wait for nav bar showing.
332         // It's Ok to get 0 nav bar here.
333         if (navWindow.size > 1) {
334             throw IllegalStateException("There should be at most one navigation bar on a display")
335         }
336         return navWindow.firstOrNull()
337     }
338 
339     private fun getWindowStateForAppToken(appToken: String): WindowState? =
340         windowStates.firstOrNull { it.token == appToken }
341 
342     /**
343      * Checks if there exists a [WindowState] matching [componentMatcher]
344      *
345      * @param componentMatcher Components to search
346      */
347     fun containsWindow(componentMatcher: IComponentMatcher): Boolean =
348         componentMatcher.windowMatchesAnyOf(windowStates)
349 
350     /**
351      * Check if at least one [WindowState] matching [componentMatcher] is visible
352      *
353      * @param componentMatcher Components to search
354      */
355     fun isWindowSurfaceShown(componentMatcher: IComponentMatcher): Boolean =
356         getMatchingVisibleWindowState(componentMatcher).isNotEmpty()
357 
358     /** Checks if the state has any window in PIP mode */
359     fun hasPipWindow(): Boolean = pinnedWindows.isNotEmpty()
360 
361     /**
362      * Checks that a [WindowState] matching [componentMatcher] is in PIP mode
363      *
364      * @param componentMatcher Components to search
365      */
366     fun isInPipMode(componentMatcher: IComponentMatcher): Boolean =
367         componentMatcher.windowMatchesAnyOf(pinnedWindows)
368 
369     fun getZOrder(w: WindowState): Int = windowStates.size - windowStates.indexOf(w)
370 
371     fun defaultMinimalTaskSize(displayId: Int): Int =
372         dpToPx(PlatformConsts.DEFAULT_RESIZABLE_TASK_SIZE_DP.toFloat(), getDisplay(displayId)!!.dpi)
373 
374     fun defaultMinimalDisplaySizeForSplitScreen(displayId: Int): Int {
375         return dpToPx(
376             PlatformConsts.DEFAULT_MINIMAL_SPLIT_SCREEN_DISPLAY_SIZE_DP.toFloat(),
377             getDisplay(displayId)!!.dpi,
378         )
379     }
380 
381     /**
382      * Checks if a [WindowState] matching [componentMatcher] exists
383      *
384      * @param componentMatcher Components to search
385      */
386     fun contains(componentMatcher: IComponentMatcher): Boolean =
387         componentMatcher.windowMatchesAnyOf(windowStates)
388 
389     /**
390      * Checks if a [WindowState] matching [componentMatcher] is visible
391      *
392      * @param componentMatcher Components to search
393      */
394     fun isVisible(componentMatcher: IComponentMatcher): Boolean =
395         componentMatcher.windowMatchesAnyOf(visibleWindows)
396 
397     /**
398      * Checks if a [WindowState] matching [componentMatcher] exists and is a non-app window
399      *
400      * @param componentMatcher Components to search
401      */
402     fun isNonAppWindow(componentMatcher: IComponentMatcher): Boolean =
403         componentMatcher.windowMatchesAnyOf(nonAppWindows)
404 
405     /**
406      * Checks if a [WindowState] matching [componentMatcher] exists and is an app window
407      *
408      * @param componentMatcher Components to search
409      */
410     fun isAppWindow(componentMatcher: IComponentMatcher): Boolean {
411         val activity = getActivitiesForWindow(componentMatcher).firstOrNull()
412         return activity != null && componentMatcher.windowMatchesAnyOf(appWindows)
413     }
414 
415     /**
416      * Checks if a [WindowState] matching [componentMatcher] exists and is above all app window
417      *
418      * @param componentMatcher Components to search
419      */
420     fun isAboveAppWindow(componentMatcher: IComponentMatcher): Boolean =
421         componentMatcher.windowMatchesAnyOf(aboveAppWindows)
422 
423     /**
424      * Checks if a [WindowState] matching [componentMatcher] exists and is below all app window
425      *
426      * @param componentMatcher Components to search
427      */
428     fun isBelowAppWindow(componentMatcher: IComponentMatcher): Boolean =
429         componentMatcher.windowMatchesAnyOf(belowAppWindows)
430 
431     fun getIsIncompleteReason(): String {
432         return buildString {
433             if (rootTasks.isEmpty()) {
434                 append("No stacks found...")
435             }
436             if (focusedStackId == -1) {
437                 append("No focused stack found...")
438             }
439             if (focusedActivity == null) {
440                 append("No focused activity found...")
441             }
442             if (resumedActivities.isEmpty()) {
443                 append("No resumed activities found...")
444             }
445             if (windowStates.isEmpty()) {
446                 append("No Windows found...")
447             }
448             if (focusedWindow == null) {
449                 append("No Focused Window...")
450             }
451             if (focusedApp.isEmpty()) {
452                 append("No Focused App...")
453             }
454             if (keyguardControllerState.isKeyguardShowing) {
455                 append("Keyguard showing...")
456             }
457         }
458     }
459 
460     fun isComplete(): Boolean = !isIncomplete()
461 
462     fun isIncomplete(): Boolean {
463         var incomplete = stackCount == 0
464         // TODO: Update when keyguard will be shown on multiple displays
465         if (!keyguardControllerState.isKeyguardShowing) {
466             incomplete = incomplete || (resumedActivitiesCount == 0)
467         }
468         incomplete = incomplete || (focusedActivity == null)
469         rootTasks.forEach { aStack ->
470             val stackId = aStack.rootTaskId
471             aStack.tasks.forEach { aTask ->
472                 incomplete = incomplete || (stackId != aTask.rootTaskId)
473             }
474         }
475         incomplete = incomplete || frontWindow == null
476         incomplete = incomplete || focusedWindow == null
477         incomplete = incomplete || focusedApp.isEmpty()
478         return incomplete
479     }
480 
481     fun asTrace(): WindowManagerTrace = WindowManagerTrace(listOf(this))
482 
483     override fun toString(): String {
484         return timestamp.toString()
485     }
486 
487     companion object {
488         fun dpToPx(dp: Float, densityDpi: Int): Int {
489             return (dp * densityDpi / PlatformConsts.DENSITY_DEFAULT + 0.5f).toInt()
490         }
491     }
492 
493     override fun equals(other: Any?): Boolean {
494         return other is WindowManagerState && other.timestamp == this.timestamp
495     }
496 
497     override fun hashCode(): Int {
498         var result = where.hashCode()
499         result = 31 * result + (policy?.hashCode() ?: 0)
500         result = 31 * result + focusedApp.hashCode()
501         result = 31 * result + focusedDisplayId
502         result = 31 * result + focusedWindow.hashCode()
503         result = 31 * result + inputMethodWindowAppToken.hashCode()
504         result = 31 * result + isHomeRecentsComponent.hashCode()
505         result = 31 * result + isDisplayFrozen.hashCode()
506         result = 31 * result + pendingActivities.hashCode()
507         result = 31 * result + root.hashCode()
508         result = 31 * result + keyguardControllerState.hashCode()
509         result = 31 * result + timestamp.hashCode()
510         result = 31 * result + isVisible.hashCode()
511         return result
512     }
513 }
514