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