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