/* * 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.traces import android.content.res.Resources import android.tools.PlatformConsts import android.tools.Rotation import android.tools.traces.component.ComponentNameMatcher import android.tools.traces.component.IComponentMatcher import android.tools.traces.surfaceflinger.Layer import android.tools.traces.surfaceflinger.Transform import android.tools.traces.surfaceflinger.Transform.Companion.isFlagSet import android.tools.traces.wm.WindowManagerState import android.tools.traces.wm.WindowState object ConditionsFactory { /** Check if this is a phone device instead of a folded foldable. */ fun isPhoneNavBar(): Boolean { val isPhone: Boolean val foldedDeviceStatesId: Int = Resources.getSystem().getIdentifier("config_foldedDeviceStates", "array", "android") isPhone = if (foldedDeviceStatesId != 0) { Resources.getSystem().getIntArray(foldedDeviceStatesId).isEmpty() } else { true } return isPhone } fun getNavBarComponent(wmState: WindowManagerState): IComponentMatcher { var component: IComponentMatcher = ComponentNameMatcher.NAV_BAR if (wmState.isTablet || !isPhoneNavBar()) { component = component.or(ComponentNameMatcher.TASK_BAR) } return component } /** * Condition to check if the [ComponentNameMatcher.NAV_BAR] or [ComponentNameMatcher.TASK_BAR] * windows are visible */ fun isNavOrTaskBarVisible(): Condition = ConditionList( listOf( isNavOrTaskBarWindowVisible(), isNavOrTaskBarLayerVisible(), isNavOrTaskBarLayerOpaque() ) ) /** * Condition to check if the [ComponentNameMatcher.NAV_BAR] or [ComponentNameMatcher.TASK_BAR] * windows are visible */ fun isNavOrTaskBarWindowVisible(): Condition = Condition("isNavBarOrTaskBarWindowVisible") { val component = getNavBarComponent(it.wmState) it.wmState.isWindowSurfaceShown(component) } /** * Condition to check if the [ComponentNameMatcher.NAV_BAR] or [ComponentNameMatcher.TASK_BAR] * layers are visible */ fun isNavOrTaskBarLayerVisible(): Condition = Condition("isNavBarOrTaskBarLayerVisible") { val component = getNavBarComponent(it.wmState) it.layerState.isVisible(component) } /** Condition to check if the [ComponentNameMatcher.NAV_BAR] layer is opaque */ fun isNavOrTaskBarLayerOpaque(): Condition = Condition("isNavOrTaskBarLayerOpaque") { val component = getNavBarComponent(it.wmState) it.layerState.getLayerWithBuffer(component)?.color?.alpha() == 1.0f } /** Condition to check if the [ComponentNameMatcher.NAV_BAR] window is visible */ fun isNavBarVisible(): Condition = ConditionList( listOf(isNavBarWindowVisible(), isNavBarLayerVisible(), isNavBarLayerOpaque()) ) /** Condition to check if the [ComponentNameMatcher.NAV_BAR] window is visible */ fun isNavBarWindowVisible(): Condition = Condition("isNavBarWindowVisible") { it.wmState.isWindowSurfaceShown(ComponentNameMatcher.NAV_BAR) } /** Condition to check if the [ComponentNameMatcher.NAV_BAR] layer is visible */ fun isNavBarLayerVisible(): Condition = isLayerVisible(ComponentNameMatcher.NAV_BAR) /** Condition to check if the [ComponentNameMatcher.NAV_BAR] layer is opaque */ fun isNavBarLayerOpaque(): Condition = Condition("isNavBarLayerOpaque") { it.layerState.getLayerWithBuffer(ComponentNameMatcher.NAV_BAR)?.color?.alpha() == 1.0f } /** Condition to check if the [ComponentNameMatcher.TASK_BAR] window is visible */ fun isTaskBarVisible(): Condition = ConditionList( listOf(isTaskBarWindowVisible(), isTaskBarLayerVisible(), isTaskBarLayerOpaque()) ) /** Condition to check if the [ComponentNameMatcher.TASK_BAR] window is visible */ fun isTaskBarWindowVisible(): Condition = Condition("isTaskBarWindowVisible") { it.wmState.isWindowSurfaceShown(ComponentNameMatcher.TASK_BAR) } /** Condition to check if the [ComponentNameMatcher.TASK_BAR] layer is visible */ fun isTaskBarLayerVisible(): Condition = isLayerVisible(ComponentNameMatcher.TASK_BAR) /** Condition to check if the [ComponentNameMatcher.TASK_BAR] layer is opaque */ fun isTaskBarLayerOpaque(): Condition = Condition("isTaskBarLayerOpaque") { it.layerState.getLayerWithBuffer(ComponentNameMatcher.TASK_BAR)?.color?.alpha() == 1.0f } /** Condition to check if the [ComponentNameMatcher.STATUS_BAR] window is visible */ fun isStatusBarVisible(): Condition = ConditionList( listOf(isStatusBarWindowVisible(), isStatusBarLayerVisible(), isStatusBarLayerOpaque()) ) /** Condition to check if the [ComponentNameMatcher.STATUS_BAR] window is visible */ fun isStatusBarWindowVisible(): Condition = Condition("isStatusBarWindowVisible") { it.wmState.isWindowSurfaceShown(ComponentNameMatcher.STATUS_BAR) } /** Condition to check if the [ComponentNameMatcher.STATUS_BAR] layer is visible */ fun isStatusBarLayerVisible(): Condition = isLayerVisible(ComponentNameMatcher.STATUS_BAR) /** Condition to check if the [ComponentNameMatcher.STATUS_BAR] layer is opaque */ fun isStatusBarLayerOpaque(): Condition = Condition("isStatusBarLayerOpaque") { it.layerState.getLayerWithBuffer(ComponentNameMatcher.STATUS_BAR)?.color?.alpha() == 1.0f } fun isHomeActivityVisible(): Condition = Condition("isHomeActivityVisible") { it.wmState.isHomeActivityVisible } fun isRecentsActivityVisible(): Condition = Condition("isRecentsActivityVisible") { it.wmState.isHomeActivityVisible || it.wmState.isRecentsActivityVisible } fun isLauncherLayerVisible(): Condition = Condition("isLauncherLayerVisible") { it.layerState.isVisible(ComponentNameMatcher.LAUNCHER) || it.layerState.isVisible(ComponentNameMatcher.AOSP_LAUNCHER) } /** * Condition to check if WM app transition is idle * * Because in shell transitions, active recents animation is running transition (never idle) * this method always assumed recents are idle */ fun isAppTransitionIdle(displayId: Int): Condition = Condition("isAppTransitionIdle[$displayId]") { (it.wmState.isHomeRecentsComponent && it.wmState.isHomeActivityVisible) || it.wmState.isRecentsActivityVisible || it.wmState.getDisplay(displayId)?.appTransitionState == PlatformConsts.APP_STATE_IDLE } fun containsActivity(componentMatcher: IComponentMatcher): Condition = Condition("containsActivity[${componentMatcher.toActivityIdentifier()}]") { it.wmState.containsActivity(componentMatcher) } fun containsWindow(componentMatcher: IComponentMatcher): Condition = Condition("containsWindow[${componentMatcher.toWindowIdentifier()}]") { it.wmState.containsWindow(componentMatcher) } fun isWindowSurfaceShown(componentMatcher: IComponentMatcher): Condition = Condition("isWindowSurfaceShown[${componentMatcher.toWindowIdentifier()}]") { it.wmState.isWindowSurfaceShown(componentMatcher) } fun isActivityVisible(componentMatcher: IComponentMatcher): Condition = Condition("isActivityVisible[${componentMatcher.toActivityIdentifier()}]") { it.wmState.isActivityVisible(componentMatcher) } fun isWMStateComplete(): Condition = Condition("isWMStateComplete") { it.wmState.isComplete() } fun hasRotation(expectedRotation: Rotation, displayId: Int): Condition { val hasRotationCondition = Condition("hasRotation[$expectedRotation, display=$displayId]") { val currRotation = it.wmState.getRotation(displayId) currRotation == expectedRotation } return ConditionList( listOf( hasRotationCondition, isLayerVisible(ComponentNameMatcher.ROTATION).negate(), isLayerVisible(ComponentNameMatcher.BACK_SURFACE).negate(), hasLayersAnimating().negate() ) ) } fun isWindowVisible( componentMatcher: IComponentMatcher, displayId: Int = 0 ): Condition = ConditionList( containsActivity(componentMatcher), containsWindow(componentMatcher), isActivityVisible(componentMatcher), isWindowSurfaceShown(componentMatcher), isAppTransitionIdle(displayId) ) fun isLayerVisible(componentMatcher: IComponentMatcher): Condition = Condition("isLayerVisible[${componentMatcher.toLayerIdentifier()}]") { it.layerState.isVisible(componentMatcher) } fun isLayerVisible(layerId: Int): Condition = Condition("isLayerVisible[layerId=$layerId]") { it.layerState.getLayerById(layerId)?.isVisible ?: false } /** Condition to check if the given layer is opaque */ fun isLayerOpaque(componentMatcher: IComponentMatcher): Condition = Condition("isLayerOpaque[${componentMatcher.toLayerIdentifier()}]") { it.layerState.getLayerWithBuffer(componentMatcher)?.color?.alpha() == 1.0f } fun isLayerColorAlphaOne(componentMatcher: IComponentMatcher): Condition = Condition("isLayerColorAlphaOne[${componentMatcher.toLayerIdentifier()}]") { it.layerState.visibleLayers .filter { layer -> componentMatcher.layerMatchesAnyOf(layer) } .any { layer -> layer.color.alpha() == 1.0f } } fun isLayerColorAlphaOne(layerId: Int): Condition = Condition("isLayerColorAlphaOne[$layerId]") { val layer = it.layerState.getLayerById(layerId) layer?.color?.alpha() == 1.0f } fun isLayerTransformFlagSet( componentMatcher: IComponentMatcher, transform: Int ): Condition = Condition( "isLayerTransformFlagSet[" + "${componentMatcher.toLayerIdentifier()}," + "transform=$transform]" ) { it.layerState.visibleLayers .filter { layer -> componentMatcher.layerMatchesAnyOf(layer) } .any { layer -> isTransformFlagSet(layer, transform) } } fun isLayerTransformFlagSet(layerId: Int, transform: Int): Condition = Condition("isLayerTransformFlagSet[$layerId, $transform]") { val layer = it.layerState.getLayerById(layerId) layer?.transform?.type?.isFlagSet(transform) ?: false } fun isLayerTransformIdentity(layerId: Int): Condition = ConditionList( listOf( isLayerTransformFlagSet(layerId, Transform.SCALE_VAL).negate(), isLayerTransformFlagSet(layerId, Transform.TRANSLATE_VAL).negate(), isLayerTransformFlagSet(layerId, Transform.ROTATE_VAL).negate() ) ) private fun isTransformFlagSet(layer: Layer, transform: Int): Boolean = layer.transform.type?.isFlagSet(transform) ?: false fun hasLayersAnimating(): Condition { var prevState: DeviceStateDump? = null return ConditionList( Condition("hasLayersAnimating") { val result = it.layerState.isAnimating(prevState?.layerState) prevState = it result }, isLayerVisible(ComponentNameMatcher.SNAPSHOT).negate(), isLayerVisible(ComponentNameMatcher.SPLASH_SCREEN).negate() ) } fun isPipWindowLayerSizeMatch(layerId: Int): Condition = Condition("isPipWindowLayerSizeMatch[layerId=$layerId]") { val pipWindow = it.wmState.pinnedWindows.firstOrNull { pinnedWindow -> pinnedWindow.layerId == layerId } ?: error("Unable to find window with layerId $layerId") val windowHeight = pipWindow.frame.height().toFloat() val windowWidth = pipWindow.frame.width().toFloat() val pipLayer = it.layerState.getLayerById(layerId) val layerHeight = pipLayer?.screenBounds?.height() ?: error("Unable to find layer with id $layerId") val layerWidth = pipLayer.screenBounds.width() windowHeight == layerHeight && windowWidth == layerWidth } fun hasPipWindow(): Condition = Condition("hasPipWindow") { it.wmState.hasPipWindow() } fun isImeShown(displayId: Int): Condition = ConditionList( listOf( isImeOnDisplay(displayId), isLayerVisible(ComponentNameMatcher.IME), isLayerOpaque(ComponentNameMatcher.IME), isImeSurfaceShown(), isWindowSurfaceShown(ComponentNameMatcher.IME) ) ) private fun isImeOnDisplay(displayId: Int): Condition = Condition("isImeOnDisplay[$displayId]") { it.wmState.inputMethodWindowState?.displayId == displayId } private fun isImeSurfaceShown(): Condition = Condition("isImeSurfaceShown") { it.wmState.inputMethodWindowState?.isSurfaceShown == true && it.wmState.inputMethodWindowState?.isVisible == true } fun isAppLaunchEnded(taskId: Int): Condition = Condition("containsVisibleAppLaunchWindow[taskId=$taskId]") { dump -> val windowStates = dump.wmState.getRootTask(taskId)?.activities?.flatMap { it.children.filterIsInstance() } windowStates != null && windowStates.none { it.attributes.type == PlatformConsts.TYPE_APPLICATION_STARTING && it.isVisible } } }