1/* 2 * Copyright (C) 2010 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.BluetoothAdapter; 20import android.bluetooth.BluetoothDevice; 21import android.bluetooth.BluetoothProfile; 22import android.bluetooth.BluetoothA2dp; 23import android.bluetooth.BluetoothHeadset; 24import android.content.BroadcastReceiver; 25import android.content.ContentResolver; 26import android.content.Context; 27import android.content.Intent; 28import android.content.IntentFilter; 29import android.content.SharedPreferences; 30import android.provider.Settings; 31import android.util.Log; 32 33import java.io.BufferedReader; 34import java.io.BufferedWriter; 35import java.io.DataInputStream; 36import java.io.File; 37import java.io.FileInputStream; 38import java.io.FileNotFoundException; 39import java.io.FileOutputStream; 40import java.io.FileWriter; 41import java.io.IOException; 42import java.io.InputStreamReader; 43import java.util.ArrayList; 44import java.util.Arrays; 45import java.util.HashMap; 46import java.util.Map; 47 48/** 49 * Local cache of bonding state. 50 * We keep our own state to track the intermediate state BONDING, which 51 * bluez does not track. 52 * All addresses must be passed in upper case. 53 */ 54class BluetoothBondState { 55 private static final String TAG = "BluetoothBondState"; 56 private static final boolean DBG = true; 57 58 private final HashMap<String, Integer> mState = new HashMap<String, Integer>(); 59 private final HashMap<String, Integer> mPinAttempt = new HashMap<String, Integer>(); 60 61 private static final String AUTO_PAIRING_BLACKLIST = 62 "/etc/bluetooth/auto_pairing.conf"; 63 private static final String DYNAMIC_AUTO_PAIRING_BLACKLIST = 64 "/data/misc/bluetooth/dynamic_auto_pairing.conf"; 65 private ArrayList<String> mAutoPairingAddressBlacklist; 66 private ArrayList<String> mAutoPairingExactNameBlacklist; 67 private ArrayList<String> mAutoPairingPartialNameBlacklist; 68 private ArrayList<String> mAutoPairingFixedPinZerosKeyboardList; 69 // Addresses added to blacklist dynamically based on usage. 70 private ArrayList<String> mAutoPairingDynamicAddressBlacklist; 71 72 // If this is an outgoing connection, store the address. 73 // There can be only 1 pending outgoing connection at a time, 74 private String mPendingOutgoingBonding; 75 76 private final Context mContext; 77 private final BluetoothService mService; 78 private final BluetoothInputProfileHandler mBluetoothInputProfileHandler; 79 private BluetoothA2dp mA2dpProxy; 80 private BluetoothHeadset mHeadsetProxy; 81 82 private ArrayList<String> mPairingRequestRcvd = new ArrayList<String>(); 83 84 BluetoothBondState(Context context, BluetoothService service) { 85 mContext = context; 86 mService = service; 87 mBluetoothInputProfileHandler = 88 BluetoothInputProfileHandler.getInstance(mContext, mService); 89 90 IntentFilter filter = new IntentFilter(); 91 filter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST); 92 mContext.registerReceiver(mReceiver, filter); 93 readAutoPairingData(); 94 } 95 96 synchronized void setPendingOutgoingBonding(String address) { 97 mPendingOutgoingBonding = address; 98 } 99 100 public synchronized String getPendingOutgoingBonding() { 101 return mPendingOutgoingBonding; 102 } 103 104 public synchronized void initBondState() { 105 getProfileProxy(); 106 loadBondState(); 107 } 108 109 private void loadBondState() { 110 if (mService.getBluetoothStateInternal() != 111 BluetoothAdapter.STATE_TURNING_ON) { 112 return; 113 } 114 String val = mService.getAdapterProperties().getProperty("Devices"); 115 if (val == null) { 116 return; 117 } 118 String[] bonds = val.split(","); 119 if (bonds == null) { 120 return; 121 } 122 mState.clear(); 123 if (DBG) Log.d(TAG, "found " + bonds.length + " bonded devices"); 124 for (String device : bonds) { 125 mState.put(mService.getAddressFromObjectPath(device).toUpperCase(), 126 BluetoothDevice.BOND_BONDED); 127 } 128 } 129 130 public synchronized void setBondState(String address, int state) { 131 setBondState(address, state, 0); 132 } 133 134 /** reason is ignored unless state == BOND_NOT_BONDED */ 135 public synchronized void setBondState(String address, int state, int reason) { 136 if (DBG) Log.d(TAG, "setBondState " + "address" + " " + state + "reason: " + reason); 137 138 int oldState = getBondState(address); 139 if (oldState == state) { 140 return; 141 } 142 143 // Check if this was an pending outgoing bonding. 144 // If yes, reset the state. 145 if (oldState == BluetoothDevice.BOND_BONDING) { 146 if (address.equals(mPendingOutgoingBonding)) { 147 mPendingOutgoingBonding = null; 148 } 149 } 150 151 if (state == BluetoothDevice.BOND_BONDED) { 152 boolean setTrust = false; 153 if (mPairingRequestRcvd.contains(address)) setTrust = true; 154 155 mService.addProfileState(address, setTrust); 156 mPairingRequestRcvd.remove(address); 157 158 } else if (state == BluetoothDevice.BOND_BONDING) { 159 if (mA2dpProxy == null || mHeadsetProxy == null) { 160 getProfileProxy(); 161 } 162 } else if (state == BluetoothDevice.BOND_NONE) { 163 mPairingRequestRcvd.remove(address); 164 } 165 166 setProfilePriorities(address, state); 167 168 if (DBG) { 169 Log.d(TAG, address + " bond state " + oldState + " -> " + state 170 + " (" + reason + ")"); 171 } 172 Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED); 173 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mService.getRemoteDevice(address)); 174 intent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, state); 175 intent.putExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, oldState); 176 if (state == BluetoothDevice.BOND_NONE) { 177 if (reason <= 0) { 178 Log.w(TAG, "setBondState() called to unbond device, but reason code is " + 179 "invalid. Overriding reason code with BOND_RESULT_REMOVED"); 180 reason = BluetoothDevice.UNBOND_REASON_REMOVED; 181 } 182 intent.putExtra(BluetoothDevice.EXTRA_REASON, reason); 183 mState.remove(address); 184 } else { 185 mState.put(address, state); 186 } 187 188 mContext.sendBroadcast(intent, BluetoothService.BLUETOOTH_PERM); 189 } 190 191 public boolean isAutoPairingBlacklisted(String address) { 192 if (mAutoPairingAddressBlacklist != null) { 193 for (String blacklistAddress : mAutoPairingAddressBlacklist) { 194 if (address.startsWith(blacklistAddress)) return true; 195 } 196 } 197 198 if (mAutoPairingDynamicAddressBlacklist != null) { 199 for (String blacklistAddress: mAutoPairingDynamicAddressBlacklist) { 200 if (address.equals(blacklistAddress)) return true; 201 } 202 } 203 204 String name = mService.getRemoteName(address); 205 if (name != null) { 206 if (mAutoPairingExactNameBlacklist != null) { 207 for (String blacklistName : mAutoPairingExactNameBlacklist) { 208 if (name.equals(blacklistName)) return true; 209 } 210 } 211 212 if (mAutoPairingPartialNameBlacklist != null) { 213 for (String blacklistName : mAutoPairingPartialNameBlacklist) { 214 if (name.startsWith(blacklistName)) return true; 215 } 216 } 217 } 218 return false; 219 } 220 221 public boolean isFixedPinZerosAutoPairKeyboard(String address) { 222 // Note: the meaning of blacklist is reversed in this case. 223 // If its in the list, we can go ahead and auto pair since 224 // by default keyboard should have a variable PIN that we don't 225 // auto pair using 0000. 226 if (mAutoPairingFixedPinZerosKeyboardList != null) { 227 for (String blacklistAddress : mAutoPairingFixedPinZerosKeyboardList) { 228 if (address.startsWith(blacklistAddress)) return true; 229 } 230 } 231 return false; 232 } 233 234 public synchronized int getBondState(String address) { 235 Integer state = mState.get(address); 236 if (state == null) { 237 return BluetoothDevice.BOND_NONE; 238 } 239 return state.intValue(); 240 } 241 242 /*package*/ synchronized String[] listInState(int state) { 243 ArrayList<String> result = new ArrayList<String>(mState.size()); 244 for (Map.Entry<String, Integer> e : mState.entrySet()) { 245 if (e.getValue().intValue() == state) { 246 result.add(e.getKey()); 247 } 248 } 249 return result.toArray(new String[result.size()]); 250 } 251 252 public synchronized void addAutoPairingFailure(String address) { 253 if (mAutoPairingDynamicAddressBlacklist == null) { 254 mAutoPairingDynamicAddressBlacklist = new ArrayList<String>(); 255 } 256 257 updateAutoPairingData(address); 258 mAutoPairingDynamicAddressBlacklist.add(address); 259 } 260 261 public synchronized boolean isAutoPairingAttemptsInProgress(String address) { 262 return getAttempt(address) != 0; 263 } 264 265 public synchronized void clearPinAttempts(String address) { 266 if (DBG) Log.d(TAG, "clearPinAttempts: " + address); 267 268 mPinAttempt.remove(address); 269 } 270 271 public synchronized boolean hasAutoPairingFailed(String address) { 272 if (mAutoPairingDynamicAddressBlacklist == null) return false; 273 274 return mAutoPairingDynamicAddressBlacklist.contains(address); 275 } 276 277 public synchronized int getAttempt(String address) { 278 Integer attempt = mPinAttempt.get(address); 279 if (attempt == null) { 280 return 0; 281 } 282 return attempt.intValue(); 283 } 284 285 public synchronized void attempt(String address) { 286 Integer attempt = mPinAttempt.get(address); 287 int newAttempt; 288 if (attempt == null) { 289 newAttempt = 1; 290 } else { 291 newAttempt = attempt.intValue() + 1; 292 } 293 if (DBG) Log.d(TAG, "attemp newAttempt: " + newAttempt); 294 295 mPinAttempt.put(address, new Integer(newAttempt)); 296 } 297 298 private void getProfileProxy() { 299 BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 300 301 if (mA2dpProxy == null) { 302 bluetoothAdapter.getProfileProxy(mContext, mProfileServiceListener, 303 BluetoothProfile.A2DP); 304 } 305 306 if (mHeadsetProxy == null) { 307 bluetoothAdapter.getProfileProxy(mContext, mProfileServiceListener, 308 BluetoothProfile.HEADSET); 309 } 310 } 311 312 private void closeProfileProxy() { 313 BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 314 315 if (mA2dpProxy != null) { 316 bluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP, mA2dpProxy); 317 } 318 319 if (mHeadsetProxy != null) { 320 bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mHeadsetProxy); 321 } 322 } 323 324 private BluetoothProfile.ServiceListener mProfileServiceListener = 325 new BluetoothProfile.ServiceListener() { 326 327 public void onServiceConnected(int profile, BluetoothProfile proxy) { 328 if (profile == BluetoothProfile.A2DP) { 329 mA2dpProxy = (BluetoothA2dp) proxy; 330 } else if (profile == BluetoothProfile.HEADSET) { 331 mHeadsetProxy = (BluetoothHeadset) proxy; 332 } 333 } 334 335 public void onServiceDisconnected(int profile) { 336 if (profile == BluetoothProfile.A2DP) { 337 mA2dpProxy = null; 338 } else if (profile == BluetoothProfile.HEADSET) { 339 mHeadsetProxy = null; 340 } 341 } 342 }; 343 344 private void copyAutoPairingData() { 345 FileInputStream in = null; 346 FileOutputStream out = null; 347 try { 348 File file = new File(DYNAMIC_AUTO_PAIRING_BLACKLIST); 349 if (file.exists()) return; 350 351 in = new FileInputStream(AUTO_PAIRING_BLACKLIST); 352 out= new FileOutputStream(DYNAMIC_AUTO_PAIRING_BLACKLIST); 353 354 byte[] buf = new byte[1024]; 355 int len; 356 while ((len = in.read(buf)) > 0) { 357 out.write(buf, 0, len); 358 } 359 } catch (FileNotFoundException e) { 360 Log.e(TAG, "FileNotFoundException: copyAutoPairingData " + e); 361 } catch (IOException e) { 362 Log.e(TAG, "IOException: copyAutoPairingData " + e); 363 } finally { 364 try { 365 if (in != null) in.close(); 366 if (out != null) out.close(); 367 } catch (IOException e) {} 368 } 369 } 370 371 synchronized public void readAutoPairingData() { 372 if (mAutoPairingAddressBlacklist != null) return; 373 copyAutoPairingData(); 374 FileInputStream fstream = null; 375 try { 376 fstream = new FileInputStream(DYNAMIC_AUTO_PAIRING_BLACKLIST); 377 DataInputStream in = new DataInputStream(fstream); 378 BufferedReader file = new BufferedReader(new InputStreamReader(in)); 379 String line; 380 while((line = file.readLine()) != null) { 381 line = line.trim(); 382 if (line.length() == 0 || line.startsWith("//")) continue; 383 String[] value = line.split("="); 384 if (value != null && value.length == 2) { 385 String[] val = value[1].split(","); 386 if (value[0].equalsIgnoreCase("AddressBlacklist")) { 387 mAutoPairingAddressBlacklist = 388 new ArrayList<String>(Arrays.asList(val)); 389 } else if (value[0].equalsIgnoreCase("ExactNameBlacklist")) { 390 mAutoPairingExactNameBlacklist = 391 new ArrayList<String>(Arrays.asList(val)); 392 } else if (value[0].equalsIgnoreCase("PartialNameBlacklist")) { 393 mAutoPairingPartialNameBlacklist = 394 new ArrayList<String>(Arrays.asList(val)); 395 } else if (value[0].equalsIgnoreCase("FixedPinZerosKeyboardBlacklist")) { 396 mAutoPairingFixedPinZerosKeyboardList = 397 new ArrayList<String>(Arrays.asList(val)); 398 } else if (value[0].equalsIgnoreCase("DynamicAddressBlacklist")) { 399 mAutoPairingDynamicAddressBlacklist = 400 new ArrayList<String>(Arrays.asList(val)); 401 } else { 402 Log.e(TAG, "Error parsing Auto pairing blacklist file"); 403 } 404 } 405 } 406 } catch (FileNotFoundException e) { 407 Log.e(TAG, "FileNotFoundException: readAutoPairingData " + e); 408 } catch (IOException e) { 409 Log.e(TAG, "IOException: readAutoPairingData " + e); 410 } finally { 411 if (fstream != null) { 412 try { 413 fstream.close(); 414 } catch (IOException e) { 415 // Ignore 416 } 417 } 418 } 419 } 420 421 // This function adds a bluetooth address to the auto pairing blacklist 422 // file. These addresses are added to DynamicAddressBlacklistSection 423 private void updateAutoPairingData(String address) { 424 BufferedWriter out = null; 425 try { 426 out = new BufferedWriter(new FileWriter(DYNAMIC_AUTO_PAIRING_BLACKLIST, true)); 427 StringBuilder str = new StringBuilder(); 428 if (mAutoPairingDynamicAddressBlacklist.size() == 0) { 429 str.append("DynamicAddressBlacklist="); 430 } 431 str.append(address); 432 str.append(","); 433 out.write(str.toString()); 434 } catch (FileNotFoundException e) { 435 Log.e(TAG, "FileNotFoundException: updateAutoPairingData " + e); 436 } catch (IOException e) { 437 Log.e(TAG, "IOException: updateAutoPairingData " + e); 438 } finally { 439 if (out != null) { 440 try { 441 out.close(); 442 } catch (IOException e) { 443 // Ignore 444 } 445 } 446 } 447 } 448 449 // Set service priority of Hid, A2DP and Headset profiles depending on 450 // the bond state change 451 private void setProfilePriorities(String address, int state) { 452 BluetoothDevice remoteDevice = mService.getRemoteDevice(address); 453 // HID is handled by BluetoothService 454 mBluetoothInputProfileHandler.setInitialInputDevicePriority(remoteDevice, state); 455 456 // Set service priority of A2DP and Headset 457 // We used to do the priority change in the 2 services after the broadcast 458 // intent reach them. But that left a small time gap that could reject 459 // incoming connection due to undefined priorities. 460 if (state == BluetoothDevice.BOND_BONDED) { 461 if (mA2dpProxy != null && 462 mA2dpProxy.getPriority(remoteDevice) == BluetoothProfile.PRIORITY_UNDEFINED) { 463 mA2dpProxy.setPriority(remoteDevice, BluetoothProfile.PRIORITY_ON); 464 } 465 466 if (mHeadsetProxy != null && 467 mHeadsetProxy.getPriority(remoteDevice) == BluetoothProfile.PRIORITY_UNDEFINED) { 468 mHeadsetProxy.setPriority(remoteDevice, BluetoothProfile.PRIORITY_ON); 469 } 470 } else if (state == BluetoothDevice.BOND_NONE) { 471 if (mA2dpProxy != null) { 472 mA2dpProxy.setPriority(remoteDevice, BluetoothProfile.PRIORITY_UNDEFINED); 473 } 474 if (mHeadsetProxy != null) { 475 mHeadsetProxy.setPriority(remoteDevice, BluetoothProfile.PRIORITY_UNDEFINED); 476 } 477 } 478 479 if (mA2dpProxy == null || mHeadsetProxy == null) { 480 Log.e(TAG, "Proxy is null:" + mA2dpProxy + ":" + mHeadsetProxy); 481 } 482 } 483 484 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 485 @Override 486 public void onReceive(Context context, Intent intent) { 487 if (intent == null) return; 488 489 String action = intent.getAction(); 490 if (action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) { 491 BluetoothDevice dev = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 492 String address = dev.getAddress(); 493 mPairingRequestRcvd.add(address); 494 } 495 } 496 }; 497} 498