BluetoothPbapService.java revision bceb51ca01959f3871648a6f68aa9f2779f1910b
1/* 2 * Copyright (c) 2008-2009, Motorola, Inc. 3 * 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * - Redistributions of source code must retain the above copyright notice, 10 * this list of conditions and the following disclaimer. 11 * 12 * - Redistributions in binary form must reproduce the above copyright notice, 13 * this list of conditions and the following disclaimer in the documentation 14 * and/or other materials provided with the distribution. 15 * 16 * - Neither the name of the Motorola, Inc. nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 * POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33package com.android.bluetooth.pbap; 34 35import android.bluetooth.BluetoothAdapter; 36import android.bluetooth.BluetoothDevice; 37import android.bluetooth.BluetoothProfile; 38import android.bluetooth.BluetoothSocket; 39import android.bluetooth.IBluetoothPbap; 40import android.content.BroadcastReceiver; 41import android.content.Context; 42import android.content.Intent; 43import android.content.IntentFilter; 44import android.database.ContentObserver; 45import android.database.sqlite.SQLiteException; 46import android.os.Handler; 47import android.os.HandlerThread; 48import android.os.Looper; 49import android.os.Message; 50import android.os.PowerManager; 51import android.support.annotation.VisibleForTesting; 52import android.telephony.TelephonyManager; 53import android.text.TextUtils; 54import android.util.Log; 55 56import com.android.bluetooth.IObexConnectionHandler; 57import com.android.bluetooth.ObexServerSockets; 58import com.android.bluetooth.R; 59import com.android.bluetooth.Utils; 60import com.android.bluetooth.btservice.ProfileService; 61import com.android.bluetooth.sdp.SdpManager; 62import com.android.bluetooth.util.DevicePolicyUtils; 63 64import java.util.ArrayList; 65import java.util.HashMap; 66import java.util.List; 67 68public class BluetoothPbapService extends ProfileService implements IObexConnectionHandler { 69 private static final String TAG = "BluetoothPbapService"; 70 71 /** 72 * To enable PBAP DEBUG/VERBOSE logging - run below cmd in adb shell, and 73 * restart com.android.bluetooth process. only enable DEBUG log: 74 * "setprop log.tag.BluetoothPbapService DEBUG"; enable both VERBOSE and 75 * DEBUG log: "setprop log.tag.BluetoothPbapService VERBOSE" 76 */ 77 78 public static final boolean DEBUG = true; 79 80 public static final boolean VERBOSE = true; 81 82 /** 83 * Intent indicating incoming obex authentication request which is from 84 * PCE(Carkit) 85 */ 86 static final String AUTH_CHALL_ACTION = "com.android.bluetooth.pbap.authchall"; 87 88 /** 89 * Intent indicating obex session key input complete by user which is sent 90 * from BluetoothPbapActivity 91 */ 92 static final String AUTH_RESPONSE_ACTION = "com.android.bluetooth.pbap.authresponse"; 93 94 /** 95 * Intent indicating user canceled obex authentication session key input 96 * which is sent from BluetoothPbapActivity 97 */ 98 static final String AUTH_CANCELLED_ACTION = "com.android.bluetooth.pbap.authcancelled"; 99 100 /** 101 * Intent indicating timeout for user confirmation, which is sent to 102 * BluetoothPbapActivity 103 */ 104 static final String USER_CONFIRM_TIMEOUT_ACTION = 105 "com.android.bluetooth.pbap.userconfirmtimeout"; 106 107 /** 108 * Intent Extra name indicating session key which is sent from 109 * BluetoothPbapActivity 110 */ 111 static final String EXTRA_SESSION_KEY = "com.android.bluetooth.pbap.sessionkey"; 112 static final String EXTRA_DEVICE = "com.android.bluetooth.pbap.device"; 113 static final String THIS_PACKAGE_NAME = "com.android.bluetooth"; 114 115 static final int MSG_ACQUIRE_WAKE_LOCK = 5004; 116 static final int MSG_RELEASE_WAKE_LOCK = 5005; 117 static final int MSG_STATE_MACHINE_DONE = 5006; 118 119 private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; 120 private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; 121 122 static final int START_LISTENER = 1; 123 static final int USER_TIMEOUT = 2; 124 static final int SHUTDOWN = 3; 125 static final int LOAD_CONTACTS = 4; 126 static final int CONTACTS_LOADED = 5; 127 static final int CHECK_SECONDARY_VERSION_COUNTER = 6; 128 static final int ROLLOVER_COUNTERS = 7; 129 130 static final int USER_CONFIRM_TIMEOUT_VALUE = 30000; 131 static final int RELEASE_WAKE_LOCK_DELAY = 10000; 132 133 private PowerManager.WakeLock mWakeLock; 134 135 private static String sLocalPhoneNum; 136 private static String sLocalPhoneName; 137 138 private ObexServerSockets mServerSockets = null; 139 140 private static final int SDP_PBAP_SERVER_VERSION = 0x0102; 141 private static final int SDP_PBAP_SUPPORTED_REPOSITORIES = 0x0001; 142 private static final int SDP_PBAP_SUPPORTED_FEATURES = 0x021F; 143 144 /* PBAP will use Bluetooth notification ID from 1000000 (included) to 2000000 (excluded). 145 The notification ID should be unique in Bluetooth package. */ 146 private static final int PBAP_NOTIFICATION_ID_START = 1000000; 147 private static final int PBAP_NOTIFICATION_ID_END = 2000000; 148 149 private int mSdpHandle = -1; 150 151 protected Context mContext; 152 153 private PbapHandler mSessionStatusHandler; 154 private HandlerThread mHandlerThread; 155 private final HashMap<BluetoothDevice, PbapStateMachine> mPbapStateMachineMap = new HashMap<>(); 156 private volatile int mNextNotificationId = PBAP_NOTIFICATION_ID_START; 157 158 // package and class name to which we send intent to check phone book access permission 159 private static final String ACCESS_AUTHORITY_PACKAGE = "com.android.settings"; 160 private static final String ACCESS_AUTHORITY_CLASS = 161 "com.android.settings.bluetooth.BluetoothPermissionRequest"; 162 163 private Thread mThreadLoadContacts; 164 private boolean mContactsLoaded = false; 165 166 private Thread mThreadUpdateSecVersionCounter; 167 168 private static BluetoothPbapService sBluetoothPbapService; 169 170 private class BluetoothPbapContentObserver extends ContentObserver { 171 BluetoothPbapContentObserver() { 172 super(new Handler()); 173 } 174 175 @Override 176 public void onChange(boolean selfChange) { 177 Log.d(TAG, " onChange on contact uri "); 178 if (mContactsLoaded) { 179 if (!mSessionStatusHandler.hasMessages(CHECK_SECONDARY_VERSION_COUNTER)) { 180 mSessionStatusHandler.sendMessage( 181 mSessionStatusHandler.obtainMessage(CHECK_SECONDARY_VERSION_COUNTER)); 182 } 183 } 184 } 185 } 186 187 private BluetoothPbapContentObserver mContactChangeObserver; 188 189 private void parseIntent(final Intent intent) { 190 String action = intent.getAction(); 191 if (DEBUG) { 192 Log.d(TAG, "action: " + action); 193 } 194 if (BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY.equals(action)) { 195 int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, 196 BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS); 197 if (requestType != BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS) { 198 return; 199 } 200 201 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 202 synchronized (mPbapStateMachineMap) { 203 PbapStateMachine sm = mPbapStateMachineMap.get(device); 204 if (sm == null) { 205 Log.w(TAG, "device not connected! device=" + device); 206 return; 207 } 208 mSessionStatusHandler.removeMessages(USER_TIMEOUT, sm); 209 int access = intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT, 210 BluetoothDevice.CONNECTION_ACCESS_NO); 211 boolean savePreference = intent.getBooleanExtra( 212 BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false); 213 214 if (access == BluetoothDevice.CONNECTION_ACCESS_YES) { 215 if (savePreference) { 216 device.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED); 217 if (VERBOSE) { 218 Log.v(TAG, "setPhonebookAccessPermission(ACCESS_ALLOWED)"); 219 } 220 } 221 sm.sendMessage(PbapStateMachine.AUTHORIZED); 222 } else { 223 if (savePreference) { 224 device.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED); 225 if (VERBOSE) { 226 Log.v(TAG, "setPhonebookAccessPermission(ACCESS_REJECTED)"); 227 } 228 } 229 sm.sendMessage(PbapStateMachine.REJECTED); 230 } 231 } 232 } else if (AUTH_RESPONSE_ACTION.equals(action)) { 233 String sessionKey = intent.getStringExtra(EXTRA_SESSION_KEY); 234 BluetoothDevice device = intent.getParcelableExtra(EXTRA_DEVICE); 235 synchronized (mPbapStateMachineMap) { 236 PbapStateMachine sm = mPbapStateMachineMap.get(device); 237 if (sm == null) { 238 return; 239 } 240 Message msg = sm.obtainMessage(PbapStateMachine.AUTH_KEY_INPUT, sessionKey); 241 sm.sendMessage(msg); 242 } 243 } else if (AUTH_CANCELLED_ACTION.equals(action)) { 244 BluetoothDevice device = intent.getParcelableExtra(EXTRA_DEVICE); 245 synchronized (mPbapStateMachineMap) { 246 PbapStateMachine sm = mPbapStateMachineMap.get(device); 247 if (sm == null) { 248 return; 249 } 250 sm.sendMessage(PbapStateMachine.AUTH_CANCELLED); 251 } 252 } else { 253 Log.w(TAG, "Unhandled intent action: " + action); 254 } 255 } 256 257 private BroadcastReceiver mPbapReceiver = new BroadcastReceiver() { 258 @Override 259 public void onReceive(Context context, Intent intent) { 260 parseIntent(intent); 261 } 262 }; 263 264 private void closeService() { 265 if (VERBOSE) { 266 Log.v(TAG, "Pbap Service closeService"); 267 } 268 269 BluetoothPbapUtils.savePbapParams(this); 270 271 if (mWakeLock != null) { 272 mWakeLock.release(); 273 mWakeLock = null; 274 } 275 276 cleanUpServerSocket(); 277 278 if (mSessionStatusHandler != null) { 279 mSessionStatusHandler.removeCallbacksAndMessages(null); 280 } 281 } 282 283 private void cleanUpServerSocket() { 284 // Step 1, 2: clean up active server session and connection socket 285 synchronized (mPbapStateMachineMap) { 286 for (PbapStateMachine stateMachine : mPbapStateMachineMap.values()) { 287 stateMachine.sendMessage(PbapStateMachine.DISCONNECT); 288 } 289 } 290 // Step 3: clean up SDP record 291 cleanUpSdpRecord(); 292 // Step 4: clean up existing server sockets 293 if (mServerSockets != null) { 294 mServerSockets.shutdown(false); 295 mServerSockets = null; 296 } 297 } 298 299 private void createSdpRecord() { 300 if (mSdpHandle > -1) { 301 Log.w(TAG, "createSdpRecord, SDP record already created"); 302 } 303 mSdpHandle = SdpManager.getDefaultManager() 304 .createPbapPseRecord("OBEX Phonebook Access Server", 305 mServerSockets.getRfcommChannel(), mServerSockets.getL2capPsm(), 306 SDP_PBAP_SERVER_VERSION, SDP_PBAP_SUPPORTED_REPOSITORIES, 307 SDP_PBAP_SUPPORTED_FEATURES); 308 if (DEBUG) { 309 Log.d(TAG, "created Sdp record, mSdpHandle=" + mSdpHandle); 310 } 311 } 312 313 private void cleanUpSdpRecord() { 314 if (mSdpHandle < 0) { 315 Log.w(TAG, "cleanUpSdpRecord, SDP record never created"); 316 return; 317 } 318 int sdpHandle = mSdpHandle; 319 mSdpHandle = -1; 320 SdpManager sdpManager = SdpManager.getDefaultManager(); 321 if (DEBUG) { 322 Log.d(TAG, "cleanUpSdpRecord, mSdpHandle=" + sdpHandle); 323 } 324 if (sdpManager == null) { 325 Log.e(TAG, "sdpManager is null"); 326 } else if (!sdpManager.removeSdpRecord(sdpHandle)) { 327 Log.w(TAG, "cleanUpSdpRecord, removeSdpRecord failed, sdpHandle=" + sdpHandle); 328 } 329 } 330 331 private class PbapHandler extends Handler { 332 private PbapHandler(Looper looper) { 333 super(looper); 334 } 335 336 @Override 337 public void handleMessage(Message msg) { 338 if (VERBOSE) { 339 Log.v(TAG, "Handler(): got msg=" + msg.what); 340 } 341 342 switch (msg.what) { 343 case START_LISTENER: 344 mServerSockets = ObexServerSockets.create(BluetoothPbapService.this); 345 if (mServerSockets == null) { 346 Log.w(TAG, "ObexServerSockets.create() returned null"); 347 break; 348 } 349 createSdpRecord(); 350 // fetch Pbap Params to check if significant change has happened to Database 351 BluetoothPbapUtils.fetchPbapParams(mContext); 352 break; 353 case USER_TIMEOUT: 354 Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL); 355 intent.setPackage(getString(R.string.pairing_ui_package)); 356 PbapStateMachine stateMachine = (PbapStateMachine) msg.obj; 357 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, stateMachine.getRemoteDevice()); 358 intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, 359 BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS); 360 sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); 361 stateMachine.sendMessage(PbapStateMachine.REJECTED); 362 break; 363 case MSG_ACQUIRE_WAKE_LOCK: 364 if (mWakeLock == null) { 365 PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); 366 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 367 "StartingObexPbapTransaction"); 368 mWakeLock.setReferenceCounted(false); 369 mWakeLock.acquire(); 370 Log.w(TAG, "Acquire Wake Lock"); 371 } 372 mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK); 373 mSessionStatusHandler.sendMessageDelayed( 374 mSessionStatusHandler.obtainMessage(MSG_RELEASE_WAKE_LOCK), 375 RELEASE_WAKE_LOCK_DELAY); 376 break; 377 case MSG_RELEASE_WAKE_LOCK: 378 if (mWakeLock != null) { 379 mWakeLock.release(); 380 mWakeLock = null; 381 } 382 break; 383 case SHUTDOWN: 384 closeService(); 385 break; 386 case LOAD_CONTACTS: 387 loadAllContacts(); 388 break; 389 case CONTACTS_LOADED: 390 mContactsLoaded = true; 391 break; 392 case CHECK_SECONDARY_VERSION_COUNTER: 393 updateSecondaryVersion(); 394 break; 395 case ROLLOVER_COUNTERS: 396 BluetoothPbapUtils.rolloverCounters(); 397 break; 398 case MSG_STATE_MACHINE_DONE: 399 PbapStateMachine sm = (PbapStateMachine) msg.obj; 400 BluetoothDevice remoteDevice = sm.getRemoteDevice(); 401 sm.quitNow(); 402 synchronized (mPbapStateMachineMap) { 403 mPbapStateMachineMap.remove(remoteDevice); 404 } 405 break; 406 default: 407 break; 408 } 409 } 410 } 411 412 int getConnectionState(BluetoothDevice device) { 413 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 414 if (mPbapStateMachineMap == null) { 415 return BluetoothProfile.STATE_DISCONNECTED; 416 } 417 418 synchronized (mPbapStateMachineMap) { 419 PbapStateMachine sm = mPbapStateMachineMap.get(device); 420 if (sm == null) { 421 return BluetoothProfile.STATE_DISCONNECTED; 422 } 423 return sm.getConnectionState(); 424 } 425 } 426 427 List<BluetoothDevice> getConnectedDevices() { 428 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 429 if (mPbapStateMachineMap == null) { 430 return new ArrayList<>(); 431 } 432 synchronized (mPbapStateMachineMap) { 433 return new ArrayList<>(mPbapStateMachineMap.keySet()); 434 } 435 } 436 437 List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 438 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 439 List<BluetoothDevice> devices = new ArrayList<>(); 440 if (mPbapStateMachineMap == null || states == null) { 441 return devices; 442 } 443 synchronized (mPbapStateMachineMap) { 444 for (int state : states) { 445 for (BluetoothDevice device : mPbapStateMachineMap.keySet()) { 446 if (state == mPbapStateMachineMap.get(device).getConnectionState()) { 447 devices.add(device); 448 } 449 } 450 } 451 } 452 return devices; 453 } 454 455 void disconnect(BluetoothDevice device) { 456 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); 457 synchronized (mPbapStateMachineMap) { 458 PbapStateMachine sm = mPbapStateMachineMap.get(device); 459 if (sm != null) { 460 sm.sendMessage(PbapStateMachine.DISCONNECT); 461 } 462 } 463 } 464 465 static String getLocalPhoneNum() { 466 return sLocalPhoneNum; 467 } 468 469 static String getLocalPhoneName() { 470 return sLocalPhoneName; 471 } 472 473 @Override 474 protected IProfileServiceBinder initBinder() { 475 return new PbapBinder(this); 476 } 477 478 @Override 479 protected boolean start() { 480 if (VERBOSE) { 481 Log.v(TAG, "start()"); 482 } 483 mContext = this; 484 mContactsLoaded = false; 485 mHandlerThread = new HandlerThread("PbapHandlerThread"); 486 mHandlerThread.start(); 487 mSessionStatusHandler = new PbapHandler(mHandlerThread.getLooper()); 488 IntentFilter filter = new IntentFilter(); 489 filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY); 490 filter.addAction(AUTH_RESPONSE_ACTION); 491 filter.addAction(AUTH_CANCELLED_ACTION); 492 BluetoothPbapConfig.init(this); 493 registerReceiver(mPbapReceiver, filter); 494 try { 495 mContactChangeObserver = new BluetoothPbapContentObserver(); 496 getContentResolver().registerContentObserver( 497 DevicePolicyUtils.getEnterprisePhoneUri(this), false, 498 mContactChangeObserver); 499 } catch (SQLiteException e) { 500 Log.e(TAG, "SQLite exception: " + e); 501 } catch (IllegalStateException e) { 502 Log.e(TAG, "Illegal state exception, content observer is already registered"); 503 } 504 505 TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); 506 if (tm != null) { 507 sLocalPhoneNum = tm.getLine1Number(); 508 sLocalPhoneName = tm.getLine1AlphaTag(); 509 if (TextUtils.isEmpty(sLocalPhoneName)) { 510 sLocalPhoneName = this.getString(R.string.localPhoneName); 511 } 512 } 513 514 mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(LOAD_CONTACTS)); 515 mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(START_LISTENER)); 516 setBluetoothPbapService(this); 517 return true; 518 } 519 520 @Override 521 protected boolean stop() { 522 if (VERBOSE) { 523 Log.v(TAG, "stop()"); 524 } 525 setBluetoothPbapService(null); 526 if (mSessionStatusHandler != null) { 527 mSessionStatusHandler.obtainMessage(SHUTDOWN).sendToTarget(); 528 } 529 if (mHandlerThread != null) { 530 mHandlerThread.quitSafely(); 531 } 532 mContactsLoaded = false; 533 if (mContactChangeObserver == null) { 534 Log.i(TAG, "Avoid unregister when receiver it is not registered"); 535 return true; 536 } 537 unregisterReceiver(mPbapReceiver); 538 getContentResolver().unregisterContentObserver(mContactChangeObserver); 539 mContactChangeObserver = null; 540 return true; 541 } 542 543 /** 544 * Get the current instance of {@link BluetoothPbapService} 545 * 546 * @return current instance of {@link BluetoothPbapService} 547 */ 548 @VisibleForTesting 549 public static synchronized BluetoothPbapService getBluetoothPbapService() { 550 if (sBluetoothPbapService == null) { 551 Log.w(TAG, "getBluetoothPbapService(): service is null"); 552 return null; 553 } 554 if (!sBluetoothPbapService.isAvailable()) { 555 Log.w(TAG, "getBluetoothPbapService(): service is not available"); 556 return null; 557 } 558 return sBluetoothPbapService; 559 } 560 561 private static synchronized void setBluetoothPbapService(BluetoothPbapService instance) { 562 if (DEBUG) { 563 Log.d(TAG, "setBluetoothPbapService(): set to: " + instance); 564 } 565 sBluetoothPbapService = instance; 566 } 567 568 private static class PbapBinder extends IBluetoothPbap.Stub implements IProfileServiceBinder { 569 private BluetoothPbapService mService; 570 571 private BluetoothPbapService getService() { 572 if (!Utils.checkCaller()) { 573 Log.w(TAG, "not allowed for non-active user"); 574 return null; 575 } 576 if (mService != null && mService.isAvailable()) { 577 return mService; 578 } 579 return null; 580 } 581 582 PbapBinder(BluetoothPbapService service) { 583 if (VERBOSE) { 584 Log.v(TAG, "PbapBinder()"); 585 } 586 mService = service; 587 } 588 589 @Override 590 public void cleanup() { 591 mService = null; 592 } 593 594 @Override 595 public List<BluetoothDevice> getConnectedDevices() { 596 if (DEBUG) { 597 Log.d(TAG, "getConnectedDevices"); 598 } 599 BluetoothPbapService service = getService(); 600 if (service == null) { 601 return new ArrayList<>(0); 602 } 603 return service.getConnectedDevices(); 604 } 605 606 @Override 607 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 608 if (DEBUG) { 609 Log.d(TAG, "getDevicesMatchingConnectionStates"); 610 } 611 BluetoothPbapService service = getService(); 612 if (service == null) { 613 return new ArrayList<>(0); 614 } 615 return service.getDevicesMatchingConnectionStates(states); 616 } 617 618 @Override 619 public int getConnectionState(BluetoothDevice device) { 620 if (DEBUG) { 621 Log.d(TAG, "getConnectionState: " + device); 622 } 623 BluetoothPbapService service = getService(); 624 if (service == null) { 625 return BluetoothAdapter.STATE_DISCONNECTED; 626 } 627 return service.getConnectionState(device); 628 } 629 630 @Override 631 public void disconnect(BluetoothDevice device) { 632 if (DEBUG) { 633 Log.d(TAG, "disconnect"); 634 } 635 BluetoothPbapService service = getService(); 636 if (service == null) { 637 return; 638 } 639 service.disconnect(device); 640 } 641 } 642 643 @Override 644 public boolean onConnect(BluetoothDevice remoteDevice, BluetoothSocket socket) { 645 if (remoteDevice == null || socket == null) { 646 Log.e(TAG, "onConnect(): Unexpected null. remoteDevice=" + remoteDevice 647 + " socket=" + socket); 648 return false; 649 } 650 651 PbapStateMachine sm = PbapStateMachine.make(this, mHandlerThread.getLooper(), remoteDevice, 652 socket, this, mSessionStatusHandler, mNextNotificationId); 653 mNextNotificationId++; 654 if (mNextNotificationId == PBAP_NOTIFICATION_ID_END) { 655 mNextNotificationId = PBAP_NOTIFICATION_ID_START; 656 } 657 synchronized (mPbapStateMachineMap) { 658 mPbapStateMachineMap.put(remoteDevice, sm); 659 } 660 sm.sendMessage(PbapStateMachine.REQUEST_PERMISSION); 661 return true; 662 } 663 664 /** 665 * Get the phonebook access permission for the device; if unknown, ask the user. 666 * Send the result to the state machine. 667 * @param stateMachine PbapStateMachine which sends the request 668 */ 669 @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) 670 public void checkOrGetPhonebookPermission(PbapStateMachine stateMachine) { 671 BluetoothDevice device = stateMachine.getRemoteDevice(); 672 int permission = device.getPhonebookAccessPermission(); 673 if (DEBUG) { 674 Log.d(TAG, "getPhonebookAccessPermission() = " + permission); 675 } 676 677 if (permission == BluetoothDevice.ACCESS_ALLOWED) { 678 stateMachine.sendMessage(PbapStateMachine.AUTHORIZED); 679 } else if (permission == BluetoothDevice.ACCESS_REJECTED) { 680 stateMachine.sendMessage(PbapStateMachine.REJECTED); 681 } else { // permission == BluetoothDevice.ACCESS_UNKNOWN 682 Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST); 683 intent.setClassName(BluetoothPbapService.ACCESS_AUTHORITY_PACKAGE, 684 BluetoothPbapService.ACCESS_AUTHORITY_CLASS); 685 intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, 686 BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS); 687 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 688 intent.putExtra(BluetoothDevice.EXTRA_PACKAGE_NAME, this.getPackageName()); 689 this.sendOrderedBroadcast(intent, BluetoothPbapService.BLUETOOTH_ADMIN_PERM); 690 if (VERBOSE) { 691 Log.v(TAG, "waiting for authorization for connection from: " + device); 692 } 693 /* In case car kit time out and try to use HFP for phonebook 694 * access, while UI still there waiting for user to confirm */ 695 Message msg = mSessionStatusHandler.obtainMessage(BluetoothPbapService.USER_TIMEOUT, 696 stateMachine); 697 mSessionStatusHandler.sendMessageDelayed(msg, USER_CONFIRM_TIMEOUT_VALUE); 698 /* We will continue the process when we receive 699 * BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY from Settings app. */ 700 } 701 } 702 703 /** 704 * Called when an unrecoverable error occurred in an accept thread. 705 * Close down the server socket, and restart. 706 */ 707 @Override 708 public synchronized void onAcceptFailed() { 709 Log.w(TAG, "PBAP server socket accept thread failed. Restarting the server socket"); 710 711 if (mWakeLock != null) { 712 mWakeLock.release(); 713 mWakeLock = null; 714 } 715 716 cleanUpServerSocket(); 717 718 if (mSessionStatusHandler != null) { 719 mSessionStatusHandler.removeCallbacksAndMessages(null); 720 } 721 722 synchronized (mPbapStateMachineMap) { 723 mPbapStateMachineMap.clear(); 724 } 725 726 mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(START_LISTENER)); 727 } 728 729 private void loadAllContacts() { 730 if (mThreadLoadContacts == null) { 731 Runnable r = new Runnable() { 732 @Override 733 public void run() { 734 BluetoothPbapUtils.loadAllContacts(mContext, 735 mSessionStatusHandler); 736 mThreadLoadContacts = null; 737 } 738 }; 739 mThreadLoadContacts = new Thread(r); 740 mThreadLoadContacts.start(); 741 } 742 } 743 744 private void updateSecondaryVersion() { 745 if (mThreadUpdateSecVersionCounter == null) { 746 Runnable r = new Runnable() { 747 @Override 748 public void run() { 749 BluetoothPbapUtils.updateSecondaryVersionCounter(mContext, 750 mSessionStatusHandler); 751 mThreadUpdateSecVersionCounter = null; 752 } 753 }; 754 mThreadUpdateSecVersionCounter = new Thread(r); 755 mThreadUpdateSecVersionCounter.start(); 756 } 757 } 758} 759