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