Move app overlay permission to app level

Test: cts, atest
Bug: 111236845
Change-Id: I21c52cc7ce6d8e48354a7f258ee8b07d96dcef47
diff --git a/api/current.txt b/api/current.txt
index de4e8241..4fa007a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5730,7 +5730,6 @@
 
   public final class NotificationChannelGroup implements android.os.Parcelable {
     ctor public NotificationChannelGroup(java.lang.String, java.lang.CharSequence);
-    method public boolean canOverlayApps();
     method public android.app.NotificationChannelGroup clone();
     method public int describeContents();
     method public java.util.List<android.app.NotificationChannel> getChannels();
@@ -5738,7 +5737,6 @@
     method public java.lang.String getId();
     method public java.lang.CharSequence getName();
     method public boolean isBlocked();
-    method public void setAllowAppOverlay(boolean);
     method public void setDescription(java.lang.String);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.NotificationChannelGroup> CREATOR;
@@ -5746,6 +5744,7 @@
 
   public class NotificationManager {
     method public java.lang.String addAutomaticZenRule(android.app.AutomaticZenRule);
+    method public boolean areAppOverlaysAllowed();
     method public boolean areNotificationsEnabled();
     method public boolean canNotifyAsPackage(java.lang.String);
     method public void cancel(int);
diff --git a/api/test-current.txt b/api/test-current.txt
index 46e7683..fda6004 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -182,7 +182,6 @@
     method public int getUserLockedFields();
     method public void lockFields(int);
     method public void setBlocked(boolean);
-    field public static final int USER_LOCKED_ALLOW_APP_OVERLAY = 2; // 0x2
   }
 
   public class NotificationManager {
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 00567523..163be8e 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -65,6 +65,10 @@
     boolean areNotificationsEnabled(String pkg);
     int getPackageImportance(String pkg);
 
+    void setAppOverlaysAllowed(String pkg, int uid, boolean allowed);
+    boolean areAppOverlaysAllowed(String pkg);
+    boolean areAppOverlaysAllowedForPackage(String pkg, int uid);
+
     void createNotificationChannelGroups(String pkg, in ParceledListSlice channelGroupList);
     void createNotificationChannels(String pkg, in ParceledListSlice channelsList);
     void createNotificationChannelsForPackage(String pkg, int uid, in ParceledListSlice channelsList);
diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java
index 2322a42..34cd9f0 100644
--- a/core/java/android/app/NotificationChannelGroup.java
+++ b/core/java/android/app/NotificationChannelGroup.java
@@ -50,20 +50,12 @@
     private static final String ATT_DESC = "desc";
     private static final String ATT_ID = "id";
     private static final String ATT_BLOCKED = "blocked";
-    private static final String ATT_ALLOW_APP_OVERLAY = "app_overlay";
     private static final String ATT_USER_LOCKED = "locked";
 
-    private static final boolean DEFAULT_ALLOW_APP_OVERLAY = true;
-
     /**
      * @hide
      */
     public static final int USER_LOCKED_BLOCKED_STATE = 0x00000001;
-    /**
-     * @hide
-     */
-    @TestApi
-    public static final int USER_LOCKED_ALLOW_APP_OVERLAY = 0x00000002;
 
     /**
      * @see #getId()
@@ -74,7 +66,6 @@
     private String mDescription;
     private boolean mBlocked;
     private List<NotificationChannel> mChannels = new ArrayList<>();
-    private boolean mAllowAppOverlay = DEFAULT_ALLOW_APP_OVERLAY;
     // Bitwise representation of fields that have been changed by the user
     private int mUserLockedFields;
 
@@ -110,7 +101,6 @@
         }
         in.readParcelableList(mChannels, NotificationChannel.class.getClassLoader());
         mBlocked = in.readBoolean();
-        mAllowAppOverlay = in.readBoolean();
         mUserLockedFields = in.readInt();
     }
 
@@ -138,7 +128,6 @@
         }
         dest.writeParcelableList(mChannels, flags);
         dest.writeBoolean(mBlocked);
-        dest.writeBoolean(mAllowAppOverlay);
         dest.writeInt(mUserLockedFields);
     }
 
@@ -181,15 +170,6 @@
     }
 
     /**
-     * Returns whether notifications posted to this channel group can display outside of the
-     * notification shade, in a floating window on top of other apps. These may additionally be
-     * blocked at the notification channel level, see {@link NotificationChannel#canOverlayApps()}.
-     */
-    public boolean canOverlayApps() {
-        return mAllowAppOverlay;
-    }
-
-    /**
      * Sets the user visible description of this group.
      *
      * <p>The recommended maximum length is 300 characters; the value may be truncated if it is too
@@ -200,21 +180,6 @@
     }
 
     /**
-     * Sets whether notifications posted to this channel group can appear outside of the
-     * notification shade, floating over other apps' content.
-     *
-     * <p>This value will be ignored for notifications that are posted to channels that do not
-     * allow app overlays ({@link NotificationChannel#canOverlayApps()}.
-     *
-     * <p>Only modifiable before the channel is submitted to
-     * {@link NotificationManager#createNotificationChannelGroup(NotificationChannelGroup)}.</p>
-     * @see Notification#getAppOverlayIntent()
-     */
-    public void setAllowAppOverlay(boolean allowAppOverlay) {
-        mAllowAppOverlay = allowAppOverlay;
-    }
-
-    /**
      * @hide
      */
     @TestApi
@@ -266,7 +231,6 @@
         // Name, id, and importance are set in the constructor.
         setDescription(parser.getAttributeValue(null, ATT_DESC));
         setBlocked(safeBool(parser, ATT_BLOCKED, false));
-        setAllowAppOverlay(safeBool(parser, ATT_ALLOW_APP_OVERLAY, DEFAULT_ALLOW_APP_OVERLAY));
     }
 
     private static boolean safeBool(XmlPullParser parser, String att, boolean defValue) {
@@ -289,9 +253,6 @@
             out.attribute(null, ATT_DESC, getDescription().toString());
         }
         out.attribute(null, ATT_BLOCKED, Boolean.toString(isBlocked()));
-        if (canOverlayApps() != DEFAULT_ALLOW_APP_OVERLAY) {
-            out.attribute(null, ATT_ALLOW_APP_OVERLAY, Boolean.toString(canOverlayApps()));
-        }
         out.attribute(null, ATT_USER_LOCKED, Integer.toString(mUserLockedFields));
 
         out.endTag(null, TAG_GROUP);
@@ -307,7 +268,6 @@
         record.put(ATT_NAME, getName());
         record.put(ATT_DESC, getDescription());
         record.put(ATT_BLOCKED, isBlocked());
-        record.put(ATT_ALLOW_APP_OVERLAY, canOverlayApps());
         record.put(ATT_USER_LOCKED, mUserLockedFields);
         return record;
     }
@@ -336,7 +296,6 @@
         if (o == null || getClass() != o.getClass()) return false;
         NotificationChannelGroup that = (NotificationChannelGroup) o;
         return isBlocked() == that.isBlocked() &&
-                mAllowAppOverlay == that.mAllowAppOverlay &&
                 mUserLockedFields == that.mUserLockedFields &&
                 Objects.equals(getId(), that.getId()) &&
                 Objects.equals(getName(), that.getName()) &&
@@ -347,7 +306,7 @@
     @Override
     public int hashCode() {
         return Objects.hash(getId(), getName(), getDescription(), isBlocked(), getChannels(),
-                mAllowAppOverlay, mUserLockedFields);
+                mUserLockedFields);
     }
 
     @Override
@@ -356,7 +315,6 @@
         cloned.setDescription(getDescription());
         cloned.setBlocked(isBlocked());
         cloned.setChannels(getChannels());
-        cloned.setAllowAppOverlay(canOverlayApps());
         cloned.lockFields(mUserLockedFields);
         return cloned;
     }
@@ -369,7 +327,6 @@
                 + ", mDescription=" + (!TextUtils.isEmpty(mDescription) ? "hasDescription " : "")
                 + ", mBlocked=" + mBlocked
                 + ", mChannels=" + mChannels
-                + ", mAllowAppOverlay=" + mAllowAppOverlay
                 + ", mUserLockedFields=" + mUserLockedFields
                 + '}';
     }
@@ -385,7 +342,6 @@
         for (NotificationChannel channel : mChannels) {
             channel.writeToProto(proto, NotificationChannelGroupProto.CHANNELS);
         }
-        proto.write(NotificationChannelGroupProto.ALLOW_APP_OVERLAY, mAllowAppOverlay);
         proto.end(token);
     }
 }
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 306c366..9590782 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1074,6 +1074,25 @@
         }
     }
 
+
+    /**
+     * Sets whether notifications posted by this app can appear outside of the
+     * notification shade, floating over other apps' content.
+     *
+     * <p>This value will be ignored for notifications that are posted to channels that do not
+     * allow app overlays ({@link NotificationChannel#canOverlayApps()}.
+     *
+     * @see Notification#getAppOverlayIntent()
+     */
+    public boolean areAppOverlaysAllowed() {
+        INotificationManager service = getService();
+        try {
+            return service.areAppOverlaysAllowed(mContext.getPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /**
      * Checks the ability to modify notification do not disturb policy for the calling package.
      *
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 405edd2..8e21863 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2279,6 +2279,26 @@
         }
 
         @Override
+        public boolean areAppOverlaysAllowed(String pkg) {
+            return areAppOverlaysAllowedForPackage(pkg, Binder.getCallingUid());
+        }
+
+        @Override
+        public boolean areAppOverlaysAllowedForPackage(String pkg, int uid) {
+            checkCallerIsSystemOrSameApp(pkg);
+
+            return mPreferencesHelper.areAppOverlaysAllowed(pkg, uid);
+        }
+
+        @Override
+        public void setAppOverlaysAllowed(String pkg, int uid, boolean allowed) {
+            checkCallerIsSystem();
+
+            mPreferencesHelper.setAppOverlaysAllowed(pkg, uid, allowed);
+            handleSavePolicyFile();
+        }
+
+        @Override
         public int getPackageImportance(String pkg) {
             checkCallerIsSystemOrSameApp(pkg);
             return mPreferencesHelper.getImportance(pkg, Binder.getCallingUid());
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index eb46d53..7c0e0b0 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -80,6 +80,7 @@
     private static final String ATT_NAME = "name";
     private static final String ATT_UID = "uid";
     private static final String ATT_ID = "id";
+    private static final String ATT_APP_OVERLAY = "overlay";
     private static final String ATT_PRIORITY = "priority";
     private static final String ATT_VISIBILITY = "visibility";
     private static final String ATT_IMPORTANCE = "importance";
@@ -92,6 +93,7 @@
     private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
     private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
     private static final boolean DEFAULT_SHOW_BADGE = true;
+    private static final boolean DEFAULT_ALLOW_APP_OVERLAY = true;
     /**
      * Default value for what fields are user locked. See {@link LockableAppFields} for all lockable
      * fields.
@@ -104,6 +106,7 @@
     @IntDef({LockableAppFields.USER_LOCKED_IMPORTANCE})
     public @interface LockableAppFields {
         int USER_LOCKED_IMPORTANCE = 0x00000001;
+        int USER_LOCKED_APP_OVERLAY = 0x00000002;
     }
 
     // pkg|uid => PackagePreferences
@@ -169,7 +172,9 @@
                                     XmlUtils.readIntAttribute(
                                             parser, ATT_VISIBILITY, DEFAULT_VISIBILITY),
                                     XmlUtils.readBooleanAttribute(
-                                            parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE));
+                                            parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE),
+                                    XmlUtils.readBooleanAttribute(
+                                            parser, ATT_APP_OVERLAY, DEFAULT_ALLOW_APP_OVERLAY));
                             r.importance = XmlUtils.readIntAttribute(
                                     parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
                             r.priority = XmlUtils.readIntAttribute(
@@ -264,11 +269,12 @@
 
     private PackagePreferences getOrCreatePackagePreferences(String pkg, int uid) {
         return getOrCreatePackagePreferences(pkg, uid,
-                DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE);
+                DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE,
+                DEFAULT_ALLOW_APP_OVERLAY);
     }
 
     private PackagePreferences getOrCreatePackagePreferences(String pkg, int uid, int importance,
-            int priority, int visibility, boolean showBadge) {
+            int priority, int visibility, boolean showBadge, boolean allowAppOverlay) {
         final String key = packagePreferencesKey(pkg, uid);
         synchronized (mPackagePreferences) {
             PackagePreferences
@@ -282,6 +288,7 @@
                 r.priority = priority;
                 r.visibility = visibility;
                 r.showBadge = showBadge;
+                r.appOverlay = allowAppOverlay;
 
                 try {
                     createDefaultChannelIfNeeded(r);
@@ -382,7 +389,8 @@
                                 || r.lockedAppFields != DEFAULT_LOCKED_APP_FIELDS
                                 || r.channels.size() > 0
                                 || r.groups.size() > 0
-                                || r.delegate != null;
+                                || r.delegate != null
+                                || r.appOverlay != DEFAULT_ALLOW_APP_OVERLAY;
                 if (hasNonDefaultSettings) {
                     out.startTag(null, TAG_PACKAGE);
                     out.attribute(null, ATT_NAME, r.pkg);
@@ -395,6 +403,9 @@
                     if (r.visibility != DEFAULT_VISIBILITY) {
                         out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
                     }
+                    if (r.appOverlay != DEFAULT_ALLOW_APP_OVERLAY) {
+                        out.attribute(null, ATT_APP_OVERLAY, Boolean.toString(r.appOverlay));
+                    }
                     out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge));
                     out.attribute(null, ATT_APP_USER_LOCKED_FIELDS,
                             Integer.toString(r.lockedAppFields));
@@ -439,6 +450,20 @@
         out.endTag(null, TAG_RANKING);
     }
 
+    public void setAppOverlaysAllowed(String pkg, int uid, boolean allowed) {
+        PackagePreferences p = getOrCreatePackagePreferences(pkg, uid);
+        p.appOverlay = allowed;
+        p.lockedAppFields = p.lockedAppFields | LockableAppFields.USER_LOCKED_APP_OVERLAY;
+    }
+
+    public boolean areAppOverlaysAllowed(String pkg, int uid) {
+        return getOrCreatePackagePreferences(pkg, uid).appOverlay;
+    }
+
+    public int getAppLockedFields(String pkg, int uid) {
+        return getOrCreatePackagePreferences(pkg, uid).lockedAppFields;
+    }
+
     /**
      * Gets importance.
      */
@@ -512,7 +537,6 @@
             // apps can't update the blocked status or app overlay permission
             if (fromTargetApp) {
                 group.setBlocked(oldGroup.isBlocked());
-                group.setAllowAppOverlay(oldGroup.canOverlayApps());
                 group.unlockFields(group.getUserLockedFields());
                 group.lockFields(oldGroup.getUserLockedFields());
             } else {
@@ -521,9 +545,6 @@
                     group.lockFields(NotificationChannelGroup.USER_LOCKED_BLOCKED_STATE);
                     updateChannelsBypassingDnd(mContext.getUserId());
                 }
-                if (group.canOverlayApps() != oldGroup.canOverlayApps()) {
-                    group.lockFields(NotificationChannelGroup.USER_LOCKED_ALLOW_APP_OVERLAY);
-                }
             }
         }
         r.groups.put(group.getId(), group);
@@ -1581,6 +1602,7 @@
         int priority = DEFAULT_PRIORITY;
         int visibility = DEFAULT_VISIBILITY;
         boolean showBadge = DEFAULT_SHOW_BADGE;
+        boolean appOverlay = DEFAULT_ALLOW_APP_OVERLAY;
         int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
 
         Delegate delegate = null;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 527a1ee..fdf6c03 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -3507,6 +3507,12 @@
     }
 
     @Test
+    public void testAppOverlay() throws Exception {
+        mBinderService.setAppOverlaysAllowed(PKG, mUid, false);
+        assertFalse(mBinderService.areAppOverlaysAllowedForPackage(PKG, mUid));
+    }
+
+    @Test
     public void testIsCallerInstantApp_primaryUser() throws Exception {
         ApplicationInfo info = new ApplicationInfo();
         info.privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index b027935..0b73481 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -1584,39 +1584,6 @@
     }
 
     @Test
-    public void testUpdateGroup_fromSystem_appOverlay() {
-        NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
-        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true);
-
-        // from system, allowed
-        NotificationChannelGroup update = ncg.clone();
-        update.setAllowAppOverlay(false);
-
-        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, update, false);
-        NotificationChannelGroup updated =
-                mHelper.getNotificationChannelGroup("group1", PKG_N_MR1, UID_N_MR1);
-        assertFalse(updated.canOverlayApps());
-        assertEquals(NotificationChannelGroup.USER_LOCKED_ALLOW_APP_OVERLAY,
-                updated.getUserLockedFields());
-    }
-
-    @Test
-    public void testUpdateGroup_fromApp_appOverlay() {
-        NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
-        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true);
-
-        // from app, not allowed
-        NotificationChannelGroup update = new NotificationChannelGroup("group1", "name1");
-        update.setAllowAppOverlay(false);
-
-        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true);
-        NotificationChannelGroup updated =
-                mHelper.getNotificationChannelGroup("group1", PKG_N_MR1, UID_N_MR1);
-        assertTrue(updated.canOverlayApps());
-        assertEquals(0, updated.getUserLockedFields());
-    }
-
-    @Test
     public void testCannotCreateChannel_badGroup() {
         NotificationChannel channel1 =
                 new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
@@ -2192,4 +2159,32 @@
         mHelper.toggleNotificationDelegate(PKG_O, UID_O, true);
         assertEquals("other", mHelper.getNotificationDelegate(PKG_O, UID_O));
     }
+
+    @Test
+    public void testAllowAppOverlay_defaults() throws Exception {
+        assertTrue(mHelper.areAppOverlaysAllowed(PKG_O, UID_O));
+
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false);
+        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
+        loadStreamXml(baos, false);
+
+        assertTrue(mHelper.areAppOverlaysAllowed(PKG_O, UID_O));
+        assertEquals(0, mHelper.getAppLockedFields(PKG_O, UID_O));
+    }
+
+    @Test
+    public void testAllowAppOverlay_xml() throws Exception {
+        mHelper.setAppOverlaysAllowed(PKG_O, UID_O, false);
+        assertFalse(mHelper.areAppOverlaysAllowed(PKG_O, UID_O));
+        assertEquals(PreferencesHelper.LockableAppFields.USER_LOCKED_APP_OVERLAY,
+                mHelper.getAppLockedFields(PKG_O, UID_O));
+
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false);
+        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
+        loadStreamXml(baos, false);
+
+        assertFalse(mHelper.areAppOverlaysAllowed(PKG_O, UID_O));
+        assertEquals(PreferencesHelper.LockableAppFields.USER_LOCKED_APP_OVERLAY,
+                mHelper.getAppLockedFields(PKG_O, UID_O));
+    }
 }