1 /* <lambda>null2 * 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.packageinstaller.install.cts 18 19 import android.Manifest 20 import android.app.ActivityManager 21 import android.app.AppOpsManager.MODE_ALLOWED 22 import android.app.AppOpsManager.OPSTR_TAKE_AUDIO_FOCUS 23 import android.app.Instrumentation 24 import android.app.UiAutomation 25 import android.content.Intent 26 import android.content.pm.PackageInstaller 27 import android.content.pm.PackageInstaller.InstallConstraints 28 import android.content.pm.PackageManager 29 import android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES 30 import android.platform.test.annotations.AppModeFull 31 import androidx.test.platform.app.InstrumentationRegistry 32 import androidx.test.runner.AndroidJUnit4 33 import com.android.compatibility.common.util.AppOpsUtils 34 import com.android.compatibility.common.util.PollingCheck 35 import com.android.compatibility.common.util.SystemUtil 36 import com.android.cts.install.lib.Install 37 import com.android.cts.install.lib.InstallUtils 38 import com.android.cts.install.lib.InstallUtils.getInstalledVersion 39 import com.android.cts.install.lib.LocalIntentSender 40 import com.android.cts.install.lib.TestApp 41 import com.android.cts.install.lib.Uninstall 42 import com.google.common.truth.Truth.assertThat 43 import java.security.MessageDigest 44 import java.util.concurrent.CompletableFuture 45 import java.util.concurrent.TimeUnit 46 import org.junit.After 47 import org.junit.Assert 48 import org.junit.Assume.assumeFalse 49 import org.junit.Before 50 import org.junit.Test 51 import org.junit.runner.RunWith 52 53 @RunWith(AndroidJUnit4::class) 54 @AppModeFull 55 class InstallConstraintsTest { 56 companion object { 57 private const val MATCH_STATIC_SHARED_AND_SDK_LIBRARIES = 0x04000000 58 private val HelloWorldSdk1 = TestApp( 59 "HelloWorldSdk1", "com.test.sdk1_1", 60 1, false, "HelloWorldSdk1.apk" 61 ) 62 private val HelloWorldUsingSdk1 = TestApp( 63 "HelloWorldUsingSdk1", 64 "com.test.sdk.user", 1, false, "HelloWorldUsingSdk1.apk" 65 ) 66 } 67 68 private val instr: Instrumentation = InstrumentationRegistry.getInstrumentation() 69 private val testUserId: Int = instr.targetContext.user.identifier 70 71 @Before 72 fun setUp() { 73 instr.uiAutomation.adoptShellPermissionIdentity( 74 Manifest.permission.PACKAGE_USAGE_STATS, 75 Manifest.permission.INSTALL_PACKAGES, 76 Manifest.permission.DELETE_PACKAGES) 77 } 78 79 @After 80 fun tearDown() { 81 Uninstall.packages(TestApp.A, TestApp.B, TestApp.S) 82 val uiAutomation: UiAutomation? = instr.uiAutomation 83 uiAutomation?.dropShellPermissionIdentity() 84 } 85 86 @Test 87 fun verifyGetters() { 88 InstallConstraints.Builder().setAppNotForegroundRequired().build().also { 89 assertThat(it.isAppNotForegroundRequired).isTrue() 90 } 91 InstallConstraints.Builder().setAppNotInteractingRequired().build().also { 92 assertThat(it.isAppNotInteractingRequired).isTrue() 93 } 94 InstallConstraints.Builder().setAppNotTopVisibleRequired().build().also { 95 assertThat(it.isAppNotTopVisibleRequired).isTrue() 96 } 97 InstallConstraints.Builder().setDeviceIdleRequired().build().also { 98 assertThat(it.isDeviceIdleRequired).isTrue() 99 } 100 InstallConstraints.Builder().setNotInCallRequired().build().also { 101 assertThat(it.isNotInCallRequired).isTrue() 102 } 103 InstallConstraints.Builder().build().also { 104 assertThat(it.isAppNotForegroundRequired).isFalse() 105 assertThat(it.isAppNotInteractingRequired).isFalse() 106 assertThat(it.isAppNotTopVisibleRequired).isFalse() 107 assertThat(it.isDeviceIdleRequired).isFalse() 108 assertThat(it.isNotInCallRequired).isFalse() 109 } 110 } 111 112 @Test 113 fun testCheckInstallConstraints_AppIsInteracting() { 114 // Skip this test as the current audio focus detection doesn't work on Auto 115 assumeFalse(isAuto()) 116 117 Install.single(TestApp.A1).commit() 118 try { 119 // Grant the OPSTR_TAKE_AUDIO_FOCUS to the test app 120 AppOpsUtils.setOpMode(TestApp.A, OPSTR_TAKE_AUDIO_FOCUS, MODE_ALLOWED) 121 // The app will have audio focus and be considered interactive with the user 122 InstallUtils.requestAudioFocus(TestApp.A) 123 val pi = InstallUtils.getPackageInstaller() 124 val constraints = InstallConstraints.Builder().setAppNotInteractingRequired().build() 125 val future = CompletableFuture<PackageInstaller.InstallConstraintsResult>() 126 pi.checkInstallConstraints( 127 listOf(TestApp.A), 128 constraints, 129 { r -> r.run() } 130 ) { result -> future.complete(result) } 131 assertThat(future.join().areAllConstraintsSatisfied()).isFalse() 132 } finally { 133 AppOpsUtils.reset(TestApp.A) 134 } 135 } 136 137 @Test 138 fun testCheckInstallConstraints_AppNotInstalled() { 139 assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1) 140 val pi = InstallUtils.getPackageInstaller() 141 try { 142 pi.checkInstallConstraints( 143 listOf(TestApp.A), 144 InstallConstraints.GENTLE_UPDATE, 145 { r -> r.run() } 146 ) { } 147 Assert.fail() 148 } catch (e: SecurityException) { 149 assertThat(e.message).contains("has no access to package") 150 } 151 } 152 153 @Test 154 fun testCheckInstallConstraints_AppIsTopVisible() { 155 Install.single(TestApp.A1).commit() 156 Install.single(TestApp.B1).commit() 157 // We will have a top-visible app 158 startActivity(TestApp.A) 159 160 val pi = InstallUtils.getPackageInstaller() 161 val f1 = CompletableFuture<PackageInstaller.InstallConstraintsResult>() 162 val constraints = InstallConstraints.Builder().setAppNotTopVisibleRequired().build() 163 pi.checkInstallConstraints( 164 listOf(TestApp.A), 165 constraints, 166 { r -> r.run() } 167 ) { result -> f1.complete(result) } 168 assertThat(f1.join().areAllConstraintsSatisfied()).isFalse() 169 170 // Test app A is no longer top-visible 171 startActivity(TestApp.B) 172 PollingCheck.waitFor { 173 val importance = getPackageImportance(TestApp.A) 174 importance > ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND 175 } 176 val f2 = CompletableFuture<PackageInstaller.InstallConstraintsResult>() 177 pi.checkInstallConstraints( 178 listOf(TestApp.A), 179 constraints, 180 { r -> r.run() } 181 ) { result -> f2.complete(result) } 182 assertThat(f2.join().areAllConstraintsSatisfied()).isTrue() 183 } 184 185 @Test 186 fun testCheckInstallConstraints_AppIsForeground() { 187 Install.single(TestApp.A1).commit() 188 Install.single(TestApp.B1).commit() 189 // We will have a foreground app 190 startActivity(TestApp.A) 191 192 val pi = InstallUtils.getPackageInstaller() 193 val f1 = CompletableFuture<PackageInstaller.InstallConstraintsResult>() 194 val constraints = InstallConstraints.Builder().setAppNotForegroundRequired().build() 195 pi.checkInstallConstraints( 196 listOf(TestApp.A), 197 constraints, 198 { r -> r.run() } 199 ) { result -> f1.complete(result) } 200 assertThat(f1.join().areAllConstraintsSatisfied()).isFalse() 201 202 // Test app A is no longer foreground 203 startActivity(TestApp.B) 204 PollingCheck.waitFor { 205 val importance = getPackageImportance(TestApp.A) 206 importance > ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND 207 } 208 val f2 = CompletableFuture<PackageInstaller.InstallConstraintsResult>() 209 pi.checkInstallConstraints( 210 listOf(TestApp.A), 211 constraints, 212 { r -> r.run() } 213 ) { result -> f2.complete(result) } 214 assertThat(f2.join().areAllConstraintsSatisfied()).isTrue() 215 } 216 217 @Test 218 fun testCheckInstallConstraints_DeviceIsIdle() { 219 val propKey = "debug.pm.gentle_update_test.is_idle" 220 221 Install.single(TestApp.A1).commit() 222 223 try { 224 // Device is not idle 225 SystemUtil.runShellCommand("setprop $propKey 0") 226 val pi = InstallUtils.getPackageInstaller() 227 val f1 = CompletableFuture<PackageInstaller.InstallConstraintsResult>() 228 val constraints = InstallConstraints.Builder().setDeviceIdleRequired().build() 229 pi.checkInstallConstraints( 230 listOf(TestApp.A), 231 constraints, 232 { r -> r.run() } 233 ) { result -> f1.complete(result) } 234 assertThat(f1.join().areAllConstraintsSatisfied()).isFalse() 235 236 // Device is idle 237 SystemUtil.runShellCommand(" setprop $propKey 1") 238 val f2 = CompletableFuture<PackageInstaller.InstallConstraintsResult>() 239 pi.checkInstallConstraints( 240 listOf(TestApp.A), 241 constraints, 242 { r -> r.run() } 243 ) { result -> f2.complete(result) } 244 assertThat(f2.join().areAllConstraintsSatisfied()).isTrue() 245 } finally { 246 SystemUtil.runShellCommand("setprop $propKey 0") 247 } 248 } 249 250 @Test 251 fun testCheckInstallConstraints_DeviceIsInCall() { 252 val propKey = "debug.pm.gentle_update_test.is_in_call" 253 Install.single(TestApp.A1).commit() 254 255 try { 256 // Device is in call 257 SystemUtil.runShellCommand("setprop $propKey 1") 258 val pi = InstallUtils.getPackageInstaller() 259 val f1 = CompletableFuture<PackageInstaller.InstallConstraintsResult>() 260 val constraints = InstallConstraints.Builder().setNotInCallRequired().build() 261 pi.checkInstallConstraints( 262 listOf(TestApp.A), 263 constraints, 264 { r -> r.run() } 265 ) { result -> f1.complete(result) } 266 assertThat(f1.join().areAllConstraintsSatisfied()).isFalse() 267 268 // Device is not in call 269 SystemUtil.runShellCommand("setprop $propKey 0") 270 val f2 = CompletableFuture<PackageInstaller.InstallConstraintsResult>() 271 pi.checkInstallConstraints( 272 listOf(TestApp.A), 273 constraints, 274 { r -> r.run() } 275 ) { result -> f2.complete(result) } 276 assertThat(f2.join().areAllConstraintsSatisfied()).isTrue() 277 } finally { 278 SystemUtil.runShellCommand("setprop $propKey 0") 279 } 280 } 281 282 @Test 283 @Throws(Exception::class) 284 fun testCheckInstallConstraints_BoundedService() { 285 Install.single(TestApp.A1).commit() 286 Install.single(TestApp.B1).commit() 287 Install.single(TestApp.S1).commit() 288 // Start an activity which will bind a service 289 // Test app S is considered foreground as A is foreground 290 startActivity(TestApp.A, "com.android.cts.install.lib.testapp.TestServiceActivity") 291 292 val pi = InstallUtils.getPackageInstaller() 293 val f1 = CompletableFuture<PackageInstaller.InstallConstraintsResult>() 294 val constraints = InstallConstraints.Builder().setAppNotForegroundRequired().build() 295 pi.checkInstallConstraints( 296 listOf(TestApp.S), 297 constraints, 298 { r -> r.run() } 299 ) { result -> f1.complete(result) } 300 assertThat(f1.join().areAllConstraintsSatisfied()).isFalse() 301 302 // Test app A is no longer foreground. So is test app S. 303 startActivity(TestApp.B) 304 PollingCheck.waitFor { 305 val importance = getPackageImportance(TestApp.A) 306 importance > ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND 307 } 308 val f2 = CompletableFuture<PackageInstaller.InstallConstraintsResult>() 309 pi.checkInstallConstraints( 310 listOf(TestApp.S), 311 constraints, 312 { r -> r.run() } 313 ) { result -> f2.complete(result) } 314 assertThat(f2.join().areAllConstraintsSatisfied()).isTrue() 315 } 316 317 @Test 318 fun testCheckInstallConstraints_UsesLibrary() { 319 val propKey = "debug.pm.uses_sdk_library_default_cert_digest" 320 321 try { 322 Install.single(TestApp.B1).commit() 323 Install.single(HelloWorldSdk1).commit() 324 // Override the certificate digest so HelloWorldUsingSdk1 can be installed 325 SystemUtil.runShellCommand( 326 "setprop $propKey ${getPackageCertDigest(HelloWorldSdk1.packageName)}") 327 Install.single(HelloWorldUsingSdk1).commit() 328 329 // HelloWorldSdk1 will be considered foreground as HelloWorldUsingSdk1 is foreground 330 startActivity(HelloWorldUsingSdk1.packageName, 331 "com.example.helloworld.MainActivityNoExit") 332 val pi = InstallUtils.getPackageInstaller() 333 val f1 = CompletableFuture<PackageInstaller.InstallConstraintsResult>() 334 val constraints = InstallConstraints.Builder().setAppNotForegroundRequired().build() 335 pi.checkInstallConstraints( 336 listOf(HelloWorldSdk1.packageName), 337 constraints, 338 { r -> r.run() } 339 ) { result -> f1.complete(result) } 340 assertThat(f1.join().areAllConstraintsSatisfied()).isFalse() 341 342 // HelloWorldUsingSdk1 is no longer foreground. So is HelloWorldSdk1. 343 startActivity(TestApp.B) 344 PollingCheck.waitFor { 345 val importance = getPackageImportance(HelloWorldUsingSdk1.packageName) 346 importance > ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND 347 } 348 349 val f2 = CompletableFuture<PackageInstaller.InstallConstraintsResult>() 350 pi.checkInstallConstraints( 351 listOf(HelloWorldSdk1.packageName), 352 constraints, 353 { r -> r.run() } 354 ) { result -> f2.complete(result) } 355 assertThat(f2.join().areAllConstraintsSatisfied()).isTrue() 356 } finally { 357 SystemUtil.runShellCommand("setprop $propKey invalid") 358 } 359 } 360 361 @Test 362 fun testWaitForInstallConstraints_AppIsForeground() { 363 Install.single(TestApp.A1).commit() 364 Install.single(TestApp.B1).commit() 365 // We will have a foreground app 366 startActivity(TestApp.A) 367 val pi = InstallUtils.getPackageInstaller() 368 val inputConstraints = InstallConstraints.Builder().setAppNotInteractingRequired().build() 369 370 // Timeout == 0, constraints not satisfied 371 with(LocalIntentSender()) { 372 pi.waitForInstallConstraints( 373 listOf(TestApp.A), inputConstraints, 374 intentSender, 0 375 ) 376 val intent = this.result 377 val packageNames = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES) 378 val receivedConstraints = intent.getParcelableExtra( 379 PackageInstaller.EXTRA_INSTALL_CONSTRAINTS, InstallConstraints::class.java) 380 val result = intent.getParcelableExtra( 381 PackageInstaller.EXTRA_INSTALL_CONSTRAINTS_RESULT, 382 PackageInstaller.InstallConstraintsResult::class.java 383 ) 384 assertThat(packageNames).asList().containsExactly(TestApp.A) 385 assertThat(receivedConstraints).isEqualTo(inputConstraints) 386 assertThat(result!!.areAllConstraintsSatisfied()).isFalse() 387 } 388 389 // Timeout == one day, constraints not satisfied 390 with(LocalIntentSender()) { 391 pi.waitForInstallConstraints( 392 listOf(TestApp.A), inputConstraints, 393 intentSender, TimeUnit.DAYS.toMillis(1) 394 ) 395 // Wait for a while and check the callback is not invoked yet 396 assertThat(pollResult(3, TimeUnit.SECONDS)).isNull() 397 398 // Test app A is no longer foreground. The callback will be invoked soon. 399 startActivity(TestApp.B) 400 val intent = this.result 401 val packageNames = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES) 402 val receivedConstraints = intent.getParcelableExtra( 403 PackageInstaller.EXTRA_INSTALL_CONSTRAINTS, InstallConstraints::class.java) 404 val result = intent.getParcelableExtra( 405 PackageInstaller.EXTRA_INSTALL_CONSTRAINTS_RESULT, 406 PackageInstaller.InstallConstraintsResult::class.java 407 ) 408 assertThat(packageNames).asList().containsExactly(TestApp.A) 409 assertThat(receivedConstraints).isEqualTo(inputConstraints) 410 assertThat(result!!.areAllConstraintsSatisfied()).isTrue() 411 } 412 } 413 414 @Test 415 fun testCommitAfterInstallConstraintsMet_NoTimeout() { 416 Install.single(TestApp.A1).commit() 417 418 // Constraints are satisfied. The session will be committed without timeout. 419 val pi = InstallUtils.getPackageInstaller() 420 val sessionId = Install.single(TestApp.A2).createSession() 421 val constraints = InstallConstraints.Builder().setAppNotForegroundRequired().build() 422 val sender = LocalIntentSender() 423 pi.commitSessionAfterInstallConstraintsAreMet( 424 sessionId, sender.intentSender, 425 constraints, TimeUnit.MINUTES.toMillis(1) 426 ) 427 InstallUtils.assertStatusSuccess(sender.result) 428 assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2) 429 } 430 431 @Test 432 fun testCommitAfterInstallConstraintsMet_RetryOnTimeout() { 433 Install.single(TestApp.A1).commit() 434 Install.single(TestApp.B1).commit() 435 // We will have a foreground app 436 startActivity(TestApp.A) 437 438 // Timeout for constraints not satisfied 439 val pi = InstallUtils.getPackageInstaller() 440 val sessionId = Install.single(TestApp.A2).createSession() 441 val constraints = InstallConstraints.Builder().setAppNotForegroundRequired().build() 442 val sender = LocalIntentSender() 443 pi.commitSessionAfterInstallConstraintsAreMet( 444 sessionId, sender.intentSender, 445 constraints, TimeUnit.SECONDS.toMillis(3) 446 ) 447 InstallUtils.assertStatusFailure(sender.result) 448 assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1) 449 450 // Test app A is no longer foreground 451 startActivity(TestApp.B) 452 PollingCheck.waitFor { 453 val importance = getPackageImportance(TestApp.A) 454 importance > ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND 455 } 456 // Commit will succeed for constraints are satisfied 457 pi.commitSessionAfterInstallConstraintsAreMet( 458 sessionId, sender.intentSender, 459 constraints, TimeUnit.MINUTES.toMillis(1) 460 ) 461 InstallUtils.assertStatusSuccess(sender.result) 462 assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2) 463 } 464 465 private fun isAuto() = 466 instr.context.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) 467 468 private fun startActivity(packageName: String) = 469 startActivity(packageName, "com.android.cts.install.lib.testapp.MainActivity") 470 471 private fun startActivity(packageName: String, className: String) = 472 // The -W option waits for the activity launch to complete 473 SystemUtil.runShellCommandOrThrow( 474 "am start-activity --user $testUserId -W -n $packageName/$className") 475 476 private fun getPackageImportance(packageName: String) = 477 instr.context.getSystemService(ActivityManager::class.java)!! 478 .getPackageImportance(packageName) 479 480 private fun computeSha256DigestBytes(data: ByteArray) = 481 MessageDigest.getInstance("SHA256").run { 482 update(data) 483 digest() 484 } 485 486 private fun encodeHex(data: ByteArray): String { 487 val hexDigits = "0123456789abcdef".toCharArray() 488 val len = data.size 489 val result = StringBuilder(len * 2) 490 for (i in 0 until len) { 491 val b = data[i] 492 result.append(hexDigits[b.toInt() ushr 4 and 0x0f]) 493 result.append(hexDigits[b.toInt() and 0x0f]) 494 } 495 return result.toString() 496 } 497 498 private fun getPackageCertDigest(packageName: String): String? { 499 val pm: PackageManager = instr.context.packageManager 500 val flags = GET_SIGNING_CERTIFICATES or MATCH_STATIC_SHARED_AND_SDK_LIBRARIES 501 val packageInfo = pm.getPackageInfo( 502 packageName, 503 PackageManager.PackageInfoFlags.of(flags.toLong()) 504 ) 505 val signatures = packageInfo.signingInfo!!.signingCertificateHistory 506 val digest = computeSha256DigestBytes(signatures[0].toByteArray()) 507 return encodeHex(digest) 508 } 509 } 510