1 /*
2  * Copyright (C) 2022 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.pkg
18 
19 import android.content.Intent
20 import android.content.pm.PackageManager
21 import android.content.pm.PathPermission
22 import android.content.pm.SharedLibraryInfo
23 import android.content.pm.VersionedPackage
24 import android.content.pm.overlay.OverlayPaths
25 import android.os.PatternMatcher
26 import android.util.ArraySet
27 import com.android.internal.pm.parsing.pkg.PackageImpl
28 import com.android.internal.pm.pkg.component.ParsedActivity
29 import com.android.internal.pm.pkg.component.ParsedActivityImpl
30 import com.android.internal.pm.pkg.component.ParsedComponentImpl
31 import com.android.internal.pm.pkg.component.ParsedInstrumentation
32 import com.android.internal.pm.pkg.component.ParsedIntentInfoImpl
33 import com.android.internal.pm.pkg.component.ParsedPermission
34 import com.android.internal.pm.pkg.component.ParsedPermissionGroup
35 import com.android.internal.pm.pkg.component.ParsedPermissionImpl
36 import com.android.internal.pm.pkg.component.ParsedProcess
37 import com.android.internal.pm.pkg.component.ParsedProcessImpl
38 import com.android.internal.pm.pkg.component.ParsedProvider
39 import com.android.internal.pm.pkg.component.ParsedProviderImpl
40 import com.android.internal.pm.pkg.component.ParsedService
41 import com.android.server.pm.PackageSetting
42 import com.android.server.pm.PackageSettingBuilder
43 import com.android.server.pm.pkg.AndroidPackage
44 import com.android.server.pm.pkg.PackageState
45 import com.android.server.pm.pkg.PackageUserState
46 import com.android.server.pm.test.parsing.parcelling.AndroidPackageTest
47 import com.google.common.truth.Expect
48 import kotlin.contracts.ExperimentalContracts
49 import kotlin.reflect.KClass
50 import kotlin.reflect.KFunction
51 import kotlin.reflect.KType
52 import kotlin.reflect.full.isSubtypeOf
53 import kotlin.reflect.full.memberFunctions
54 import kotlin.reflect.full.starProjectedType
55 import org.junit.Rule
56 import org.junit.Test
57 import org.junit.rules.TemporaryFolder
58 
59 class PackageStateTest {
60 
61     companion object {
62         private val IGNORED_TYPES = listOf(
63                 "java.io.File",
64                 "java.lang.Boolean",
65                 "java.lang.Byte",
66                 "java.lang.CharSequence",
67                 "java.lang.Character",
68                 "java.lang.Double",
69                 "java.lang.Float",
70                 "java.lang.Integer",
71                 "java.lang.Long",
72                 "java.lang.Short",
73                 "java.lang.String",
74                 "java.lang.Void",
75         )
76         // STOPSHIP: Remove these and fix the implementations
77         private val IGNORED_FUNCTIONS = listOf(
78             ParsedActivity::getIntents,
79             ParsedActivity::getKnownActivityEmbeddingCerts,
80             ParsedActivity::getProperties,
81             ParsedInstrumentation::getIntents,
82             ParsedInstrumentation::getIntents,
83             ParsedInstrumentation::getProperties,
84             ParsedInstrumentation::getProperties,
85             ParsedPermission::getIntents,
86             ParsedPermission::getProperties,
87             ParsedPermissionGroup::getIntents,
88             ParsedPermissionGroup::getProperties,
89             ParsedProcess::getAppClassNamesByPackage,
90             ParsedProvider::getIntents,
91             ParsedProvider::getPathPermissions,
92             ParsedProvider::getProperties,
93             ParsedProvider::getUriPermissionPatterns,
94             ParsedService::getIntents,
95             ParsedService::getProperties,
96             Intent::getCategories,
97             Intent::getExtraIntentKeys,
98             PackageUserState::getDisabledComponents,
99             PackageUserState::getEnabledComponents,
100             PackageUserState::getSharedLibraryOverlayPaths,
101             OverlayPaths::getOverlayPaths,
102             OverlayPaths::getResourceDirs,
103         )
104     }
105 
106     @get:Rule
107     val tempFolder = TemporaryFolder()
108 
109     @get:Rule
110     val expect = Expect.create()
111 
112     private val collectionType = MutableCollection::class.starProjectedType
113     private val mapType = Map::class.starProjectedType
114 
115     @OptIn(ExperimentalContracts::class)
116     @Test
collectionImmutabilitynull117     fun collectionImmutability() {
118         val seenTypes = mutableSetOf<KType>()
119         val (_, pkg) = AndroidPackageTest().buildBefore()
120         val packageState = PackageSettingBuilder()
121             .setPackage(pkg as AndroidPackage)
122             .setCodePath(tempFolder.newFile().path)
123             .build()
124 
125         fillMissingData(packageState, pkg as PackageImpl)
126 
127         visitType(seenTypes, emptyList(), PackageSetting(packageState, true),
128             PackageState::class.starProjectedType)
129         visitType(seenTypes, emptyList(), pkg, AndroidPackage::class.starProjectedType)
130         visitType(seenTypes, emptyList(), packageState.getUserStateOrDefault(0),
131                 PackageUserState::class.starProjectedType)
132 
133         // Don't check empties for defaults since their collections will always be empty
134         visitType(seenTypes, emptyList(), PackageUserState.DEFAULT,
135                 PackageUserState::class.starProjectedType, enforceNonEmpty = false)
136 
137         // Check that some minimum number of functions were validated,
138         // in case the type checking breaks somehow
139         expect.that(seenTypes.size).isGreaterThan(10)
140     }
141 
142     /**
143      * Fill fields in [PackageState] and its children that are not filled by [AndroidPackageTest].
144      * Real objects and real invocations of the live APIs are necessary to ensure that the test
145      * mirrors real device behavior.
146      */
fillMissingDatanull147     private fun fillMissingData(pkgSetting: PackageSetting, pkg: PackageImpl) {
148         pkgSetting.addUsesLibraryFile("usesLibraryFile")
149 
150         val sharedLibraryDependency = listOf(SharedLibraryInfo(
151             "pathDependency",
152             "packageNameDependency",
153             listOf(tempFolder.newFile().path),
154             "nameDependency",
155             1,
156             0,
157             VersionedPackage("versionedPackage0Dependency", 1),
158             listOf(VersionedPackage("versionedPackage1Dependency", 2)),
159             emptyList(),
160             false
161         ))
162 
163         pkgSetting.addUsesLibraryInfo(SharedLibraryInfo(
164             "path",
165             "packageName",
166             listOf(tempFolder.newFile().path),
167             "name",
168             1,
169             0,
170             VersionedPackage("versionedPackage0", 1),
171             listOf(VersionedPackage("versionedPackage1", 2)),
172             sharedLibraryDependency,
173             false
174         ))
175         pkgSetting.addMimeTypes("mimeGroup", setOf("mimeType"))
176         pkgSetting.getOrCreateUserState(0).apply {
177             setEnabledComponents(ArraySet<String>().apply { add("com.test.EnabledComponent") })
178             setDisabledComponents(ArraySet<String>().apply { add("com.test.DisabledComponent") })
179             setSharedLibraryOverlayPaths("sharedLibrary",
180                 OverlayPaths.Builder().addApkPath("/test/overlay.apk").build())
181         }
182 
183         val property = PackageManager.Property("propertyName", 1, "com.test", null)
184         listOf(
185             pkg.activities,
186             pkg.receivers,
187             pkg.providers,
188             pkg.services,
189             pkg.instrumentations,
190             pkg.permissions,
191             pkg.permissionGroups
192         ).map { it.first() as ParsedComponentImpl }
193             .forEach {
194                 it.addIntent(ParsedIntentInfoImpl())
195                 it.addProperty(property)
196             }
197 
198         (pkg.activities.first() as ParsedActivityImpl).knownActivityEmbeddingCerts =
199             setOf("TESTEMBEDDINGCERT")
200 
201         (pkg.permissions.first() as ParsedPermissionImpl).knownCerts = setOf("TESTEMBEDDINGCERT")
202 
203         (pkg.providers.first() as ParsedProviderImpl).apply {
204             addPathPermission(PathPermission("pattern", PatternMatcher.PATTERN_LITERAL,
205                 "readPermission", "writerPermission"))
206             addUriPermissionPattern(PatternMatcher("*", PatternMatcher.PATTERN_LITERAL))
207         }
208 
209         (pkg.processes.values.first() as ParsedProcessImpl).apply {
210             deniedPermissions = setOf("deniedPermission")
211             putAppClassNameForPackage("package", "className")
212         }
213     }
214 
visitTypenull215     private fun visitType(
216         seenTypes: MutableSet<KType>,
217         parentChain: List<String>,
218         impl: Any,
219         type: KType,
220         enforceNonEmpty: Boolean = true
221     ) {
222         if (!seenTypes.add(type)) return
223         val kClass = type.classifier as KClass<*>
224         val qualifiedName = kClass.qualifiedName!!
225         if (IGNORED_TYPES.contains(qualifiedName)) return
226 
227         val newChain = parentChain + kClass.simpleName!!
228         val newChainText = newChain.joinToString()
229 
230         val filteredFunctions = kClass.memberFunctions
231             .filter {
232                 // Size 1 because the impl receiver counts as a parameter
233                 it.parameters.size == 1
234             }
235             .filterNot(IGNORED_FUNCTIONS::contains)
236 
237         filteredFunctions.filter { it.returnType.isSubtypeOf(collectionType) }
238                 .forEach {
239                     val collection = it.call(impl)
240                     if (collection as? MutableCollection<*> == null) {
241                         expect.withMessage("Method $newChainText ${it.name} cannot return null")
242                             .fail()
243                         return@forEach
244                     }
245 
246                     val value = try {
247                         if (AndroidPackage::getSplits == it) {
248                             // The base split is defined to never have any dependencies,
249                             // so force the visitor to use the split at index 1 instead of 0.
250                             collection.last()
251                         } else {
252                             collection.first()
253                         }
254                     } catch (e: Exception) {
255                         if (enforceNonEmpty) {
256                             expect.withMessage("Method $newChainText ${it.name} returns empty")
257                                 .that(e)
258                                 .isNull()
259                             return@forEach
260                         } else null
261                     }
262 
263                     if (value != null) {
264                         it.returnType.arguments.forEach {
265                             visitType(seenTypes, newChain, value, it.type!!)
266                         }
267                     }
268 
269                     // Must test clear last in case it works and actually clears the collection
270                     expectUnsupported(newChain, it) { collection.clear() }
271                 }
272         filteredFunctions.filter { it.returnType.isSubtypeOf(mapType) }
273                 .forEach {
274                     val map = it.call(impl)
275                     if (map as? MutableMap<*, *> == null) {
276                         expect.withMessage("Method $newChainText ${it.name} cannot return null")
277                             .fail()
278                         return@forEach
279                     }
280 
281                     val entry = try {
282                         map.entries.stream().findFirst().get()!!
283                     } catch (e: Exception) {
284                         expect.withMessage("Method $newChainText ${it.name} returns empty")
285                                 .that(e)
286                                 .isNull()
287                         return@forEach
288                     }
289 
290                     visitType(seenTypes, newChain, entry.key!!, it.returnType.arguments[0].type!!)
291                     visitType(seenTypes, newChain, entry.value!!, it.returnType.arguments[1].type!!)
292 
293                     // Must test clear last in case it works and actually clears the map
294                     expectUnsupported(newChain, it) { map.clear() }
295                 }
296     }
297 
expectUnsupportednull298     private fun expectUnsupported(
299             parentChain: List<String>,
300             function: KFunction<*>,
301             block: () -> Unit
302     ) {
303         val exception = try {
304             block()
305             null
306         } catch (e: UnsupportedOperationException) {
307             e
308         }
309 
310         expect.withMessage("Method ${parentChain.joinToString()} $function doesn't throw")
311                 .that(exception)
312                 .isNotNull()
313     }
314 }
315