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