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 final Map<String, ParsedOverlayInfo> packageManagerOverlayInfos = 144 packageProvider == null ? null : getOverlayPackageInfos(packageProvider); 145 146 final ArrayList<ParsedConfiguration> overlays = new ArrayList<>(); 147 for (int i = 0, n = partitions.size(); i < n; i++) { 148 final OverlayPartition partition = partitions.get(i); 149 final OverlayScanner scanner = (scannerFactory == null) ? null : scannerFactory.get(); 150 final ArrayList<ParsedConfiguration> partitionOverlays = 151 OverlayConfigParser.getConfigurations(partition, scanner, 152 packageManagerOverlayInfos, 153 activeApexesPerPartition.getOrDefault(partition.type, 154 Collections.emptyList())); 155 if (partitionOverlays != null) { 156 overlays.addAll(partitionOverlays); 157 continue; 158 } 159 160 // If the configuration file is not present, then use android:isStatic and 161 // android:priority to configure the overlays in the partition. 162 // TODO(147840005): Remove converting static overlays to immutable, default-enabled 163 // overlays when android:siStatic and android:priority are fully deprecated. 164 final ArrayList<ParsedOverlayInfo> partitionOverlayInfos; 165 if (scannerFactory != null) { 166 partitionOverlayInfos = new ArrayList<>(scanner.getAllParsedInfos()); 167 } else { 168 // Filter out overlays not present in the partition. 169 partitionOverlayInfos = new ArrayList<>(packageManagerOverlayInfos.values()); 170 for (int j = partitionOverlayInfos.size() - 1; j >= 0; j--) { 171 if (!partition.containsFile(partitionOverlayInfos.get(j) 172 .getOriginalPartitionPath())) { 173 partitionOverlayInfos.remove(j); 174 } 175 } 176 } 177 178 // Static overlays are configured as immutable, default-enabled overlays. 179 final ArrayList<ParsedConfiguration> partitionConfigs = new ArrayList<>(); 180 for (int j = 0, m = partitionOverlayInfos.size(); j < m; j++) { 181 final ParsedOverlayInfo p = partitionOverlayInfos.get(j); 182 if (p.isStatic) { 183 partitionConfigs.add(new ParsedConfiguration(p.packageName, 184 true /* enabled */, false /* mutable */, partition.policy, p, null)); 185 } 186 } 187 188 partitionConfigs.sort(sStaticOverlayComparator); 189 overlays.addAll(partitionConfigs); 190 } 191 192 for (int i = 0, n = overlays.size(); i < n; i++) { 193 // Add the configurations to a map so definitions of an overlay in an earlier 194 // partition can be replaced by an overlay with the same package name in a later 195 // partition. 196 final ParsedConfiguration config = overlays.get(i); 197 mConfigurations.put(config.packageName, new Configuration(config, i)); 198 } 199 } 200 201 /** 202 * Creates an instance of OverlayConfig for use in the zygote process. 203 * This instance will not include information of static overlays existing outside of a partition 204 * overlay directory. 205 */ 206 @NonNull getZygoteInstance()207 public static OverlayConfig getZygoteInstance() { 208 Trace.traceBegin(Trace.TRACE_TAG_RRO, "OverlayConfig#getZygoteInstance"); 209 try { 210 return new OverlayConfig(null /* rootDirectory */, OverlayScanner::new, 211 null /* packageProvider */); 212 } finally { 213 Trace.traceEnd(Trace.TRACE_TAG_RRO); 214 } 215 } 216 217 /** 218 * Initializes a singleton instance for use in the system process. 219 * Can only be called once. This instance is cached so future invocations of 220 * {@link #getSystemInstance()} will return the initialized instance. 221 */ 222 @NonNull initializeSystemInstance(PackageProvider packageProvider)223 public static OverlayConfig initializeSystemInstance(PackageProvider packageProvider) { 224 Trace.traceBegin(Trace.TRACE_TAG_RRO, "OverlayConfig#initializeSystemInstance"); 225 try { 226 sInstance = new OverlayConfig(null, null, packageProvider); 227 } finally { 228 Trace.traceEnd(Trace.TRACE_TAG_RRO); 229 } 230 return sInstance; 231 } 232 233 /** 234 * Retrieves the singleton instance initialized by 235 * {@link #initializeSystemInstance(PackageProvider)}. 236 */ 237 @NonNull getSystemInstance()238 public static OverlayConfig getSystemInstance() { 239 if (sInstance == null) { 240 throw new IllegalStateException("System instance not initialized"); 241 } 242 243 return sInstance; 244 } 245 246 @VisibleForTesting 247 @Nullable getConfiguration(@onNull String packageName)248 public Configuration getConfiguration(@NonNull String packageName) { 249 return mConfigurations.get(packageName); 250 } 251 252 /** 253 * Returns whether the overlay is enabled by default. 254 * Overlays that are not configured are disabled by default. 255 * 256 * If an immutable overlay has its enabled state change, the new enabled state is applied to the 257 * overlay. 258 * 259 * When a mutable is first seen by the OverlayManagerService, the default-enabled state will be 260 * applied to the overlay. If the configured default-enabled state changes in a subsequent boot, 261 * the default-enabled state will not be applied to the overlay. 262 * 263 * The configured enabled state will only be applied when: 264 * <ul> 265 * <li> The device is factory reset 266 * <li> The overlay is removed from the device and added back to the device in a future OTA 267 * <li> The overlay changes its package name 268 * <li> The overlay changes its target package name or target overlayable name 269 * <li> An immutable overlay becomes mutable 270 * </ul> 271 */ isEnabled(String packageName)272 public boolean isEnabled(String packageName) { 273 final Configuration config = mConfigurations.get(packageName); 274 return config == null? OverlayConfigParser.DEFAULT_ENABLED_STATE 275 : config.parsedConfig.enabled; 276 } 277 278 /** 279 * Returns whether the overlay is mutable and can have its enabled state changed dynamically. 280 * Overlays that are not configured are mutable. 281 */ isMutable(String packageName)282 public boolean isMutable(String packageName) { 283 final Configuration config = mConfigurations.get(packageName); 284 return config == null ? OverlayConfigParser.DEFAULT_MUTABILITY 285 : config.parsedConfig.mutable; 286 } 287 288 /** 289 * Returns an integer corresponding to the priority of the overlay. 290 * When multiple overlays override the same resource, the overlay with the highest priority will 291 * will have its value chosen. Overlays that are not configured have a priority of 292 * {@link Integer#MAX_VALUE}. 293 */ getPriority(String packageName)294 public int getPriority(String packageName) { 295 final Configuration config = mConfigurations.get(packageName); 296 return config == null ? DEFAULT_PRIORITY : config.configIndex; 297 } 298 299 @NonNull getSortedOverlays()300 private ArrayList<Configuration> getSortedOverlays() { 301 final ArrayList<Configuration> sortedOverlays = new ArrayList<>(); 302 for (int i = 0, n = mConfigurations.size(); i < n; i++) { 303 sortedOverlays.add(mConfigurations.valueAt(i)); 304 } 305 sortedOverlays.sort(Comparator.comparingInt(o -> o.configIndex)); 306 return sortedOverlays; 307 } 308 309 @NonNull getOverlayPackageInfos( @onNull PackageProvider packageManager)310 private static Map<String, ParsedOverlayInfo> getOverlayPackageInfos( 311 @NonNull PackageProvider packageManager) { 312 final HashMap<String, ParsedOverlayInfo> overlays = new HashMap<>(); 313 packageManager.forEachPackage((PackageProvider.Package p, Boolean isSystem, 314 @Nullable File preInstalledApexPath) -> { 315 if (p.getOverlayTarget() != null && isSystem) { 316 overlays.put(p.getPackageName(), new ParsedOverlayInfo(p.getPackageName(), 317 p.getOverlayTarget(), p.getTargetSdkVersion(), p.isOverlayIsStatic(), 318 p.getOverlayPriority(), new File(p.getBaseApkPath()), 319 preInstalledApexPath)); 320 } 321 }); 322 return overlays; 323 } 324 325 /** Returns a map of PartitionType to List of active APEX module names. */ 326 @NonNull getActiveApexes( @onNull List<OverlayPartition> partitions)327 private static ArrayMap<Integer, List<String>> getActiveApexes( 328 @NonNull List<OverlayPartition> partitions) { 329 // An Overlay in an APEX, which is an update of an APEX in a given partition, 330 // is considered as belonging to that partition. 331 ArrayMap<Integer, List<String>> result = new ArrayMap<>(); 332 for (OverlayPartition partition : partitions) { 333 result.put(partition.type, new ArrayList<String>()); 334 } 335 // Read from apex-info-list because ApexManager is not accessible to zygote. 336 File apexInfoList = new File("/apex/apex-info-list.xml"); 337 if (apexInfoList.exists() && apexInfoList.canRead()) { 338 try (FileInputStream stream = new FileInputStream(apexInfoList)) { 339 List<ApexInfo> apexInfos = XmlParser.readApexInfoList(stream).getApexInfo(); 340 for (ApexInfo info : apexInfos) { 341 if (info.getIsActive()) { 342 for (OverlayPartition partition : partitions) { 343 if (partition.containsPath(info.getPreinstalledModulePath())) { 344 result.get(partition.type).add(info.getModuleName()); 345 break; 346 } 347 } 348 } 349 } 350 } catch (Exception e) { 351 Log.w(TAG, "Error reading apex-info-list: " + e); 352 } 353 } 354 return result; 355 } 356 357 /** Represents a single call to idmap create-multiple. */ 358 @VisibleForTesting 359 public static class IdmapInvocation { 360 public final boolean enforceOverlayable; 361 public final String policy; 362 public final ArrayList<String> overlayPaths = new ArrayList<>(); 363 IdmapInvocation(boolean enforceOverlayable, @NonNull String policy)364 IdmapInvocation(boolean enforceOverlayable, @NonNull String policy) { 365 this.enforceOverlayable = enforceOverlayable; 366 this.policy = policy; 367 } 368 369 @Override toString()370 public String toString() { 371 return getClass().getSimpleName() + String.format("{enforceOverlayable=%s, policy=%s" 372 + ", overlayPaths=[%s]}", enforceOverlayable, policy, 373 String.join(", ", overlayPaths)); 374 } 375 } 376 377 /** 378 * Retrieves a list of immutable framework overlays in order of least precedence to greatest 379 * precedence. 380 */ 381 @VisibleForTesting getImmutableFrameworkOverlayIdmapInvocations()382 public ArrayList<IdmapInvocation> getImmutableFrameworkOverlayIdmapInvocations() { 383 final ArrayList<IdmapInvocation> idmapInvocations = new ArrayList<>(); 384 final ArrayList<Configuration> sortedConfigs = getSortedOverlays(); 385 for (int i = 0, n = sortedConfigs.size(); i < n; i++) { 386 final Configuration overlay = sortedConfigs.get(i); 387 if (overlay.parsedConfig.mutable || !overlay.parsedConfig.enabled 388 || !"android".equals(overlay.parsedConfig.parsedInfo.targetPackageName)) { 389 continue; 390 } 391 392 // Only enforce that overlays targeting packages with overlayable declarations abide by 393 // those declarations if the target sdk of the overlay is at least Q (when overlayable 394 // was introduced). 395 final boolean enforceOverlayable = overlay.parsedConfig.parsedInfo.targetSdkVersion 396 >= Build.VERSION_CODES.Q; 397 398 // Determine if the idmap for the current overlay can be generated in the last idmap 399 // create-multiple invocation. 400 IdmapInvocation invocation = null; 401 if (!idmapInvocations.isEmpty()) { 402 final IdmapInvocation last = idmapInvocations.get(idmapInvocations.size() - 1); 403 if (last.enforceOverlayable == enforceOverlayable 404 && last.policy.equals(overlay.parsedConfig.policy)) { 405 invocation = last; 406 } 407 } 408 409 if (invocation == null) { 410 invocation = new IdmapInvocation(enforceOverlayable, overlay.parsedConfig.policy); 411 idmapInvocations.add(invocation); 412 } 413 414 invocation.overlayPaths.add(overlay.parsedConfig.parsedInfo.path.getAbsolutePath()); 415 } 416 return idmapInvocations; 417 } 418 419 /** 420 * Creates idmap files for immutable overlays targeting the framework packages. Currently the 421 * android package is the only preloaded system package. Only the zygote can invoke this method. 422 * 423 * @return the paths of the created idmap files 424 */ 425 @NonNull createImmutableFrameworkIdmapsInZygote()426 public String[] createImmutableFrameworkIdmapsInZygote() { 427 final String targetPath = "/system/framework/framework-res.apk"; 428 final ArrayList<String> idmapPaths = new ArrayList<>(); 429 final ArrayList<IdmapInvocation> idmapInvocations = 430 getImmutableFrameworkOverlayIdmapInvocations(); 431 432 for (int i = 0, n = idmapInvocations.size(); i < n; i++) { 433 final IdmapInvocation invocation = idmapInvocations.get(i); 434 final String[] idmaps = createIdmap(targetPath, 435 invocation.overlayPaths.toArray(new String[0]), 436 new String[]{OverlayConfigParser.OverlayPartition.POLICY_PUBLIC, 437 invocation.policy}, 438 invocation.enforceOverlayable); 439 440 if (idmaps == null) { 441 Log.w(TAG, "'idmap2 create-multiple' failed: no mutable=\"false\" overlays" 442 + " targeting \"android\" will be loaded"); 443 return new String[0]; 444 } 445 446 idmapPaths.addAll(Arrays.asList(idmaps)); 447 } 448 449 return idmapPaths.toArray(new String[0]); 450 } 451 452 /** Dump all overlay configurations to the Printer. */ dump(@onNull PrintWriter writer)453 public void dump(@NonNull PrintWriter writer) { 454 final IndentingPrintWriter ipw = new IndentingPrintWriter(writer); 455 ipw.println("Overlay configurations:"); 456 ipw.increaseIndent(); 457 458 final ArrayList<Configuration> configurations = new ArrayList<>(mConfigurations.values()); 459 configurations.sort(Comparator.comparingInt(o -> o.configIndex)); 460 for (int i = 0; i < configurations.size(); i++) { 461 final Configuration configuration = configurations.get(i); 462 ipw.print(configuration.configIndex); 463 ipw.print(", "); 464 ipw.print(configuration.parsedConfig); 465 ipw.println(); 466 } 467 ipw.decreaseIndent(); 468 ipw.println(); 469 } 470 471 /** 472 * For each overlay APK, this creates the idmap file that allows the overlay to override the 473 * target package. 474 * 475 * @return the paths of the created idmap 476 */ createIdmap(@onNull String targetPath, @NonNull String[] overlayPath, @NonNull String[] policies, boolean enforceOverlayable)477 private static native String[] createIdmap(@NonNull String targetPath, 478 @NonNull String[] overlayPath, @NonNull String[] policies, boolean enforceOverlayable); 479 } 480