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