diff options
| -rw-r--r-- | core/java/android/content/pm/flags.aconfig | 7 | ||||
| -rw-r--r-- | core/java/android/content/pm/parsing/ApkLite.java | 54 | ||||
| -rw-r--r-- | core/java/android/content/pm/parsing/ApkLiteParseUtils.java | 64 | ||||
| -rw-r--r-- | core/java/android/content/pm/parsing/PackageLite.java | 28 | ||||
| -rw-r--r-- | core/tests/coretests/Android.bp | 4 | ||||
| -rw-r--r-- | core/tests/coretests/AndroidTest.xml | 12 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java | 301 |
7 files changed, 462 insertions, 8 deletions
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index 03377445123e..6f70586881be 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -320,6 +320,13 @@ flag { } flag { + name: "sdk_dependency_installer" + namespace: "package_manager_service" + description: "Feature flag to enable installation of missing sdk dependency of app" + bug: "370822870" +} + +flag { name: "include_feature_flags_in_package_cacher" namespace: "package_manager_service" description: "Include feature flag status when determining hits or misses in PackageCacher." diff --git a/core/java/android/content/pm/parsing/ApkLite.java b/core/java/android/content/pm/parsing/ApkLite.java index 19a13db15b05..4220590a6943 100644 --- a/core/java/android/content/pm/parsing/ApkLite.java +++ b/core/java/android/content/pm/parsing/ApkLite.java @@ -28,6 +28,7 @@ import android.content.pm.VerifierInfo; import com.android.internal.util.CollectionUtils; import com.android.internal.util.DataClass; +import java.util.Collections; import java.util.List; import java.util.Set; @@ -141,6 +142,21 @@ public class ApkLite { private final boolean mIsSdkLibrary; /** + * List of SDK names used by this apk. + */ + private final @NonNull List<String> mUsesSdkLibraries; + + /** + * List of SDK major versions used by this apk. + */ + private final @Nullable long[] mUsesSdkLibrariesVersionsMajor; + + /** + * List of SDK certificates used by this apk. + */ + private final @Nullable String[][] mUsesSdkLibrariesCertDigests; + + /** * Indicates if this system app can be updated. */ private final boolean mUpdatableSystem; @@ -167,7 +183,9 @@ public class ApkLite { String requiredSystemPropertyName, String requiredSystemPropertyValue, int minSdkVersion, int targetSdkVersion, int rollbackDataPolicy, Set<String> requiredSplitTypes, Set<String> splitTypes, - boolean hasDeviceAdminReceiver, boolean isSdkLibrary, boolean updatableSystem, + boolean hasDeviceAdminReceiver, boolean isSdkLibrary, + List<String> usesSdkLibraries, long[] usesSdkLibrariesVersionsMajor, + String[][] usesSdkLibrariesCertDigests, boolean updatableSystem, String emergencyInstaller, List<SharedLibraryInfo> declaredLibraries) { mPath = path; mPackageName = packageName; @@ -202,6 +220,9 @@ public class ApkLite { mRollbackDataPolicy = rollbackDataPolicy; mHasDeviceAdminReceiver = hasDeviceAdminReceiver; mIsSdkLibrary = isSdkLibrary; + mUsesSdkLibraries = usesSdkLibraries; + mUsesSdkLibrariesVersionsMajor = usesSdkLibrariesVersionsMajor; + mUsesSdkLibrariesCertDigests = usesSdkLibrariesCertDigests; mUpdatableSystem = updatableSystem; mEmergencyInstaller = emergencyInstaller; mArchivedPackage = null; @@ -242,6 +263,9 @@ public class ApkLite { mRollbackDataPolicy = 0; mHasDeviceAdminReceiver = false; mIsSdkLibrary = false; + mUsesSdkLibraries = Collections.emptyList(); + mUsesSdkLibrariesVersionsMajor = null; + mUsesSdkLibrariesCertDigests = null; mUpdatableSystem = true; mEmergencyInstaller = null; mArchivedPackage = archivedPackage; @@ -555,6 +579,30 @@ public class ApkLite { } /** + * List of SDK names used by this apk. + */ + @DataClass.Generated.Member + public @NonNull List<String> getUsesSdkLibraries() { + return mUsesSdkLibraries; + } + + /** + * List of SDK major versions used by this apk. + */ + @DataClass.Generated.Member + public @Nullable long[] getUsesSdkLibrariesVersionsMajor() { + return mUsesSdkLibrariesVersionsMajor; + } + + /** + * List of SDK certificates used by this apk. + */ + @DataClass.Generated.Member + public @Nullable String[][] getUsesSdkLibrariesCertDigests() { + return mUsesSdkLibrariesCertDigests; + } + + /** * Indicates if this system app can be updated. */ @DataClass.Generated.Member @@ -584,10 +632,10 @@ public class ApkLite { } @DataClass.Generated( - time = 1728333566322L, + time = 1729247366948L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/content/pm/parsing/ApkLite.java", - inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\nprivate final boolean mIsSdkLibrary\nprivate final boolean mUpdatableSystem\nprivate final @android.annotation.Nullable java.lang.String mEmergencyInstaller\nprivate final @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> mDeclaredLibraries\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)") + inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\nprivate final boolean mIsSdkLibrary\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesSdkLibraries\nprivate final @android.annotation.Nullable long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.Nullable java.lang.String[][] mUsesSdkLibrariesCertDigests\nprivate final boolean mUpdatableSystem\nprivate final @android.annotation.Nullable java.lang.String mEmergencyInstaller\nprivate final @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> mDeclaredLibraries\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)") @Deprecated private void __metadata() {} diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java index 1a7f628ae61c..50d875845b2f 100644 --- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java @@ -32,6 +32,7 @@ import android.content.pm.parsing.result.ParseResult; import android.content.res.ApkAssets; import android.content.res.XmlResourceParser; import android.os.Build; +import android.os.SystemProperties; import android.os.Trace; import android.text.TextUtils; import android.util.ArrayMap; @@ -44,6 +45,7 @@ import com.android.internal.pm.pkg.component.flags.Flags; import com.android.internal.util.ArrayUtils; import libcore.io.IoUtils; +import libcore.util.HexEncoding; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -88,6 +90,7 @@ public class ApkLiteParseUtils { private static final String TAG_USES_SDK = "uses-sdk"; private static final String TAG_USES_SPLIT = "uses-split"; private static final String TAG_MANIFEST = "manifest"; + private static final String TAG_USES_SDK_LIBRARY = "uses-sdk-library"; private static final String TAG_SDK_LIBRARY = "sdk-library"; private static final int SDK_VERSION = Build.VERSION.SDK_INT; private static final String[] SDK_CODENAMES = Build.VERSION.ACTIVE_CODENAMES; @@ -460,6 +463,9 @@ public class ApkLiteParseUtils { boolean hasDeviceAdminReceiver = false; boolean isSdkLibrary = false; + List<String> usesSdkLibraries = new ArrayList<>(); + long[] usesSdkLibrariesVersionsMajor = new long[0]; + String[][] usesSdkLibrariesCertDigests = new String[0][0]; List<SharedLibraryInfo> declaredLibraries = new ArrayList<>(); // Only search the tree when the tag is the direct child of <manifest> tag @@ -523,6 +529,57 @@ public class ApkLiteParseUtils { hasDeviceAdminReceiver |= isDeviceAdminReceiver(parser, hasBindDeviceAdminPermission); break; + case TAG_USES_SDK_LIBRARY: + String usesSdkLibName = parser.getAttributeValue( + ANDROID_RES_NAMESPACE, "name"); + long usesSdkLibVersionMajor = parser.getAttributeIntValue( + ANDROID_RES_NAMESPACE, "versionMajor", -1); + String usesSdkCertDigest = parser.getAttributeValue( + ANDROID_RES_NAMESPACE, "certDigest"); + + if (usesSdkLibName == null || usesSdkLibName.isBlank() + || usesSdkLibVersionMajor < 0) { + return input.error( + PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, + "Bad uses-sdk-library declaration name: " + + usesSdkLibName + + " version: " + usesSdkLibVersionMajor); + } + + if (usesSdkLibraries.contains(usesSdkLibName)) { + return input.error( + PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, + "Bad uses-sdk-library declaration. Depending on" + + " multiple versions of SDK library: " + + usesSdkLibName); + } + + usesSdkLibraries.add(usesSdkLibName); + usesSdkLibrariesVersionsMajor = ArrayUtils.appendLong( + usesSdkLibrariesVersionsMajor, usesSdkLibVersionMajor, + /*allowDuplicates=*/ true); + + // We allow ":" delimiters in the SHA declaration as this is the format + // emitted by the certtool making it easy for developers to copy/paste. + // TODO(372862145): Add test for this replacement + usesSdkCertDigest = usesSdkCertDigest.replace(":", "").toLowerCase(); + + if ("".equals(usesSdkCertDigest)) { + // Test-only uses-sdk-library empty certificate digest override. + usesSdkCertDigest = SystemProperties.get( + "debug.pm.uses_sdk_library_default_cert_digest", ""); + // Validate the overridden digest. + try { + HexEncoding.decode(usesSdkCertDigest, false); + } catch (IllegalArgumentException e) { + usesSdkCertDigest = ""; + } + } + // TODO(372862145): Add support for multiple signer + usesSdkLibrariesCertDigests = ArrayUtils.appendElement(String[].class, + usesSdkLibrariesCertDigests, new String[]{usesSdkCertDigest}, + /*allowDuplicates=*/ true); + break; case TAG_SDK_LIBRARY: isSdkLibrary = true; // Mirrors ParsingPackageUtils#parseSdkLibrary until lite and full @@ -534,7 +591,7 @@ public class ApkLiteParseUtils { if (sdkLibName == null || sdkLibVersionMajor < 0) { return input.error( PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, - "Bad uses-sdk-library declaration name: " + sdkLibName + "Bad sdk-library declaration name: " + sdkLibName + " version: " + sdkLibVersionMajor); } declaredLibraries.add(new SharedLibraryInfo( @@ -694,8 +751,9 @@ public class ApkLiteParseUtils { overlayIsStatic, overlayPriority, requiredSystemPropertyName, requiredSystemPropertyValue, minSdkVersion, targetSdkVersion, rollbackDataPolicy, requiredSplitTypes.first, requiredSplitTypes.second, - hasDeviceAdminReceiver, isSdkLibrary, updatableSystem, emergencyInstaller, - declaredLibraries)); + hasDeviceAdminReceiver, isSdkLibrary, usesSdkLibraries, + usesSdkLibrariesVersionsMajor, usesSdkLibrariesCertDigests, + updatableSystem, emergencyInstaller, declaredLibraries)); } private static boolean isDeviceAdminReceiver( diff --git a/core/java/android/content/pm/parsing/PackageLite.java b/core/java/android/content/pm/parsing/PackageLite.java index 9a2ee7fe4cc6..79c597327f5a 100644 --- a/core/java/android/content/pm/parsing/PackageLite.java +++ b/core/java/android/content/pm/parsing/PackageLite.java @@ -115,6 +115,12 @@ public class PackageLite { */ private final boolean mIsSdkLibrary; + private final @NonNull List<String> mUsesSdkLibraries; + + private final @Nullable long[] mUsesSdkLibrariesVersionsMajor; + + private final @Nullable String[][] mUsesSdkLibrariesCertDigests; + private final @NonNull List<SharedLibraryInfo> mDeclaredLibraries; /** @@ -149,6 +155,9 @@ public class PackageLite { mSplitRequired = (baseApk.isSplitRequired() || hasAnyRequiredSplitTypes()); mProfileableByShell = baseApk.isProfileableByShell(); mIsSdkLibrary = baseApk.isIsSdkLibrary(); + mUsesSdkLibraries = baseApk.getUsesSdkLibraries(); + mUsesSdkLibrariesVersionsMajor = baseApk.getUsesSdkLibrariesVersionsMajor(); + mUsesSdkLibrariesCertDigests = baseApk.getUsesSdkLibrariesCertDigests(); mSplitNames = splitNames; mSplitTypes = splitTypes; mIsFeatureSplits = isFeatureSplits; @@ -438,6 +447,21 @@ public class PackageLite { } @DataClass.Generated.Member + public @NonNull List<String> getUsesSdkLibraries() { + return mUsesSdkLibraries; + } + + @DataClass.Generated.Member + public @Nullable long[] getUsesSdkLibrariesVersionsMajor() { + return mUsesSdkLibrariesVersionsMajor; + } + + @DataClass.Generated.Member + public @Nullable String[][] getUsesSdkLibrariesCertDigests() { + return mUsesSdkLibrariesCertDigests; + } + + @DataClass.Generated.Member public @NonNull List<SharedLibraryInfo> getDeclaredLibraries() { return mDeclaredLibraries; } @@ -451,10 +475,10 @@ public class PackageLite { } @DataClass.Generated( - time = 1728333569917L, + time = 1729248757933L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/content/pm/parsing/PackageLite.java", - inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mTargetSdk\nprivate final int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mProfileableByShell\nprivate final boolean mUseEmbeddedDex\nprivate final boolean mIsSdkLibrary\nprivate final @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> mDeclaredLibraries\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic java.util.List<java.lang.String> getAllApkPaths()\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)") + inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mTargetSdk\nprivate final int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mProfileableByShell\nprivate final boolean mUseEmbeddedDex\nprivate final boolean mIsSdkLibrary\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesSdkLibraries\nprivate final @android.annotation.Nullable long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.Nullable java.lang.String[][] mUsesSdkLibrariesCertDigests\nprivate final @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> mDeclaredLibraries\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic java.util.List<java.lang.String> getAllApkPaths()\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)") @Deprecated private void __metadata() {} diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 56e18e6c443f..aee1c3b2f28c 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -146,6 +146,10 @@ android_test { ":BinderProxyCountingTestService", ":AppThatUsesAppOps", ":AppThatCallsBinderMethods", + ":HelloWorldSdk1", + ":HelloWorldUsingSdk1AndSdk1", + ":HelloWorldUsingSdk1And2", + ":HelloWorldUsingSdkMalformedNegativeVersion", ], } diff --git a/core/tests/coretests/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml index 05ab783c01bb..3bc81724bc0a 100644 --- a/core/tests/coretests/AndroidTest.xml +++ b/core/tests/coretests/AndroidTest.xml @@ -29,6 +29,18 @@ <option name="test-file-name" value="AppThatCallsBinderMethods.apk" /> </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="cleanup" value="true"/> + <option name="push-file" key="HelloWorldUsingSdk1And2.apk" + value="/data/local/tmp/tests/coretests/pm/HelloWorldUsingSdk1And2.apk"/> + <option name="push-file" key="HelloWorldUsingSdk1AndSdk1.apk" + value="/data/local/tmp/tests/coretests/pm/HelloWorldUsingSdk1AndSdk1.apk"/> + <option name="push-file" key="HelloWorldUsingSdkMalformedNegativeVersion.apk" + value="/data/local/tmp/tests/coretests/pm/HelloWorldUsingSdkMalformedNegativeVersion.apk"/> + <option name="push-file" key="HelloWorldSdk1.apk" + value="/data/local/tmp/tests/coretests/pm/HelloWorldSdk1.apk"/> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <!-- TODO(b/254155965): Design a mechanism to finally remove this command. --> <option name="run-command" value="settings put global device_config_sync_disabled 0" /> diff --git a/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java b/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java new file mode 100644 index 000000000000..f9b481ffc1ef --- /dev/null +++ b/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.parsing; + +import static android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES; +import static android.content.pm.PackageManager.MATCH_STATIC_SHARED_AND_SDK_LIBRARIES; + +import static com.google.common.truth.Truth.assertThat; + +import android.annotation.SuppressLint; +import android.app.UiAutomation; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.Signature; +import android.content.pm.SigningInfo; +import android.content.pm.parsing.result.ParseResult; +import android.content.pm.parsing.result.ParseTypeImpl; +import android.os.FileUtils; +import android.os.ParcelFileDescriptor; +import android.platform.test.annotations.Presubmit; +import android.util.ArraySet; +import android.util.PackageUtils; + +import androidx.annotation.NonNull; +import androidx.test.InstrumentationRegistry; + +import com.android.internal.pm.parsing.PackageParser2; +import com.android.server.pm.pkg.AndroidPackage; + +import libcore.util.HexEncoding; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +@Presubmit +public class ApkLiteParseUtilsTest { + + @Rule + public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); + + private static final String PUSH_FILE_DIR = "/data/local/tmp/tests/coretests/pm/"; + private static final String TEST_APP_USING_SDK1_AND_SDK2 = "HelloWorldUsingSdk1And2.apk"; + private static final String TEST_APP_USING_SDK1_AND_SDK1 = "HelloWorldUsingSdk1AndSdk1.apk"; + private static final String TEST_APP_USING_SDK_MALFORMED_VERSION = + "HelloWorldUsingSdkMalformedNegativeVersion.apk"; + private static final String TEST_SDK1 = "HelloWorldSdk1.apk"; + private static final String TEST_SDK1_PACKAGE = "com.test.sdk1_1"; + private static final String TEST_SDK1_NAME = "com.test.sdk1"; + private static final long TEST_SDK1_VERSION = 1; + private static final String TEST_SDK2_NAME = "com.test.sdk2"; + private static final long TEST_SDK2_VERSION = 2; + + private final PackageParser2 mPackageParser2 = new PackageParser2( + null, null, null, new FakePackageParser2Callback()); + + private File mTmpDir = null; + + @Before + public void setUp() throws IOException { + mTmpDir = mTemporaryFolder.newFolder("DexMetadataHelperTest"); + } + + @After + public void tearDown() throws Exception { + setSystemProperty("debug.pm.uses_sdk_library_default_cert_digest", "invalid"); + uninstallPackageSilently(TEST_SDK1_NAME); + } + + @SuppressLint("CheckResult") + @Test + public void testParseApkLite_getUsesSdkLibrary() throws Exception { + File apkFile = copyApkToTmpDir(TEST_APP_USING_SDK1_AND_SDK2); + ParseResult<ApkLite> result = ApkLiteParseUtils + .parseApkLite(ParseTypeImpl.forDefaultParsing().reset(), apkFile, 0); + assertThat(result.isError()).isFalse(); + + ApkLite baseApk = result.getResult(); + assertThat(baseApk.getUsesSdkLibraries()).containsExactly(TEST_SDK1_NAME, TEST_SDK2_NAME); + assertThat(baseApk.getUsesSdkLibrariesVersionsMajor()).asList().containsExactly( + TEST_SDK1_VERSION, TEST_SDK2_VERSION + ); + for (String[] certDigests: baseApk.getUsesSdkLibrariesCertDigests()) { + assertThat(certDigests).asList().containsExactly(""); + } + } + + @SuppressLint("CheckResult") + @Test + public void testParseApkLite_getUsesSdkLibrary_overrideCertDigest() throws Exception { + installPackage(TEST_SDK1); + String certDigest = getPackageCertDigest(TEST_SDK1_PACKAGE); + overrideUsesSdkLibraryCertificateDigest(certDigest); + + File apkFile = copyApkToTmpDir(TEST_APP_USING_SDK1_AND_SDK2); + ParseResult<ApkLite> result = ApkLiteParseUtils + .parseApkLite(ParseTypeImpl.forDefaultParsing().reset(), apkFile, 0); + ApkLite baseApk = result.getResult(); + + String[][] liteCerts = baseApk.getUsesSdkLibrariesCertDigests(); + assertThat(liteCerts).isNotNull(); + for (String[] certDigests: liteCerts) { + assertThat(certDigests).asList().containsExactly(certDigest); + } + + // Same for package parser + AndroidPackage pkg = mPackageParser2.parsePackage(apkFile, 0, true).hideAsFinal(); + String[][] pkgCerts = pkg.getUsesSdkLibrariesCertDigests(); + assertThat(pkgCerts).isNotNull(); + for (int i = 0; i < liteCerts.length; i++) { + assertThat(liteCerts[i]).isEqualTo(pkgCerts[i]); + } + } + + + @SuppressLint("CheckResult") + @Test + public void testParseApkLite_getUsesSdkLibrary_sameAsPackageParser() throws Exception { + File apkFile = copyApkToTmpDir(TEST_APP_USING_SDK1_AND_SDK2); + ParseResult<ApkLite> result = ApkLiteParseUtils + .parseApkLite(ParseTypeImpl.forDefaultParsing().reset(), apkFile, 0); + assertThat(result.isError()).isFalse(); + ApkLite baseApk = result.getResult(); + + AndroidPackage pkg = mPackageParser2.parsePackage(apkFile, 0, true).hideAsFinal(); + assertThat(baseApk.getUsesSdkLibraries()) + .containsExactlyElementsIn(pkg.getUsesSdkLibraries()); + List<Long> versionsBoxed = Arrays.stream(pkg.getUsesSdkLibrariesVersionsMajor()).boxed() + .toList(); + assertThat(baseApk.getUsesSdkLibrariesVersionsMajor()).asList() + .containsExactlyElementsIn(versionsBoxed); + + String[][] liteCerts = baseApk.getUsesSdkLibrariesCertDigests(); + String[][] pkgCerts = pkg.getUsesSdkLibrariesCertDigests(); + for (int i = 0; i < liteCerts.length; i++) { + assertThat(liteCerts[i]).isEqualTo(pkgCerts[i]); + } + } + + @SuppressLint("CheckResult") + @Test + public void testParseApkLite_malformedUsesSdkLibrary_duplicate() throws Exception { + File apkFile = copyApkToTmpDir(TEST_APP_USING_SDK1_AND_SDK1); + ParseResult<ApkLite> result = ApkLiteParseUtils + .parseApkLite(ParseTypeImpl.forDefaultParsing().reset(), apkFile, 0); + assertThat(result.isError()).isTrue(); + assertThat(result.getErrorMessage()).contains("Bad uses-sdk-library declaration"); + assertThat(result.getErrorMessage()).contains( + "Depending on multiple versions of SDK library"); + } + + @SuppressLint("CheckResult") + @Test + public void testParseApkLite_malformedUsesSdkLibrary_missingVersion() throws Exception { + File apkFile = copyApkToTmpDir(TEST_APP_USING_SDK_MALFORMED_VERSION); + ParseResult<ApkLite> result = ApkLiteParseUtils + .parseApkLite(ParseTypeImpl.forDefaultParsing().reset(), apkFile, 0); + assertThat(result.isError()).isTrue(); + assertThat(result.getErrorMessage()).contains("Bad uses-sdk-library declaration"); + } + + private String getPackageCertDigest(String packageName) throws Exception { + getUiAutomation().adoptShellPermissionIdentity(); + try { + PackageInfo sdkPackageInfo = getPackageManager().getPackageInfo(packageName, + PackageManager.PackageInfoFlags.of( + GET_SIGNING_CERTIFICATES | MATCH_STATIC_SHARED_AND_SDK_LIBRARIES)); + SigningInfo signingInfo = sdkPackageInfo.signingInfo; + Signature[] signatures = + signingInfo != null ? signingInfo.getSigningCertificateHistory() : null; + byte[] digest = PackageUtils.computeSha256DigestBytes(signatures[0].toByteArray()); + return new String(HexEncoding.encode(digest)); + } finally { + getUiAutomation().dropShellPermissionIdentity(); + } + } + + private static PackageManager getPackageManager() { + return InstrumentationRegistry.getContext().getPackageManager(); + } + + /** + * SDK package is signed by build system. In theory we could try to extract the signature, + * and patch the app manifest. This property allows us to override in runtime, which is much + * easier. + */ + private void overrideUsesSdkLibraryCertificateDigest(String sdkCertDigest) throws Exception { + setSystemProperty("debug.pm.uses_sdk_library_default_cert_digest", sdkCertDigest); + } + + private void setSystemProperty(String name, String value) throws Exception { + assertThat(executeShellCommand("setprop " + name + " " + value)).isEmpty(); + } + + private static String executeShellCommand(String command) throws IOException { + final ParcelFileDescriptor stdout = getUiAutomation().executeShellCommand(command); + try (InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(stdout)) { + return readFullStream(inputStream); + } + } + + private static String readFullStream(InputStream inputStream) throws IOException { + ByteArrayOutputStream result = new ByteArrayOutputStream(); + writeFullStream(inputStream, result, -1); + return result.toString("UTF-8"); + } + + private static void writeFullStream(InputStream inputStream, OutputStream outputStream, + long expected) throws IOException { + byte[] buffer = new byte[1024]; + long total = 0; + int length; + while ((length = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, length); + total += length; + } + if (expected > 0) { + assertThat(expected).isEqualTo(total); + } + } + + private static UiAutomation getUiAutomation() { + return InstrumentationRegistry.getInstrumentation().getUiAutomation(); + } + + private File copyApkToTmpDir(String apkFileName) throws Exception { + File outFile = new File(mTmpDir, apkFileName); + String apkFilePath = PUSH_FILE_DIR + apkFileName; + File apkFile = new File(apkFilePath); + assertThat(apkFile.exists()).isTrue(); + try (InputStream is = new FileInputStream(apkFile)) { + FileUtils.copyToFileOrThrow(is, outFile); + } + return outFile; + } + + static String createApkPath(String baseName) { + return PUSH_FILE_DIR + baseName; + } + + /* Install for all the users */ + private void installPackage(String baseName) throws IOException { + File file = new File(createApkPath(baseName)); + assertThat(executeShellCommand("pm install -t -g " + file.getPath())) + .isEqualTo("Success\n"); + } + + private static String uninstallPackageSilently(String packageName) throws IOException { + return executeShellCommand("pm uninstall " + packageName); + } + + static class FakePackageParser2Callback extends PackageParser2.Callback { + + @Override + public boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo) { + return true; + } + + @Override + public boolean hasFeature(String feature) { + return true; + } + + @Override + public @NonNull Set<String> getHiddenApiWhitelistedApps() { + return new ArraySet<>(); + } + + @Override + public @NonNull Set<String> getInstallConstraintsAllowlist() { + return new ArraySet<>(); + } + } +} |