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
This commit is contained in:
Mårten Kongstad
2020-02-14 15:49:53 +01:00
committed by Ryan Mitchell
parent 038b24eb67
commit ac6bbdb6b0
5 changed files with 568 additions and 167 deletions

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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<OverlayManagerTransaction.Request>, Parcelable {
// TODO: remove @hide from this class when OverlayManager is added to the
// SDK, but keep OverlayManagerTransaction.Request @hidden
private final List<Request> mRequests;
OverlayManagerTransaction(@NonNull final List<Request> 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<Request>(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<Request> 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<Request> 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<OverlayManagerTransaction> CREATOR =
new Parcelable.Creator<OverlayManagerTransaction>() {
@Override
public OverlayManagerTransaction createFromParcel(Parcel source) {
return new OverlayManagerTransaction(source);
}
@Override
public OverlayManagerTransaction[] newArray(int size) {
return new OverlayManagerTransaction[size];
}
};
}

View File

@@ -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<PackageAndUser> mOnOverlaysChanged = (pair) -> {
onOverlaysChanged(pair.packageName, pair.userId);
private final Consumer<PackageAndUser> mPropagateOverlayChange = (pair) -> {
persistSettings();
FgThread.getHandler().post(() -> {
List<String> 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<String> 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<String> targets = mImpl.updateOverlaysForUser(newUserId);
updateAssets(newUserId, targets);
final List<String> 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<PackageAndUser> 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<PackageAndUser> opt1 =
mImpl.setEnabled(request.packageName, true, request.userId);
Optional<PackageAndUser> 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<targetPackageName>
SparseArray<List<String>> affectedTargetsToUpdate = new SparseArray<>();
synchronized (mLock) {
// map: userId -> set<targetPackageName>
SparseArray<Set<String>> targetsToUpdate = new SparseArray<>();
// execute the requests (as calling user)
for (final OverlayManagerTransaction.Request request : transaction) {
executeRequest(request).ifPresent(target -> {
Set<String> userTargets = targetsToUpdate.get(target.userId);
if (userTargets == null) {
userTargets = new ArraySet<String>();
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<String> 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<String> 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<String> updateOverlayPaths(int userId, List<String> 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<String, List<String>> pendingChanges =
new ArrayMap<>(targetPackageNames.size());
synchronized (mLock) {
final List<String> frameworkOverlays =
mImpl.getEnabledOverlayPackageNames("android", userId);
final int n = targetPackageNames.size();
for (int i = 0; i < n; i++) {
final String targetPackageName = targetPackageNames.get(i);
List<String> list = new ArrayList<>();
if (!"android".equals(targetPackageName)) {
list.addAll(frameworkOverlays);
}
list.addAll(mImpl.getEnabledOverlayPackageNames(targetPackageName, userId));
pendingChanges.put(targetPackageName, list);
}
}
final HashSet<String> 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<String> targetPackageNames) {
final IActivityManager am = ActivityManager.getService();
try {
final ArrayList<String> 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<UserInfo> 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<String> 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<String> targetPackageNames, final int userId) {
final IActivityManager am = ActivityManager.getService();
try {
am.scheduleApplicationInfoChanged(targetPackageNames, userId);
} catch (RemoteException e) {
// Intentionally left empty.
}
}
private ArrayList<String> 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<String> updatePackageManager(@NonNull Collection<String> 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<String, List<String>> pendingChanges =
new ArrayMap<>(targetPackageNames.size());
synchronized (mLock) {
final List<String> frameworkOverlays =
mImpl.getEnabledOverlayPackageNames("android", userId);
for (final String targetPackageName : targetPackageNames) {
List<String> list = new ArrayList<>();
if (!"android".equals(targetPackageName)) {
list.addAll(frameworkOverlays);
}
list.addAll(mImpl.getEnabledOverlayPackageNames(targetPackageName, userId));
pendingChanges.put(targetPackageName, list);
}
}
final HashSet<String> 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<UserInfo> 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);
}
}
}