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: ComponentNameMatcher,
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     val packageNameMatcher = ComponentNameMatcher(componentMatcher.packageName, "")
66 
67     override val className = componentMatcher.className
68 
69     protected val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
70 
getAppSelectornull71     private fun getAppSelector(expectedPackageName: String): BySelector {
72         val expected = expectedPackageName.ifEmpty { packageName }
73         return By.pkg(expected).depth(0)
74     }
75 
opennull76     override fun open() {
77         open(packageName)
78     }
79 
opennull80     protected fun open(expectedPackageName: String) {
81         tapl.goHome().switchToAllApps().getAppIcon(appName).launch(expectedPackageName)
82     }
83 
84     /** {@inheritDoc} */
85     open val openAppIntent: Intent
86         get() {
87             val intent = Intent()
88             intent.addCategory(Intent.CATEGORY_LAUNCHER)
89             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
90             intent.component = ComponentName(packageName, className)
91             return intent
92         }
93 
94     /** {@inheritDoc} */
exitnull95     override fun exit() {
96         withTracing("${this::class.simpleName}#exit") {
97             // Ensure all testing components end up being closed.
98             activityManager?.forceStopPackage(packageName)
99         }
100     }
101 
102     /** {@inheritDoc} */
exitnull103     override fun exit(wmHelper: WindowManagerStateHelper) {
104         withTracing("${this::class.simpleName}#exitAndWait") {
105             exit()
106             waitForActivityDestroyed(wmHelper)
107         }
108     }
109 
110     /** Waits the activity until state change to {link WindowManagerState.STATE_DESTROYED} */
waitForActivityDestroyednull111     private fun waitForActivityDestroyed(wmHelper: WindowManagerStateHelper) {
112         val waitMsg =
113             "state of ${componentMatcher.toActivityIdentifier()} to be " +
114                 PlatformConsts.STATE_DESTROYED
115         wmHelper
116             .StateSyncBuilder()
117             .add(waitMsg) {
118                 !it.wmState.containsActivity(componentMatcher) ||
119                     it.wmState.hasActivityState(componentMatcher, PlatformConsts.STATE_DESTROYED)
120             }
121             .withAppTransitionIdle()
122             .waitForAndVerify()
123     }
124 
launchAppViaIntentnull125     private fun launchAppViaIntent(
126         action: String? = null,
127         stringExtras: Map<String, String> = mapOf()
128     ) {
129         withTracing("${this::class.simpleName}#launchAppViaIntent") {
130             val intent = openAppIntent
131             intent.action = action ?: Intent.ACTION_MAIN
132             stringExtras.forEach { intent.putExtra(it.key, it.value) }
133             context.startActivity(intent)
134         }
135     }
136 
137     /** {@inheritDoc} */
launchViaIntentnull138     override fun launchViaIntent(
139         expectedPackageName: String,
140         action: String?,
141         stringExtras: Map<String, String>
142     ) {
143         launchAppViaIntent(action, stringExtras)
144         val appSelector = getAppSelector(expectedPackageName)
145         uiDevice.wait(Until.hasObject(appSelector), APP_LAUNCH_WAIT_TIME_MS)
146     }
147 
148     /** {@inheritDoc} */
launchViaIntentnull149     override fun launchViaIntent(
150         wmHelper: WindowManagerStateHelper,
151         launchedAppComponentMatcherOverride: IComponentMatcher?,
152         action: String?,
153         stringExtras: Map<String, String>,
154         waitConditionsBuilder: WindowManagerStateHelper.StateSyncBuilder
155     ) {
156         launchAppViaIntent(action, stringExtras)
157         doWaitShown(launchedAppComponentMatcherOverride, waitConditionsBuilder)
158     }
159 
160     /** {@inheritDoc} */
launchViaIntentnull161     override fun launchViaIntent(
162         wmHelper: WindowManagerStateHelper,
163         intent: Intent,
164         launchedAppComponentMatcherOverride: IComponentMatcher?,
165         waitConditionsBuilder: WindowManagerStateHelper.StateSyncBuilder
166     ) {
167         withTracing("${this::class.simpleName}#launchViaIntent") {
168             context.startActivity(intent)
169             doWaitShown(launchedAppComponentMatcherOverride, waitConditionsBuilder)
170         }
171     }
172 
doWaitShownnull173     private fun doWaitShown(
174         launchedAppComponentMatcherOverride: IComponentMatcher? = null,
175         waitConditionsBuilder: WindowManagerStateHelper.StateSyncBuilder
176     ) {
177         withTracing("${this::class.simpleName}#doWaitShown") {
178             val expectedWindow = launchedAppComponentMatcherOverride ?: componentMatcher
179             val builder = waitConditionsBuilder.withWindowSurfaceAppeared(expectedWindow)
180             builder.waitForAndVerify()
181         }
182     }
183 
isAvailablenull184     override fun isAvailable(): Boolean {
185         return try {
186             pkgManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0))
187             true
188         } catch (e: PackageManager.NameNotFoundException) {
189             false
190         }
191     }
192 
toStringnull193     override fun toString(): String = componentMatcher.toString()
194 
195     companion object {
196         private const val APP_LAUNCH_WAIT_TIME_MS = 10000L
197     }
198 }
199