Loading core/java/android/content/pm/PackageManager.java +12 −0 Original line number Diff line number Diff line Loading @@ -1297,6 +1297,15 @@ public abstract class PackageManager { */ public static final int INSTALL_FAILED_INSTANT_APP_INVALID = -116; /** * Installation parse return code: this is passed in the * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the dex metadata file is invalid or * if there was no matching apk file for a dex metadata file. * * @hide */ public static final int INSTALL_FAILED_BAD_DEX_METADATA = -117; /** @hide */ @IntDef(flag = true, prefix = { "DELETE_" }, value = { DELETE_KEEP_DATA, Loading Loading @@ -5618,6 +5627,8 @@ public abstract class PackageManager { case INSTALL_FAILED_DUPLICATE_PERMISSION: return "INSTALL_FAILED_DUPLICATE_PERMISSION"; case INSTALL_FAILED_NO_MATCHING_ABIS: return "INSTALL_FAILED_NO_MATCHING_ABIS"; case INSTALL_FAILED_ABORTED: return "INSTALL_FAILED_ABORTED"; case INSTALL_FAILED_BAD_DEX_METADATA: return "INSTALL_FAILED_BAD_DEX_METADATA"; default: return Integer.toString(status); } } Loading Loading @@ -5662,6 +5673,7 @@ public abstract class PackageManager { case INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID: return PackageInstaller.STATUS_FAILURE_INVALID; case INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: return PackageInstaller.STATUS_FAILURE_INVALID; case INSTALL_PARSE_FAILED_MANIFEST_EMPTY: return PackageInstaller.STATUS_FAILURE_INVALID; case INSTALL_FAILED_BAD_DEX_METADATA: return PackageInstaller.STATUS_FAILURE_INVALID; case INSTALL_FAILED_INTERNAL_ERROR: return PackageInstaller.STATUS_FAILURE; case INSTALL_FAILED_USER_RESTRICTED: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE; case INSTALL_FAILED_DUPLICATE_PERMISSION: return PackageInstaller.STATUS_FAILURE_CONFLICT; Loading core/java/android/content/pm/PackageParser.java +4 −1 Original line number Diff line number Diff line Loading @@ -239,6 +239,9 @@ public class PackageParser { SAFE_BROADCASTS.add(Intent.ACTION_BOOT_COMPLETED); } /** @hide */ public static final String APK_FILE_EXTENSION = ".apk"; /** @hide */ public static class NewPermissionInfo { public final String name; Loading Loading @@ -613,7 +616,7 @@ public class PackageParser { } public static boolean isApkPath(String path) { return path.endsWith(".apk"); return path.endsWith(APK_FILE_EXTENSION); } /** Loading core/java/android/content/pm/dex/DexMetadataHelper.java 0 → 100644 +230 −0 Original line number Diff line number Diff line /** * Copyright 2018 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.dex; import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_DEX_METADATA; import static android.content.pm.PackageParser.APK_FILE_EXTENSION; import android.content.pm.PackageParser; import android.content.pm.PackageParser.PackageLite; import android.content.pm.PackageParser.PackageParserException; import android.util.ArrayMap; import android.util.jar.StrictJarFile; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; /** * Helper class used to compute and validate the location of dex metadata files. * * @hide */ public class DexMetadataHelper { private static final String DEX_METADATA_FILE_EXTENSION = ".dm"; private DexMetadataHelper() {} /** Return true if the given file is a dex metadata file. */ public static boolean isDexMetadataFile(File file) { return isDexMetadataPath(file.getName()); } /** Return true if the given path is a dex metadata path. */ private static boolean isDexMetadataPath(String path) { return path.endsWith(DEX_METADATA_FILE_EXTENSION); } /** * Return the size (in bytes) of all dex metadata files associated with the given package. */ public static long getPackageDexMetadataSize(PackageLite pkg) { long sizeBytes = 0; Collection<String> dexMetadataList = DexMetadataHelper.getPackageDexMetadata(pkg).values(); for (String dexMetadata : dexMetadataList) { sizeBytes += new File(dexMetadata).length(); } return sizeBytes; } /** * Search for the dex metadata file associated with the given target file. * If it exists, the method returns the dex metadata file; otherwise it returns null. * * Note that this performs a loose matching suitable to be used in the InstallerSession logic. * i.e. the method will attempt to match the {@code dmFile} regardless of {@code targetFile} * extension (e.g. 'foo.dm' will match 'foo' or 'foo.apk'). */ public static File findDexMetadataForFile(File targetFile) { String dexMetadataPath = buildDexMetadataPathForFile(targetFile); File dexMetadataFile = new File(dexMetadataPath); return dexMetadataFile.exists() ? dexMetadataFile : null; } /** * Return the dex metadata files for the given package as a map * [code path -> dex metadata path]. * * NOTE: involves I/O checks. */ public static Map<String, String> getPackageDexMetadata(PackageParser.Package pkg) { return buildPackageApkToDexMetadataMap(pkg.getAllCodePaths()); } /** * Return the dex metadata files for the given package as a map * [code path -> dex metadata path]. * * NOTE: involves I/O checks. */ private static Map<String, String> getPackageDexMetadata(PackageLite pkg) { return buildPackageApkToDexMetadataMap(pkg.getAllCodePaths()); } /** * Look up the dex metadata files for the given code paths building the map * [code path -> dex metadata]. * * For each code path (.apk) the method checks if a matching dex metadata file (.dm) exists. * If it does it adds the pair to the returned map. * * Note that this method will do a strict * matching based on the extension ('foo.dm' will only match 'foo.apk'). * * This should only be used for code paths extracted from a package structure after the naming * was enforced in the installer. */ private static Map<String, String> buildPackageApkToDexMetadataMap( List<String> codePaths) { ArrayMap<String, String> result = new ArrayMap<>(); for (int i = codePaths.size() - 1; i >= 0; i--) { String codePath = codePaths.get(i); String dexMetadataPath = buildDexMetadataPathForApk(codePath); if (Files.exists(Paths.get(dexMetadataPath))) { result.put(codePath, dexMetadataPath); } } return result; } /** * Return the dex metadata path associated with the given code path. * (replaces '.apk' extension with '.dm') * * @throws IllegalArgumentException if the code path is not an .apk. */ public static String buildDexMetadataPathForApk(String codePath) { if (!PackageParser.isApkPath(codePath)) { throw new IllegalStateException( "Corrupted package. Code path is not an apk " + codePath); } return codePath.substring(0, codePath.length() - APK_FILE_EXTENSION.length()) + DEX_METADATA_FILE_EXTENSION; } /** * Return the dex metadata path corresponding to the given {@code targetFile} using a loose * matching. * i.e. the method will attempt to match the {@code dmFile} regardless of {@code targetFile} * extension (e.g. 'foo.dm' will match 'foo' or 'foo.apk'). */ private static String buildDexMetadataPathForFile(File targetFile) { return PackageParser.isApkFile(targetFile) ? buildDexMetadataPathForApk(targetFile.getPath()) : targetFile.getPath() + DEX_METADATA_FILE_EXTENSION; } /** * Validate the dex metadata files installed for the given package. * * @throws PackageParserException in case of errors. */ public static void validatePackageDexMetadata(PackageParser.Package pkg) throws PackageParserException { Collection<String> apkToDexMetadataList = getPackageDexMetadata(pkg).values(); for (String dexMetadata : apkToDexMetadataList) { validateDexMetadataFile(dexMetadata); } } /** * Validate that the given file is a dex metadata archive. * This is just a sanity validation that the file is a zip archive. * * @throws PackageParserException if the file is not a .dm file. */ private static void validateDexMetadataFile(String dmaPath) throws PackageParserException { StrictJarFile jarFile = null; try { jarFile = new StrictJarFile(dmaPath, false, false); } catch (IOException e) { throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA, "Error opening " + dmaPath, e); } finally { if (jarFile != null) { try { jarFile.close(); } catch (IOException ignored) { } } } } /** * Validates that all dex metadata paths in the given list have a matching apk. * (for any foo.dm there should be either a 'foo' of a 'foo.apk' file). * If that's not the case it throws {@code IllegalStateException}. * * This is used to perform a basic sanity check during adb install commands. * (The installer does not support stand alone .dm files) */ public static void validateDexPaths(String[] paths) { ArrayList<String> apks = new ArrayList<>(); for (int i = 0; i < paths.length; i++) { if (PackageParser.isApkPath(paths[i])) { apks.add(paths[i]); } } ArrayList<String> unmatchedDmFiles = new ArrayList<>(); for (int i = 0; i < paths.length; i++) { String dmPath = paths[i]; if (isDexMetadataPath(dmPath)) { boolean valid = false; for (int j = apks.size() - 1; j >= 0; j--) { if (dmPath.equals(buildDexMetadataPathForFile(new File(apks.get(j))))) { valid = true; break; } } if (!valid) { unmatchedDmFiles.add(dmPath); } } } if (!unmatchedDmFiles.isEmpty()) { throw new IllegalStateException("Unmatched .dm files: " + unmatchedDmFiles); } } } core/java/com/android/internal/content/PackageHelper.java +4 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import android.content.pm.PackageInstaller.SessionParams; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageParser.PackageLite; import android.content.pm.dex.DexMetadataHelper; import android.os.Environment; import android.os.IBinder; import android.os.RemoteException; Loading Loading @@ -415,6 +416,9 @@ public class PackageHelper { sizeBytes += codeFile.length(); } // Include raw dex metadata files sizeBytes += DexMetadataHelper.getPackageDexMetadataSize(pkg); // Include all relevant native code sizeBytes += NativeLibraryHelper.sumNativeBinariesWithOverride(handle, abiOverride); Loading core/tests/coretests/apks/install-split-base/Android.mk 0 → 100644 +10 −0 Original line number Diff line number Diff line LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_PACKAGE_NAME := install_split_base include $(FrameworkCoreTests_BUILD_PACKAGE) No newline at end of file Loading
core/java/android/content/pm/PackageManager.java +12 −0 Original line number Diff line number Diff line Loading @@ -1297,6 +1297,15 @@ public abstract class PackageManager { */ public static final int INSTALL_FAILED_INSTANT_APP_INVALID = -116; /** * Installation parse return code: this is passed in the * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the dex metadata file is invalid or * if there was no matching apk file for a dex metadata file. * * @hide */ public static final int INSTALL_FAILED_BAD_DEX_METADATA = -117; /** @hide */ @IntDef(flag = true, prefix = { "DELETE_" }, value = { DELETE_KEEP_DATA, Loading Loading @@ -5618,6 +5627,8 @@ public abstract class PackageManager { case INSTALL_FAILED_DUPLICATE_PERMISSION: return "INSTALL_FAILED_DUPLICATE_PERMISSION"; case INSTALL_FAILED_NO_MATCHING_ABIS: return "INSTALL_FAILED_NO_MATCHING_ABIS"; case INSTALL_FAILED_ABORTED: return "INSTALL_FAILED_ABORTED"; case INSTALL_FAILED_BAD_DEX_METADATA: return "INSTALL_FAILED_BAD_DEX_METADATA"; default: return Integer.toString(status); } } Loading Loading @@ -5662,6 +5673,7 @@ public abstract class PackageManager { case INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID: return PackageInstaller.STATUS_FAILURE_INVALID; case INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: return PackageInstaller.STATUS_FAILURE_INVALID; case INSTALL_PARSE_FAILED_MANIFEST_EMPTY: return PackageInstaller.STATUS_FAILURE_INVALID; case INSTALL_FAILED_BAD_DEX_METADATA: return PackageInstaller.STATUS_FAILURE_INVALID; case INSTALL_FAILED_INTERNAL_ERROR: return PackageInstaller.STATUS_FAILURE; case INSTALL_FAILED_USER_RESTRICTED: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE; case INSTALL_FAILED_DUPLICATE_PERMISSION: return PackageInstaller.STATUS_FAILURE_CONFLICT; Loading
core/java/android/content/pm/PackageParser.java +4 −1 Original line number Diff line number Diff line Loading @@ -239,6 +239,9 @@ public class PackageParser { SAFE_BROADCASTS.add(Intent.ACTION_BOOT_COMPLETED); } /** @hide */ public static final String APK_FILE_EXTENSION = ".apk"; /** @hide */ public static class NewPermissionInfo { public final String name; Loading Loading @@ -613,7 +616,7 @@ public class PackageParser { } public static boolean isApkPath(String path) { return path.endsWith(".apk"); return path.endsWith(APK_FILE_EXTENSION); } /** Loading
core/java/android/content/pm/dex/DexMetadataHelper.java 0 → 100644 +230 −0 Original line number Diff line number Diff line /** * Copyright 2018 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.dex; import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_DEX_METADATA; import static android.content.pm.PackageParser.APK_FILE_EXTENSION; import android.content.pm.PackageParser; import android.content.pm.PackageParser.PackageLite; import android.content.pm.PackageParser.PackageParserException; import android.util.ArrayMap; import android.util.jar.StrictJarFile; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; /** * Helper class used to compute and validate the location of dex metadata files. * * @hide */ public class DexMetadataHelper { private static final String DEX_METADATA_FILE_EXTENSION = ".dm"; private DexMetadataHelper() {} /** Return true if the given file is a dex metadata file. */ public static boolean isDexMetadataFile(File file) { return isDexMetadataPath(file.getName()); } /** Return true if the given path is a dex metadata path. */ private static boolean isDexMetadataPath(String path) { return path.endsWith(DEX_METADATA_FILE_EXTENSION); } /** * Return the size (in bytes) of all dex metadata files associated with the given package. */ public static long getPackageDexMetadataSize(PackageLite pkg) { long sizeBytes = 0; Collection<String> dexMetadataList = DexMetadataHelper.getPackageDexMetadata(pkg).values(); for (String dexMetadata : dexMetadataList) { sizeBytes += new File(dexMetadata).length(); } return sizeBytes; } /** * Search for the dex metadata file associated with the given target file. * If it exists, the method returns the dex metadata file; otherwise it returns null. * * Note that this performs a loose matching suitable to be used in the InstallerSession logic. * i.e. the method will attempt to match the {@code dmFile} regardless of {@code targetFile} * extension (e.g. 'foo.dm' will match 'foo' or 'foo.apk'). */ public static File findDexMetadataForFile(File targetFile) { String dexMetadataPath = buildDexMetadataPathForFile(targetFile); File dexMetadataFile = new File(dexMetadataPath); return dexMetadataFile.exists() ? dexMetadataFile : null; } /** * Return the dex metadata files for the given package as a map * [code path -> dex metadata path]. * * NOTE: involves I/O checks. */ public static Map<String, String> getPackageDexMetadata(PackageParser.Package pkg) { return buildPackageApkToDexMetadataMap(pkg.getAllCodePaths()); } /** * Return the dex metadata files for the given package as a map * [code path -> dex metadata path]. * * NOTE: involves I/O checks. */ private static Map<String, String> getPackageDexMetadata(PackageLite pkg) { return buildPackageApkToDexMetadataMap(pkg.getAllCodePaths()); } /** * Look up the dex metadata files for the given code paths building the map * [code path -> dex metadata]. * * For each code path (.apk) the method checks if a matching dex metadata file (.dm) exists. * If it does it adds the pair to the returned map. * * Note that this method will do a strict * matching based on the extension ('foo.dm' will only match 'foo.apk'). * * This should only be used for code paths extracted from a package structure after the naming * was enforced in the installer. */ private static Map<String, String> buildPackageApkToDexMetadataMap( List<String> codePaths) { ArrayMap<String, String> result = new ArrayMap<>(); for (int i = codePaths.size() - 1; i >= 0; i--) { String codePath = codePaths.get(i); String dexMetadataPath = buildDexMetadataPathForApk(codePath); if (Files.exists(Paths.get(dexMetadataPath))) { result.put(codePath, dexMetadataPath); } } return result; } /** * Return the dex metadata path associated with the given code path. * (replaces '.apk' extension with '.dm') * * @throws IllegalArgumentException if the code path is not an .apk. */ public static String buildDexMetadataPathForApk(String codePath) { if (!PackageParser.isApkPath(codePath)) { throw new IllegalStateException( "Corrupted package. Code path is not an apk " + codePath); } return codePath.substring(0, codePath.length() - APK_FILE_EXTENSION.length()) + DEX_METADATA_FILE_EXTENSION; } /** * Return the dex metadata path corresponding to the given {@code targetFile} using a loose * matching. * i.e. the method will attempt to match the {@code dmFile} regardless of {@code targetFile} * extension (e.g. 'foo.dm' will match 'foo' or 'foo.apk'). */ private static String buildDexMetadataPathForFile(File targetFile) { return PackageParser.isApkFile(targetFile) ? buildDexMetadataPathForApk(targetFile.getPath()) : targetFile.getPath() + DEX_METADATA_FILE_EXTENSION; } /** * Validate the dex metadata files installed for the given package. * * @throws PackageParserException in case of errors. */ public static void validatePackageDexMetadata(PackageParser.Package pkg) throws PackageParserException { Collection<String> apkToDexMetadataList = getPackageDexMetadata(pkg).values(); for (String dexMetadata : apkToDexMetadataList) { validateDexMetadataFile(dexMetadata); } } /** * Validate that the given file is a dex metadata archive. * This is just a sanity validation that the file is a zip archive. * * @throws PackageParserException if the file is not a .dm file. */ private static void validateDexMetadataFile(String dmaPath) throws PackageParserException { StrictJarFile jarFile = null; try { jarFile = new StrictJarFile(dmaPath, false, false); } catch (IOException e) { throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA, "Error opening " + dmaPath, e); } finally { if (jarFile != null) { try { jarFile.close(); } catch (IOException ignored) { } } } } /** * Validates that all dex metadata paths in the given list have a matching apk. * (for any foo.dm there should be either a 'foo' of a 'foo.apk' file). * If that's not the case it throws {@code IllegalStateException}. * * This is used to perform a basic sanity check during adb install commands. * (The installer does not support stand alone .dm files) */ public static void validateDexPaths(String[] paths) { ArrayList<String> apks = new ArrayList<>(); for (int i = 0; i < paths.length; i++) { if (PackageParser.isApkPath(paths[i])) { apks.add(paths[i]); } } ArrayList<String> unmatchedDmFiles = new ArrayList<>(); for (int i = 0; i < paths.length; i++) { String dmPath = paths[i]; if (isDexMetadataPath(dmPath)) { boolean valid = false; for (int j = apks.size() - 1; j >= 0; j--) { if (dmPath.equals(buildDexMetadataPathForFile(new File(apks.get(j))))) { valid = true; break; } } if (!valid) { unmatchedDmFiles.add(dmPath); } } } if (!unmatchedDmFiles.isEmpty()) { throw new IllegalStateException("Unmatched .dm files: " + unmatchedDmFiles); } } }
core/java/com/android/internal/content/PackageHelper.java +4 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import android.content.pm.PackageInstaller.SessionParams; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageParser.PackageLite; import android.content.pm.dex.DexMetadataHelper; import android.os.Environment; import android.os.IBinder; import android.os.RemoteException; Loading Loading @@ -415,6 +416,9 @@ public class PackageHelper { sizeBytes += codeFile.length(); } // Include raw dex metadata files sizeBytes += DexMetadataHelper.getPackageDexMetadataSize(pkg); // Include all relevant native code sizeBytes += NativeLibraryHelper.sumNativeBinariesWithOverride(handle, abiOverride); Loading
core/tests/coretests/apks/install-split-base/Android.mk 0 → 100644 +10 −0 Original line number Diff line number Diff line LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_PACKAGE_NAME := install_split_base include $(FrameworkCoreTests_BUILD_PACKAGE) No newline at end of file