1 /* 2 * 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.device.apphelpers 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.content.pm.PackageManager 25 import android.platform.helpers.AbstractStandardAppHelper 26 import android.tools.common.Logger 27 import android.tools.common.PlatformConsts 28 import android.tools.common.traces.Condition 29 import android.tools.common.traces.ConditionsFactory 30 import android.tools.common.traces.DeviceStateDump 31 import android.tools.common.traces.component.ComponentNameMatcher 32 import android.tools.common.traces.component.IComponentMatcher 33 import android.tools.common.traces.component.IComponentNameMatcher 34 import android.tools.device.traces.parsers.WindowManagerStateHelper 35 import androidx.test.uiautomator.By 36 import androidx.test.uiautomator.BySelector 37 import androidx.test.uiautomator.UiDevice 38 import androidx.test.uiautomator.Until 39 import com.android.launcher3.tapl.LauncherInstrumentation 40 41 /** 42 * Class to take advantage of {@code IAppHelper} interface so the same test can be run against first 43 * party and third party apps. 44 */ 45 open class StandardAppHelper( 46 instr: Instrumentation, 47 val appName: String, 48 val componentMatcher: ComponentNameMatcher 49 ) : AbstractStandardAppHelper(instr), IComponentNameMatcher by componentMatcher { 50 constructor( 51 instr: Instrumentation, 52 appName: String, 53 packageName: String, 54 activity: String 55 ) : this(instr, appName, ComponentNameMatcher(packageName, ".$activity")) 56 57 protected val pkgManager: PackageManager = instr.context.packageManager 58 59 protected val tapl: LauncherInstrumentation = LauncherInstrumentation() 60 61 private val activityManager: ActivityManager? 62 get() = mInstrumentation.context.getSystemService(ActivityManager::class.java) 63 64 protected val context: Context 65 get() = mInstrumentation.context 66 67 override val packageName = componentMatcher.packageName 68 69 override val className = componentMatcher.className 70 71 protected val uiDevice: UiDevice = UiDevice.getInstance(mInstrumentation) 72 getAppSelectornull73 private fun getAppSelector(expectedPackageName: String): BySelector { 74 val expected = expectedPackageName.ifEmpty { packageName } 75 return By.pkg(expected).depth(0) 76 } 77 opennull78 override fun open() { 79 open(`package`) 80 } 81 opennull82 protected fun open(expectedPackageName: String) { 83 tapl.goHome().switchToAllApps().getAppIcon(launcherName).launch(expectedPackageName) 84 } 85 86 /** {@inheritDoc} */ getPackagenull87 override fun getPackage(): String { 88 return packageName 89 } 90 91 /** {@inheritDoc} */ getOpenAppIntentnull92 override fun getOpenAppIntent(): Intent { 93 val intent = Intent() 94 intent.addCategory(Intent.CATEGORY_LAUNCHER) 95 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 96 intent.component = ComponentName(packageName, className) 97 return intent 98 } 99 100 /** {@inheritDoc} */ getLauncherNamenull101 override fun getLauncherName(): String { 102 return appName 103 } 104 105 /** {@inheritDoc} */ dismissInitialDialogsnull106 override fun dismissInitialDialogs() {} 107 108 /** {@inheritDoc} */ exitnull109 override fun exit() { 110 Logger.withTracing("exit") { 111 // Ensure all testing components end up being closed. 112 activityManager?.forceStopPackage(packageName) 113 } 114 } 115 116 /** Exits the activity and wait for activity destroyed */ exitnull117 fun exit(wmHelper: WindowManagerStateHelper) { 118 Logger.withTracing("${this::class.simpleName}#exitAndWait") { 119 exit() 120 waitForActivityDestroyed(wmHelper) 121 } 122 } 123 124 /** Waits the activity until state change to {link WindowManagerState.STATE_DESTROYED} */ waitForActivityDestroyednull125 private fun waitForActivityDestroyed(wmHelper: WindowManagerStateHelper) { 126 val waitMsg = 127 "state of ${componentMatcher.toActivityIdentifier()} to be " + 128 PlatformConsts.STATE_DESTROYED 129 wmHelper 130 .StateSyncBuilder() 131 .add(waitMsg) { 132 !it.wmState.containsActivity(componentMatcher) || 133 it.wmState.hasActivityState(componentMatcher, PlatformConsts.STATE_DESTROYED) 134 } 135 .withAppTransitionIdle() 136 .waitForAndVerify() 137 } 138 launchAppViaIntentnull139 private fun launchAppViaIntent( 140 action: String? = null, 141 stringExtras: Map<String, String> = mapOf() 142 ) { 143 Logger.withTracing("${this::class.simpleName}#launchAppViaIntent") { 144 val intent = openAppIntent 145 intent.action = action ?: Intent.ACTION_MAIN 146 stringExtras.forEach { intent.putExtra(it.key, it.value) } 147 context.startActivity(intent) 148 } 149 } 150 151 /** 152 * Launches the app through an intent instead of interacting with the launcher. 153 * 154 * Uses UiAutomation to detect when the app is open 155 */ 156 @JvmOverloads launchViaIntentnull157 open fun launchViaIntent( 158 expectedPackageName: String = "", 159 action: String? = null, 160 stringExtras: Map<String, String> = mapOf() 161 ) { 162 launchAppViaIntent(action, stringExtras) 163 val appSelector = getAppSelector(expectedPackageName) 164 uiDevice.wait( 165 Until.hasObject(appSelector), 166 StandardAppHelper.Companion.APP_LAUNCH_WAIT_TIME_MS 167 ) 168 } 169 170 /** 171 * Launches the app through an intent instead of interacting with the launcher and waits until 172 * the app window is visible 173 */ 174 @JvmOverloads launchViaIntentnull175 open fun launchViaIntent( 176 wmHelper: WindowManagerStateHelper, 177 launchedAppComponentMatcherOverride: IComponentMatcher? = null, 178 action: String? = null, 179 stringExtras: Map<String, String> = mapOf(), 180 waitConditions: Array<Condition<DeviceStateDump>> = emptyArray() 181 ) = 182 launchViaIntentAndWaitShown( 183 wmHelper, 184 launchedAppComponentMatcherOverride, 185 action, 186 stringExtras, 187 waitConditions 188 ) 189 190 /** 191 * Launches the app through an intent instead of interacting with the launcher and waits until 192 * the app window is visible 193 */ 194 protected fun launchViaIntentAndWaitShown( 195 wmHelper: WindowManagerStateHelper, 196 launchedAppComponentMatcherOverride: IComponentMatcher? = null, 197 action: String? = null, 198 stringExtras: Map<String, String> = mapOf(), 199 waitConditions: Array<Condition<DeviceStateDump>> = emptyArray() 200 ) { 201 launchAppViaIntent(action, stringExtras) 202 doWaitShown(wmHelper, launchedAppComponentMatcherOverride, waitConditions) 203 } 204 doWaitShownnull205 private fun doWaitShown( 206 wmHelper: WindowManagerStateHelper, 207 launchedAppComponentMatcherOverride: IComponentMatcher? = null, 208 waitConditions: Array<Condition<DeviceStateDump>> = emptyArray() 209 ) { 210 Logger.withTracing("${this::class.simpleName}#doWaitShown") { 211 val expectedWindow = launchedAppComponentMatcherOverride ?: componentMatcher 212 val builder = 213 wmHelper 214 .StateSyncBuilder() 215 .add(ConditionsFactory.isWMStateComplete()) 216 .withAppTransitionIdle() 217 .withWindowSurfaceAppeared(expectedWindow) 218 219 waitConditions.forEach { builder.add(it) } 220 builder.waitForAndVerify() 221 222 // During seamless rotation the app window is shown 223 val currWmState = wmHelper.currentState.wmState 224 if (currWmState.visibleWindows.none { it.isFullscreen }) { 225 wmHelper 226 .StateSyncBuilder() 227 .withNavOrTaskBarVisible() 228 .withStatusBarVisible() 229 .waitForAndVerify() 230 } 231 } 232 } 233 isAvailablenull234 fun isAvailable(): Boolean { 235 return try { 236 pkgManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0)) 237 true 238 } catch (e: PackageManager.NameNotFoundException) { 239 false 240 } 241 } 242 243 companion object { 244 private const val APP_LAUNCH_WAIT_TIME_MS = 10000L 245 } 246 } 247