blob: fbef027ac37f4b2b6057be5e247ec0a3b04b6eb9 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.content.om;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.PackagePartitions;
import android.content.pm.parsing.ParsingPackageRead;
import android.os.Build;
import android.os.Trace;
import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.om.OverlayConfigParser.OverlayPartition;
import com.android.internal.content.om.OverlayConfigParser.ParsedConfiguration;
import com.android.internal.content.om.OverlayScanner.ParsedOverlayInfo;
import com.android.internal.util.Preconditions;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
/**
* Responsible for reading overlay configuration files and handling queries of overlay mutability,
* default-enabled state, and priority.
*
* @see OverlayConfigParser
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public class OverlayConfig {
static final String TAG = "OverlayConfig";
// The default priority of an overlay that has not been configured. Overlays with default
// priority have a higher precedence than configured overlays.
@VisibleForTesting
public static final int DEFAULT_PRIORITY = Integer.MAX_VALUE;
@VisibleForTesting
public static final class Configuration {
@Nullable
public final ParsedConfiguration parsedConfig;
public final int configIndex;
public Configuration(@Nullable ParsedConfiguration parsedConfig, int configIndex) {
this.parsedConfig = parsedConfig;
this.configIndex = configIndex;
}
}
/**
* Interface for providing information on scanned packages.
* TODO(147840005): Remove this when android:isStatic and android:priority are fully deprecated
*/
public interface PackageProvider {
/** Performs the given action for each package. */
void forEachPackage(BiConsumer<ParsingPackageRead, Boolean> p);
}
private static final Comparator<ParsedConfiguration> sStaticOverlayComparator = (c1, c2) -> {
final ParsedOverlayInfo o1 = c1.parsedInfo;
final ParsedOverlayInfo o2 = c2.parsedInfo;
Preconditions.checkArgument(o1.isStatic && o2.isStatic,
"attempted to sort non-static overlay");
if (!o1.targetPackageName.equals(o2.targetPackageName)) {
return o1.targetPackageName.compareTo(o2.targetPackageName);
}
final int comparedPriority = o1.priority - o2.priority;
return comparedPriority == 0 ? o1.path.compareTo(o2.path) : comparedPriority;
};
// Map of overlay package name to configured overlay settings
private final ArrayMap<String, Configuration> mConfigurations = new ArrayMap<>();
// Singleton instance only assigned in system server
private static OverlayConfig sInstance;
@VisibleForTesting
public OverlayConfig(@Nullable File rootDirectory,
@Nullable Supplier<OverlayScanner> scannerFactory,
@Nullable PackageProvider packageProvider) {
Preconditions.checkArgument((scannerFactory == null) != (packageProvider == null),
"scannerFactory and packageProvider cannot be both null or both non-null");
final ArrayList<OverlayPartition> partitions;
if (rootDirectory == null) {
partitions = new ArrayList<>(
PackagePartitions.getOrderedPartitions(OverlayPartition::new));
} else {
// Rebase the system partitions and settings file on the specified root directory.
partitions = new ArrayList<>(PackagePartitions.getOrderedPartitions(
p -> new OverlayPartition(new File(rootDirectory, p.folder.getPath()), p)));
}
boolean foundConfigFile = false;
ArrayList<ParsedOverlayInfo> packageManagerOverlayInfos = null;
final ArrayList<ParsedConfiguration> overlays = new ArrayList<>();
for (int i = 0, n = partitions.size(); i < n; i++) {
final OverlayPartition partition = partitions.get(i);
final OverlayScanner scanner = (scannerFactory == null) ? null : scannerFactory.get();
final ArrayList<ParsedConfiguration> partitionOverlays =
OverlayConfigParser.getConfigurations(partition, scanner);
if (partitionOverlays != null) {
foundConfigFile = true;
overlays.addAll(partitionOverlays);
continue;
}
// If the configuration file is not present, then use android:isStatic and
// android:priority to configure the overlays in the partition.
// TODO(147840005): Remove converting static overlays to immutable, default-enabled
// overlays when android:siStatic and android:priority are fully deprecated.
final ArrayList<ParsedOverlayInfo> partitionOverlayInfos;
if (scannerFactory != null) {
partitionOverlayInfos = new ArrayList<>(scanner.getAllParsedInfos());
} else {
if (packageManagerOverlayInfos == null) {
packageManagerOverlayInfos = getOverlayPackageInfos(packageProvider);
}
// Filter out overlays not present in the partition.
partitionOverlayInfos = new ArrayList<>(packageManagerOverlayInfos);
for (int j = partitionOverlayInfos.size() - 1; j >= 0; j--) {
if (!partition.containsPath(partitionOverlayInfos.get(j).path.getPath())) {
partitionOverlayInfos.remove(j);
}
}
}
// Static overlays are configured as immutable, default-enabled overlays.
final ArrayList<ParsedConfiguration> partitionConfigs = new ArrayList<>();
for (int j = 0, m = partitionOverlayInfos.size(); j < m; j++) {
final ParsedOverlayInfo p = partitionOverlayInfos.get(j);
if (p.isStatic) {
partitionConfigs.add(new ParsedConfiguration(p.packageName,
true /* enabled */, false /* mutable */, partition.policy, p));
}
}
partitionConfigs.sort(sStaticOverlayComparator);
overlays.addAll(partitionConfigs);
}
if (!foundConfigFile) {
// If no overlay configuration files exist, disregard partition precedence and allow
// android:priority to reorder overlays across partition boundaries.
overlays.sort(sStaticOverlayComparator);
}
for (int i = 0, n = overlays.size(); i < n; i++) {
// Add the configurations to a map so definitions of an overlay in an earlier
// partition can be replaced by an overlay with the same package name in a later
// partition.
final ParsedConfiguration config = overlays.get(i);
mConfigurations.put(config.packageName, new Configuration(config, i));
}
}
/**
* Creates an instance of OverlayConfig for use in the zygote process.
* This instance will not include information of static overlays existing outside of a partition
* overlay directory.
*/
@NonNull
public static OverlayConfig getZygoteInstance() {
Trace.traceBegin(Trace.TRACE_TAG_RRO, "OverlayConfig#getZygoteInstance");
try {
return new OverlayConfig(null /* rootDirectory */, OverlayScanner::new,
null /* packageProvider */);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RRO);
}
}
/**
* Initializes a singleton instance for use in the system process.
* Can only be called once. This instance is cached so future invocations of
* {@link #getSystemInstance()} will return the initialized instance.
*/
@NonNull
public static OverlayConfig initializeSystemInstance(PackageProvider packageProvider) {
Trace.traceBegin(Trace.TRACE_TAG_RRO, "OverlayConfig#initializeSystemInstance");
try {
sInstance = new OverlayConfig(null, null, packageProvider);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RRO);
}
return sInstance;
}
/**
* Retrieves the singleton instance initialized by
* {@link #initializeSystemInstance(PackageProvider)}.
*/
@NonNull
public static OverlayConfig getSystemInstance() {
if (sInstance == null) {
throw new IllegalStateException("System instance not initialized");
}
return sInstance;
}
@VisibleForTesting
@Nullable
public Configuration getConfiguration(@NonNull String packageName) {
return mConfigurations.get(packageName);
}
/**
* Returns whether the overlay is enabled by default.
* Overlays that are not configured are disabled by default.
*
* If an immutable overlay has its enabled state change, the new enabled state is applied to the
* overlay.
*
* When a mutable is first seen by the OverlayManagerService, the default-enabled state will be
* applied to the overlay. If the configured default-enabled state changes in a subsequent boot,
* the default-enabled state will not be applied to the overlay.
*
* The configured enabled state will only be applied when:
* <ul>
* <li> The device is factory reset
* <li> The overlay is removed from the device and added back to the device in a future OTA
* <li> The overlay changes its package name
* <li> The overlay changes its target package name or target overlayable name
* <li> An immutable overlay becomes mutable
* </ul>
*/
public boolean isEnabled(String packageName) {
final Configuration config = mConfigurations.get(packageName);
return config == null? OverlayConfigParser.DEFAULT_ENABLED_STATE
: config.parsedConfig.enabled;
}
/**
* Returns whether the overlay is mutable and can have its enabled state changed dynamically.
* Overlays that are not configured are mutable.
*/
public boolean isMutable(String packageName) {
final Configuration config = mConfigurations.get(packageName);
return config == null ? OverlayConfigParser.DEFAULT_MUTABILITY
: config.parsedConfig.mutable;
}
/**
* Returns an integer corresponding to the priority of the overlay.
* When multiple overlays override the same resource, the overlay with the highest priority will
* will have its value chosen. Overlays that are not configured have a priority of
* {@link Integer#MAX_VALUE}.
*/
public int getPriority(String packageName) {
final Configuration config = mConfigurations.get(packageName);
return config == null ? DEFAULT_PRIORITY : config.configIndex;
}
@NonNull
private ArrayList<Configuration> getSortedOverlays() {
final ArrayList<Configuration> sortedOverlays = new ArrayList<>();
for (int i = 0, n = mConfigurations.size(); i < n; i++) {
sortedOverlays.add(mConfigurations.valueAt(i));
}
sortedOverlays.sort(Comparator.comparingInt(o -> o.configIndex));
return sortedOverlays;
}
@NonNull
private static ArrayList<ParsedOverlayInfo> getOverlayPackageInfos(
@NonNull PackageProvider packageManager) {
final ArrayList<ParsedOverlayInfo> overlays = new ArrayList<>();
packageManager.forEachPackage((ParsingPackageRead p, Boolean isSystem) -> {
if (p.getOverlayTarget() != null && isSystem) {
overlays.add(new ParsedOverlayInfo(p.getPackageName(), p.getOverlayTarget(),
p.getTargetSdkVersion(), p.isOverlayIsStatic(), p.getOverlayPriority(),
new File(p.getBaseCodePath())));
}
});
return overlays;
}
/** Represents a single call to idmap create-multiple. */
@VisibleForTesting
public static class IdmapInvocation {
public final boolean enforceOverlayable;
public final String policy;
public final ArrayList<String> overlayPaths = new ArrayList<>();
IdmapInvocation(boolean enforceOverlayable, @NonNull String policy) {
this.enforceOverlayable = enforceOverlayable;
this.policy = policy;
}
@Override
public String toString() {
return getClass().getSimpleName() + String.format("{enforceOverlayable=%s, policy=%s"
+ ", overlayPaths=[%s]}", enforceOverlayable, policy,
String.join(", ", overlayPaths));
}
}
/**
* Retrieves a list of immutable framework overlays in order of least precedence to greatest
* precedence.
*/
@VisibleForTesting
public ArrayList<IdmapInvocation> getImmutableFrameworkOverlayIdmapInvocations() {
final ArrayList<IdmapInvocation> idmapInvocations = new ArrayList<>();
final ArrayList<Configuration> sortedConfigs = getSortedOverlays();
for (int i = 0, n = sortedConfigs.size(); i < n; i++) {
final Configuration overlay = sortedConfigs.get(i);
if (overlay.parsedConfig.mutable || !overlay.parsedConfig.enabled
|| !"android".equals(overlay.parsedConfig.parsedInfo.targetPackageName)) {
continue;
}
// Only enforce that overlays targeting packages with overlayable declarations abide by
// those declarations if the target sdk of the overlay is at least Q (when overlayable
// was introduced).
final boolean enforceOverlayable = overlay.parsedConfig.parsedInfo.targetSdkVersion
>= Build.VERSION_CODES.Q;
// Determine if the idmap for the current overlay can be generated in the last idmap
// create-multiple invocation.
IdmapInvocation invocation = null;
if (!idmapInvocations.isEmpty()) {
final IdmapInvocation last = idmapInvocations.get(idmapInvocations.size() - 1);
if (last.enforceOverlayable == enforceOverlayable
&& last.policy.equals(overlay.parsedConfig.policy)) {
invocation = last;
}
}
if (invocation == null) {
invocation = new IdmapInvocation(enforceOverlayable, overlay.parsedConfig.policy);
idmapInvocations.add(invocation);
}
invocation.overlayPaths.add(overlay.parsedConfig.parsedInfo.path.getAbsolutePath());
}
return idmapInvocations;
}
/**
* Creates idmap files for immutable overlays targeting the framework packages. Currently the
* android package is the only preloaded system package. Only the zygote can invoke this method.
*
* @return the paths of the created idmap files
*/
@NonNull
public String[] createImmutableFrameworkIdmapsInZygote() {
final String targetPath = "/system/framework/framework-res.apk";
final ArrayList<String> idmapPaths = new ArrayList<>();
final ArrayList<IdmapInvocation> idmapInvocations =
getImmutableFrameworkOverlayIdmapInvocations();
for (int i = 0, n = idmapInvocations.size(); i < n; i++) {
final IdmapInvocation invocation = idmapInvocations.get(i);
final String[] idmaps = createIdmap(targetPath,
invocation.overlayPaths.toArray(new String[0]),
new String[]{OverlayConfigParser.OverlayPartition.POLICY_PUBLIC,
invocation.policy},
invocation.enforceOverlayable);
if (idmaps == null) {
Log.w(TAG, "'idmap2 create-multiple' failed: no mutable=\"false\" overlays"
+ " targeting \"android\" will be loaded");
return new String[0];
}
idmapPaths.addAll(Arrays.asList(idmaps));
}
return idmapPaths.toArray(new String[0]);
}
/**
* For each overlay APK, this creates the idmap file that allows the overlay to override the
* target package.
*
* @return the paths of the created idmap
*/
private static native String[] createIdmap(@NonNull String targetPath,
@NonNull String[] overlayPath, @NonNull String[] policies, boolean enforceOverlayable);
}