1 /* 2 * Copyright (C) 2019 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.Context 20 import android.content.pm.ActivityInfo 21 import android.content.pm.ApplicationInfo 22 import android.content.pm.ComponentInfo 23 import android.content.pm.ConfigurationInfo 24 import android.content.pm.FeatureInfo 25 import android.content.pm.InstrumentationInfo 26 import android.content.pm.PackageInfo 27 import android.content.pm.PackageParser 28 import android.content.pm.PackageUserState 29 import android.content.pm.PermissionInfo 30 import android.content.pm.ProviderInfo 31 import android.content.pm.ServiceInfo 32 import android.os.Bundle 33 import android.os.Debug 34 import android.os.Environment 35 import android.os.Process 36 import android.util.SparseArray 37 import androidx.test.platform.app.InstrumentationRegistry 38 import com.android.server.pm.PackageManagerService 39 import com.android.server.pm.PackageSetting 40 import com.android.server.pm.parsing.pkg.AndroidPackage 41 import com.android.server.pm.pkg.PackageStateUnserialized 42 import com.android.server.testutils.mockThrowOnUnmocked 43 import com.android.server.testutils.whenever 44 import org.junit.BeforeClass 45 import org.mockito.Mockito.any 46 import org.mockito.Mockito.anyBoolean 47 import org.mockito.Mockito.anyInt 48 import org.mockito.Mockito.anyString 49 import org.mockito.Mockito.mock 50 import java.io.File 51 52 open class AndroidPackageParsingTestBase { 53 54 companion object { 55 56 private const val VERIFY_ALL_APKS = true 57 58 // For auditing memory usage differences to /sdcard/AndroidPackageParsingTestBase.hprof 59 private const val DUMP_HPROF_TO_EXTERNAL = false 60 61 val context: Context = InstrumentationRegistry.getInstrumentation().getContext() 62 protected val packageParser = PackageParser().apply { 63 setOnlyCoreApps(false) 64 setDisplayMetrics(context.resources.displayMetrics) 65 setCallback { false /* hasFeature */ } 66 } 67 68 protected val packageParser2 = PackageParser2.forParsingFileWithDefaults() 69 70 /** 71 * It would be difficult to mock all possibilities, so just use the APKs on device. 72 * Unfortunately, this means the device must be bootable to verify potentially 73 * boot-breaking behavior. 74 */ 75 private val apks = mutableListOf(File(Environment.getRootDirectory(), "framework")) 76 .apply { 77 @Suppress("ConstantConditionIf") 78 if (VERIFY_ALL_APKS) { 79 this += (PackageManagerService.SYSTEM_PARTITIONS) 80 .flatMap { 81 listOfNotNull(it.privAppFolder, it.appFolder, it.overlayFolder) 82 } 83 } 84 } 85 .flatMap { 86 it.walkTopDown() 87 .filter { file -> file.name.endsWith(".apk") } 88 .toList() 89 } 90 .distinct() 91 92 private val dummyUserState = mock(PackageUserState::class.java).apply { 93 installed = true 94 whenever(isAvailable(anyInt())) { true } 95 whenever(isMatch(any<ComponentInfo>(), anyInt())) { true } 96 whenever(isMatch(anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), 97 anyString(), anyInt())) { true } 98 } 99 100 val oldPackages = mutableListOf<PackageParser.Package>() 101 102 val newPackages = mutableListOf<AndroidPackage>() 103 104 @Suppress("ConstantConditionIf") 105 @JvmStatic 106 @BeforeClass 107 fun setUpPackages() { 108 var uid = Process.FIRST_APPLICATION_UID 109 apks.mapNotNull { 110 try { 111 packageParser.parsePackage(it, PackageParser.PARSE_IS_SYSTEM_DIR, false) to 112 packageParser2.parsePackage(it, PackageParser.PARSE_IS_SYSTEM_DIR, 113 false) 114 } catch (ignored: Exception) { 115 // It is intentional that a failure of either call here will result in failing 116 // both. Having null on one side would mean nothing to compare. Due to the 117 // nature of presubmit, this may not be caused by the change being tested, so 118 // it's unhelpful to consider it a failure. Actual parsing issues will be 119 // reported by SystemPartitionParseTest in postsubmit. 120 null 121 } 122 }.forEach { (old, new) -> 123 // Assign an arbitrary UID. This is normally done after parsing completes, inside 124 // PackageManagerService, but since that code isn't run here, need to mock it. This 125 // is equivalent to what the system would assign. 126 old.applicationInfo.uid = uid 127 new.uid = uid 128 uid++ 129 130 oldPackages += old 131 newPackages += new.hideAsFinal() 132 } 133 134 if (DUMP_HPROF_TO_EXTERNAL) { 135 System.gc() 136 Environment.getExternalStorageDirectory() 137 .resolve( 138 "${AndroidPackageParsingTestBase::class.java.simpleName}.hprof") 139 .absolutePath 140 .run(Debug::dumpHprofData) 141 } 142 } 143 144 fun oldAppInfo( 145 pkg: PackageParser.Package, 146 flags: Int = 0, 147 userId: Int = 0 148 ): ApplicationInfo? { 149 return PackageParser.generateApplicationInfo(pkg, flags, dummyUserState, userId) 150 } 151 152 fun newAppInfo( 153 pkg: AndroidPackage, 154 flags: Int = 0, 155 userId: Int = 0 156 ): ApplicationInfo? { 157 return PackageInfoUtils.generateApplicationInfo(pkg, flags, dummyUserState, userId, 158 mockPkgSetting(pkg)) 159 } 160 161 fun newAppInfoWithoutState( 162 pkg: AndroidPackage, 163 flags: Int = 0, 164 userId: Int = 0 165 ): ApplicationInfo? { 166 return PackageInfoUtils.generateApplicationInfo(pkg, flags, dummyUserState, userId, 167 mockPkgSetting(pkg)) 168 } 169 170 fun oldPackageInfo(pkg: PackageParser.Package, flags: Int = 0): PackageInfo? { 171 return PackageParser.generatePackageInfo(pkg, intArrayOf(), flags, 5, 6, emptySet(), 172 dummyUserState) 173 } 174 175 fun newPackageInfo(pkg: AndroidPackage, flags: Int = 0): PackageInfo? { 176 return PackageInfoUtils.generate(pkg, intArrayOf(), flags, 5, 6, emptySet(), 177 dummyUserState, 0, mockPkgSetting(pkg)) 178 } 179 180 private fun mockPkgSetting(aPkg: AndroidPackage) = mockThrowOnUnmocked<PackageSetting> { 181 this.pkg = aPkg 182 this.appId = aPkg.uid 183 whenever(pkgState) { PackageStateUnserialized() } 184 whenever(readUserState(anyInt())) { dummyUserState } 185 } 186 } 187 188 // The following methods dump an exact set of fields from the object to compare, because 189 // 1. comprehensive equals/toStrings do not exist on all of the Info objects, and 190 // 2. the test must only verify fields that [PackageParser.Package] can actually fill, as 191 // no new functionality will be added to it. 192 193 // The following methods prepend "this." because @hide APIs can cause an IDE to auto-import 194 // the R.attr constant instead of referencing the field in an attempt to fix the error. 195 196 // It's difficult to comment out a line in a triple quoted string, so this is used instead 197 // to ignore specific fields. A comment is required to explain why a field was ignored. 198 private fun Any?.ignored(comment: String): String = "IGNORED" 199 200 protected fun ApplicationInfo.dumpToString() = """ 201 appComponentFactory=${this.appComponentFactory} 202 backupAgentName=${this.backupAgentName} 203 banner=${this.banner} 204 category=${this.category} 205 classLoaderName=${this.classLoaderName} 206 className=${this.className} 207 compatibleWidthLimitDp=${this.compatibleWidthLimitDp} 208 compileSdkVersion=${this.compileSdkVersion} 209 compileSdkVersionCodename=${this.compileSdkVersionCodename} 210 credentialProtectedDataDir=${this.credentialProtectedDataDir 211 .ignored("Deferred pre-R, but assigned immediately in R")} 212 crossProfile=${this.crossProfile.ignored("Added in R")} 213 dataDir=${this.dataDir.ignored("Deferred pre-R, but assigned immediately in R")} 214 descriptionRes=${this.descriptionRes} 215 deviceProtectedDataDir=${this.deviceProtectedDataDir 216 .ignored("Deferred pre-R, but assigned immediately in R")} 217 enabled=${this.enabled} 218 enabledSetting=${this.enabledSetting} 219 flags=${Integer.toBinaryString(this.flags)} 220 fullBackupContent=${this.fullBackupContent} 221 gwpAsanMode=${this.gwpAsanMode.ignored("Added in R")} 222 hiddenUntilInstalled=${this.hiddenUntilInstalled} 223 icon=${this.icon} 224 iconRes=${this.iconRes} 225 installLocation=${this.installLocation} 226 labelRes=${this.labelRes} 227 largestWidthLimitDp=${this.largestWidthLimitDp} 228 logo=${this.logo} 229 longVersionCode=${this.longVersionCode} 230 ${"".ignored("mHiddenApiPolicy is a private field")} 231 manageSpaceActivityName=${this.manageSpaceActivityName} 232 maxAspectRatio=${this.maxAspectRatio} 233 metaData=${this.metaData.dumpToString()} 234 minAspectRatio=${this.minAspectRatio} 235 minSdkVersion=${this.minSdkVersion} 236 name=${this.name} 237 nativeLibraryDir=${this.nativeLibraryDir} 238 nativeLibraryRootDir=${this.nativeLibraryRootDir} 239 nativeLibraryRootRequiresIsa=${this.nativeLibraryRootRequiresIsa} 240 networkSecurityConfigRes=${this.networkSecurityConfigRes} 241 nonLocalizedLabel=${ 242 // Per b/184574333, v1 mistakenly trimmed the label. v2 fixed this, but for test 243 // comparison, trim both so they can be matched. 244 this.nonLocalizedLabel?.trim() 245 } 246 packageName=${this.packageName} 247 permission=${this.permission} 248 primaryCpuAbi=${this.primaryCpuAbi} 249 privateFlags=${Integer.toBinaryString(this.privateFlags)} 250 processName=${this.processName.ignored("Deferred pre-R, but assigned immediately in R")} 251 publicSourceDir=${this.publicSourceDir 252 .ignored("Deferred pre-R, but assigned immediately in R")} 253 requiresSmallestWidthDp=${this.requiresSmallestWidthDp} 254 resourceDirs=${this.resourceDirs?.contentToString()} 255 overlayPaths=${this.overlayPaths?.contentToString()} 256 roundIconRes=${this.roundIconRes} 257 scanPublicSourceDir=${this.scanPublicSourceDir 258 .ignored("Deferred pre-R, but assigned immediately in R")} 259 scanSourceDir=${this.scanSourceDir 260 .ignored("Deferred pre-R, but assigned immediately in R")} 261 seInfo=${this.seInfo} 262 seInfoUser=${this.seInfoUser} 263 secondaryCpuAbi=${this.secondaryCpuAbi} 264 secondaryNativeLibraryDir=${this.secondaryNativeLibraryDir} 265 sharedLibraryFiles=${this.sharedLibraryFiles?.contentToString()} 266 sharedLibraryInfos=${this.sharedLibraryInfos} 267 showUserIcon=${this.showUserIcon} 268 sourceDir=${this.sourceDir 269 .ignored("Deferred pre-R, but assigned immediately in R")} 270 splitClassLoaderNames=${this.splitClassLoaderNames?.contentToString()} 271 splitDependencies=${this.splitDependencies.dumpToString()} 272 splitNames=${this.splitNames?.contentToString()} 273 splitPublicSourceDirs=${this.splitPublicSourceDirs?.contentToString()} 274 splitSourceDirs=${this.splitSourceDirs?.contentToString()} 275 storageUuid=${this.storageUuid} 276 targetSandboxVersion=${this.targetSandboxVersion} 277 targetSdkVersion=${this.targetSdkVersion} 278 taskAffinity=${this.taskAffinity} 279 theme=${this.theme} 280 uiOptions=${this.uiOptions} 281 uid=${this.uid} 282 versionCode=${this.versionCode} 283 volumeUuid=${this.volumeUuid} 284 zygotePreloadName=${this.zygotePreloadName} 285 """.trimIndent() 286 287 protected fun FeatureInfo.dumpToString() = """ 288 flags=${Integer.toBinaryString(this.flags)} 289 name=${this.name} 290 reqGlEsVersion=${this.reqGlEsVersion} 291 version=${this.version} 292 """.trimIndent() 293 294 protected fun InstrumentationInfo.dumpToString() = """ 295 banner=${this.banner} 296 credentialProtectedDataDir=${this.credentialProtectedDataDir} 297 dataDir=${this.dataDir} 298 deviceProtectedDataDir=${this.deviceProtectedDataDir} 299 functionalTest=${this.functionalTest} 300 handleProfiling=${this.handleProfiling} 301 icon=${this.icon} 302 labelRes=${this.labelRes} 303 logo=${this.logo} 304 metaData=${this.metaData} 305 name=${this.name} 306 nativeLibraryDir=${this.nativeLibraryDir} 307 nonLocalizedLabel=${ 308 // Per b/184574333, v1 mistakenly trimmed the label. v2 fixed this, but for test 309 // comparison, trim both so they can be matched. 310 this.nonLocalizedLabel?.trim() 311 } 312 packageName=${this.packageName} 313 primaryCpuAbi=${this.primaryCpuAbi} 314 publicSourceDir=${this.publicSourceDir} 315 secondaryCpuAbi=${this.secondaryCpuAbi} 316 secondaryNativeLibraryDir=${this.secondaryNativeLibraryDir} 317 showUserIcon=${this.showUserIcon} 318 sourceDir=${this.sourceDir} 319 splitDependencies=${this.splitDependencies.dumpToString()} 320 splitNames=${this.splitNames?.contentToString()} 321 splitPublicSourceDirs=${this.splitPublicSourceDirs?.contentToString()} 322 splitSourceDirs=${this.splitSourceDirs?.contentToString()} 323 targetPackage=${this.targetPackage} 324 targetProcesses=${this.targetProcesses} 325 """.trimIndent() 326 327 protected fun ActivityInfo.dumpToString() = """ 328 banner=${this.banner} 329 colorMode=${this.colorMode} 330 configChanges=${this.configChanges} 331 descriptionRes=${this.descriptionRes} 332 directBootAware=${this.directBootAware} 333 documentLaunchMode=${this.documentLaunchMode 334 .ignored("Update for fixing b/128526493 and the testing is no longer valid")} 335 enabled=${this.enabled} 336 exported=${this.exported} 337 flags=${Integer.toBinaryString(this.flags)} 338 icon=${this.icon} 339 labelRes=${this.labelRes} 340 launchMode=${this.launchMode} 341 launchToken=${this.launchToken} 342 lockTaskLaunchMode=${this.lockTaskLaunchMode} 343 logo=${this.logo} 344 maxRecents=${this.maxRecents} 345 metaData=${this.metaData.dumpToString()} 346 name=${this.name} 347 nonLocalizedLabel=${ 348 // Per b/184574333, v1 mistakenly trimmed the label. v2 fixed this, but for test 349 // comparison, trim both so they can be matched. 350 this.nonLocalizedLabel?.trim() 351 } 352 packageName=${this.packageName} 353 parentActivityName=${this.parentActivityName} 354 permission=${this.permission} 355 persistableMode=${this.persistableMode.ignored("Could be dropped pre-R, fixed in R")} 356 privateFlags=${ 357 // Strip flag added in S 358 this.privateFlags and (ActivityInfo.PRIVATE_FLAG_HOME_TRANSITION_SOUND.inv()) 359 } 360 processName=${this.processName.ignored("Deferred pre-R, but assigned immediately in R")} 361 requestedVrComponent=${this.requestedVrComponent} 362 resizeMode=${this.resizeMode} 363 rotationAnimation=${this.rotationAnimation} 364 screenOrientation=${this.screenOrientation} 365 showUserIcon=${this.showUserIcon} 366 softInputMode=${this.softInputMode} 367 splitName=${this.splitName} 368 targetActivity=${this.targetActivity} 369 taskAffinity=${this.taskAffinity} 370 theme=${this.theme} 371 uiOptions=${this.uiOptions} 372 windowLayout=${this.windowLayout?.dumpToString()} 373 """.trimIndent() 374 375 protected fun ActivityInfo.WindowLayout.dumpToString() = """ 376 gravity=${this.gravity} 377 height=${this.height} 378 heightFraction=${this.heightFraction} 379 minHeight=${this.minHeight} 380 minWidth=${this.minWidth} 381 width=${this.width} 382 widthFraction=${this.widthFraction} 383 """.trimIndent() 384 385 protected fun PermissionInfo.dumpToString() = """ 386 backgroundPermission=${this.backgroundPermission} 387 banner=${this.banner} 388 descriptionRes=${this.descriptionRes} 389 flags=${Integer.toBinaryString(this.flags)} 390 group=${this.group} 391 icon=${this.icon} 392 labelRes=${this.labelRes} 393 logo=${this.logo} 394 metaData=${this.metaData.dumpToString()} 395 name=${this.name} 396 nonLocalizedDescription=${this.nonLocalizedDescription} 397 nonLocalizedLabel=${ 398 // Per b/184574333, v1 mistakenly trimmed the label. v2 fixed this, but for test 399 // comparison, trim both so they can be matched. 400 this.nonLocalizedLabel?.trim() 401 } 402 packageName=${this.packageName} 403 protectionLevel=${this.protectionLevel} 404 requestRes=${this.requestRes} 405 showUserIcon=${this.showUserIcon} 406 """.trimIndent() 407 408 protected fun ProviderInfo.dumpToString() = """ 409 applicationInfo=${this.applicationInfo.ignored("Already checked")} 410 authority=${this.authority} 411 banner=${this.banner} 412 descriptionRes=${this.descriptionRes} 413 directBootAware=${this.directBootAware} 414 enabled=${this.enabled} 415 exported=${this.exported} 416 flags=${Integer.toBinaryString(this.flags)} 417 forceUriPermissions=${this.forceUriPermissions} 418 grantUriPermissions=${this.grantUriPermissions} 419 icon=${this.icon} 420 initOrder=${this.initOrder} 421 isSyncable=${this.isSyncable} 422 labelRes=${this.labelRes} 423 logo=${this.logo} 424 metaData=${this.metaData.dumpToString()} 425 multiprocess=${this.multiprocess} 426 name=${this.name} 427 nonLocalizedLabel=${ 428 // Per b/184574333, v1 mistakenly trimmed the label. v2 fixed this, but for test 429 // comparison, trim both so they can be matched. 430 this.nonLocalizedLabel?.trim() 431 } 432 packageName=${this.packageName} 433 pathPermissions=${this.pathPermissions?.joinToString { 434 "readPermission=${it.readPermission}\nwritePermission=${it.writePermission}" 435 }} 436 processName=${this.processName.ignored("Deferred pre-R, but assigned immediately in R")} 437 readPermission=${this.readPermission} 438 showUserIcon=${this.showUserIcon} 439 splitName=${this.splitName} 440 uriPermissionPatterns=${this.uriPermissionPatterns?.contentToString()} 441 writePermission=${this.writePermission} 442 """.trimIndent() 443 444 protected fun ServiceInfo.dumpToString() = """ 445 applicationInfo=${this.applicationInfo.ignored("Already checked")} 446 banner=${this.banner} 447 descriptionRes=${this.descriptionRes} 448 directBootAware=${this.directBootAware} 449 enabled=${this.enabled} 450 exported=${this.exported} 451 flags=${Integer.toBinaryString(this.flags)} 452 icon=${this.icon} 453 labelRes=${this.labelRes} 454 logo=${this.logo} 455 mForegroundServiceType"${this.mForegroundServiceType} 456 metaData=${this.metaData.dumpToString()} 457 name=${this.name} 458 nonLocalizedLabel=${ 459 // Per b/184574333, v1 mistakenly trimmed the label. v2 fixed this, but for test 460 // comparison, trim both so they can be matched. 461 this.nonLocalizedLabel?.trim() 462 } 463 packageName=${this.packageName} 464 permission=${this.permission} 465 processName=${this.processName.ignored("Deferred pre-R, but assigned immediately in R")} 466 showUserIcon=${this.showUserIcon} 467 splitName=${this.splitName} 468 """.trimIndent() 469 470 protected fun ConfigurationInfo.dumpToString() = """ 471 reqGlEsVersion=${this.reqGlEsVersion} 472 reqInputFeatures=${this.reqInputFeatures} 473 reqKeyboardType=${this.reqKeyboardType} 474 reqNavigation=${this.reqNavigation} 475 reqTouchScreen=${this.reqTouchScreen} 476 """.trimIndent() 477 478 protected fun PackageInfo.dumpToString() = """ 479 activities=${this.activities?.joinToString { it.dumpToString() } 480 .ignored("Checked separately in test")} 481 applicationInfo=${this.applicationInfo.dumpToString() 482 .ignored("Checked separately in test")} 483 baseRevisionCode=${this.baseRevisionCode} 484 compileSdkVersion=${this.compileSdkVersion} 485 compileSdkVersionCodename=${this.compileSdkVersionCodename} 486 configPreferences=${this.configPreferences?.joinToString { it.dumpToString() }} 487 coreApp=${this.coreApp} 488 featureGroups=${this.featureGroups?.joinToString { 489 it.features?.joinToString { featureInfo -> featureInfo.dumpToString() }.orEmpty() 490 }} 491 firstInstallTime=${this.firstInstallTime} 492 gids=${gids?.contentToString()} 493 installLocation=${this.installLocation} 494 instrumentation=${instrumentation?.joinToString { it.dumpToString() }} 495 isApex=${this.isApex} 496 isStub=${this.isStub} 497 lastUpdateTime=${this.lastUpdateTime} 498 mOverlayIsStatic=${this.mOverlayIsStatic} 499 overlayCategory=${this.overlayCategory} 500 overlayPriority=${this.overlayPriority} 501 overlayTarget=${this.overlayTarget} 502 packageName=${this.packageName} 503 permissions=${this.permissions?.joinToString { it.dumpToString() }} 504 providers=${this.providers?.joinToString { it.dumpToString() } 505 .ignored("Checked separately in test")} 506 receivers=${this.receivers?.joinToString { it.dumpToString() } 507 .ignored("Checked separately in test")} 508 reqFeatures=${this.reqFeatures?.joinToString { it.dumpToString() }} 509 requestedPermissions=${this.requestedPermissions?.contentToString()} 510 requestedPermissionsFlags=${ 511 this.requestedPermissionsFlags?.map { 512 // Newer flags are stripped 513 it and (PackageInfo.REQUESTED_PERMISSION_REQUIRED 514 or PackageInfo.REQUESTED_PERMISSION_GRANTED) 515 }?.joinToString() 516 } 517 requiredAccountType=${this.requiredAccountType} 518 requiredForAllUsers=${this.requiredForAllUsers} 519 restrictedAccountType=${this.restrictedAccountType} 520 services=${this.services?.joinToString { it.dumpToString() } 521 .ignored("Checked separately in test")} 522 sharedUserId=${this.sharedUserId} 523 sharedUserLabel=${this.sharedUserLabel} 524 signatures=${this.signatures?.joinToString { it.toCharsString() }} 525 signingInfo=${this.signingInfo?.signingCertificateHistory 526 ?.joinToString { it.toCharsString() }.orEmpty()} 527 splitNames=${this.splitNames?.contentToString()} 528 splitRevisionCodes=${this.splitRevisionCodes?.contentToString()} 529 targetOverlayableName=${this.targetOverlayableName} 530 versionCode=${this.versionCode} 531 versionCodeMajor=${this.versionCodeMajor} 532 versionName=${this.versionName} 533 """.trimIndent() 534 535 private fun Bundle?.dumpToString() = this?.keySet()?.associateWith { get(it) }?.toString() 536 537 private fun <T> SparseArray<T>?.dumpToString(): String { 538 if (this == null) { 539 return "EMPTY" 540 } 541 542 val list = mutableListOf<Pair<Int, T>>() 543 for (index in (0 until size())) { 544 list += keyAt(index) to valueAt(index) 545 } 546 return list.toString() 547 } 548 } 549