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.pm.parsing.ParsingPackageRead; 23 import android.os.Build; 24 import android.os.Trace; 25 import android.util.ArrayMap; 26 import android.util.Log; 27 28 import com.android.internal.annotations.VisibleForTesting; 29 import com.android.internal.content.om.OverlayConfigParser.OverlayPartition; 30 import com.android.internal.content.om.OverlayConfigParser.ParsedConfiguration; 31 import com.android.internal.content.om.OverlayScanner.ParsedOverlayInfo; 32 import com.android.internal.util.Preconditions; 33 34 import java.io.File; 35 import java.util.ArrayList; 36 import java.util.Arrays; 37 import java.util.Comparator; 38 import java.util.function.BiConsumer; 39 import java.util.function.Supplier; 40 41 /** 42 * Responsible for reading overlay configuration files and handling queries of overlay mutability, 43 * default-enabled state, and priority. 44 * 45 * @see OverlayConfigParser 46 */ 47 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) 48 public class OverlayConfig { 49 static final String TAG = "OverlayConfig"; 50 51 // The default priority of an overlay that has not been configured. Overlays with default 52 // priority have a higher precedence than configured overlays. 53 @VisibleForTesting 54 public static final int DEFAULT_PRIORITY = Integer.MAX_VALUE; 55 56 @VisibleForTesting 57 public static final class Configuration { 58 @Nullable 59 public final ParsedConfiguration parsedConfig; 60 61 public final int configIndex; 62 Configuration(@ullable ParsedConfiguration parsedConfig, int configIndex)63 public Configuration(@Nullable ParsedConfiguration parsedConfig, int configIndex) { 64 this.parsedConfig = parsedConfig; 65 this.configIndex = configIndex; 66 } 67 } 68 69 /** 70 * Interface for providing information on scanned packages. 71 * TODO(147840005): Remove this when android:isStatic and android:priority are fully deprecated 72 */ 73 public interface PackageProvider { 74 75 /** Performs the given action for each package. */ forEachPackage(BiConsumer<ParsingPackageRead, Boolean> p)76 void forEachPackage(BiConsumer<ParsingPackageRead, Boolean> p); 77 } 78 79 private static final Comparator<ParsedConfiguration> sStaticOverlayComparator = (c1, c2) -> { 80 final ParsedOverlayInfo o1 = c1.parsedInfo; 81 final ParsedOverlayInfo o2 = c2.parsedInfo; 82 Preconditions.checkArgument(o1.isStatic && o2.isStatic, 83 "attempted to sort non-static overlay"); 84 85 if (!o1.targetPackageName.equals(o2.targetPackageName)) { 86 return o1.targetPackageName.compareTo(o2.targetPackageName); 87 } 88 89 final int comparedPriority = o1.priority - o2.priority; 90 return comparedPriority == 0 ? o1.path.compareTo(o2.path) : comparedPriority; 91 }; 92 93 // Map of overlay package name to configured overlay settings 94 private final ArrayMap<String, Configuration> mConfigurations = new ArrayMap<>(); 95 96 // Singleton instance only assigned in system server 97 private static OverlayConfig sInstance; 98 99 @VisibleForTesting OverlayConfig(@ullable File rootDirectory, @Nullable Supplier<OverlayScanner> scannerFactory, @Nullable PackageProvider packageProvider)100 public OverlayConfig(@Nullable File rootDirectory, 101 @Nullable Supplier<OverlayScanner> scannerFactory, 102 @Nullable PackageProvider packageProvider) { 103 Preconditions.checkArgument((scannerFactory == null) != (packageProvider == null), 104 "scannerFactory and packageProvider cannot be both null or both non-null"); 105 106 final ArrayList<OverlayPartition> partitions; 107 if (rootDirectory == null) { 108 partitions = new ArrayList<>( 109 PackagePartitions.getOrderedPartitions(OverlayPartition::new)); 110 } else { 111 // Rebase the system partitions and settings file on the specified root directory. 112 partitions = new ArrayList<>(PackagePartitions.getOrderedPartitions( 113 p -> new OverlayPartition( 114 new File(rootDirectory, p.getNonConicalFolder().getPath()), 115 p))); 116 } 117 118 boolean foundConfigFile = false; 119 ArrayList<ParsedOverlayInfo> packageManagerOverlayInfos = null; 120 121 final ArrayList<ParsedConfiguration> overlays = new ArrayList<>(); 122 for (int i = 0, n = partitions.size(); i < n; i++) { 123 final OverlayPartition partition = partitions.get(i); 124 final OverlayScanner scanner = (scannerFactory == null) ? null : scannerFactory.get(); 125 final ArrayList<ParsedConfiguration> partitionOverlays = 126 OverlayConfigParser.getConfigurations(partition, scanner); 127 if (partitionOverlays != null) { 128 foundConfigFile = true; 129 overlays.addAll(partitionOverlays); 130 continue; 131 } 132 133 // If the configuration file is not present, then use android:isStatic and 134 // android:priority to configure the overlays in the partition. 135 // TODO(147840005): Remove converting static overlays to immutable, default-enabled 136 // overlays when android:siStatic and android:priority are fully deprecated. 137 final ArrayList<ParsedOverlayInfo> partitionOverlayInfos; 138 if (scannerFactory != null) { 139 partitionOverlayInfos = new ArrayList<>(scanner.getAllParsedInfos()); 140 } else { 141 if (packageManagerOverlayInfos == null) { 142 packageManagerOverlayInfos = getOverlayPackageInfos(packageProvider); 143 } 144 145 // Filter out overlays not present in the partition. 146 partitionOverlayInfos = new ArrayList<>(packageManagerOverlayInfos); 147 for (int j = partitionOverlayInfos.size() - 1; j >= 0; j--) { 148 if (!partition.containsFile(partitionOverlayInfos.get(j).path)) { 149 partitionOverlayInfos.remove(j); 150 } 151 } 152 } 153 154 // Static overlays are configured as immutable, default-enabled overlays. 155 final ArrayList<ParsedConfiguration> partitionConfigs = new ArrayList<>(); 156 for (int j = 0, m = partitionOverlayInfos.size(); j < m; j++) { 157 final ParsedOverlayInfo p = partitionOverlayInfos.get(j); 158 if (p.isStatic) { 159 partitionConfigs.add(new ParsedConfiguration(p.packageName, 160 true /* enabled */, false /* mutable */, partition.policy, p)); 161 } 162 } 163 164 partitionConfigs.sort(sStaticOverlayComparator); 165 overlays.addAll(partitionConfigs); 166 } 167 168 if (!foundConfigFile) { 169 // If no overlay configuration files exist, disregard partition precedence and allow 170 // android:priority to reorder overlays across partition boundaries. 171 overlays.sort(sStaticOverlayComparator); 172 } 173 174 for (int i = 0, n = overlays.size(); i < n; i++) { 175 // Add the configurations to a map so definitions of an overlay in an earlier 176 // partition can be replaced by an overlay with the same package name in a later 177 // partition. 178 final ParsedConfiguration config = overlays.get(i); 179 mConfigurations.put(config.packageName, new Configuration(config, i)); 180 } 181 } 182 183 /** 184 * Creates an instance of OverlayConfig for use in the zygote process. 185 * This instance will not include information of static overlays existing outside of a partition 186 * overlay directory. 187 */ 188 @NonNull getZygoteInstance()189 public static OverlayConfig getZygoteInstance() { 190 Trace.traceBegin(Trace.TRACE_TAG_RRO, "OverlayConfig#getZygoteInstance"); 191 try { 192 return new OverlayConfig(null /* rootDirectory */, OverlayScanner::new, 193 null /* packageProvider */); 194 } finally { 195 Trace.traceEnd(Trace.TRACE_TAG_RRO); 196 } 197 } 198 199 /** 200 * Initializes a singleton instance for use in the system process. 201 * Can only be called once. This instance is cached so future invocations of 202 * {@link #getSystemInstance()} will return the initialized instance. 203 */ 204 @NonNull initializeSystemInstance(PackageProvider packageProvider)205 public static OverlayConfig initializeSystemInstance(PackageProvider packageProvider) { 206 Trace.traceBegin(Trace.TRACE_TAG_RRO, "OverlayConfig#initializeSystemInstance"); 207 try { 208 sInstance = new OverlayConfig(null, null, packageProvider); 209 } finally { 210 Trace.traceEnd(Trace.TRACE_TAG_RRO); 211 } 212 return sInstance; 213 } 214 215 /** 216 * Retrieves the singleton instance initialized by 217 * {@link #initializeSystemInstance(PackageProvider)}. 218 */ 219 @NonNull getSystemInstance()220 public static OverlayConfig getSystemInstance() { 221 if (sInstance == null) { 222 throw new IllegalStateException("System instance not initialized"); 223 } 224 225 return sInstance; 226 } 227 228 @VisibleForTesting 229 @Nullable getConfiguration(@onNull String packageName)230 public Configuration getConfiguration(@NonNull String packageName) { 231 return mConfigurations.get(packageName); 232 } 233 234 /** 235 * Returns whether the overlay is enabled by default. 236 * Overlays that are not configured are disabled by default. 237 * 238 * If an immutable overlay has its enabled state change, the new enabled state is applied to the 239 * overlay. 240 * 241 * When a mutable is first seen by the OverlayManagerService, the default-enabled state will be 242 * applied to the overlay. If the configured default-enabled state changes in a subsequent boot, 243 * the default-enabled state will not be applied to the overlay. 244 * 245 * The configured enabled state will only be applied when: 246 * <ul> 247 * <li> The device is factory reset 248 * <li> The overlay is removed from the device and added back to the device in a future OTA 249 * <li> The overlay changes its package name 250 * <li> The overlay changes its target package name or target overlayable name 251 * <li> An immutable overlay becomes mutable 252 * </ul> 253 */ isEnabled(String packageName)254 public boolean isEnabled(String packageName) { 255 final Configuration config = mConfigurations.get(packageName); 256 return config == null? OverlayConfigParser.DEFAULT_ENABLED_STATE 257 : config.parsedConfig.enabled; 258 } 259 260 /** 261 * Returns whether the overlay is mutable and can have its enabled state changed dynamically. 262 * Overlays that are not configured are mutable. 263 */ isMutable(String packageName)264 public boolean isMutable(String packageName) { 265 final Configuration config = mConfigurations.get(packageName); 266 return config == null ? OverlayConfigParser.DEFAULT_MUTABILITY 267 : config.parsedConfig.mutable; 268 } 269 270 /** 271 * Returns an integer corresponding to the priority of the overlay. 272 * When multiple overlays override the same resource, the overlay with the highest priority will 273 * will have its value chosen. Overlays that are not configured have a priority of 274 * {@link Integer#MAX_VALUE}. 275 */ getPriority(String packageName)276 public int getPriority(String packageName) { 277 final Configuration config = mConfigurations.get(packageName); 278 return config == null ? DEFAULT_PRIORITY : config.configIndex; 279 } 280 281 @NonNull getSortedOverlays()282 private ArrayList<Configuration> getSortedOverlays() { 283 final ArrayList<Configuration> sortedOverlays = new ArrayList<>(); 284 for (int i = 0, n = mConfigurations.size(); i < n; i++) { 285 sortedOverlays.add(mConfigurations.valueAt(i)); 286 } 287 sortedOverlays.sort(Comparator.comparingInt(o -> o.configIndex)); 288 return sortedOverlays; 289 } 290 291 @NonNull getOverlayPackageInfos( @onNull PackageProvider packageManager)292 private static ArrayList<ParsedOverlayInfo> getOverlayPackageInfos( 293 @NonNull PackageProvider packageManager) { 294 final ArrayList<ParsedOverlayInfo> overlays = new ArrayList<>(); 295 packageManager.forEachPackage((ParsingPackageRead p, Boolean isSystem) -> { 296 if (p.getOverlayTarget() != null && isSystem) { 297 overlays.add(new ParsedOverlayInfo(p.getPackageName(), p.getOverlayTarget(), 298 p.getTargetSdkVersion(), p.isOverlayIsStatic(), p.getOverlayPriority(), 299 new File(p.getBaseApkPath()))); 300 } 301 }); 302 return overlays; 303 } 304 305 /** Represents a single call to idmap create-multiple. */ 306 @VisibleForTesting 307 public static class IdmapInvocation { 308 public final boolean enforceOverlayable; 309 public final String policy; 310 public final ArrayList<String> overlayPaths = new ArrayList<>(); 311 IdmapInvocation(boolean enforceOverlayable, @NonNull String policy)312 IdmapInvocation(boolean enforceOverlayable, @NonNull String policy) { 313 this.enforceOverlayable = enforceOverlayable; 314 this.policy = policy; 315 } 316 317 @Override toString()318 public String toString() { 319 return getClass().getSimpleName() + String.format("{enforceOverlayable=%s, policy=%s" 320 + ", overlayPaths=[%s]}", enforceOverlayable, policy, 321 String.join(", ", overlayPaths)); 322 } 323 } 324 325 /** 326 * Retrieves a list of immutable framework overlays in order of least precedence to greatest 327 * precedence. 328 */ 329 @VisibleForTesting getImmutableFrameworkOverlayIdmapInvocations()330 public ArrayList<IdmapInvocation> getImmutableFrameworkOverlayIdmapInvocations() { 331 final ArrayList<IdmapInvocation> idmapInvocations = new ArrayList<>(); 332 final ArrayList<Configuration> sortedConfigs = getSortedOverlays(); 333 for (int i = 0, n = sortedConfigs.size(); i < n; i++) { 334 final Configuration overlay = sortedConfigs.get(i); 335 if (overlay.parsedConfig.mutable || !overlay.parsedConfig.enabled 336 || !"android".equals(overlay.parsedConfig.parsedInfo.targetPackageName)) { 337 continue; 338 } 339 340 // Only enforce that overlays targeting packages with overlayable declarations abide by 341 // those declarations if the target sdk of the overlay is at least Q (when overlayable 342 // was introduced). 343 final boolean enforceOverlayable = overlay.parsedConfig.parsedInfo.targetSdkVersion 344 >= Build.VERSION_CODES.Q; 345 346 // Determine if the idmap for the current overlay can be generated in the last idmap 347 // create-multiple invocation. 348 IdmapInvocation invocation = null; 349 if (!idmapInvocations.isEmpty()) { 350 final IdmapInvocation last = idmapInvocations.get(idmapInvocations.size() - 1); 351 if (last.enforceOverlayable == enforceOverlayable 352 && last.policy.equals(overlay.parsedConfig.policy)) { 353 invocation = last; 354 } 355 } 356 357 if (invocation == null) { 358 invocation = new IdmapInvocation(enforceOverlayable, overlay.parsedConfig.policy); 359 idmapInvocations.add(invocation); 360 } 361 362 invocation.overlayPaths.add(overlay.parsedConfig.parsedInfo.path.getAbsolutePath()); 363 } 364 return idmapInvocations; 365 } 366 367 /** 368 * Creates idmap files for immutable overlays targeting the framework packages. Currently the 369 * android package is the only preloaded system package. Only the zygote can invoke this method. 370 * 371 * @return the paths of the created idmap files 372 */ 373 @NonNull createImmutableFrameworkIdmapsInZygote()374 public String[] createImmutableFrameworkIdmapsInZygote() { 375 final String targetPath = "/system/framework/framework-res.apk"; 376 final ArrayList<String> idmapPaths = new ArrayList<>(); 377 final ArrayList<IdmapInvocation> idmapInvocations = 378 getImmutableFrameworkOverlayIdmapInvocations(); 379 380 for (int i = 0, n = idmapInvocations.size(); i < n; i++) { 381 final IdmapInvocation invocation = idmapInvocations.get(i); 382 final String[] idmaps = createIdmap(targetPath, 383 invocation.overlayPaths.toArray(new String[0]), 384 new String[]{OverlayConfigParser.OverlayPartition.POLICY_PUBLIC, 385 invocation.policy}, 386 invocation.enforceOverlayable); 387 388 if (idmaps == null) { 389 Log.w(TAG, "'idmap2 create-multiple' failed: no mutable=\"false\" overlays" 390 + " targeting \"android\" will be loaded"); 391 return new String[0]; 392 } 393 394 idmapPaths.addAll(Arrays.asList(idmaps)); 395 } 396 397 return idmapPaths.toArray(new String[0]); 398 } 399 400 /** 401 * For each overlay APK, this creates the idmap file that allows the overlay to override the 402 * target package. 403 * 404 * @return the paths of the created idmap 405 */ createIdmap(@onNull String targetPath, @NonNull String[] overlayPath, @NonNull String[] policies, boolean enforceOverlayable)406 private static native String[] createIdmap(@NonNull String targetPath, 407 @NonNull String[] overlayPath, @NonNull String[] policies, boolean enforceOverlayable); 408 } 409