1 /* 2 * Copyright (C) 2020 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 com.android.server.wm.flicker.helpers 18 19 import android.app.ActivityManager 20 import android.app.Instrumentation 21 import android.content.ComponentName 22 import android.content.Context 23 import android.content.Intent 24 import android.platform.helpers.AbstractStandardAppHelper 25 import android.support.test.launcherhelper.ILauncherStrategy 26 import android.support.test.launcherhelper.LauncherStrategyFactory 27 import androidx.test.uiautomator.By 28 import androidx.test.uiautomator.BySelector 29 import androidx.test.uiautomator.UiDevice 30 import androidx.test.uiautomator.Until 31 import com.android.server.wm.traces.common.Condition 32 import com.android.server.wm.traces.common.DeviceStateDump 33 import com.android.server.wm.traces.common.FlickerComponentName 34 import com.android.server.wm.traces.common.WindowManagerConditionsFactory 35 import com.android.server.wm.traces.common.layers.BaseLayerTraceEntry 36 import com.android.server.wm.traces.common.windowmanager.WindowManagerState 37 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper 38 39 /** 40 * Class to take advantage of {@code IAppHelper} interface so the same test can be run against first 41 * party and third party apps. 42 */ 43 open class StandardAppHelper @JvmOverloads constructor( 44 instr: Instrumentation, 45 @JvmField val appName: String, 46 @JvmField val component: FlickerComponentName, 47 protected val launcherStrategy: ILauncherStrategy = 48 LauncherStrategyFactory.getInstance(instr).launcherStrategy 49 ) : AbstractStandardAppHelper(instr) { 50 constructor( 51 instr: Instrumentation, 52 appName: String, 53 packageName: String, 54 activity: String, 55 launcherStrategy: ILauncherStrategy = 56 LauncherStrategyFactory.getInstance(instr).launcherStrategy 57 ): this(instr, appName, 58 FlickerComponentName(packageName, ".$activity"), launcherStrategy) 59 60 private val activityManager: ActivityManager? 61 get() = mInstrumentation.context.getSystemService(ActivityManager::class.java) 62 63 protected val context: Context 64 get() = mInstrumentation.context 65 66 protected val uiDevice: UiDevice = UiDevice.getInstance(mInstrumentation) 67 getAppSelectornull68 private fun getAppSelector(expectedPackageName: String): BySelector { 69 val expected = if (expectedPackageName.isNotEmpty()) { 70 expectedPackageName 71 } else { 72 component.packageName 73 } 74 return By.pkg(expected).depth(0) 75 } 76 opennull77 override fun open() { 78 launcherStrategy.launch(appName, component.packageName) 79 } 80 81 /** {@inheritDoc} */ getPackagenull82 override fun getPackage(): String { 83 return component.packageName 84 } 85 86 /** {@inheritDoc} */ getOpenAppIntentnull87 override fun getOpenAppIntent(): Intent { 88 val intent = Intent() 89 intent.addCategory(Intent.CATEGORY_LAUNCHER) 90 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 91 intent.component = ComponentName(component.packageName, component.className) 92 return intent 93 } 94 95 /** {@inheritDoc} */ getLauncherNamenull96 override fun getLauncherName(): String { 97 return appName 98 } 99 100 /** {@inheritDoc} */ dismissInitialDialogsnull101 override fun dismissInitialDialogs() {} 102 103 /** {@inheritDoc} */ exitnull104 override fun exit() { 105 // Ensure all testing components end up being closed. 106 activityManager?.forceStopPackage(component.packageName) 107 } 108 109 /** 110 * Exits the activity and wait for activity destroyed 111 */ exitnull112 fun exit( 113 wmHelper: WindowManagerStateHelper 114 ) { 115 exit() 116 waitForActivityDestroyed(wmHelper) 117 } 118 119 /** 120 * Waits the activity until state change to {link WindowManagerState.STATE_DESTROYED} 121 */ waitForActivityDestroyednull122 private fun waitForActivityDestroyed( 123 wmHelper: WindowManagerStateHelper 124 ) { 125 val activityName = component.toActivityName() 126 val waitMsg = "state of $activityName to be ${WindowManagerState.STATE_DESTROYED}" 127 require( 128 wmHelper.waitFor(waitMsg) { 129 !it.wmState.containsActivity(activityName) || 130 it.wmState.hasActivityState(activityName, WindowManagerState.STATE_DESTROYED) 131 } 132 ) { "App activity should have been destroyed" } 133 wmHelper.waitForAppTransitionIdle() 134 // Ensure WindowManagerService wait until all animations have completed 135 mInstrumentation.uiAutomation.syncInputTransactions() 136 } 137 launchAppViaIntentnull138 private fun launchAppViaIntent( 139 action: String? = null, 140 stringExtras: Map<String, String> = mapOf() 141 ) { 142 val intent = openAppIntent 143 intent.action = action 144 stringExtras.forEach { 145 intent.putExtra(it.key, it.value) 146 } 147 context.startActivity(intent) 148 } 149 150 /** 151 * Launches the app through an intent instead of interacting with the launcher. 152 * 153 * Uses UiAutomation to detect when the app is open 154 */ 155 @JvmOverloads launchViaIntentnull156 open fun launchViaIntent( 157 expectedPackageName: String = "", 158 action: String? = null, 159 stringExtras: Map<String, String> = mapOf() 160 ) { 161 launchAppViaIntent(action, stringExtras) 162 val appSelector = getAppSelector(expectedPackageName) 163 uiDevice.wait(Until.hasObject(appSelector), APP_LAUNCH_WAIT_TIME_MS) 164 } 165 166 /** 167 * Launches the app through an intent instead of interacting with the launcher and waits 168 * until the app window is visible 169 */ 170 @JvmOverloads launchViaIntentnull171 open fun launchViaIntent( 172 wmHelper: WindowManagerStateHelper, 173 expectedWindowName: String = "", 174 action: String? = null, 175 stringExtras: Map<String, String> = mapOf() 176 ) = launchViaIntentAndWaitShown(wmHelper, expectedWindowName, action, stringExtras) 177 178 /** 179 * Launches the app through an intent instead of interacting with the launcher and waits 180 * until the app window is visible 181 */ 182 protected fun launchViaIntentAndWaitShown( 183 wmHelper: WindowManagerStateHelper, 184 expectedWindowName: String = "", 185 action: String? = null, 186 stringExtras: Map<String, String> = mapOf(), 187 waitConditions: Array< 188 Condition<DeviceStateDump<WindowManagerState, BaseLayerTraceEntry>>> = 189 emptyArray() 190 ) { 191 launchAppViaIntent(action, stringExtras) 192 193 val expectedWindow = if (expectedWindowName.isNotEmpty()) { 194 FlickerComponentName("", expectedWindowName) 195 } else { 196 component 197 } 198 val appShown = wmHelper.waitFor( 199 WindowManagerConditionsFactory.isWMStateComplete(), 200 WindowManagerConditionsFactory.hasLayersAnimating().negate(), 201 WindowManagerConditionsFactory.isWindowVisible(expectedWindow), 202 *waitConditions 203 ) 204 require(appShown) { "App didn't launch correctly via intent" } 205 206 wmHelper.waitForAppTransitionIdle() 207 // During seamless rotation the app window is shown 208 val currWmState = wmHelper.currentState.wmState 209 if (currWmState.visibleWindows.none { it.isFullscreen }) { 210 wmHelper.waitForNavBarStatusBarVisible() 211 } 212 213 // Ensure WindowManagerService wait until all animations have completed 214 mInstrumentation.uiAutomation.syncInputTransactions() 215 } 216 217 companion object { 218 private const val APP_LAUNCH_WAIT_TIME_MS = 10000L 219 } 220 } 221