1 /*
<lambda>null2  * Copyright (C) 2020 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 com.android.server.pm.test
18 
19 import com.android.internal.util.test.SystemPreparer
20 import com.android.tradefed.device.ITestDevice
21 import com.android.tradefed.device.UserInfo
22 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner
23 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test
24 import com.google.common.truth.Truth.assertThat
25 import org.junit.AfterClass
26 import org.junit.Before
27 import org.junit.ClassRule
28 import org.junit.Rule
29 import org.junit.Test
30 import org.junit.rules.RuleChain
31 import org.junit.rules.TemporaryFolder
32 import org.junit.runner.RunWith
33 import java.io.File
34 import java.util.zip.GZIPOutputStream
35 
36 @RunWith(DeviceJUnit4ClassRunner::class)
37 class SystemStubMultiUserDisableUninstallTest : BaseHostJUnit4Test() {
38 
39     companion object {
40         private const val TEST_PKG_NAME = "com.android.server.pm.test.test_app"
41         private const val VERSION_STUB = "PackageManagerTestAppStub.apk"
42         private const val VERSION_ONE = "PackageManagerTestAppVersion1.apk"
43 
44         /**
45          * How many total users on device to test, including primary. This will clean up any
46          * users created specifically for this test.
47          */
48         private const val USER_COUNT = 3
49 
50         /**
51          * Whether to manually reset state at each test method without rebooting
52          * for faster iterative development.
53          */
54         private const val DEBUG_NO_REBOOT = false
55 
56         @get:ClassRule
57         val deviceRebootRule = SystemPreparer.TestRuleDelegate(true)
58 
59         private val parentClassName = SystemStubMultiUserDisableUninstallTest::class.java.simpleName
60 
61         private val deviceCompressedFile =
62                 HostUtils.makePathForApk("$parentClassName.apk", Partition.PRODUCT).parent
63                         .resolve("$parentClassName.apk.gz")
64 
65         private val stubFile =
66                 HostUtils.makePathForApk("$parentClassName-Stub.apk", Partition.PRODUCT)
67 
68         private val secondaryUsers = mutableListOf<Int>()
69         private val usersToRemove = mutableListOf<Int>()
70         private var savedDevice: ITestDevice? = null
71         private var savedPreparer: SystemPreparer? = null
72 
73         private fun setUpUsers(device: ITestDevice) {
74             if (this.savedDevice != null) return
75             this.savedDevice = device
76             secondaryUsers.clear()
77             secondaryUsers += device.userInfos.values.map(UserInfo::userId).filterNot { it == 0 }
78             while (secondaryUsers.size < USER_COUNT) {
79                 secondaryUsers += device.createUser(parentClassName + secondaryUsers.size)
80                         .also { usersToRemove += it }
81             }
82         }
83 
84         @JvmStatic
85         @AfterClass
86         fun cleanUp() {
87             savedDevice ?: return
88 
89             usersToRemove.forEach {
90                 savedDevice?.removeUser(it)
91             }
92 
93             savedDevice?.uninstallPackage(TEST_PKG_NAME)
94             savedDevice?.deleteFile(stubFile.parent.toString())
95             savedDevice?.deleteFile(deviceCompressedFile.parent.toString())
96             savedDevice?.reboot()
97             savedDevice = null
98 
99             if (DEBUG_NO_REBOOT) {
100                 savedPreparer?.after()
101                 savedPreparer = null
102             }
103         }
104     }
105 
106     private val tempFolder = TemporaryFolder()
107 
108     // TODO(b/160159215): Use START_STOP rather than FULL once it's fixed. This will drastically
109     //  improve pre/post-submit times.
110     private val preparer: SystemPreparer = SystemPreparer(tempFolder,
111             SystemPreparer.RebootStrategy.FULL, deviceRebootRule) { this.device }
112 
113     @Rule
114     @JvmField
115     val rules = RuleChain.outerRule(tempFolder).let {
116         if (DEBUG_NO_REBOOT) {
117             it!!
118         } else {
119             it.around(preparer)!!
120         }
121     }
122 
123     private var hostCompressedFile: File? = null
124 
125     private val previousCodePaths = mutableListOf<String>()
126 
127     @Before
128     fun ensureUserAndCompressStubAndInstall() {
129         setUpUsers(device)
130 
131         val initialized = hostCompressedFile != null
132         if (!initialized) {
133             hostCompressedFile = tempFolder.newFile()
134             hostCompressedFile!!.outputStream().use {
135                 javaClass.classLoader
136                         .getResource(VERSION_ONE)!!
137                         .openStream()
138                         .use { input ->
139                             GZIPOutputStream(it).use { output ->
140                                 input.copyTo(output)
141                             }
142                         }
143             }
144         }
145 
146         device.uninstallPackage(TEST_PKG_NAME)
147 
148         if (!initialized || !DEBUG_NO_REBOOT) {
149             savedPreparer = preparer
150             preparer.pushResourceFile(VERSION_STUB, stubFile.toString())
151                     .pushFile(hostCompressedFile, deviceCompressedFile.toString())
152                     .reboot()
153         }
154 
155         // This test forces the state to installed/enabled for all users,
156         // since it only tests the uninstall/disable side.
157         installExisting(User.PRIMARY)
158         installExisting(User.SECONDARY)
159 
160         ensureEnabled()
161 
162         // Ensure data app isn't re-installed multiple times by comparing against the original path
163         val codePath = HostUtils.getCodePaths(device, TEST_PKG_NAME).first()
164         assertThat(codePath).contains("/data/app")
165         assertThat(codePath).contains(TEST_PKG_NAME)
166 
167         previousCodePaths.clear()
168         previousCodePaths += codePath
169 
170         assertState(
171                 primaryInstalled = true, primaryEnabled = true,
172                 secondaryInstalled = true, secondaryEnabled = true,
173                 codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
174         )
175     }
176 
177     @Test
178     fun disablePrimaryFirstAndUninstall() {
179         toggleEnabled(false, User.PRIMARY)
180 
181         assertState(
182                 primaryInstalled = true, primaryEnabled = false,
183                 secondaryInstalled = true, secondaryEnabled = true,
184                 codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
185         )
186 
187         toggleEnabled(false, User.SECONDARY)
188 
189         assertState(
190                 primaryInstalled = true, primaryEnabled = false,
191                 secondaryInstalled = true, secondaryEnabled = false,
192                 codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
193         )
194 
195         device.uninstallPackage(TEST_PKG_NAME)
196 
197         assertState(
198                 primaryInstalled = true, primaryEnabled = false,
199                 secondaryInstalled = true, secondaryEnabled = false,
200                 codePaths = listOf(CodePath.SYSTEM)
201         )
202     }
203 
204     @Test
205     fun disableSecondaryFirstAndUninstall() {
206         toggleEnabled(false, User.SECONDARY)
207 
208         assertState(
209                 primaryInstalled = true, primaryEnabled = true,
210                 secondaryInstalled = true, secondaryEnabled = false,
211                 codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
212         )
213 
214         toggleEnabled(false, User.PRIMARY)
215 
216         assertState(
217                 primaryInstalled = true, primaryEnabled = false,
218                 secondaryInstalled = true, secondaryEnabled = false,
219                 codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
220         )
221 
222         device.uninstallPackage(TEST_PKG_NAME)
223 
224         assertState(
225                 primaryInstalled = true, primaryEnabled = false,
226                 secondaryInstalled = true, secondaryEnabled = false,
227                 codePaths = listOf(CodePath.SYSTEM)
228         )
229     }
230 
231     @Test
232     fun disabledUninstalledEnablePrimaryFirst() {
233         toggleEnabled(false, User.PRIMARY)
234         toggleEnabled(false, User.SECONDARY)
235         device.uninstallPackage(TEST_PKG_NAME)
236 
237         toggleEnabled(true, User.PRIMARY)
238 
239         assertState(
240                 primaryInstalled = true, primaryEnabled = true,
241                 secondaryInstalled = true, secondaryEnabled = false,
242                 codePaths = listOf(CodePath.DIFFERENT, CodePath.SYSTEM)
243         )
244 
245         toggleEnabled(true, User.SECONDARY)
246 
247         assertState(
248                 primaryInstalled = true, primaryEnabled = true,
249                 secondaryInstalled = true, secondaryEnabled = true,
250                 codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
251         )
252     }
253 
254     @Test
255     fun disabledUninstalledEnableSecondaryFirst() {
256         toggleEnabled(false, User.PRIMARY)
257         toggleEnabled(false, User.SECONDARY)
258         device.uninstallPackage(TEST_PKG_NAME)
259 
260         toggleEnabled(true, User.SECONDARY)
261 
262         assertState(
263                 primaryInstalled = true, primaryEnabled = false,
264                 secondaryInstalled = true, secondaryEnabled = true,
265                 codePaths = listOf(CodePath.DIFFERENT, CodePath.SYSTEM)
266         )
267 
268         toggleEnabled(true, User.PRIMARY)
269 
270         assertState(
271                 primaryInstalled = true, primaryEnabled = true,
272                 secondaryInstalled = true, secondaryEnabled = true,
273                 codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
274         )
275     }
276 
277     @Test
278     fun uninstallPrimaryFirstByUserAndInstallExistingPrimaryFirst() {
279         uninstall(User.PRIMARY)
280 
281         assertState(
282                 primaryInstalled = false, primaryEnabled = true,
283                 secondaryInstalled = true, secondaryEnabled = true,
284                 codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
285         )
286 
287         uninstall(User.SECONDARY)
288 
289         assertState(
290                 primaryInstalled = false, primaryEnabled = true,
291                 secondaryInstalled = false, secondaryEnabled = true,
292                 codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
293         )
294 
295         installExisting(User.PRIMARY)
296 
297         assertState(
298                 primaryInstalled = true, primaryEnabled = true,
299                 secondaryInstalled = false, secondaryEnabled = true,
300                 codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
301         )
302 
303         installExisting(User.SECONDARY)
304 
305         assertState(
306                 primaryInstalled = true, primaryEnabled = true,
307                 secondaryInstalled = true, secondaryEnabled = true,
308                 codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
309         )
310     }
311 
312     @Test
313     fun uninstallSecondaryFirstByUserAndInstallExistingSecondaryFirst() {
314         uninstall(User.SECONDARY)
315 
316         assertState(
317                 primaryInstalled = true, primaryEnabled = true,
318                 secondaryInstalled = false, secondaryEnabled = true,
319                 codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
320         )
321 
322         uninstall(User.PRIMARY)
323 
324         assertState(
325                 primaryInstalled = false, primaryEnabled = true,
326                 secondaryInstalled = false, secondaryEnabled = true,
327                 codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
328         )
329 
330         installExisting(User.SECONDARY)
331 
332         assertState(
333                 primaryInstalled = false, primaryEnabled = true,
334                 secondaryInstalled = true, secondaryEnabled = true,
335                 codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
336         )
337 
338         installExisting(User.PRIMARY)
339 
340         assertState(
341                 primaryInstalled = true, primaryEnabled = true,
342                 secondaryInstalled = true, secondaryEnabled = true,
343                 codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
344         )
345     }
346 
347     @Test
348     fun uninstallUpdatesAndEnablePrimaryFirst() {
349         device.executeShellCommand("pm uninstall-system-updates $TEST_PKG_NAME")
350 
351         assertState(
352                 primaryInstalled = true, primaryEnabled = true,
353                 secondaryInstalled = true, secondaryEnabled = true,
354                 // If any user is enabled when uninstalling updates, /data is re-uncompressed
355                 codePaths = listOf(CodePath.DIFFERENT, CodePath.SYSTEM)
356         )
357 
358         toggleEnabled(false, User.PRIMARY)
359         toggleEnabled(true, User.PRIMARY)
360 
361         assertState(
362                 primaryInstalled = true, primaryEnabled = true,
363                 secondaryInstalled = true, secondaryEnabled = true,
364                 codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
365         )
366 
367         // Test enabling secondary to ensure path does not change, even though it's already enabled
368         toggleEnabled(true, User.SECONDARY)
369 
370         assertState(
371                 primaryInstalled = true, primaryEnabled = true,
372                 secondaryInstalled = true, secondaryEnabled = true,
373                 codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
374         )
375     }
376 
377     @Test
378     fun uninstallUpdatesAndEnableSecondaryFirst() {
379         device.executeShellCommand("pm uninstall-system-updates $TEST_PKG_NAME")
380 
381         assertState(
382                 primaryInstalled = true, primaryEnabled = true,
383                 secondaryInstalled = true, secondaryEnabled = true,
384                 // If any user is enabled when uninstalling updates, /data is re-uncompressed
385                 codePaths = listOf(CodePath.DIFFERENT, CodePath.SYSTEM)
386         )
387 
388         toggleEnabled(false, User.PRIMARY)
389 
390         toggleEnabled(true, User.SECONDARY)
391 
392         assertState(
393                 primaryInstalled = true, primaryEnabled = false,
394                 secondaryInstalled = true, secondaryEnabled = true,
395                 codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
396         )
397 
398         toggleEnabled(true, User.PRIMARY)
399 
400         assertState(
401                 primaryInstalled = true, primaryEnabled = true,
402                 secondaryInstalled = true, secondaryEnabled = true,
403                 codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
404         )
405     }
406 
407     @Test
408     fun disabledUninstallUpdatesAndEnablePrimaryFirst() {
409         toggleEnabled(false, User.PRIMARY)
410         toggleEnabled(false, User.SECONDARY)
411 
412         device.executeShellCommand("pm uninstall-system-updates $TEST_PKG_NAME")
413 
414         assertState(
415                 primaryInstalled = true, primaryEnabled = false,
416                 secondaryInstalled = true, secondaryEnabled = false,
417                 codePaths = listOf(CodePath.SYSTEM)
418         )
419 
420         toggleEnabled(false, User.PRIMARY)
421         toggleEnabled(true, User.PRIMARY)
422 
423         assertState(
424                 primaryInstalled = true, primaryEnabled = true,
425                 secondaryInstalled = true, secondaryEnabled = false,
426                 codePaths = listOf(CodePath.DIFFERENT, CodePath.SYSTEM)
427         )
428 
429         toggleEnabled(true, User.SECONDARY)
430 
431         assertState(
432                 primaryInstalled = true, primaryEnabled = true,
433                 secondaryInstalled = true, secondaryEnabled = true,
434                 codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
435         )
436     }
437 
438     @Test
439     fun disabledUninstallUpdatesAndEnableSecondaryFirst() {
440         toggleEnabled(false, User.PRIMARY)
441         toggleEnabled(false, User.SECONDARY)
442 
443         device.executeShellCommand("pm uninstall-system-updates $TEST_PKG_NAME")
444 
445         assertState(
446                 primaryInstalled = true, primaryEnabled = false,
447                 secondaryInstalled = true, secondaryEnabled = false,
448                 codePaths = listOf(CodePath.SYSTEM)
449         )
450 
451         toggleEnabled(false, User.PRIMARY)
452         toggleEnabled(true, User.SECONDARY)
453 
454         assertState(
455                 primaryInstalled = true, primaryEnabled = false,
456                 secondaryInstalled = true, secondaryEnabled = true,
457                 codePaths = listOf(CodePath.DIFFERENT, CodePath.SYSTEM)
458         )
459 
460         toggleEnabled(true, User.PRIMARY)
461 
462         assertState(
463                 primaryInstalled = true, primaryEnabled = true,
464                 secondaryInstalled = true, secondaryEnabled = true,
465                 codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
466         )
467     }
468 
469     @Test
470     fun uninstalledUninstallUpdatesAndEnablePrimaryFirst() {
471         uninstall(User.PRIMARY)
472         uninstall(User.SECONDARY)
473 
474         device.executeShellCommand("pm uninstall-system-updates $TEST_PKG_NAME")
475 
476         assertState(
477                 primaryInstalled = false, primaryEnabled = true,
478                 secondaryInstalled = false, secondaryEnabled = true,
479                 codePaths = listOf(CodePath.SYSTEM)
480         )
481 
482         toggleEnabled(false, User.PRIMARY)
483         toggleEnabled(true, User.PRIMARY)
484 
485         assertState(
486                 primaryInstalled = false, primaryEnabled = true,
487                 secondaryInstalled = false, secondaryEnabled = true,
488                 codePaths = listOf(CodePath.DIFFERENT, CodePath.SYSTEM)
489         )
490 
491         toggleEnabled(true, User.SECONDARY)
492 
493         assertState(
494                 primaryInstalled = false, primaryEnabled = true,
495                 secondaryInstalled = false, secondaryEnabled = true,
496                 codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
497         )
498     }
499 
500     @Test
501     fun uninstalledUninstallUpdatesAndEnableSecondaryFirst() {
502         uninstall(User.PRIMARY)
503         uninstall(User.SECONDARY)
504 
505         device.executeShellCommand("pm uninstall-system-updates $TEST_PKG_NAME")
506 
507         assertState(
508                 primaryInstalled = false, primaryEnabled = true,
509                 secondaryInstalled = false, secondaryEnabled = true,
510                 codePaths = listOf(CodePath.SYSTEM)
511         )
512 
513         toggleEnabled(true, User.SECONDARY)
514 
515         assertState(
516                 primaryInstalled = false, primaryEnabled = true,
517                 secondaryInstalled = false, secondaryEnabled = true,
518                 codePaths = listOf(CodePath.DIFFERENT, CodePath.SYSTEM)
519         )
520 
521         toggleEnabled(true, User.PRIMARY)
522 
523         assertState(
524                 primaryInstalled = false, primaryEnabled = true,
525                 secondaryInstalled = false, secondaryEnabled = true,
526                 codePaths = listOf(CodePath.SAME, CodePath.SYSTEM)
527         )
528     }
529 
530     private fun ensureEnabled() {
531         toggleEnabled(true, User.PRIMARY)
532         toggleEnabled(true, User.SECONDARY)
533 
534         assertThat(HostUtils.getUserIdToPkgEnabledState(device, TEST_PKG_NAME).all { it.value })
535                 .isTrue()
536     }
537 
538     private fun toggleEnabled(enabled: Boolean, user: User, pkgName: String = TEST_PKG_NAME) {
539         val command = if (enabled) "enable" else "disable"
540         @Suppress("UNUSED_VARIABLE") val exhaust: Any = when (user) {
541             User.PRIMARY -> {
542                 device.executeShellCommand("pm $command --user 0 $pkgName")
543             }
544             User.SECONDARY -> {
545                 secondaryUsers.forEach {
546                     device.executeShellCommand("pm $command --user $it $pkgName")
547                 }
548             }
549         }
550     }
551 
552     private fun uninstall(user: User, pkgName: String = TEST_PKG_NAME) {
553         @Suppress("UNUSED_VARIABLE") val exhaust: Any = when (user) {
554             User.PRIMARY -> {
555                 device.executeShellCommand("pm uninstall --user 0 $pkgName")
556             }
557             User.SECONDARY -> {
558                 secondaryUsers.forEach {
559                     device.executeShellCommand("pm uninstall --user $it $pkgName")
560                 }
561             }
562         }
563     }
564 
565     private fun installExisting(user: User, pkgName: String = TEST_PKG_NAME) {
566         @Suppress("UNUSED_VARIABLE") val exhaust: Any = when (user) {
567             User.PRIMARY -> {
568                 device.executeShellCommand("pm install-existing --user 0 $pkgName")
569             }
570             User.SECONDARY -> {
571                 secondaryUsers.forEach {
572                     device.executeShellCommand("pm install-existing --user $it $pkgName")
573                 }
574             }
575         }
576     }
577 
578     private fun assertState(
579         primaryInstalled: Boolean,
580         primaryEnabled: Boolean,
581         secondaryInstalled: Boolean,
582         secondaryEnabled: Boolean,
583         codePaths: List<CodePath>
584     ) {
585         HostUtils.getUserIdToPkgInstalledState(device, TEST_PKG_NAME)
586             .also { assertThat(it.size).isAtLeast(USER_COUNT) }
587             .forEach { (userId, installed) ->
588                 if (userId == 0) {
589                     assertThat(installed).isEqualTo(primaryInstalled)
590                 } else {
591                     assertThat(installed).isEqualTo(secondaryInstalled)
592                 }
593             }
594 
595         HostUtils.getUserIdToPkgEnabledState(device, TEST_PKG_NAME)
596             .also { assertThat(it.size).isAtLeast(USER_COUNT) }
597             .forEach { (userId, enabled) ->
598                 if (userId == 0) {
599                     assertThat(enabled).isEqualTo(primaryEnabled)
600                 } else {
601                     assertThat(enabled).isEqualTo(secondaryEnabled)
602                 }
603             }
604 
605         assertCodePaths(codePaths.first(), codePaths.getOrNull(1))
606     }
607 
608     private fun assertCodePaths(firstCodePath: CodePath, secondCodePath: CodePath? = null) {
609         val codePaths = HostUtils.getCodePaths(device, TEST_PKG_NAME)
610         assertThat(codePaths).hasSize(listOfNotNull(firstCodePath, secondCodePath).size)
611 
612         when (firstCodePath) {
613             CodePath.SAME -> {
614                 assertThat(codePaths[0]).contains("/data/app")
615                 assertThat(codePaths[0]).contains(TEST_PKG_NAME)
616                 assertThat(codePaths[0]).isEqualTo(previousCodePaths.last())
617             }
618             CodePath.DIFFERENT -> {
619                 assertThat(codePaths[0]).contains("/data/app")
620                 assertThat(codePaths[0]).contains(TEST_PKG_NAME)
621                 assertThat(previousCodePaths).doesNotContain(codePaths[0])
622                 previousCodePaths.add(codePaths[0])
623             }
624             CodePath.SYSTEM -> assertThat(codePaths[0]).isEqualTo(stubFile.parent.toString())
625         }
626 
627         when (secondCodePath) {
628             CodePath.SAME, CodePath.DIFFERENT ->
629                 throw AssertionError("secondDataPath cannot be a data path")
630             CodePath.SYSTEM -> assertThat(codePaths[1]).isEqualTo(stubFile.parent.toString())
631             null -> {}
632         }
633     }
634 
635     enum class User {
636         /** The primary system user 0 */
637         PRIMARY,
638 
639         /**
640          * All other users on the device that are not 0. This is split into an enum so that all
641          * methods that handle secondary act on all non-system users. Some behaviors only occur
642          * if a package state is marked for all non-primary users on the device, which can be
643          * more than just 1.
644          */
645         SECONDARY
646     }
647 
648     enum class CodePath {
649         /** The data code path hasn't changed */
650         SAME,
651 
652         /** New data code path */
653         DIFFERENT,
654 
655         /** The static system code path */
656         SYSTEM
657     }
658 }
659