1 /* 2 * Copyright (C) 2020 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.permissioncontroller.permission.ui; 18 19 import static android.app.PendingIntent.FLAG_IMMUTABLE; 20 import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; 21 import static android.app.PendingIntent.getActivity; 22 import static android.app.admin.DevicePolicyResources.Strings.PermissionSettings.LOCATION_AUTO_GRANTED_MESSAGE; 23 import static android.content.Intent.ACTION_MANAGE_APP_PERMISSION; 24 import static android.content.Intent.EXTRA_PACKAGE_NAME; 25 import static android.content.Intent.EXTRA_PERMISSION_GROUP_NAME; 26 import static android.content.Intent.EXTRA_USER; 27 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; 28 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; 29 import static android.os.UserHandle.getUserHandleForUid; 30 31 import static com.android.permissioncontroller.Constants.ADMIN_AUTO_GRANTED_PERMISSIONS_ALERTING_NOTIFICATION_CHANNEL_ID; 32 import static com.android.permissioncontroller.Constants.ADMIN_AUTO_GRANTED_PERMISSIONS_NOTIFICATION_CHANNEL_ID; 33 import static com.android.permissioncontroller.Constants.ADMIN_AUTO_GRANTED_PERMISSIONS_NOTIFICATION_GROUP_ID; 34 import static com.android.permissioncontroller.Constants.ADMIN_AUTO_GRANTED_PERMISSIONS_NOTIFICATION_SUMMARY_ID; 35 import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID; 36 import static com.android.permissioncontroller.Constants.PERMISSION_GRANTED_BY_ADMIN_NOTIFICATION_ID; 37 import static com.android.permissioncontroller.permission.utils.Utils.getSystemServiceSafe; 38 import static com.android.permissioncontroller.permission.utils.Utils.getValidSessionId; 39 40 import android.Manifest; 41 import android.app.Notification; 42 import android.app.NotificationChannel; 43 import android.app.NotificationManager; 44 import android.content.Context; 45 import android.content.Intent; 46 import android.content.pm.PackageInfo; 47 import android.content.pm.PackageManager; 48 import android.content.pm.ResolveInfo; 49 import android.graphics.Bitmap; 50 import android.net.Uri; 51 import android.os.Bundle; 52 import android.os.UserHandle; 53 import android.provider.Settings; 54 import android.service.notification.StatusBarNotification; 55 import android.util.ArraySet; 56 57 import androidx.annotation.NonNull; 58 import androidx.annotation.Nullable; 59 60 import com.android.permissioncontroller.R; 61 import com.android.permissioncontroller.permission.utils.KotlinUtils; 62 import com.android.permissioncontroller.permission.utils.Utils; 63 64 import java.util.ArrayList; 65 66 /** 67 * Notifies the user when the admin has granted sensitive permissions, such as location-related 68 * permissions, to apps. 69 * 70 * <p>NOTE: This class currently only handles location permissions. 71 * 72 * <p>To handle other sensitive permissions, it would have to be expanded to notify the user 73 * not only of location issues and use icons of the different groups associated with different 74 * permissions. 75 */ 76 public class AutoGrantPermissionsNotifier { 77 /** 78 * Set of permissions for which the user should be notified when the admin auto-grants one of 79 * them. 80 */ 81 private static final ArraySet<String> PERMISSIONS_TO_NOTIFY_FOR = new ArraySet<>(); 82 83 static { 84 PERMISSIONS_TO_NOTIFY_FOR.add(Manifest.permission.ACCESS_FINE_LOCATION); 85 PERMISSIONS_TO_NOTIFY_FOR.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION); 86 PERMISSIONS_TO_NOTIFY_FOR.add(Manifest.permission.ACCESS_COARSE_LOCATION); 87 } 88 89 private final @NonNull Context mContext; 90 /** 91 * The package to which permissions were auto-granted. 92 */ 93 private final @NonNull PackageInfo mPackageInfo; 94 /** 95 * The permissions that were auto-granted. 96 */ 97 private final ArrayList<String> mGrantedPermissions = new ArrayList<>(); 98 99 private final NotificationManager mNotificationManager; 100 AutoGrantPermissionsNotifier(@onNull Context context, @NonNull PackageInfo packageInfo)101 public AutoGrantPermissionsNotifier(@NonNull Context context, 102 @NonNull PackageInfo packageInfo) { 103 mPackageInfo = packageInfo; 104 mNotificationManager = getSystemServiceSafe(context, NotificationManager.class); 105 UserHandle callingUser = getUserHandleForUid(mPackageInfo.applicationInfo.uid); 106 mContext = context.createContextAsUser(callingUser, 0); 107 } 108 109 /** 110 * Create the channel to which the notification about auto-granted permission should be posted. 111 * 112 * @param user The user for which the permission was auto-granted. 113 * @param shouldAlertUser 114 */ createAutoGrantNotifierChannel(boolean shouldNotifySilently)115 private void createAutoGrantNotifierChannel(boolean shouldNotifySilently) { 116 NotificationChannel autoGrantedPermissionsChannel = new NotificationChannel( 117 getNotificationChannelId(shouldNotifySilently), 118 mContext.getString(R.string.auto_granted_permissions), 119 NotificationManager.IMPORTANCE_HIGH); 120 if (shouldNotifySilently) { 121 autoGrantedPermissionsChannel.enableVibration(false); 122 autoGrantedPermissionsChannel.setSound(Uri.EMPTY, null); 123 } 124 mNotificationManager.createNotificationChannel(autoGrantedPermissionsChannel); 125 } 126 127 /** 128 * Notifies the user if any permissions were auto-granted. 129 * 130 * <p>NOTE: Right now this method only deals with location permissions. 131 */ notifyOfAutoGrantPermissions(boolean shouldNotifySilently)132 public void notifyOfAutoGrantPermissions(boolean shouldNotifySilently) { 133 if (mGrantedPermissions.isEmpty()) { 134 return; 135 } 136 137 createAutoGrantNotifierChannel(shouldNotifySilently); 138 139 PackageManager pm = mContext.getPackageManager(); 140 CharSequence pkgLabel = pm.getApplicationLabel(mPackageInfo.applicationInfo); 141 142 long sessionId = getValidSessionId(); 143 144 Intent manageAppPermission = getSettingsPermissionIntent(sessionId); 145 Bitmap pkgIconBmp = KotlinUtils.INSTANCE.convertToBitmap( 146 pm.getApplicationIcon(mPackageInfo.applicationInfo)); 147 // Use the hash code of the package name string as a unique request code for 148 // PendingIntent.getActivity. 149 // To prevent multiple notifications related to different apps all leading to the same 150 // "Manage app permissions" screen for one single app, the pending intent for each 151 // notification has to be distinguished from other pending intents. 152 // This is done by specifying a different request code. However, a random request code 153 // cannot be used as we'd like the pending intent to be updated if multiple 154 // notifications are shown for the same app. 155 // The package name hash code serves as a stable request code value. 156 int packageBasedRequestCode = mPackageInfo.packageName.hashCode(); 157 158 String title = mContext.getString( 159 R.string.auto_granted_location_permission_notification_title, pkgLabel); 160 String messageText = Utils.getEnterpriseString(mContext, LOCATION_AUTO_GRANTED_MESSAGE, 161 R.string.auto_granted_permission_notification_body, pkgLabel); 162 Notification.Builder notificationBuilder = (new Notification.Builder(mContext, 163 getNotificationChannelId(shouldNotifySilently))) 164 .setContentTitle(title) 165 .setContentText(messageText) 166 .setStyle(new Notification.BigTextStyle().bigText(messageText).setBigContentTitle( 167 title)) 168 .setGroup(ADMIN_AUTO_GRANTED_PERMISSIONS_NOTIFICATION_GROUP_ID) 169 // NOTE: Different icons would be needed for different permissions. 170 .setGroupAlertBehavior(Notification.GROUP_ALERT_SUMMARY) 171 .setSmallIcon(R.drawable.ic_pin_drop) 172 .setLargeIcon(pkgIconBmp) 173 .setColor(mContext.getColor(android.R.color.system_notification_accent_color)) 174 .setContentIntent(getActivity(mContext, packageBasedRequestCode, 175 manageAppPermission, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE)); 176 177 // Add the Settings app name since we masquerade it. 178 CharSequence appName = getSettingsAppName(); 179 if (appName != null) { 180 Bundle extras = new Bundle(); 181 extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, appName.toString()); 182 notificationBuilder.addExtras(extras); 183 } 184 185 // Cancel previous notifications for the same package to avoid redundant notifications. 186 // This code currently only deals with location-related notifications, which would all lead 187 // to the same Settings activity for managing location permissions. 188 // If ever extended to cover multiple types of notifications, then only multiple 189 // notifications of the same group should be canceled. 190 mNotificationManager.cancel( 191 mPackageInfo.packageName, PERMISSION_GRANTED_BY_ADMIN_NOTIFICATION_ID); 192 193 mNotificationManager.notify(mPackageInfo.packageName, 194 PERMISSION_GRANTED_BY_ADMIN_NOTIFICATION_ID, 195 notificationBuilder.build()); 196 197 // only show the summary notification if it is not already showing. Otherwise, this 198 // breaks the alerting behaviour. 199 if (!isNotificationActive(ADMIN_AUTO_GRANTED_PERMISSIONS_NOTIFICATION_SUMMARY_ID)) { 200 String summaryTitle = mContext.getString(R.string.auto_granted_permissions); 201 202 Notification.Builder summaryNotificationBuilder = new Notification.Builder(mContext, 203 getNotificationChannelId(shouldNotifySilently)) 204 .setContentTitle(summaryTitle) 205 .setSmallIcon(R.drawable.ic_pin_drop) 206 .setGroup(ADMIN_AUTO_GRANTED_PERMISSIONS_NOTIFICATION_GROUP_ID) 207 .setGroupSummary(true); 208 209 mNotificationManager.notify(ADMIN_AUTO_GRANTED_PERMISSIONS_NOTIFICATION_SUMMARY_ID, 210 summaryNotificationBuilder.build()); 211 } 212 } 213 214 /** 215 * Checks if the auto-granted permission is one of those for which the user has to be notified 216 * and if so, stores it for when the user actually is notified. 217 * 218 * @param permissionName the permission that was auto-granted. 219 */ onPermissionAutoGranted(@onNull String permissionName)220 public void onPermissionAutoGranted(@NonNull String permissionName) { 221 if (PERMISSIONS_TO_NOTIFY_FOR.contains(permissionName)) { 222 mGrantedPermissions.add(permissionName); 223 } 224 } 225 getSettingsAppName()226 private @Nullable CharSequence getSettingsAppName() { 227 PackageManager pm = mContext.getPackageManager(); 228 // We pretend we're the Settings app sending the notification, so figure out its name. 229 Intent openSettingsIntent = new Intent(Settings.ACTION_SETTINGS); 230 ResolveInfo resolveInfo = pm.resolveActivity(openSettingsIntent, 0); 231 if (resolveInfo == null) { 232 return null; 233 } 234 return pm.getApplicationLabel(resolveInfo.activityInfo.applicationInfo); 235 } 236 getSettingsPermissionIntent(long sessionId)237 private @NonNull Intent getSettingsPermissionIntent(long sessionId) { 238 UserHandle callingUser = getUserHandleForUid(mPackageInfo.applicationInfo.uid); 239 240 return new Intent(ACTION_MANAGE_APP_PERMISSION) 241 .addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK) 242 .putExtra(EXTRA_PERMISSION_GROUP_NAME, Manifest.permission_group.LOCATION) 243 .putExtra(EXTRA_PACKAGE_NAME, mPackageInfo.packageName) 244 .putExtra(EXTRA_USER, callingUser) 245 .putExtra(EXTRA_SESSION_ID, sessionId) 246 .putExtra(ManagePermissionsActivity.EXTRA_CALLER_NAME, 247 AutoGrantPermissionsNotifier.class.getName()); 248 } 249 getNotificationChannelId(boolean shouldNotifySilently)250 private String getNotificationChannelId(boolean shouldNotifySilently) { 251 if (shouldNotifySilently) { 252 return ADMIN_AUTO_GRANTED_PERMISSIONS_NOTIFICATION_CHANNEL_ID; 253 } else { 254 return ADMIN_AUTO_GRANTED_PERMISSIONS_ALERTING_NOTIFICATION_CHANNEL_ID; 255 } 256 } 257 isNotificationActive(int notificationId)258 private boolean isNotificationActive(int notificationId) { 259 for (StatusBarNotification notification : mNotificationManager.getActiveNotifications()) { 260 if (notification.getId() == notificationId) { 261 return true; 262 } 263 } 264 return false; 265 } 266 } 267 268