| /* |
| * 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_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.content.om.OverlayConfig; |
| 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 OverlayConfig mOverlayConfig; |
| 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 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; |
| } |
| |
| boolean isMutable = isPackageConfiguredMutable(theTruth.packageName); |
| if (isMutable != oldSettings.isMutable) { |
| return true; |
| } |
| |
| if (getPackageConfiguredPriority(theTruth.packageName) != oldSettings.priority) { |
| return true; |
| } |
| |
| // If an immutable overlay changes its configured enabled state, reinitialize the overlay. |
| if (!isMutable && isPackageConfiguredEnabled(theTruth.packageName) |
| != oldSettings.isEnabled()) { |
| 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, |
| @NonNull final OverlayChangeListener listener) { |
| mPackageManager = packageManager; |
| mIdmapManager = idmapManager; |
| mSettings = settings; |
| mOverlayConfig = overlayConfig; |
| 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(), |
| isPackageConfiguredMutable(overlayPackage.packageName), |
| isPackageConfiguredEnabled(overlayPackage.packageName), |
| getPackageConfiguredPriority(overlayPackage.packageName), |
| 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(), |
| isPackageConfiguredMutable(overlayPackage.packageName), |
| isPackageConfiguredEnabled(overlayPackage.packageName), |
| getPackageConfiguredPriority(overlayPackage.packageName), |
| 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(), |
| isPackageConfiguredMutable(pkg.packageName), |
| isPackageConfiguredEnabled(pkg.packageName), |
| getPackageConfiguredPriority(pkg.packageName), 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; |
| } |
| |
| try { |
| final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId); |
| if (!oi.isMutable) { |
| // Ignore immutable overlays. |
| return false; |
| } |
| |
| 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 OverlayInfo disabledInfo = allOverlays.get(i); |
| final String disabledOverlayPackageName = disabledInfo.packageName; |
| final PackageInfo disabledOverlayPackageInfo = mPackageManager.getPackageInfo( |
| disabledOverlayPackageName, userId); |
| if (disabledOverlayPackageInfo == null) { |
| modified |= mSettings.remove(disabledOverlayPackageName, userId); |
| continue; |
| } |
| |
| if (!disabledInfo.isMutable) { |
| // Don't touch immutable 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 isPackageConfiguredMutable(@NonNull final String packageName) { |
| return mOverlayConfig.isMutable(packageName); |
| } |
| |
| private int getPackageConfiguredPriority(@NonNull final String packageName) { |
| return mOverlayConfig.getPriority(packageName); |
| } |
| |
| private boolean isPackageConfiguredEnabled(@NonNull final String packageName) { |
| return mOverlayConfig.isEnabled(packageName); |
| } |
| |
| 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 (!isPackageConfiguredMutable(packageName)) { |
| 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 (!isPackageConfiguredMutable(packageName)) { |
| 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 (!isPackageConfiguredMutable(packageName)) { |
| 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; |
| } |
| |
| void removeIdmapForOverlay(String packageName, int userId) { |
| final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId); |
| removeIdmapIfPossible(oi); |
| } |
| |
| 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); |
| |
| // Immutable RROs targeting to "android", ie framework-res.apk, are handled by native layers. |
| if (targetPackage != null && overlayPackage != null |
| && !("android".equals(targetPackageName) |
| && !isPackageConfiguredMutable(overlayPackageName))) { |
| 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; |
| } |
| |
| 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); |
| } |
| } |