/* * 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.Context import android.content.res.Resources import android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY import android.hardware.devicestate.DeviceStateManager import android.hardware.devicestate.feature.flags.Flags as DeviceStateManagerFlags import android.tools.PlatformConsts import android.tools.Rotation import android.tools.traces.component.ComponentNameMatcher import android.tools.traces.component.ComponentNameMatcher.Companion.BUBBLE 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.WindowState import androidx.test.platform.app.InstrumentationRegistry object ConditionsFactory { /** Check if this is a phone device instead of a folded foldable. */ fun isPhoneNavBar(): Boolean { val isPhone: Boolean if (DeviceStateManagerFlags.deviceStatePropertyMigration()) { val context = InstrumentationRegistry.getInstrumentation().context val deviceStateManager = context.getSystemService(Context.DEVICE_STATE_SERVICE) as DeviceStateManager? isPhone = deviceStateManager?.supportedDeviceStates?.any { it.hasProperty(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY) } ?: true } else { val foldedDeviceStatesId: Int = Resources.getSystem().getIdentifier("config_foldedDeviceStates", "array", "android") isPhone = if (foldedDeviceStatesId != 0) { Resources.getSystem().getIntArray(foldedDeviceStatesId).isEmpty() } else { true } } return isPhone } fun getNavBarComponentOrLegacy(): IComponentMatcher { // FlickerLib tests may still use legacy navigation bar. return ComponentNameMatcher.NAV_BAR_LEGACY.or(ComponentNameMatcher.NAV_BAR) } /** * 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 = getNavBarComponentOrLegacy() 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 = getNavBarComponentOrLegacy() it.layerState.isVisible(component) } /** Condition to check if the [ComponentNameMatcher.NAV_BAR] layer is opaque */ fun isNavOrTaskBarLayerOpaque(): Condition = Condition("isNavOrTaskBarLayerOpaque") { val component = getNavBarComponentOrLegacy() 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 on a specific * display */ @JvmOverloads fun isNavBarWindowVisible( displayId: Int = PlatformConsts.DEFAULT_DISPLAY ): Condition = Condition("isNavBarWindowVisible[$displayId]") { it.wmState.isWindowSurfaceShown(ComponentNameMatcher.NAV_BAR, displayId) } /** 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 on a specific * display */ @JvmOverloads fun isTaskBarWindowVisible( displayId: Int = PlatformConsts.DEFAULT_DISPLAY ): Condition = Condition("isTaskBarWindowVisible[$displayId]") { it.wmState.isWindowSurfaceShown( ComponentNameMatcher.TASK_BAR, displayId) } /** 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 on a specific * display */ @JvmOverloads fun isStatusBarWindowVisible( displayId: Int = PlatformConsts.DEFAULT_DISPLAY ): Condition = Condition("isStatusBarWindowVisible[$displayId]") { it.wmState.isWindowSurfaceShown(ComponentNameMatcher.STATUS_BAR, displayId) } /** 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 } @JvmOverloads fun isHomeActivityVisible( displayId: Int = PlatformConsts.DEFAULT_DISPLAY ): Condition = Condition("isHomeActivityVisible[display=$displayId]") { it.wmState.isHomeActivityVisible(displayId) } /** * Condition to check if the [ComponentNameMatcher.IMAGE_WALLPAPER] window is visible on a * specific display */ @JvmOverloads fun isImageWallpaperWindowVisible( displayId: Int = PlatformConsts.DEFAULT_DISPLAY ): Condition = Condition("isWallpaperWindowVisible[$displayId]") { it.wmState.isWindowSurfaceShown(ComponentNameMatcher.IMAGE_WALLPAPER, displayId) } 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) } /** Condition to check if a specific display contains no activity */ @JvmOverloads fun hasNoActivityOnDisplay( displayId: Int = PlatformConsts.DEFAULT_DISPLAY ): Condition = Condition("hasNoActivityOnDisplay[$displayId]") { it.wmState.hasNoActivityOnDisplay(displayId) } 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() } /** Checks whether the [BUBBLE] window exists. */ fun hasBubbleWindow(): Condition = Condition("hasBubbleWindow") { it.wmState.containsWindow(BUBBLE) } 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 } } }