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