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