1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.content.om; 18 19 import static com.android.internal.content.om.OverlayConfig.TAG; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.content.pm.parsing.ApkLite; 24 import android.content.pm.parsing.ApkLiteParseUtils; 25 import android.content.pm.parsing.result.ParseResult; 26 import android.content.pm.parsing.result.ParseTypeImpl; 27 import android.util.ArrayMap; 28 import android.util.Log; 29 30 import com.android.internal.annotations.VisibleForTesting; 31 32 import java.io.File; 33 import java.util.Collection; 34 35 /** 36 * This class scans a directory containing overlay APKs and extracts information from the overlay 37 * manifests by parsing the overlay manifests. 38 */ 39 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) 40 public class OverlayScanner { 41 42 /** Represents information parsed from the manifest of an overlay. */ 43 public static class ParsedOverlayInfo { 44 public final String packageName; 45 public final String targetPackageName; 46 public final int targetSdkVersion; 47 public final boolean isStatic; 48 public final int priority; 49 public final File path; 50 ParsedOverlayInfo(String packageName, String targetPackageName, int targetSdkVersion, boolean isStatic, int priority, File path)51 public ParsedOverlayInfo(String packageName, String targetPackageName, 52 int targetSdkVersion, boolean isStatic, int priority, File path) { 53 this.packageName = packageName; 54 this.targetPackageName = targetPackageName; 55 this.targetSdkVersion = targetSdkVersion; 56 this.isStatic = isStatic; 57 this.priority = priority; 58 this.path = path; 59 } 60 61 @Override toString()62 public String toString() { 63 return getClass().getSimpleName() + String.format("{packageName=%s" 64 + ", targetPackageName=%s, targetSdkVersion=%s, isStatic=%s" 65 + ", priority=%s, path=%s}", 66 packageName, targetPackageName, targetSdkVersion, isStatic, priority, path); 67 } 68 } 69 70 /** 71 * A map of overlay package name to the parsed manifest information of the latest version of 72 * the overlay. 73 */ 74 private final ArrayMap<String, ParsedOverlayInfo> mParsedOverlayInfos = new ArrayMap<>(); 75 76 /** Retrieves information parsed from the overlay with the package name. */ 77 @Nullable getParsedInfo(String packageName)78 public final ParsedOverlayInfo getParsedInfo(String packageName) { 79 return mParsedOverlayInfos.get(packageName); 80 } 81 82 /** Retrieves all of the scanned overlays. */ 83 @NonNull getAllParsedInfos()84 final Collection<ParsedOverlayInfo> getAllParsedInfos() { 85 return mParsedOverlayInfos.values(); 86 } 87 88 /** 89 * Recursively searches the directory for overlay APKs. If an overlay is found with the same 90 * package name as a previously scanned overlay, the info of the new overlay will replace the 91 * info of the previously scanned overlay. 92 */ scanDir(File partitionOverlayDir)93 public void scanDir(File partitionOverlayDir) { 94 if (!partitionOverlayDir.exists() || !partitionOverlayDir.isDirectory()) { 95 return; 96 } 97 98 if (!partitionOverlayDir.canRead()) { 99 Log.w(TAG, "Directory " + partitionOverlayDir + " cannot be read"); 100 return; 101 } 102 103 final File[] files = partitionOverlayDir.listFiles(); 104 if (files == null) { 105 return; 106 } 107 108 for (int i = 0; i < files.length; i++) { 109 final File f = files[i]; 110 if (f.isDirectory()) { 111 scanDir(f); 112 } 113 114 if (!f.isFile() || !f.getPath().endsWith(".apk")) { 115 continue; 116 } 117 118 final ParsedOverlayInfo info = parseOverlayManifest(f); 119 if (info == null) { 120 continue; 121 } 122 123 mParsedOverlayInfos.put(info.packageName, info); 124 } 125 } 126 127 /** Extracts information about the overlay from its manifest. */ 128 @VisibleForTesting parseOverlayManifest(File overlayApk)129 public ParsedOverlayInfo parseOverlayManifest(File overlayApk) { 130 final ParseTypeImpl input = ParseTypeImpl.forParsingWithoutPlatformCompat(); 131 final ParseResult<ApkLite> ret = ApkLiteParseUtils.parseApkLite(input.reset(), 132 overlayApk, /* flags */ 0); 133 if (ret.isError()) { 134 Log.w(TAG, "Got exception loading overlay.", ret.getException()); 135 return null; 136 } 137 final ApkLite apkLite = ret.getResult(); 138 return apkLite.getTargetPackageName() == null ? null : 139 new ParsedOverlayInfo(apkLite.getPackageName(), apkLite.getTargetPackageName(), 140 apkLite.getTargetSdkVersion(), apkLite.isOverlayIsStatic(), 141 apkLite.getOverlayPriority(), new File(apkLite.getPath())); 142 } 143 } 144