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