• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.devicepolicy;
18 
19 import static com.android.server.devicepolicy.DevicePolicyManagerService.LOG_TAG;
20 
21 import android.annotation.Nullable;
22 import android.app.admin.flags.Flags;
23 import android.content.pm.PackageManagerInternal;
24 import android.util.ArraySet;
25 
26 import com.android.server.utils.Slogf;
27 
28 import java.util.Arrays;
29 import java.util.Collections;
30 import java.util.List;
31 import java.util.Set;
32 
33 /**
34  * Helper class for calling into PackageManagerInternal.setPackagesSuspendedByAdmin.
35  * Two main things this class encapsulates:
36  *    1. Handling of the DPM internal suspension exemption list
37  *    2. Calculating the failed packages result in the context of coexistence
38  *
39  * 1 is handled by the two internal methods {@link #suspendWithExemption(Set)} and
40  * {@link #unsuspendWithExemption(Set)} where the exemption list is taken into consideration
41  * before and after calling {@link PackageManagerInternal#setPackagesSuspendedByAdmin}.
42  * In order to compute 2, the resolved package suspension state before and after suspension is
43  * needed as multiple admins can both suspend the same packages under coexistence.
44  */
45 public class PackageSuspender {
46 
47     private final Set<String> mSuspendedPackageBefore;
48     private final Set<String> mSuspendedPackageAfter;
49     private final List<String> mExemptedPackages;
50     private final PackageManagerInternal mPackageManager;
51     private final int mUserId;
52 
PackageSuspender(@ullable Set<String> suspendedPackageBefore, @Nullable Set<String> suspendedPackageAfter, List<String> exemptedPackages, PackageManagerInternal pmi, int userId)53     public PackageSuspender(@Nullable Set<String> suspendedPackageBefore,
54             @Nullable Set<String> suspendedPackageAfter, List<String> exemptedPackages,
55             PackageManagerInternal pmi, int userId) {
56         mSuspendedPackageBefore =
57                 suspendedPackageBefore != null ? suspendedPackageBefore : Collections.emptySet();
58         mSuspendedPackageAfter =
59                 suspendedPackageAfter != null ? suspendedPackageAfter : Collections.emptySet();
60         mExemptedPackages = exemptedPackages;
61         mPackageManager = pmi;
62         mUserId = userId;
63     }
64 
65     /**
66      * Suspend packages that are requested by a single admin
67      *
68      * @return an array of packages that the admin has requested to suspend but could not be
69      * suspended, due to DPM and PackageManager exemption list.
70      *
71      */
suspend(Set<String> packages)72     public String[] suspend(Set<String> packages) {
73         // When suspending, call PM with the list of packages admin has requested, even if some
74         // of these packages are already in suspension (some other admin might have already
75         // suspended them). We do it this way so that we can simply return the failed list from
76         // PackageManager to the caller as the accurate list of unsuspended packages.
77         // This is different from the unsuspend() logic, please see below.
78         //
79         // For example admin A already suspended package 1, 2 and 3, but package 3 is
80         // PackageManager-exempted. Now admin B wants to suspend package 2, 3 and 4 (2 and 4 are
81         // suspendable). We need to return package 3 as the unsuspended package here, and we ask
82         // PackageManager to suspend package 2, 3 and 4 here (who will return package 3 in the
83         // failed list, and package 2 is already suspended).
84         Set<String> result = suspendWithExemption(packages);
85         return result.toArray(String[]::new);
86     }
87 
88     /**
89      * Suspend packages considering the exemption list.
90      *
91      * @return the set of packages that couldn't be suspended, either due to the exemption list,
92      * or due to failures from PackageManagerInternal itself.
93      */
suspendWithExemption(Set<String> packages)94     private Set<String> suspendWithExemption(Set<String> packages) {
95         Set<String> packagesToSuspend = new ArraySet<>(packages);
96         // Any original packages that are also in the exempted list will not be suspended and hence
97         // will appear in the final result.
98         Set<String> result = new ArraySet<>(mExemptedPackages);
99         result.retainAll(packagesToSuspend);
100         // Remove exempted packages before calling PackageManager
101         packagesToSuspend.removeAll(mExemptedPackages);
102         String[] failedPackages = mPackageManager.setPackagesSuspendedByAdmin(
103                 mUserId, packagesToSuspend.toArray(String[]::new), true);
104         if (failedPackages == null) {
105             Slogf.w(LOG_TAG, "PM failed to suspend packages (%s)", packages);
106             return packages;
107         } else {
108             result.addAll(Arrays.asList(failedPackages));
109             return result;
110         }
111     }
112 
113     /**
114      * Unsuspend packages that are requested by a single admin
115      *
116      * @return an array of packages that the admin has requested to unsuspend but could not be
117      * unsuspended, due to other admin's policy or PackageManager restriction.
118      *
119      */
unsuspend(Set<String> packages)120     public String[] unsuspend(Set<String> packages) {
121         // Unlike suspend(), when unsuspending, take suspension by other admins into account: only
122         // packages not suspended by other admins are passed to PackageManager.
123         Set<String> packagesToUnsuspend = new ArraySet<>(
124                 Flags.unsuspendNotSuspended() ? packages : mSuspendedPackageBefore);
125         packagesToUnsuspend.removeAll(mSuspendedPackageAfter);
126 
127         // To calculate the result (which packages are not unsuspended), start with packages that
128         // are still subject to another admin's suspension policy. This is calculated by
129         // intersecting the packages argument with mSuspendedPackageAfter.
130         Set<String> result = new ArraySet<>(packages);
131         result.retainAll(mSuspendedPackageAfter);
132         // Remove mExemptedPackages since they can't be suspended to start with.
133         result.removeAll(mExemptedPackages);
134         // Finally make the unsuspend() request and add packages that PackageManager can't unsuspend
135         // to the result.
136         result.addAll(unsuspendWithExemption(packagesToUnsuspend));
137         return result.toArray(String[]::new);
138     }
139 
140     /**
141      * Unsuspend packages considering the exemption list.
142      *
143      * @return the set of packages that couldn't be unsuspended, either due to the exemption list,
144      * or due to failures from PackageManagerInternal itself.
145      */
unsuspendWithExemption(Set<String> packages)146     private Set<String> unsuspendWithExemption(Set<String> packages) {
147         // when unsuspending, no need to consider exemption list since by definition they can't
148         // be suspended to begin with.
149         String[] failedPackages = mPackageManager.setPackagesSuspendedByAdmin(
150                 mUserId, packages.toArray(String[]::new), false);
151         if (failedPackages == null) {
152             Slogf.w(LOG_TAG, "PM failed to unsuspend packages (%s)", packages);
153         }
154         return new ArraySet<>(failedPackages);
155     }
156 }
157