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