OverlayManager: Add categories

Adds the possibility to declare different categories
for resource overlays (e.g. themes, display cutout emulation, ...)

Bug: 72436677
Test: adb shell cmd overlay enable-exclusive --category com.android.internal.display_cutout_emulation android com.android.internal.display.cutout.emulation.narrow
Test: adb shell cmd overlay enable-exclusive --category com.android.internal.display_cutout_emulation android
Change-Id: I23f22113351b3948beb9e3a1fb969700852539cc
diff --git a/core/java/android/content/om/IOverlayManager.aidl b/core/java/android/content/om/IOverlayManager.aidl
index 86c1aa8..5b3c9dd 100644
--- a/core/java/android/content/om/IOverlayManager.aidl
+++ b/core/java/android/content/om/IOverlayManager.aidl
@@ -83,17 +83,36 @@
      * @param packageName The name of the overlay package.
      * @param enable true to enable the overlay, false to disable it.
      * @param userId The user for which to change the overlay.
-     * @return true if the system successfully registered the request, false
-     *         otherwise.
+     * @return true if the system successfully registered the request, false otherwise.
      */
     boolean setEnabled(in String packageName, in boolean enable, in int userId);
 
     /**
-     * Version of setEnabled that will also disable any other overlays for the target package.
+     * Request that an overlay package is enabled and any other overlay packages with the same
+     * target package are disabled.
+     *
+     * See {@link #setEnabled} for the details on overlay packages.
+     *
+     * @param packageName the name of the overlay package to enable.
+     * @param enabled must be true, otherwise the operation fails.
+     * @param userId The user for which to change the overlay.
+     * @return true if the system successfully registered the request, false otherwise.
      */
     boolean setEnabledExclusive(in String packageName, in boolean enable, in int userId);
 
     /**
+     * Request that an overlay package is enabled and any other overlay packages with the same
+     * target package and category are disabled.
+     *
+     * See {@link #setEnabled} for the details on overlay packages.
+     *
+     * @param packageName the name of the overlay package to enable.
+     * @param userId The user for which to change the overlay.
+     * @return true if the system successfully registered the request, false otherwise.
+     */
+    boolean setEnabledExclusiveInCategory(in String packageName, in int userId);
+
+    /**
      * Change the priority of the given overlay to be just higher than the
      * overlay with package name newParentPackageName. Both overlay packages
      * must have the same target and user.
diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java
index 8464e26..6e633426 100644
--- a/core/java/android/content/om/OverlayInfo.java
+++ b/core/java/android/content/om/OverlayInfo.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -66,14 +67,14 @@
     /**
      * The overlay is currently disabled. It can be enabled.
      *
-     * @see IOverlayManager.setEnabled
+     * @see IOverlayManager#setEnabled
      */
     public static final int STATE_DISABLED = 2;
 
     /**
      * The overlay is currently enabled. It can be disabled.
      *
-     * @see IOverlayManager.setEnabled
+     * @see IOverlayManager#setEnabled
      */
     public static final int STATE_ENABLED = 3;
 
@@ -90,6 +91,11 @@
     public static final int STATE_OVERLAY_UPGRADING = 5;
 
     /**
+     * Category for theme overlays.
+     */
+    public static final String CATEGORY_THEME = "android.theme";
+
+    /**
      * Package name of the overlay package
      */
     public final String packageName;
@@ -100,6 +106,11 @@
     public final String targetPackageName;
 
     /**
+     * Category of the overlay package
+     */
+    public final String category;
+
+    /**
      * Full path to the base APK for this overlay package
      */
     public final String baseCodePath;
@@ -121,14 +132,15 @@
      * @param state the new state for the source OverlayInfo
      */
     public OverlayInfo(@NonNull OverlayInfo source, @State int state) {
-        this(source.packageName, source.targetPackageName, source.baseCodePath, state,
-                source.userId);
+        this(source.packageName, source.targetPackageName, source.category, source.baseCodePath,
+                state, source.userId);
     }
 
     public OverlayInfo(@NonNull String packageName, @NonNull String targetPackageName,
-            @NonNull String baseCodePath, @State int state, int userId) {
+            @Nullable String category, @NonNull String baseCodePath, int state, int userId) {
         this.packageName = packageName;
         this.targetPackageName = targetPackageName;
+        this.category = category;
         this.baseCodePath = baseCodePath;
         this.state = state;
         this.userId = userId;
@@ -138,6 +150,7 @@
     public OverlayInfo(Parcel source) {
         packageName = source.readString();
         targetPackageName = source.readString();
+        category = source.readString();
         baseCodePath = source.readString();
         state = source.readInt();
         userId = source.readInt();
@@ -177,6 +190,7 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(packageName);
         dest.writeString(targetPackageName);
+        dest.writeString(category);
         dest.writeString(baseCodePath);
         dest.writeInt(state);
         dest.writeInt(userId);
@@ -275,6 +289,9 @@
         if (!targetPackageName.equals(other.targetPackageName)) {
             return false;
         }
+        if (!category.equals(other.category)) {
+            return false;
+        }
         if (!baseCodePath.equals(other.baseCodePath)) {
             return false;
         }
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 0342c93..627ceb7 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -362,6 +362,13 @@
      */
     public String overlayTarget;
 
+    /**
+     * The overlay category, if any, of this package
+     *
+     * @hide
+     */
+    public String overlayCategory;
+
     /** @hide */
     public int overlayPriority;
 
@@ -464,6 +471,7 @@
         dest.writeString(restrictedAccountType);
         dest.writeString(requiredAccountType);
         dest.writeString(overlayTarget);
+        dest.writeString(overlayCategory);
         dest.writeInt(overlayPriority);
         dest.writeBoolean(mOverlayIsStatic);
         dest.writeInt(compileSdkVersion);
@@ -531,6 +539,7 @@
         restrictedAccountType = source.readString();
         requiredAccountType = source.readString();
         overlayTarget = source.readString();
+        overlayCategory = source.readString();
         overlayPriority = source.readInt();
         mOverlayIsStatic = source.readBoolean();
         compileSdkVersion = source.readInt();
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index dda4167..9e4166e 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -676,6 +676,7 @@
         pi.restrictedAccountType = p.mRestrictedAccountType;
         pi.requiredAccountType = p.mRequiredAccountType;
         pi.overlayTarget = p.mOverlayTarget;
+        pi.overlayCategory = p.mOverlayCategory;
         pi.overlayPriority = p.mOverlayPriority;
         pi.mOverlayIsStatic = p.mOverlayIsStatic;
         pi.compileSdkVersion = p.mCompileSdkVersion;
@@ -2073,6 +2074,8 @@
                         com.android.internal.R.styleable.AndroidManifestResourceOverlay);
                 pkg.mOverlayTarget = sa.getString(
                         com.android.internal.R.styleable.AndroidManifestResourceOverlay_targetPackage);
+                pkg.mOverlayCategory = sa.getString(
+                        com.android.internal.R.styleable.AndroidManifestResourceOverlay_category);
                 pkg.mOverlayPriority = sa.getInt(
                         com.android.internal.R.styleable.AndroidManifestResourceOverlay_priority,
                         0);
@@ -6324,6 +6327,7 @@
         public String mRequiredAccountType;
 
         public String mOverlayTarget;
+        public String mOverlayCategory;
         public int mOverlayPriority;
         public boolean mOverlayIsStatic;
 
@@ -6834,6 +6838,7 @@
             mRestrictedAccountType = dest.readString();
             mRequiredAccountType = dest.readString();
             mOverlayTarget = dest.readString();
+            mOverlayCategory = dest.readString();
             mOverlayPriority = dest.readInt();
             mOverlayIsStatic = (dest.readInt() == 1);
             mCompileSdkVersion = dest.readInt();
@@ -6957,6 +6962,7 @@
             dest.writeString(mRestrictedAccountType);
             dest.writeString(mRequiredAccountType);
             dest.writeString(mOverlayTarget);
+            dest.writeString(mOverlayCategory);
             dest.writeInt(mOverlayPriority);
             dest.writeInt(mOverlayIsStatic ? 1 : 0);
             dest.writeInt(mCompileSdkVersion);
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index a61c8c1..0b72c22 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -52,6 +52,16 @@
     private static final String TAG = "DisplayCutout";
     private static final String DP_MARKER = "@dp";
 
+    /**
+     * Category for overlays that allow emulating a display cutout on devices that don't have
+     * one.
+     *
+     * @see android.content.om.IOverlayManager
+     * @hide
+     */
+    public static final String EMULATION_OVERLAY_CATEGORY =
+            "com.android.internal.display_cutout_emulation";
+
     private static final Rect ZERO_RECT = new Rect();
     private static final Region EMPTY_REGION = new Region();
 
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 287f296..32fe2b2 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2558,6 +2558,9 @@
         <!-- Package name of base package whose resources will be overlaid. -->
         <attr name="targetPackage" />
 
+        <!-- Category of the resource overlay. -->
+        <attr name="category" format="string"/>
+
         <!-- Load order of overlay package. -->
         <attr name="priority" />
 
diff --git a/packages/overlays/DisplayCutoutEmulationNarrowOverlay/AndroidManifest.xml b/packages/overlays/DisplayCutoutEmulationNarrowOverlay/AndroidManifest.xml
index 71ce6b4..426aae9 100644
--- a/packages/overlays/DisplayCutoutEmulationNarrowOverlay/AndroidManifest.xml
+++ b/packages/overlays/DisplayCutoutEmulationNarrowOverlay/AndroidManifest.xml
@@ -2,7 +2,9 @@
         package="com.android.internal.display.cutout.emulation.narrow"
         android:versionCode="1"
         android:versionName="1.0">
-    <overlay android:targetPackage="android" android:priority="1"/>
+    <overlay android:targetPackage="android"
+        android:category="com.android.internal.display_cutout_emulation"
+        android:priority="1"/>
 
     <application android:label="@string/display_cutout_emulation_overlay" android:hasCode="false"/>
 </manifest>
diff --git a/packages/overlays/DisplayCutoutEmulationTallOverlay/AndroidManifest.xml b/packages/overlays/DisplayCutoutEmulationTallOverlay/AndroidManifest.xml
index 5a93cfb..368a2d5 100644
--- a/packages/overlays/DisplayCutoutEmulationTallOverlay/AndroidManifest.xml
+++ b/packages/overlays/DisplayCutoutEmulationTallOverlay/AndroidManifest.xml
@@ -18,7 +18,9 @@
         package="com.android.internal.display.cutout.emulation.tall"
         android:versionCode="1"
         android:versionName="1.0">
-    <overlay android:targetPackage="android" android:priority="1"/>
+    <overlay android:targetPackage="android"
+            android:category="com.android.internal.display_cutout_emulation"
+            android:priority="1"/>
 
     <application android:label="@string/display_cutout_emulation_overlay" android:hasCode="false"/>
 </manifest>
diff --git a/packages/overlays/DisplayCutoutEmulationWideOverlay/AndroidManifest.xml b/packages/overlays/DisplayCutoutEmulationWideOverlay/AndroidManifest.xml
index 96bd060..b721efe 100644
--- a/packages/overlays/DisplayCutoutEmulationWideOverlay/AndroidManifest.xml
+++ b/packages/overlays/DisplayCutoutEmulationWideOverlay/AndroidManifest.xml
@@ -18,7 +18,9 @@
         package="com.android.internal.display.cutout.emulation.wide"
         android:versionCode="1"
         android:versionName="1.0">
-    <overlay android:targetPackage="android" android:priority="1"/>
+    <overlay android:targetPackage="android"
+        android:category="com.android.internal.display_cutout_emulation"
+        android:priority="1"/>
 
     <application android:label="@string/display_cutout_emulation_overlay" android:hasCode="false"/>
 </manifest>
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 4bc4a7e..7467954 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -544,7 +544,28 @@
             final long ident = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
-                    return mImpl.setEnabledExclusive(packageName, userId);
+                    return mImpl.setEnabledExclusive(packageName, false /* withinCategory */,
+                            userId);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override
+        public boolean setEnabledExclusiveInCategory(@Nullable String packageName, int userId)
+                throws RemoteException {
+            enforceChangeOverlayPackagesPermission("setEnabled");
+            userId = handleIncomingUser(userId, "setEnabled");
+            if (packageName == null) {
+                return false;
+            }
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    return mImpl.setEnabledExclusive(packageName, true /* withinCategory */,
+                            userId);
                 }
             } finally {
                 Binder.restoreCallingIdentity(ident);
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 6e02db7..a027fc6 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -35,6 +35,8 @@
 import android.util.ArraySet;
 import android.util.Slog;
 
+import libcore.util.Objects;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -103,12 +105,14 @@
         for (int i = 0; i < overlayPackagesSize; i++) {
             final PackageInfo overlayPackage = overlayPackages.get(i);
             final OverlayInfo oi = storedOverlayInfos.get(overlayPackage.packageName);
-            if (oi == null || !oi.targetPackageName.equals(overlayPackage.overlayTarget)) {
+            if (oi == null || !oi.targetPackageName.equals(overlayPackage.overlayTarget)
+                    || !Objects.equal(oi.category, overlayPackage.overlayCategory)) {
                 // Update the overlay if it didn't exist or had the wrong target package.
                 mSettings.init(overlayPackage.packageName, newUserId,
                         overlayPackage.overlayTarget,
                         overlayPackage.applicationInfo.getBaseCodePath(),
-                        overlayPackage.isStaticOverlayPackage(), overlayPackage.overlayPriority);
+                        overlayPackage.isStaticOverlayPackage(), overlayPackage.overlayPriority,
+                        overlayPackage.overlayCategory);
 
                 if (oi == null) {
                     // This overlay does not exist in our settings.
@@ -259,7 +263,8 @@
 
         mSettings.init(packageName, userId, overlayPackage.overlayTarget,
                 overlayPackage.applicationInfo.getBaseCodePath(),
-                overlayPackage.isStaticOverlayPackage(), overlayPackage.overlayPriority);
+                overlayPackage.isStaticOverlayPackage(), overlayPackage.overlayPriority,
+                overlayPackage.overlayCategory);
         try {
             if (updateState(overlayPackage.overlayTarget, packageName, userId, 0)) {
                 mListener.onOverlaysChanged(overlayPackage.overlayTarget, userId);
@@ -320,7 +325,7 @@
             if (!oldOi.targetPackageName.equals(pkg.overlayTarget)) {
                 mSettings.init(packageName, userId, pkg.overlayTarget,
                         pkg.applicationInfo.getBaseCodePath(), pkg.isStaticOverlayPackage(),
-                        pkg.overlayPriority);
+                        pkg.overlayPriority, pkg.overlayCategory);
             }
 
             if (updateState(pkg.overlayTarget, packageName, userId, 0)) {
@@ -394,10 +399,11 @@
         }
     }
 
-    boolean setEnabledExclusive(@NonNull final String packageName, final int userId) {
+    boolean setEnabledExclusive(@NonNull final String packageName, boolean withinCategory,
+            final int userId) {
         if (DEBUG) {
-            Slog.d(TAG, String.format("setEnabledExclusive packageName=%s userId=%d", packageName,
-                    userId));
+            Slog.d(TAG, String.format("setEnabledExclusive packageName=%s"
+                    + " withinCategory=%s userId=%d", packageName, withinCategory, userId));
         }
 
         final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
@@ -428,6 +434,11 @@
                     // Don't touch static overlays.
                     continue;
                 }
+                if (withinCategory && !Objects.equal(disabledOverlayPackageInfo.overlayCategory,
+                        oi.category)) {
+                    // Don't touch overlays from other categories.
+                    continue;
+                }
 
                 // Disable the overlay.
                 modified |= mSettings.setEnabled(disabledOverlayPackageName, userId, false);
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index a80cae4..e57fa0b 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -20,10 +20,7 @@
 import static com.android.server.om.OverlayManagerService.TAG;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.content.om.OverlayInfo;
-import android.os.UserHandle;
-import android.util.AndroidRuntimeException;
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.util.Xml;
@@ -67,11 +64,11 @@
 
     void init(@NonNull final String packageName, final int userId,
             @NonNull final String targetPackageName, @NonNull final String baseCodePath,
-            boolean isStatic, int priority) {
+            boolean isStatic, int priority, String overlayCategory) {
         remove(packageName, userId);
         final SettingsItem item =
                 new SettingsItem(packageName, userId, targetPackageName, baseCodePath,
-                        isStatic, priority);
+                        isStatic, priority, overlayCategory);
         if (isStatic) {
             int i;
             for (i = mItems.size() - 1; i >= 0; i--) {
@@ -292,6 +289,7 @@
             pw.print("mState.............: "); pw.println(OverlayInfo.stateToString(item.getState()));
             pw.print("mIsEnabled.........: "); pw.println(item.isEnabled());
             pw.print("mIsStatic..........: "); pw.println(item.isStatic());
+            pw.print("mCategory..........: "); pw.println(item.mCategory);
 
             pw.decreaseIndent();
             pw.println("}");
@@ -317,6 +315,7 @@
         private static final String ATTR_TARGET_PACKAGE_NAME = "targetPackageName";
         private static final String ATTR_IS_STATIC = "isStatic";
         private static final String ATTR_PRIORITY = "priority";
+        private static final String ATTR_CATEGORY = "category";
         private static final String ATTR_USER_ID = "userId";
         private static final String ATTR_VERSION = "version";
 
@@ -371,9 +370,10 @@
             final boolean isEnabled = XmlUtils.readBooleanAttribute(parser, ATTR_IS_ENABLED);
             final boolean isStatic = XmlUtils.readBooleanAttribute(parser, ATTR_IS_STATIC);
             final int priority = XmlUtils.readIntAttribute(parser, ATTR_PRIORITY);
+            final String category = XmlUtils.readStringAttribute(parser, ATTR_CATEGORY);
 
-            return new SettingsItem(packageName, userId, targetPackageName, baseCodePath, state,
-                    isEnabled, isStatic, priority);
+            return new SettingsItem(packageName, userId, targetPackageName, baseCodePath,
+                    state, isEnabled, isStatic, priority, category);
         }
 
         public static void persist(@NonNull final ArrayList<SettingsItem> table,
@@ -405,6 +405,7 @@
             XmlUtils.writeBooleanAttribute(xml, ATTR_IS_ENABLED, item.mIsEnabled);
             XmlUtils.writeBooleanAttribute(xml, ATTR_IS_STATIC, item.mIsStatic);
             XmlUtils.writeIntAttribute(xml, ATTR_PRIORITY, item.mPriority);
+            XmlUtils.writeStringAttribute(xml, ATTR_CATEGORY, item.mCategory);
             xml.endTag(null, TAG_ITEM);
         }
     }
@@ -419,17 +420,19 @@
         private OverlayInfo mCache;
         private boolean mIsStatic;
         private int mPriority;
+        private final String mCategory;
 
         SettingsItem(@NonNull final String packageName, final int userId,
                 @NonNull final String targetPackageName, @NonNull final String baseCodePath,
                 final @OverlayInfo.State int state, final boolean isEnabled, final boolean isStatic,
-                final int priority) {
+                final int priority, String category) {
             mPackageName = packageName;
             mUserId = userId;
             mTargetPackageName = targetPackageName;
             mBaseCodePath = baseCodePath;
             mState = state;
             mIsEnabled = isEnabled;
+            mCategory = category;
             mCache = null;
             mIsStatic = isStatic;
             mPriority = priority;
@@ -437,9 +440,9 @@
 
         SettingsItem(@NonNull final String packageName, final int userId,
                 @NonNull final String targetPackageName, @NonNull final String baseCodePath,
-                final boolean isStatic, final int priority) {
+                final boolean isStatic, final int priority, String category) {
             this(packageName, userId, targetPackageName, baseCodePath, OverlayInfo.STATE_UNKNOWN,
-                    false, isStatic, priority);
+                    false, isStatic, priority, category);
         }
 
         private String getTargetPackageName() {
@@ -491,8 +494,8 @@
 
         private OverlayInfo getOverlayInfo() {
             if (mCache == null) {
-                mCache = new OverlayInfo(mPackageName, mTargetPackageName, mBaseCodePath, mState,
-                        mUserId);
+                mCache = new OverlayInfo(mPackageName, mTargetPackageName, mCategory, mBaseCodePath,
+                        mState, mUserId);
             }
             return mCache;
         }
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index 29ddaf4..54bb115 100644
--- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -56,6 +56,8 @@
                     return runEnableDisable(true);
                 case "disable":
                     return runEnableDisable(false);
+                case "enable-exclusive":
+                    return runEnableExclusive();
                 case "set-priority":
                     return runSetPriority();
                 default:
@@ -86,6 +88,10 @@
         out.println("    Enable overlay package PACKAGE.");
         out.println("  disable [--user USER_ID] PACKAGE");
         out.println("    Disable overlay package PACKAGE.");
+        out.println("  enable-exclusive [--user USER_ID] [--category] PACKAGE");
+        out.println("    Enable overlay package PACKAGE and disable all other overlays for");
+        out.println("    its target package. If the --category option is given, only disables");
+        out.println("    other overlays in the same category.");
         out.println("  set-priority [--user USER_ID] PACKAGE PARENT|lowest|highest");
         out.println("    Change the priority of the overlay PACKAGE to be just higher than");
         out.println("    the priority of PACKAGE_PARENT If PARENT is the special keyword");
@@ -157,6 +163,33 @@
         return mInterface.setEnabled(packageName, enable, userId) ? 0 : 1;
     }
 
+    private int runEnableExclusive() throws RemoteException {
+        final PrintWriter err = getErrPrintWriter();
+
+        int userId = UserHandle.USER_SYSTEM;
+        boolean inCategory = false;
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "--user":
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+                case "--category":
+                    inCategory = true;
+                    break;
+                default:
+                    err.println("Error: Unknown option: " + opt);
+                    return 1;
+            }
+        }
+        final String overlay = getNextArgRequired();
+        if (inCategory) {
+            return mInterface.setEnabledExclusiveInCategory(overlay, userId) ? 0 : 1;
+        } else {
+            return mInterface.setEnabledExclusive(overlay, true, userId) ? 0 : 1;
+        }
+    }
+
     private int runSetPriority() throws RemoteException {
         final PrintWriter err = getErrPrintWriter();
 
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index b5fe9ea..a38cbda 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4637,6 +4637,11 @@
         pw.print(prefix); pw.print("  pkgFlags="); printFlags(pw, ps.pkgFlags, FLAG_DUMP_SPEC);
                 pw.println();
 
+        if (ps.pkg.mOverlayTarget != null) {
+            pw.print(prefix); pw.print("  overlayTarget="); pw.println(ps.pkg.mOverlayTarget);
+            pw.print(prefix); pw.print("  overlayCategory="); pw.println(ps.pkg.mOverlayCategory);
+        }
+
         if (ps.pkg != null && ps.pkg.permissions != null && ps.pkg.permissions.size() > 0) {
             final ArrayList<PackageParser.Permission> perms = ps.pkg.permissions;
             pw.print(prefix); pw.println("  declared permissions:");