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