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