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.FrameworkParsingPackageUtils;
26 import android.content.pm.parsing.result.ParseResult;
27 import android.content.pm.parsing.result.ParseTypeImpl;
28 import android.text.TextUtils;
29 import android.util.ArrayMap;
30 import android.util.Log;
31 import android.util.Pair;
32 
33 import com.android.internal.annotations.VisibleForTesting;
34 
35 import java.io.File;
36 import java.util.ArrayList;
37 import java.util.Collection;
38 import java.util.List;
39 
40 /**
41  * This class scans a directory containing overlay APKs and extracts information from the overlay
42  * manifests by parsing the overlay manifests.
43  */
44 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
45 public class OverlayScanner {
46 
47     /** Represents information parsed from the manifest of an overlay. */
48     public static class ParsedOverlayInfo {
49         public final String packageName;
50         public final String targetPackageName;
51         public final int targetSdkVersion;
52         public final boolean isStatic;
53         public final int priority;
54         public final File path;
55         @Nullable public final File preInstalledApexPath;
56 
ParsedOverlayInfo(String packageName, String targetPackageName, int targetSdkVersion, boolean isStatic, int priority, File path, @Nullable File preInstalledApexPath)57         public ParsedOverlayInfo(String packageName, String targetPackageName,
58                 int targetSdkVersion, boolean isStatic, int priority, File path,
59                 @Nullable File preInstalledApexPath) {
60             this.packageName = packageName;
61             this.targetPackageName = targetPackageName;
62             this.targetSdkVersion = targetSdkVersion;
63             this.isStatic = isStatic;
64             this.priority = priority;
65             this.path = path;
66             this.preInstalledApexPath = preInstalledApexPath;
67         }
68 
69         @Override
toString()70         public String toString() {
71             return getClass().getSimpleName() + String.format("{packageName=%s"
72                             + ", targetPackageName=%s, targetSdkVersion=%s, isStatic=%s"
73                             + ", priority=%s, path=%s, preInstalledApexPath=%s}",
74                     packageName, targetPackageName, targetSdkVersion, isStatic,
75                     priority, path, preInstalledApexPath);
76         }
77 
78         /**
79          * Retrieves the path of the overlay in its original installation partition.
80          *
81          * An Overlay in an APEX, which is an update of an APEX in a given partition,
82          * is considered as belonging to that partition.
83          */
84         @NonNull
getOriginalPartitionPath()85         public File getOriginalPartitionPath() {
86             return preInstalledApexPath != null ? preInstalledApexPath : path;
87         }
88     }
89 
90     /**
91      * A map of overlay package name to the parsed manifest information of the latest version of
92      * the overlay.
93      */
94     private final ArrayMap<String, ParsedOverlayInfo> mParsedOverlayInfos = new ArrayMap<>();
95 
96     /**
97      * A list of pair<packageName, apkFile> which is excluded from the system based on the
98      * system property condition.
99      *
100      * @see #isExcludedOverlayPackage(String, OverlayConfigParser.OverlayPartition)
101      */
102     private final List<Pair<String, File>> mExcludedOverlayPackages = new ArrayList<>();
103 
104     /** Retrieves information parsed from the overlay with the package name. */
105     @Nullable
getParsedInfo(String packageName)106     public final ParsedOverlayInfo getParsedInfo(String packageName) {
107         return mParsedOverlayInfos.get(packageName);
108     }
109 
110     /** Retrieves all of the scanned overlays. */
111     @NonNull
getAllParsedInfos()112     final Collection<ParsedOverlayInfo> getAllParsedInfos() {
113         return mParsedOverlayInfos.values();
114     }
115 
116     /**
117      * Returns {@code true} if the given package name on the given overlay partition is an
118      * excluded overlay package.
119      * <p>
120      * An excluded overlay package declares overlay attributes of required system property in its
121      * manifest that do not match the corresponding values on the device.
122      */
isExcludedOverlayPackage(@onNull String packageName, @NonNull OverlayConfigParser.OverlayPartition overlayPartition)123     final boolean isExcludedOverlayPackage(@NonNull String packageName,
124             @NonNull OverlayConfigParser.OverlayPartition overlayPartition) {
125         for (int i = 0; i < mExcludedOverlayPackages.size(); i++) {
126             final Pair<String, File> pair = mExcludedOverlayPackages.get(i);
127             if (pair.first.equals(packageName)
128                     && overlayPartition.containsOverlay(pair.second)) {
129                 return true;
130             }
131         }
132         return false;
133     }
134 
135     /**
136      * Recursively searches the directory for overlay APKs. If an overlay is found with the same
137      * package name as a previously scanned overlay, the info of the new overlay will replace the
138      * info of the previously scanned overlay.
139      */
scanDir(File partitionOverlayDir)140     public void scanDir(File partitionOverlayDir) {
141         if (!partitionOverlayDir.exists() || !partitionOverlayDir.isDirectory()) {
142             return;
143         }
144 
145         if (!partitionOverlayDir.canRead()) {
146             Log.w(TAG, "Directory " + partitionOverlayDir + " cannot be read");
147             return;
148         }
149 
150         final File[] files = partitionOverlayDir.listFiles();
151         if (files == null) {
152             return;
153         }
154 
155         for (int i = 0; i < files.length; i++) {
156             final File f = files[i];
157             if (f.isDirectory()) {
158                 scanDir(f);
159             }
160 
161             if (!f.isFile() || !f.getPath().endsWith(".apk")) {
162                 continue;
163             }
164 
165             final ParsedOverlayInfo info = parseOverlayManifest(f, mExcludedOverlayPackages);
166             if (info == null) {
167                 continue;
168             }
169 
170             mParsedOverlayInfos.put(info.packageName, info);
171         }
172     }
173 
174     /**
175      * Extracts information about the overlay from its manifest. Adds the package name and apk file
176      * into the {@code outExcludedOverlayPackages} if the apk is excluded from the system based
177      * on the system property condition.
178      */
179     @VisibleForTesting
parseOverlayManifest(File overlayApk, List<Pair<String, File>> outExcludedOverlayPackages)180     public ParsedOverlayInfo parseOverlayManifest(File overlayApk,
181             List<Pair<String, File>> outExcludedOverlayPackages) {
182         final ParseTypeImpl input = ParseTypeImpl.forParsingWithoutPlatformCompat();
183         final ParseResult<ApkLite> ret = ApkLiteParseUtils.parseApkLite(input.reset(),
184                 overlayApk,
185                 FrameworkParsingPackageUtils.PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY);
186         if (ret.isError()) {
187             Log.w(TAG, "Got exception loading overlay.", ret.getException());
188             return null;
189         }
190         final ApkLite apkLite = ret.getResult();
191         if (apkLite.getTargetPackageName() == null) {
192             // Not an overlay package
193             return null;
194         }
195         final String propName = apkLite.getRequiredSystemPropertyName();
196         final String propValue = apkLite.getRequiredSystemPropertyValue();
197         if ((!TextUtils.isEmpty(propName) || !TextUtils.isEmpty(propValue))
198                 && !FrameworkParsingPackageUtils.checkRequiredSystemProperties(propName,
199                 propValue)) {
200             // The overlay package should be excluded. Adds it into the outExcludedOverlayPackages
201             // for overlay configuration parser to ignore it.
202             outExcludedOverlayPackages.add(Pair.create(apkLite.getPackageName(), overlayApk));
203             return null;
204         }
205         return new ParsedOverlayInfo(apkLite.getPackageName(), apkLite.getTargetPackageName(),
206                 apkLite.getTargetSdkVersion(), apkLite.isOverlayIsStatic(),
207                 apkLite.getOverlayPriority(), new File(apkLite.getPath()), null);
208     }
209 }
210