OverlayManagerService: Make broadcasts/updates explicit
Previously there was a listener attached to the Settings object
which would fire any time a single settings change was made.
This made it very inefficient to do batch updates.
This change makes the Settings object return its mutated status
on each call to a mutating method, allowing the caller to keep track
of whether or not to notify the listener of any changes.
This allows for the implementation of setEnabledExclusive, where
all but the target overlay are disabled and only a single notification
/ update is sent out.
Bug: 36099320
Test: manual (with OverlayManagerService.DEBUG = true), observe logcat
Test: when Going to Settings -> Display -> Advanced -> Themes and
Test: selecting a theme.
Change-Id: Ic8b8ca3ba0cf5d2d682bf6dac5a6c82e4f0f2502
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index da887af..9815037 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3189,13 +3189,6 @@
"android.intent.action.MEDIA_RESOURCE_GRANTED";
/**
- * Broadcast Action: An overlay package has been installed. The data
- * contains the name of the added overlay package.
- * @hide
- */
- public static final String ACTION_OVERLAY_ADDED = "android.intent.action.OVERLAY_ADDED";
-
- /**
* Broadcast Action: An overlay package has changed. The data contains the
* name of the overlay package which has changed. This is broadcast on all
* changes to the OverlayInfo returned by {@link
@@ -3207,22 +3200,6 @@
public static final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
/**
- * Broadcast Action: An overlay package has been removed. The data contains
- * the name of the overlay package which has been removed.
- * @hide
- */
- public static final String ACTION_OVERLAY_REMOVED = "android.intent.action.OVERLAY_REMOVED";
-
- /**
- * Broadcast Action: The order of a package's list of overlay packages has
- * changed. The data contains the package name of the overlay package that
- * had its position in the list adjusted.
- * @hide
- */
- public static final String
- ACTION_OVERLAY_PRIORITY_CHANGED = "android.intent.action.OVERLAY_PRIORITY_CHANGED";
-
- /**
* Activity Action: Allow the user to select and return one or more existing
* documents. When invoked, the system will display the various
* {@link DocumentsProvider} instances installed on the device, letting the
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 2026c1b..818c3ad 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -193,13 +193,10 @@
* </ul>
*/
public final class OverlayManagerService extends SystemService {
-
static final String TAG = "OverlayManager";
static final boolean DEBUG = false;
- static final String PERMISSION_DENIED = "Operation not permitted for user shell";
-
/**
* The system property that specifies the default overlays to apply.
* This is a semicolon separated list of package names.
@@ -234,7 +231,7 @@
IdmapManager im = new IdmapManager(installer);
mSettings = new OverlayManagerSettings();
mImpl = new OverlayManagerServiceImpl(mPackageManager, im, mSettings,
- getDefaultOverlayPackages());
+ getDefaultOverlayPackages(), new OverlayChangeListener());
mInitCompleteSignal = SystemServerInitThreadPool.get().submit(() -> {
final IntentFilter packageFilter = new IntentFilter();
packageFilter.addAction(ACTION_PACKAGE_ADDED);
@@ -251,9 +248,6 @@
restoreSettings();
onSwitchUser(UserHandle.USER_SYSTEM);
- schedulePersistSettings();
-
- mSettings.addChangeListener(new OverlayChangeListener());
publishBinderService(Context.OVERLAY_SERVICE, mService);
publishLocalService(OverlayManagerService.class, this);
@@ -281,8 +275,9 @@
final List<String> targets;
synchronized (mLock) {
targets = mImpl.onSwitchUser(newUserId);
+ updateAssetsLocked(newUserId, targets);
}
- updateAssets(newUserId, targets);
+ schedulePersistSettings();
}
private static Set<String> getDefaultOverlayPackages() {
@@ -348,7 +343,8 @@
@NonNull final int[] userIds) {
for (final int userId : userIds) {
synchronized (mLock) {
- final PackageInfo pi = mPackageManager.getPackageInfo(packageName, userId, false);
+ final PackageInfo pi = mPackageManager.getPackageInfo(packageName, userId,
+ false);
if (pi != null) {
mPackageManager.cachePackageInfo(packageName, userId, pi);
if (!isOverlayPackage(pi)) {
@@ -365,7 +361,8 @@
@NonNull final int[] userIds) {
for (int userId : userIds) {
synchronized (mLock) {
- final PackageInfo pi = mPackageManager.getPackageInfo(packageName, userId, false);
+ final PackageInfo pi = mPackageManager.getPackageInfo(packageName, userId,
+ false);
if (pi != null) {
mPackageManager.cachePackageInfo(packageName, userId, pi);
if (!isOverlayPackage(pi)) {
@@ -397,7 +394,8 @@
@NonNull final int[] userIds) {
for (int userId : userIds) {
synchronized (mLock) {
- final PackageInfo pi = mPackageManager.getPackageInfo(packageName, userId, false);
+ final PackageInfo pi = mPackageManager.getPackageInfo(packageName, userId,
+ false);
if (pi != null) {
mPackageManager.cachePackageInfo(packageName, userId, pi);
if (!isOverlayPackage(pi)) {
@@ -449,8 +447,7 @@
private final IBinder mService = new IOverlayManager.Stub() {
@Override
- public Map<String, List<OverlayInfo>> getAllOverlays(int userId)
- throws RemoteException {
+ public Map<String, List<OverlayInfo>> getAllOverlays(int userId) throws RemoteException {
userId = handleIncomingUser(userId, "getAllOverlays");
synchronized (mLock) {
@@ -508,14 +505,14 @@
int userId) throws RemoteException {
enforceChangeOverlayPackagesPermission("setEnabled");
userId = handleIncomingUser(userId, "setEnabled");
- if (packageName == null) {
+ if (packageName == null || !enable) {
return false;
}
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- return mImpl.setEnabledExclusive(packageName, enable, userId);
+ return mImpl.setEnabledExclusive(packageName, userId);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -643,68 +640,24 @@
return pi != null && pi.overlayTarget != null;
}
- private final class OverlayChangeListener implements OverlayManagerSettings.ChangeListener {
+ private final class OverlayChangeListener
+ implements OverlayManagerServiceImpl.OverlayChangeListener {
@Override
- public void onSettingsChanged() {
+ public void onOverlaysChanged(@NonNull final String targetPackageName, final int userId) {
schedulePersistSettings();
- }
-
- @Override
- public void onOverlayAdded(@NonNull final OverlayInfo oi) {
- scheduleBroadcast(Intent.ACTION_OVERLAY_ADDED, oi, oi.isEnabled());
- }
-
- @Override
- public void onOverlayRemoved(@NonNull final OverlayInfo oi) {
- scheduleBroadcast(Intent.ACTION_OVERLAY_REMOVED, oi, oi.isEnabled());
- }
-
- @Override
- public void onOverlayChanged(@NonNull final OverlayInfo oi,
- @NonNull final OverlayInfo oldOi) {
- scheduleBroadcast(Intent.ACTION_OVERLAY_CHANGED, oi, oi.isEnabled() != oldOi.isEnabled());
- }
-
- @Override
- public void onOverlayPriorityChanged(@NonNull final OverlayInfo oi) {
- scheduleBroadcast(Intent.ACTION_OVERLAY_PRIORITY_CHANGED, oi, oi.isEnabled());
- }
-
- private void scheduleBroadcast(@NonNull final String action, @NonNull final OverlayInfo oi,
- final boolean doUpdate) {
- FgThread.getHandler().post(new BroadcastRunnable(action, oi, doUpdate));
- }
-
- private final class BroadcastRunnable implements Runnable {
- private final String mAction;
- private final OverlayInfo mOverlayInfo;
- private final boolean mDoUpdate;
-
- BroadcastRunnable(@NonNull final String action, @NonNull final OverlayInfo oi,
- final boolean doUpdate) {
- mAction = action;
- mOverlayInfo = oi;
- mDoUpdate = doUpdate;
- }
-
- @Override
- public void run() {
- if (mDoUpdate) {
- updateAssets(mOverlayInfo.userId, mOverlayInfo.targetPackageName);
+ FgThread.getHandler().post(() -> {
+ synchronized (mLock) {
+ updateAssetsLocked(userId, targetPackageName);
}
- sendBroadcast(mAction, mOverlayInfo.targetPackageName, mOverlayInfo.packageName,
- mOverlayInfo.userId);
- }
- private void sendBroadcast(@NonNull final String action,
- @NonNull final String targetPackageName, @NonNull final String packageName,
- final int userId) {
- final Intent intent = new Intent(action, Uri.fromParts("package",
- String.format("%s/%s", targetPackageName, packageName), null));
+ final Intent intent = new Intent(Intent.ACTION_OVERLAY_CHANGED,
+ Uri.fromParts("package", targetPackageName, null));
intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+
if (DEBUG) {
- Slog.d(TAG, String.format("send broadcast %s", intent));
+ 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,
@@ -712,18 +665,20 @@
} catch (RemoteException e) {
// Intentionally left empty.
}
- }
-
+ });
}
}
- private void updateAssets(final int userId, final String targetPackageName) {
+ private void updateAssetsLocked(final int userId, final String targetPackageName) {
final List<String> list = new ArrayList<>();
list.add(targetPackageName);
- updateAssets(userId, list);
+ updateAssetsLocked(userId, list);
}
- private void updateAssets(final int userId, List<String> targetPackageNames) {
+ private void updateAssetsLocked(final int userId, List<String> targetPackageNames) {
+ if (DEBUG) {
+ Slog.d(TAG, "Updating overlay assets");
+ }
final PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
final boolean updateFrameworkRes = targetPackageNames.contains("android");
if (updateFrameworkRes) {
@@ -743,6 +698,12 @@
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))) {
Slog.e(TAG, String.format("Failed to change enabled overlays for %s user %d",
@@ -762,20 +723,20 @@
if (mPersistSettingsScheduled.getAndSet(true)) {
return;
}
- IoThread.getHandler().post(new Runnable() {
- @Override
- public void run() {
- mPersistSettingsScheduled.set(false);
- 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);
- }
+ IoThread.getHandler().post(() -> {
+ mPersistSettingsScheduled.set(false);
+ 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);
}
}
});
@@ -862,7 +823,8 @@
// The package manager does not support different versions of packages
// to be installed for different users: ignore userId for now.
try {
- return mPackageManager.checkSignatures(packageName1, packageName2) == SIGNATURE_MATCH;
+ return mPackageManager.checkSignatures(
+ packageName1, packageName2) == SIGNATURE_MATCH;
} catch (RemoteException e) {
// Intentionally left blank
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 3705946..c536278 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -54,15 +54,18 @@
private final IdmapManager mIdmapManager;
private final OverlayManagerSettings mSettings;
private final Set<String> mDefaultOverlays;
+ private final OverlayChangeListener mListener;
OverlayManagerServiceImpl(@NonNull final PackageManagerHelper packageManager,
@NonNull final IdmapManager idmapManager,
@NonNull final OverlayManagerSettings settings,
- @NonNull final Set<String> defaultOverlays) {
+ @NonNull final Set<String> defaultOverlays,
+ @NonNull final OverlayChangeListener listener) {
mPackageManager = packageManager;
mIdmapManager = idmapManager;
mSettings = settings;
mDefaultOverlays = defaultOverlays;
+ mListener = listener;
}
/*
@@ -145,7 +148,6 @@
iter.remove();
}
}
-
return new ArrayList<>(packagesToUpdateAssets);
}
@@ -199,25 +201,30 @@
updateAllOverlaysForTarget(packageName, userId, null);
}
- private void updateAllOverlaysForTarget(@NonNull final String packageName, final int userId,
+ /**
+ * Returns true if the settings were modified for this target.
+ */
+ private boolean updateAllOverlaysForTarget(@NonNull final String packageName, final int userId,
@Nullable final PackageInfo targetPackage) {
+ boolean modified = false;
final List<OverlayInfo> ois = mSettings.getOverlaysForTarget(packageName, userId);
final int N = ois.size();
for (int i = 0; i < N; i++) {
final OverlayInfo oi = ois.get(i);
final PackageInfo overlayPackage = mPackageManager.getPackageInfo(oi.packageName, userId);
if (overlayPackage == null) {
- mSettings.remove(oi.packageName, oi.userId);
+ modified |= mSettings.remove(oi.packageName, oi.userId);
removeIdmapIfPossible(oi);
} else {
try {
- updateState(targetPackage, overlayPackage, userId);
+ modified |= updateState(targetPackage, overlayPackage, userId);
} catch (OverlayManagerSettings.BadKeyException e) {
Slog.e(TAG, "failed to update settings", e);
- mSettings.remove(oi.packageName, userId);
+ modified |= mSettings.remove(oi.packageName, userId);
}
}
}
+ return modified;
}
void onOverlayPackageAdded(@NonNull final String packageName, final int userId) {
@@ -238,7 +245,9 @@
mSettings.init(packageName, userId, overlayPackage.overlayTarget,
overlayPackage.applicationInfo.getBaseCodePath());
try {
- updateState(targetPackage, overlayPackage, userId);
+ if (updateState(targetPackage, overlayPackage, userId)) {
+ mListener.onOverlaysChanged(overlayPackage.overlayTarget, userId);
+ }
} catch (OverlayManagerSettings.BadKeyException e) {
Slog.e(TAG, "failed to update settings", e);
mSettings.remove(packageName, userId);
@@ -289,8 +298,9 @@
if (overlayPackage == null) {
return false;
}
- // Static overlay is always being enabled.
- if (!enable && overlayPackage.isStaticOverlay) {
+
+ // Ignore static overlays.
+ if (overlayPackage.isStaticOverlay) {
return false;
}
@@ -298,19 +308,21 @@
final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
final PackageInfo targetPackage =
mPackageManager.getPackageInfo(oi.targetPackageName, userId);
- mSettings.setEnabled(packageName, userId, enable);
- updateState(targetPackage, overlayPackage, userId);
+ boolean modified = mSettings.setEnabled(packageName, userId, enable);
+ modified |= updateState(targetPackage, overlayPackage, userId);
+
+ if (modified) {
+ mListener.onOverlaysChanged(oi.targetPackageName, userId);
+ }
return true;
} catch (OverlayManagerSettings.BadKeyException e) {
return false;
}
}
- boolean setEnabledExclusive(@NonNull final String packageName, final boolean enable,
- final int userId) {
+ boolean setEnabledExclusive(@NonNull final String packageName, final int userId) {
if (DEBUG) {
- Slog.d(TAG, String.format("setEnabled packageName=%s enable=%s userId=%d",
- packageName, enable, userId));
+ Slog.d(TAG, String.format("setEnabledExclusive packageName=%s userId=%d", packageName, userId));
}
final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
@@ -320,23 +332,48 @@
try {
final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
+ final PackageInfo targetPackage =
+ mPackageManager.getPackageInfo(oi.targetPackageName, userId);
+
List<OverlayInfo> allOverlays = getOverlayInfosForTarget(oi.targetPackageName, userId);
+ boolean modified = false;
+
// Disable all other overlays.
allOverlays.remove(oi);
for (int i = 0; i < allOverlays.size(); i++) {
- // TODO: Optimize this to only send updates after all changes.
- setEnabled(allOverlays.get(i).packageName, false, userId);
+ final String disabledOverlayPackageName = allOverlays.get(i).packageName;
+ final PackageInfo disabledOverlayPackageInfo = mPackageManager.getPackageInfo(
+ disabledOverlayPackageName, userId);
+ if (disabledOverlayPackageInfo == null) {
+ modified |= mSettings.remove(disabledOverlayPackageName, userId);
+ continue;
+ }
+
+ if (disabledOverlayPackageInfo.isStaticOverlay) {
+ // Don't touch static overlays.
+ continue;
+ }
+
+ // Disable the overlay.
+ modified |= mSettings.setEnabled(disabledOverlayPackageName, userId, false);
+ modified |= updateState(targetPackage, disabledOverlayPackageInfo, userId);
}
- setEnabled(packageName, enable, userId);
+ // Enable the selected overlay.
+ modified |= mSettings.setEnabled(packageName, userId, true);
+ modified |= updateState(targetPackage, overlayPackage, userId);
+
+ if (modified) {
+ mListener.onOverlaysChanged(oi.targetPackageName, userId);
+ }
return true;
} catch (OverlayManagerSettings.BadKeyException e) {
return false;
}
}
- boolean isPackageUpdatableOverlay(@NonNull final String packageName, final int userId) {
+ private boolean isPackageUpdatableOverlay(@NonNull final String packageName, final int userId) {
final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
if (overlayPackage == null || overlayPackage.isStaticOverlay) {
return false;
@@ -346,18 +383,64 @@
boolean setPriority(@NonNull final String packageName,
@NonNull final String newParentPackageName, final int userId) {
- return isPackageUpdatableOverlay(packageName, userId) &&
- mSettings.setPriority(packageName, newParentPackageName, userId);
+ if (DEBUG) {
+ Slog.d(TAG, "setPriority packageName=" + packageName + " newParentPackageName="
+ + newParentPackageName + " userId=" + userId);
+ }
+
+ if (!isPackageUpdatableOverlay(packageName, userId)) {
+ return false;
+ }
+
+ final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
+ if (overlayPackage == null) {
+ return false;
+ }
+
+ if (mSettings.setPriority(packageName, newParentPackageName, userId)) {
+ mListener.onOverlaysChanged(overlayPackage.overlayTarget, userId);
+ }
+ return true;
}
boolean setHighestPriority(@NonNull final String packageName, final int userId) {
- return isPackageUpdatableOverlay(packageName, userId) &&
- mSettings.setHighestPriority(packageName, userId);
+ if (DEBUG) {
+ Slog.d(TAG, "setHighestPriority packageName=" + packageName + " userId=" + userId);
+ }
+
+ if (!isPackageUpdatableOverlay(packageName, userId)) {
+ return false;
+ }
+
+ final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
+ if (overlayPackage == null) {
+ return false;
+ }
+
+ if (mSettings.setHighestPriority(packageName, userId)) {
+ mListener.onOverlaysChanged(overlayPackage.overlayTarget, userId);
+ }
+ return true;
}
boolean setLowestPriority(@NonNull final String packageName, final int userId) {
- return isPackageUpdatableOverlay(packageName, userId) &&
- mSettings.setLowestPriority(packageName, userId);
+ if (DEBUG) {
+ Slog.d(TAG, "setLowestPriority packageName=" + packageName + " userId=" + userId);
+ }
+
+ if (!isPackageUpdatableOverlay(packageName, userId)) {
+ return false;
+ }
+
+ final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
+ if (overlayPackage == null) {
+ return false;
+ }
+
+ if (mSettings.setLowestPriority(packageName, userId)) {
+ mListener.onOverlaysChanged(overlayPackage.overlayTarget, userId);
+ }
+ return true;
}
void onDump(@NonNull final PrintWriter pw) {
@@ -379,16 +462,19 @@
return paths;
}
- private void updateState(@Nullable final PackageInfo targetPackage,
+ /**
+ * Returns true if the settings/state was modified, false otherwise.
+ */
+ private boolean updateState(@Nullable final PackageInfo targetPackage,
@NonNull final PackageInfo overlayPackage, final int userId)
- throws OverlayManagerSettings.BadKeyException {
+ throws OverlayManagerSettings.BadKeyException {
// Static RROs targeting to "android", ie framework-res.apk, are handled by native layers.
if (targetPackage != null &&
!("android".equals(targetPackage.packageName) && overlayPackage.isStaticOverlay)) {
mIdmapManager.createIdmap(targetPackage, overlayPackage, userId);
}
- mSettings.setBaseCodePath(overlayPackage.packageName, userId,
+ boolean modified = mSettings.setBaseCodePath(overlayPackage.packageName, userId,
overlayPackage.applicationInfo.getBaseCodePath());
final int currentState = mSettings.getState(overlayPackage.packageName, userId);
@@ -400,8 +486,9 @@
OverlayInfo.stateToString(currentState),
OverlayInfo.stateToString(newState)));
}
- mSettings.setState(overlayPackage.packageName, userId, newState);
+ modified |= mSettings.setState(overlayPackage.packageName, userId, newState);
}
+ return modified;
}
private int calculateNewState(@Nullable final PackageInfo targetPackage,
@@ -441,10 +528,8 @@
if (!mIdmapManager.idmapExists(oi)) {
return;
}
- final List<Integer> userIds = mSettings.getUsers();
- final int N = userIds.size();
- for (int i = 0; i < N; i++) {
- final int userId = userIds.get(i);
+ final int[] userIds = mSettings.getUsers();
+ for (int userId : userIds) {
try {
final OverlayInfo tmp = mSettings.getOverlayInfo(oi.packageName, userId);
if (tmp != null && tmp.isEnabled()) {
@@ -458,6 +543,10 @@
mIdmapManager.removeIdmap(oi, oi.userId);
}
+ interface OverlayChangeListener {
+ void onOverlaysChanged(@NonNull String targetPackage, int userId);
+ }
+
interface PackageManagerHelper {
PackageInfo getPackageInfo(@NonNull String packageName, int userId);
boolean signaturesMatching(@NonNull String packageName1, @NonNull String packageName2,
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index 2262a2e..2cafa39 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -16,17 +16,11 @@
package com.android.server.om;
-import static android.content.om.OverlayInfo.STATE_UNKNOWN;
-
-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.util.AndroidRuntimeException;
import android.util.ArrayMap;
-import android.util.Slog;
import android.util.Xml;
import com.android.internal.util.FastXmlSerializer;
@@ -41,23 +35,28 @@
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
import java.util.List;
-import java.util.ListIterator;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* Data structure representing the current state of all overlay packages in the
* system.
*
- * Modifications to the data are exposed through the ChangeListener interface.
+ * Modifications to the data are signaled by returning true from any state mutating method.
*
- * @see ChangeListener
* @see OverlayManagerService
*/
final class OverlayManagerSettings {
- private final List<ChangeListener> mListeners = new ArrayList<>();
-
+ /**
+ * All overlay data for all users and target packages is stored in this list.
+ * This keeps memory down, while increasing the cost of running queries or mutating the
+ * data. This is ok, since changing of overlays is very rare and has larger costs associated
+ * with it.
+ *
+ * The order of the items in the list is important, those with a lower index having a lower
+ * priority.
+ */
private final ArrayList<SettingsItem> mItems = new ArrayList<>();
void init(@NonNull final String packageName, final int userId,
@@ -68,225 +67,176 @@
mItems.add(item);
}
- void remove(@NonNull final String packageName, final int userId) {
- final SettingsItem item = select(packageName, userId);
- if (item == null) {
- return;
+ /**
+ * Returns true if the settings were modified, false if they remain the same.
+ */
+ boolean remove(@NonNull final String packageName, final int userId) {
+ final int idx = select(packageName, userId);
+ if (idx < 0) {
+ return false;
}
- final OverlayInfo oi = item.getOverlayInfo();
- mItems.remove(item);
- if (oi != null) {
- notifyOverlayRemoved(oi);
- }
- }
- boolean contains(@NonNull final String packageName, final int userId) {
- return select(packageName, userId) != null;
+ mItems.remove(idx);
+ return true;
}
OverlayInfo getOverlayInfo(@NonNull final String packageName, final int userId)
throws BadKeyException {
- final SettingsItem item = select(packageName, userId);
- if (item == null) {
+ final int idx = select(packageName, userId);
+ if (idx < 0) {
throw new BadKeyException(packageName, userId);
}
- return item.getOverlayInfo();
+ return mItems.get(idx).getOverlayInfo();
}
- String getTargetPackageName(@NonNull final String packageName, final int userId)
- throws BadKeyException {
- final SettingsItem item = select(packageName, userId);
- if (item == null) {
- throw new BadKeyException(packageName, userId);
- }
- return item.getTargetPackageName();
- }
-
- void setBaseCodePath(@NonNull final String packageName, final int userId,
+ /**
+ * Returns true if the settings were modified, false if they remain the same.
+ */
+ boolean setBaseCodePath(@NonNull final String packageName, final int userId,
@NonNull final String path) throws BadKeyException {
- final SettingsItem item = select(packageName, userId);
- if (item == null) {
+ final int idx = select(packageName, userId);
+ if (idx < 0) {
throw new BadKeyException(packageName, userId);
}
- item.setBaseCodePath(path);
- notifySettingsChanged();
+ return mItems.get(idx).setBaseCodePath(path);
}
boolean getEnabled(@NonNull final String packageName, final int userId) throws BadKeyException {
- final SettingsItem item = select(packageName, userId);
- if (item == null) {
+ final int idx = select(packageName, userId);
+ if (idx < 0) {
throw new BadKeyException(packageName, userId);
}
- return item.isEnabled();
+ return mItems.get(idx).isEnabled();
}
- void setEnabled(@NonNull final String packageName, final int userId, final boolean enable)
+ /**
+ * Returns true if the settings were modified, false if they remain the same.
+ */
+ boolean setEnabled(@NonNull final String packageName, final int userId, final boolean enable)
throws BadKeyException {
- final SettingsItem item = select(packageName, userId);
- if (item == null) {
+ final int idx = select(packageName, userId);
+ if (idx < 0) {
throw new BadKeyException(packageName, userId);
}
- if (enable == item.isEnabled()) {
- return; // nothing to do
- }
-
- item.setEnabled(enable);
- notifySettingsChanged();
+ return mItems.get(idx).setEnabled(enable);
}
int getState(@NonNull final String packageName, final int userId) throws BadKeyException {
- final SettingsItem item = select(packageName, userId);
- if (item == null) {
+ final int idx = select(packageName, userId);
+ if (idx < 0) {
throw new BadKeyException(packageName, userId);
}
- return item.getState();
+ return mItems.get(idx).getState();
}
- void setState(@NonNull final String packageName, final int userId, final int state)
+ /**
+ * Returns true if the settings were modified, false if they remain the same.
+ */
+ boolean setState(@NonNull final String packageName, final int userId, final int state)
throws BadKeyException {
- final SettingsItem item = select(packageName, userId);
- if (item == null) {
+ final int idx = select(packageName, userId);
+ if (idx < 0) {
throw new BadKeyException(packageName, userId);
}
- final OverlayInfo previous = item.getOverlayInfo();
- item.setState(state);
- final OverlayInfo current = item.getOverlayInfo();
- if (previous.state == STATE_UNKNOWN) {
- notifyOverlayAdded(current);
- notifySettingsChanged();
- } else if (current.state != previous.state) {
- notifyOverlayChanged(current, previous);
- notifySettingsChanged();
- }
+ return mItems.get(idx).setState(state);
}
List<OverlayInfo> getOverlaysForTarget(@NonNull final String targetPackageName,
final int userId) {
- final List<SettingsItem> items = selectWhereTarget(targetPackageName, userId);
- if (items.isEmpty()) {
- return Collections.emptyList();
- }
- final List<OverlayInfo> out = new ArrayList<>(items.size());
- final int N = items.size();
- for (int i = 0; i < N; i++) {
- final SettingsItem item = items.get(i);
- out.add(item.getOverlayInfo());
- }
- return out;
+ return selectWhereTarget(targetPackageName, userId)
+ .map(SettingsItem::getOverlayInfo)
+ .collect(Collectors.toList());
}
ArrayMap<String, List<OverlayInfo>> getOverlaysForUser(final int userId) {
- final List<SettingsItem> items = selectWhereUser(userId);
- if (items.isEmpty()) {
- return ArrayMap.EMPTY;
- }
- final ArrayMap<String, List<OverlayInfo>> out = new ArrayMap<>(items.size());
- final int N = items.size();
- for (int i = 0; i < N; i++) {
- final SettingsItem item = items.get(i);
- final String targetPackageName = item.getTargetPackageName();
- if (!out.containsKey(targetPackageName)) {
- out.put(targetPackageName, new ArrayList<OverlayInfo>());
- }
- final List<OverlayInfo> overlays = out.get(targetPackageName);
- overlays.add(item.getOverlayInfo());
- }
- return out;
+ return selectWhereUser(userId)
+ .map(SettingsItem::getOverlayInfo)
+ .collect(Collectors.groupingBy(info -> info.targetPackageName, ArrayMap::new,
+ Collectors.toList()));
}
- List<String> getTargetPackageNamesForUser(final int userId) {
- final List<SettingsItem> items = selectWhereUser(userId);
- if (items.isEmpty()) {
- return Collections.emptyList();
- }
- final List<String> out = new ArrayList<>();
- final int N = items.size();
- for (int i = 0; i < N; i++) {
- final SettingsItem item = items.get(i);
- final String targetPackageName = item.getTargetPackageName();
- if (!out.contains(targetPackageName)) {
- out.add(targetPackageName);
- }
- }
- return out;
+ int[] getUsers() {
+ return mItems.stream().mapToInt(SettingsItem::getUserId).distinct().toArray();
}
- List<Integer> getUsers() {
- final ArrayList<Integer> users = new ArrayList<>();
- final int N = mItems.size();
- for (int i = 0; i < N; i++) {
+ /**
+ * Returns true if the settings were modified, false if they remain the same.
+ */
+ boolean removeUser(final int userId) {
+ boolean removed = false;
+ for (int i = 0; i < mItems.size(); i++) {
final SettingsItem item = mItems.get(i);
- if (!users.contains(item.userId)) {
- users.add(item.userId);
+ if (item.getUserId() == userId) {
+ mItems.remove(i);
+ removed = true;
+ i--;
}
}
- return users;
+ return removed;
}
- void removeUser(final int userId) {
- final Iterator<SettingsItem> iter = mItems.iterator();
- while (iter.hasNext()) {
- final SettingsItem item = iter.next();
- if (item.userId == userId) {
- iter.remove();
- }
- }
- }
-
+ /**
+ * Returns true if the settings were modified, false if they remain the same.
+ */
boolean setPriority(@NonNull final String packageName,
@NonNull final String newParentPackageName, final int userId) {
if (packageName.equals(newParentPackageName)) {
return false;
}
- final SettingsItem rowToMove = select(packageName, userId);
- if (rowToMove == null) {
- return false;
- }
- final SettingsItem newParentRow = select(newParentPackageName, userId);
- if (newParentRow == null) {
- return false;
- }
- if (!rowToMove.getTargetPackageName().equals(newParentRow.getTargetPackageName())) {
+ final int moveIdx = select(packageName, userId);
+ if (moveIdx < 0) {
return false;
}
- mItems.remove(rowToMove);
- final ListIterator<SettingsItem> iter = mItems.listIterator();
- while (iter.hasNext()) {
- final SettingsItem item = iter.next();
- if (item.userId == userId && item.packageName.equals(newParentPackageName)) {
- iter.add(rowToMove);
- notifyOverlayPriorityChanged(rowToMove.getOverlayInfo());
- notifySettingsChanged();
- return true;
- }
+ final int parentIdx = select(newParentPackageName, userId);
+ if (parentIdx < 0) {
+ return false;
}
- Slog.wtf(TAG, "failed to find the parent item a second time");
- return false;
+ final SettingsItem itemToMove = mItems.get(moveIdx);
+
+ // Make sure both packages are targeting the same package.
+ if (!itemToMove.getTargetPackageName().equals(
+ mItems.get(parentIdx).getTargetPackageName())) {
+ return false;
+ }
+
+ mItems.remove(moveIdx);
+ final int newParentIdx = select(newParentPackageName, userId);
+ mItems.add(newParentIdx, itemToMove);
+ return moveIdx != newParentIdx;
}
+ /**
+ * Returns true if the settings were modified, false if they remain the same.
+ */
boolean setLowestPriority(@NonNull final String packageName, final int userId) {
- final SettingsItem item = select(packageName, userId);
- if (item == null) {
+ final int idx = select(packageName, userId);
+ if (idx <= 0) {
+ // If the item doesn't exist or is already the lowest, don't change anything.
return false;
}
+
+ final SettingsItem item = mItems.get(idx);
mItems.remove(item);
mItems.add(0, item);
- notifyOverlayPriorityChanged(item.getOverlayInfo());
- notifySettingsChanged();
return true;
}
+ /**
+ * Returns true if the settings were modified, false if they remain the same.
+ */
boolean setHighestPriority(@NonNull final String packageName, final int userId) {
- final SettingsItem item = select(packageName, userId);
- if (item == null) {
+ final int idx = select(packageName, userId);
+
+ // If the item doesn't exist or is already the highest, don't change anything.
+ if (idx < 0 || idx == mItems.size() - 1) {
return false;
}
- mItems.remove(item);
+
+ final SettingsItem item = mItems.get(idx);
+ mItems.remove(idx);
mItems.add(item);
- notifyOverlayPriorityChanged(item.getOverlayInfo());
- notifySettingsChanged();
return true;
}
@@ -296,11 +246,6 @@
void dump(@NonNull final PrintWriter pw) {
pw.println("Settings");
- dumpItems(pw);
- dumpListeners(pw);
- }
-
- private void dumpItems(@NonNull final PrintWriter pw) {
pw.println(TAB1 + "Items");
if (mItems.isEmpty()) {
@@ -312,34 +257,18 @@
for (int i = 0; i < N; i++) {
final SettingsItem item = mItems.get(i);
final StringBuilder sb = new StringBuilder();
- sb.append(TAB2 + item.packageName + ":" + item.userId + " {\n");
- sb.append(TAB3 + "packageName.......: " + item.packageName + "\n");
- sb.append(TAB3 + "userId............: " + item.userId + "\n");
- sb.append(TAB3 + "targetPackageName.: " + item.getTargetPackageName() + "\n");
- sb.append(TAB3 + "baseCodePath......: " + item.getBaseCodePath() + "\n");
- sb.append(TAB3 + "state.............: " + OverlayInfo.stateToString(item.getState()) + "\n");
- sb.append(TAB3 + "isEnabled.........: " + item.isEnabled() + "\n");
+ sb.append(TAB2 + item.mPackageName + ":" + item.getUserId() + " {\n");
+ sb.append(TAB3 + "mPackageName.......: " + item.mPackageName + "\n");
+ sb.append(TAB3 + "mUserId............: " + item.getUserId() + "\n");
+ sb.append(TAB3 + "mTargetPackageName.: " + item.getTargetPackageName() + "\n");
+ sb.append(TAB3 + "mBaseCodePath......: " + item.getBaseCodePath() + "\n");
+ sb.append(TAB3 + "mState.............: " + OverlayInfo.stateToString(item.getState()) + "\n");
+ sb.append(TAB3 + "mIsEnabled.........: " + item.isEnabled() + "\n");
sb.append(TAB2 + "}");
pw.println(sb.toString());
}
}
- private void dumpListeners(@NonNull final PrintWriter pw) {
- pw.println(TAB1 + "Change listeners");
-
- if (mListeners.isEmpty()) {
- pw.println(TAB2 + "<none>");
- return;
- }
-
- final int N = mListeners.size();
- for (int i = 0; i < N; i++) {
- final ChangeListener ch = mListeners.get(i);
- pw.println(TAB2 + ch);
- }
-
- }
-
void restore(@NonNull final InputStream is) throws IOException, XmlPullParserException {
Serializer.restore(mItems, is);
}
@@ -434,127 +363,122 @@
private static void persistRow(@NonNull final FastXmlSerializer xml,
@NonNull final SettingsItem item) throws IOException {
xml.startTag(null, TAG_ITEM);
- XmlUtils.writeStringAttribute(xml, ATTR_PACKAGE_NAME, item.packageName);
- XmlUtils.writeIntAttribute(xml, ATTR_USER_ID, item.userId);
- XmlUtils.writeStringAttribute(xml, ATTR_TARGET_PACKAGE_NAME, item.targetPackageName);
- XmlUtils.writeStringAttribute(xml, ATTR_BASE_CODE_PATH, item.baseCodePath);
- XmlUtils.writeIntAttribute(xml, ATTR_STATE, item.state);
- XmlUtils.writeBooleanAttribute(xml, ATTR_IS_ENABLED, item.isEnabled);
+ XmlUtils.writeStringAttribute(xml, ATTR_PACKAGE_NAME, item.mPackageName);
+ XmlUtils.writeIntAttribute(xml, ATTR_USER_ID, item.mUserId);
+ XmlUtils.writeStringAttribute(xml, ATTR_TARGET_PACKAGE_NAME, item.mTargetPackageName);
+ XmlUtils.writeStringAttribute(xml, ATTR_BASE_CODE_PATH, item.mBaseCodePath);
+ XmlUtils.writeIntAttribute(xml, ATTR_STATE, item.mState);
+ XmlUtils.writeBooleanAttribute(xml, ATTR_IS_ENABLED, item.mIsEnabled);
xml.endTag(null, TAG_ITEM);
}
}
private static final class SettingsItem {
- private final int userId;
- private final String packageName;
- private final String targetPackageName;
- private String baseCodePath;
- private int state;
- private boolean isEnabled;
- private OverlayInfo cache;
+ private final int mUserId;
+ private final String mPackageName;
+ private final String mTargetPackageName;
+ private String mBaseCodePath;
+ private int mState;
+ private boolean mIsEnabled;
+ private OverlayInfo mCache;
SettingsItem(@NonNull final String packageName, final int userId,
@NonNull final String targetPackageName, @NonNull final String baseCodePath,
final int state, final boolean isEnabled) {
- this.packageName = packageName;
- this.userId = userId;
- this.targetPackageName = targetPackageName;
- this.baseCodePath = baseCodePath;
- this.state = state;
- this.isEnabled = isEnabled;
- cache = null;
+ mPackageName = packageName;
+ mUserId = userId;
+ mTargetPackageName = targetPackageName;
+ mBaseCodePath = baseCodePath;
+ mState = state;
+ mIsEnabled = isEnabled;
+ mCache = null;
}
SettingsItem(@NonNull final String packageName, final int userId,
@NonNull final String targetPackageName, @NonNull final String baseCodePath) {
- this(packageName, userId, targetPackageName, baseCodePath, STATE_UNKNOWN,
+ this(packageName, userId, targetPackageName, baseCodePath, OverlayInfo.STATE_UNKNOWN,
false);
}
private String getTargetPackageName() {
- return targetPackageName;
+ return mTargetPackageName;
+ }
+
+ private int getUserId() {
+ return mUserId;
}
private String getBaseCodePath() {
- return baseCodePath;
+ return mBaseCodePath;
}
- private void setBaseCodePath(@NonNull final String path) {
- if (!baseCodePath.equals(path)) {
- baseCodePath = path;
+ private boolean setBaseCodePath(@NonNull final String path) {
+ if (!mBaseCodePath.equals(path)) {
+ mBaseCodePath = path;
invalidateCache();
+ return true;
}
+ return false;
}
private int getState() {
- return state;
+ return mState;
}
- private void setState(final int state) {
- if (this.state != state) {
- this.state = state;
+ private boolean setState(final int state) {
+ if (mState != state) {
+ mState = state;
invalidateCache();
+ return true;
}
+ return false;
}
private boolean isEnabled() {
- return isEnabled;
+ return mIsEnabled;
}
- private void setEnabled(final boolean enable) {
- if (isEnabled != enable) {
- isEnabled = enable;
+ private boolean setEnabled(final boolean enable) {
+ if (mIsEnabled != enable) {
+ mIsEnabled = enable;
invalidateCache();
+ return true;
}
+ return false;
}
private OverlayInfo getOverlayInfo() {
- if (cache == null) {
- cache = new OverlayInfo(packageName, targetPackageName, baseCodePath,
- state, userId);
+ if (mCache == null) {
+ mCache = new OverlayInfo(mPackageName, mTargetPackageName, mBaseCodePath, mState,
+ mUserId);
}
- return cache;
+ return mCache;
}
private void invalidateCache() {
- cache = null;
+ mCache = null;
}
}
- private SettingsItem select(@NonNull final String packageName, final int userId) {
+ private int select(@NonNull final String packageName, final int userId) {
final int N = mItems.size();
for (int i = 0; i < N; i++) {
final SettingsItem item = mItems.get(i);
- if (item.userId == userId && item.packageName.equals(packageName)) {
- return item;
+ if (item.mUserId == userId && item.mPackageName.equals(packageName)) {
+ return i;
}
}
- return null;
+ return -1;
}
- private List<SettingsItem> selectWhereUser(final int userId) {
- final ArrayList<SettingsItem> items = new ArrayList<>();
- final int N = mItems.size();
- for (int i = 0; i < N; i++) {
- final SettingsItem item = mItems.get(i);
- if (item.userId == userId) {
- items.add(item);
- }
- }
- return items;
+ private Stream<SettingsItem> selectWhereUser(final int userId) {
+ return mItems.stream().filter(item -> item.mUserId == userId);
}
- private List<SettingsItem> selectWhereTarget(@NonNull final String targetPackageName,
+ private Stream<SettingsItem> selectWhereTarget(@NonNull final String targetPackageName,
final int userId) {
- final ArrayList<SettingsItem> items = new ArrayList<>();
- final int N = mItems.size();
- for (int i = 0; i < N; i++) {
- final SettingsItem item = mItems.get(i);
- if (item.userId == userId && item.getTargetPackageName().equals(targetPackageName)) {
- items.add(item);
- }
- }
- return items;
+ return selectWhereUser(userId)
+ .filter(item -> item.getTargetPackageName().equals(targetPackageName));
}
private void assertNotNull(@Nullable final Object o) {
@@ -563,79 +487,9 @@
}
}
- void addChangeListener(@NonNull final ChangeListener listener) {
- mListeners.add(listener);
- }
-
- void removeChangeListener(@NonNull final ChangeListener listener) {
- mListeners.remove(listener);
- }
-
- private void notifySettingsChanged() {
- final int N = mListeners.size();
- for (int i = 0; i < N; i++) {
- final ChangeListener listener = mListeners.get(i);
- listener.onSettingsChanged();
- }
- }
-
- private void notifyOverlayAdded(@NonNull final OverlayInfo oi) {
- if (DEBUG) {
- assertNotNull(oi);
- }
- final int N = mListeners.size();
- for (int i = 0; i < N; i++) {
- final ChangeListener listener = mListeners.get(i);
- listener.onOverlayAdded(oi);
- }
- }
-
- private void notifyOverlayRemoved(@NonNull final OverlayInfo oi) {
- if (DEBUG) {
- assertNotNull(oi);
- }
- final int N = mListeners.size();
- for (int i = 0; i < N; i++) {
- final ChangeListener listener = mListeners.get(i);
- listener.onOverlayRemoved(oi);
- }
- }
-
- private void notifyOverlayChanged(@NonNull final OverlayInfo oi,
- @NonNull final OverlayInfo oldOi) {
- if (DEBUG) {
- assertNotNull(oi);
- assertNotNull(oldOi);
- }
- final int N = mListeners.size();
- for (int i = 0; i < N; i++) {
- final ChangeListener listener = mListeners.get(i);
- listener.onOverlayChanged(oi, oldOi);
- }
- }
-
- private void notifyOverlayPriorityChanged(@NonNull final OverlayInfo oi) {
- if (DEBUG) {
- assertNotNull(oi);
- }
- final int N = mListeners.size();
- for (int i = 0; i < N; i++) {
- final ChangeListener listener = mListeners.get(i);
- listener.onOverlayPriorityChanged(oi);
- }
- }
-
- interface ChangeListener {
- void onSettingsChanged();
- void onOverlayAdded(@NonNull OverlayInfo oi);
- void onOverlayRemoved(@NonNull OverlayInfo oi);
- void onOverlayChanged(@NonNull OverlayInfo oi, @NonNull OverlayInfo oldOi);
- void onOverlayPriorityChanged(@NonNull OverlayInfo oi);
- }
-
static final class BadKeyException extends RuntimeException {
BadKeyException(@NonNull final String packageName, final int userId) {
- super("Bad key packageName=" + packageName + " userId=" + userId);
+ super("Bad key mPackageName=" + packageName + " mUserId=" + userId);
}
}
}