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