• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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