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