| /* |
| * Copyright (C) 2023 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.tools.common.traces.wm |
| |
| import android.tools.common.Rotation |
| import android.tools.common.datatypes.Rect |
| import android.tools.common.datatypes.component.IComponentMatcher |
| import kotlin.js.JsExport |
| import kotlin.js.JsName |
| import kotlin.math.min |
| |
| /** |
| * Represents a display content in the window manager hierarchy |
| * |
| * This is a generic object that is reused by both Flicker and Winscope and cannot access internal |
| * Java/Android functionality |
| */ |
| @JsExport |
| class DisplayContent( |
| @JsName("id") val id: Int, |
| @JsName("focusedRootTaskId") val focusedRootTaskId: Int, |
| @JsName("resumedActivity") val resumedActivity: String, |
| @JsName("singleTaskInstance") val singleTaskInstance: Boolean, |
| @JsName("defaultPinnedStackBounds") val defaultPinnedStackBounds: Rect, |
| @JsName("pinnedStackMovementBounds") val pinnedStackMovementBounds: Rect, |
| @JsName("displayRect") val displayRect: Rect, |
| @JsName("appRect") val appRect: Rect, |
| @JsName("dpi") val dpi: Int, |
| @JsName("flags") val flags: Int, |
| @JsName("stableBounds") val stableBounds: Rect, |
| @JsName("surfaceSize") val surfaceSize: Int, |
| @JsName("focusedApp") val focusedApp: String, |
| @JsName("lastTransition") val lastTransition: String, |
| @JsName("appTransitionState") val appTransitionState: String, |
| @JsName("rotation") val rotation: Rotation, |
| @JsName("lastOrientation") val lastOrientation: Int, |
| @JsName("cutout") val cutout: DisplayCutout?, |
| windowContainer: WindowContainer |
| ) : WindowContainer(windowContainer) { |
| override val name: String = id.toString() |
| override val isVisible: Boolean = false |
| |
| @JsName("isTablet") |
| val isTablet: Boolean |
| get() { |
| val smallestWidth = |
| dpiFromPx(min(displayRect.width.toFloat(), displayRect.height.toFloat()), dpi) |
| return smallestWidth >= TABLET_MIN_DPS |
| } |
| |
| @JsName("rootTasks") |
| val rootTasks: Array<Task> |
| get() { |
| val tasks = this.collectDescendants<Task> { it.isRootTask }.toMutableList() |
| // TODO(b/149338177): figure out how CTS tests deal with organizer. For now, |
| // don't treat them as regular stacks |
| val rootOrganizedTasks = mutableListOf<Task>() |
| val reversedTaskList = tasks.reversed() |
| reversedTaskList.forEach { task -> |
| // Skip tasks created by an organizer |
| if (task.createdByOrganizer) { |
| tasks.remove(task) |
| rootOrganizedTasks.add(task) |
| } |
| } |
| // Add root tasks controlled by an organizer |
| rootOrganizedTasks.reversed().forEach { task -> |
| tasks.addAll(task.children.reversed().map { it as Task }) |
| } |
| |
| return tasks.toTypedArray() |
| } |
| |
| /** |
| * @return if [componentMatcher] matches any activity |
| * |
| * @param componentMatcher Components to search |
| */ |
| @JsName("containsActivity") |
| fun containsActivity(componentMatcher: IComponentMatcher): Boolean = |
| rootTasks.any { it.containsActivity(componentMatcher) } |
| |
| /** |
| * @return THe [DisplayArea] matching [componentMatcher], or null if none matches |
| * |
| * @param componentMatcher Components to search |
| */ |
| @JsName("getTaskDisplayArea") |
| fun getTaskDisplayArea(componentMatcher: IComponentMatcher): DisplayArea? { |
| val taskDisplayAreas = |
| this.collectDescendants<DisplayArea> { it.isTaskDisplayArea } |
| .filter { it.containsActivity(componentMatcher) } |
| |
| if (taskDisplayAreas.size > 1) { |
| throw IllegalArgumentException( |
| "There must be exactly one activity among all TaskDisplayAreas." |
| ) |
| } |
| |
| return taskDisplayAreas.firstOrNull() |
| } |
| |
| override fun toString(): String { |
| return "${this::class.simpleName} #$id: name=$title mDisplayRect=$displayRect " + |
| "mAppRect=$appRect mFlags=$flags" |
| } |
| |
| override fun equals(other: Any?): Boolean { |
| if (this === other) return true |
| if (other !is DisplayContent) return false |
| if (!super.equals(other)) return false |
| |
| if (id != other.id) return false |
| if (focusedRootTaskId != other.focusedRootTaskId) return false |
| if (resumedActivity != other.resumedActivity) return false |
| if (defaultPinnedStackBounds != other.defaultPinnedStackBounds) return false |
| if (pinnedStackMovementBounds != other.pinnedStackMovementBounds) return false |
| if (stableBounds != other.stableBounds) return false |
| if (displayRect != other.displayRect) return false |
| if (appRect != other.appRect) return false |
| if (dpi != other.dpi) return false |
| if (flags != other.flags) return false |
| if (focusedApp != other.focusedApp) return false |
| if (lastTransition != other.lastTransition) return false |
| if (appTransitionState != other.appTransitionState) return false |
| if (rotation != other.rotation) return false |
| if (lastOrientation != other.lastOrientation) return false |
| if (cutout != other.cutout) return false |
| if (name != other.name) return false |
| if (singleTaskInstance != other.singleTaskInstance) return false |
| if (surfaceSize != other.surfaceSize) return false |
| |
| return true |
| } |
| |
| override fun hashCode(): Int { |
| var result = super.hashCode() |
| result = 31 * result + id |
| result = 31 * result + focusedRootTaskId |
| result = 31 * result + resumedActivity.hashCode() |
| result = 31 * result + singleTaskInstance.hashCode() |
| result = 31 * result + defaultPinnedStackBounds.hashCode() |
| result = 31 * result + pinnedStackMovementBounds.hashCode() |
| result = 31 * result + displayRect.hashCode() |
| result = 31 * result + appRect.hashCode() |
| result = 31 * result + dpi |
| result = 31 * result + flags |
| result = 31 * result + stableBounds.hashCode() |
| result = 31 * result + surfaceSize |
| result = 31 * result + focusedApp.hashCode() |
| result = 31 * result + lastTransition.hashCode() |
| result = 31 * result + appTransitionState.hashCode() |
| result = 31 * result + rotation.value |
| result = 31 * result + lastOrientation |
| result = 31 * result + cutout.hashCode() |
| result = 31 * result + name.hashCode() |
| result = 31 * result + isVisible.hashCode() |
| return result |
| } |
| |
| companion object { |
| /** From [android.util.DisplayMetrics] */ |
| @JsName("DENSITY_DEFAULT") private const val DENSITY_DEFAULT = 160f |
| /** From [com.android.systemui.shared.recents.utilities.Utilities] */ |
| @JsName("TABLET_MIN_DPS") private const val TABLET_MIN_DPS = 600f |
| |
| @JsName("dpiFromPx") |
| private fun dpiFromPx(size: Float, densityDpi: Int): Float { |
| val densityRatio: Float = densityDpi.toFloat() / DENSITY_DEFAULT |
| return size / densityRatio |
| } |
| } |
| } |