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