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_USER_FIXED;
20 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
21 import static android.content.pm.PackageManager.GET_PERMISSIONS;
22 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
23 
24 import android.Manifest;
25 import android.annotation.NonNull;
26 import android.annotation.UserIdInt;
27 import android.companion.virtual.VirtualDeviceManager;
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,
200                         VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT, userId);
201             } else if (!grant && currentlyGranted) {
202                 mPermManager.revokeRuntimePermission(packageName, NOTIFICATION_PERMISSION,
203                         VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT, userId, TAG);
204             }
205             int flagMask = FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_USER_FIXED;
206             if (userSet) {
207                 mPermManager.updatePermissionFlags(packageName, NOTIFICATION_PERMISSION, flagMask,
208                         FLAG_PERMISSION_USER_SET, true,
209                         VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT, userId);
210             } else {
211                 mPermManager.updatePermissionFlags(packageName, NOTIFICATION_PERMISSION,
212                         flagMask, 0, true, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT,
213                         userId);
214             }
215         } catch (RemoteException e) {
216             Slog.e(TAG, "Could not reach system server", e);
217         } finally {
218             Binder.restoreCallingIdentity(callingId);
219         }
220     }
221 
222     /**
223      * Set the notification permission state upon phone version upgrade from S- to T+, or upon
224      * restoring a pre-T backup on a T+ device
225      */
setNotificationPermission(PackagePermission pkgPerm)226     public void setNotificationPermission(PackagePermission pkgPerm) {
227         if (pkgPerm == null || pkgPerm.packageName == null) {
228             return;
229         }
230         if (!isPermissionFixed(pkgPerm.packageName, pkgPerm.userId)) {
231             setNotificationPermission(pkgPerm.packageName, pkgPerm.userId, pkgPerm.granted,
232                     true /* userSet always true on upgrade */);
233         }
234     }
235 
isPermissionFixed(String packageName, @UserIdInt int userId)236     public boolean isPermissionFixed(String packageName, @UserIdInt int userId) {
237         final long callingId = Binder.clearCallingIdentity();
238         try {
239             try {
240                 int flags = mPermManager.getPermissionFlags(packageName, NOTIFICATION_PERMISSION,
241                         VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT, userId);
242                 return (flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0
243                         || (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0;
244             } catch (RemoteException e) {
245                 Slog.e(TAG, "Could not reach system server", e);
246             }
247             return false;
248         } finally {
249             Binder.restoreCallingIdentity(callingId);
250         }
251     }
252 
isPermissionUserSet(String packageName, @UserIdInt int userId)253     boolean isPermissionUserSet(String packageName, @UserIdInt int userId) {
254         final long callingId = Binder.clearCallingIdentity();
255         try {
256             try {
257                 int flags = mPermManager.getPermissionFlags(packageName, NOTIFICATION_PERMISSION,
258                         VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT, userId);
259                 return (flags & (PackageManager.FLAG_PERMISSION_USER_SET
260                         | PackageManager.FLAG_PERMISSION_USER_FIXED)) != 0;
261             } catch (RemoteException e) {
262                 Slog.e(TAG, "Could not reach system server", e);
263             }
264             return false;
265         } finally {
266             Binder.restoreCallingIdentity(callingId);
267         }
268     }
269 
isPermissionGrantedByDefaultOrRole(String packageName, @UserIdInt int userId)270     boolean isPermissionGrantedByDefaultOrRole(String packageName, @UserIdInt int userId) {
271         final long callingId = Binder.clearCallingIdentity();
272         try {
273             try {
274                 int flags = mPermManager.getPermissionFlags(packageName, NOTIFICATION_PERMISSION,
275                         VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT, userId);
276                 return (flags & (PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT
277                         | PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE)) != 0;
278             } catch (RemoteException e) {
279                 Slog.e(TAG, "Could not reach system server", e);
280             }
281             return false;
282         } finally {
283             Binder.restoreCallingIdentity(callingId);
284         }
285     }
286 
packageRequestsNotificationPermission(String packageName, @UserIdInt int userId)287     private boolean packageRequestsNotificationPermission(String packageName,
288             @UserIdInt int userId) {
289         try {
290             PackageInfo pi = mPackageManager.getPackageInfo(packageName, GET_PERMISSIONS, userId);
291             if (pi != null) {
292                 String[] permissions = pi.requestedPermissions;
293                 return ArrayUtils.contains(permissions, NOTIFICATION_PERMISSION);
294             }
295         } catch (RemoteException e) {
296             Slog.e(TAG, "Could not reach system server", e);
297         }
298         return false;
299     }
300 
301     public static class PackagePermission {
302         public final String packageName;
303         public final @UserIdInt int userId;
304         public final boolean granted;
305         public final boolean userModifiedSettings;
306 
PackagePermission(String pkg, int userId, boolean granted, boolean userSet)307         public PackagePermission(String pkg, int userId, boolean granted, boolean userSet) {
308             this.packageName = pkg;
309             this.userId = userId;
310             this.granted = granted;
311             this.userModifiedSettings = userSet;
312         }
313 
314         @Override
equals(Object o)315         public boolean equals(Object o) {
316             if (this == o) return true;
317             if (o == null || getClass() != o.getClass()) return false;
318             PackagePermission that = (PackagePermission) o;
319             return userId == that.userId && granted == that.granted && userModifiedSettings
320                     == that.userModifiedSettings
321                     && Objects.equals(packageName, that.packageName);
322         }
323 
324         @Override
hashCode()325         public int hashCode() {
326             return Objects.hash(packageName, userId, granted, userModifiedSettings);
327         }
328 
329         @Override
toString()330         public String toString() {
331             return "PackagePermission{" +
332                     "packageName='" + packageName + '\'' +
333                     ", userId=" + userId +
334                     ", granted=" + granted +
335                     ", userSet=" + userModifiedSettings +
336                     '}';
337         }
338     }
339 }
340