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