• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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