From c745f42169df6687a764e48b65878f22965c1530 Mon Sep 17 00:00:00 2001 From: Adam Lesinski Date: Wed, 5 Apr 2017 16:31:30 -0700 Subject: [PATCH] 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 --- core/java/android/content/Intent.java | 23 - .../server/om/OverlayManagerService.java | 142 ++--- .../server/om/OverlayManagerServiceImpl.java | 155 ++++-- .../server/om/OverlayManagerSettings.java | 496 ++++++------------ 4 files changed, 349 insertions(+), 467 deletions(-) diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index da887af52b2ad..9815037c92510 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -3188,13 +3188,6 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_MEDIA_RESOURCE_GRANTED = "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 @@ -3206,22 +3199,6 @@ public class Intent implements Parcelable, Cloneable { */ 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 diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index 2026c1b176bab..818c3ad9728be 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 @@ import java.util.concurrent.atomic.AtomicBoolean; * */ 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 @@ public final class OverlayManagerService extends SystemService { 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 @@ public final class OverlayManagerService extends SystemService { restoreSettings(); onSwitchUser(UserHandle.USER_SYSTEM); - schedulePersistSettings(); - - mSettings.addChangeListener(new OverlayChangeListener()); publishBinderService(Context.OVERLAY_SERVICE, mService); publishLocalService(OverlayManagerService.class, this); @@ -281,8 +275,9 @@ public final class OverlayManagerService extends SystemService { final List targets; synchronized (mLock) { targets = mImpl.onSwitchUser(newUserId); + updateAssetsLocked(newUserId, targets); } - updateAssets(newUserId, targets); + schedulePersistSettings(); } private static Set getDefaultOverlayPackages() { @@ -348,7 +343,8 @@ public final class OverlayManagerService extends SystemService { @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 @@ public final class OverlayManagerService extends SystemService { @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 @@ public final class OverlayManagerService extends SystemService { @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 @@ public final class OverlayManagerService extends SystemService { private final IBinder mService = new IOverlayManager.Stub() { @Override - public Map> getAllOverlays(int userId) - throws RemoteException { + public Map> getAllOverlays(int userId) throws RemoteException { userId = handleIncomingUser(userId, "getAllOverlays"); synchronized (mLock) { @@ -508,14 +505,14 @@ public final class OverlayManagerService extends SystemService { 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 @@ public final class OverlayManagerService extends SystemService { 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 @@ public final class OverlayManagerService extends SystemService { } 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 list = new ArrayList<>(); list.add(targetPackageName); - updateAssets(userId, list); + updateAssetsLocked(userId, list); } - private void updateAssets(final int userId, List targetPackageNames) { + private void updateAssetsLocked(final int userId, List 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 @@ public final class OverlayManagerService extends SystemService { 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 @@ public final class OverlayManagerService extends SystemService { 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 @@ public final class OverlayManagerService extends SystemService { // 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 3705946008f15..c536278d1499b 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 @@ final class OverlayManagerServiceImpl { private final IdmapManager mIdmapManager; private final OverlayManagerSettings mSettings; private final Set mDefaultOverlays; + private final OverlayChangeListener mListener; OverlayManagerServiceImpl(@NonNull final PackageManagerHelper packageManager, @NonNull final IdmapManager idmapManager, @NonNull final OverlayManagerSettings settings, - @NonNull final Set defaultOverlays) { + @NonNull final Set defaultOverlays, + @NonNull final OverlayChangeListener listener) { mPackageManager = packageManager; mIdmapManager = idmapManager; mSettings = settings; mDefaultOverlays = defaultOverlays; + mListener = listener; } /* @@ -145,7 +148,6 @@ final class OverlayManagerServiceImpl { iter.remove(); } } - return new ArrayList<>(packagesToUpdateAssets); } @@ -199,25 +201,30 @@ final class OverlayManagerServiceImpl { 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 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 @@ final class OverlayManagerServiceImpl { 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 @@ final class OverlayManagerServiceImpl { 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 class OverlayManagerServiceImpl { 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 @@ final class OverlayManagerServiceImpl { try { final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId); + final PackageInfo targetPackage = + mPackageManager.getPackageInfo(oi.targetPackageName, userId); + List 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 @@ final class OverlayManagerServiceImpl { 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 @@ final class OverlayManagerServiceImpl { 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 @@ final class OverlayManagerServiceImpl { 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 @@ final class OverlayManagerServiceImpl { if (!mIdmapManager.idmapExists(oi)) { return; } - final List 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 @@ final class OverlayManagerServiceImpl { 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 2262a2e0c2088..2cafa39aff6e0 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.InputStreamReader; 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 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 mItems = new ArrayList<>(); void init(@NonNull final String packageName, final int userId, @@ -68,225 +67,176 @@ final class OverlayManagerSettings { 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 getOverlaysForTarget(@NonNull final String targetPackageName, final int userId) { - final List items = selectWhereTarget(targetPackageName, userId); - if (items.isEmpty()) { - return Collections.emptyList(); - } - final List 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> getOverlaysForUser(final int userId) { - final List items = selectWhereUser(userId); - if (items.isEmpty()) { - return ArrayMap.EMPTY; - } - final ArrayMap> 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()); - } - final List 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 getTargetPackageNamesForUser(final int userId) { - final List items = selectWhereUser(userId); - if (items.isEmpty()) { - return Collections.emptyList(); - } - final List 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 getUsers() { - final ArrayList 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); - } - } - return users; - } - - void removeUser(final int userId) { - final Iterator iter = mItems.iterator(); - while (iter.hasNext()) { - final SettingsItem item = iter.next(); - if (item.userId == userId) { - iter.remove(); + if (item.getUserId() == userId) { + mItems.remove(i); + removed = true; + i--; } } + return removed; } + /** + * 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 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 @@ final class OverlayManagerSettings { 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 @@ final class OverlayManagerSettings { 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 + ""); - 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 @@ final class OverlayManagerSettings { 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 selectWhereUser(final int userId) { - final ArrayList 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 selectWhereUser(final int userId) { + return mItems.stream().filter(item -> item.mUserId == userId); } - private List selectWhereTarget(@NonNull final String targetPackageName, + private Stream selectWhereTarget(@NonNull final String targetPackageName, final int userId) { - final ArrayList 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 @@ final class OverlayManagerSettings { } } - 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); } } }