• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.pm;
18 
19 import static android.os.Process.SYSTEM_UID;
20 
21 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
22 import static com.android.server.pm.PackageManagerService.TAG;
23 
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.annotation.UserIdInt;
27 import android.app.ActivityManager;
28 import android.app.IActivityManager;
29 import android.content.Intent;
30 import android.content.pm.SuspendDialogInfo;
31 import android.os.Binder;
32 import android.os.Bundle;
33 import android.os.Handler;
34 import android.os.PersistableBundle;
35 import android.os.UserHandle;
36 import android.os.UserManager;
37 import android.util.ArrayMap;
38 import android.util.ArraySet;
39 import android.util.IntArray;
40 import android.util.Slog;
41 import android.util.SparseArray;
42 
43 import com.android.internal.annotations.VisibleForTesting;
44 import com.android.internal.util.ArrayUtils;
45 import com.android.internal.util.CollectionUtils;
46 import com.android.server.pm.parsing.pkg.AndroidPackage;
47 import com.android.server.pm.pkg.PackageStateInternal;
48 import com.android.server.pm.pkg.PackageUserStateInternal;
49 import com.android.server.pm.pkg.SuspendParams;
50 import com.android.server.pm.pkg.mutate.PackageUserStateWrite;
51 import com.android.server.utils.WatchedArrayMap;
52 
53 import java.util.ArrayList;
54 import java.util.Arrays;
55 import java.util.List;
56 import java.util.Objects;
57 import java.util.function.Predicate;
58 
59 public final class SuspendPackageHelper {
60     // TODO(b/198166813): remove PMS dependency
61     private final PackageManagerService mPm;
62     private final PackageManagerServiceInjector mInjector;
63 
64     private final BroadcastHelper mBroadcastHelper;
65     private final ProtectedPackages mProtectedPackages;
66 
67     /**
68      * Constructor for {@link PackageManagerService}.
69      */
SuspendPackageHelper(PackageManagerService pm, PackageManagerServiceInjector injector, BroadcastHelper broadcastHelper, ProtectedPackages protectedPackages)70     SuspendPackageHelper(PackageManagerService pm, PackageManagerServiceInjector injector,
71             BroadcastHelper broadcastHelper, ProtectedPackages protectedPackages) {
72         mPm = pm;
73         mInjector = injector;
74         mBroadcastHelper = broadcastHelper;
75         mProtectedPackages = protectedPackages;
76     }
77 
78     /**
79      * Updates the package to the suspended or unsuspended state.
80      *
81      * @param packageNames The names of the packages to set the suspended status.
82      * @param suspended {@code true} to suspend packages, or {@code false} to unsuspend packages.
83      * @param appExtras An optional {@link PersistableBundle} that the suspending app can provide
84      *                  which will be shared with the apps being suspended. Ignored if
85      *                  {@code suspended} is false.
86      * @param launcherExtras An optional {@link PersistableBundle} that the suspending app can
87      *                       provide which will be shared with the launcher. Ignored if
88      *                       {@code suspended} is false.
89      * @param dialogInfo An optional {@link SuspendDialogInfo} object describing the dialog that
90      *                   should be shown to the user when they try to launch a suspended app.
91      *                   Ignored if {@code suspended} is false.
92      * @param callingPackage The caller's package name.
93      * @param userId The user where packages reside.
94      * @param callingUid The caller's uid.
95      * @return The names of failed packages.
96      */
97     @Nullable
setPackagesSuspended(@onNull Computer snapshot, @Nullable String[] packageNames, boolean suspended, @Nullable PersistableBundle appExtras, @Nullable PersistableBundle launcherExtras, @Nullable SuspendDialogInfo dialogInfo, @NonNull String callingPackage, @UserIdInt int userId, int callingUid)98     String[] setPackagesSuspended(@NonNull Computer snapshot, @Nullable String[] packageNames,
99             boolean suspended, @Nullable PersistableBundle appExtras,
100             @Nullable PersistableBundle launcherExtras, @Nullable SuspendDialogInfo dialogInfo,
101             @NonNull String callingPackage, @UserIdInt int userId, int callingUid) {
102         if (ArrayUtils.isEmpty(packageNames)) {
103             return packageNames;
104         }
105         if (suspended && !isSuspendAllowedForUser(snapshot, userId, callingUid)) {
106             Slog.w(TAG, "Cannot suspend due to restrictions on user " + userId);
107             return packageNames;
108         }
109 
110         final SuspendParams newSuspendParams =
111                 new SuspendParams(dialogInfo, appExtras, launcherExtras);
112 
113         final List<String> unmodifiablePackages = new ArrayList<>(packageNames.length);
114 
115         final List<String> notifyPackagesList = new ArrayList<>(packageNames.length);
116         final IntArray notifyUids = new IntArray(packageNames.length);
117         final ArraySet<String> changedPackagesList = new ArraySet<>(packageNames.length);
118         final IntArray changedUids = new IntArray(packageNames.length);
119 
120         final boolean[] canSuspend = suspended
121                 ? canSuspendPackageForUser(snapshot, packageNames, userId, callingUid) : null;
122         for (int i = 0; i < packageNames.length; i++) {
123             final String packageName = packageNames[i];
124             if (callingPackage.equals(packageName)) {
125                 Slog.w(TAG, "Calling package: " + callingPackage + " trying to "
126                         + (suspended ? "" : "un") + "suspend itself. Ignoring");
127                 unmodifiablePackages.add(packageName);
128                 continue;
129             }
130             final PackageStateInternal packageState =
131                     snapshot.getPackageStateInternal(packageName);
132             if (packageState == null
133                     || snapshot.shouldFilterApplication(packageState, callingUid, userId)) {
134                 Slog.w(TAG, "Could not find package setting for package: " + packageName
135                         + ". Skipping suspending/un-suspending.");
136                 unmodifiablePackages.add(packageName);
137                 continue;
138             }
139             if (canSuspend != null && !canSuspend[i]) {
140                 unmodifiablePackages.add(packageName);
141                 continue;
142             }
143 
144             final WatchedArrayMap<String, SuspendParams> suspendParamsMap =
145                     packageState.getUserStateOrDefault(userId).getSuspendParams();
146 
147             SuspendParams oldSuspendParams = suspendParamsMap == null
148                     ? null : suspendParamsMap.get(packageName);
149             boolean changed = !Objects.equals(oldSuspendParams, newSuspendParams);
150 
151             if (suspended && !changed) {
152                 // Carried over API behavior, must notify change even if no change
153                 notifyPackagesList.add(packageName);
154                 notifyUids.add(UserHandle.getUid(userId, packageState.getAppId()));
155                 continue;
156             }
157 
158             // If only the callingPackage is suspending this package,
159             // it will be unsuspended when this change is committed
160             boolean packageUnsuspended = !suspended
161                     && CollectionUtils.size(suspendParamsMap) == 1
162                     && suspendParamsMap.containsKey(callingPackage);
163             if (suspended || packageUnsuspended) {
164                 // Always notify of a suspend call + notify when fully unsuspended
165                 notifyPackagesList.add(packageName);
166                 notifyUids.add(UserHandle.getUid(userId, packageState.getAppId()));
167             }
168 
169             if (changed) {
170                 changedPackagesList.add(packageName);
171                 changedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
172             }
173         }
174 
175         mPm.commitPackageStateMutation(null, mutator -> {
176             final int size = changedPackagesList.size();
177             for (int index = 0; index < size; index++) {
178                 final String packageName  = changedPackagesList.valueAt(index);
179                 final PackageUserStateWrite userState = mutator.forPackage(packageName)
180                         .userState(userId);
181                 if (suspended) {
182                     userState.putSuspendParams(callingPackage, newSuspendParams);
183                 } else {
184                     userState.removeSuspension(callingPackage);
185                 }
186             }
187         });
188 
189         final Computer newSnapshot = mPm.snapshotComputer();
190 
191         if (!notifyPackagesList.isEmpty()) {
192             final String[] notifyPackages = notifyPackagesList.toArray(new String[0]);
193             sendPackagesSuspendedForUser(newSnapshot,
194                     suspended ? Intent.ACTION_PACKAGES_SUSPENDED
195                             : Intent.ACTION_PACKAGES_UNSUSPENDED,
196                     notifyPackages, notifyUids.toArray(), userId);
197             sendMyPackageSuspendedOrUnsuspended(notifyPackages, suspended, userId);
198             mPm.scheduleWritePackageRestrictions(userId);
199         }
200         // Send the suspension changed broadcast to ensure suspension state is not stale.
201         if (!changedPackagesList.isEmpty()) {
202             sendPackagesSuspendedForUser(newSnapshot, Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
203                     changedPackagesList.toArray(new String[0]), changedUids.toArray(), userId);
204         }
205         return unmodifiablePackages.toArray(new String[0]);
206     }
207 
208     /**
209      * Returns the names in the {@code packageNames} which can not be suspended by the caller.
210      *
211      * @param packageNames The names of packages to check.
212      * @param userId The user where packages reside.
213      * @param callingUid The caller's uid.
214      * @return The names of packages which are Unsuspendable.
215      */
216     @NonNull
getUnsuspendablePackagesForUser(@onNull Computer snapshot, @NonNull String[] packageNames, @UserIdInt int userId, int callingUid)217     String[] getUnsuspendablePackagesForUser(@NonNull Computer snapshot,
218             @NonNull String[] packageNames, @UserIdInt int userId, int callingUid) {
219         if (!isSuspendAllowedForUser(snapshot, userId, callingUid)) {
220             Slog.w(TAG, "Cannot suspend due to restrictions on user " + userId);
221             return packageNames;
222         }
223         final ArraySet<String> unactionablePackages = new ArraySet<>();
224         final boolean[] canSuspend = canSuspendPackageForUser(snapshot, packageNames, userId,
225                 callingUid);
226         for (int i = 0; i < packageNames.length; i++) {
227             if (!canSuspend[i]) {
228                 unactionablePackages.add(packageNames[i]);
229                 continue;
230             }
231             final PackageStateInternal packageState =
232                     snapshot.getPackageStateFiltered(packageNames[i], callingUid, userId);
233             if (packageState == null) {
234                 Slog.w(TAG, "Could not find package setting for package: " + packageNames[i]);
235                 unactionablePackages.add(packageNames[i]);
236             }
237         }
238         return unactionablePackages.toArray(new String[unactionablePackages.size()]);
239     }
240 
241     /**
242      * Returns the app extras of the given suspended package.
243      *
244      * @param packageName The suspended package name.
245      * @param userId The user where the package resides.
246      * @param callingUid The caller's uid.
247      * @return The app extras of the suspended package.
248      */
249     @Nullable
getSuspendedPackageAppExtras(@onNull Computer snapshot, @NonNull String packageName, int userId, int callingUid)250     Bundle getSuspendedPackageAppExtras(@NonNull Computer snapshot, @NonNull String packageName,
251             int userId, int callingUid) {
252         final PackageStateInternal ps = snapshot.getPackageStateInternal(packageName, callingUid);
253         if (ps == null) {
254             return null;
255         }
256         final PackageUserStateInternal pus = ps.getUserStateOrDefault(userId);
257         final Bundle allExtras = new Bundle();
258         if (pus.isSuspended()) {
259             for (int i = 0; i < pus.getSuspendParams().size(); i++) {
260                 final SuspendParams params = pus.getSuspendParams().valueAt(i);
261                 if (params != null && params.getAppExtras() != null) {
262                     allExtras.putAll(params.getAppExtras());
263                 }
264             }
265         }
266         return (allExtras.size() > 0) ? allExtras : null;
267     }
268 
269     /**
270      * Removes any suspensions on given packages that were added by packages that pass the given
271      * predicate.
272      *
273      * <p> Caller must flush package restrictions if it cares about immediate data consistency.
274      *
275      * @param packagesToChange The packages on which the suspension are to be removed.
276      * @param suspendingPackagePredicate A predicate identifying the suspending packages whose
277      *                                   suspensions will be removed.
278      * @param userId The user for which the changes are taking place.
279      */
removeSuspensionsBySuspendingPackage(@onNull Computer computer, @NonNull String[] packagesToChange, @NonNull Predicate<String> suspendingPackagePredicate, int userId)280     void removeSuspensionsBySuspendingPackage(@NonNull Computer computer,
281             @NonNull String[] packagesToChange,
282             @NonNull Predicate<String> suspendingPackagePredicate, int userId) {
283         final List<String> unsuspendedPackages = new ArrayList<>();
284         final IntArray unsuspendedUids = new IntArray();
285         final ArrayMap<String, ArraySet<String>> pkgToSuspendingPkgsToCommit = new ArrayMap<>();
286         for (String packageName : packagesToChange) {
287             final PackageStateInternal packageState =
288                     computer.getPackageStateInternal(packageName);
289             final PackageUserStateInternal packageUserState = packageState == null
290                     ? null : packageState.getUserStateOrDefault(userId);
291             if (packageUserState == null || !packageUserState.isSuspended()) {
292                 continue;
293             }
294 
295             WatchedArrayMap<String, SuspendParams> suspendParamsMap =
296                     packageUserState.getSuspendParams();
297             int countRemoved = 0;
298             for (int index = 0; index < suspendParamsMap.size(); index++) {
299                 String suspendingPackage = suspendParamsMap.keyAt(index);
300                 if (suspendingPackagePredicate.test(suspendingPackage)) {
301                     ArraySet<String> suspendingPkgsToCommit =
302                             pkgToSuspendingPkgsToCommit.get(packageName);
303                     if (suspendingPkgsToCommit == null) {
304                         suspendingPkgsToCommit = new ArraySet<>();
305                         pkgToSuspendingPkgsToCommit.put(packageName, suspendingPkgsToCommit);
306                     }
307                     suspendingPkgsToCommit.add(suspendingPackage);
308                     countRemoved++;
309                 }
310             }
311 
312             // Everything would be removed and package unsuspended
313             if (countRemoved == suspendParamsMap.size()) {
314                 unsuspendedPackages.add(packageState.getPackageName());
315                 unsuspendedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
316             }
317         }
318 
319         mPm.commitPackageStateMutation(null, mutator -> {
320             for (int mapIndex = 0; mapIndex < pkgToSuspendingPkgsToCommit.size(); mapIndex++) {
321                 String packageName = pkgToSuspendingPkgsToCommit.keyAt(mapIndex);
322                 ArraySet<String> packagesToRemove = pkgToSuspendingPkgsToCommit.valueAt(mapIndex);
323                 PackageUserStateWrite userState = mutator.forPackage(packageName).userState(userId);
324                 for (int setIndex = 0; setIndex < packagesToRemove.size(); setIndex++) {
325                     userState.removeSuspension(packagesToRemove.valueAt(setIndex));
326                 }
327             }
328         });
329 
330         final Computer newSnapshot = mPm.snapshotComputer();
331 
332         mPm.scheduleWritePackageRestrictions(userId);
333         if (!unsuspendedPackages.isEmpty()) {
334             final String[] packageArray = unsuspendedPackages.toArray(
335                     new String[unsuspendedPackages.size()]);
336             sendMyPackageSuspendedOrUnsuspended(packageArray, false, userId);
337             sendPackagesSuspendedForUser(newSnapshot, Intent.ACTION_PACKAGES_UNSUSPENDED,
338                     packageArray, unsuspendedUids.toArray(), userId);
339         }
340     }
341 
342     /**
343      * Returns the launcher extras for the given suspended package.
344      *
345      * @param packageName The name of the suspended package.
346      * @param userId The user where the package resides.
347      * @param callingUid The caller's uid.
348      * @return The launcher extras.
349      */
350     @Nullable
getSuspendedPackageLauncherExtras(@onNull Computer snapshot, @NonNull String packageName, int userId, int callingUid)351     Bundle getSuspendedPackageLauncherExtras(@NonNull Computer snapshot,
352             @NonNull String packageName, int userId, int callingUid) {
353         final PackageStateInternal packageState =
354                 snapshot.getPackageStateInternal(packageName, callingUid);
355         if (packageState == null) {
356             return null;
357         }
358         Bundle allExtras = new Bundle();
359         PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
360         if (userState.isSuspended()) {
361             for (int i = 0; i < userState.getSuspendParams().size(); i++) {
362                 final SuspendParams params = userState.getSuspendParams().valueAt(i);
363                 if (params != null && params.getLauncherExtras() != null) {
364                     allExtras.putAll(params.getLauncherExtras());
365                 }
366             }
367         }
368         return (allExtras.size() > 0) ? allExtras : null;
369     }
370 
371     /**
372      * Return {@code true}, if the given package is suspended.
373      *
374      * @param packageName The name of package to check.
375      * @param userId The user where the package resides.
376      * @param callingUid The caller's uid.
377      * @return {@code true}, if the given package is suspended.
378      */
isPackageSuspended(@onNull Computer snapshot, @NonNull String packageName, int userId, int callingUid)379     boolean isPackageSuspended(@NonNull Computer snapshot, @NonNull String packageName, int userId,
380             int callingUid) {
381         final PackageStateInternal packageState =
382                 snapshot.getPackageStateInternal(packageName, callingUid);
383         return packageState != null && packageState.getUserStateOrDefault(userId)
384                 .isSuspended();
385     }
386 
387     /**
388      * Given a suspended package, returns the name of package which invokes suspending to it.
389      *
390      * @param suspendedPackage The suspended package to check.
391      * @param userId The user where the package resides.
392      * @param callingUid The caller's uid.
393      * @return The name of suspending package.
394      */
395     @Nullable
getSuspendingPackage(@onNull Computer snapshot, @NonNull String suspendedPackage, int userId, int callingUid)396     String getSuspendingPackage(@NonNull Computer snapshot, @NonNull String suspendedPackage,
397             int userId, int callingUid) {
398         final PackageStateInternal packageState = snapshot.getPackageStateInternal(
399                 suspendedPackage, callingUid);
400         if (packageState == null) {
401             return  null;
402         }
403 
404         final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
405         if (!userState.isSuspended()) {
406             return null;
407         }
408 
409         String suspendingPackage = null;
410         for (int i = 0; i < userState.getSuspendParams().size(); i++) {
411             suspendingPackage = userState.getSuspendParams().keyAt(i);
412             if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) {
413                 return suspendingPackage;
414             }
415         }
416         return suspendingPackage;
417     }
418 
419     /**
420      *  Returns the dialog info of the given suspended package.
421      *
422      * @param suspendedPackage The name of the suspended package.
423      * @param suspendingPackage The name of the suspending package.
424      * @param userId The user where the package resides.
425      * @param callingUid The caller's uid.
426      * @return The dialog info.
427      */
428     @Nullable
getSuspendedDialogInfo(@onNull Computer snapshot, @NonNull String suspendedPackage, @NonNull String suspendingPackage, int userId, int callingUid)429     SuspendDialogInfo getSuspendedDialogInfo(@NonNull Computer snapshot,
430             @NonNull String suspendedPackage, @NonNull String suspendingPackage, int userId,
431             int callingUid) {
432         final PackageStateInternal packageState = snapshot.getPackageStateInternal(
433                 suspendedPackage, callingUid);
434         if (packageState == null) {
435             return  null;
436         }
437 
438         final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
439         if (!userState.isSuspended()) {
440             return null;
441         }
442 
443         final WatchedArrayMap<String, SuspendParams> suspendParamsMap =
444                 userState.getSuspendParams();
445         if (suspendParamsMap == null) {
446             return null;
447         }
448 
449         final SuspendParams suspendParams = suspendParamsMap.get(suspendingPackage);
450         return (suspendParams != null) ? suspendParams.getDialogInfo() : null;
451     }
452 
453     /**
454      * Return {@code true} if the user is allowed to suspend packages by the caller.
455      *
456      * @param userId The user id to check.
457      * @param callingUid The caller's uid.
458      * @return {@code true} if the user is allowed to suspend packages by the caller.
459      */
isSuspendAllowedForUser(@onNull Computer snapshot, int userId, int callingUid)460     boolean isSuspendAllowedForUser(@NonNull Computer snapshot, int userId, int callingUid) {
461         final UserManagerService userManager = mInjector.getUserManagerService();
462         return isCallerDeviceOrProfileOwner(snapshot, userId, callingUid)
463                 || (!userManager.hasUserRestriction(UserManager.DISALLOW_APPS_CONTROL, userId)
464                 && !userManager.hasUserRestriction(UserManager.DISALLOW_UNINSTALL_APPS, userId));
465     }
466 
467     /**
468      * Returns an array of booleans, such that the ith boolean denotes whether the ith package can
469      * be suspended or not.
470      *
471      * @param packageNames  The package names to check suspendability for.
472      * @param userId The user to check in
473      * @param callingUid The caller's uid.
474      * @return An array containing results of the checks
475      */
476     @NonNull
canSuspendPackageForUser(@onNull Computer snapshot, @NonNull String[] packageNames, int userId, int callingUid)477     boolean[] canSuspendPackageForUser(@NonNull Computer snapshot, @NonNull String[] packageNames,
478             int userId, int callingUid) {
479         final boolean[] canSuspend = new boolean[packageNames.length];
480         final boolean isCallerOwner = isCallerDeviceOrProfileOwner(snapshot, userId, callingUid);
481         final long token = Binder.clearCallingIdentity();
482         try {
483             final DefaultAppProvider defaultAppProvider = mInjector.getDefaultAppProvider();
484             final String activeLauncherPackageName = defaultAppProvider.getDefaultHome(userId);
485             final String dialerPackageName = defaultAppProvider.getDefaultDialer(userId);
486             final String requiredInstallerPackage =
487                     getKnownPackageName(snapshot, KnownPackages.PACKAGE_INSTALLER, userId);
488             final String requiredUninstallerPackage =
489                     getKnownPackageName(snapshot, KnownPackages.PACKAGE_UNINSTALLER, userId);
490             final String requiredVerifierPackage =
491                     getKnownPackageName(snapshot, KnownPackages.PACKAGE_VERIFIER, userId);
492             final String requiredPermissionControllerPackage =
493                     getKnownPackageName(snapshot, KnownPackages.PACKAGE_PERMISSION_CONTROLLER,
494                             userId);
495             for (int i = 0; i < packageNames.length; i++) {
496                 canSuspend[i] = false;
497                 final String packageName = packageNames[i];
498 
499                 if (mPm.isPackageDeviceAdmin(packageName, userId)) {
500                     Slog.w(TAG, "Cannot suspend package \"" + packageName
501                             + "\": has an active device admin");
502                     continue;
503                 }
504                 if (packageName.equals(activeLauncherPackageName)) {
505                     Slog.w(TAG, "Cannot suspend package \"" + packageName
506                             + "\": contains the active launcher");
507                     continue;
508                 }
509                 if (packageName.equals(requiredInstallerPackage)) {
510                     Slog.w(TAG, "Cannot suspend package \"" + packageName
511                             + "\": required for package installation");
512                     continue;
513                 }
514                 if (packageName.equals(requiredUninstallerPackage)) {
515                     Slog.w(TAG, "Cannot suspend package \"" + packageName
516                             + "\": required for package uninstallation");
517                     continue;
518                 }
519                 if (packageName.equals(requiredVerifierPackage)) {
520                     Slog.w(TAG, "Cannot suspend package \"" + packageName
521                             + "\": required for package verification");
522                     continue;
523                 }
524                 if (packageName.equals(dialerPackageName)) {
525                     Slog.w(TAG, "Cannot suspend package \"" + packageName
526                             + "\": is the default dialer");
527                     continue;
528                 }
529                 if (packageName.equals(requiredPermissionControllerPackage)) {
530                     Slog.w(TAG, "Cannot suspend package \"" + packageName
531                             + "\": required for permissions management");
532                     continue;
533                 }
534                 if (mProtectedPackages.isPackageStateProtected(userId, packageName)) {
535                     Slog.w(TAG, "Cannot suspend package \"" + packageName
536                             + "\": protected package");
537                     continue;
538                 }
539                 if (!isCallerOwner && snapshot.getBlockUninstall(userId, packageName)) {
540                     Slog.w(TAG, "Cannot suspend package \"" + packageName
541                             + "\": blocked by admin");
542                     continue;
543                 }
544 
545                 // Cannot suspend static shared libs as they are considered
546                 // a part of the using app (emulating static linking). Also
547                 // static libs are installed always on internal storage.
548                 PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
549                 AndroidPackage pkg = packageState == null ? null : packageState.getPkg();
550                 if (pkg != null) {
551                     // Cannot suspend SDK libs as they are controlled by SDK manager.
552                     if (pkg.isSdkLibrary()) {
553                         Slog.w(TAG, "Cannot suspend package: " + packageName
554                                 + " providing SDK library: "
555                                 + pkg.getSdkLibName());
556                         continue;
557                     }
558                     // Cannot suspend static shared libs as they are considered
559                     // a part of the using app (emulating static linking). Also
560                     // static libs are installed always on internal storage.
561                     if (pkg.isStaticSharedLibrary()) {
562                         Slog.w(TAG, "Cannot suspend package: " + packageName
563                                 + " providing static shared library: "
564                                 + pkg.getStaticSharedLibName());
565                         continue;
566                     }
567                 }
568                 if (PLATFORM_PACKAGE_NAME.equals(packageName)) {
569                     Slog.w(TAG, "Cannot suspend the platform package: " + packageName);
570                     continue;
571                 }
572                 canSuspend[i] = true;
573             }
574         } finally {
575             Binder.restoreCallingIdentity(token);
576         }
577         return canSuspend;
578     }
579 
580     /**
581      * Send broadcast intents for packages suspension changes.
582      *
583      * @param intent The action name of the suspension intent.
584      * @param pkgList The names of packages which have suspension changes.
585      * @param uidList The uids of packages which have suspension changes.
586      * @param userId The user where packages reside.
587      */
588     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
sendPackagesSuspendedForUser(@onNull Computer snapshot, @NonNull String intent, @NonNull String[] pkgList, @NonNull int[] uidList, int userId)589     void sendPackagesSuspendedForUser(@NonNull Computer snapshot, @NonNull String intent,
590             @NonNull String[] pkgList, @NonNull int[] uidList, int userId) {
591         final List<List<String>> pkgsToSend = new ArrayList(pkgList.length);
592         final List<IntArray> uidsToSend = new ArrayList(pkgList.length);
593         final List<SparseArray<int[]>> allowListsToSend = new ArrayList(pkgList.length);
594         final int[] userIds = new int[] {userId};
595         // Get allow lists for the pkg in the pkgList. Merge into the existed pkgs and uids if
596         // allow lists are the same.
597         for (int i = 0; i < pkgList.length; i++) {
598             final String pkgName = pkgList[i];
599             final int uid = uidList[i];
600             SparseArray<int[]> allowList = mInjector.getAppsFilter().getVisibilityAllowList(
601                     snapshot, snapshot.getPackageStateInternal(pkgName, SYSTEM_UID),
602                     userIds, snapshot.getPackageStates());
603             if (allowList == null) {
604                 allowList = new SparseArray<>(0);
605             }
606             boolean merged = false;
607             for (int j = 0; j < allowListsToSend.size(); j++) {
608                 if (Arrays.equals(allowListsToSend.get(j).get(userId), allowList.get(userId))) {
609                     pkgsToSend.get(j).add(pkgName);
610                     uidsToSend.get(j).add(uid);
611                     merged = true;
612                     break;
613                 }
614             }
615             if (!merged) {
616                 pkgsToSend.add(new ArrayList<>(Arrays.asList(pkgName)));
617                 uidsToSend.add(IntArray.wrap(new int[] {uid}));
618                 allowListsToSend.add(allowList);
619             }
620         }
621 
622         final Handler handler = mInjector.getHandler();
623         for (int i = 0; i < pkgsToSend.size(); i++) {
624             final Bundle extras = new Bundle(3);
625             extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST,
626                     pkgsToSend.get(i).toArray(new String[pkgsToSend.get(i).size()]));
627             extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidsToSend.get(i).toArray());
628             final SparseArray<int[]> allowList = allowListsToSend.get(i).size() == 0
629                     ? null : allowListsToSend.get(i);
630             handler.post(() -> mBroadcastHelper.sendPackageBroadcast(intent, null /* pkg */,
631                     extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null /* targetPkg */,
632                     null /* finishedReceiver */, userIds, null /* instantUserIds */,
633                     allowList, null /* bOptions */));
634         }
635     }
636 
getKnownPackageName(@onNull Computer snapshot, @KnownPackages.KnownPackage int knownPackage, int userId)637     private String getKnownPackageName(@NonNull Computer snapshot,
638             @KnownPackages.KnownPackage int knownPackage, int userId) {
639         final String[] knownPackages =
640                 mPm.getKnownPackageNamesInternal(snapshot, knownPackage, userId);
641         return knownPackages.length > 0 ? knownPackages[0] : null;
642     }
643 
isCallerDeviceOrProfileOwner(@onNull Computer snapshot, int userId, int callingUid)644     private boolean isCallerDeviceOrProfileOwner(@NonNull Computer snapshot, int userId,
645             int callingUid) {
646         if (callingUid == SYSTEM_UID) {
647             return true;
648         }
649         final String ownerPackage = mProtectedPackages.getDeviceOwnerOrProfileOwnerPackage(userId);
650         if (ownerPackage != null) {
651             return callingUid == snapshot.getPackageUidInternal(ownerPackage, 0, userId,
652                     callingUid);
653         }
654         return false;
655     }
656 
sendMyPackageSuspendedOrUnsuspended(String[] affectedPackages, boolean suspended, int userId)657     private void sendMyPackageSuspendedOrUnsuspended(String[] affectedPackages, boolean suspended,
658             int userId) {
659         final Handler handler = mInjector.getHandler();
660         final String action = suspended
661                 ? Intent.ACTION_MY_PACKAGE_SUSPENDED
662                 : Intent.ACTION_MY_PACKAGE_UNSUSPENDED;
663         handler.post(() -> {
664             final IActivityManager am = ActivityManager.getService();
665             if (am == null) {
666                 Slog.wtf(TAG, "IActivityManager null. Cannot send MY_PACKAGE_ "
667                         + (suspended ? "" : "UN") + "SUSPENDED broadcasts");
668                 return;
669             }
670             final int[] targetUserIds = new int[] {userId};
671             final Computer snapshot = mPm.snapshotComputer();
672             for (String packageName : affectedPackages) {
673                 final Bundle appExtras = suspended
674                         ? getSuspendedPackageAppExtras(snapshot, packageName, userId, SYSTEM_UID)
675                         : null;
676                 final Bundle intentExtras;
677                 if (appExtras != null) {
678                     intentExtras = new Bundle(1);
679                     intentExtras.putBundle(Intent.EXTRA_SUSPENDED_PACKAGE_EXTRAS, appExtras);
680                 } else {
681                     intentExtras = null;
682                 }
683                 mBroadcastHelper.doSendBroadcast(action, null, intentExtras,
684                         Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, packageName, null,
685                         targetUserIds, false, null, null);
686             }
687         });
688     }
689 }
690