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