• 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.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