1 /* 2 * Copyright (C) 2024 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 package com.android.bedstead.testapps 17 18 import com.android.bedstead.enterprise.annotations.EnsureHasDelegate 19 import com.android.bedstead.harrier.AnnotationExecutorUtil 20 import com.android.bedstead.harrier.BedsteadServiceLocator 21 import com.android.bedstead.harrier.DeviceStateComponent 22 import com.android.bedstead.harrier.annotations.EnsureTestAppDoesNotHavePermission 23 import com.android.bedstead.harrier.annotations.EnsureTestAppHasAppOp 24 import com.android.bedstead.harrier.annotations.EnsureTestAppHasPermission 25 import com.android.bedstead.harrier.annotations.EnsureTestAppInstalled 26 import com.android.bedstead.harrier.annotations.FailureMode 27 import com.android.bedstead.harrier.annotations.enterprise.AdditionalQueryParameters 28 import com.android.bedstead.nene.TestApis.packages 29 import com.android.bedstead.nene.exceptions.NeneException 30 import com.android.bedstead.nene.users.UserReference 31 import com.android.bedstead.remotedpc.RemoteDpc 32 import com.android.bedstead.remotedpc.RemoteTestApp 33 import com.android.bedstead.testapp.TestApp 34 import com.android.bedstead.testapp.TestAppInstance 35 import com.android.bedstead.testapp.TestAppProvider 36 import com.android.queryable.annotations.Query 37 import com.google.errorprone.annotations.CanIgnoreReturnValue 38 import org.junit.Assume 39 40 /** 41 * Manages test apps for device state tests. 42 * 43 * @param locator provides access to other dependencies. 44 */ 45 class TestAppsComponent(locator: BedsteadServiceLocator) : DeviceStateComponent { 46 47 private val testApps: MutableMap<String, TestAppInstance> = HashMap() 48 private val installedTestApps: MutableSet<TestAppInstance> = HashSet() 49 private val uninstalledTestApps: MutableSet<TestAppInstance> = HashSet() 50 val testAppProvider = TestAppProvider() 51 private val _additionalQueryParameters: MutableMap<String, Query> = mutableMapOf() 52 val additionalQueryParameters: Map<String, Query> 53 get() = _additionalQueryParameters 54 55 /** 56 * See [EnsureTestAppHasPermission] 57 */ ensureTestAppHasPermissionnull58 fun ensureTestAppHasPermission( 59 testAppKey: String, 60 permissions: Array<String>, 61 minVersion: Int, 62 maxVersion: Int, 63 failureMode: FailureMode 64 ) { 65 checkTestAppExistsWithKey(testAppKey) 66 try { 67 testApps[testAppKey]!!.permissions() 68 .withPermissionOnVersionBetween(minVersion, maxVersion, *permissions) 69 } catch (e: NeneException) { 70 if (failureMode == FailureMode.SKIP && e.message!!.contains("Cannot grant") || 71 e.message!!.contains("Error granting") 72 ) { 73 AnnotationExecutorUtil.failOrSkip(e.message, FailureMode.SKIP) 74 } else { 75 throw e 76 } 77 } 78 } 79 80 /** 81 * See [EnsureTestAppDoesNotHavePermission] 82 */ ensureTestAppDoesNotHavePermissionnull83 fun ensureTestAppDoesNotHavePermission( 84 testAppKey: String, 85 permissions: Array<String>, 86 failureMode: FailureMode 87 ) { 88 checkTestAppExistsWithKey(testAppKey) 89 try { 90 testApps[testAppKey]!!.permissions().withoutPermission(*permissions) 91 } catch (e: NeneException) { 92 if (failureMode == FailureMode.SKIP) { 93 AnnotationExecutorUtil.failOrSkip(e.message, FailureMode.SKIP) 94 } else { 95 throw e 96 } 97 } 98 } 99 100 /** 101 * See [EnsureTestAppHasAppOp] 102 */ ensureTestAppHasAppOpnull103 fun ensureTestAppHasAppOp( 104 testAppKey: String, 105 appOps: Array<String>, 106 minVersion: Int, 107 maxVersion: Int 108 ) { 109 checkTestAppExistsWithKey(testAppKey) 110 testApps[testAppKey]!!.permissions() 111 .withAppOpOnVersionBetween(minVersion, maxVersion, *appOps) 112 } 113 checkTestAppExistsWithKeynull114 private fun checkTestAppExistsWithKey(testAppKey: String) { 115 if (!testApps.containsKey(testAppKey)) { 116 throw NeneException( 117 "No testapp with key " + testAppKey + ". Use @EnsureTestAppInstalled." + 118 " Valid Test apps: " + testApps 119 ) 120 } 121 } 122 123 /** 124 * See [EnsureTestAppInstalled] 125 */ 126 @CanIgnoreReturnValue ensureTestAppInstallednull127 fun ensureTestAppInstalled(testApp: TestApp, user: UserReference): TestAppInstance? { 128 return ensureTestAppInstalled(key = null, testApp, user) 129 } 130 131 /** 132 * See [EnsureTestAppInstalled] 133 */ 134 @CanIgnoreReturnValue ensureTestAppInstallednull135 fun ensureTestAppInstalled( 136 key: String?, 137 testApp: TestApp, 138 user: UserReference 139 ): TestAppInstance? { 140 if (additionalQueryParameters.isNotEmpty()) { 141 Assume.assumeFalse( 142 "b/276740719 - we don't support custom delegates", 143 EnsureHasDelegate.DELEGATE_KEY == key 144 ) 145 } 146 val pkg = packages().find(testApp.packageName()) 147 val testAppInstance: TestAppInstance? 148 if (pkg != null && packages().find(testApp.packageName()).installedOnUser(user)) { 149 testAppInstance = testApp.instance(user) 150 } else { 151 // TODO: Consider if we want to record that we've started it so we can stop it after 152 // if needed? 153 user.start() 154 testAppInstance = testApp.install(user) 155 installedTestApps.add(testAppInstance) 156 } 157 if (key != null) { 158 testApps[key] = testAppInstance 159 } 160 return testAppInstance 161 } 162 163 /** 164 * Uninstalls a test app for the given user. 165 * If the test app is not installed, this method does nothing. 166 * 167 * @param testApp The test app to uninstall. 168 * @param user The user for whom the test app should be uninstalled. 169 */ ensureTestAppNotInstallednull170 fun ensureTestAppNotInstalled(testApp: TestApp, user: UserReference?) { 171 val pkg = packages().find(testApp.packageName()) 172 if (pkg == null || !packages().find(testApp.packageName()).installedOnUser(user)) { 173 return 174 } 175 val instance = testApp.instance(user) 176 if (installedTestApps.contains(instance)) { 177 installedTestApps.remove(instance) 178 } else { 179 uninstalledTestApps.add(instance) 180 } 181 testApp.uninstall(user) 182 } 183 184 /** 185 * Gets TestAppInstance for the given [key] 186 * @throws NeneException if there is no TestAppInstance for a given [key] 187 */ testAppnull188 fun testApp(key: String): TestAppInstance { 189 return testApps[key] 190 ?: throw NeneException("No testapp with given key. Use @EnsureTestAppInstalled") 191 } 192 193 /** 194 * Saves [RemoteDpc] for the given [key] 195 */ addRemoteDpcTestAppnull196 fun addRemoteDpcTestApp(key: String, remoteDpc: RemoteDpc) { 197 testApps[key] = remoteDpc 198 } 199 200 /** 201 * See [EnsureTestAppInstalled] 202 */ 203 @CanIgnoreReturnValue ensureTestAppInstallednull204 fun ensureTestAppInstalled( 205 key: String, 206 query: Query, 207 user: UserReference 208 ): TestAppInstance? { 209 val testApp: TestApp = testAppProvider.query(query).applyAnnotation( 210 additionalQueryParameters.getOrDefault(key, null) 211 ).get() 212 return ensureTestAppInstalled( 213 key, 214 testApp, 215 user 216 ) 217 } 218 prepareTestStatenull219 override fun prepareTestState() { 220 testAppProvider.snapshot() 221 } 222 teardownNonShareableStatenull223 override fun teardownNonShareableState() { 224 testApps.clear() 225 testAppProvider.restore() 226 _additionalQueryParameters.clear() 227 } 228 teardownShareableStatenull229 override fun teardownShareableState() { 230 for (installedTestApp in installedTestApps) { 231 installedTestApp.uninstall() 232 } 233 installedTestApps.clear() 234 235 for (uninstalledTestApp in uninstalledTestApps) { 236 uninstalledTestApp.testApp().install(uninstalledTestApp.user()) 237 } 238 uninstalledTestApps.clear() 239 } 240 addQueryParametersnull241 fun addQueryParameters(annotation: AdditionalQueryParameters) { 242 _additionalQueryParameters[annotation.forTestApp] = annotation.query 243 } 244 } 245