/*
|
* Copyright (C) 2016 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.server.om;
|
|
import static android.content.om.OverlayInfo.STATE_DISABLED;
|
import static android.content.om.OverlayInfo.STATE_ENABLED;
|
import static android.content.om.OverlayInfo.STATE_ENABLED_STATIC;
|
import static android.content.om.OverlayInfo.STATE_MISSING_TARGET;
|
import static android.content.om.OverlayInfo.STATE_NO_IDMAP;
|
import static android.content.om.OverlayInfo.STATE_OVERLAY_IS_BEING_REPLACED;
|
import static android.content.om.OverlayInfo.STATE_TARGET_IS_BEING_REPLACED;
|
|
import static com.android.server.om.OverlayManagerService.DEBUG;
|
import static com.android.server.om.OverlayManagerService.TAG;
|
|
import android.annotation.NonNull;
|
import android.annotation.Nullable;
|
import android.content.om.OverlayInfo;
|
import android.content.pm.ApplicationInfo;
|
import android.content.pm.PackageInfo;
|
import android.text.TextUtils;
|
import android.util.ArrayMap;
|
import android.util.ArraySet;
|
import android.util.Slog;
|
|
import com.android.internal.util.ArrayUtils;
|
|
import java.io.PrintWriter;
|
import java.util.ArrayList;
|
import java.util.Iterator;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Objects;
|
import java.util.Set;
|
|
/**
|
* Internal implementation of OverlayManagerService.
|
*
|
* Methods in this class should only be called by the OverlayManagerService.
|
* This class is not thread-safe; the caller is expected to ensure the
|
* necessary thread synchronization.
|
*
|
* @see OverlayManagerService
|
*/
|
final class OverlayManagerServiceImpl {
|
/**
|
* @deprecated Not used. See {@link android.content.om.OverlayInfo#STATE_TARGET_UPGRADING}.
|
*/
|
@Deprecated
|
private static final int FLAG_TARGET_IS_BEING_REPLACED = 1 << 0;
|
|
// Flags to use in conjunction with updateState.
|
private static final int FLAG_OVERLAY_IS_BEING_REPLACED = 1 << 1;
|
|
private final PackageManagerHelper mPackageManager;
|
private final IdmapManager mIdmapManager;
|
private final OverlayManagerSettings mSettings;
|
private final String[] mDefaultOverlays;
|
private final OverlayChangeListener mListener;
|
|
/**
|
* Helper method to merge the overlay manager's (as read from overlays.xml)
|
* and package manager's (as parsed from AndroidManifest.xml files) views
|
* on overlays.
|
*
|
* Both managers are usually in agreement, but especially after an OTA things
|
* may differ. The package manager is always providing the truth; the overlay
|
* manager has to adapt. Depending on what has changed about an overlay, we
|
* should either scrap the overlay manager's previous settings or merge the old
|
* settings with the new.
|
*/
|
private static boolean mustReinitializeOverlay(@NonNull final PackageInfo theTruth,
|
@Nullable final OverlayInfo oldSettings) {
|
if (oldSettings == null) {
|
return true;
|
}
|
if (!Objects.equals(theTruth.overlayTarget, oldSettings.targetPackageName)) {
|
return true;
|
}
|
if (!Objects.equals(theTruth.targetOverlayableName, oldSettings.targetOverlayableName)) {
|
return true;
|
}
|
if (theTruth.isStaticOverlayPackage() != oldSettings.isStatic) {
|
return true;
|
}
|
// a change in priority is only relevant for static RROs: specifically,
|
// a regular RRO should not have its state reset only because a change
|
// in priority
|
if (theTruth.isStaticOverlayPackage()
|
&& theTruth.overlayPriority != oldSettings.priority) {
|
return true;
|
}
|
return false;
|
}
|
|
OverlayManagerServiceImpl(@NonNull final PackageManagerHelper packageManager,
|
@NonNull final IdmapManager idmapManager,
|
@NonNull final OverlayManagerSettings settings,
|
@NonNull final String[] defaultOverlays,
|
@NonNull final OverlayChangeListener listener) {
|
mPackageManager = packageManager;
|
mIdmapManager = idmapManager;
|
mSettings = settings;
|
mDefaultOverlays = defaultOverlays;
|
mListener = listener;
|
}
|
|
/**
|
* Call this to synchronize the Settings for a user with what PackageManager knows about a user.
|
* Returns a list of target packages that must refresh their overlays. This list is the union
|
* of two sets: the set of targets with currently active overlays, and the
|
* set of targets that had, but no longer have, active overlays.
|
*/
|
ArrayList<String> updateOverlaysForUser(final int newUserId) {
|
if (DEBUG) {
|
Slog.d(TAG, "updateOverlaysForUser newUserId=" + newUserId);
|
}
|
|
final Set<String> packagesToUpdateAssets = new ArraySet<>();
|
final ArrayMap<String, List<OverlayInfo>> tmp = mSettings.getOverlaysForUser(newUserId);
|
final int tmpSize = tmp.size();
|
final ArrayMap<String, OverlayInfo> storedOverlayInfos = new ArrayMap<>(tmpSize);
|
for (int i = 0; i < tmpSize; i++) {
|
final List<OverlayInfo> chunk = tmp.valueAt(i);
|
final int chunkSize = chunk.size();
|
for (int j = 0; j < chunkSize; j++) {
|
final OverlayInfo oi = chunk.get(j);
|
storedOverlayInfos.put(oi.packageName, oi);
|
}
|
}
|
|
// Reset overlays if something critical like the target package name
|
// has changed
|
List<PackageInfo> overlayPackages = mPackageManager.getOverlayPackages(newUserId);
|
final int overlayPackagesSize = overlayPackages.size();
|
for (int i = 0; i < overlayPackagesSize; i++) {
|
final PackageInfo overlayPackage = overlayPackages.get(i);
|
final OverlayInfo oi = storedOverlayInfos.get(overlayPackage.packageName);
|
|
if (mustReinitializeOverlay(overlayPackage, oi)) {
|
// if targetPackageName has changed the package that *used* to
|
// be the target must also update its assets
|
if (oi != null) {
|
packagesToUpdateAssets.add(oi.targetPackageName);
|
}
|
|
mSettings.init(overlayPackage.packageName, newUserId,
|
overlayPackage.overlayTarget,
|
overlayPackage.targetOverlayableName,
|
overlayPackage.applicationInfo.getBaseCodePath(),
|
overlayPackage.isStaticOverlayPackage(),
|
overlayPackage.overlayPriority,
|
overlayPackage.overlayCategory);
|
}
|
|
storedOverlayInfos.remove(overlayPackage.packageName);
|
}
|
|
// any OverlayInfo left in storedOverlayInfos is no longer
|
// installed and should be removed
|
final int storedOverlayInfosSize = storedOverlayInfos.size();
|
for (int i = 0; i < storedOverlayInfosSize; i++) {
|
final OverlayInfo oi = storedOverlayInfos.valueAt(i);
|
mSettings.remove(oi.packageName, oi.userId);
|
removeIdmapIfPossible(oi);
|
packagesToUpdateAssets.add(oi.targetPackageName);
|
}
|
|
// make sure every overlay's state is up-to-date; this needs to happen
|
// after old overlays have been removed, or we risk removing a
|
// legitimate idmap file if a new overlay package has the same apk path
|
// as the removed overlay package used to have
|
for (int i = 0; i < overlayPackagesSize; i++) {
|
final PackageInfo overlayPackage = overlayPackages.get(i);
|
try {
|
updateState(overlayPackage.overlayTarget, overlayPackage.packageName,
|
newUserId, 0);
|
} catch (OverlayManagerSettings.BadKeyException e) {
|
Slog.e(TAG, "failed to update settings", e);
|
mSettings.remove(overlayPackage.packageName, newUserId);
|
}
|
packagesToUpdateAssets.add(overlayPackage.overlayTarget);
|
}
|
|
// remove target packages that are not installed
|
final Iterator<String> iter = packagesToUpdateAssets.iterator();
|
while (iter.hasNext()) {
|
String targetPackageName = iter.next();
|
if (mPackageManager.getPackageInfo(targetPackageName, newUserId) == null) {
|
iter.remove();
|
}
|
}
|
|
// Collect all of the categories in which we have at least one overlay enabled.
|
final ArraySet<String> enabledCategories = new ArraySet<>();
|
final ArrayMap<String, List<OverlayInfo>> userOverlays =
|
mSettings.getOverlaysForUser(newUserId);
|
final int userOverlayTargetCount = userOverlays.size();
|
for (int i = 0; i < userOverlayTargetCount; i++) {
|
final List<OverlayInfo> overlayList = userOverlays.valueAt(i);
|
final int overlayCount = overlayList != null ? overlayList.size() : 0;
|
for (int j = 0; j < overlayCount; j++) {
|
final OverlayInfo oi = overlayList.get(j);
|
if (oi.isEnabled()) {
|
enabledCategories.add(oi.category);
|
}
|
}
|
}
|
|
// Enable the default overlay if its category does not have a single overlay enabled.
|
for (final String defaultOverlay : mDefaultOverlays) {
|
try {
|
final OverlayInfo oi = mSettings.getOverlayInfo(defaultOverlay, newUserId);
|
if (!enabledCategories.contains(oi.category)) {
|
Slog.w(TAG, "Enabling default overlay '" + defaultOverlay + "' for target '"
|
+ oi.targetPackageName + "' in category '" + oi.category + "' for user "
|
+ newUserId);
|
mSettings.setEnabled(oi.packageName, newUserId, true);
|
if (updateState(oi.targetPackageName, oi.packageName, newUserId, 0)) {
|
packagesToUpdateAssets.add(oi.targetPackageName);
|
}
|
}
|
} catch (OverlayManagerSettings.BadKeyException e) {
|
Slog.e(TAG, "Failed to set default overlay '" + defaultOverlay + "' for user "
|
+ newUserId, e);
|
}
|
}
|
|
return new ArrayList<>(packagesToUpdateAssets);
|
}
|
|
void onUserRemoved(final int userId) {
|
if (DEBUG) {
|
Slog.d(TAG, "onUserRemoved userId=" + userId);
|
}
|
mSettings.removeUser(userId);
|
}
|
|
void onTargetPackageAdded(@NonNull final String packageName, final int userId) {
|
if (DEBUG) {
|
Slog.d(TAG, "onTargetPackageAdded packageName=" + packageName + " userId=" + userId);
|
}
|
|
updateAndRefreshOverlaysForTarget(packageName, userId, 0);
|
}
|
|
void onTargetPackageChanged(@NonNull final String packageName, final int userId) {
|
if (DEBUG) {
|
Slog.d(TAG, "onTargetPackageChanged packageName=" + packageName + " userId=" + userId);
|
}
|
|
updateAndRefreshOverlaysForTarget(packageName, userId, 0);
|
}
|
|
void onTargetPackageReplacing(@NonNull final String packageName, final int userId) {
|
if (DEBUG) {
|
Slog.d(TAG, "onTargetPackageReplacing packageName=" + packageName + " userId="
|
+ userId);
|
}
|
|
updateAndRefreshOverlaysForTarget(packageName, userId, 0);
|
}
|
|
void onTargetPackageReplaced(@NonNull final String packageName, final int userId) {
|
if (DEBUG) {
|
Slog.d(TAG, "onTargetPackageReplaced packageName=" + packageName + " userId=" + userId);
|
}
|
|
updateAndRefreshOverlaysForTarget(packageName, userId, 0);
|
}
|
|
void onTargetPackageRemoved(@NonNull final String packageName, final int userId) {
|
if (DEBUG) {
|
Slog.d(TAG, "onTargetPackageRemoved packageName=" + packageName + " userId=" + userId);
|
}
|
|
updateAndRefreshOverlaysForTarget(packageName, userId, 0);
|
}
|
|
/**
|
* Update the state of any overlays for this target.
|
*/
|
private void updateAndRefreshOverlaysForTarget(@NonNull final String targetPackageName,
|
final int userId, final int flags) {
|
final List<OverlayInfo> targetOverlays = mSettings.getOverlaysForTarget(targetPackageName,
|
userId);
|
|
// Update the state for any overlay that targets this package.
|
boolean modified = false;
|
for (final OverlayInfo oi : targetOverlays) {
|
final PackageInfo overlayPackage = mPackageManager.getPackageInfo(oi.packageName,
|
userId);
|
if (overlayPackage == null) {
|
modified |= mSettings.remove(oi.packageName, oi.userId);
|
removeIdmapIfPossible(oi);
|
} else {
|
try {
|
modified |= updateState(targetPackageName, oi.packageName, userId, flags);
|
} catch (OverlayManagerSettings.BadKeyException e) {
|
Slog.e(TAG, "failed to update settings", e);
|
modified |= mSettings.remove(oi.packageName, userId);
|
}
|
}
|
}
|
|
if (!modified) {
|
// Update the overlay paths of the target within package manager if necessary.
|
final List<String> enabledOverlayPaths = new ArrayList<>(targetOverlays.size());
|
|
// Framework overlays are first in the overlay paths of a package within PackageManager.
|
for (final OverlayInfo oi : mSettings.getOverlaysForTarget("android", userId)) {
|
if (oi.isEnabled()) {
|
enabledOverlayPaths.add(oi.baseCodePath);
|
}
|
}
|
|
for (final OverlayInfo oi : targetOverlays) {
|
if (oi.isEnabled()) {
|
enabledOverlayPaths.add(oi.baseCodePath);
|
}
|
}
|
|
// TODO(): Use getEnabledOverlayPaths(userId, targetPackageName) instead of
|
// resourceDirs if in the future resourceDirs contains APKs other than overlays
|
PackageInfo packageInfo = mPackageManager.getPackageInfo(targetPackageName, userId);
|
ApplicationInfo appInfo = packageInfo == null ? null : packageInfo.applicationInfo;
|
String[] resourceDirs = appInfo == null ? null : appInfo.resourceDirs;
|
|
// If the lists aren't the same length, the enabled overlays have changed
|
if (ArrayUtils.size(resourceDirs) != enabledOverlayPaths.size()) {
|
modified = true;
|
} else if (resourceDirs != null) {
|
// If any element isn't equal, an overlay or the order of overlays has changed
|
for (int index = 0; index < resourceDirs.length; index++) {
|
if (!resourceDirs[index].equals(enabledOverlayPaths.get(index))) {
|
modified = true;
|
break;
|
}
|
}
|
}
|
}
|
|
if (modified) {
|
mListener.onOverlaysChanged(targetPackageName, userId);
|
}
|
}
|
|
void onOverlayPackageAdded(@NonNull final String packageName, final int userId) {
|
if (DEBUG) {
|
Slog.d(TAG, "onOverlayPackageAdded packageName=" + packageName + " userId=" + userId);
|
}
|
|
final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
|
if (overlayPackage == null) {
|
Slog.w(TAG, "overlay package " + packageName + " was added, but couldn't be found");
|
onOverlayPackageRemoved(packageName, userId);
|
return;
|
}
|
|
mSettings.init(packageName, userId, overlayPackage.overlayTarget,
|
overlayPackage.targetOverlayableName,
|
overlayPackage.applicationInfo.getBaseCodePath(),
|
overlayPackage.isStaticOverlayPackage(), overlayPackage.overlayPriority,
|
overlayPackage.overlayCategory);
|
try {
|
if (updateState(overlayPackage.overlayTarget, packageName, userId, 0)) {
|
mListener.onOverlaysChanged(overlayPackage.overlayTarget, userId);
|
}
|
} catch (OverlayManagerSettings.BadKeyException e) {
|
Slog.e(TAG, "failed to update settings", e);
|
mSettings.remove(packageName, userId);
|
}
|
}
|
|
void onOverlayPackageChanged(@NonNull final String packageName, final int userId) {
|
if (DEBUG) {
|
Slog.d(TAG, "onOverlayPackageChanged packageName=" + packageName + " userId=" + userId);
|
}
|
|
try {
|
final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
|
if (updateState(oi.targetPackageName, packageName, userId, 0)) {
|
mListener.onOverlaysChanged(oi.targetPackageName, userId);
|
}
|
} catch (OverlayManagerSettings.BadKeyException e) {
|
Slog.e(TAG, "failed to update settings", e);
|
}
|
}
|
|
void onOverlayPackageReplacing(@NonNull final String packageName, final int userId) {
|
if (DEBUG) {
|
Slog.d(TAG, "onOverlayPackageReplacing packageName=" + packageName + " userId="
|
+ userId);
|
}
|
|
try {
|
final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
|
if (updateState(oi.targetPackageName, packageName, userId,
|
FLAG_OVERLAY_IS_BEING_REPLACED)) {
|
removeIdmapIfPossible(oi);
|
mListener.onOverlaysChanged(oi.targetPackageName, userId);
|
}
|
} catch (OverlayManagerSettings.BadKeyException e) {
|
Slog.e(TAG, "failed to update settings", e);
|
}
|
}
|
|
void onOverlayPackageReplaced(@NonNull final String packageName, final int userId) {
|
if (DEBUG) {
|
Slog.d(TAG, "onOverlayPackageReplaced packageName=" + packageName + " userId="
|
+ userId);
|
}
|
|
final PackageInfo pkg = mPackageManager.getPackageInfo(packageName, userId);
|
if (pkg == null) {
|
Slog.w(TAG, "overlay package " + packageName + " was replaced, but couldn't be found");
|
onOverlayPackageRemoved(packageName, userId);
|
return;
|
}
|
|
try {
|
final OverlayInfo oldOi = mSettings.getOverlayInfo(packageName, userId);
|
if (mustReinitializeOverlay(pkg, oldOi)) {
|
if (oldOi != null && !oldOi.targetPackageName.equals(pkg.overlayTarget)) {
|
mListener.onOverlaysChanged(pkg.overlayTarget, userId);
|
}
|
mSettings.init(packageName, userId, pkg.overlayTarget, pkg.targetOverlayableName,
|
pkg.applicationInfo.getBaseCodePath(), pkg.isStaticOverlayPackage(),
|
pkg.overlayPriority, pkg.overlayCategory);
|
}
|
|
if (updateState(pkg.overlayTarget, packageName, userId, 0)) {
|
mListener.onOverlaysChanged(pkg.overlayTarget, userId);
|
}
|
} catch (OverlayManagerSettings.BadKeyException e) {
|
Slog.e(TAG, "failed to update settings", e);
|
}
|
}
|
|
void onOverlayPackageRemoved(@NonNull final String packageName, final int userId) {
|
try {
|
final OverlayInfo overlayInfo = mSettings.getOverlayInfo(packageName, userId);
|
if (mSettings.remove(packageName, userId)) {
|
removeIdmapIfPossible(overlayInfo);
|
mListener.onOverlaysChanged(overlayInfo.targetPackageName, userId);
|
}
|
} catch (OverlayManagerSettings.BadKeyException e) {
|
Slog.e(TAG, "failed to remove overlay", e);
|
}
|
}
|
|
OverlayInfo getOverlayInfo(@NonNull final String packageName, final int userId) {
|
try {
|
return mSettings.getOverlayInfo(packageName, userId);
|
} catch (OverlayManagerSettings.BadKeyException e) {
|
return null;
|
}
|
}
|
|
List<OverlayInfo> getOverlayInfosForTarget(@NonNull final String targetPackageName,
|
final int userId) {
|
return mSettings.getOverlaysForTarget(targetPackageName, userId);
|
}
|
|
Map<String, List<OverlayInfo>> getOverlaysForUser(final int userId) {
|
return mSettings.getOverlaysForUser(userId);
|
}
|
|
boolean setEnabled(@NonNull final String packageName, final boolean enable,
|
final int userId) {
|
if (DEBUG) {
|
Slog.d(TAG, String.format("setEnabled packageName=%s enable=%s userId=%d",
|
packageName, enable, userId));
|
}
|
|
final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
|
if (overlayPackage == null) {
|
return false;
|
}
|
|
// Ignore static overlays.
|
if (overlayPackage.isStaticOverlayPackage()) {
|
return false;
|
}
|
|
try {
|
final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
|
boolean modified = mSettings.setEnabled(packageName, userId, enable);
|
modified |= updateState(oi.targetPackageName, oi.packageName, userId, 0);
|
|
if (modified) {
|
mListener.onOverlaysChanged(oi.targetPackageName, userId);
|
}
|
return true;
|
} catch (OverlayManagerSettings.BadKeyException e) {
|
return false;
|
}
|
}
|
|
boolean setEnabledExclusive(@NonNull final String packageName, boolean withinCategory,
|
final int userId) {
|
if (DEBUG) {
|
Slog.d(TAG, String.format("setEnabledExclusive packageName=%s"
|
+ " withinCategory=%s userId=%d", packageName, withinCategory, userId));
|
}
|
|
final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
|
if (overlayPackage == null) {
|
return false;
|
}
|
|
try {
|
final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
|
final String targetPackageName = oi.targetPackageName;
|
|
List<OverlayInfo> allOverlays = getOverlayInfosForTarget(targetPackageName, userId);
|
|
boolean modified = false;
|
|
// Disable all other overlays.
|
allOverlays.remove(oi);
|
for (int i = 0; i < allOverlays.size(); i++) {
|
final String disabledOverlayPackageName = allOverlays.get(i).packageName;
|
final PackageInfo disabledOverlayPackageInfo = mPackageManager.getPackageInfo(
|
disabledOverlayPackageName, userId);
|
if (disabledOverlayPackageInfo == null) {
|
modified |= mSettings.remove(disabledOverlayPackageName, userId);
|
continue;
|
}
|
|
if (disabledOverlayPackageInfo.isStaticOverlayPackage()) {
|
// Don't touch static overlays.
|
continue;
|
}
|
if (withinCategory && !Objects.equals(disabledOverlayPackageInfo.overlayCategory,
|
oi.category)) {
|
// Don't touch overlays from other categories.
|
continue;
|
}
|
|
// Disable the overlay.
|
modified |= mSettings.setEnabled(disabledOverlayPackageName, userId, false);
|
modified |= updateState(targetPackageName, disabledOverlayPackageName, userId, 0);
|
}
|
|
// Enable the selected overlay.
|
modified |= mSettings.setEnabled(packageName, userId, true);
|
modified |= updateState(targetPackageName, packageName, userId, 0);
|
|
if (modified) {
|
mListener.onOverlaysChanged(targetPackageName, userId);
|
}
|
return true;
|
} catch (OverlayManagerSettings.BadKeyException e) {
|
return false;
|
}
|
}
|
|
private boolean isPackageUpdatableOverlay(@NonNull final String packageName, final int userId) {
|
final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
|
if (overlayPackage == null || overlayPackage.isStaticOverlayPackage()) {
|
return false;
|
}
|
return true;
|
}
|
|
boolean setPriority(@NonNull final String packageName,
|
@NonNull final String newParentPackageName, final int userId) {
|
if (DEBUG) {
|
Slog.d(TAG, "setPriority packageName=" + packageName + " newParentPackageName="
|
+ newParentPackageName + " userId=" + userId);
|
}
|
|
if (!isPackageUpdatableOverlay(packageName, userId)) {
|
return false;
|
}
|
|
final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
|
if (overlayPackage == null) {
|
return false;
|
}
|
|
if (mSettings.setPriority(packageName, newParentPackageName, userId)) {
|
mListener.onOverlaysChanged(overlayPackage.overlayTarget, userId);
|
}
|
return true;
|
}
|
|
boolean setHighestPriority(@NonNull final String packageName, final int userId) {
|
if (DEBUG) {
|
Slog.d(TAG, "setHighestPriority packageName=" + packageName + " userId=" + userId);
|
}
|
|
if (!isPackageUpdatableOverlay(packageName, userId)) {
|
return false;
|
}
|
|
final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
|
if (overlayPackage == null) {
|
return false;
|
}
|
|
if (mSettings.setHighestPriority(packageName, userId)) {
|
mListener.onOverlaysChanged(overlayPackage.overlayTarget, userId);
|
}
|
return true;
|
}
|
|
boolean setLowestPriority(@NonNull final String packageName, final int userId) {
|
if (DEBUG) {
|
Slog.d(TAG, "setLowestPriority packageName=" + packageName + " userId=" + userId);
|
}
|
|
if (!isPackageUpdatableOverlay(packageName, userId)) {
|
return false;
|
}
|
|
final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
|
if (overlayPackage == null) {
|
return false;
|
}
|
|
if (mSettings.setLowestPriority(packageName, userId)) {
|
mListener.onOverlaysChanged(overlayPackage.overlayTarget, userId);
|
}
|
return true;
|
}
|
|
void dump(@NonNull final PrintWriter pw, @NonNull DumpState dumpState) {
|
mSettings.dump(pw, dumpState);
|
if (dumpState.getPackageName() == null) {
|
pw.println("Default overlays: " + TextUtils.join(";", mDefaultOverlays));
|
}
|
}
|
|
@NonNull String[] getDefaultOverlayPackages() {
|
return mDefaultOverlays;
|
}
|
|
List<String> getEnabledOverlayPackageNames(@NonNull final String targetPackageName,
|
final int userId) {
|
final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackageName,
|
userId);
|
final List<String> paths = new ArrayList<>(overlays.size());
|
final int n = overlays.size();
|
for (int i = 0; i < n; i++) {
|
final OverlayInfo oi = overlays.get(i);
|
if (oi.isEnabled()) {
|
paths.add(oi.packageName);
|
}
|
}
|
return paths;
|
}
|
|
/**
|
* Returns true if the settings/state was modified, false otherwise.
|
*/
|
private boolean updateState(@NonNull final String targetPackageName,
|
@NonNull final String overlayPackageName, final int userId, final int flags)
|
throws OverlayManagerSettings.BadKeyException {
|
|
final PackageInfo targetPackage = mPackageManager.getPackageInfo(targetPackageName, userId);
|
final PackageInfo overlayPackage = mPackageManager.getPackageInfo(overlayPackageName,
|
userId);
|
|
// Static RROs targeting to "android", ie framework-res.apk, are handled by native layers.
|
if (targetPackage != null && overlayPackage != null
|
&& !("android".equals(targetPackageName)
|
&& overlayPackage.isStaticOverlayPackage())) {
|
mIdmapManager.createIdmap(targetPackage, overlayPackage, userId);
|
}
|
|
boolean modified = false;
|
if (overlayPackage != null) {
|
modified |= mSettings.setBaseCodePath(overlayPackageName, userId,
|
overlayPackage.applicationInfo.getBaseCodePath());
|
modified |= mSettings.setCategory(overlayPackageName, userId,
|
overlayPackage.overlayCategory);
|
}
|
|
final @OverlayInfo.State int currentState = mSettings.getState(overlayPackageName, userId);
|
final @OverlayInfo.State int newState = calculateNewState(targetPackage, overlayPackage,
|
userId, flags);
|
if (currentState != newState) {
|
if (DEBUG) {
|
Slog.d(TAG, String.format("%s:%d: %s -> %s",
|
overlayPackageName, userId,
|
OverlayInfo.stateToString(currentState),
|
OverlayInfo.stateToString(newState)));
|
}
|
modified |= mSettings.setState(overlayPackageName, userId, newState);
|
}
|
return modified;
|
}
|
|
private @OverlayInfo.State int calculateNewState(@Nullable final PackageInfo targetPackage,
|
@Nullable final PackageInfo overlayPackage, final int userId, final int flags)
|
throws OverlayManagerSettings.BadKeyException {
|
|
if ((flags & FLAG_TARGET_IS_BEING_REPLACED) != 0) {
|
return STATE_TARGET_IS_BEING_REPLACED;
|
}
|
|
if ((flags & FLAG_OVERLAY_IS_BEING_REPLACED) != 0) {
|
return STATE_OVERLAY_IS_BEING_REPLACED;
|
}
|
|
// assert expectation on overlay package: can only be null if the flags are used
|
if (DEBUG && overlayPackage == null) {
|
throw new IllegalArgumentException("null overlay package not compatible with no flags");
|
}
|
|
if (targetPackage == null) {
|
return STATE_MISSING_TARGET;
|
}
|
|
if (!mIdmapManager.idmapExists(overlayPackage, userId)) {
|
return STATE_NO_IDMAP;
|
}
|
|
if (overlayPackage.isStaticOverlayPackage()) {
|
return STATE_ENABLED_STATIC;
|
}
|
|
final boolean enabled = mSettings.getEnabled(overlayPackage.packageName, userId);
|
return enabled ? STATE_ENABLED : STATE_DISABLED;
|
}
|
|
private void removeIdmapIfPossible(@NonNull final OverlayInfo oi) {
|
// For a given package, all Android users share the same idmap file.
|
// This works because Android currently does not support users to
|
// install different versions of the same package. It also means we
|
// cannot remove an idmap file if any user still needs it.
|
//
|
// When/if the Android framework allows different versions of the same
|
// package to be installed for different users, idmap file handling
|
// should be revised:
|
//
|
// - an idmap file should be unique for each {user, package} pair
|
//
|
// - the path to the idmap file should be passed to the native Asset
|
// Manager layers, just like the path to the apk is passed today
|
//
|
// As part of that change, calls to this method should be replaced by
|
// direct calls to IdmapManager.removeIdmap, without looping over all
|
// users.
|
|
if (!mIdmapManager.idmapExists(oi)) {
|
return;
|
}
|
final int[] userIds = mSettings.getUsers();
|
for (int userId : userIds) {
|
try {
|
final OverlayInfo tmp = mSettings.getOverlayInfo(oi.packageName, userId);
|
if (tmp != null && tmp.isEnabled()) {
|
// someone is still using the idmap file -> we cannot remove it
|
return;
|
}
|
} catch (OverlayManagerSettings.BadKeyException e) {
|
// intentionally left empty
|
}
|
}
|
mIdmapManager.removeIdmap(oi, oi.userId);
|
}
|
|
interface OverlayChangeListener {
|
|
/**
|
* An event triggered by changes made to overlay state or settings as well as changes that
|
* add or remove target packages of overlays.
|
**/
|
void onOverlaysChanged(@NonNull String targetPackage, int userId);
|
}
|
|
interface PackageManagerHelper {
|
PackageInfo getPackageInfo(@NonNull String packageName, int userId);
|
boolean signaturesMatching(@NonNull String packageName1, @NonNull String packageName2,
|
int userId);
|
List<PackageInfo> getOverlayPackages(int userId);
|
}
|
}
|