• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.notification;
18 
19 import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
20 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
21 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
22 import static android.content.pm.PackageManager.GET_PERMISSIONS;
23 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
24 
25 import android.Manifest;
26 import android.annotation.NonNull;
27 import android.annotation.UserIdInt;
28 import android.content.pm.IPackageManager;
29 import android.content.pm.PackageInfo;
30 import android.content.pm.PackageManager;
31 import android.content.pm.ParceledListSlice;
32 import android.os.Binder;
33 import android.os.RemoteException;
34 import android.permission.IPermissionManager;
35 import android.util.ArrayMap;
36 import android.util.Pair;
37 import android.util.Slog;
38 
39 import com.android.internal.util.ArrayUtils;
40 import com.android.server.pm.permission.PermissionManagerServiceInternal;
41 
42 import java.util.Collections;
43 import java.util.HashSet;
44 import java.util.List;
45 import java.util.Objects;
46 import java.util.Set;
47 
48 /**
49  * NotificationManagerService helper for querying/setting the app-level notification permission
50  */
51 public final class PermissionHelper {
52     private static final String TAG = "PermissionHelper";
53 
54     private static final String NOTIFICATION_PERMISSION = Manifest.permission.POST_NOTIFICATIONS;
55 
56     private final PermissionManagerServiceInternal mPmi;
57     private final IPackageManager mPackageManager;
58     private final IPermissionManager mPermManager;
59 
PermissionHelper(PermissionManagerServiceInternal pmi, IPackageManager packageManager, IPermissionManager permManager)60     public PermissionHelper(PermissionManagerServiceInternal pmi, IPackageManager packageManager,
61             IPermissionManager permManager) {
62         mPmi = pmi;
63         mPackageManager = packageManager;
64         mPermManager = permManager;
65     }
66 
67     /**
68      * Returns whether the given uid holds the notification permission. Must not be called
69      * with a lock held.
70      */
hasPermission(int uid)71     public boolean hasPermission(int uid) {
72         final long callingId = Binder.clearCallingIdentity();
73         try {
74             return mPmi.checkUidPermission(uid, NOTIFICATION_PERMISSION) == PERMISSION_GRANTED;
75         } finally {
76             Binder.restoreCallingIdentity(callingId);
77         }
78     }
79 
80     /**
81      * Returns all of the apps that have requested the notification permission in a given user.
82      * Must not be called with a lock held. Format: uid, packageName
83      */
getAppsRequestingPermission(int userId)84     Set<Pair<Integer, String>> getAppsRequestingPermission(int userId) {
85         Set<Pair<Integer, String>> requested = new HashSet<>();
86         List<PackageInfo> pkgs = getInstalledPackages(userId);
87         for (PackageInfo pi : pkgs) {
88             // when data was stored in PreferencesHelper, we only had data for apps that
89             // had ever registered an intent to send a notification. To match that behavior,
90             // filter the app list to apps that have requested the notification permission.
91             if (pi.requestedPermissions == null) {
92                 continue;
93             }
94             for (String perm : pi.requestedPermissions) {
95                 if (NOTIFICATION_PERMISSION.equals(perm)) {
96                     requested.add(new Pair<>(pi.applicationInfo.uid, pi.packageName));
97                     break;
98                 }
99             }
100         }
101         return requested;
102     }
103 
getInstalledPackages(int userId)104     private List<PackageInfo> getInstalledPackages(int userId) {
105         ParceledListSlice<PackageInfo> parceledList = null;
106         try {
107             parceledList = mPackageManager.getInstalledPackages(GET_PERMISSIONS, userId);
108         } catch (RemoteException e) {
109             Slog.d(TAG, "Could not reach system server", e);
110         }
111         if (parceledList == null) {
112             return Collections.emptyList();
113         }
114         return parceledList.getList();
115     }
116 
117     /**
118      * Returns a list of apps that hold the notification permission. Must not be called
119      * with a lock held. Format: uid, packageName.
120      */
getAppsGrantedPermission(int userId)121     Set<Pair<Integer, String>> getAppsGrantedPermission(int userId) {
122         Set<Pair<Integer, String>> granted = new HashSet<>();
123         ParceledListSlice<PackageInfo> parceledList = null;
124         try {
125             parceledList = mPackageManager.getPackagesHoldingPermissions(
126                     new String[] {NOTIFICATION_PERMISSION}, 0, userId);
127         } catch (RemoteException e) {
128             Slog.e(TAG, "Could not reach system server", e);
129         }
130         if (parceledList == null) {
131             return granted;
132         }
133         for (PackageInfo pi : parceledList.getList()) {
134             granted.add(new Pair<>(pi.applicationInfo.uid, pi.packageName));
135         }
136         return granted;
137     }
138 
139     // Key: (uid, package name); Value: (granted, user set)
140     public @NonNull
141             ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>>
getNotificationPermissionValues(int userId)142                     getNotificationPermissionValues(int userId) {
143         ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> notifPermissions = new ArrayMap<>();
144         Set<Pair<Integer, String>> allRequestingUids = getAppsRequestingPermission(userId);
145         Set<Pair<Integer, String>> allApprovedUids = getAppsGrantedPermission(userId);
146         for (Pair<Integer, String> pair : allRequestingUids) {
147             notifPermissions.put(pair, new Pair(allApprovedUids.contains(pair),
148                     isPermissionUserSet(pair.second /* package name */, userId)));
149         }
150         return notifPermissions;
151     }
152 
153     /**
154      * Grants or revokes the notification permission for a given package/user. UserSet should
155      * only be true if this method is being called to migrate existing user choice, because it
156      * can prevent the user from seeing the in app permission dialog. Must not be called
157      * with a lock held.
158      */
setNotificationPermission(String packageName, @UserIdInt int userId, boolean grant, boolean userSet)159     public void setNotificationPermission(String packageName, @UserIdInt int userId, boolean grant,
160             boolean userSet) {
161         final long callingId = Binder.clearCallingIdentity();
162         try {
163             // Do not change the permission if the package doesn't request it, do not change fixed
164             // permissions, and do not change non-user set permissions that are granted by default,
165             // or granted by role.
166             if (!packageRequestsNotificationPermission(packageName, userId)
167                     || isPermissionFixed(packageName, userId)
168                     || (isPermissionGrantedByDefaultOrRole(packageName, userId) && !userSet)) {
169                 return;
170             }
171 
172             boolean currentlyGranted = mPmi.checkPermission(packageName, NOTIFICATION_PERMISSION,
173                     userId) != PackageManager.PERMISSION_DENIED;
174             if (grant && !currentlyGranted) {
175                 mPermManager.grantRuntimePermission(packageName, NOTIFICATION_PERMISSION, userId);
176             } else if (!grant && currentlyGranted) {
177                 mPermManager.revokeRuntimePermission(packageName, NOTIFICATION_PERMISSION,
178                         userId, TAG);
179             }
180             int flagMask = FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_USER_FIXED;
181             flagMask = userSet || !grant ? flagMask | FLAG_PERMISSION_GRANTED_BY_DEFAULT : flagMask;
182             if (userSet) {
183                 mPermManager.updatePermissionFlags(packageName, NOTIFICATION_PERMISSION,
184                         flagMask, FLAG_PERMISSION_USER_SET, true, userId);
185             } else {
186                 mPermManager.updatePermissionFlags(packageName, NOTIFICATION_PERMISSION,
187                         flagMask, 0, true, userId);
188             }
189         } catch (RemoteException e) {
190             Slog.e(TAG, "Could not reach system server", e);
191         } finally {
192             Binder.restoreCallingIdentity(callingId);
193         }
194     }
195 
196     /**
197      * Set the notification permission state upon phone version upgrade from S- to T+, or upon
198      * restoring a pre-T backup on a T+ device
199      */
setNotificationPermission(PackagePermission pkgPerm)200     public void setNotificationPermission(PackagePermission pkgPerm) {
201         if (pkgPerm == null || pkgPerm.packageName == null) {
202             return;
203         }
204         if (!isPermissionFixed(pkgPerm.packageName, pkgPerm.userId)) {
205             setNotificationPermission(pkgPerm.packageName, pkgPerm.userId, pkgPerm.granted,
206                     true /* userSet always true on upgrade */);
207         }
208     }
209 
isPermissionFixed(String packageName, @UserIdInt int userId)210     public boolean isPermissionFixed(String packageName, @UserIdInt int userId) {
211         final long callingId = Binder.clearCallingIdentity();
212         try {
213             try {
214                 int flags = mPermManager.getPermissionFlags(packageName, NOTIFICATION_PERMISSION,
215                         userId);
216                 return (flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0
217                         || (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0;
218             } catch (RemoteException e) {
219                 Slog.e(TAG, "Could not reach system server", e);
220             }
221             return false;
222         } finally {
223             Binder.restoreCallingIdentity(callingId);
224         }
225     }
226 
isPermissionUserSet(String packageName, @UserIdInt int userId)227     boolean isPermissionUserSet(String packageName, @UserIdInt int userId) {
228         final long callingId = Binder.clearCallingIdentity();
229         try {
230             try {
231                 int flags = mPermManager.getPermissionFlags(packageName, NOTIFICATION_PERMISSION,
232                         userId);
233                 return (flags & (PackageManager.FLAG_PERMISSION_USER_SET
234                         | PackageManager.FLAG_PERMISSION_USER_FIXED)) != 0;
235             } catch (RemoteException e) {
236                 Slog.e(TAG, "Could not reach system server", e);
237             }
238             return false;
239         } finally {
240             Binder.restoreCallingIdentity(callingId);
241         }
242     }
243 
isPermissionGrantedByDefaultOrRole(String packageName, @UserIdInt int userId)244     boolean isPermissionGrantedByDefaultOrRole(String packageName, @UserIdInt int userId) {
245         final long callingId = Binder.clearCallingIdentity();
246         try {
247             try {
248                 int flags = mPermManager.getPermissionFlags(packageName, NOTIFICATION_PERMISSION,
249                         userId);
250                 return (flags & (PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT
251                         | PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE)) != 0;
252             } catch (RemoteException e) {
253                 Slog.e(TAG, "Could not reach system server", e);
254             }
255             return false;
256         } finally {
257             Binder.restoreCallingIdentity(callingId);
258         }
259     }
260 
packageRequestsNotificationPermission(String packageName, @UserIdInt int userId)261     private boolean packageRequestsNotificationPermission(String packageName,
262             @UserIdInt int userId) {
263         try {
264             String[] permissions = mPackageManager.getPackageInfo(packageName, GET_PERMISSIONS,
265                     userId).requestedPermissions;
266             return ArrayUtils.contains(permissions, NOTIFICATION_PERMISSION);
267         } catch (RemoteException e) {
268             Slog.e(TAG, "Could not reach system server", e);
269         }
270         return false;
271     }
272 
273     public static class PackagePermission {
274         public final String packageName;
275         public final @UserIdInt int userId;
276         public final boolean granted;
277         public final boolean userModifiedSettings;
278 
PackagePermission(String pkg, int userId, boolean granted, boolean userSet)279         public PackagePermission(String pkg, int userId, boolean granted, boolean userSet) {
280             this.packageName = pkg;
281             this.userId = userId;
282             this.granted = granted;
283             this.userModifiedSettings = userSet;
284         }
285 
286         @Override
equals(Object o)287         public boolean equals(Object o) {
288             if (this == o) return true;
289             if (o == null || getClass() != o.getClass()) return false;
290             PackagePermission that = (PackagePermission) o;
291             return userId == that.userId && granted == that.granted && userModifiedSettings
292                     == that.userModifiedSettings
293                     && Objects.equals(packageName, that.packageName);
294         }
295 
296         @Override
hashCode()297         public int hashCode() {
298             return Objects.hash(packageName, userId, granted, userModifiedSettings);
299         }
300 
301         @Override
toString()302         public String toString() {
303             return "PackagePermission{" +
304                     "packageName='" + packageName + '\'' +
305                     ", userId=" + userId +
306                     ", granted=" + granted +
307                     ", userSet=" + userModifiedSettings +
308                     '}';
309         }
310     }
311 }
312