Adding an api to mark a package as distracting

Adding a package manager api to mark packages as distracting to the
user. While doing this, some restrictions can be imposed on these
packages to enable the user to refrain from using them often.

Test: Unit tests:
atest com.android.server.pm.PackageManagerSettingsTests\
com.android.server.pm.PackageUserStateTest

GTS test: atest GtsSuspendAppsTestCases

Bug: 117407613
Change-Id: I5d0606b3c6c1edcaba001852d10f1a9e140b8028
diff --git a/api/current.txt b/api/current.txt
index f81eb54..3af107f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -11184,6 +11184,7 @@
     method public void registerCallback(android.content.pm.LauncherApps.Callback);
     method public void registerCallback(android.content.pm.LauncherApps.Callback, android.os.Handler);
     method public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle);
+    method public boolean shouldHideFromSuggestions(java.lang.String, android.os.UserHandle);
     method public void startAppDetailsActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
     method public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
     method public void startShortcut(java.lang.String, java.lang.String, android.graphics.Rect, android.os.Bundle, android.os.UserHandle);
diff --git a/api/system-current.txt b/api/system-current.txt
index 05aa0ad..d0f45ee 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1253,6 +1253,7 @@
     method public abstract void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
     method public void sendDeviceCustomizationReadyBroadcast();
     method public abstract boolean setDefaultBrowserPackageNameAsUser(java.lang.String, int);
+    method public java.lang.String[] setDistractingPackageRestrictions(java.lang.String[], int);
     method public void setHarmfulAppWarning(java.lang.String, java.lang.CharSequence);
     method public deprecated java.lang.String[] setPackagesSuspended(java.lang.String[], boolean, android.os.PersistableBundle, android.os.PersistableBundle, java.lang.String);
     method public java.lang.String[] setPackagesSuspended(java.lang.String[], boolean, android.os.PersistableBundle, android.os.PersistableBundle, android.content.pm.SuspendDialogInfo);
@@ -1320,6 +1321,9 @@
     field public static final int MATCH_ANY_USER = 4194304; // 0x400000
     field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000
     field public static final int MATCH_INSTANT = 8388608; // 0x800000
+    field public static final int RESTRICTION_HIDE_FROM_SUGGESTIONS = 1; // 0x1
+    field public static final int RESTRICTION_HIDE_NOTIFICATIONS = 2; // 0x2
+    field public static final int RESTRICTION_NONE = 0; // 0x0
   }
 
   public static abstract class PackageManager.DexModuleRegisterCallback {
@@ -1327,6 +1331,9 @@
     method public abstract void onDexModuleRegistered(java.lang.String, boolean, java.lang.String);
   }
 
+  public static abstract class PackageManager.DistractionRestriction implements java.lang.annotation.Annotation {
+  }
+
   public static abstract interface PackageManager.OnPermissionsChangedListener {
     method public abstract void onPermissionsChanged(int);
   }
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 94983e1..9bcb36f 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -2276,6 +2276,16 @@
     }
 
     @Override
+    public String[] setDistractingPackageRestrictions(String[] packages, int distractionFlags) {
+        try {
+            return mPM.setDistractingPackageRestrictionsAsUser(packages, distractionFlags,
+                    mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
     public String[] setPackagesSuspended(String[] packageNames, boolean suspended,
             PersistableBundle appExtras, PersistableBundle launcherExtras,
             String dialogMessage) {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 7b3497b..ec0692e 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2353,6 +2353,25 @@
     public static final String ACTION_PACKAGES_UNSUSPENDED = "android.intent.action.PACKAGES_UNSUSPENDED";
 
     /**
+     * Broadcast Action: Distracting packages have been changed.
+     * <p>Includes the following extras:
+     * <ul>
+     * <li> {@link #EXTRA_CHANGED_PACKAGE_LIST} is the set of packages which have been changed.
+     * <li> {@link #EXTRA_CHANGED_UID_LIST} is the set of uids which have been changed.
+     * <li> {@link #EXTRA_DISTRACTION_RESTRICTIONS} the new restrictions set on these packages.
+     * </ul>
+     *
+     * <p class="note">This is a protected intent that can only be sent
+     * by the system. It is only sent to registered receivers.
+     *
+     * @see PackageManager#setDistractingPackageRestrictions(String[], int)
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_DISTRACTING_PACKAGES_CHANGED =
+            "android.intent.action.DISTRACTING_PACKAGES_CHANGED";
+
+    /**
      * Broadcast Action: Sent to a package that has been suspended by the system. This is sent
      * whenever a package is put into a suspended state or any of its app extras change while in the
      * suspended state.
@@ -5062,6 +5081,17 @@
             "android.intent.extra.changed_uid_list";
 
     /**
+     * An integer denoting a bitwise combination of restrictions set on distracting packages via
+     * {@link PackageManager#setDistractingPackageRestrictions(String[], int)}
+     *
+     * @hide
+     * @see PackageManager.DistractionRestriction
+     * @see PackageManager#setDistractingPackageRestrictions(String[], int)
+     */
+    public static final String EXTRA_DISTRACTION_RESTRICTIONS =
+            "android.intent.extra.distraction_restrictions";
+
+    /**
      * @hide
      * Magic extra system code can use when binding, to give a label for
      * who it is that has bound to a service.  This is an integer giving
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index ba7710b..db2b6fd 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -69,6 +69,7 @@
             int userId);
 
     boolean hasShortcutHostPermission(String callingPackage);
+    boolean shouldHideFromSuggestions(String packageName, in UserHandle user);
 
     ParceledListSlice getShortcutConfigActivities(
             String callingPackage, String packageName, in UserHandle user);
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 64a4479b..d5c3b26 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -273,6 +273,9 @@
 
     void clearCrossProfileIntentFilters(int sourceUserId, String ownerPackage);
 
+    String[] setDistractingPackageRestrictionsAsUser(in String[] packageNames, int restrictionFlags,
+            int userId);
+
     String[] setPackagesSuspendedAsUser(in String[] packageNames, boolean suspended,
             in PersistableBundle appExtras, in PersistableBundle launcherExtras,
             in SuspendDialogInfo dialogInfo, String callingPackage, int userId);
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 44e652f..983ea9f 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -697,6 +697,26 @@
     }
 
     /**
+     * Returns whether a package should be hidden from suggestions to the user. Currently, this
+     * could be done because the package was marked as distracting to the user via
+     * {@code PackageManager.setDistractingPackageRestrictions(String[], int)}.
+     *
+     * @param packageName The package for which to check.
+     * @param user the {@link UserHandle} of the profile.
+     * @return
+     */
+    public boolean shouldHideFromSuggestions(@NonNull String packageName,
+            @NonNull UserHandle user) {
+        Preconditions.checkNotNull(packageName, "packageName");
+        Preconditions.checkNotNull(user, "user");
+        try {
+            return mService.shouldHideFromSuggestions(packageName, user);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns {@link ApplicationInfo} about an application installed for a specific user profile.
      *
      * @param packageName The package name of the application
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 2aeb68d..636a70f 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5889,6 +5889,74 @@
     public abstract boolean isSignedByExactly(String packageName, KeySet ks);
 
     /**
+     * Flag to denote no restrictions. This should be used to clear any restrictions that may have
+     * been previously set for the package.
+     * @see PackageManager.DistractionRestriction
+     * @hide
+     */
+    @SystemApi
+    public static final int RESTRICTION_NONE = 0x0;
+
+    /**
+     * Flag to denote that a package should be hidden from any suggestions to the user.
+     * @see PackageManager.DistractionRestriction
+     * @hide
+     */
+    @SystemApi
+    public static final int RESTRICTION_HIDE_FROM_SUGGESTIONS = 0x00000001;
+
+    /**
+     * Flag to denote that a package's notifications should be hidden.
+     * @see PackageManager.DistractionRestriction
+     * @hide
+     */
+    @SystemApi
+    public static final int RESTRICTION_HIDE_NOTIFICATIONS = 0x00000002;
+
+    /**
+     * Restriction flags to set on a package that is considered as distracting to the user.
+     * These should help the user to restrict their usage of these apps.
+     *
+     * @see #setDistractingPackageRestrictions(String[], int)
+     * @hide
+     */
+    @SystemApi
+    @IntDef(flag = true, prefix = {"RESTRICTION_"}, value = {
+            RESTRICTION_NONE,
+            RESTRICTION_HIDE_FROM_SUGGESTIONS,
+            RESTRICTION_HIDE_NOTIFICATIONS
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DistractionRestriction {}
+
+    /**
+     * Mark or unmark the given packages as distracting to the user.
+     * These packages can have certain restrictions set that should discourage the user to launch
+     * them often. For example, notifications from such an app can be hidden, or the app can be
+     * removed from launcher suggestions, so the user is able to restrict their use of these apps.
+     *
+     * <p>The caller must hold {@link android.Manifest.permission#SUSPEND_APPS} to use this API.
+     *
+     * @param packages Packages to mark as distracting.
+     * @param restrictionFlags Any combination of {@link DistractionRestriction restrictions} to
+     *                         impose on the given packages. {@link #RESTRICTION_NONE} can be used
+     *                         to clear any existing restrictions.
+     * @return A list of packages that could not have the {@code restrictionFlags} set. The system
+     * may prevent restricting critical packages to preserve normal device function.
+     *
+     * @see DistractionRestriction
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.SUSPEND_APPS)
+    @NonNull
+    public String[] setDistractingPackageRestrictions(@NonNull String[] packages,
+            @DistractionRestriction int restrictionFlags) {
+        throw new UnsupportedOperationException(
+                "setDistractingPackageRestrictions not implemented");
+    }
+
+    /**
      * Puts the package in a suspended state, where attempts at starting activities are denied.
      *
      * <p>It doesn't remove the data or the actual package file. The application's notifications
@@ -5911,8 +5979,7 @@
      * {@link PersistableBundle} objects to be shared with the apps being suspended and the
      * launcher to support customization that they might need to handle the suspended state.
      *
-     * <p>The caller must hold {@link Manifest.permission#SUSPEND_APPS} or
-     * {@link Manifest.permission#MANAGE_USERS} to use this api.</p>
+     * <p>The caller must hold {@link Manifest.permission#SUSPEND_APPS} to use this API.
      *
      * @param packageNames The names of the packages to set the suspended status.
      * @param suspended If set to {@code true}, the packages will be suspended, if set to
@@ -5955,7 +6022,7 @@
      * <p>When the user tries to launch a suspended app, a system dialog alerting them that the app
      * is suspended will be shown instead.
      * The caller can optionally customize the dialog by passing a {@link SuspendDialogInfo} object
-     * to this api. This dialog will have a button that starts the
+     * to this API. This dialog will have a button that starts the
      * {@link Intent#ACTION_SHOW_SUSPENDED_APP_DETAILS} intent if the suspending app declares an
      * activity which handles this action.
      *
@@ -5966,7 +6033,7 @@
      * {@link PersistableBundle} objects to be shared with the apps being suspended and the
      * launcher to support customization that they might need to handle the suspended state.
      *
-     * <p>The caller must hold {@link Manifest.permission#SUSPEND_APPS} to use this api.
+     * <p>The caller must hold {@link Manifest.permission#SUSPEND_APPS} to use this API.
      *
      * @param packageNames The names of the packages to set the suspended status.
      * @param suspended If set to {@code true}, the packages will be suspended, if set to
@@ -6005,7 +6072,7 @@
      * #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle,
      * SuspendDialogInfo) setPackagesSuspended}. The platform prevents suspending certain critical
      * packages to keep the device in a functioning state, e.g. the default dialer.
-     * Apps need to hold {@link Manifest.permission#SUSPEND_APPS SUSPEND_APPS} to call this api.
+     * Apps need to hold {@link Manifest.permission#SUSPEND_APPS SUSPEND_APPS} to call this API.
      *
      * <p>
      * Note that this set of critical packages can change with time, so even though a package name
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 83979e9..83563d0 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -284,6 +284,17 @@
     public abstract SuspendDialogInfo getSuspendedDialogInfo(String suspendedPackage, int userId);
 
     /**
+     * Gets any distraction flags set via
+     * {@link PackageManager#setDistractingPackageRestrictions(String[], int)}
+     *
+     * @param packageName
+     * @param userId
+     * @return A bitwise OR of any of the {@link PackageManager.DistractionRestriction}
+     */
+    public abstract @PackageManager.DistractionRestriction int getDistractingPackageRestrictions(
+            String packageName, int userId);
+
+    /**
      * Do a straight uid lookup for the given package/application in the given user.
      * @see PackageManager#getPackageUidAsUser(String, int, int)
      * @return The app's uid, or < 0 if the package was not found in that user
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index be6ed51..ce775de 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -48,6 +48,7 @@
     public boolean stopped;
     public boolean notLaunched;
     public boolean hidden; // Is the app restricted by owner / admin
+    public int distractionFlags;
     public boolean suspended;
     public String suspendingPackage;
     public SuspendDialogInfo dialogInfo;
@@ -86,6 +87,7 @@
         stopped = o.stopped;
         notLaunched = o.notLaunched;
         hidden = o.hidden;
+        distractionFlags = o.distractionFlags;
         suspended = o.suspended;
         suspendingPackage = o.suspendingPackage;
         dialogInfo = o.dialogInfo;
@@ -207,6 +209,9 @@
         if (hidden != oldState.hidden) {
             return false;
         }
+        if (distractionFlags != oldState.distractionFlags) {
+            return false;
+        }
         if (suspended != oldState.suspended) {
             return false;
         }
diff --git a/core/proto/android/service/package.proto b/core/proto/android/service/package.proto
index 4ecf52c..7f96d70 100644
--- a/core/proto/android/service/package.proto
+++ b/core/proto/android/service/package.proto
@@ -111,6 +111,7 @@
         optional EnabledState enabled_state = 7;
         optional string last_disabled_app_caller = 8;
         optional string suspending_package = 9;
+        optional int32 distraction_flags = 10;
     }
 
     // Name of package. e.g. "com.android.providers.telephony".
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 0778304..e39eec5 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -51,6 +51,7 @@
     <protected-broadcast android:name="android.intent.action.PACKAGE_VERIFIED" />
     <protected-broadcast android:name="android.intent.action.PACKAGES_SUSPENDED" />
     <protected-broadcast android:name="android.intent.action.PACKAGES_UNSUSPENDED" />
+    <protected-broadcast android:name="android.intent.action.DISTRACTING_PACKAGES_CHANGED" />
     <protected-broadcast android:name="android.intent.action.ACTION_PREFERRED_ACTIVITY_CHANGED" />
     <protected-broadcast android:name="android.intent.action.UID_REMOVED" />
     <protected-broadcast android:name="android.intent.action.QUERY_PACKAGE_RESTART" />
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 7f1fb6c..e92f75d 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -301,6 +301,17 @@
         }
 
         @Override
+        public boolean shouldHideFromSuggestions(String packageName, UserHandle user) {
+            if (!canAccessProfile(user.getIdentifier(), "cannot get shouldHideFromSuggestions")) {
+                return false;
+            }
+            final PackageManagerInternal pmi = LocalServices.getService(
+                    PackageManagerInternal.class);
+            int flags = pmi.getDistractingPackageRestrictions(packageName, user.getIdentifier());
+            return (flags & PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS) != 0;
+        }
+
+        @Override
         public ParceledListSlice<ResolveInfo> getLauncherActivities(String callingPackage,
                 String packageName, UserHandle user) throws RemoteException {
             ParceledListSlice<ResolveInfo> launcherActivities = queryActivitiesForUser(
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index fe89be6..4e647ac 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -84,6 +84,7 @@
 import static android.content.pm.PackageManager.MOVE_FAILED_SYSTEM_PACKAGE;
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.content.pm.PackageManager.RESTRICTION_NONE;
 import static android.content.pm.PackageParser.isApkFile;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
@@ -12657,22 +12658,30 @@
         info.sendPackageRemovedBroadcasts(true /*killApp*/);
     }
 
+    private void sendDistractingPackagesChanged(String[] pkgList, int[] uidList, int userId,
+            int distractionFlags) {
+        final Bundle extras = new Bundle(3);
+        extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList);
+        extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList);
+        extras.putInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS, distractionFlags);
+        sendPackageBroadcast(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED, null, extras,
+                Intent.FLAG_RECEIVER_REGISTERED_ONLY, null, null, new int[]{userId}, null);
+    }
+
     private void sendPackagesSuspendedForUser(String[] pkgList, int[] uidList, int userId,
             boolean suspended, PersistableBundle launcherExtras) {
-        if (pkgList.length > 0) {
-            Bundle extras = new Bundle(1);
-            extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList);
-            extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList);
-            if (launcherExtras != null) {
-                extras.putBundle(Intent.EXTRA_LAUNCHER_EXTRAS,
-                        new Bundle(launcherExtras.deepCopy()));
-            }
-            sendPackageBroadcast(
-                    suspended ? Intent.ACTION_PACKAGES_SUSPENDED
-                            : Intent.ACTION_PACKAGES_UNSUSPENDED,
-                    null, extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null, null,
-                    new int[] {userId}, null);
+        final Bundle extras = new Bundle(3);
+        extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList);
+        extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList);
+        if (launcherExtras != null) {
+            extras.putBundle(Intent.EXTRA_LAUNCHER_EXTRAS,
+                    new Bundle(launcherExtras.deepCopy()));
         }
+        sendPackageBroadcast(
+                suspended ? Intent.ACTION_PACKAGES_SUSPENDED
+                        : Intent.ACTION_PACKAGES_UNSUSPENDED,
+                null, extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null, null,
+                new int[] {userId}, null);
     }
 
     /**
@@ -12822,6 +12831,62 @@
     }
 
     @Override
+    public String[] setDistractingPackageRestrictionsAsUser(String[] packageNames,
+            int restrictionFlags, int userId) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SUSPEND_APPS,
+                "setPackagesSuspendedAsUser");
+
+        final int callingUid = Binder.getCallingUid();
+        if (callingUid != Process.ROOT_UID && callingUid != Process.SYSTEM_UID
+                && UserHandle.getUserId(callingUid) != userId) {
+            throw new SecurityException("Calling uid " + callingUid + " cannot call for user "
+                    + userId);
+        }
+        Preconditions.checkNotNull(packageNames, "packageNames cannot be null");
+
+        final List<String> changedPackagesList = new ArrayList<>(packageNames.length);
+        final IntArray changedUids = new IntArray(packageNames.length);
+        final List<String> unactionedPackages = new ArrayList<>(packageNames.length);
+
+        for (int i = 0; i < packageNames.length; i++) {
+            final String packageName = packageNames[i];
+            final PackageSetting pkgSetting;
+            synchronized (mPackages) {
+                pkgSetting = mSettings.mPackages.get(packageName);
+                if (pkgSetting == null || filterAppAccessLPr(pkgSetting, callingUid, userId)) {
+                    Slog.w(TAG, "Could not find package setting for package: " + packageName
+                            + ". Skipping...");
+                    unactionedPackages.add(packageName);
+                    continue;
+                }
+            }
+            if (restrictionFlags != 0 && !canSuspendPackageForUserInternal(packageName, userId)) {
+                unactionedPackages.add(packageName);
+                continue;
+            }
+            synchronized (mPackages) {
+                final int oldDistractionFlags = pkgSetting.getDistractionFlags(userId);
+                if (restrictionFlags != oldDistractionFlags) {
+                    pkgSetting.setDistractionFlags(restrictionFlags, userId);
+                    changedPackagesList.add(packageName);
+                    changedUids.add(UserHandle.getUid(userId, pkgSetting.appId));
+                }
+            }
+        }
+
+        if (!changedPackagesList.isEmpty()) {
+            final String[] changedPackages = changedPackagesList.toArray(
+                    new String[changedPackagesList.size()]);
+            sendDistractingPackagesChanged(changedPackages, changedUids.toArray(), userId,
+                    restrictionFlags);
+            synchronized (mPackages) {
+                scheduleWritePackageRestrictionsLocked(userId);
+            }
+        }
+        return unactionedPackages.toArray(new String[0]);
+    }
+
+    @Override
     public String[] setPackagesSuspendedAsUser(String[] packageNames, boolean suspended,
             PersistableBundle appExtras, PersistableBundle launcherExtras,
             SuspendDialogInfo dialogInfo, String callingPackage, int userId) {
@@ -12846,44 +12911,37 @@
         final List<String> changedPackagesList = new ArrayList<>(packageNames.length);
         final IntArray changedUids = new IntArray(packageNames.length);
         final List<String> unactionedPackages = new ArrayList<>(packageNames.length);
-        final long callingId = Binder.clearCallingIdentity();
-        try {
-            for (int i = 0; i < packageNames.length; i++) {
-                final String packageName = packageNames[i];
-                if (callingPackage.equals(packageName)) {
-                    Slog.w(TAG, "Calling package: " + callingPackage + " trying to "
-                            + (suspended ? "" : "un") + "suspend itself. Ignoring");
-                    unactionedPackages.add(packageName);
-                    continue;
-                }
-                PackageSetting pkgSetting;
-                synchronized (mPackages) {
-                    pkgSetting = mSettings.mPackages.get(packageName);
-                    if (pkgSetting == null
-                            || filterAppAccessLPr(pkgSetting, callingUid, userId)) {
-                        Slog.w(TAG, "Could not find package setting for package: " + packageName
-                                + ". Skipping suspending/un-suspending.");
-                        unactionedPackages.add(packageName);
-                        continue;
-                    }
-                }
-                if (suspended && !canSuspendPackageForUserInternal(packageName, userId)) {
-                    unactionedPackages.add(packageName);
-                    continue;
-                }
-                synchronized (mPackages) {
-                    pkgSetting = mSettings.mPackages.get(packageName);
-                    if (pkgSetting != null) {
-                        pkgSetting.setSuspended(suspended, callingPackage, dialogInfo, appExtras,
-                                launcherExtras, userId);
-                    }
-                }
-                changedPackagesList.add(packageName);
-                changedUids.add(UserHandle.getUid(userId, pkgSetting.appId));
+
+        for (int i = 0; i < packageNames.length; i++) {
+            final String packageName = packageNames[i];
+            if (callingPackage.equals(packageName)) {
+                Slog.w(TAG, "Calling package: " + callingPackage + " trying to "
+                        + (suspended ? "" : "un") + "suspend itself. Ignoring");
+                unactionedPackages.add(packageName);
+                continue;
             }
-        } finally {
-            Binder.restoreCallingIdentity(callingId);
+            final PackageSetting pkgSetting;
+            synchronized (mPackages) {
+                pkgSetting = mSettings.mPackages.get(packageName);
+                if (pkgSetting == null || filterAppAccessLPr(pkgSetting, callingUid, userId)) {
+                    Slog.w(TAG, "Could not find package setting for package: " + packageName
+                            + ". Skipping suspending/un-suspending.");
+                    unactionedPackages.add(packageName);
+                    continue;
+                }
+            }
+            if (suspended && !canSuspendPackageForUserInternal(packageName, userId)) {
+                unactionedPackages.add(packageName);
+                continue;
+            }
+            synchronized (mPackages) {
+                pkgSetting.setSuspended(suspended, callingPackage, dialogInfo, appExtras,
+                        launcherExtras, userId);
+            }
+            changedPackagesList.add(packageName);
+            changedUids.add(UserHandle.getUid(userId, pkgSetting.appId));
         }
+
         if (!changedPackagesList.isEmpty()) {
             final String[] changedPackages = changedPackagesList.toArray(
                     new String[changedPackagesList.size()]);
@@ -13035,88 +13093,87 @@
                     + " cannot query getUnsuspendablePackagesForUser for user " + userId);
         }
         final ArraySet<String> unactionablePackages = new ArraySet<>();
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            for (String packageName : packageNames) {
-                if (!canSuspendPackageForUserInternal(packageName, userId)) {
-                    unactionablePackages.add(packageName);
-                }
+        for (String packageName : packageNames) {
+            if (!canSuspendPackageForUserInternal(packageName, userId)) {
+                unactionablePackages.add(packageName);
             }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
         }
         return unactionablePackages.toArray(new String[unactionablePackages.size()]);
     }
 
     private boolean canSuspendPackageForUserInternal(String packageName, int userId) {
-        if (isPackageDeviceAdmin(packageName, userId)) {
-            Slog.w(TAG, "Cannot suspend package \"" + packageName
-                    + "\": has an active device admin");
-            return false;
-        }
-
-        String activeLauncherPackageName = getActiveLauncherPackageName(userId);
-        if (packageName.equals(activeLauncherPackageName)) {
-            Slog.w(TAG, "Cannot suspend package \"" + packageName
-                    + "\": contains the active launcher");
-            return false;
-        }
-
-        if (packageName.equals(mRequiredInstallerPackage)) {
-            Slog.w(TAG, "Cannot suspend package \"" + packageName
-                    + "\": required for package installation");
-            return false;
-        }
-
-        if (packageName.equals(mRequiredUninstallerPackage)) {
-            Slog.w(TAG, "Cannot suspend package \"" + packageName
-                    + "\": required for package uninstallation");
-            return false;
-        }
-
-        if (packageName.equals(mRequiredVerifierPackage)) {
-            Slog.w(TAG, "Cannot suspend package \"" + packageName
-                    + "\": required for package verification");
-            return false;
-        }
-
-        if (packageName.equals(getDefaultDialerPackageName(userId))) {
-            Slog.w(TAG, "Cannot suspend package \"" + packageName
-                    + "\": is the default dialer");
-            return false;
-        }
-
-        if (packageName.equals(mRequiredPermissionControllerPackage)) {
-            Slog.w(TAG, "Cannot suspend package \"" + packageName
-                    + "\": required for permissions management");
-            return false;
-        }
-
-        synchronized (mPackages) {
-            if (mProtectedPackages.isPackageStateProtected(userId, packageName)) {
+        final long callingId = Binder.clearCallingIdentity();
+        try {
+            if (isPackageDeviceAdmin(packageName, userId)) {
                 Slog.w(TAG, "Cannot suspend package \"" + packageName
-                        + "\": protected package");
+                        + "\": has an active device admin");
                 return false;
             }
 
-            // Cannot suspend static shared libs as they are considered
-            // a part of the using app (emulating static linking). Also
-            // static libs are installed always on internal storage.
-            PackageParser.Package pkg = mPackages.get(packageName);
-            if (pkg != null && pkg.applicationInfo.isStaticSharedLibrary()) {
-                Slog.w(TAG, "Cannot suspend package: " + packageName
-                        + " providing static shared library: "
-                        + pkg.staticSharedLibName);
+            String activeLauncherPackageName = getActiveLauncherPackageName(userId);
+            if (packageName.equals(activeLauncherPackageName)) {
+                Slog.w(TAG, "Cannot suspend package \"" + packageName
+                        + "\": contains the active launcher");
                 return false;
             }
-        }
 
-        if (PLATFORM_PACKAGE_NAME.equals(packageName)) {
-            Slog.w(TAG, "Cannot suspend the platform package: " + packageName);
-            return false;
-        }
+            if (packageName.equals(mRequiredInstallerPackage)) {
+                Slog.w(TAG, "Cannot suspend package \"" + packageName
+                        + "\": required for package installation");
+                return false;
+            }
 
-        return true;
+            if (packageName.equals(mRequiredUninstallerPackage)) {
+                Slog.w(TAG, "Cannot suspend package \"" + packageName
+                        + "\": required for package uninstallation");
+                return false;
+            }
+
+            if (packageName.equals(mRequiredVerifierPackage)) {
+                Slog.w(TAG, "Cannot suspend package \"" + packageName
+                        + "\": required for package verification");
+                return false;
+            }
+
+            if (packageName.equals(getDefaultDialerPackageName(userId))) {
+                Slog.w(TAG, "Cannot suspend package \"" + packageName
+                        + "\": is the default dialer");
+                return false;
+            }
+
+            if (packageName.equals(mRequiredPermissionControllerPackage)) {
+                Slog.w(TAG, "Cannot suspend package \"" + packageName
+                        + "\": required for permissions management");
+                return false;
+            }
+
+            synchronized (mPackages) {
+                if (mProtectedPackages.isPackageStateProtected(userId, packageName)) {
+                    Slog.w(TAG, "Cannot suspend package \"" + packageName
+                            + "\": protected package");
+                    return false;
+                }
+
+                // Cannot suspend static shared libs as they are considered
+                // a part of the using app (emulating static linking). Also
+                // static libs are installed always on internal storage.
+                PackageParser.Package pkg = mPackages.get(packageName);
+                if (pkg != null && pkg.applicationInfo.isStaticSharedLibrary()) {
+                    Slog.w(TAG, "Cannot suspend package: " + packageName
+                            + " providing static shared library: "
+                            + pkg.staticSharedLibName);
+                    return false;
+                }
+            }
+
+            if (PLATFORM_PACKAGE_NAME.equals(packageName)) {
+                Slog.w(TAG, "Cannot suspend the platform package: " + packageName);
+                return false;
+            }
+            return true;
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
     }
 
     private String getActiveLauncherPackageName(int userId) {
@@ -18417,6 +18474,7 @@
                     true /*stopped*/,
                     true /*notLaunched*/,
                     false /*hidden*/,
+                    0 /*distractionFlags*/,
                     false /*suspended*/,
                     null /*suspendingPackage*/,
                     null /*dialogInfo*/,
@@ -23232,6 +23290,14 @@
         }
 
         @Override
+        public int getDistractingPackageRestrictions(String packageName, int userId) {
+            synchronized (mPackages) {
+                final PackageSetting ps = mSettings.mPackages.get(packageName);
+                return (ps != null) ? ps.getDistractionFlags(userId) : RESTRICTION_NONE;
+            }
+        }
+
+        @Override
         public int getPackageUid(String packageName, int flags, int userId) {
             return PackageManagerService.this
                     .getPackageUid(packageName, flags, userId);
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 3c22f07..58f262c 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -392,6 +392,14 @@
         modifyUserState(userId).hidden = hidden;
     }
 
+    int getDistractionFlags(int userId) {
+        return readUserState(userId).distractionFlags;
+    }
+
+    void setDistractionFlags(int distractionFlags, int userId) {
+        modifyUserState(userId).distractionFlags = distractionFlags;
+    }
+
     boolean getSuspended(int userId) {
         return readUserState(userId).suspended;
     }
@@ -423,7 +431,8 @@
     }
 
     void setUserState(int userId, long ceDataInode, int enabled, boolean installed, boolean stopped,
-            boolean notLaunched, boolean hidden, boolean suspended, String suspendingPackage,
+            boolean notLaunched, boolean hidden, int distractionFlags, boolean suspended,
+            String suspendingPackage,
             SuspendDialogInfo dialogInfo, PersistableBundle suspendedAppExtras,
             PersistableBundle suspendedLauncherExtras, boolean instantApp,
             boolean virtualPreload, String lastDisableAppCaller,
@@ -437,6 +446,7 @@
         state.stopped = stopped;
         state.notLaunched = notLaunched;
         state.hidden = hidden;
+        state.distractionFlags = distractionFlags;
         state.suspended = suspended;
         state.suspendingPackage = suspendingPackage;
         state.dialogInfo = dialogInfo;
@@ -607,6 +617,7 @@
             }
             proto.write(PackageProto.UserInfoProto.INSTALL_TYPE, installType);
             proto.write(PackageProto.UserInfoProto.IS_HIDDEN, state.hidden);
+            proto.write(PackageProto.UserInfoProto.DISTRACTION_FLAGS, state.distractionFlags);
             proto.write(PackageProto.UserInfoProto.IS_SUSPENDED, state.suspended);
             if (state.suspended) {
                 proto.write(PackageProto.UserInfoProto.SUSPENDING_PACKAGE, state.suspendingPackage);
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index c524dba..95da209 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -223,6 +223,7 @@
     private static final String ATTR_BLOCKED = "blocked";
     // New name for the above attribute.
     private static final String ATTR_HIDDEN = "hidden";
+    private static final String ATTR_DISTRACTION_FLAGS = "distraction_flags";
     private static final String ATTR_SUSPENDED = "suspended";
     private static final String ATTR_SUSPENDING_PACKAGE = "suspending-package";
     /**
@@ -734,6 +735,7 @@
                                 true /*stopped*/,
                                 true /*notLaunched*/,
                                 false /*hidden*/,
+                                0 /*distractionFlags*/,
                                 false /*suspended*/,
                                 null /*suspendingPackage*/,
                                 null /*dialogInfo*/,
@@ -1628,6 +1630,7 @@
                                 false /*stopped*/,
                                 false /*notLaunched*/,
                                 false /*hidden*/,
+                                0 /*distractionFlags*/,
                                 false /*suspended*/,
                                 null /*suspendingPackage*/,
                                 null /*dialogInfo*/,
@@ -1703,6 +1706,8 @@
                     hidden = hiddenStr == null
                             ? hidden : Boolean.parseBoolean(hiddenStr);
 
+                    final int distractionFlags = XmlUtils.readIntAttribute(parser,
+                            ATTR_DISTRACTION_FLAGS, 0);
                     final boolean suspended = XmlUtils.readBooleanAttribute(parser, ATTR_SUSPENDED,
                             false);
                     String suspendingPackage = parser.getAttributeValue(null,
@@ -1781,7 +1786,8 @@
                         setBlockUninstallLPw(userId, name, true);
                     }
                     ps.setUserState(userId, ceDataInode, enabled, installed, stopped, notLaunched,
-                            hidden, suspended, suspendingPackage, suspendDialogInfo,
+                            hidden, distractionFlags, suspended, suspendingPackage,
+                            suspendDialogInfo,
                             suspendedAppExtras, suspendedLauncherExtras, instantApp, virtualPreload,
                             enabledCaller, enabledComponents, disabledComponents, verifState,
                             linkGeneration, installReason, harmfulAppWarning);
@@ -2089,6 +2095,10 @@
                 if (ustate.hidden) {
                     serializer.attribute(null, ATTR_HIDDEN, "true");
                 }
+                if (ustate.distractionFlags != 0) {
+                    serializer.attribute(null, ATTR_DISTRACTION_FLAGS,
+                            Integer.toString(ustate.distractionFlags));
+                }
                 if (ustate.suspended) {
                     serializer.attribute(null, ATTR_SUSPENDED, "true");
                     if (ustate.suspendingPackage != null) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index 517b5ad..6d28ed1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -35,6 +35,7 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageParser;
 import android.content.pm.PackageUserState;
 import android.content.pm.SuspendDialogInfo;
@@ -226,8 +227,8 @@
         settingsUnderTest.mPackages.put(PACKAGE_NAME_3, createPackageSetting(PACKAGE_NAME_3));
         // now read and verify
         settingsUnderTest.readPackageRestrictionsLPr(0);
-        final PackageUserState readPus1 = settingsUnderTest.mPackages.get(PACKAGE_NAME_1).
-                readUserState(0);
+        final PackageUserState readPus1 = settingsUnderTest.mPackages.get(PACKAGE_NAME_1)
+                .readUserState(0);
         assertThat(readPus1.suspended, is(true));
         assertThat(readPus1.suspendingPackage, equalTo("suspendingPackage1"));
         assertThat(readPus1.dialogInfo, equalTo(dialogInfo1));
@@ -235,16 +236,16 @@
         assertThat(BaseBundle.kindofEquals(readPus1.suspendedLauncherExtras, launcherExtras1),
                 is(true));
 
-        final PackageUserState readPus2 = settingsUnderTest.mPackages.get(PACKAGE_NAME_2).
-                readUserState(0);
+        final PackageUserState readPus2 = settingsUnderTest.mPackages.get(PACKAGE_NAME_2)
+                .readUserState(0);
         assertThat(readPus2.suspended, is(true));
         assertThat(readPus2.suspendingPackage, equalTo("suspendingPackage2"));
         assertThat(readPus2.dialogInfo, is(nullValue()));
         assertThat(readPus2.suspendedAppExtras, is(nullValue()));
         assertThat(readPus2.suspendedLauncherExtras, is(nullValue()));
 
-        final PackageUserState readPus3 = settingsUnderTest.mPackages.get(PACKAGE_NAME_3).
-                readUserState(0);
+        final PackageUserState readPus3 = settingsUnderTest.mPackages.get(PACKAGE_NAME_3)
+                .readUserState(0);
         assertThat(readPus3.suspended, is(false));
         assertThat(readPus3.suspendingPackage, is(nullValue()));
         assertThat(readPus3.dialogInfo, is(nullValue()));
@@ -254,11 +255,59 @@
 
     @Test
     public void testPackageRestrictionsSuspendedDefault() {
-        final PackageSetting defaultSetting =  createPackageSetting(PACKAGE_NAME_1);
+        final PackageSetting defaultSetting = createPackageSetting(PACKAGE_NAME_1);
         assertThat(defaultSetting.getSuspended(0), is(false));
     }
 
     @Test
+    public void testReadWritePackageRestrictions_distractionFlags() {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, new Object());
+        final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
+        final PackageSetting ps2 = createPackageSetting(PACKAGE_NAME_2);
+        final PackageSetting ps3 = createPackageSetting(PACKAGE_NAME_3);
+
+        final int distractionFlags1 = PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS;
+        ps1.setDistractionFlags(distractionFlags1, 0);
+        settingsUnderTest.mPackages.put(PACKAGE_NAME_1, ps1);
+
+        final int distractionFlags2 = PackageManager.RESTRICTION_HIDE_NOTIFICATIONS
+                | PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS;
+        ps2.setDistractionFlags(distractionFlags2, 0);
+        settingsUnderTest.mPackages.put(PACKAGE_NAME_2, ps2);
+
+        final int distractionFlags3 = PackageManager.RESTRICTION_NONE;
+        ps3.setDistractionFlags(distractionFlags3, 0);
+        settingsUnderTest.mPackages.put(PACKAGE_NAME_3, ps3);
+
+        settingsUnderTest.writePackageRestrictionsLPr(0);
+
+        settingsUnderTest.mPackages.clear();
+        settingsUnderTest.mPackages.put(PACKAGE_NAME_1, createPackageSetting(PACKAGE_NAME_1));
+        settingsUnderTest.mPackages.put(PACKAGE_NAME_2, createPackageSetting(PACKAGE_NAME_2));
+        settingsUnderTest.mPackages.put(PACKAGE_NAME_3, createPackageSetting(PACKAGE_NAME_3));
+        // now read and verify
+        settingsUnderTest.readPackageRestrictionsLPr(0);
+        final PackageUserState readPus1 = settingsUnderTest.mPackages.get(PACKAGE_NAME_1)
+                .readUserState(0);
+        assertThat(readPus1.distractionFlags, is(distractionFlags1));
+
+        final PackageUserState readPus2 = settingsUnderTest.mPackages.get(PACKAGE_NAME_2)
+                .readUserState(0);
+        assertThat(readPus2.distractionFlags, is(distractionFlags2));
+
+        final PackageUserState readPus3 = settingsUnderTest.mPackages.get(PACKAGE_NAME_3)
+                .readUserState(0);
+        assertThat(readPus3.distractionFlags, is(distractionFlags3));
+    }
+
+    @Test
+    public void testPackageRestrictionsDistractionFlagsDefault() {
+        final PackageSetting defaultSetting = createPackageSetting(PACKAGE_NAME_1);
+        assertThat(defaultSetting.getDistractionFlags(0), is(PackageManager.RESTRICTION_NONE));
+    }
+
+    @Test
     public void testEnableDisable() {
         // Write the package files and make sure they're parsed properly the first time
         writeOldFiles();
@@ -692,6 +741,7 @@
         assertThat(userState.notLaunched, is(notLaunched));
         assertThat(userState.stopped, is(stopped));
         assertThat(userState.suspended, is(false));
+        assertThat(userState.distractionFlags, is(0));
         if (oldUserState != null) {
             assertThat(userState.equals(oldUserState), is(not(userStateChanged)));
         }
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java
index f0ed612..8eaf35f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java
@@ -22,6 +22,7 @@
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertThat;
 
+import android.content.pm.PackageManager;
 import android.content.pm.PackageUserState;
 import android.content.pm.SuspendDialogInfo;
 import android.os.PersistableBundle;
@@ -227,4 +228,19 @@
         assertThat(testUserState1.equals(testUserState2), is(true));
     }
 
+    @Test
+    public void testPackageUserState06() {
+        final PackageUserState userState1 = new PackageUserState();
+        assertThat(userState1.distractionFlags, is(PackageManager.RESTRICTION_NONE));
+        userState1.distractionFlags = PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS;
+
+        final PackageUserState copyOfUserState1 = new PackageUserState(userState1);
+        assertThat(userState1.distractionFlags, is(copyOfUserState1.distractionFlags));
+        assertThat(userState1.equals(copyOfUserState1), is(true));
+
+        final PackageUserState userState2 = new PackageUserState(userState1);
+        userState2.distractionFlags = PackageManager.RESTRICTION_HIDE_NOTIFICATIONS;
+        assertThat(userState1.equals(userState2), is(false));
+    }
+
 }