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