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.tools.PlatformConsts 26 import android.tools.traces.component.ComponentNameMatcher 27 import android.tools.traces.component.IComponentMatcher 28 import android.tools.traces.component.IComponentNameMatcher 29 import android.tools.traces.parsers.WindowManagerStateHelper 30 import android.tools.withTracing 31 import androidx.test.uiautomator.By 32 import androidx.test.uiautomator.BySelector 33 import androidx.test.uiautomator.UiDevice 34 import androidx.test.uiautomator.Until 35 import com.android.launcher3.tapl.LauncherInstrumentation 36 37 /** 38 * Class to take advantage of {@code IAppHelper} interface so the same test can be run against first 39 * party and third party apps. 40 */ 41 open class StandardAppHelper( 42 val instrumentation: Instrumentation, 43 val appName: String, 44 val componentMatcher: IComponentNameMatcher, 45 ) : IStandardAppHelper, IComponentNameMatcher by componentMatcher { 46 constructor( 47 instr: Instrumentation, 48 appName: String, 49 packageName: String, 50 activity: String, 51 ) : this(instr, appName, ComponentNameMatcher(packageName, ".$activity")) 52 53 protected val pkgManager: PackageManager = instrumentation.context.packageManager 54 <lambda>null55 protected val tapl: LauncherInstrumentation by lazy { LauncherInstrumentation() } 56 57 private val activityManager: ActivityManager? 58 get() = instrumentation.context.getSystemService(ActivityManager::class.java) 59 60 protected val context: Context 61 get() = instrumentation.context 62 63 override val packageName = componentMatcher.packageName 64 65 override val className = componentMatcher.className 66 67 protected val uiDevice: UiDevice = UiDevice.getInstance(instrumentation) 68 getAppSelectornull69 private fun getAppSelector(expectedPackageName: String): BySelector { 70 val expected = expectedPackageName.ifEmpty { packageName } 71 return By.pkg(expected).depth(0) 72 } 73 opennull74 override fun open() { 75 open(packageName) 76 } 77 opennull78 protected fun open(expectedPackageName: String) { 79 tapl.goHome().switchToAllApps().getAppIcon(appName).launch(expectedPackageName) 80 } 81 82 /** {@inheritDoc} */ 83 open val openAppIntent: Intent 84 get() { 85 val intent = Intent() 86 intent.addCategory(Intent.CATEGORY_LAUNCHER) 87 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 88 intent.component = ComponentName(packageName, className) 89 return intent 90 } 91 92 /** {@inheritDoc} */ exitnull93 override fun exit() { 94 withTracing("${this::class.simpleName}#exit") { 95 // Ensure all testing components end up being closed. 96 activityManager?.forceStopPackage(packageName) 97 } 98 } 99 100 /** {@inheritDoc} */ exitnull101 override fun exit(wmHelper: WindowManagerStateHelper) { 102 withTracing("${this::class.simpleName}#exitAndWait") { 103 exit() 104 waitForActivityDestroyed(wmHelper) 105 } 106 } 107 108 /** Waits the activity until state change to {link WindowManagerState.STATE_DESTROYED} */ waitForActivityDestroyednull109 private fun waitForActivityDestroyed(wmHelper: WindowManagerStateHelper) { 110 val waitMsg = 111 "state of ${componentMatcher.toActivityIdentifier()} to be " + 112 PlatformConsts.STATE_DESTROYED 113 wmHelper 114 .StateSyncBuilder() 115 .add(waitMsg) { 116 !it.wmState.containsActivity(componentMatcher) || 117 it.wmState.hasActivityState(componentMatcher, PlatformConsts.STATE_DESTROYED) 118 } 119 .withAppTransitionIdle() 120 .waitForAndVerify() 121 } 122 launchAppViaIntentnull123 private fun launchAppViaIntent( 124 action: String? = null, 125 stringExtras: Map<String, String> = mapOf(), 126 ) { 127 withTracing("${this::class.simpleName}#launchAppViaIntent") { 128 val intent = openAppIntent 129 intent.action = action ?: Intent.ACTION_MAIN 130 stringExtras.forEach { intent.putExtra(it.key, it.value) } 131 context.startActivity(intent) 132 } 133 } 134 135 /** {@inheritDoc} */ launchViaIntentnull136 override fun launchViaIntent( 137 expectedPackageName: String, 138 action: String?, 139 stringExtras: Map<String, String>, 140 ) { 141 launchAppViaIntent(action, stringExtras) 142 val appSelector = getAppSelector(expectedPackageName) 143 uiDevice.wait(Until.hasObject(appSelector), APP_LAUNCH_WAIT_TIME_MS) 144 } 145 146 /** {@inheritDoc} */ launchViaIntentnull147 override fun launchViaIntent( 148 wmHelper: WindowManagerStateHelper, 149 launchedAppComponentMatcherOverride: IComponentMatcher?, 150 action: String?, 151 stringExtras: Map<String, String>, 152 waitConditionsBuilder: WindowManagerStateHelper.StateSyncBuilder, 153 ) { 154 launchAppViaIntent(action, stringExtras) 155 doWaitShown(launchedAppComponentMatcherOverride, waitConditionsBuilder) 156 } 157 158 /** {@inheritDoc} */ launchViaIntentnull159 override fun launchViaIntent( 160 wmHelper: WindowManagerStateHelper, 161 intent: Intent, 162 launchedAppComponentMatcherOverride: IComponentMatcher?, 163 waitConditionsBuilder: WindowManagerStateHelper.StateSyncBuilder, 164 ) { 165 withTracing("${this::class.simpleName}#launchViaIntent") { 166 context.startActivity(intent) 167 doWaitShown(launchedAppComponentMatcherOverride, waitConditionsBuilder) 168 } 169 } 170 doWaitShownnull171 private fun doWaitShown( 172 launchedAppComponentMatcherOverride: IComponentMatcher? = null, 173 waitConditionsBuilder: WindowManagerStateHelper.StateSyncBuilder, 174 ) { 175 withTracing("${this::class.simpleName}#doWaitShown") { 176 val expectedWindow = launchedAppComponentMatcherOverride ?: componentMatcher 177 doWaitShownLight(expectedWindow) 178 doWaitShownHeavy(expectedWindow, waitConditionsBuilder) 179 } 180 } 181 doWaitShownLightnull182 private fun doWaitShownLight(expectedWindow: IComponentMatcher) { 183 try { 184 val expectedPackageName = 185 ComponentNameMatcher.unflattenFromString(expectedWindow.toWindowIdentifier()) 186 .packageName 187 val appSelector = getAppSelector(expectedPackageName) 188 uiDevice.wait(Until.hasObject(appSelector), APP_LAUNCH_WAIT_TIME_MS) 189 } catch (e: Exception) { 190 // IComponentMatcher#toWindowIdentifier() might not be implemented. 191 // Let's just skip the light-weight busy waiting. 192 } 193 } 194 doWaitShownHeavynull195 private fun doWaitShownHeavy( 196 expectedWindow: IComponentMatcher, 197 waitConditionsBuilder: WindowManagerStateHelper.StateSyncBuilder, 198 ) { 199 val builder = waitConditionsBuilder.withWindowSurfaceAppeared(expectedWindow) 200 builder.waitForAndVerify() 201 } 202 isAvailablenull203 override fun isAvailable(): Boolean { 204 return try { 205 pkgManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0)) 206 true 207 } catch (e: PackageManager.NameNotFoundException) { 208 false 209 } 210 } 211 toStringnull212 override fun toString(): String = componentMatcher.toString() 213 214 companion object { 215 private const val APP_LAUNCH_WAIT_TIME_MS = 10000L 216 } 217 } 218