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