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