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