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