• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.pm.PackagePartitions;
22 import android.content.pm.parsing.ParsingPackageRead;
23 import android.os.Build;
24 import android.os.Trace;
25 import android.util.ArrayMap;
26 import android.util.Log;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 import com.android.internal.content.om.OverlayConfigParser.OverlayPartition;
30 import com.android.internal.content.om.OverlayConfigParser.ParsedConfiguration;
31 import com.android.internal.content.om.OverlayScanner.ParsedOverlayInfo;
32 import com.android.internal.util.Preconditions;
33 
34 import java.io.File;
35 import java.util.ArrayList;
36 import java.util.Arrays;
37 import java.util.Comparator;
38 import java.util.function.BiConsumer;
39 import java.util.function.Supplier;
40 
41 /**
42  * Responsible for reading overlay configuration files and handling queries of overlay mutability,
43  * default-enabled state, and priority.
44  *
45  * @see OverlayConfigParser
46  */
47 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
48 public class OverlayConfig {
49     static final String TAG = "OverlayConfig";
50 
51     // The default priority of an overlay that has not been configured. Overlays with default
52     // priority have a higher precedence than configured overlays.
53     @VisibleForTesting
54     public static final int DEFAULT_PRIORITY = Integer.MAX_VALUE;
55 
56     @VisibleForTesting
57     public static final class Configuration {
58         @Nullable
59         public final ParsedConfiguration parsedConfig;
60 
61         public final int configIndex;
62 
Configuration(@ullable ParsedConfiguration parsedConfig, int configIndex)63         public Configuration(@Nullable ParsedConfiguration parsedConfig, int configIndex) {
64             this.parsedConfig = parsedConfig;
65             this.configIndex = configIndex;
66         }
67     }
68 
69     /**
70      * Interface for providing information on scanned packages.
71      * TODO(147840005): Remove this when android:isStatic and android:priority are fully deprecated
72      */
73     public interface PackageProvider {
74 
75         /** Performs the given action for each package. */
forEachPackage(BiConsumer<ParsingPackageRead, Boolean> p)76         void forEachPackage(BiConsumer<ParsingPackageRead, Boolean> p);
77     }
78 
79     private static final Comparator<ParsedConfiguration> sStaticOverlayComparator = (c1, c2) -> {
80         final ParsedOverlayInfo o1 = c1.parsedInfo;
81         final ParsedOverlayInfo o2 = c2.parsedInfo;
82         Preconditions.checkArgument(o1.isStatic && o2.isStatic,
83                 "attempted to sort non-static overlay");
84 
85         if (!o1.targetPackageName.equals(o2.targetPackageName)) {
86             return o1.targetPackageName.compareTo(o2.targetPackageName);
87         }
88 
89         final int comparedPriority = o1.priority - o2.priority;
90         return comparedPriority == 0 ? o1.path.compareTo(o2.path) : comparedPriority;
91     };
92 
93     // Map of overlay package name to configured overlay settings
94     private final ArrayMap<String, Configuration> mConfigurations = new ArrayMap<>();
95 
96     // Singleton instance only assigned in system server
97     private static OverlayConfig sInstance;
98 
99     @VisibleForTesting
OverlayConfig(@ullable File rootDirectory, @Nullable Supplier<OverlayScanner> scannerFactory, @Nullable PackageProvider packageProvider)100     public OverlayConfig(@Nullable File rootDirectory,
101             @Nullable Supplier<OverlayScanner> scannerFactory,
102             @Nullable PackageProvider packageProvider) {
103         Preconditions.checkArgument((scannerFactory == null) != (packageProvider == null),
104                 "scannerFactory and packageProvider cannot be both null or both non-null");
105 
106         final ArrayList<OverlayPartition> partitions;
107         if (rootDirectory == null) {
108             partitions = new ArrayList<>(
109                     PackagePartitions.getOrderedPartitions(OverlayPartition::new));
110         } else {
111             // Rebase the system partitions and settings file on the specified root directory.
112             partitions = new ArrayList<>(PackagePartitions.getOrderedPartitions(
113                     p -> new OverlayPartition(
114                             new File(rootDirectory, p.getNonConicalFolder().getPath()),
115                             p)));
116         }
117 
118         boolean foundConfigFile = false;
119         ArrayList<ParsedOverlayInfo> packageManagerOverlayInfos = null;
120 
121         final ArrayList<ParsedConfiguration> overlays = new ArrayList<>();
122         for (int i = 0, n = partitions.size(); i < n; i++) {
123             final OverlayPartition partition = partitions.get(i);
124             final OverlayScanner scanner = (scannerFactory == null) ? null : scannerFactory.get();
125             final ArrayList<ParsedConfiguration> partitionOverlays =
126                     OverlayConfigParser.getConfigurations(partition, scanner);
127             if (partitionOverlays != null) {
128                 foundConfigFile = true;
129                 overlays.addAll(partitionOverlays);
130                 continue;
131             }
132 
133             // If the configuration file is not present, then use android:isStatic and
134             // android:priority to configure the overlays in the partition.
135             // TODO(147840005): Remove converting static overlays to immutable, default-enabled
136             //  overlays when android:siStatic and android:priority are fully deprecated.
137             final ArrayList<ParsedOverlayInfo> partitionOverlayInfos;
138             if (scannerFactory != null) {
139                 partitionOverlayInfos = new ArrayList<>(scanner.getAllParsedInfos());
140             } else {
141                 if (packageManagerOverlayInfos == null) {
142                     packageManagerOverlayInfos = getOverlayPackageInfos(packageProvider);
143                 }
144 
145                 // Filter out overlays not present in the partition.
146                 partitionOverlayInfos = new ArrayList<>(packageManagerOverlayInfos);
147                 for (int j = partitionOverlayInfos.size() - 1; j >= 0; j--) {
148                     if (!partition.containsFile(partitionOverlayInfos.get(j).path)) {
149                         partitionOverlayInfos.remove(j);
150                     }
151                 }
152             }
153 
154             // Static overlays are configured as immutable, default-enabled overlays.
155             final ArrayList<ParsedConfiguration> partitionConfigs = new ArrayList<>();
156             for (int j = 0, m = partitionOverlayInfos.size(); j < m; j++) {
157                 final ParsedOverlayInfo p = partitionOverlayInfos.get(j);
158                 if (p.isStatic) {
159                     partitionConfigs.add(new ParsedConfiguration(p.packageName,
160                             true /* enabled */, false /* mutable */, partition.policy, p));
161                 }
162             }
163 
164             partitionConfigs.sort(sStaticOverlayComparator);
165             overlays.addAll(partitionConfigs);
166         }
167 
168         if (!foundConfigFile) {
169             // If no overlay configuration files exist, disregard partition precedence and allow
170             // android:priority to reorder overlays across partition boundaries.
171             overlays.sort(sStaticOverlayComparator);
172         }
173 
174         for (int i = 0, n = overlays.size(); i < n; i++) {
175             // Add the configurations to a map so definitions of an overlay in an earlier
176             // partition can be replaced by an overlay with the same package name in a later
177             // partition.
178             final ParsedConfiguration config = overlays.get(i);
179             mConfigurations.put(config.packageName, new Configuration(config, i));
180         }
181     }
182 
183     /**
184      * Creates an instance of OverlayConfig for use in the zygote process.
185      * This instance will not include information of static overlays existing outside of a partition
186      * overlay directory.
187      */
188     @NonNull
getZygoteInstance()189     public static OverlayConfig getZygoteInstance() {
190         Trace.traceBegin(Trace.TRACE_TAG_RRO, "OverlayConfig#getZygoteInstance");
191         try {
192             return new OverlayConfig(null /* rootDirectory */, OverlayScanner::new,
193                     null /* packageProvider */);
194         } finally {
195             Trace.traceEnd(Trace.TRACE_TAG_RRO);
196         }
197     }
198 
199     /**
200      * Initializes a singleton instance for use in the system process.
201      * Can only be called once. This instance is cached so future invocations of
202      * {@link #getSystemInstance()} will return the initialized instance.
203      */
204     @NonNull
initializeSystemInstance(PackageProvider packageProvider)205     public static OverlayConfig initializeSystemInstance(PackageProvider packageProvider) {
206         Trace.traceBegin(Trace.TRACE_TAG_RRO, "OverlayConfig#initializeSystemInstance");
207         try {
208             sInstance = new OverlayConfig(null, null, packageProvider);
209         } finally {
210             Trace.traceEnd(Trace.TRACE_TAG_RRO);
211         }
212         return sInstance;
213     }
214 
215     /**
216      * Retrieves the singleton instance initialized by
217      * {@link #initializeSystemInstance(PackageProvider)}.
218      */
219     @NonNull
getSystemInstance()220     public static OverlayConfig getSystemInstance() {
221         if (sInstance == null) {
222             throw new IllegalStateException("System instance not initialized");
223         }
224 
225         return sInstance;
226     }
227 
228     @VisibleForTesting
229     @Nullable
getConfiguration(@onNull String packageName)230     public Configuration getConfiguration(@NonNull String packageName) {
231         return mConfigurations.get(packageName);
232     }
233 
234     /**
235      * Returns whether the overlay is enabled by default.
236      * Overlays that are not configured are disabled by default.
237      *
238      * If an immutable overlay has its enabled state change, the new enabled state is applied to the
239      * overlay.
240      *
241      * When a mutable is first seen by the OverlayManagerService, the default-enabled state will be
242      * applied to the overlay. If the configured default-enabled state changes in a subsequent boot,
243      * the default-enabled state will not be applied to the overlay.
244      *
245      * The configured enabled state will only be applied when:
246      * <ul>
247      * <li> The device is factory reset
248      * <li> The overlay is removed from the device and added back to the device in a future OTA
249      * <li> The overlay changes its package name
250      * <li> The overlay changes its target package name or target overlayable name
251      * <li> An immutable overlay becomes mutable
252      * </ul>
253      */
isEnabled(String packageName)254     public boolean isEnabled(String packageName) {
255         final Configuration config = mConfigurations.get(packageName);
256         return config == null? OverlayConfigParser.DEFAULT_ENABLED_STATE
257                 : config.parsedConfig.enabled;
258     }
259 
260     /**
261      * Returns whether the overlay is mutable and can have its enabled state changed dynamically.
262      * Overlays that are not configured are mutable.
263      */
isMutable(String packageName)264     public boolean isMutable(String packageName) {
265         final Configuration config = mConfigurations.get(packageName);
266         return config == null ? OverlayConfigParser.DEFAULT_MUTABILITY
267                 : config.parsedConfig.mutable;
268     }
269 
270     /**
271      * Returns an integer corresponding to the priority of the overlay.
272      * When multiple overlays override the same resource, the overlay with the highest priority will
273      * will have its value chosen. Overlays that are not configured have a priority of
274      * {@link Integer#MAX_VALUE}.
275      */
getPriority(String packageName)276     public int getPriority(String packageName) {
277         final Configuration config = mConfigurations.get(packageName);
278         return config == null ? DEFAULT_PRIORITY : config.configIndex;
279     }
280 
281     @NonNull
getSortedOverlays()282     private ArrayList<Configuration> getSortedOverlays() {
283         final ArrayList<Configuration> sortedOverlays = new ArrayList<>();
284         for (int i = 0, n = mConfigurations.size(); i < n; i++) {
285             sortedOverlays.add(mConfigurations.valueAt(i));
286         }
287         sortedOverlays.sort(Comparator.comparingInt(o -> o.configIndex));
288         return sortedOverlays;
289     }
290 
291     @NonNull
getOverlayPackageInfos( @onNull PackageProvider packageManager)292     private static ArrayList<ParsedOverlayInfo> getOverlayPackageInfos(
293             @NonNull PackageProvider packageManager) {
294         final ArrayList<ParsedOverlayInfo> overlays = new ArrayList<>();
295         packageManager.forEachPackage((ParsingPackageRead p, Boolean isSystem) -> {
296             if (p.getOverlayTarget() != null && isSystem) {
297                 overlays.add(new ParsedOverlayInfo(p.getPackageName(), p.getOverlayTarget(),
298                         p.getTargetSdkVersion(), p.isOverlayIsStatic(), p.getOverlayPriority(),
299                         new File(p.getBaseApkPath())));
300             }
301         });
302         return overlays;
303     }
304 
305     /** Represents a single call to idmap create-multiple. */
306     @VisibleForTesting
307     public static class IdmapInvocation {
308         public final boolean enforceOverlayable;
309         public final String policy;
310         public final ArrayList<String> overlayPaths = new ArrayList<>();
311 
IdmapInvocation(boolean enforceOverlayable, @NonNull String policy)312         IdmapInvocation(boolean enforceOverlayable, @NonNull String policy) {
313             this.enforceOverlayable = enforceOverlayable;
314             this.policy = policy;
315         }
316 
317         @Override
toString()318         public String toString() {
319             return getClass().getSimpleName() + String.format("{enforceOverlayable=%s, policy=%s"
320                             + ", overlayPaths=[%s]}", enforceOverlayable, policy,
321                     String.join(", ", overlayPaths));
322         }
323     }
324 
325     /**
326      * Retrieves a list of immutable framework overlays in order of least precedence to greatest
327      * precedence.
328      */
329     @VisibleForTesting
getImmutableFrameworkOverlayIdmapInvocations()330     public ArrayList<IdmapInvocation> getImmutableFrameworkOverlayIdmapInvocations() {
331         final ArrayList<IdmapInvocation> idmapInvocations = new ArrayList<>();
332         final ArrayList<Configuration> sortedConfigs = getSortedOverlays();
333         for (int i = 0, n = sortedConfigs.size(); i < n; i++) {
334             final Configuration overlay = sortedConfigs.get(i);
335             if (overlay.parsedConfig.mutable || !overlay.parsedConfig.enabled
336                     || !"android".equals(overlay.parsedConfig.parsedInfo.targetPackageName)) {
337                 continue;
338             }
339 
340             // Only enforce that overlays targeting packages with overlayable declarations abide by
341             // those declarations if the target sdk of the overlay is at least Q (when overlayable
342             // was introduced).
343             final boolean enforceOverlayable = overlay.parsedConfig.parsedInfo.targetSdkVersion
344                     >= Build.VERSION_CODES.Q;
345 
346             // Determine if the idmap for the current overlay can be generated in the last idmap
347             // create-multiple invocation.
348             IdmapInvocation invocation = null;
349             if (!idmapInvocations.isEmpty()) {
350                 final IdmapInvocation last = idmapInvocations.get(idmapInvocations.size() - 1);
351                 if (last.enforceOverlayable == enforceOverlayable
352                         && last.policy.equals(overlay.parsedConfig.policy)) {
353                     invocation = last;
354                 }
355             }
356 
357             if (invocation == null) {
358                 invocation = new IdmapInvocation(enforceOverlayable, overlay.parsedConfig.policy);
359                 idmapInvocations.add(invocation);
360             }
361 
362             invocation.overlayPaths.add(overlay.parsedConfig.parsedInfo.path.getAbsolutePath());
363         }
364         return idmapInvocations;
365     }
366 
367     /**
368      * Creates idmap files for immutable overlays targeting the framework packages. Currently the
369      * android package is the only preloaded system package. Only the zygote can invoke this method.
370      *
371      * @return the paths of the created idmap files
372      */
373     @NonNull
createImmutableFrameworkIdmapsInZygote()374     public String[] createImmutableFrameworkIdmapsInZygote() {
375         final String targetPath = "/system/framework/framework-res.apk";
376         final ArrayList<String> idmapPaths = new ArrayList<>();
377         final ArrayList<IdmapInvocation> idmapInvocations =
378                 getImmutableFrameworkOverlayIdmapInvocations();
379 
380         for (int i = 0, n = idmapInvocations.size(); i < n; i++) {
381             final IdmapInvocation invocation = idmapInvocations.get(i);
382             final String[] idmaps = createIdmap(targetPath,
383                     invocation.overlayPaths.toArray(new String[0]),
384                     new String[]{OverlayConfigParser.OverlayPartition.POLICY_PUBLIC,
385                             invocation.policy},
386                     invocation.enforceOverlayable);
387 
388             if (idmaps == null) {
389                 Log.w(TAG, "'idmap2 create-multiple' failed: no mutable=\"false\" overlays"
390                         + " targeting \"android\" will be loaded");
391                 return new String[0];
392             }
393 
394             idmapPaths.addAll(Arrays.asList(idmaps));
395         }
396 
397         return idmapPaths.toArray(new String[0]);
398     }
399 
400     /**
401      * For each overlay APK, this creates the idmap file that allows the overlay to override the
402      * target package.
403      *
404      * @return the paths of the created idmap
405      */
createIdmap(@onNull String targetPath, @NonNull String[] overlayPath, @NonNull String[] policies, boolean enforceOverlayable)406     private static native String[] createIdmap(@NonNull String targetPath,
407             @NonNull String[] overlayPath, @NonNull String[] policies, boolean enforceOverlayable);
408 }
409