From ac6bbdb6b08e131743b2869a7454a1df752edf50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A5rten=20Kongstad?= Date: Fri, 14 Feb 2020 15:49:53 +0100 Subject: [PATCH] OMS: add transactional API Implement a transactional API for the OMS. This allows a caller to batch together individual operations, and apply those operations atomically: either they all succeed (and notifications are sent out accordingly), or some operation fails (and the transaction is cancelled, any internal changes are rolled back, and no notifications are sent out). As an added bonus, batching OMS operations result in fewer requests to the system to refresh affected packages. This commit adds support for transactions but leaves the old one-shot API intact. The old API should be considered deprecated and will be removed at some point in the future. Note: the old API provided both setEnabled and setHighestPriority as separate calls. In the transactional API, setEnabled implies setHighestPriority. Also, refactor the methods to broadcast ACTION_OVERLAY_CHANGED and to reach out to the package and activity managers. The methods no longer call each other: instead, it is up to the caller to explicitly invoke each method. Bug: 119916381 Test: builds Change-Id: I63df58510efbc5f7a7d3146a122c3d6209e7478b --- .../android/content/om/IOverlayManager.aidl | 15 + .../android/content/om/OverlayManager.java | 23 + .../content/om/OverlayManagerTransaction.aidl | 19 + .../content/om/OverlayManagerTransaction.java | 216 ++++++++ .../server/om/OverlayManagerService.java | 462 +++++++++++------- 5 files changed, 568 insertions(+), 167 deletions(-) create mode 100644 core/java/android/content/om/OverlayManagerTransaction.aidl create mode 100644 core/java/android/content/om/OverlayManagerTransaction.java diff --git a/core/java/android/content/om/IOverlayManager.aidl b/core/java/android/content/om/IOverlayManager.aidl index 44b5c44825998..0b950b4612854 100644 --- a/core/java/android/content/om/IOverlayManager.aidl +++ b/core/java/android/content/om/IOverlayManager.aidl @@ -17,6 +17,7 @@ package android.content.om; import android.content.om.OverlayInfo; +import android.content.om.OverlayManagerTransaction; /** * Api for getting information about overlay packages. @@ -163,4 +164,18 @@ interface IOverlayManager { * @param packageName The name of the overlay package whose idmap should be deleted. */ void invalidateCachesForOverlay(in String packageName, in int userIs); + + /** + * Perform a series of requests related to overlay packages. This is an + * atomic operation: either all requests were performed successfully and + * the changes were propagated to the rest of the system, or at least one + * request could not be performed successfully and nothing is changed and + * nothing is propagated to the rest of the system. + * + * @see OverlayManagerTransaction + * + * @param transaction the series of overlay related requests to perform + * @throws SecurityException if the transaction failed + */ + void commit(in OverlayManagerTransaction transaction); } diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java index 217f637cf9e39..7c14c2891d017 100644 --- a/core/java/android/content/om/OverlayManager.java +++ b/core/java/android/content/om/OverlayManager.java @@ -253,6 +253,29 @@ public class OverlayManager { } } + /** + * Perform a series of requests related to overlay packages. This is an + * atomic operation: either all requests were performed successfully and + * the changes were propagated to the rest of the system, or at least one + * request could not be performed successfully and nothing is changed and + * nothing is propagated to the rest of the system. + * + * @see OverlayManagerTransaction + * + * @param transaction the series of overlay related requests to perform + * @throws Exception if not all the requests could be successfully and + * atomically executed + * + * @hide + */ + public void commit(@NonNull final OverlayManagerTransaction transaction) { + try { + mService.commit(transaction); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Starting on R, actor enforcement and app visibility changes introduce additional failure * cases, but the SecurityException thrown with these checks is unexpected for existing diff --git a/core/java/android/content/om/OverlayManagerTransaction.aidl b/core/java/android/content/om/OverlayManagerTransaction.aidl new file mode 100644 index 0000000000000..6715c82d4e6ff --- /dev/null +++ b/core/java/android/content/om/OverlayManagerTransaction.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2019 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 android.content.om; + +parcelable OverlayManagerTransaction; diff --git a/core/java/android/content/om/OverlayManagerTransaction.java b/core/java/android/content/om/OverlayManagerTransaction.java new file mode 100644 index 0000000000000..1fa8973c35b56 --- /dev/null +++ b/core/java/android/content/om/OverlayManagerTransaction.java @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2019 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 android.content.om; + +import static com.android.internal.util.Preconditions.checkNotNull; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.UserHandle; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Container for a batch of requests to the OverlayManagerService. + * + * Transactions are created using a builder interface. Example usage: + * + * final OverlayManager om = ctx.getSystemService(OverlayManager.class); + * final OverlayManagerTransaction t = new OverlayManagerTransaction.Builder() + * .setEnabled(...) + * .setEnabled(...) + * .build(); + * om.commit(t); + * + * @hide + */ +public class OverlayManagerTransaction + implements Iterable, Parcelable { + // TODO: remove @hide from this class when OverlayManager is added to the + // SDK, but keep OverlayManagerTransaction.Request @hidden + private final List mRequests; + + OverlayManagerTransaction(@NonNull final List requests) { + checkNotNull(requests); + if (requests.contains(null)) { + throw new IllegalArgumentException("null request"); + } + mRequests = requests; + } + + private OverlayManagerTransaction(@NonNull final Parcel source) { + final int size = source.readInt(); + mRequests = new ArrayList(size); + for (int i = 0; i < size; i++) { + final int request = source.readInt(); + final String packageName = source.readString(); + final int userId = source.readInt(); + mRequests.add(new Request(request, packageName, userId)); + } + } + + @Override + public Iterator iterator() { + return mRequests.iterator(); + } + + @Override + public String toString() { + return String.format("OverlayManagerTransaction { mRequests = %s }", mRequests); + } + + /** + * A single unit of the transaction, such as a request to enable an + * overlay, or to disable an overlay. + * + * @hide + */ + public static class Request { + @IntDef(prefix = "TYPE_", value = { + TYPE_SET_ENABLED, + TYPE_SET_DISABLED, + }) + @Retention(RetentionPolicy.SOURCE) + @interface RequestType {} + + public static final int TYPE_SET_ENABLED = 0; + public static final int TYPE_SET_DISABLED = 1; + + @RequestType public final int type; + public final String packageName; + public final int userId; + + public Request(@RequestType final int type, @NonNull final String packageName, + final int userId) { + this.type = type; + this.packageName = packageName; + this.userId = userId; + } + + @Override + public String toString() { + return String.format("Request{type=0x%02x (%s), packageName=%s, userId=%d}", + type, typeToString(), packageName, userId); + } + + /** + * Translate the request type into a human readable string. Only + * intended for debugging. + * + * @hide + */ + public String typeToString() { + switch (type) { + case TYPE_SET_ENABLED: return "TYPE_SET_ENABLED"; + case TYPE_SET_DISABLED: return "TYPE_SET_DISABLED"; + default: return String.format("TYPE_UNKNOWN (0x%02x)", type); + } + } + } + + /** + * Builder class for OverlayManagerTransaction objects. + * + * @hide + */ + public static class Builder { + private final List mRequests = new ArrayList<>(); + + /** + * Request that an overlay package be enabled and change its loading + * order to the last package to be loaded, or disabled + * + * If the caller has the correct permissions, it is always possible to + * disable an overlay. Due to technical and security reasons it may not + * always be possible to enable an overlay, for instance if the overlay + * does not successfully overlay any target resources due to + * overlayable policy restrictions. + * + * An enabled overlay is a part of target package's resources, i.e. it will + * be part of any lookups performed via {@link android.content.res.Resources} + * and {@link android.content.res.AssetManager}. A disabled overlay will no + * longer affect the resources of the target package. If the target is + * currently running, its outdated resources will be replaced by new ones. + * + * @param packageName The name of the overlay package. + * @param enable true to enable the overlay, false to disable it. + * @return this Builder object, so you can chain additional requests + */ + public Builder setEnabled(@NonNull String packageName, boolean enable) { + return setEnabled(packageName, enable, UserHandle.myUserId()); + } + + /** + * @hide + */ + public Builder setEnabled(@NonNull String packageName, boolean enable, int userId) { + checkNotNull(packageName); + @Request.RequestType final int type = + enable ? Request.TYPE_SET_ENABLED : Request.TYPE_SET_DISABLED; + mRequests.add(new Request(type, packageName, userId)); + return this; + } + + /** + * Create a new transaction out of the requests added so far. Execute + * the transaction by calling OverlayManager#commit. + * + * @see OverlayManager#commit + * @return a new transaction + */ + public OverlayManagerTransaction build() { + return new OverlayManagerTransaction(mRequests); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + final int size = mRequests.size(); + dest.writeInt(size); + for (int i = 0; i < size; i++) { + final Request req = mRequests.get(i); + dest.writeInt(req.type); + dest.writeString(req.packageName); + dest.writeInt(req.userId); + } + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + @Override + public OverlayManagerTransaction createFromParcel(Parcel source) { + return new OverlayManagerTransaction(source); + } + + @Override + public OverlayManagerTransaction[] newArray(int size) { + return new OverlayManagerTransaction[size]; + } + }; +} diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index 437e33d66c919..7a6792c121c04 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -24,6 +24,8 @@ import static android.content.Intent.ACTION_PACKAGE_REMOVED; import static android.content.Intent.ACTION_USER_ADDED; import static android.content.Intent.ACTION_USER_REMOVED; import static android.content.Intent.EXTRA_REASON; +import static android.content.om.OverlayManagerTransaction.Request.TYPE_SET_DISABLED; +import static android.content.om.OverlayManagerTransaction.Request.TYPE_SET_ENABLED; import static android.content.pm.PackageManager.SIGNATURE_MATCH; import static android.os.Trace.TRACE_TAG_RRO; import static android.os.Trace.traceBegin; @@ -41,6 +43,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.om.IOverlayManager; import android.content.om.OverlayInfo; +import android.content.om.OverlayManagerTransaction; import android.content.om.OverlayableInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; @@ -84,11 +87,14 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.function.Consumer; /** @@ -238,8 +244,13 @@ public final class OverlayManagerService extends SystemService { private final OverlayActorEnforcer mActorEnforcer; - private final Consumer mOnOverlaysChanged = (pair) -> { - onOverlaysChanged(pair.packageName, pair.userId); + private final Consumer mPropagateOverlayChange = (pair) -> { + persistSettings(); + FgThread.getHandler().post(() -> { + List affectedTargets = updatePackageManager(pair.packageName, pair.userId); + updateActivityManager(affectedTargets, pair.userId); + broadcastActionOverlayChanged(affectedTargets, pair.userId); + }); }; public OverlayManagerService(@NonNull final Context context) { @@ -298,11 +309,11 @@ public final class OverlayManagerService extends SystemService { for (int i = 0; i < userCount; i++) { final UserInfo userInfo = users.get(i); if (!userInfo.supportsSwitchTo() && userInfo.id != UserHandle.USER_SYSTEM) { - // Initialize any users that can't be switched to, as there state would + // Initialize any users that can't be switched to, as their state would // never be setup in onSwitchUser(). We will switch to the system user right // after this, and its state will be setup there. final List targets = mImpl.updateOverlaysForUser(users.get(i).id); - updateOverlayPaths(users.get(i).id, targets); + updatePackageManager(targets, users.get(i).id); } } } @@ -316,7 +327,8 @@ public final class OverlayManagerService extends SystemService { // any asset changes to the rest of the system synchronized (mLock) { final List targets = mImpl.updateOverlaysForUser(newUserId); - updateAssets(newUserId, targets); + final List affectedTargets = updatePackageManager(targets, newUserId); + updateActivityManager(affectedTargets, newUserId); } persistSettings(); } finally { @@ -406,10 +418,10 @@ public final class OverlayManagerService extends SystemService { try { if (pi.isOverlayPackage()) { mImpl.onOverlayPackageAdded(packageName, userId) - .ifPresent(mOnOverlaysChanged); + .ifPresent(mPropagateOverlayChange); } else { mImpl.onTargetPackageAdded(packageName, userId) - .ifPresent(mOnOverlaysChanged); + .ifPresent(mPropagateOverlayChange); } } catch (OperationFailedException e) { Slog.e(TAG, "onPackageAdded internal error", e); @@ -436,10 +448,10 @@ public final class OverlayManagerService extends SystemService { try { if (pi.isOverlayPackage()) { mImpl.onOverlayPackageChanged(packageName, userId) - .ifPresent(mOnOverlaysChanged); + .ifPresent(mPropagateOverlayChange); } else { mImpl.onTargetPackageChanged(packageName, userId) - .ifPresent(mOnOverlaysChanged); + .ifPresent(mPropagateOverlayChange); } } catch (OperationFailedException e) { Slog.e(TAG, "onPackageChanged internal error", e); @@ -463,7 +475,7 @@ public final class OverlayManagerService extends SystemService { if (oi != null) { try { mImpl.onOverlayPackageReplacing(packageName, userId) - .ifPresent(mOnOverlaysChanged); + .ifPresent(mPropagateOverlayChange); } catch (OperationFailedException e) { Slog.e(TAG, "onPackageReplacing internal error", e); } @@ -488,10 +500,10 @@ public final class OverlayManagerService extends SystemService { try { if (pi.isOverlayPackage()) { mImpl.onOverlayPackageReplaced(packageName, userId) - .ifPresent(mOnOverlaysChanged); + .ifPresent(mPropagateOverlayChange); } else { mImpl.onTargetPackageReplaced(packageName, userId) - .ifPresent(mOnOverlaysChanged); + .ifPresent(mPropagateOverlayChange); } } catch (OperationFailedException e) { Slog.e(TAG, "onPackageReplaced internal error", e); @@ -516,10 +528,10 @@ public final class OverlayManagerService extends SystemService { try { if (oi != null) { mImpl.onOverlayPackageRemoved(packageName, userId) - .ifPresent(mOnOverlaysChanged); + .ifPresent(mPropagateOverlayChange); } else { mImpl.onTargetPackageRemoved(packageName, userId) - .ifPresent(mOnOverlaysChanged); + .ifPresent(mPropagateOverlayChange); } } catch (OperationFailedException e) { Slog.e(TAG, "onPackageRemoved internal error", e); @@ -545,7 +557,7 @@ public final class OverlayManagerService extends SystemService { synchronized (mLock) { targets = mImpl.updateOverlaysForUser(userId); } - updateOverlayPaths(userId, targets); + updatePackageManager(targets, userId); } finally { traceEnd(TRACE_TAG_RRO); } @@ -642,7 +654,7 @@ public final class OverlayManagerService extends SystemService { synchronized (mLock) { try { mImpl.setEnabled(packageName, enable, realUserId) - .ifPresent(mOnOverlaysChanged); + .ifPresent(mPropagateOverlayChange); return true; } catch (OperationFailedException e) { return false; @@ -674,7 +686,7 @@ public final class OverlayManagerService extends SystemService { try { mImpl.setEnabledExclusive(packageName, false /* withinCategory */, realUserId) - .ifPresent(mOnOverlaysChanged); + .ifPresent(mPropagateOverlayChange); return true; } catch (OperationFailedException e) { return false; @@ -707,7 +719,7 @@ public final class OverlayManagerService extends SystemService { try { mImpl.setEnabledExclusive(packageName, true /* withinCategory */, realUserId) - .ifPresent(mOnOverlaysChanged); + .ifPresent(mPropagateOverlayChange); return true; } catch (OperationFailedException e) { return false; @@ -739,7 +751,7 @@ public final class OverlayManagerService extends SystemService { synchronized (mLock) { try { mImpl.setPriority(packageName, parentPackageName, realUserId) - .ifPresent(mOnOverlaysChanged); + .ifPresent(mPropagateOverlayChange); return true; } catch (OperationFailedException e) { return false; @@ -769,7 +781,7 @@ public final class OverlayManagerService extends SystemService { synchronized (mLock) { try { mImpl.setHighestPriority(packageName, realUserId) - .ifPresent(mOnOverlaysChanged); + .ifPresent(mPropagateOverlayChange); return true; } catch (OperationFailedException e) { return false; @@ -799,7 +811,7 @@ public final class OverlayManagerService extends SystemService { synchronized (mLock) { try { mImpl.setLowestPriority(packageName, realUserId) - .ifPresent(mOnOverlaysChanged); + .ifPresent(mPropagateOverlayChange); return true; } catch (OperationFailedException e) { return false; @@ -851,6 +863,120 @@ public final class OverlayManagerService extends SystemService { } } + @Override + public void commit(@NonNull final OverlayManagerTransaction transaction) + throws RemoteException { + try { + traceBegin(TRACE_TAG_RRO, "OMS#commit " + transaction); + try { + executeAllRequests(transaction); + } catch (Exception e) { + final long ident = Binder.clearCallingIdentity(); + try { + restoreSettings(); + } finally { + Binder.restoreCallingIdentity(ident); + } + Slog.d(TAG, "commit failed: " + e.getMessage(), e); + throw new SecurityException("commit failed" + + (DEBUG ? ": " + e.getMessage() : "")); + } + } finally { + traceEnd(TRACE_TAG_RRO); + } + } + + private Optional executeRequest( + @NonNull final OverlayManagerTransaction.Request request) throws Exception { + final int realUserId = handleIncomingUser(request.userId, request.typeToString()); + enforceActor(request.packageName, request.typeToString(), realUserId); + + final long ident = Binder.clearCallingIdentity(); + try { + switch (request.type) { + case TYPE_SET_ENABLED: + Optional opt1 = + mImpl.setEnabled(request.packageName, true, request.userId); + Optional opt2 = + mImpl.setHighestPriority(request.packageName, request.userId); + // Both setEnabled and setHighestPriority affected the same + // target package and user: if both return non-empty + // Optionals, they are identical + return opt1.isPresent() ? opt1 : opt2; + case TYPE_SET_DISABLED: + return mImpl.setEnabled(request.packageName, false, request.userId); + default: + throw new IllegalArgumentException("unsupported request: " + request); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void executeAllRequests(@NonNull final OverlayManagerTransaction transaction) + throws Exception { + if (DEBUG) { + Slog.d(TAG, "commit " + transaction); + } + if (transaction == null) { + throw new IllegalArgumentException("null transaction"); + } + + // map: userId -> list + SparseArray> affectedTargetsToUpdate = new SparseArray<>(); + + synchronized (mLock) { + // map: userId -> set + SparseArray> targetsToUpdate = new SparseArray<>(); + + // execute the requests (as calling user) + for (final OverlayManagerTransaction.Request request : transaction) { + executeRequest(request).ifPresent(target -> { + Set userTargets = targetsToUpdate.get(target.userId); + if (userTargets == null) { + userTargets = new ArraySet(); + targetsToUpdate.put(target.userId, userTargets); + } + userTargets.add(target.packageName); + }); + } + + // past the point of no return: the entire transaction has been + // processed successfully, we can no longer fail: continue as + // system_server + final long ident = Binder.clearCallingIdentity(); + try { + persistSettings(); + + // inform the package manager about the new paths + for (int index = 0; index < targetsToUpdate.size(); index++) { + final int userId = targetsToUpdate.keyAt(index); + final List affectedTargets = + updatePackageManager(targetsToUpdate.valueAt(index), userId); + affectedTargetsToUpdate.put(userId, affectedTargets); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } // synchronized (mLock) + + FgThread.getHandler().post(() -> { + final long ident = Binder.clearCallingIdentity(); + try { + // schedule apps to refresh + broadcast the ACTION_OVERLAY_CHANGED intents + for (int index = 0; index < affectedTargetsToUpdate.size(); index++) { + final int userId = affectedTargetsToUpdate.keyAt(index); + final List packageNames = affectedTargetsToUpdate.valueAt(index); + + updateActivityManager(packageNames, userId); + broadcastActionOverlayChanged(packageNames, userId); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + }); + } + @Override public void onShellCommand(@NonNull final FileDescriptor in, @NonNull final FileDescriptor out, @NonNull final FileDescriptor err, @@ -972,152 +1098,7 @@ public final class OverlayManagerService extends SystemService { } }; - private void onOverlaysChanged(@NonNull final String targetPackageName, final int userId) { - persistSettings(); - FgThread.getHandler().post(() -> { - updateAssets(userId, targetPackageName); - - final Intent intent = new Intent(ACTION_OVERLAY_CHANGED, - Uri.fromParts("package", targetPackageName, null)); - intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - - if (DEBUG) { - Slog.d(TAG, "send broadcast " + intent); - } - - try { - ActivityManager.getService().broadcastIntent(null, intent, null, null, 0, - null, null, null, android.app.AppOpsManager.OP_NONE, null, false, false, - userId); - } catch (RemoteException e) { - // Intentionally left empty. - } - }); - } - - /** - * Updates the target packages' set of enabled overlays in PackageManager. - */ - private ArrayList updateOverlayPaths(int userId, List targetPackageNames) { - try { - traceBegin(TRACE_TAG_RRO, "OMS#updateOverlayPaths " + targetPackageNames); - if (DEBUG) { - Slog.d(TAG, "Updating overlay assets"); - } - final PackageManagerInternal pm = - LocalServices.getService(PackageManagerInternal.class); - final boolean updateFrameworkRes = targetPackageNames.contains("android"); - if (updateFrameworkRes) { - targetPackageNames = pm.getTargetPackageNames(userId); - } - - final Map> pendingChanges = - new ArrayMap<>(targetPackageNames.size()); - synchronized (mLock) { - final List frameworkOverlays = - mImpl.getEnabledOverlayPackageNames("android", userId); - final int n = targetPackageNames.size(); - for (int i = 0; i < n; i++) { - final String targetPackageName = targetPackageNames.get(i); - List list = new ArrayList<>(); - if (!"android".equals(targetPackageName)) { - list.addAll(frameworkOverlays); - } - list.addAll(mImpl.getEnabledOverlayPackageNames(targetPackageName, userId)); - pendingChanges.put(targetPackageName, list); - } - } - - final HashSet updatedPackages = new HashSet<>(); - final int n = targetPackageNames.size(); - for (int i = 0; i < n; i++) { - final String targetPackageName = targetPackageNames.get(i); - if (DEBUG) { - Slog.d(TAG, "-> Updating overlay: target=" + targetPackageName + " overlays=[" - + TextUtils.join(",", pendingChanges.get(targetPackageName)) - + "] userId=" + userId); - } - - if (!pm.setEnabledOverlayPackages( - userId, targetPackageName, pendingChanges.get(targetPackageName), - updatedPackages)) { - Slog.e(TAG, String.format("Failed to change enabled overlays for %s user %d", - targetPackageName, userId)); - } - } - return new ArrayList<>(updatedPackages); - } finally { - traceEnd(TRACE_TAG_RRO); - } - } - - private void updateAssets(final int userId, final String targetPackageName) { - updateAssets(userId, Collections.singletonList(targetPackageName)); - } - - private void updateAssets(final int userId, List targetPackageNames) { - final IActivityManager am = ActivityManager.getService(); - try { - final ArrayList updatedPaths = updateOverlayPaths(userId, targetPackageNames); - am.scheduleApplicationInfoChanged(updatedPaths, userId); - } catch (RemoteException e) { - // Intentionally left empty. - } - } - - private void persistSettings() { - if (DEBUG) { - Slog.d(TAG, "Writing overlay settings"); - } - synchronized (mLock) { - FileOutputStream stream = null; - try { - stream = mSettingsFile.startWrite(); - mSettings.persist(stream); - mSettingsFile.finishWrite(stream); - } catch (IOException | XmlPullParserException e) { - mSettingsFile.failWrite(stream); - Slog.e(TAG, "failed to persist overlay state", e); - } - } - } - - private void restoreSettings() { - try { - traceBegin(TRACE_TAG_RRO, "OMS#restoreSettings"); - synchronized (mLock) { - if (!mSettingsFile.getBaseFile().exists()) { - return; - } - try (FileInputStream stream = mSettingsFile.openRead()) { - mSettings.restore(stream); - - // We might have data for dying users if the device was - // restarted before we received USER_REMOVED. Remove data for - // users that will not exist after the system is ready. - - final List liveUsers = mUserManager.getUsers(true /*excludeDying*/); - final int[] liveUserIds = new int[liveUsers.size()]; - for (int i = 0; i < liveUsers.size(); i++) { - liveUserIds[i] = liveUsers.get(i).getUserHandle().getIdentifier(); - } - Arrays.sort(liveUserIds); - - for (int userId : mSettings.getUsers()) { - if (Arrays.binarySearch(liveUserIds, userId) < 0) { - mSettings.removeUser(userId); - } - } - } catch (IOException | XmlPullParserException e) { - Slog.e(TAG, "failed to restore overlay state", e); - } - } - } finally { - traceEnd(TRACE_TAG_RRO); - } - } - - private static final class PackageManagerHelperImpl implements PackageManagerHelper { + private static final class PackageManagerHelperImpl implements PackageManagerHelper { private final Context mContext; private final IPackageManager mPackageManager; @@ -1327,4 +1308,151 @@ public final class OverlayManagerService extends SystemService { } } } + + // Helper methods to update other parts of the system or read/write + // settings: these methods should never call into each other! + + private void broadcastActionOverlayChanged(@NonNull final Collection packageNames, + final int userId) { + for (final String packageName : packageNames) { + broadcastActionOverlayChanged(packageName, userId); + } + } + + private void broadcastActionOverlayChanged(@NonNull final String targetPackageName, + final int userId) { + final Intent intent = new Intent(ACTION_OVERLAY_CHANGED, + Uri.fromParts("package", targetPackageName, null)); + intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + try { + ActivityManager.getService().broadcastIntent(null, intent, null, null, 0, null, null, + null, android.app.AppOpsManager.OP_NONE, null, false, false, userId); + } catch (RemoteException e) { + // Intentionally left empty. + } + } + + /** + * Tell the activity manager to tell a set of packages to reload their + * resources. + */ + private void updateActivityManager(List targetPackageNames, final int userId) { + final IActivityManager am = ActivityManager.getService(); + try { + am.scheduleApplicationInfoChanged(targetPackageNames, userId); + } catch (RemoteException e) { + // Intentionally left empty. + } + } + + private ArrayList updatePackageManager(String targetPackageNames, final int userId) { + return updatePackageManager(Collections.singletonList(targetPackageNames), userId); + } + + /** + * Updates the target packages' set of enabled overlays in PackageManager. + * @return the package names of affected targets (a superset of + * targetPackageNames: the target themserlves and shared libraries) + */ + private ArrayList updatePackageManager(@NonNull Collection targetPackageNames, + final int userId) { + try { + traceBegin(TRACE_TAG_RRO, "OMS#updatePackageManager " + targetPackageNames); + if (DEBUG) { + Slog.d(TAG, "Update package manager about changed overlays"); + } + final PackageManagerInternal pm = + LocalServices.getService(PackageManagerInternal.class); + final boolean updateFrameworkRes = targetPackageNames.contains("android"); + if (updateFrameworkRes) { + targetPackageNames = pm.getTargetPackageNames(userId); + } + + final Map> pendingChanges = + new ArrayMap<>(targetPackageNames.size()); + synchronized (mLock) { + final List frameworkOverlays = + mImpl.getEnabledOverlayPackageNames("android", userId); + for (final String targetPackageName : targetPackageNames) { + List list = new ArrayList<>(); + if (!"android".equals(targetPackageName)) { + list.addAll(frameworkOverlays); + } + list.addAll(mImpl.getEnabledOverlayPackageNames(targetPackageName, userId)); + pendingChanges.put(targetPackageName, list); + } + } + + final HashSet updatedPackages = new HashSet<>(); + for (final String targetPackageName : targetPackageNames) { + if (DEBUG) { + Slog.d(TAG, "-> Updating overlay: target=" + targetPackageName + " overlays=[" + + TextUtils.join(",", pendingChanges.get(targetPackageName)) + + "] userId=" + userId); + } + + if (!pm.setEnabledOverlayPackages( + userId, targetPackageName, pendingChanges.get(targetPackageName), + updatedPackages)) { + Slog.e(TAG, String.format("Failed to change enabled overlays for %s user %d", + targetPackageName, userId)); + } + } + return new ArrayList<>(updatedPackages); + } finally { + traceEnd(TRACE_TAG_RRO); + } + } + + private void persistSettings() { + if (DEBUG) { + Slog.d(TAG, "Writing overlay settings"); + } + synchronized (mLock) { + FileOutputStream stream = null; + try { + stream = mSettingsFile.startWrite(); + mSettings.persist(stream); + mSettingsFile.finishWrite(stream); + } catch (IOException | XmlPullParserException e) { + mSettingsFile.failWrite(stream); + Slog.e(TAG, "failed to persist overlay state", e); + } + } + } + + private void restoreSettings() { + try { + traceBegin(TRACE_TAG_RRO, "OMS#restoreSettings"); + synchronized (mLock) { + if (!mSettingsFile.getBaseFile().exists()) { + return; + } + try (FileInputStream stream = mSettingsFile.openRead()) { + mSettings.restore(stream); + + // We might have data for dying users if the device was + // restarted before we received USER_REMOVED. Remove data for + // users that will not exist after the system is ready. + + final List liveUsers = mUserManager.getUsers(true /*excludeDying*/); + final int[] liveUserIds = new int[liveUsers.size()]; + for (int i = 0; i < liveUsers.size(); i++) { + liveUserIds[i] = liveUsers.get(i).getUserHandle().getIdentifier(); + } + Arrays.sort(liveUserIds); + + for (int userId : mSettings.getUsers()) { + if (Arrays.binarySearch(liveUserIds, userId) < 0) { + mSettings.removeUser(userId); + } + } + } catch (IOException | XmlPullParserException e) { + Slog.e(TAG, "failed to restore overlay state", e); + } + } + } finally { + traceEnd(TRACE_TAG_RRO); + } + } }