Introduce DistractingPackageHelper

Move the implementaion of handling distracting packages out of the
PackageManagerService.

Fix: 225784320
Test: atest CtsSuspendAppsTestCases:DistractingPackageTest
Test: atest CtsSuspendAppsPermissionTestCases:NegativePermissionsTest
Test: atest CtsAppEnumerationTestCases:AppEnumerationTests
Test: atest FrameworksServicesTests:PackageManagerSettingsTests
Test: atest FrameworksServicesTests:PackageUserStateTest
Change-Id: I4e44c1f039318bf02d00ff6a6b8b973244108008
Merged-In: I4e44c1f039318bf02d00ff6a6b8b973244108008
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index f1394d4..bf1196d 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -320,17 +320,6 @@
                 broadcastAllowlist, null);
     }
 
-    public 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, null,
-                null);
-    }
-
     public void sendFirstLaunchBroadcast(String pkgName, String installerPkg,
             int[] userIds, int[] instantUserIds) {
         sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH, pkgName, null, 0,
diff --git a/services/core/java/com/android/server/pm/DistractingPackageHelper.java b/services/core/java/com/android/server/pm/DistractingPackageHelper.java
new file mode 100644
index 0000000..7dc45b5
--- /dev/null
+++ b/services/core/java/com/android/server/pm/DistractingPackageHelper.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static android.content.pm.PackageManager.RESTRICTION_NONE;
+
+import android.annotation.NonNull;
+import android.content.Intent;
+import android.content.pm.PackageManager.DistractionRestriction;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.IntArray;
+import android.util.Slog;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.server.pm.pkg.PackageStateInternal;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Mark, unmark, or remove any {@link DistractionRestriction restrictions} set on given packages.
+ */
+public final class DistractingPackageHelper {
+
+    // TODO(b/198166813): remove PMS dependency
+    private final PackageManagerService mPm;
+    private final PackageManagerServiceInjector mInjector;
+    private final BroadcastHelper mBroadcastHelper;
+    private final SuspendPackageHelper mSuspendPackageHelper;
+
+    /**
+     * Constructor for {@link PackageManagerService}.
+     */
+    DistractingPackageHelper(PackageManagerService pm, PackageManagerServiceInjector injector,
+            BroadcastHelper broadcastHelper, SuspendPackageHelper suspendPackageHelper) {
+        mPm = pm;
+        mInjector = injector;
+        mBroadcastHelper = broadcastHelper;
+        mSuspendPackageHelper = suspendPackageHelper;
+    }
+
+    /**
+     * Mark or unmark the given packages as distracting to the given user.
+     *
+     * @param packageNames Packages to mark as distracting.
+     * @param restrictionFlags Any combination of restrictions to impose on the given packages.
+     *                         {@link DistractionRestriction#RESTRICTION_NONE} can be used to
+     *                         clear any existing restrictions.
+     * @param userId the user for which changes are taking place.
+     * @param callingUid The caller's uid.
+     *
+     * @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.
+     */
+    String[] setDistractingPackageRestrictionsAsUser(@NonNull Computer snapshot,
+            String[] packageNames, int restrictionFlags, int userId, int callingUid) {
+        if (ArrayUtils.isEmpty(packageNames)) {
+            return packageNames;
+        }
+        if (restrictionFlags != RESTRICTION_NONE
+                && !mSuspendPackageHelper.isSuspendAllowedForUser(snapshot, userId, callingUid)) {
+            Slog.w(PackageManagerService.TAG,
+                    "Cannot restrict packages due to restrictions on user " + userId);
+            return packageNames;
+        }
+
+        final List<String> changedPackagesList = new ArrayList<>(packageNames.length);
+        final IntArray changedUids = new IntArray(packageNames.length);
+        final List<String> unactionedPackages = new ArrayList<>(packageNames.length);
+
+        final ArraySet<String> changesToCommit = new ArraySet<>();
+        final boolean[] canRestrict = (restrictionFlags != RESTRICTION_NONE)
+                ? mSuspendPackageHelper.canSuspendPackageForUser(snapshot, packageNames, userId,
+                callingUid) : null;
+        for (int i = 0; i < packageNames.length; i++) {
+            final String packageName = packageNames[i];
+            final PackageStateInternal packageState =
+                    snapshot.getPackageStateInternal(packageName);
+            if (packageState == null
+                    || snapshot.shouldFilterApplication(packageState, callingUid, userId)) {
+                Slog.w(PackageManagerService.TAG,
+                        "Could not find package setting for package: " + packageName
+                                + ". Skipping...");
+                unactionedPackages.add(packageName);
+                continue;
+            }
+            if (canRestrict != null && !canRestrict[i]) {
+                unactionedPackages.add(packageName);
+                continue;
+            }
+            final int oldDistractionFlags = packageState.getUserStateOrDefault(userId)
+                    .getDistractionFlags();
+            if (restrictionFlags != oldDistractionFlags) {
+                changedPackagesList.add(packageName);
+                changedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
+                changesToCommit.add(packageName);
+            }
+        }
+
+        mPm.commitPackageStateMutation(null /* initialState */, mutator -> {
+            final int size = changesToCommit.size();
+            for (int index = 0; index < size; index++) {
+                mutator.forPackage(changesToCommit.valueAt(index))
+                        .userState(userId)
+                        .setDistractionFlags(restrictionFlags);
+            }
+        });
+
+        if (!changedPackagesList.isEmpty()) {
+            final String[] changedPackages = changedPackagesList.toArray(
+                    new String[changedPackagesList.size()]);
+            sendDistractingPackagesChanged(changedPackages, changedUids.toArray(), userId,
+                    restrictionFlags);
+            mPm.scheduleWritePackageRestrictions(userId);
+        }
+        return unactionedPackages.toArray(new String[0]);
+    }
+
+    /**
+     * Removes any {@link DistractionRestriction restrictions} set on given packages.
+     *
+     * <p> Caller must flush package restrictions if it cares about immediate data consistency.
+     *
+     * @param packagesToChange The packages on which restrictions are to be removed.
+     * @param userId the user for which changes are taking place.
+     */
+    void removeDistractingPackageRestrictions(@NonNull Computer snapshot,
+            String[] packagesToChange, int userId) {
+        if (ArrayUtils.isEmpty(packagesToChange)) {
+            return;
+        }
+        final List<String> changedPackages = new ArrayList<>(packagesToChange.length);
+        final IntArray changedUids = new IntArray(packagesToChange.length);
+        for (int i = 0; i < packagesToChange.length; i++) {
+            final String packageName = packagesToChange[i];
+            final PackageStateInternal ps = snapshot.getPackageStateInternal(packageName);
+            if (ps != null && ps.getUserStateOrDefault(userId).getDistractionFlags()
+                    != RESTRICTION_NONE) {
+                changedPackages.add(ps.getPackageName());
+                changedUids.add(UserHandle.getUid(userId, ps.getAppId()));
+            }
+        }
+        mPm.commitPackageStateMutation(null /* initialState */, mutator -> {
+            for (int index = 0; index < changedPackages.size(); index++) {
+                mutator.forPackage(changedPackages.get(index))
+                        .userState(userId)
+                        .setDistractionFlags(RESTRICTION_NONE);
+            }
+        });
+
+        if (!changedPackages.isEmpty()) {
+            final String[] packageArray = changedPackages.toArray(
+                    new String[changedPackages.size()]);
+            sendDistractingPackagesChanged(packageArray, changedUids.toArray(), userId,
+                    RESTRICTION_NONE);
+            mPm.scheduleWritePackageRestrictions(userId);
+        }
+    }
+
+    /**
+     * Send broadcast intents for packages distracting changes.
+     *
+     * @param pkgList The names of packages which have suspension changes.
+     * @param uidList The uids of packages which have suspension changes.
+     * @param userId The user where packages reside.
+     */
+    void sendDistractingPackagesChanged(@NonNull 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);
+
+        final Handler handler = mInjector.getHandler();
+        handler.post(() -> mBroadcastHelper.sendPackageBroadcast(
+                Intent.ACTION_DISTRACTING_PACKAGES_CHANGED, null /* pkg */, extras,
+                Intent.FLAG_RECEIVER_REGISTERED_ONLY, null /* targetPkg */,
+                null /* finishedReceiver */, new int[]{userId}, null /* instantUserIds */,
+                null /* allowList */, null /* bOptions */));
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
index 2b73375..2fe7913 100644
--- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
+++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@@ -83,6 +83,7 @@
     @NonNull protected abstract PackageObserverHelper getPackageObserverHelper();
     @NonNull protected abstract ResolveIntentHelper getResolveIntentHelper();
     @NonNull protected abstract SuspendPackageHelper getSuspendPackageHelper();
+    @NonNull protected abstract DistractingPackageHelper getDistractingPackageHelper();
     @NonNull protected abstract ProtectedPackages getProtectedPackages();
     @NonNull protected abstract UserNeedsBadgingCache getUserNeedsBadging();
     @NonNull protected abstract InstantAppRegistry getInstantAppRegistry();
@@ -248,8 +249,8 @@
     @Override
     @Deprecated
     public final void removeDistractingPackageRestrictions(String packageName, int userId) {
-        mService.removeDistractingPackageRestrictions(snapshot(), new String[]{packageName},
-                userId);
+        getDistractingPackageHelper().removeDistractingPackageRestrictions(snapshot(),
+                new String[]{packageName}, userId);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6cf63d1..bf80a46 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -159,7 +159,6 @@
 import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.ExceptionUtils;
-import android.util.IntArray;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
@@ -939,6 +938,7 @@
     private final ResolveIntentHelper mResolveIntentHelper;
     private final DexOptHelper mDexOptHelper;
     private final SuspendPackageHelper mSuspendPackageHelper;
+    private final DistractingPackageHelper mDistractingPackageHelper;
     private final IntentResolverInterceptor mIntentResolverInterceptor;
     private final StorageEventHelper mStorageEventHelper;
 
@@ -1683,6 +1683,7 @@
         mResolveIntentHelper = testParams.resolveIntentHelper;
         mDexOptHelper = testParams.dexOptHelper;
         mSuspendPackageHelper = testParams.suspendPackageHelper;
+        mDistractingPackageHelper = testParams.distractingPackageHelper;
 
         mSharedLibraries.setDeletePackageHelper(mDeletePackageHelper);
 
@@ -1842,6 +1843,8 @@
                 mProtectedPackages);
         mStorageEventHelper = new StorageEventHelper(this, mDeletePackageHelper,
                 mRemovePackageHelper);
+        mDistractingPackageHelper = new DistractingPackageHelper(this, mInjector, mBroadcastHelper,
+                mSuspendPackageHelper);
 
         synchronized (mLock) {
             // Create the computer as soon as the state objects have been installed.  The
@@ -3065,43 +3068,19 @@
 
     void removeAllDistractingPackageRestrictions(@NonNull Computer snapshot, int userId) {
         final String[] allPackages = snapshot.getAllAvailablePackageNames();
-        removeDistractingPackageRestrictions(snapshot, allPackages, userId);
+        mDistractingPackageHelper.removeDistractingPackageRestrictions(snapshot, allPackages,
+                userId);
     }
 
-    /**
-     * Removes any {@link android.content.pm.PackageManager.DistractionRestriction restrictions}
-     * set on given packages.
-     *
-     * <p> Caller must flush package restrictions if it cares about immediate data consistency.
-     *
-     * @param packagesToChange The packages on which restrictions are to be removed.
-     * @param userId the user for which changes are taking place.
-     */
-    void removeDistractingPackageRestrictions(@NonNull Computer snapshot,
-            String[] packagesToChange, int userId) {
-        final List<String> changedPackages = new ArrayList<>();
-        final IntArray changedUids = new IntArray();
-        for (String packageName : packagesToChange) {
-            final PackageStateInternal ps = snapshot.getPackageStateInternal(packageName);
-            if (ps != null && ps.getUserStateOrDefault(userId).getDistractionFlags() != 0) {
-                changedPackages.add(ps.getPackageName());
-                changedUids.add(UserHandle.getUid(userId, ps.getAppId()));
-            }
-        }
-        commitPackageStateMutation(null, mutator -> {
-            for (int index = 0; index < changedPackages.size(); index++) {
-                mutator.forPackage(changedPackages.get(index))
-                        .userState(userId)
-                        .setDistractionFlags(0);
-            }
-        });
+    private void enforceCanSetDistractingPackageRestrictionsAsUser(@NonNull Computer snapshot,
+            int callingUid, int userId, String callingMethod) {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS,
+                callingMethod);
 
-        if (!changedPackages.isEmpty()) {
-            final String[] packageArray = changedPackages.toArray(
-                    new String[changedPackages.size()]);
-            mHandler.post(() -> mBroadcastHelper.sendDistractingPackagesChanged(
-                    packageArray, changedUids.toArray(), userId, 0));
-            scheduleWritePackageRestrictions(userId);
+        if (callingUid != Process.ROOT_UID && callingUid != Process.SYSTEM_UID
+                && UserHandle.getUserId(callingUid) != userId) {
+            throw new SecurityException("Calling uid " + callingUid + " cannot call for user "
+                    + userId);
         }
     }
 
@@ -5591,73 +5570,13 @@
         @Override
         public String[] setDistractingPackageRestrictionsAsUser(String[] packageNames,
                 int restrictionFlags, int userId) {
-            mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS,
-                    "setDistractingPackageRestrictionsAsUser");
-
             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);
-            }
-            Objects.requireNonNull(packageNames, "packageNames cannot be null");
             final Computer snapshot = snapshotComputer();
-            if (restrictionFlags != 0
-                    && !mSuspendPackageHelper.isSuspendAllowedForUser(snapshot, userId,
-                    callingUid)) {
-                Slog.w(PackageManagerService.TAG, "Cannot restrict packages due to restrictions on user " + userId);
-                return packageNames;
-            }
-
-            final List<String> changedPackagesList = new ArrayList<>(packageNames.length);
-            final IntArray changedUids = new IntArray(packageNames.length);
-            final List<String> unactionedPackages = new ArrayList<>(packageNames.length);
-
-            ArraySet<String> changesToCommit = new ArraySet<>();
-            final boolean[] canRestrict = (restrictionFlags != 0)
-                    ? mSuspendPackageHelper.canSuspendPackageForUser(snapshot, packageNames, userId,
-                    callingUid) : null;
-            for (int i = 0; i < packageNames.length; i++) {
-                final String packageName = packageNames[i];
-                final PackageStateInternal packageState =
-                        snapshot.getPackageStateInternal(packageName);
-                if (packageState == null
-                        || snapshot.shouldFilterApplication(packageState, callingUid, userId)) {
-                    Slog.w(PackageManagerService.TAG, "Could not find package setting for package: " + packageName
-                            + ". Skipping...");
-                    unactionedPackages.add(packageName);
-                    continue;
-                }
-                if (canRestrict != null && !canRestrict[i]) {
-                    unactionedPackages.add(packageName);
-                    continue;
-                }
-                final int oldDistractionFlags = packageState.getUserStateOrDefault(userId)
-                        .getDistractionFlags();
-                if (restrictionFlags != oldDistractionFlags) {
-                    changedPackagesList.add(packageName);
-                    changedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
-                    changesToCommit.add(packageName);
-                }
-            }
-
-            commitPackageStateMutation(null, mutator -> {
-                final int size = changesToCommit.size();
-                for (int index = 0; index < size; index++) {
-                    mutator.forPackage(changesToCommit.valueAt(index))
-                            .userState(userId)
-                            .setDistractionFlags(restrictionFlags);
-                }
-            });
-
-            if (!changedPackagesList.isEmpty()) {
-                final String[] changedPackages = changedPackagesList.toArray(
-                        new String[changedPackagesList.size()]);
-                mHandler.post(() -> mBroadcastHelper.sendDistractingPackagesChanged(
-                        changedPackages, changedUids.toArray(), userId, restrictionFlags));
-                scheduleWritePackageRestrictions(userId);
-            }
-            return unactionedPackages.toArray(new String[0]);
+            enforceCanSetDistractingPackageRestrictionsAsUser(snapshot, callingUid, userId,
+                    "setDistractingPackageRestrictionsAsUser");
+            Objects.requireNonNull(packageNames, "packageNames cannot be null");
+            return mDistractingPackageHelper.setDistractingPackageRestrictionsAsUser(snapshot,
+                    packageNames, restrictionFlags, userId, callingUid);
         }
 
         @Override
@@ -6121,6 +6040,12 @@
 
         @NonNull
         @Override
+        protected DistractingPackageHelper getDistractingPackageHelper() {
+            return mDistractingPackageHelper;
+        }
+
+        @NonNull
+        @Override
         protected ProtectedPackages getProtectedPackages() {
             return mProtectedPackages;
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index 144231c..16829e0e8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -116,4 +116,5 @@
     public DexOptHelper dexOptHelper;
     public SuspendPackageHelper suspendPackageHelper;
     public StorageEventHelper storageEventHelper;
+    public DistractingPackageHelper distractingPackageHelper;
 }