| /* |
| * 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_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_SYSTEM_UPDATE_UNINSTALL; |
| import static android.content.om.OverlayInfo.STATE_TARGET_IS_BEING_REPLACED; |
| import static android.os.UserHandle.USER_SYSTEM; |
| |
| import static com.android.server.om.IdmapManager.IDMAP_IS_MODIFIED; |
| import static com.android.server.om.IdmapManager.IDMAP_IS_VERIFIED; |
| import static com.android.server.om.IdmapManager.IDMAP_NOT_EXIST; |
| 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.CriticalOverlayInfo; |
| import android.content.om.OverlayIdentifier; |
| import android.content.om.OverlayInfo; |
| import android.content.pm.UserPackage; |
| import android.content.pm.overlay.OverlayPaths; |
| import android.content.pm.parsing.FrameworkParsingPackageUtils; |
| import android.os.FabricatedOverlayInfo; |
| import android.os.FabricatedOverlayInternal; |
| import android.text.TextUtils; |
| import android.util.ArrayMap; |
| import android.util.ArraySet; |
| import android.util.Pair; |
| import android.util.Slog; |
| |
| import com.android.internal.content.om.OverlayConfig; |
| import com.android.internal.util.CollectionUtils; |
| import com.android.server.pm.pkg.AndroidPackage; |
| import com.android.server.pm.pkg.PackageState; |
| |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.function.Predicate; |
| |
| /** |
| * 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 OverlayInfo#STATE_TARGET_IS_BEING_REPLACED}. |
| */ |
| @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 static final int FLAG_SYSTEM_UPDATE_UNINSTALL = 1 << 2; |
| |
| private final PackageManagerHelper mPackageManager; |
| private final IdmapManager mIdmapManager; |
| private final OverlayManagerSettings mSettings; |
| private final OverlayConfig mOverlayConfig; |
| private final String[] mDefaultOverlays; |
| |
| /** |
| * 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 boolean mustReinitializeOverlay(@NonNull final AndroidPackage theTruth, |
| @Nullable final OverlayInfo oldSettings) { |
| if (oldSettings == null) { |
| return true; |
| } |
| if (!Objects.equals(theTruth.getOverlayTarget(), oldSettings.targetPackageName)) { |
| return true; |
| } |
| if (!Objects.equals(theTruth.getOverlayTargetOverlayableName(), |
| oldSettings.targetOverlayableName)) { |
| return true; |
| } |
| if (oldSettings.isFabricated) { |
| return true; |
| } |
| boolean isMutable = isPackageConfiguredMutable(theTruth); |
| if (isMutable != oldSettings.isMutable) { |
| return true; |
| } |
| // If an immutable overlay changes its configured enabled state, reinitialize the overlay. |
| if (!isMutable && isPackageConfiguredEnabled(theTruth) != oldSettings.isEnabled()) { |
| return true; |
| } |
| return false; |
| } |
| |
| private boolean mustReinitializeOverlay(@NonNull final FabricatedOverlayInfo theTruth, |
| @Nullable final OverlayInfo oldSettings) { |
| if (oldSettings == null) { |
| return true; |
| } |
| if (!Objects.equals(theTruth.targetPackageName, oldSettings.targetPackageName)) { |
| return true; |
| } |
| if (!Objects.equals(theTruth.targetOverlayable, oldSettings.targetOverlayableName)) { |
| return true; |
| } |
| return false; |
| } |
| |
| OverlayManagerServiceImpl(@NonNull final PackageManagerHelper packageManager, |
| @NonNull final IdmapManager idmapManager, |
| @NonNull final OverlayManagerSettings settings, |
| @NonNull final OverlayConfig overlayConfig, |
| @NonNull final String[] defaultOverlays) { |
| mPackageManager = packageManager; |
| mIdmapManager = idmapManager; |
| mSettings = settings; |
| mOverlayConfig = overlayConfig; |
| mDefaultOverlays = defaultOverlays; |
| } |
| |
| /** |
| * 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. |
| */ |
| @NonNull |
| ArraySet<UserPackage> updateOverlaysForUser(final int newUserId) { |
| if (DEBUG) { |
| Slog.d(TAG, "updateOverlaysForUser newUserId=" + newUserId); |
| } |
| |
| // Remove the settings of all overlays that are no longer installed for this user. |
| final ArraySet<UserPackage> updatedTargets = new ArraySet<>(); |
| final ArrayMap<String, PackageState> userPackages = mPackageManager.initializeForUser( |
| newUserId); |
| CollectionUtils.addAll(updatedTargets, removeOverlaysForUser( |
| (info) -> !userPackages.containsKey(info.packageName), newUserId)); |
| |
| final ArraySet<String> overlaidByOthers = new ArraySet<>(); |
| for (PackageState packageState : userPackages.values()) { |
| var pkg = packageState.getAndroidPackage(); |
| final String overlayTarget = pkg == null ? null : pkg.getOverlayTarget(); |
| if (!TextUtils.isEmpty(overlayTarget)) { |
| overlaidByOthers.add(overlayTarget); |
| } |
| } |
| |
| // Update the state of all installed packages containing overlays, and initialize new |
| // overlays that are not currently in the settings. |
| for (int i = 0, n = userPackages.size(); i < n; i++) { |
| final PackageState packageState = userPackages.valueAt(i); |
| var pkg = packageState.getAndroidPackage(); |
| if (pkg == null) { |
| continue; |
| } |
| |
| var packageName = packageState.getPackageName(); |
| try { |
| CollectionUtils.addAll(updatedTargets, |
| updatePackageOverlays(pkg, newUserId, 0 /* flags */)); |
| |
| // When a new user is switched to for the first time, package manager must be |
| // informed of the overlay paths for all overlaid packages installed in the user. |
| if (overlaidByOthers.contains(packageName)) { |
| updatedTargets.add(UserPackage.of(newUserId, packageName)); |
| } |
| } catch (OperationFailedException e) { |
| Slog.e(TAG, "failed to initialize overlays of '" + packageName |
| + "' for user " + newUserId + "", e); |
| } |
| } |
| |
| // Update the state of all fabricated overlays, and initialize fabricated overlays in the |
| // new user. |
| for (final FabricatedOverlayInfo info : getFabricatedOverlayInfos()) { |
| try { |
| CollectionUtils.addAll(updatedTargets, registerFabricatedOverlay( |
| info, newUserId)); |
| } catch (OperationFailedException e) { |
| Slog.e(TAG, "failed to initialize fabricated overlay of '" + info.path |
| + "' for user " + newUserId + "", e); |
| } |
| } |
| |
| // 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 { |
| // OverlayConfig is the new preferred way to enable overlays by default. This legacy |
| // default enabled method was created before overlays could have a name specified. |
| // Only allow enabling overlays without a name using this mechanism. |
| final OverlayIdentifier overlay = new OverlayIdentifier(defaultOverlay); |
| |
| final OverlayInfo oi = mSettings.getOverlayInfo(overlay, 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(overlay, newUserId, true); |
| if (updateState(oi, newUserId, 0)) { |
| CollectionUtils.add(updatedTargets, |
| UserPackage.of(oi.userId, oi.targetPackageName)); |
| } |
| } |
| } catch (OverlayManagerSettings.BadKeyException e) { |
| Slog.e(TAG, "Failed to set default overlay '" + defaultOverlay + "' for user " |
| + newUserId, e); |
| } |
| } |
| |
| cleanStaleResourceCache(); |
| return updatedTargets; |
| } |
| |
| void onUserRemoved(final int userId) { |
| if (DEBUG) { |
| Slog.d(TAG, "onUserRemoved userId=" + userId); |
| } |
| mSettings.removeUser(userId); |
| } |
| |
| @NonNull |
| Set<UserPackage> onPackageAdded(@NonNull final String pkgName, |
| final int userId) throws OperationFailedException { |
| final Set<UserPackage> updatedTargets = new ArraySet<>(); |
| // Always update the overlays of newly added packages. |
| updatedTargets.add(UserPackage.of(userId, pkgName)); |
| updatedTargets.addAll(reconcileSettingsForPackage(pkgName, userId, 0 /* flags */)); |
| return updatedTargets; |
| } |
| |
| @NonNull |
| Set<UserPackage> onPackageChanged(@NonNull final String pkgName, |
| final int userId) throws OperationFailedException { |
| return reconcileSettingsForPackage(pkgName, userId, 0 /* flags */); |
| } |
| |
| @NonNull |
| Set<UserPackage> onPackageReplacing(@NonNull final String pkgName, |
| boolean systemUpdateUninstall, final int userId) throws OperationFailedException { |
| int flags = FLAG_OVERLAY_IS_BEING_REPLACED; |
| if (systemUpdateUninstall) { |
| flags |= FLAG_SYSTEM_UPDATE_UNINSTALL; |
| } |
| return reconcileSettingsForPackage(pkgName, userId, flags); |
| } |
| |
| @NonNull |
| Set<UserPackage> onPackageReplaced(@NonNull final String pkgName, final int userId) |
| throws OperationFailedException { |
| return reconcileSettingsForPackage(pkgName, userId, 0 /* flags */); |
| } |
| |
| @NonNull |
| Set<UserPackage> onPackageRemoved(@NonNull final String pkgName, final int userId) { |
| if (DEBUG) { |
| Slog.d(TAG, "onPackageRemoved pkgName=" + pkgName + " userId=" + userId); |
| } |
| // Update the state of all overlays that target this package. |
| final Set<UserPackage> targets = updateOverlaysForTarget(pkgName, userId, 0 /* flags */); |
| |
| // Remove all the overlays this package declares. |
| return CollectionUtils.addAll(targets, |
| removeOverlaysForUser(oi -> pkgName.equals(oi.packageName), userId)); |
| } |
| |
| @NonNull |
| private Set<UserPackage> removeOverlaysForUser( |
| @NonNull final Predicate<OverlayInfo> condition, final int userId) { |
| final List<OverlayInfo> overlays = mSettings.removeIf( |
| io -> userId == io.userId && condition.test(io)); |
| Set<UserPackage> targets = Collections.emptySet(); |
| for (int i = 0, n = overlays.size(); i < n; i++) { |
| final OverlayInfo info = overlays.get(i); |
| targets = CollectionUtils.add(targets, |
| UserPackage.of(userId, info.targetPackageName)); |
| |
| // Remove the idmap if the overlay is no longer installed for any user. |
| removeIdmapIfPossible(info); |
| } |
| return targets; |
| } |
| |
| @NonNull |
| private Set<UserPackage> updateOverlaysForTarget(@NonNull final String targetPackage, |
| final int userId, final int flags) { |
| boolean modified = false; |
| final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackage, userId); |
| for (int i = 0, n = overlays.size(); i < n; i++) { |
| final OverlayInfo oi = overlays.get(i); |
| try { |
| modified |= updateState(oi, userId, flags); |
| } catch (OverlayManagerSettings.BadKeyException e) { |
| Slog.e(TAG, "failed to update settings", e); |
| modified |= mSettings.remove(oi.getOverlayIdentifier(), userId); |
| } |
| } |
| if (!modified) { |
| return Collections.emptySet(); |
| } |
| return Set.of(UserPackage.of(userId, targetPackage)); |
| } |
| |
| @NonNull |
| private Set<UserPackage> updatePackageOverlays(@NonNull AndroidPackage pkg, |
| final int userId, final int flags) throws OperationFailedException { |
| if (pkg.getOverlayTarget() == null) { |
| // This package does not have overlays declared in its manifest. |
| return Collections.emptySet(); |
| } |
| |
| Set<UserPackage> updatedTargets = Collections.emptySet(); |
| final OverlayIdentifier overlay = new OverlayIdentifier(pkg.getPackageName()); |
| final int priority = getPackageConfiguredPriority(pkg); |
| try { |
| OverlayInfo currentInfo = mSettings.getNullableOverlayInfo(overlay, userId); |
| if (mustReinitializeOverlay(pkg, currentInfo)) { |
| if (currentInfo != null) { |
| // If the targetPackageName has changed, the package that *used* to |
| // be the target must also update its assets. |
| updatedTargets = CollectionUtils.add(updatedTargets, |
| UserPackage.of(userId, currentInfo.targetPackageName)); |
| } |
| |
| currentInfo = mSettings.init(overlay, userId, pkg.getOverlayTarget(), |
| pkg.getOverlayTargetOverlayableName(), pkg.getSplits().get(0).getPath(), |
| isPackageConfiguredMutable(pkg), |
| isPackageConfiguredEnabled(pkg), |
| getPackageConfiguredPriority(pkg), pkg.getOverlayCategory(), |
| false); |
| } else if (priority != currentInfo.priority) { |
| // Changing the priority of an overlay does not cause its settings to be |
| // reinitialized. Reorder the overlay and update its target package. |
| mSettings.setPriority(overlay, userId, priority); |
| updatedTargets = CollectionUtils.add(updatedTargets, |
| UserPackage.of(userId, currentInfo.targetPackageName)); |
| } |
| |
| // Update the enabled state of the overlay. |
| if (updateState(currentInfo, userId, flags)) { |
| updatedTargets = CollectionUtils.add(updatedTargets, |
| UserPackage.of(userId, currentInfo.targetPackageName)); |
| } |
| } catch (OverlayManagerSettings.BadKeyException e) { |
| throw new OperationFailedException("failed to update settings", e); |
| } |
| return updatedTargets; |
| } |
| |
| @NonNull |
| private Set<UserPackage> reconcileSettingsForPackage(@NonNull final String pkgName, |
| final int userId, final int flags) throws OperationFailedException { |
| if (DEBUG) { |
| Slog.d(TAG, "reconcileSettingsForPackage pkgName=" + pkgName + " userId=" + userId); |
| } |
| |
| // Update the state of overlays that target this package. |
| Set<UserPackage> updatedTargets = Collections.emptySet(); |
| updatedTargets = CollectionUtils.addAll(updatedTargets, |
| updateOverlaysForTarget(pkgName, userId, flags)); |
| |
| // Realign the overlay settings with PackageManager's view of the package. |
| final PackageState packageState = mPackageManager.getPackageStateForUser(pkgName, userId); |
| var pkg = packageState == null ? null : packageState.getAndroidPackage(); |
| if (pkg == null) { |
| return onPackageRemoved(pkgName, userId); |
| } |
| |
| // Update the state of the overlays this package declares in its manifest. |
| updatedTargets = CollectionUtils.addAll(updatedTargets, |
| updatePackageOverlays(pkg, userId, flags)); |
| return updatedTargets; |
| } |
| |
| OverlayInfo getOverlayInfo(@NonNull final OverlayIdentifier 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); |
| } |
| |
| @NonNull |
| Set<UserPackage> setEnabled(@NonNull final OverlayIdentifier overlay, |
| final boolean enable, final int userId) throws OperationFailedException { |
| if (DEBUG) { |
| Slog.d(TAG, String.format("setEnabled overlay=%s enable=%s userId=%d", |
| overlay, enable, userId)); |
| } |
| |
| try { |
| final OverlayInfo oi = mSettings.getOverlayInfo(overlay, userId); |
| if (!oi.isMutable) { |
| // Ignore immutable overlays. |
| throw new OperationFailedException( |
| "cannot enable immutable overlay packages in runtime"); |
| } |
| |
| boolean modified = mSettings.setEnabled(overlay, userId, enable); |
| modified |= updateState(oi, userId, 0); |
| |
| if (modified) { |
| return Set.of(UserPackage.of(userId, oi.targetPackageName)); |
| } |
| return Set.of(); |
| } catch (OverlayManagerSettings.BadKeyException e) { |
| throw new OperationFailedException("failed to update settings", e); |
| } |
| } |
| |
| Optional<UserPackage> setEnabledExclusive(@NonNull final OverlayIdentifier overlay, |
| boolean withinCategory, final int userId) throws OperationFailedException { |
| if (DEBUG) { |
| Slog.d(TAG, String.format("setEnabledExclusive overlay=%s" |
| + " withinCategory=%s userId=%d", overlay, withinCategory, userId)); |
| } |
| |
| try { |
| final OverlayInfo enabledInfo = mSettings.getOverlayInfo(overlay, userId); |
| if (!enabledInfo.isMutable) { |
| throw new OperationFailedException( |
| "cannot enable immutable overlay packages in runtime"); |
| } |
| |
| // Remove the overlay to have enabled from the list of overlays to disable. |
| List<OverlayInfo> allOverlays = getOverlayInfosForTarget(enabledInfo.targetPackageName, |
| userId); |
| allOverlays.remove(enabledInfo); |
| |
| boolean modified = false; |
| for (int i = 0; i < allOverlays.size(); i++) { |
| final OverlayInfo disabledInfo = allOverlays.get(i); |
| final OverlayIdentifier disabledOverlay = disabledInfo.getOverlayIdentifier(); |
| if (!disabledInfo.isMutable) { |
| // Don't touch immutable overlays. |
| continue; |
| } |
| if (withinCategory && !Objects.equals(disabledInfo.category, |
| enabledInfo.category)) { |
| // Don't touch overlays from other categories. |
| continue; |
| } |
| |
| // Disable the overlay. |
| modified |= mSettings.setEnabled(disabledOverlay, userId, false); |
| modified |= updateState(disabledInfo, userId, 0); |
| } |
| |
| // Enable the selected overlay. |
| modified |= mSettings.setEnabled(overlay, userId, true); |
| modified |= updateState(enabledInfo, userId, 0); |
| |
| if (modified) { |
| return Optional.of(UserPackage.of(userId, enabledInfo.targetPackageName)); |
| } |
| return Optional.empty(); |
| } catch (OverlayManagerSettings.BadKeyException e) { |
| throw new OperationFailedException("failed to update settings", e); |
| } |
| } |
| |
| @NonNull |
| Set<UserPackage> registerFabricatedOverlay( |
| @NonNull final FabricatedOverlayInternal overlay) |
| throws OperationFailedException { |
| if (FrameworkParsingPackageUtils.validateName(overlay.overlayName, |
| false /* requireSeparator */, true /* requireFilename */) != null) { |
| throw new OperationFailedException( |
| "overlay name can only consist of alphanumeric characters, '_', and '.'"); |
| } |
| |
| final FabricatedOverlayInfo info = mIdmapManager.createFabricatedOverlay(overlay); |
| if (info == null) { |
| throw new OperationFailedException("failed to create fabricated overlay"); |
| } |
| |
| final Set<UserPackage> updatedTargets = new ArraySet<>(); |
| for (int userId : mSettings.getUsers()) { |
| updatedTargets.addAll(registerFabricatedOverlay(info, userId)); |
| } |
| return updatedTargets; |
| } |
| |
| @NonNull |
| private Set<UserPackage> registerFabricatedOverlay( |
| @NonNull final FabricatedOverlayInfo info, int userId) |
| throws OperationFailedException { |
| final OverlayIdentifier overlayIdentifier = new OverlayIdentifier( |
| info.packageName, info.overlayName); |
| |
| final Set<UserPackage> updatedTargets = new ArraySet<>(); |
| OverlayInfo oi = mSettings.getNullableOverlayInfo(overlayIdentifier, userId); |
| if (oi != null) { |
| if (!oi.isFabricated) { |
| throw new OperationFailedException("non-fabricated overlay with name '" + |
| oi.overlayName + "' already present in '" + oi.packageName + "'"); |
| } |
| } |
| try { |
| if (mustReinitializeOverlay(info, oi)) { |
| if (oi != null) { |
| // If the fabricated overlay changes its target package, update the previous |
| // target package so it no longer is overlaid. |
| updatedTargets.add(UserPackage.of(userId, oi.targetPackageName)); |
| } |
| oi = mSettings.init(overlayIdentifier, userId, info.targetPackageName, |
| info.targetOverlayable, info.path, true, false, |
| OverlayConfig.DEFAULT_PRIORITY, null, true); |
| } else { |
| // The only non-critical part of the info that will change is path to the fabricated |
| // overlay. |
| mSettings.setBaseCodePath(overlayIdentifier, userId, info.path); |
| } |
| if (updateState(oi, userId, 0)) { |
| updatedTargets.add(UserPackage.of(userId, oi.targetPackageName)); |
| } |
| } catch (OverlayManagerSettings.BadKeyException e) { |
| throw new OperationFailedException("failed to update settings", e); |
| } |
| |
| return updatedTargets; |
| } |
| |
| @NonNull |
| Set<UserPackage> unregisterFabricatedOverlay(@NonNull final OverlayIdentifier overlay) { |
| final Set<UserPackage> updatedTargets = new ArraySet<>(); |
| for (int userId : mSettings.getUsers()) { |
| updatedTargets.addAll(unregisterFabricatedOverlay(overlay, userId)); |
| } |
| return updatedTargets; |
| } |
| |
| @NonNull |
| private Set<UserPackage> unregisterFabricatedOverlay( |
| @NonNull final OverlayIdentifier overlay, int userId) { |
| final OverlayInfo oi = mSettings.getNullableOverlayInfo(overlay, userId); |
| if (oi != null) { |
| mSettings.remove(overlay, userId); |
| if (oi.isEnabled()) { |
| // Removing a fabricated overlay only changes the overlay path of a package if it is |
| // currently enabled. |
| return Set.of(UserPackage.of(userId, oi.targetPackageName)); |
| } |
| } |
| return Set.of(); |
| } |
| |
| |
| private void cleanStaleResourceCache() { |
| // Clean up fabricated overlays that are no longer registered in any user. |
| final Set<String> fabricatedPaths = mSettings.getAllBaseCodePaths(); |
| for (final FabricatedOverlayInfo info : mIdmapManager.getFabricatedOverlayInfos()) { |
| if (!fabricatedPaths.contains(info.path)) { |
| mIdmapManager.deleteFabricatedOverlay(info.path); |
| } |
| } |
| } |
| |
| /** |
| * Retrieves information about the fabricated overlays still in use. |
| * @return |
| */ |
| @NonNull |
| private List<FabricatedOverlayInfo> getFabricatedOverlayInfos() { |
| final Set<String> fabricatedPaths = mSettings.getAllBaseCodePaths(); |
| // Filter out stale fabricated overlays. |
| final ArrayList<FabricatedOverlayInfo> infos = new ArrayList<>( |
| mIdmapManager.getFabricatedOverlayInfos()); |
| infos.removeIf(info -> !fabricatedPaths.contains(info.path)); |
| return infos; |
| } |
| |
| private boolean isPackageConfiguredMutable(@NonNull final AndroidPackage overlay) { |
| // TODO(162841629): Support overlay name in OverlayConfig |
| return mOverlayConfig.isMutable(overlay.getPackageName()); |
| } |
| |
| private int getPackageConfiguredPriority(@NonNull final AndroidPackage overlay) { |
| // TODO(162841629): Support overlay name in OverlayConfig |
| return mOverlayConfig.getPriority(overlay.getPackageName()); |
| } |
| |
| private boolean isPackageConfiguredEnabled(@NonNull final AndroidPackage overlay) { |
| // TODO(162841629): Support overlay name in OverlayConfig |
| return mOverlayConfig.isEnabled(overlay.getPackageName()); |
| } |
| |
| Optional<UserPackage> setPriority(@NonNull final OverlayIdentifier overlay, |
| @NonNull final OverlayIdentifier newParentOverlay, final int userId) |
| throws OperationFailedException { |
| try { |
| if (DEBUG) { |
| Slog.d(TAG, "setPriority overlay=" + overlay + " newParentOverlay=" |
| + newParentOverlay + " userId=" + userId); |
| } |
| |
| final OverlayInfo overlayInfo = mSettings.getOverlayInfo(overlay, userId); |
| if (!overlayInfo.isMutable) { |
| // Ignore immutable overlays. |
| throw new OperationFailedException( |
| "cannot change priority of an immutable overlay package at runtime"); |
| } |
| |
| if (mSettings.setPriority(overlay, newParentOverlay, userId)) { |
| return Optional.of(UserPackage.of(userId, overlayInfo.targetPackageName)); |
| } |
| return Optional.empty(); |
| } catch (OverlayManagerSettings.BadKeyException e) { |
| throw new OperationFailedException("failed to update settings", e); |
| } |
| } |
| |
| Set<UserPackage> setHighestPriority(@NonNull final OverlayIdentifier overlay, |
| final int userId) throws OperationFailedException { |
| try{ |
| if (DEBUG) { |
| Slog.d(TAG, "setHighestPriority overlay=" + overlay + " userId=" + userId); |
| } |
| |
| final OverlayInfo overlayInfo = mSettings.getOverlayInfo(overlay, userId); |
| if (!overlayInfo.isMutable) { |
| // Ignore immutable overlays. |
| throw new OperationFailedException( |
| "cannot change priority of an immutable overlay package at runtime"); |
| } |
| |
| if (mSettings.setHighestPriority(overlay, userId)) { |
| return Set.of(UserPackage.of(userId, overlayInfo.targetPackageName)); |
| } |
| return Set.of(); |
| } catch (OverlayManagerSettings.BadKeyException e) { |
| throw new OperationFailedException("failed to update settings", e); |
| } |
| } |
| |
| Optional<UserPackage> setLowestPriority(@NonNull final OverlayIdentifier overlay, |
| final int userId) throws OperationFailedException { |
| try{ |
| if (DEBUG) { |
| Slog.d(TAG, "setLowestPriority packageName=" + overlay + " userId=" + userId); |
| } |
| |
| final OverlayInfo overlayInfo = mSettings.getOverlayInfo(overlay, userId); |
| if (!overlayInfo.isMutable) { |
| // Ignore immutable overlays. |
| throw new OperationFailedException( |
| "cannot change priority of an immutable overlay package at runtime"); |
| } |
| |
| if (mSettings.setLowestPriority(overlay, userId)) { |
| return Optional.of(UserPackage.of(userId, overlayInfo.targetPackageName)); |
| } |
| return Optional.empty(); |
| } catch (OverlayManagerSettings.BadKeyException e) { |
| throw new OperationFailedException("failed to update settings", e); |
| } |
| } |
| |
| void dump(@NonNull final PrintWriter pw, @NonNull DumpState dumpState) { |
| Pair<OverlayIdentifier, String> overlayIdmap = null; |
| if (dumpState.getPackageName() != null) { |
| OverlayIdentifier id = new OverlayIdentifier(dumpState.getPackageName(), |
| dumpState.getOverlayName()); |
| OverlayInfo oi = mSettings.getNullableOverlayInfo(id, USER_SYSTEM); |
| if (oi != null) { |
| overlayIdmap = new Pair<>(id, oi.baseCodePath); |
| } |
| } |
| |
| // settings |
| mSettings.dump(pw, dumpState); |
| |
| // idmap data |
| if (dumpState.getField() == null) { |
| Set<Pair<OverlayIdentifier, String>> allIdmaps = (overlayIdmap != null) |
| ? Set.of(overlayIdmap) : mSettings.getAllIdentifiersAndBaseCodePaths(); |
| for (Pair<OverlayIdentifier, String> pair : allIdmaps) { |
| pw.println("IDMAP OF " + pair.first); |
| String dump = mIdmapManager.dumpIdmap(pair.second); |
| if (dump != null) { |
| pw.println(dump); |
| } else { |
| OverlayInfo oi = mSettings.getNullableOverlayInfo(pair.first, USER_SYSTEM); |
| pw.println((oi != null && !mIdmapManager.idmapExists(oi)) |
| ? "<missing idmap>" : "<internal error>"); |
| } |
| } |
| } |
| |
| // default overlays |
| if (overlayIdmap == null) { |
| pw.println("Default overlays: " + TextUtils.join(";", mDefaultOverlays)); |
| } |
| |
| // overlay configurations |
| if (dumpState.getPackageName() == null) { |
| mOverlayConfig.dump(pw); |
| } |
| } |
| |
| @NonNull String[] getDefaultOverlayPackages() { |
| return mDefaultOverlays; |
| } |
| |
| void removeIdmapForOverlay(OverlayIdentifier overlay, int userId) |
| throws OperationFailedException { |
| try { |
| final OverlayInfo oi = mSettings.getOverlayInfo(overlay, userId); |
| removeIdmapIfPossible(oi); |
| } catch (OverlayManagerSettings.BadKeyException e) { |
| throw new OperationFailedException("failed to update settings", e); |
| } |
| } |
| |
| OverlayPaths getEnabledOverlayPaths(@NonNull final String targetPackageName, |
| final int userId, boolean includeImmutableOverlays) { |
| final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackageName, |
| userId); |
| final OverlayPaths.Builder paths = new OverlayPaths.Builder(); |
| final int n = overlays.size(); |
| for (int i = 0; i < n; i++) { |
| final OverlayInfo oi = overlays.get(i); |
| if (!oi.isEnabled()) { |
| continue; |
| } |
| if (!includeImmutableOverlays && !oi.isMutable) { |
| continue; |
| } |
| if (oi.isFabricated()) { |
| paths.addNonApkPath(oi.baseCodePath); |
| } else { |
| paths.addApkPath(oi.baseCodePath); |
| } |
| } |
| return paths.build(); |
| } |
| |
| /** |
| * Returns true if the settings/state was modified, false otherwise. |
| */ |
| private boolean updateState(@NonNull final CriticalOverlayInfo info, |
| final int userId, final int flags) throws OverlayManagerSettings.BadKeyException { |
| final OverlayIdentifier overlay = info.getOverlayIdentifier(); |
| var targetPackageState = |
| mPackageManager.getPackageStateForUser(info.getTargetPackageName(), userId); |
| var targetPackage = |
| targetPackageState == null ? null : targetPackageState.getAndroidPackage(); |
| |
| var overlayPackageState = |
| mPackageManager.getPackageStateForUser(info.getPackageName(), userId); |
| var overlayPackage = |
| overlayPackageState == null ? null : overlayPackageState.getAndroidPackage(); |
| |
| boolean modified = false; |
| if (overlayPackage == null) { |
| removeIdmapIfPossible(mSettings.getOverlayInfo(overlay, userId)); |
| return mSettings.remove(overlay, userId); |
| } |
| |
| modified |= mSettings.setCategory(overlay, userId, overlayPackage.getOverlayCategory()); |
| if (!info.isFabricated()) { |
| modified |= mSettings.setBaseCodePath(overlay, userId, |
| overlayPackage.getSplits().get(0).getPath()); |
| } |
| |
| // Immutable RROs targeting to "android", ie framework-res.apk, are handled by native |
| // layers. |
| final OverlayInfo updatedOverlayInfo = mSettings.getOverlayInfo(overlay, userId); |
| @IdmapManager.IdmapStatus int idmapStatus = IDMAP_NOT_EXIST; |
| if (targetPackage != null && !("android".equals(info.getTargetPackageName()) |
| && !isPackageConfiguredMutable(overlayPackage))) { |
| idmapStatus = mIdmapManager.createIdmap(targetPackage, overlayPackageState, |
| overlayPackage, updatedOverlayInfo.baseCodePath, overlay.getOverlayName(), |
| userId); |
| modified |= (idmapStatus & IDMAP_IS_MODIFIED) != 0; |
| } |
| |
| final @OverlayInfo.State int currentState = mSettings.getState(overlay, userId); |
| final @OverlayInfo.State int newState = calculateNewState(updatedOverlayInfo, targetPackage, |
| userId, flags, idmapStatus); |
| if (currentState != newState) { |
| if (DEBUG) { |
| Slog.d(TAG, String.format("%s:%d: %s -> %s", |
| overlay, userId, |
| OverlayInfo.stateToString(currentState), |
| OverlayInfo.stateToString(newState))); |
| } |
| modified |= mSettings.setState(overlay, userId, newState); |
| } |
| |
| return modified; |
| } |
| |
| private @OverlayInfo.State int calculateNewState(@NonNull final OverlayInfo info, |
| @Nullable final AndroidPackage targetPackage, final int userId, final int flags, |
| @IdmapManager.IdmapStatus final int idmapStatus) |
| 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; |
| } |
| |
| if ((flags & FLAG_SYSTEM_UPDATE_UNINSTALL) != 0) { |
| return STATE_SYSTEM_UPDATE_UNINSTALL; |
| } |
| |
| if (targetPackage == null) { |
| return STATE_MISSING_TARGET; |
| } |
| |
| if ((idmapStatus & IDMAP_IS_VERIFIED) == 0) { |
| if (!mIdmapManager.idmapExists(info)) { |
| return STATE_NO_IDMAP; |
| } |
| } |
| |
| final boolean enabled = mSettings.getEnabled(info.getOverlayIdentifier(), 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.getOverlayIdentifier(), 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); |
| } |
| |
| static final class OperationFailedException extends Exception { |
| OperationFailedException(@NonNull final String message) { |
| super(message); |
| } |
| |
| OperationFailedException(@NonNull final String message, @NonNull Throwable cause) { |
| super(message, cause); |
| } |
| } |
| |
| OverlayConfig getOverlayConfig() { |
| return mOverlayConfig; |
| } |
| } |