diff options
Diffstat (limited to 'android/content/pm/dex/DexMetadataHelper.java')
-rw-r--r-- | android/content/pm/dex/DexMetadataHelper.java | 230 |
1 files changed, 230 insertions, 0 deletions
diff --git a/android/content/pm/dex/DexMetadataHelper.java b/android/content/pm/dex/DexMetadataHelper.java new file mode 100644 index 00000000..5d10b882 --- /dev/null +++ b/android/content/pm/dex/DexMetadataHelper.java @@ -0,0 +1,230 @@ +/** + * 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 loose + * matching based on the extension ('foo.dm' will match 'foo.apk' or 'foo'). + * + * 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 = buildDexMetadataPathForFile(new File(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); + } + } + +} |