1 /*
2  * Copyright (C) 2011 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.settings.bluetooth;
18 
19 import android.app.Notification;
20 import android.app.NotificationChannel;
21 import android.app.NotificationManager;
22 import android.app.PendingIntent;
23 import android.bluetooth.BluetoothDevice;
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.pm.PackageManager.NameNotFoundException;
28 import android.os.PowerManager;
29 import android.os.UserManager;
30 import android.util.Log;
31 
32 import com.android.settings.R;
33 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
34 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
35 import com.android.settingslib.bluetooth.LocalBluetoothManager;
36 
37 /**
38  * BluetoothPermissionRequest is a receiver to receive Bluetooth connection
39  * access request.
40  */
41 public final class BluetoothPermissionRequest extends BroadcastReceiver {
42 
43     private static final String TAG = "BluetoothPermissionRequest";
44     private static final boolean DEBUG = Utils.V;
45     private static final int NOTIFICATION_ID = android.R.drawable.stat_sys_data_bluetooth;
46 
47     private static final String NOTIFICATION_TAG_PBAP = "Phonebook Access" ;
48     private static final String NOTIFICATION_TAG_MAP = "Message Access";
49     private static final String NOTIFICATION_TAG_SAP = "SIM Access";
50     /* TODO: Consolidate this multiple defined but common channel ID with other
51      * handlers that declare and use the same channel ID */
52     private static final String BLUETOOTH_NOTIFICATION_CHANNEL =
53         "bluetooth_notification_channel";
54 
55     private NotificationChannel mNotificationChannel = null;
56 
57     Context mContext;
58     int mRequestType;
59     BluetoothDevice mDevice;
60 
61     @Override
onReceive(Context context, Intent intent)62     public void onReceive(Context context, Intent intent) {
63         mContext = context;
64         String action = intent.getAction();
65 
66         if (DEBUG) Log.d(TAG, "onReceive" + action);
67 
68         if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST)) {
69             UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
70             // skip the notification for managed profiles.
71             if (um.isManagedProfile()) {
72                 if (DEBUG) Log.d(TAG, "Blocking notification for managed profile.");
73                 return;
74             }
75             // convert broadcast intent into activity intent (same action string)
76             mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
77             mRequestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
78                                                  BluetoothDevice.REQUEST_TYPE_PROFILE_CONNECTION);
79 
80             if (DEBUG) {
81                 Log.d(TAG, "onReceive request type: " + mRequestType);
82             }
83 
84             // Even if the user has already made the choice, Bluetooth still may not know that if
85             // the user preference data have not been migrated from Settings app's shared
86             // preferences to Bluetooth app's. In that case, Bluetooth app broadcasts an
87             // ACTION_CONNECTION_ACCESS_REQUEST intent to ask to Settings app.
88             //
89             // If that happens, 'checkUserChoice()' here will do migration because it finds or
90             // creates a 'CachedBluetoothDevice' object for the device.
91             //
92             // After migration is done, 'checkUserChoice()' replies to the request by sending an
93             // ACTION_CONNECTION_ACCESS_REPLY intent. And we don't need to start permission activity
94             // dialog or notification.
95             if (checkUserChoice()) {
96                 return;
97             }
98 
99             Intent connectionAccessIntent = new Intent(action);
100             connectionAccessIntent.setClass(context, BluetoothPermissionActivity.class);
101             // We use the FLAG_ACTIVITY_MULTIPLE_TASK since we can have multiple concurrent access
102             // requests.
103             connectionAccessIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
104                     | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
105             // This is needed to create two pending intents to the same activity. The value is not
106             // used in the activity.
107             connectionAccessIntent.setType(Integer.toString(mRequestType));
108             connectionAccessIntent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
109                                             mRequestType);
110             connectionAccessIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
111 
112             String title = null;
113             String message = null;
114             PowerManager powerManager =
115                 (PowerManager) context.getSystemService(Context.POWER_SERVICE);
116 
117             if (powerManager.isScreenOn()
118                     && LocalBluetoothPreferences.shouldShowDialogInForeground(
119                             context, mDevice)) {
120                 context.startActivity(connectionAccessIntent);
121             } else {
122                 // Put up a notification that leads to the dialog
123 
124                 // Create an intent triggered by clicking on the
125                 // "Clear All Notifications" button
126 
127                 String bluetoothName;
128                 try {
129                     bluetoothName = Utils.findBluetoothPackageName(context);
130                 } catch (NameNotFoundException e) {
131                     e.printStackTrace();
132                     return;
133                 }
134                 Intent deleteIntent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
135                 deleteIntent.setPackage(bluetoothName);
136                 deleteIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
137                 deleteIntent.putExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
138                         BluetoothDevice.CONNECTION_ACCESS_NO);
139                 deleteIntent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, mRequestType);
140                 String deviceAlias = Utils.createRemoteName(context, mDevice);
141                 switch (mRequestType) {
142                     case BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS:
143                         title = context.getString(R.string.bluetooth_phonebook_request);
144                         message = context.getString(
145                                 R.string.bluetooth_phonebook_access_notification_content);
146                         break;
147                     case BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS:
148                         title = context.getString(R.string.bluetooth_map_request);
149                         message = context.getString(
150                                 R.string.bluetooth_message_access_notification_content);
151                         break;
152                     case BluetoothDevice.REQUEST_TYPE_SIM_ACCESS:
153                         title = context.getString(
154                                 R.string.bluetooth_sim_card_access_notification_title);
155                         message = context.getString(
156                                 R.string.bluetooth_sim_card_access_notification_content);
157                         break;
158                     default:
159                         title = context.getString(
160                                 R.string.bluetooth_connect_access_notification_title);
161                         message = context.getString(
162                                 R.string.bluetooth_connect_access_notification_content);
163                         break;
164                 }
165                 NotificationManager notificationManager =
166                     (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
167                 if (mNotificationChannel == null) {
168                     mNotificationChannel = new NotificationChannel(BLUETOOTH_NOTIFICATION_CHANNEL,
169                             context.getString(R.string.bluetooth),
170                             NotificationManager.IMPORTANCE_HIGH);
171                     notificationManager.createNotificationChannel(mNotificationChannel);
172                 }
173                 Notification notification = new Notification.Builder(context,
174                         BLUETOOTH_NOTIFICATION_CHANNEL)
175                         .setContentTitle(title)
176                         .setTicker(message)
177                         .setContentText(message)
178                         .setStyle(new Notification.BigTextStyle().bigText(message))
179                         .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
180                         .setAutoCancel(true)
181                         .setPriority(Notification.PRIORITY_MAX)
182                         .setOnlyAlertOnce(false)
183                         .setDefaults(Notification.DEFAULT_ALL)
184                         .setContentIntent(PendingIntent.getActivity(context, 0,
185                                 connectionAccessIntent, PendingIntent.FLAG_IMMUTABLE))
186                         .setDeleteIntent(PendingIntent.getBroadcast(context, 0, deleteIntent,
187                                 PendingIntent.FLAG_IMMUTABLE))
188                         .setColor(context.getColor(
189                                 com.android.internal.R.color.system_notification_accent_color))
190                         .setLocalOnly(true)
191                         .build();
192 
193                 notification.flags |= Notification.FLAG_NO_CLEAR; // Cannot be set with the builder.
194 
195                 notificationManager.notify(getNotificationTag(mRequestType), NOTIFICATION_ID,
196                         notification);
197             }
198         } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL)) {
199             // Remove the notification
200             NotificationManager manager = (NotificationManager) context
201                 .getSystemService(Context.NOTIFICATION_SERVICE);
202             mRequestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
203                                         BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
204             manager.cancel(getNotificationTag(mRequestType), NOTIFICATION_ID);
205         }
206     }
207 
getNotificationTag(int requestType)208     private String getNotificationTag(int requestType) {
209         if(requestType == BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS) {
210             return NOTIFICATION_TAG_PBAP;
211         } else if(mRequestType == BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS) {
212             return NOTIFICATION_TAG_MAP;
213         } else if(mRequestType == BluetoothDevice.REQUEST_TYPE_SIM_ACCESS) {
214             return NOTIFICATION_TAG_SAP;
215         }
216         return null;
217     }
218 
219     /**
220      * @return true user had made a choice, this method replies to the request according
221      *              to user's previous decision
222      *         false user hadnot made any choice on this device
223      */
checkUserChoice()224     private boolean checkUserChoice() {
225         boolean processed = false;
226 
227         // ignore if it is something else than phonebook/message settings it wants us to remember
228         if (mRequestType != BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS
229                 && mRequestType != BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS
230                 && mRequestType != BluetoothDevice.REQUEST_TYPE_SIM_ACCESS) {
231             if (DEBUG) Log.d(TAG, "checkUserChoice(): Unknown RequestType " + mRequestType);
232             return processed;
233         }
234 
235         LocalBluetoothManager bluetoothManager = Utils.getLocalBtManager(mContext);
236         CachedBluetoothDeviceManager cachedDeviceManager =
237                 bluetoothManager.getCachedDeviceManager();
238         CachedBluetoothDevice cachedDevice = cachedDeviceManager.findDevice(mDevice);
239         if (cachedDevice == null) {
240             cachedDevice = cachedDeviceManager.addDevice(mDevice);
241         }
242 
243         String intentName = BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY;
244 
245         if (mRequestType == BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS) {
246             int phonebookPermission = mDevice.getPhonebookAccessPermission();
247 
248             if (phonebookPermission == BluetoothDevice.ACCESS_UNKNOWN) {
249                 // Leave 'processed' as false.
250             } else if (phonebookPermission == BluetoothDevice.ACCESS_ALLOWED) {
251                 sendReplyIntentToReceiver(true);
252                 processed = true;
253             } else if (phonebookPermission == BluetoothDevice.ACCESS_REJECTED) {
254                 sendReplyIntentToReceiver(false);
255                 processed = true;
256             } else {
257                 Log.e(TAG, "Bad phonebookPermission: " + phonebookPermission);
258             }
259         } else if (mRequestType == BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS) {
260             int messagePermission = mDevice.getMessageAccessPermission();
261 
262             if (messagePermission == BluetoothDevice.ACCESS_UNKNOWN) {
263                 // Leave 'processed' as false.
264             } else if (messagePermission == BluetoothDevice.ACCESS_ALLOWED) {
265                 sendReplyIntentToReceiver(true);
266                 processed = true;
267             } else if (messagePermission == BluetoothDevice.ACCESS_REJECTED) {
268                 sendReplyIntentToReceiver(false);
269                 processed = true;
270             } else {
271                 Log.e(TAG, "Bad messagePermission: " + messagePermission);
272             }
273         } else if(mRequestType == BluetoothDevice.REQUEST_TYPE_SIM_ACCESS) {
274             int simPermission = mDevice.getSimAccessPermission();
275 
276             if (simPermission == BluetoothDevice.ACCESS_UNKNOWN) {
277                 // Leave 'processed' as false.
278             } else if (simPermission == BluetoothDevice.ACCESS_ALLOWED) {
279                 sendReplyIntentToReceiver(true);
280                 processed = true;
281             } else if (simPermission == BluetoothDevice.ACCESS_REJECTED) {
282                 sendReplyIntentToReceiver(false);
283                 processed = true;
284             } else {
285                 Log.e(TAG, "Bad simPermission: " + simPermission);
286             }
287         }
288         if (DEBUG) Log.d(TAG,"checkUserChoice(): returning " + processed);
289         return processed;
290     }
291 
sendReplyIntentToReceiver(final boolean allowed)292     private void sendReplyIntentToReceiver(final boolean allowed) {
293         Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
294 
295         intent.putExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
296                 allowed ? BluetoothDevice.CONNECTION_ACCESS_YES
297                         : BluetoothDevice.CONNECTION_ACCESS_NO);
298         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
299         intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, mRequestType);
300         mContext.sendBroadcast(intent, android.Manifest.permission.BLUETOOTH_CONNECT);
301     }
302 }
303