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