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