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