Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9f15ba2e authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Fix up discrepancies between v1 and v2 package parsing" into rvc-dev

parents 6a033d23 69738c75
Loading
Loading
Loading
Loading
+8 −1
Original line number Original line Diff line number Diff line
@@ -302,7 +302,14 @@ public class ParsedActivityUtils {
        }
        }


        String permission = array.getNonConfigurationString(permissionAttr, 0);
        String permission = array.getNonConfigurationString(permissionAttr, 0);
        if (isAlias) {
            // An alias will override permissions to allow referencing an Activity through its alias
            // without needing the original permission. If an alias needs the same permission,
            // it must be re-declared.
            activity.setPermission(permission);
        } else {
            activity.setPermission(permission != null ? permission : pkg.getPermission());
            activity.setPermission(permission != null ? permission : pkg.getPermission());
        }


        final boolean setExported = array.hasValue(exportedAttr);
        final boolean setExported = array.hasValue(exportedAttr);
        if (setExported) {
        if (setExported) {
+20 −9
Original line number Original line Diff line number Diff line
@@ -20,7 +20,10 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager;
import android.content.pm.parsing.ParsingPackage;
import android.content.pm.parsing.ParsingPackage;
import android.content.pm.parsing.ParsingPackageUtils;
import android.content.pm.parsing.ParsingUtils;
import android.content.pm.parsing.ParsingUtils;
import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseResult;
import android.content.res.Resources;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.content.res.XmlResourceParser;
@@ -29,9 +32,6 @@ import android.text.TextUtils;
import android.util.TypedValue;
import android.util.TypedValue;


import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting;
import android.content.pm.parsing.ParsingPackageUtils;
import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseResult;


/** @hide */
/** @hide */
class ParsedComponentUtils {
class ParsedComponentUtils {
@@ -60,16 +60,27 @@ class ParsedComponentUtils {
        component.setName(className);
        component.setName(className);
        component.setPackageName(packageName);
        component.setPackageName(packageName);


        if (useRoundIcon) {
        int roundIconVal = useRoundIcon ? array.getResourceId(roundIconAttr, 0) : 0;
            component.icon = array.getResourceId(roundIconAttr, 0);
        if (roundIconVal != 0) {
            component.icon = roundIconVal;
            component.nonLocalizedLabel = null;
        } else {
            int iconVal = array.getResourceId(iconAttr, 0);
            if (iconVal != 0) {
                component.icon = iconVal;
                component.nonLocalizedLabel = null;
            }
        }
        }


        if (component.icon == 0) {
        int logoVal = array.getResourceId(logoAttr, 0);
            component.icon = array.getResourceId(iconAttr, 0);
        if (logoVal != 0) {
            component.logo = logoVal;
        }
        }


        component.logo = array.getResourceId(logoAttr, 0);
        int bannerVal = array.getResourceId(bannerAttr, 0);
        component.banner = array.getResourceId(bannerAttr, 0);
        if (bannerVal != 0) {
            component.banner = bannerVal;
        }


        if (descriptionAttr != null) {
        if (descriptionAttr != null) {
            component.descriptionRes = array.getResourceId(descriptionAttr, 0);
            component.descriptionRes = array.getResourceId(descriptionAttr, 0);
+78 −7
Original line number Original line Diff line number Diff line
@@ -18,8 +18,8 @@ package com.android.server.pm.parsing


import android.content.pm.PackageManager
import android.content.pm.PackageManager
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.Presubmit
import androidx.test.filters.LargeTest
import com.google.common.truth.Expect
import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Rule
import org.junit.Rule
import org.junit.Test
import org.junit.Test


@@ -52,6 +52,7 @@ class AndroidPackageParsingEquivalenceTest : AndroidPackageParsingTestBase() {
        }
        }
    }
    }


    @LargeTest
    @Test
    @Test
    fun packageInfoEquality() {
    fun packageInfoEquality() {
        val flags = PackageManager.GET_ACTIVITIES or
        val flags = PackageManager.GET_ACTIVITIES or
@@ -65,7 +66,9 @@ class AndroidPackageParsingEquivalenceTest : AndroidPackageParsingTestBase() {
                PackageManager.GET_SERVICES or
                PackageManager.GET_SERVICES or
                PackageManager.GET_SHARED_LIBRARY_FILES or
                PackageManager.GET_SHARED_LIBRARY_FILES or
                PackageManager.GET_SIGNATURES or
                PackageManager.GET_SIGNATURES or
                PackageManager.GET_SIGNING_CERTIFICATES
                PackageManager.GET_SIGNING_CERTIFICATES or
                PackageManager.MATCH_DIRECT_BOOT_UNAWARE or
                PackageManager.MATCH_DIRECT_BOOT_AWARE
        val oldPackageInfo = oldPackages.asSequence().map { oldPackageInfo(it, flags) }
        val oldPackageInfo = oldPackages.asSequence().map { oldPackageInfo(it, flags) }
        val newPackageInfo = newPackages.asSequence().map { newPackageInfo(it, flags) }
        val newPackageInfo = newPackages.asSequence().map { newPackageInfo(it, flags) }


@@ -77,11 +80,79 @@ class AndroidPackageParsingEquivalenceTest : AndroidPackageParsingTestBase() {
            } else {
            } else {
                "$firstName | $secondName"
                "$firstName | $secondName"
            }
            }
            expect.withMessage("${it.first?.applicationInfo?.sourceDir} $packageName")

                    .that(it.first?.dumpToString())
            // Main components are asserted independently to separate the failures. Otherwise the
                    .isEqualTo(it.second?.dumpToString())
            // comparison would include every component in one massive string.

            val prefix = "${it.first?.applicationInfo?.sourceDir} $packageName"

            expect.withMessage("$prefix PackageInfo")
                    .that(it.second?.dumpToString())
                    .isEqualTo(it.first?.dumpToString())

            expect.withMessage("$prefix ApplicationInfo")
                    .that(it.second?.applicationInfo?.dumpToString())
                    .isEqualTo(it.first?.applicationInfo?.dumpToString())

            val firstActivityNames = it.first?.activities?.map { it.name } ?: emptyList()
            val secondActivityNames = it.second?.activities?.map { it.name } ?: emptyList()
            expect.withMessage("$prefix activities")
                    .that(secondActivityNames)
                    .containsExactlyElementsIn(firstActivityNames)
                    .inOrder()

            if (!it.first?.activities.isNullOrEmpty() && !it.second?.activities.isNullOrEmpty()) {
                it.first?.activities?.zip(it.second?.activities!!)?.forEach {
                    expect.withMessage("$prefix ${it.first.name}")
                            .that(it.second.dumpToString())
                            .isEqualTo(it.first.dumpToString())
                }
            }

            val firstReceiverNames = it.first?.receivers?.map { it.name } ?: emptyList()
            val secondReceiverNames = it.second?.receivers?.map { it.name } ?: emptyList()
            expect.withMessage("$prefix receivers")
                    .that(secondReceiverNames)
                    .containsExactlyElementsIn(firstReceiverNames)
                    .inOrder()

            if (!it.first?.receivers.isNullOrEmpty() && !it.second?.receivers.isNullOrEmpty()) {
                it.first?.receivers?.zip(it.second?.receivers!!)?.forEach {
                    expect.withMessage("$prefix ${it.first.name}")
                            .that(it.second.dumpToString())
                            .isEqualTo(it.first.dumpToString())
                }
            }
            }

            val firstProviderNames = it.first?.providers?.map { it.name } ?: emptyList()
            val secondProviderNames = it.second?.providers?.map { it.name } ?: emptyList()
            expect.withMessage("$prefix providers")
                    .that(secondProviderNames)
                    .containsExactlyElementsIn(firstProviderNames)
                    .inOrder()

            if (!it.first?.providers.isNullOrEmpty() && !it.second?.providers.isNullOrEmpty()) {
                it.first?.providers?.zip(it.second?.providers!!)?.forEach {
                    expect.withMessage("$prefix ${it.first.name}")
                            .that(it.second.dumpToString())
                            .isEqualTo(it.first.dumpToString())
                }
                }
            }
            }


            val firstServiceNames = it.first?.services?.map { it.name } ?: emptyList()
            val secondServiceNames = it.second?.services?.map { it.name } ?: emptyList()
            expect.withMessage("$prefix services")
                    .that(secondServiceNames)
                    .containsExactlyElementsIn(firstServiceNames)
                    .inOrder()


            if (!it.first?.services.isNullOrEmpty() && !it.second?.services.isNullOrEmpty()) {
                it.first?.services?.zip(it.second?.services!!)?.forEach {
                    expect.withMessage("$prefix ${it.first.name}")
                            .that(it.second.dumpToString())
                            .isEqualTo(it.first.dumpToString())
                }
            }
        }
    }
}
+137 −37
Original line number Original line Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.server.pm.parsing
import android.content.Context
import android.content.Context
import android.content.pm.ActivityInfo
import android.content.pm.ActivityInfo
import android.content.pm.ApplicationInfo
import android.content.pm.ApplicationInfo
import android.content.pm.ComponentInfo
import android.content.pm.ConfigurationInfo
import android.content.pm.ConfigurationInfo
import android.content.pm.FeatureInfo
import android.content.pm.FeatureInfo
import android.content.pm.InstrumentationInfo
import android.content.pm.InstrumentationInfo
@@ -27,6 +28,8 @@ import android.content.pm.PackageParser
import android.content.pm.PackageUserState
import android.content.pm.PackageUserState
import android.content.pm.PermissionInfo
import android.content.pm.PermissionInfo
import android.content.pm.ProviderInfo
import android.content.pm.ProviderInfo
import android.content.pm.ServiceInfo
import android.os.Bundle
import android.os.Debug
import android.os.Debug
import android.os.Environment
import android.os.Environment
import android.util.SparseArray
import android.util.SparseArray
@@ -38,8 +41,10 @@ import com.android.server.pm.pkg.PackageStateUnserialized
import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.whenever
import com.android.server.testutils.whenever
import org.junit.BeforeClass
import org.junit.BeforeClass
import org.mockito.Mockito
import org.mockito.Mockito.any
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.anyString
import org.mockito.Mockito.mock
import org.mockito.Mockito.mock
import java.io.File
import java.io.File


@@ -47,7 +52,7 @@ open class AndroidPackageParsingTestBase {


    companion object {
    companion object {


        private const val VERIFY_ALL_APKS = false
        private const val VERIFY_ALL_APKS = true


        /** For auditing memory usage differences */
        /** For auditing memory usage differences */
        private const val DUMP_HPROF_TO_EXTERNAL = false
        private const val DUMP_HPROF_TO_EXTERNAL = false
@@ -81,10 +86,14 @@ open class AndroidPackageParsingTestBase {
                            .filter { file -> file.name.endsWith(".apk") }
                            .filter { file -> file.name.endsWith(".apk") }
                            .toList()
                            .toList()
                }
                }
                .distinct()


        private val dummyUserState = mock(PackageUserState::class.java).apply {
        private val dummyUserState = mock(PackageUserState::class.java).apply {
            installed = true
            installed = true
            Mockito.`when`(isAvailable(anyInt())).thenReturn(true)
            whenever(isAvailable(anyInt())) { true }
            whenever(isMatch(any<ComponentInfo>(), anyInt())) { true }
            whenever(isMatch(anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(),
                    anyString(), anyInt())) { true }
        }
        }


        lateinit var oldPackages: List<PackageParser.Package>
        lateinit var oldPackages: List<PackageParser.Package>
@@ -145,6 +154,7 @@ open class AndroidPackageParsingTestBase {
        private fun mockPkgSetting(aPkg: AndroidPackage) = mockThrowOnUnmocked<PackageSetting> {
        private fun mockPkgSetting(aPkg: AndroidPackage) = mockThrowOnUnmocked<PackageSetting> {
            this.pkg = aPkg
            this.pkg = aPkg
            whenever(pkgState) { PackageStateUnserialized() }
            whenever(pkgState) { PackageStateUnserialized() }
            whenever(readUserState(anyInt())) { dummyUserState }
        }
        }
    }
    }


@@ -156,19 +166,10 @@ open class AndroidPackageParsingTestBase {
    // The following methods prepend "this." because @hide APIs can cause an IDE to auto-import
    // The following methods prepend "this." because @hide APIs can cause an IDE to auto-import
    // the R.attr constant instead of referencing the field in an attempt to fix the error.
    // the R.attr constant instead of referencing the field in an attempt to fix the error.


    /**
    // It's difficult to comment out a line in a triple quoted string, so this is used instead
     * Known exclusions:
    // to ignore specific fields. A comment is required to explain why a field was ignored.
     *   - [ApplicationInfo.credentialProtectedDataDir]
    private fun Any?.ignored(comment: String): String = "IGNORED"
     *   - [ApplicationInfo.dataDir]

     *   - [ApplicationInfo.deviceProtectedDataDir]
     *   - [ApplicationInfo.processName]
     *   - [ApplicationInfo.publicSourceDir]
     *   - [ApplicationInfo.scanPublicSourceDir]
     *   - [ApplicationInfo.scanSourceDir]
     *   - [ApplicationInfo.sourceDir]
     * These attributes used to be assigned post-package-parsing as part of another component,
     * but are now adjusted directly inside [PackageImpl].
     */
    protected fun ApplicationInfo.dumpToString() = """
    protected fun ApplicationInfo.dumpToString() = """
            appComponentFactory=${this.appComponentFactory}
            appComponentFactory=${this.appComponentFactory}
            backupAgentName=${this.backupAgentName}
            backupAgentName=${this.backupAgentName}
@@ -179,22 +180,31 @@ open class AndroidPackageParsingTestBase {
            compatibleWidthLimitDp=${this.compatibleWidthLimitDp}
            compatibleWidthLimitDp=${this.compatibleWidthLimitDp}
            compileSdkVersion=${this.compileSdkVersion}
            compileSdkVersion=${this.compileSdkVersion}
            compileSdkVersionCodename=${this.compileSdkVersionCodename}
            compileSdkVersionCodename=${this.compileSdkVersionCodename}
            credentialProtectedDataDir=${this.credentialProtectedDataDir
            .ignored("Deferred pre-R, but assigned immediately in R")}
            crossProfile=${this.crossProfile.ignored("Added in R")}
            dataDir=${this.dataDir.ignored("Deferred pre-R, but assigned immediately in R")}
            descriptionRes=${this.descriptionRes}
            descriptionRes=${this.descriptionRes}
            deviceProtectedDataDir=${this.deviceProtectedDataDir
            .ignored("Deferred pre-R, but assigned immediately in R")}
            enabled=${this.enabled}
            enabled=${this.enabled}
            enabledSetting=${this.enabledSetting}
            enabledSetting=${this.enabledSetting}
            flags=${Integer.toBinaryString(this.flags)}
            flags=${Integer.toBinaryString(this.flags)}
            fullBackupContent=${this.fullBackupContent}
            fullBackupContent=${this.fullBackupContent}
            gwpAsanMode=${this.gwpAsanMode.ignored("Added in R")}
            hiddenUntilInstalled=${this.hiddenUntilInstalled}
            hiddenUntilInstalled=${this.hiddenUntilInstalled}
            icon=${this.icon}
            icon=${this.icon}
            iconRes=${this.iconRes}
            iconRes=${this.iconRes}
            installLocation=${this.installLocation}
            installLocation=${this.installLocation}
            labelRes=${this.labelRes}
            largestWidthLimitDp=${this.largestWidthLimitDp}
            largestWidthLimitDp=${this.largestWidthLimitDp}
            logo=${this.logo}
            logo=${this.logo}
            longVersionCode=${this.longVersionCode}
            longVersionCode=${this.longVersionCode}
            ${"".ignored("mHiddenApiPolicy is a private field")}
            manageSpaceActivityName=${this.manageSpaceActivityName}
            manageSpaceActivityName=${this.manageSpaceActivityName}
            maxAspectRatio.compareTo(that.maxAspectRatio)=${this.maxAspectRatio}
            maxAspectRatio=${this.maxAspectRatio}
            metaData=${this.metaData}
            metaData=${this.metaData.dumpToString()}
            minAspectRatio.compareTo(that.minAspectRatio)=${this.minAspectRatio}
            minAspectRatio=${this.minAspectRatio}
            minSdkVersion=${this.minSdkVersion}
            minSdkVersion=${this.minSdkVersion}
            name=${this.name}
            name=${this.name}
            nativeLibraryDir=${this.nativeLibraryDir}
            nativeLibraryDir=${this.nativeLibraryDir}
@@ -206,18 +216,27 @@ open class AndroidPackageParsingTestBase {
            permission=${this.permission}
            permission=${this.permission}
            primaryCpuAbi=${this.primaryCpuAbi}
            primaryCpuAbi=${this.primaryCpuAbi}
            privateFlags=${Integer.toBinaryString(this.privateFlags)}
            privateFlags=${Integer.toBinaryString(this.privateFlags)}
            processName=${this.processName.ignored("Deferred pre-R, but assigned immediately in R")}
            publicSourceDir=${this.publicSourceDir
            .ignored("Deferred pre-R, but assigned immediately in R")}
            requiresSmallestWidthDp=${this.requiresSmallestWidthDp}
            requiresSmallestWidthDp=${this.requiresSmallestWidthDp}
            resourceDirs=${this.resourceDirs?.contentToString()}
            resourceDirs=${this.resourceDirs?.contentToString()}
            roundIconRes=${this.roundIconRes}
            roundIconRes=${this.roundIconRes}
            secondaryCpuAbi=${this.secondaryCpuAbi}
            scanPublicSourceDir=${this.scanPublicSourceDir
            secondaryNativeLibraryDir=${this.secondaryNativeLibraryDir}
            .ignored("Deferred pre-R, but assigned immediately in R")}
            scanSourceDir=${this.scanSourceDir
            .ignored("Deferred pre-R, but assigned immediately in R")}
            seInfo=${this.seInfo}
            seInfo=${this.seInfo}
            seInfoUser=${this.seInfoUser}
            seInfoUser=${this.seInfoUser}
            secondaryCpuAbi=${this.secondaryCpuAbi}
            secondaryNativeLibraryDir=${this.secondaryNativeLibraryDir}
            sharedLibraryFiles=${this.sharedLibraryFiles?.contentToString()}
            sharedLibraryFiles=${this.sharedLibraryFiles?.contentToString()}
            sharedLibraryInfos=${this.sharedLibraryInfos}
            sharedLibraryInfos=${this.sharedLibraryInfos}
            showUserIcon=${this.showUserIcon}
            showUserIcon=${this.showUserIcon}
            sourceDir=${this.sourceDir
            .ignored("Deferred pre-R, but assigned immediately in R")}
            splitClassLoaderNames=${this.splitClassLoaderNames?.contentToString()}
            splitClassLoaderNames=${this.splitClassLoaderNames?.contentToString()}
            splitDependencies=${this.splitDependencies}
            splitDependencies=${this.splitDependencies.dumpToString()}
            splitNames=${this.splitNames?.contentToString()}
            splitNames=${this.splitNames?.contentToString()}
            splitPublicSourceDirs=${this.splitPublicSourceDirs?.contentToString()}
            splitPublicSourceDirs=${this.splitPublicSourceDirs?.contentToString()}
            splitSourceDirs=${this.splitSourceDirs?.contentToString()}
            splitSourceDirs=${this.splitSourceDirs?.contentToString()}
@@ -226,8 +245,8 @@ open class AndroidPackageParsingTestBase {
            targetSdkVersion=${this.targetSdkVersion}
            targetSdkVersion=${this.targetSdkVersion}
            taskAffinity=${this.taskAffinity}
            taskAffinity=${this.taskAffinity}
            theme=${this.theme}
            theme=${this.theme}
            uid=${this.uid}
            uiOptions=${this.uiOptions}
            uiOptions=${this.uiOptions}
            uid=${this.uid}
            versionCode=${this.versionCode}
            versionCode=${this.versionCode}
            volumeUuid=${this.volumeUuid}
            volumeUuid=${this.volumeUuid}
            zygotePreloadName=${this.zygotePreloadName}
            zygotePreloadName=${this.zygotePreloadName}
@@ -241,19 +260,27 @@ open class AndroidPackageParsingTestBase {
            """.trimIndent()
            """.trimIndent()


    protected fun InstrumentationInfo.dumpToString() = """
    protected fun InstrumentationInfo.dumpToString() = """
            banner=${this.banner}
            credentialProtectedDataDir=${this.credentialProtectedDataDir}
            credentialProtectedDataDir=${this.credentialProtectedDataDir}
            dataDir=${this.dataDir}
            dataDir=${this.dataDir}
            deviceProtectedDataDir=${this.deviceProtectedDataDir}
            deviceProtectedDataDir=${this.deviceProtectedDataDir}
            functionalTest=${this.functionalTest}
            functionalTest=${this.functionalTest}
            handleProfiling=${this.handleProfiling}
            handleProfiling=${this.handleProfiling}
            icon=${this.icon}
            labelRes=${this.labelRes}
            logo=${this.logo}
            metaData=${this.metaData}
            name=${this.name}
            nativeLibraryDir=${this.nativeLibraryDir}
            nativeLibraryDir=${this.nativeLibraryDir}
            nonLocalizedLabel=${this.nonLocalizedLabel}
            packageName=${this.packageName}
            primaryCpuAbi=${this.primaryCpuAbi}
            primaryCpuAbi=${this.primaryCpuAbi}
            publicSourceDir=${this.publicSourceDir}
            publicSourceDir=${this.publicSourceDir}
            secondaryCpuAbi=${this.secondaryCpuAbi}
            secondaryCpuAbi=${this.secondaryCpuAbi}
            secondaryNativeLibraryDir=${this.secondaryNativeLibraryDir}
            secondaryNativeLibraryDir=${this.secondaryNativeLibraryDir}
            showUserIcon=${this.showUserIcon}
            sourceDir=${this.sourceDir}
            sourceDir=${this.sourceDir}
            splitDependencies=${this.splitDependencies.sequence()
            splitDependencies=${this.splitDependencies.dumpToString()}
            .map { it.first to it.second?.contentToString() }.joinToString()}
            splitNames=${this.splitNames?.contentToString()}
            splitNames=${this.splitNames?.contentToString()}
            splitPublicSourceDirs=${this.splitPublicSourceDirs?.contentToString()}
            splitPublicSourceDirs=${this.splitPublicSourceDirs?.contentToString()}
            splitSourceDirs=${this.splitSourceDirs?.contentToString()}
            splitSourceDirs=${this.splitSourceDirs?.contentToString()}
@@ -262,25 +289,40 @@ open class AndroidPackageParsingTestBase {
            """.trimIndent()
            """.trimIndent()


    protected fun ActivityInfo.dumpToString() = """
    protected fun ActivityInfo.dumpToString() = """
            banner=${this.banner}
            colorMode=${this.colorMode}
            colorMode=${this.colorMode}
            configChanges=${this.configChanges}
            configChanges=${this.configChanges}
            descriptionRes=${this.descriptionRes}
            directBootAware=${this.directBootAware}
            documentLaunchMode=${this.documentLaunchMode}
            documentLaunchMode=${this.documentLaunchMode}
            enabled=${this.enabled}
            exported=${this.exported}
            flags=${Integer.toBinaryString(this.flags)}
            flags=${Integer.toBinaryString(this.flags)}
            icon=${this.icon}
            labelRes=${this.labelRes}
            launchMode=${this.launchMode}
            launchMode=${this.launchMode}
            launchToken=${this.launchToken}
            launchToken=${this.launchToken}
            lockTaskLaunchMode=${this.lockTaskLaunchMode}
            lockTaskLaunchMode=${this.lockTaskLaunchMode}
            logo=${this.logo}
            maxAspectRatio=${this.maxAspectRatio}
            maxAspectRatio=${this.maxAspectRatio}
            maxRecents=${this.maxRecents}
            maxRecents=${this.maxRecents}
            metaData=${this.metaData.dumpToString()}
            minAspectRatio=${this.minAspectRatio}
            minAspectRatio=${this.minAspectRatio}
            name=${this.name}
            nonLocalizedLabel=${this.nonLocalizedLabel}
            packageName=${this.packageName}
            parentActivityName=${this.parentActivityName}
            parentActivityName=${this.parentActivityName}
            permission=${this.permission}
            permission=${this.permission}
            persistableMode=${this.persistableMode}
            persistableMode=${this.persistableMode.ignored("Could be dropped pre-R, fixed in R")}
            privateFlags=${Integer.toBinaryString(this.privateFlags)}
            privateFlags=${this.privateFlags}
            processName=${this.processName.ignored("Deferred pre-R, but assigned immediately in R")}
            requestedVrComponent=${this.requestedVrComponent}
            requestedVrComponent=${this.requestedVrComponent}
            resizeMode=${this.resizeMode}
            resizeMode=${this.resizeMode}
            rotationAnimation=${this.rotationAnimation}
            rotationAnimation=${this.rotationAnimation}
            screenOrientation=${this.screenOrientation}
            screenOrientation=${this.screenOrientation}
            showUserIcon=${this.showUserIcon}
            softInputMode=${this.softInputMode}
            softInputMode=${this.softInputMode}
            splitName=${this.splitName}
            targetActivity=${this.targetActivity}
            targetActivity=${this.targetActivity}
            taskAffinity=${this.taskAffinity}
            taskAffinity=${this.taskAffinity}
            theme=${this.theme}
            theme=${this.theme}
@@ -300,30 +342,77 @@ open class AndroidPackageParsingTestBase {


    protected fun PermissionInfo.dumpToString() = """
    protected fun PermissionInfo.dumpToString() = """
            backgroundPermission=${this.backgroundPermission}
            backgroundPermission=${this.backgroundPermission}
            banner=${this.banner}
            descriptionRes=${this.descriptionRes}
            descriptionRes=${this.descriptionRes}
            flags=${Integer.toBinaryString(this.flags)}
            flags=${Integer.toBinaryString(this.flags)}
            group=${this.group}
            group=${this.group}
            icon=${this.icon}
            labelRes=${this.labelRes}
            logo=${this.logo}
            metaData=${this.metaData.dumpToString()}
            name=${this.name}
            nonLocalizedDescription=${this.nonLocalizedDescription}
            nonLocalizedDescription=${this.nonLocalizedDescription}
            nonLocalizedLabel=${this.nonLocalizedLabel}
            packageName=${this.packageName}
            protectionLevel=${this.protectionLevel}
            protectionLevel=${this.protectionLevel}
            requestRes=${this.requestRes}
            requestRes=${this.requestRes}
            showUserIcon=${this.showUserIcon}
            """.trimIndent()
            """.trimIndent()


    protected fun ProviderInfo.dumpToString() = """
    protected fun ProviderInfo.dumpToString() = """
            applicationInfo=${this.applicationInfo.ignored("Already checked")}
            authority=${this.authority}
            authority=${this.authority}
            banner=${this.banner}
            descriptionRes=${this.descriptionRes}
            directBootAware=${this.directBootAware}
            enabled=${this.enabled}
            exported=${this.exported}
            flags=${Integer.toBinaryString(this.flags)}
            flags=${Integer.toBinaryString(this.flags)}
            forceUriPermissions=${this.forceUriPermissions}
            forceUriPermissions=${this.forceUriPermissions}
            grantUriPermissions=${this.grantUriPermissions}
            grantUriPermissions=${this.grantUriPermissions}
            icon=${this.icon}
            initOrder=${this.initOrder}
            initOrder=${this.initOrder}
            isSyncable=${this.isSyncable}
            isSyncable=${this.isSyncable}
            labelRes=${this.labelRes}
            logo=${this.logo}
            metaData=${this.metaData.dumpToString()}
            multiprocess=${this.multiprocess}
            multiprocess=${this.multiprocess}
            name=${this.name}
            nonLocalizedLabel=${this.nonLocalizedLabel}
            packageName=${this.packageName}
            pathPermissions=${this.pathPermissions?.joinToString {
            pathPermissions=${this.pathPermissions?.joinToString {
        "readPermission=${it.readPermission}\nwritePermission=${it.writePermission}"
        "readPermission=${it.readPermission}\nwritePermission=${it.writePermission}"
    }}
    }}
            processName=${this.processName.ignored("Deferred pre-R, but assigned immediately in R")}
            readPermission=${this.readPermission}
            readPermission=${this.readPermission}
            showUserIcon=${this.showUserIcon}
            splitName=${this.splitName}
            uriPermissionPatterns=${this.uriPermissionPatterns?.contentToString()}
            uriPermissionPatterns=${this.uriPermissionPatterns?.contentToString()}
            writePermission=${this.writePermission}
            writePermission=${this.writePermission}
            """.trimIndent()
            """.trimIndent()


    protected fun ServiceInfo.dumpToString() = """
            applicationInfo=${this.applicationInfo.ignored("Already checked")}
            banner=${this.banner}
            descriptionRes=${this.descriptionRes}
            directBootAware=${this.directBootAware}
            enabled=${this.enabled}
            exported=${this.exported}
            flags=${Integer.toBinaryString(this.flags)}
            icon=${this.icon}
            labelRes=${this.labelRes}
            logo=${this.logo}
            mForegroundServiceType"${this.mForegroundServiceType}
            metaData=${this.metaData.dumpToString()}
            name=${this.name}
            nonLocalizedLabel=${this.nonLocalizedLabel}
            packageName=${this.packageName}
            permission=${this.permission}
            processName=${this.processName.ignored("Deferred pre-R, but assigned immediately in R")}
            showUserIcon=${this.showUserIcon}
            splitName=${this.splitName}
            """.trimIndent()

    protected fun ConfigurationInfo.dumpToString() = """
    protected fun ConfigurationInfo.dumpToString() = """
            reqGlEsVersion=${this.reqGlEsVersion}
            reqGlEsVersion=${this.reqGlEsVersion}
            reqInputFeatures=${this.reqInputFeatures}
            reqInputFeatures=${this.reqInputFeatures}
@@ -333,8 +422,10 @@ open class AndroidPackageParsingTestBase {
            """.trimIndent()
            """.trimIndent()


    protected fun PackageInfo.dumpToString() = """
    protected fun PackageInfo.dumpToString() = """
            activities=${this.activities?.joinToString { it.dumpToString() }}
            activities=${this.activities?.joinToString { it.dumpToString() }
            applicationInfo=${this.applicationInfo.dumpToString()}
            .ignored("Checked separately in test")}
            applicationInfo=${this.applicationInfo.dumpToString()
            .ignored("Checked separately in test")}
            baseRevisionCode=${this.baseRevisionCode}
            baseRevisionCode=${this.baseRevisionCode}
            compileSdkVersion=${this.compileSdkVersion}
            compileSdkVersion=${this.compileSdkVersion}
            compileSdkVersionCodename=${this.compileSdkVersionCodename}
            compileSdkVersionCodename=${this.compileSdkVersionCodename}
@@ -356,15 +447,18 @@ open class AndroidPackageParsingTestBase {
            overlayTarget=${this.overlayTarget}
            overlayTarget=${this.overlayTarget}
            packageName=${this.packageName}
            packageName=${this.packageName}
            permissions=${this.permissions?.joinToString { it.dumpToString() }}
            permissions=${this.permissions?.joinToString { it.dumpToString() }}
            providers=${this.providers?.joinToString { it.dumpToString() }}
            providers=${this.providers?.joinToString { it.dumpToString() }
            receivers=${this.receivers?.joinToString { it.dumpToString() }}
            .ignored("Checked separately in test")}
            receivers=${this.receivers?.joinToString { it.dumpToString() }
            .ignored("Checked separately in test")}
            reqFeatures=${this.reqFeatures?.joinToString { it.dumpToString() }}
            reqFeatures=${this.reqFeatures?.joinToString { it.dumpToString() }}
            requestedPermissions=${this.requestedPermissions?.contentToString()}
            requestedPermissions=${this.requestedPermissions?.contentToString()}
            requestedPermissionsFlags=${this.requestedPermissionsFlags?.contentToString()}
            requestedPermissionsFlags=${this.requestedPermissionsFlags?.contentToString()}
            requiredAccountType=${this.requiredAccountType}
            requiredAccountType=${this.requiredAccountType}
            requiredForAllUsers=${this.requiredForAllUsers}
            requiredForAllUsers=${this.requiredForAllUsers}
            restrictedAccountType=${this.restrictedAccountType}
            restrictedAccountType=${this.restrictedAccountType}
            services=${this.services?.contentToString()}
            services=${this.services?.joinToString { it.dumpToString() }
            .ignored("Checked separately in test")}
            sharedUserId=${this.sharedUserId}
            sharedUserId=${this.sharedUserId}
            sharedUserLabel=${this.sharedUserLabel}
            sharedUserLabel=${this.sharedUserLabel}
            signatures=${this.signatures?.joinToString { it.toCharsString() }}
            signatures=${this.signatures?.joinToString { it.toCharsString() }}
@@ -378,11 +472,17 @@ open class AndroidPackageParsingTestBase {
            versionName=${this.versionName}
            versionName=${this.versionName}
            """.trimIndent()
            """.trimIndent()


    @Suppress("unused")
    private fun Bundle?.dumpToString() = this?.keySet()?.associateWith { get(it) }?.toString()
    private fun <T> SparseArray<T>.sequence(): Sequence<Pair<Int, T>> {

        var index = 0
    private fun <T> SparseArray<T>?.dumpToString(): String {
        return generateSequence {
        if (this == null) {
            index++.takeIf { it < size() }?.let { keyAt(it) to valueAt(index) }
            return "EMPTY"
        }

        val list = mutableListOf<Pair<Int, T>>()
        for (index in (0 until size())) {
            list += keyAt(index) to valueAt(index)
        }
        }
        return list.toString()
    }
    }
}
}