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