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