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