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