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