1/* 2 * Copyright (C) 2008 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 android.server; 18 19import android.bluetooth.BluetoothA2dp; 20import android.bluetooth.BluetoothClass; 21import android.bluetooth.BluetoothDevice; 22import android.bluetooth.BluetoothError; 23import android.bluetooth.BluetoothIntent; 24import android.bluetooth.IBluetoothDeviceCallback; 25import android.content.Context; 26import android.content.Intent; 27import android.os.Handler; 28import android.os.Message; 29import android.os.RemoteException; 30import android.util.Log; 31 32import java.util.HashMap; 33 34/** 35 * TODO: Move this to 36 * java/services/com/android/server/BluetoothEventLoop.java 37 * and make the contructor package private again. 38 * 39 * @hide 40 */ 41class BluetoothEventLoop { 42 private static final String TAG = "BluetoothEventLoop"; 43 private static final boolean DBG = false; 44 45 private int mNativeData; 46 private Thread mThread; 47 private boolean mStarted; 48 private boolean mInterrupted; 49 private final HashMap<String, Integer> mPasskeyAgentRequestData; 50 private final HashMap<String, IBluetoothDeviceCallback> mGetRemoteServiceChannelCallbacks; 51 private final BluetoothDeviceService mBluetoothService; 52 private final Context mContext; 53 54 private static final int EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 1; 55 private static final int EVENT_RESTART_BLUETOOTH = 2; 56 57 // The time (in millisecs) to delay the pairing attempt after the first 58 // auto pairing attempt fails. We use an exponential delay with 59 // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the initial value and 60 // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the max value. 61 private static final long INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 3000; 62 private static final long MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 12000; 63 64 private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; 65 private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; 66 67 private final Handler mHandler = new Handler() { 68 @Override 69 public void handleMessage(Message msg) { 70 switch (msg.what) { 71 case EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY: 72 String address = (String)msg.obj; 73 if (address != null) { 74 mBluetoothService.createBond(address); 75 return; 76 } 77 break; 78 case EVENT_RESTART_BLUETOOTH: 79 mBluetoothService.restart(); 80 break; 81 } 82 } 83 }; 84 85 static { classInitNative(); } 86 private static native void classInitNative(); 87 88 /* pacakge */ BluetoothEventLoop(Context context, BluetoothDeviceService bluetoothService) { 89 mBluetoothService = bluetoothService; 90 mContext = context; 91 mPasskeyAgentRequestData = new HashMap(); 92 mGetRemoteServiceChannelCallbacks = new HashMap(); 93 initializeNativeDataNative(); 94 } 95 private native void initializeNativeDataNative(); 96 97 protected void finalize() throws Throwable { 98 try { 99 cleanupNativeDataNative(); 100 } finally { 101 super.finalize(); 102 } 103 } 104 private native void cleanupNativeDataNative(); 105 106 /* pacakge */ HashMap<String, IBluetoothDeviceCallback> getRemoteServiceChannelCallbacks() { 107 return mGetRemoteServiceChannelCallbacks; 108 } 109 110 /* pacakge */ HashMap<String, Integer> getPasskeyAgentRequestData() { 111 return mPasskeyAgentRequestData; 112 } 113 114 private native void startEventLoopNative(); 115 private native void stopEventLoopNative(); 116 private native boolean isEventLoopRunningNative(); 117 118 /* package */ void start() { 119 120 if (!isEventLoopRunningNative()) { 121 if (DBG) log("Starting Event Loop thread"); 122 startEventLoopNative(); 123 } 124 } 125 126 public void stop() { 127 if (isEventLoopRunningNative()) { 128 if (DBG) log("Stopping Event Loop thread"); 129 stopEventLoopNative(); 130 } 131 } 132 133 public boolean isEventLoopRunning() { 134 return isEventLoopRunningNative(); 135 } 136 137 /*package*/ void onModeChanged(String bluezMode) { 138 int mode = BluetoothDeviceService.bluezStringToScanMode(bluezMode); 139 if (mode >= 0) { 140 Intent intent = new Intent(BluetoothIntent.SCAN_MODE_CHANGED_ACTION); 141 intent.putExtra(BluetoothIntent.SCAN_MODE, mode); 142 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 143 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 144 } 145 } 146 147 private void onDiscoveryStarted() { 148 mBluetoothService.setIsDiscovering(true); 149 Intent intent = new Intent(BluetoothIntent.DISCOVERY_STARTED_ACTION); 150 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 151 } 152 private void onDiscoveryCompleted() { 153 mBluetoothService.setIsDiscovering(false); 154 Intent intent = new Intent(BluetoothIntent.DISCOVERY_COMPLETED_ACTION); 155 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 156 } 157 158 private void onRemoteDeviceFound(String address, int deviceClass, short rssi) { 159 Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_FOUND_ACTION); 160 intent.putExtra(BluetoothIntent.ADDRESS, address); 161 intent.putExtra(BluetoothIntent.CLASS, deviceClass); 162 intent.putExtra(BluetoothIntent.RSSI, rssi); 163 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 164 } 165 private void onRemoteDeviceDisappeared(String address) { 166 Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_DISAPPEARED_ACTION); 167 intent.putExtra(BluetoothIntent.ADDRESS, address); 168 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 169 } 170 private void onRemoteClassUpdated(String address, int deviceClass) { 171 Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_CLASS_UPDATED_ACTION); 172 intent.putExtra(BluetoothIntent.ADDRESS, address); 173 intent.putExtra(BluetoothIntent.CLASS, deviceClass); 174 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 175 } 176 private void onRemoteDeviceConnected(String address) { 177 Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_CONNECTED_ACTION); 178 intent.putExtra(BluetoothIntent.ADDRESS, address); 179 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 180 } 181 private void onRemoteDeviceDisconnectRequested(String address) { 182 Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_DISCONNECT_REQUESTED_ACTION); 183 intent.putExtra(BluetoothIntent.ADDRESS, address); 184 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 185 } 186 private void onRemoteDeviceDisconnected(String address) { 187 Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_DISCONNECTED_ACTION); 188 intent.putExtra(BluetoothIntent.ADDRESS, address); 189 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 190 } 191 private void onRemoteNameUpdated(String address, String name) { 192 Intent intent = new Intent(BluetoothIntent.REMOTE_NAME_UPDATED_ACTION); 193 intent.putExtra(BluetoothIntent.ADDRESS, address); 194 intent.putExtra(BluetoothIntent.NAME, name); 195 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 196 } 197 private void onRemoteNameFailed(String address) { 198 Intent intent = new Intent(BluetoothIntent.REMOTE_NAME_FAILED_ACTION); 199 intent.putExtra(BluetoothIntent.ADDRESS, address); 200 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 201 } 202 private void onRemoteNameChanged(String address, String name) { 203 Intent intent = new Intent(BluetoothIntent.REMOTE_NAME_UPDATED_ACTION); 204 intent.putExtra(BluetoothIntent.ADDRESS, address); 205 intent.putExtra(BluetoothIntent.NAME, name); 206 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 207 } 208 209 private void onCreateBondingResult(String address, int result) { 210 address = address.toUpperCase(); 211 if (result == BluetoothError.SUCCESS) { 212 mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDED); 213 if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) { 214 mBluetoothService.getBondState().clearPinAttempts(address); 215 } 216 } else if (result == BluetoothDevice.UNBOND_REASON_AUTH_FAILED && 217 mBluetoothService.getBondState().getAttempt(address) == 1) { 218 mBluetoothService.getBondState().addAutoPairingFailure(address); 219 pairingAttempt(address, result); 220 } else if (result == BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN && 221 mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) { 222 pairingAttempt(address, result); 223 } else { 224 mBluetoothService.getBondState().setBondState(address, 225 BluetoothDevice.BOND_NOT_BONDED, result); 226 if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) { 227 mBluetoothService.getBondState().clearPinAttempts(address); 228 } 229 } 230 } 231 232 private void pairingAttempt(String address, int result) { 233 // This happens when our initial guess of "0000" as the pass key 234 // fails. Try to create the bond again and display the pin dialog 235 // to the user. Use back-off while posting the delayed 236 // message. The initial value is 237 // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY and the max value is 238 // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY. If the max value is 239 // reached, display an error to the user. 240 int attempt = mBluetoothService.getBondState().getAttempt(address); 241 if (attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY > 242 MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY) { 243 mBluetoothService.getBondState().clearPinAttempts(address); 244 mBluetoothService.getBondState().setBondState(address, 245 BluetoothDevice.BOND_NOT_BONDED, result); 246 return; 247 } 248 249 Message message = mHandler.obtainMessage(EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY); 250 message.obj = address; 251 boolean postResult = mHandler.sendMessageDelayed(message, 252 attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY); 253 if (!postResult) { 254 mBluetoothService.getBondState().clearPinAttempts(address); 255 mBluetoothService.getBondState().setBondState(address, 256 BluetoothDevice.BOND_NOT_BONDED, result); 257 return; 258 } 259 mBluetoothService.getBondState().attempt(address); 260 } 261 262 private void onBondingCreated(String address) { 263 mBluetoothService.getBondState().setBondState(address.toUpperCase(), 264 BluetoothDevice.BOND_BONDED); 265 } 266 267 private void onBondingRemoved(String address) { 268 mBluetoothService.getBondState().setBondState(address.toUpperCase(), 269 BluetoothDevice.BOND_NOT_BONDED, BluetoothDevice.UNBOND_REASON_REMOVED); 270 } 271 272 private void onNameChanged(String name) { 273 Intent intent = new Intent(BluetoothIntent.NAME_CHANGED_ACTION); 274 intent.putExtra(BluetoothIntent.NAME, name); 275 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 276 } 277 278 private void onPasskeyAgentRequest(String address, int nativeData) { 279 address = address.toUpperCase(); 280 mPasskeyAgentRequestData.put(address, new Integer(nativeData)); 281 282 if (mBluetoothService.getBluetoothState() == BluetoothDevice.BLUETOOTH_STATE_TURNING_OFF) { 283 // shutdown path 284 mBluetoothService.cancelPin(address); 285 return; 286 } 287 288 if (mBluetoothService.getBondState().getBondState(address) == 289 BluetoothDevice.BOND_BONDING) { 290 // we initiated the bonding 291 int btClass = mBluetoothService.getRemoteClass(address); 292 293 // try 0000 once if the device looks dumb 294 switch (BluetoothClass.Device.getDevice(btClass)) { 295 case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET: 296 case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE: 297 case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES: 298 case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO: 299 case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO: 300 case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO: 301 if (!mBluetoothService.getBondState().hasAutoPairingFailed(address) && 302 !mBluetoothService.getBondState().isAutoPairingBlacklisted(address)) { 303 mBluetoothService.getBondState().attempt(address); 304 mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes("0000")); 305 return; 306 } 307 } 308 } 309 Intent intent = new Intent(BluetoothIntent.PAIRING_REQUEST_ACTION); 310 intent.putExtra(BluetoothIntent.ADDRESS, address); 311 mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); 312 } 313 314 private void onPasskeyAgentCancel(String address) { 315 address = address.toUpperCase(); 316 mBluetoothService.cancelPin(address); 317 Intent intent = new Intent(BluetoothIntent.PAIRING_CANCEL_ACTION); 318 intent.putExtra(BluetoothIntent.ADDRESS, address); 319 mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); 320 mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_NOT_BONDED, 321 BluetoothDevice.UNBOND_REASON_AUTH_CANCELED); 322 } 323 324 private boolean onAuthAgentAuthorize(String address, String service, String uuid) { 325 boolean authorized = false; 326 if (mBluetoothService.isEnabled() && service.endsWith("service_audio")) { 327 BluetoothA2dp a2dp = new BluetoothA2dp(mContext); 328 authorized = a2dp.getSinkPriority(address) > BluetoothA2dp.PRIORITY_OFF; 329 if (authorized) { 330 Log.i(TAG, "Allowing incoming A2DP connection from " + address); 331 } else { 332 Log.i(TAG, "Rejecting incoming A2DP connection from " + address); 333 } 334 } else { 335 Log.i(TAG, "Rejecting incoming " + service + " connection from " + address); 336 } 337 return authorized; 338 } 339 340 private void onAuthAgentCancel(String address, String service, String uuid) { 341 // We immediately response to DBUS Authorize() so this should not 342 // usually happen 343 log("onAuthAgentCancel(" + address + ", " + service + ", " + uuid + ")"); 344 } 345 346 private void onGetRemoteServiceChannelResult(String address, int channel) { 347 IBluetoothDeviceCallback callback = mGetRemoteServiceChannelCallbacks.get(address); 348 if (callback != null) { 349 mGetRemoteServiceChannelCallbacks.remove(address); 350 try { 351 callback.onGetRemoteServiceChannelResult(address, channel); 352 } catch (RemoteException e) {} 353 } 354 } 355 356 private void onRestartRequired() { 357 if (mBluetoothService.isEnabled()) { 358 Log.e(TAG, "*** A serious error occured (did hcid crash?) - restarting Bluetooth ***"); 359 mHandler.sendEmptyMessage(EVENT_RESTART_BLUETOOTH); 360 } 361 } 362 363 private static void log(String msg) { 364 Log.d(TAG, msg); 365 } 366} 367