1 /*
2  * 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.parsing
18 
19 import android.content.pm.ApplicationInfo
20 import android.content.pm.PackageParser
21 import android.os.Environment
22 import android.os.UserHandle
23 import android.platform.test.annotations.Presubmit
24 import com.google.common.truth.Truth.assertWithMessage
25 import org.junit.Test
26 
27 /**
28  * As a performance optimization, the new parsing code builds the user data directories manually
29  * using string concatenation. This tries to mirror the logic that [Environment] uses, but it is
30  * still fragile to changes and potentially different device configurations.
31  *
32  * This compares the resultant values against the old [PackageParser] outputs as well as
33  * [ApplicationInfo]'s own [ApplicationInfo.initForUser].
34  */
35 @Presubmit
36 class PackageInfoUserFieldsTest : AndroidPackageParsingTestBase() {
37 
38     @Test
39     fun userEnvironmentValues() {
40         // Specifically use a large user ID to test assumptions about single character IDs
41         val userId = 110
42 
43         oldPackages.zip(newPackages)
44                 .map { (old, new) ->
45                     (old to oldAppInfo(pkg = old, userId = userId)!!) to
46                             (new to newAppInfo(pkg = new, userId = userId)!!)
47                 }
48                 .forEach { (oldPair, newPair) ->
49                     val (oldPkg, oldInfo) = oldPair
50                     val (newPkg, newInfo) = newPair
51 
52                     val oldValuesActual = extractActual(oldInfo)
53                     val newValuesActual = extractActual(newInfo)
54                     val oldValuesExpected: Values
55                     val newValuesExpected: Values
56 
57                     val packageName = oldPkg.packageName
58                     if (packageName == "android") {
59                         val systemDataDir = Environment.getDataSystemDirectory().absolutePath
60                         oldValuesExpected = Values(
61                                 uid = UserHandle.getUid(userId,
62                                         UserHandle.getAppId(oldPkg.applicationInfo.uid)),
63                                 userDe = null,
64                                 userCe = null,
65                                 dataDir = systemDataDir
66                         )
67                         newValuesExpected = Values(
68                                 uid = UserHandle.getUid(userId, UserHandle.getAppId(newPkg.uid)),
69                                 userDe = null,
70                                 userCe = null,
71                                 dataDir = systemDataDir
72                         )
73                     } else {
74                         oldValuesExpected = extractExpected(oldInfo, oldInfo.uid, userId)
75                         newValuesExpected = extractExpected(newInfo, newPkg.uid, userId)
76                     }
77 
78                     // Calls the internal ApplicationInfo logic to compare against. This must be
79                     // done after saving the original values, since this will overwrite them.
80                     oldInfo.initForUser(userId)
81                     newInfo.initForUser(userId)
82 
83                     val oldInitValues = extractActual(oldInfo)
84                     val newInitValues = extractActual(newInfo)
85 
86                     // The optimization is also done for the no state API that isn't used by the
87                     // system. This API is still exposed publicly, so for this test we should
88                     // verify it.
89                     val newNoStateValues = extractActual(
90                             newAppInfoWithoutState(newPkg, 0, userId)!!)
91 
92                     assertAllEquals(packageName,
93                             oldValuesActual, oldValuesExpected, oldInitValues,
94                             newValuesActual, newValuesExpected, newInitValues, newNoStateValues)
95                 }
96     }
97 
98     private fun assertAllEquals(packageName: String, vararg values: Values) {
99         // Local function to avoid accidentally calling wrong type
100         fun assertAllEquals(message: String, vararg values: Any?) {
101             values.forEachIndexed { index, value ->
102                 if (index == 0) return@forEachIndexed
103                 assertWithMessage("$message $index").that(values[0]).isEqualTo(value)
104             }
105         }
106 
107         assertAllEquals("$packageName mismatched uid", values.map { it.uid })
108         assertAllEquals("$packageName mismatched userDe", values.map { it.userDe })
109         assertAllEquals("$packageName mismatched userCe", values.map { it.userCe })
110         assertAllEquals("$packageName mismatched dataDir", values.map { it.dataDir })
111     }
112 
113     private fun extractActual(appInfo: ApplicationInfo) = Values(
114             uid = appInfo.uid,
115             userDe = appInfo.deviceProtectedDataDir,
116             userCe = appInfo.credentialProtectedDataDir,
117             dataDir = appInfo.dataDir
118     )
119 
120     private fun extractExpected(appInfo: ApplicationInfo, appIdUid: Int, userId: Int): Values {
121         val userDe = Environment.getDataUserDePackageDirectory(appInfo.volumeUuid, userId,
122                 appInfo.packageName).absolutePath
123         val userCe = Environment.getDataUserCePackageDirectory(appInfo.volumeUuid, userId,
124                 appInfo.packageName).absolutePath
125         val dataDir = if (appInfo.isDefaultToDeviceProtectedStorage) {
126             appInfo.deviceProtectedDataDir
127         } else {
128             appInfo.credentialProtectedDataDir
129         }
130 
131         return Values(
132                 uid = UserHandle.getUid(userId, UserHandle.getAppId(appIdUid)),
133                 userDe = userDe,
134                 userCe = userCe,
135                 dataDir = dataDir
136         )
137     }
138 
139     data class Values(
140         val uid: Int,
141         val userDe: String?,
142         val userCe: String?,
143         val dataDir: String?
144     )
145 }
146