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