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