BluetoothPbapService.java revision 557b7d5218c907beaac2d7e28807dd4752db5f18
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.app.Notification; 36import android.app.NotificationChannel; 37import android.app.NotificationManager; 38import android.app.PendingIntent; 39import android.app.Service; 40import android.bluetooth.BluetoothAdapter; 41import android.bluetooth.BluetoothDevice; 42import android.bluetooth.BluetoothPbap; 43import android.bluetooth.BluetoothProfile; 44import android.bluetooth.BluetoothServerSocket; 45import android.bluetooth.BluetoothSocket; 46import android.bluetooth.BluetoothUuid; 47import android.bluetooth.IBluetoothPbap; 48import android.content.BroadcastReceiver; 49import android.content.Context; 50import android.content.Intent; 51import android.content.IntentFilter; 52import android.os.Handler; 53import android.os.IBinder; 54import android.os.Message; 55import android.os.PowerManager; 56import android.telephony.TelephonyManager; 57import android.text.TextUtils; 58import android.util.Log; 59 60import com.android.bluetooth.BluetoothObexTransport; 61import com.android.bluetooth.R; 62import com.android.bluetooth.Utils; 63import com.android.bluetooth.btservice.ProfileService; 64import com.android.bluetooth.btservice.ProfileService.IProfileServiceBinder; 65 66import java.io.IOException; 67 68import javax.obex.ServerSession; 69 70public class BluetoothPbapService extends ProfileService { 71 private static final String TAG = "BluetoothPbapService"; 72 73 /** 74 * To enable PBAP DEBUG/VERBOSE logging - run below cmd in adb shell, and 75 * restart com.android.bluetooth process. only enable DEBUG log: 76 * "setprop log.tag.BluetoothPbapService DEBUG"; enable both VERBOSE and 77 * DEBUG log: "setprop log.tag.BluetoothPbapService VERBOSE" 78 */ 79 80 public static final boolean DEBUG = true; 81 82 public static final boolean VERBOSE = false; 83 84 /** 85 * Intent indicating incoming obex authentication request which is from 86 * PCE(Carkit) 87 */ 88 public static final String AUTH_CHALL_ACTION = "com.android.bluetooth.pbap.authchall"; 89 90 /** 91 * Intent indicating obex session key input complete by user which is sent 92 * from BluetoothPbapActivity 93 */ 94 public static final String AUTH_RESPONSE_ACTION = "com.android.bluetooth.pbap.authresponse"; 95 96 /** 97 * Intent indicating user canceled obex authentication session key input 98 * which is sent from BluetoothPbapActivity 99 */ 100 public static final String AUTH_CANCELLED_ACTION = "com.android.bluetooth.pbap.authcancelled"; 101 102 /** 103 * Intent indicating timeout for user confirmation, which is sent to 104 * BluetoothPbapActivity 105 */ 106 public static final String USER_CONFIRM_TIMEOUT_ACTION = 107 "com.android.bluetooth.pbap.userconfirmtimeout"; 108 109 /** 110 * Intent Extra name indicating session key which is sent from 111 * BluetoothPbapActivity 112 */ 113 public static final String EXTRA_SESSION_KEY = "com.android.bluetooth.pbap.sessionkey"; 114 115 public static final String THIS_PACKAGE_NAME = "com.android.bluetooth"; 116 117 public static final int MSG_SERVERSESSION_CLOSE = 5000; 118 119 public static final int MSG_SESSION_ESTABLISHED = 5001; 120 121 public static final int MSG_SESSION_DISCONNECTED = 5002; 122 123 public static final int MSG_OBEX_AUTH_CHALL = 5003; 124 125 public static final int MSG_ACQUIRE_WAKE_LOCK = 5004; 126 127 public static final int MSG_RELEASE_WAKE_LOCK = 5005; 128 129 private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; 130 131 private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; 132 133 private static final int START_LISTENER = 1; 134 135 private static final int USER_TIMEOUT = 2; 136 137 private static final int AUTH_TIMEOUT = 3; 138 139 140 private static final int USER_CONFIRM_TIMEOUT_VALUE = 30000; 141 142 private static final int RELEASE_WAKE_LOCK_DELAY = 10000; 143 144 // Ensure not conflict with Opp notification ID 145 private static final int NOTIFICATION_ID_ACCESS = -1000001; 146 147 private static final int NOTIFICATION_ID_AUTH = -1000002; 148 149 private static final String PBAP_NOTIFICATION_CHANNEL = "pbap_notification_channel"; 150 151 private PowerManager.WakeLock mWakeLock = null; 152 153 private BluetoothAdapter mAdapter; 154 155 private SocketAcceptThread mAcceptThread = null; 156 157 private BluetoothPbapAuthenticator mAuth = null; 158 159 private BluetoothPbapObexServer mPbapServer; 160 161 private ServerSession mServerSession = null; 162 163 private BluetoothServerSocket mServerSocket = null; 164 165 private BluetoothSocket mConnSocket = null; 166 167 private BluetoothDevice mRemoteDevice = null; 168 169 private static String sLocalPhoneNum = null; 170 171 private static String sLocalPhoneName = null; 172 173 private static String sRemoteDeviceName = null; 174 175 private boolean mHasStarted = false; 176 177 private volatile boolean mInterrupted; 178 179 private int mState; 180 181 private int mStartId = -1; 182 183 private boolean mIsWaitingAuthorization = false; 184 private boolean mIsRegistered = false; 185 186 public BluetoothPbapService() { 187 mState = BluetoothPbap.STATE_DISCONNECTED; 188 } 189 190 // process the intent from receiver 191 private void parseIntent(final Intent intent) { 192 String action = intent.getAction(); 193 if (action == null) return; // Nothing to do 194 if (VERBOSE) Log.v(TAG, "action: " + action); 195 196 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); 197 if (VERBOSE) Log.v(TAG, "state: " + state); 198 199 if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { 200 if (state == BluetoothAdapter.STATE_TURNING_OFF) { 201 // Send any pending timeout now, as this service will be destroyed. 202 if (mSessionStatusHandler.hasMessages(USER_TIMEOUT)) { 203 mSessionStatusHandler.removeMessages(USER_TIMEOUT); 204 mSessionStatusHandler.obtainMessage(USER_TIMEOUT).sendToTarget(); 205 } 206 // Release all resources 207 closeService(); 208 return; 209 } else if (state == BluetoothAdapter.STATE_ON) { 210 // start RFCOMM listener 211 mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(START_LISTENER)); 212 } 213 } 214 215 if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED) && mIsWaitingAuthorization) { 216 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 217 218 if (mRemoteDevice == null) return; 219 if (DEBUG) Log.d(TAG,"ACL disconnected for "+ device); 220 if (mRemoteDevice.equals(device)) { 221 mSessionStatusHandler.removeMessages(USER_TIMEOUT); 222 mSessionStatusHandler.obtainMessage(USER_TIMEOUT).sendToTarget(); 223 } 224 return; 225 } 226 227 if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) { 228 int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, 229 BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS); 230 231 if ((!mIsWaitingAuthorization) 232 || (requestType != BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS)) { 233 // this reply is not for us 234 return; 235 } 236 237 mSessionStatusHandler.removeMessages(USER_TIMEOUT); 238 mIsWaitingAuthorization = false; 239 240 if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT, 241 BluetoothDevice.CONNECTION_ACCESS_NO) 242 == BluetoothDevice.CONNECTION_ACCESS_YES) { 243 if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) { 244 boolean result = mRemoteDevice.setPhonebookAccessPermission( 245 BluetoothDevice.ACCESS_ALLOWED); 246 if (VERBOSE) { 247 Log.v(TAG, "setPhonebookAccessPermission(ACCESS_ALLOWED)=" + result); 248 } 249 } 250 try { 251 if (mConnSocket != null) { 252 startObexServerSession(); 253 } else { 254 stopObexServerSession(); 255 } 256 } catch (IOException ex) { 257 Log.e(TAG, "Caught the error: " + ex.toString()); 258 } 259 } else { 260 if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) { 261 boolean result = mRemoteDevice.setPhonebookAccessPermission( 262 BluetoothDevice.ACCESS_REJECTED); 263 if (VERBOSE) { 264 Log.v(TAG, "setPhonebookAccessPermission(ACCESS_REJECTED)=" + result); 265 } 266 } 267 stopObexServerSession(); 268 } 269 return; 270 } 271 272 if (action.equals(AUTH_RESPONSE_ACTION)) { 273 String sessionkey = intent.getStringExtra(EXTRA_SESSION_KEY); 274 notifyAuthKeyInput(sessionkey); 275 } else if (action.equals(AUTH_CANCELLED_ACTION)) { 276 notifyAuthCancelled(); 277 } else { 278 Log.w(TAG, "Unrecognized intent!"); 279 return; 280 } 281 282 mSessionStatusHandler.removeMessages(USER_TIMEOUT); 283 } 284 285 private BroadcastReceiver mPbapReceiver = new BroadcastReceiver() { 286 @Override 287 public void onReceive(Context context, Intent intent) { 288 parseIntent(intent); 289 } 290 }; 291 292 private void startRfcommSocketListener() { 293 if (VERBOSE) Log.v(TAG, "Pbap Service startRfcommSocketListener"); 294 295 if (mAcceptThread == null) { 296 mAcceptThread = new SocketAcceptThread(); 297 mAcceptThread.setName("BluetoothPbapAcceptThread"); 298 mAcceptThread.start(); 299 } 300 } 301 302 private final boolean initSocket() { 303 if (VERBOSE) Log.v(TAG, "Pbap Service initSocket"); 304 305 boolean initSocketOK = false; 306 final int CREATE_RETRY_TIME = 10; 307 308 // It's possible that create will fail in some cases. retry for 10 times 309 for (int i = 0; i < CREATE_RETRY_TIME && !mInterrupted; i++) { 310 initSocketOK = true; 311 try { 312 // It is mandatory for PSE to support initiation of bonding and 313 // encryption. 314 mServerSocket = mAdapter.listenUsingEncryptedRfcommWithServiceRecord 315 ("OBEX Phonebook Access Server", BluetoothUuid.PBAP_PSE.getUuid()); 316 317 } catch (IOException e) { 318 Log.e(TAG, "Error create RfcommServerSocket " + e.toString()); 319 initSocketOK = false; 320 } 321 if (!initSocketOK) { 322 // Need to break out of this loop if BT is being turned off. 323 if (mAdapter == null) break; 324 int state = mAdapter.getState(); 325 if ((state != BluetoothAdapter.STATE_TURNING_ON) && 326 (state != BluetoothAdapter.STATE_ON)) { 327 Log.w(TAG, "initServerSocket failed as BT is (being) turned off"); 328 break; 329 } 330 try { 331 if (VERBOSE) Log.v(TAG, "wait 300 ms"); 332 Thread.sleep(300); 333 } catch (InterruptedException e) { 334 Log.e(TAG, "socketAcceptThread thread was interrupted (3)"); 335 break; 336 } 337 } else { 338 break; 339 } 340 } 341 342 if (mInterrupted) { 343 initSocketOK = false; 344 // close server socket to avoid resource leakage 345 closeServerSocket(); 346 } 347 348 if (initSocketOK) { 349 if (VERBOSE) Log.v(TAG, "Succeed to create listening socket "); 350 351 } else { 352 Log.e(TAG, "Error to create listening socket after " + CREATE_RETRY_TIME + " try"); 353 } 354 return initSocketOK; 355 } 356 357 private final synchronized void closeServerSocket() { 358 // exit SocketAcceptThread early 359 if (mServerSocket != null) { 360 try { 361 // this will cause mServerSocket.accept() return early with IOException 362 mServerSocket.close(); 363 mServerSocket = null; 364 } catch (IOException ex) { 365 Log.e(TAG, "Close Server Socket error: " + ex); 366 } 367 } 368 } 369 370 private final synchronized void closeConnectionSocket() { 371 if (mConnSocket != null) { 372 try { 373 mConnSocket.close(); 374 mConnSocket = null; 375 } catch (IOException e) { 376 Log.e(TAG, "Close Connection Socket error: " + e.toString()); 377 } 378 } 379 } 380 381 private final void closeService() { 382 if (VERBOSE) Log.v(TAG, "Pbap Service closeService in"); 383 384 // exit initSocket early 385 mInterrupted = true; 386 closeServerSocket(); 387 388 if (mAcceptThread != null) { 389 try { 390 mAcceptThread.shutdown(); 391 mAcceptThread.join(); 392 mAcceptThread = null; 393 } catch (InterruptedException ex) { 394 Log.w(TAG, "mAcceptThread close error" + ex); 395 } 396 } 397 398 if (mWakeLock != null) { 399 mWakeLock.release(); 400 mWakeLock = null; 401 } 402 403 if (mServerSession != null) { 404 mServerSession.close(); 405 mServerSession = null; 406 } 407 408 closeConnectionSocket(); 409 410 mHasStarted = false; 411 if (mStartId != -1 && stopSelfResult(mStartId)) { 412 if (VERBOSE) Log.v(TAG, "successfully stopped pbap service"); 413 mStartId = -1; 414 } 415 if (VERBOSE) Log.v(TAG, "Pbap Service closeService out"); 416 } 417 418 private final void startObexServerSession() throws IOException { 419 if (VERBOSE) Log.v(TAG, "Pbap Service startObexServerSession"); 420 421 // acquire the wakeLock before start Obex transaction thread 422 if (mWakeLock == null) { 423 PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE); 424 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 425 "StartingObexPbapTransaction"); 426 mWakeLock.setReferenceCounted(false); 427 mWakeLock.acquire(); 428 } 429 TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE); 430 if (tm != null) { 431 sLocalPhoneNum = tm.getLine1Number(); 432 sLocalPhoneName = tm.getLine1AlphaTag(); 433 if (TextUtils.isEmpty(sLocalPhoneName)) { 434 sLocalPhoneName = this.getString(R.string.localPhoneName); 435 } 436 } 437 438 mPbapServer = new BluetoothPbapObexServer(mSessionStatusHandler, this); 439 synchronized (this) { 440 mAuth = new BluetoothPbapAuthenticator(mSessionStatusHandler); 441 mAuth.setChallenged(false); 442 mAuth.setCancelled(false); 443 } 444 BluetoothObexTransport transport = new BluetoothObexTransport(mConnSocket); 445 mServerSession = new ServerSession(transport, mPbapServer, mAuth); 446 setState(BluetoothPbap.STATE_CONNECTED); 447 448 mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK); 449 mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler 450 .obtainMessage(MSG_RELEASE_WAKE_LOCK), RELEASE_WAKE_LOCK_DELAY); 451 452 if (VERBOSE) { 453 Log.v(TAG, "startObexServerSession() success!"); 454 } 455 } 456 457 private void stopObexServerSession() { 458 if (VERBOSE) Log.v(TAG, "Pbap Service stopObexServerSession"); 459 460 mSessionStatusHandler.removeMessages(MSG_ACQUIRE_WAKE_LOCK); 461 mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK); 462 // Release the wake lock if obex transaction is over 463 if (mWakeLock != null) { 464 mWakeLock.release(); 465 mWakeLock = null; 466 } 467 468 if (mServerSession != null) { 469 mServerSession.close(); 470 mServerSession = null; 471 } 472 473 mAcceptThread = null; 474 475 closeConnectionSocket(); 476 477 // Last obex transaction is finished, we start to listen for incoming 478 // connection again 479 if (mAdapter.isEnabled()) { 480 startRfcommSocketListener(); 481 } 482 setState(BluetoothPbap.STATE_DISCONNECTED); 483 } 484 485 private void notifyAuthKeyInput(final String key) { 486 synchronized (mAuth) { 487 if (key != null) { 488 mAuth.setSessionKey(key); 489 } 490 mAuth.setChallenged(true); 491 mAuth.notify(); 492 } 493 } 494 495 private void notifyAuthCancelled() { 496 synchronized (mAuth) { 497 mAuth.setCancelled(true); 498 mAuth.notify(); 499 } 500 } 501 502 /** 503 * A thread that runs in the background waiting for remote rfcomm 504 * connect.Once a remote socket connected, this thread shall be 505 * shutdown.When the remote disconnect,this thread shall run again waiting 506 * for next request. 507 */ 508 private class SocketAcceptThread extends Thread { 509 510 private boolean stopped = false; 511 512 @Override 513 public void run() { 514 BluetoothServerSocket serverSocket; 515 if (mServerSocket == null) { 516 if (!initSocket()) { 517 return; 518 } 519 } 520 521 while (!stopped) { 522 try { 523 if (VERBOSE) Log.v(TAG, "Accepting socket connection..."); 524 serverSocket = mServerSocket; 525 if (serverSocket == null) { 526 Log.w(TAG, "mServerSocket is null"); 527 break; 528 } 529 mConnSocket = serverSocket.accept(); 530 if (VERBOSE) Log.v(TAG, "Accepted socket connection..."); 531 532 synchronized (BluetoothPbapService.this) { 533 if (mConnSocket == null) { 534 Log.w(TAG, "mConnSocket is null"); 535 break; 536 } 537 mRemoteDevice = mConnSocket.getRemoteDevice(); 538 } 539 if (mRemoteDevice == null) { 540 Log.i(TAG, "getRemoteDevice() = null"); 541 break; 542 } 543 sRemoteDeviceName = mRemoteDevice.getName(); 544 // In case getRemoteName failed and return null 545 if (TextUtils.isEmpty(sRemoteDeviceName)) { 546 sRemoteDeviceName = getString(R.string.defaultname); 547 } 548 int permission = mRemoteDevice.getPhonebookAccessPermission(); 549 if (VERBOSE) Log.v(TAG, "getPhonebookAccessPermission() = " + permission); 550 551 if (permission == BluetoothDevice.ACCESS_ALLOWED) { 552 try { 553 if (VERBOSE) { 554 Log.v(TAG, "incoming connection accepted from: " + sRemoteDeviceName 555 + " automatically as already allowed device"); 556 } 557 startObexServerSession(); 558 } catch (IOException ex) { 559 Log.e(TAG, "Caught exception starting obex server session" 560 + ex.toString()); 561 } 562 } else if (permission == BluetoothDevice.ACCESS_REJECTED) { 563 if (VERBOSE) { 564 Log.v(TAG, "incoming connection rejected from: " + sRemoteDeviceName 565 + " automatically as already rejected device"); 566 } 567 stopObexServerSession(); 568 } else { // permission == BluetoothDevice.ACCESS_UNKNOWN 569 // Send an Intent to Settings app to ask user preference. 570 Intent intent = 571 new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST); 572 intent.setPackage(getString(R.string.pairing_ui_package)); 573 intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, 574 BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS); 575 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice); 576 intent.putExtra(BluetoothDevice.EXTRA_PACKAGE_NAME, getPackageName()); 577 intent.putExtra(BluetoothDevice.EXTRA_CLASS_NAME, getName()); 578 579 mIsWaitingAuthorization = true; 580 sendOrderedBroadcast(intent, BLUETOOTH_ADMIN_PERM); 581 582 if (VERBOSE) Log.v(TAG, "waiting for authorization for connection from: " 583 + sRemoteDeviceName); 584 585 // In case car kit time out and try to use HFP for 586 // phonebook 587 // access, while UI still there waiting for user to 588 // confirm 589 mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler 590 .obtainMessage(USER_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE); 591 // We will continue the process when we receive 592 // BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY from Settings app. 593 } 594 stopped = true; // job done ,close this thread; 595 } catch (IOException ex) { 596 stopped=true; 597 /* 598 if (stopped) { 599 break; 600 } 601 */ 602 if (VERBOSE) Log.v(TAG, "Accept exception: " + ex.toString()); 603 } 604 } 605 } 606 607 void shutdown() { 608 stopped = true; 609 interrupt(); 610 } 611 } 612 613 private final Handler mSessionStatusHandler = new Handler() { 614 @Override 615 public void handleMessage(Message msg) { 616 if (VERBOSE) Log.v(TAG, "Handler(): got msg=" + msg.what); 617 618 switch (msg.what) { 619 case START_LISTENER: 620 if (mAdapter.isEnabled()) { 621 startRfcommSocketListener(); 622 } 623 break; 624 case USER_TIMEOUT: 625 Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL); 626 intent.setPackage(getString(R.string.pairing_ui_package)); 627 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice); 628 intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, 629 BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS); 630 sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); 631 mIsWaitingAuthorization = false; 632 stopObexServerSession(); 633 break; 634 case AUTH_TIMEOUT: 635 Intent i = new Intent(USER_CONFIRM_TIMEOUT_ACTION); 636 sendBroadcast(i); 637 removePbapNotification(NOTIFICATION_ID_AUTH); 638 notifyAuthCancelled(); 639 break; 640 case MSG_SERVERSESSION_CLOSE: 641 stopObexServerSession(); 642 break; 643 case MSG_SESSION_ESTABLISHED: 644 break; 645 case MSG_SESSION_DISCONNECTED: 646 // case MSG_SERVERSESSION_CLOSE will handle ,so just skip 647 break; 648 case MSG_OBEX_AUTH_CHALL: 649 createPbapNotification(AUTH_CHALL_ACTION); 650 mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler 651 .obtainMessage(AUTH_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE); 652 break; 653 case MSG_ACQUIRE_WAKE_LOCK: 654 if (mWakeLock == null) { 655 PowerManager pm = (PowerManager)getSystemService( 656 Context.POWER_SERVICE); 657 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 658 "StartingObexPbapTransaction"); 659 mWakeLock.setReferenceCounted(false); 660 mWakeLock.acquire(); 661 Log.w(TAG, "Acquire Wake Lock"); 662 } 663 mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK); 664 mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler 665 .obtainMessage(MSG_RELEASE_WAKE_LOCK), RELEASE_WAKE_LOCK_DELAY); 666 break; 667 case MSG_RELEASE_WAKE_LOCK: 668 if (mWakeLock != null) { 669 mWakeLock.release(); 670 mWakeLock = null; 671 Log.w(TAG, "Release Wake Lock"); 672 } 673 break; 674 default: 675 break; 676 } 677 } 678 }; 679 680 private void setState(int state) { 681 setState(state, BluetoothPbap.RESULT_SUCCESS); 682 } 683 684 private synchronized void setState(int state, int result) { 685 if (state != mState) { 686 if (DEBUG) Log.d(TAG, "Pbap state " + mState + " -> " + state + ", result = " 687 + result); 688 int prevState = mState; 689 mState = state; 690 Intent intent = new Intent(BluetoothPbap.PBAP_STATE_CHANGED_ACTION); 691 intent.putExtra(BluetoothPbap.PBAP_PREVIOUS_STATE, prevState); 692 intent.putExtra(BluetoothPbap.PBAP_STATE, mState); 693 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice); 694 sendBroadcast(intent, BLUETOOTH_PERM); 695 } 696 } 697 698 protected int getState() { 699 return mState; 700 } 701 702 protected BluetoothDevice getRemoteDevice() { 703 return mRemoteDevice; 704 } 705 706 private void createPbapNotification(String action) { 707 708 NotificationManager nm = (NotificationManager) 709 getSystemService(Context.NOTIFICATION_SERVICE); 710 NotificationChannel notificationChannel = new NotificationChannel(PBAP_NOTIFICATION_CHANNEL, 711 getString(R.string.pbap_notification_group), NotificationManager.IMPORTANCE_HIGH); 712 nm.createNotificationChannel(notificationChannel); 713 714 // Create an intent triggered by clicking on the status icon. 715 Intent clickIntent = new Intent(); 716 clickIntent.setClass(this, BluetoothPbapActivity.class); 717 clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 718 clickIntent.setAction(action); 719 720 // Create an intent triggered by clicking on the 721 // "Clear All Notifications" button 722 Intent deleteIntent = new Intent(); 723 deleteIntent.setClass(this, BluetoothPbapService.class); 724 deleteIntent.setAction(AUTH_CANCELLED_ACTION); 725 726 String name = getRemoteDeviceName(); 727 728 if (action.equals(AUTH_CHALL_ACTION)) { 729 Notification notification = 730 new Notification.Builder(this, PBAP_NOTIFICATION_CHANNEL) 731 .setWhen(System.currentTimeMillis()) 732 .setContentTitle(getString(R.string.auth_notif_title)) 733 .setContentText(getString(R.string.auth_notif_message, name)) 734 .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth) 735 .setTicker(getString(R.string.auth_notif_ticker)) 736 .setColor(getResources().getColor( 737 com.android.internal.R.color.system_notification_accent_color, 738 this.getTheme())) 739 .setFlag(Notification.FLAG_AUTO_CANCEL, true) 740 .setFlag(Notification.FLAG_ONLY_ALERT_ONCE, true) 741 .setDefaults(Notification.DEFAULT_SOUND) 742 .setContentIntent(PendingIntent.getActivity(this, 0, clickIntent, 0)) 743 .setDeleteIntent(PendingIntent.getBroadcast(this, 0, deleteIntent, 0)) 744 .build(); 745 nm.notify(NOTIFICATION_ID_AUTH, notification); 746 } 747 } 748 749 private void removePbapNotification(int id) { 750 NotificationManager nm = (NotificationManager) 751 getSystemService(Context.NOTIFICATION_SERVICE); 752 nm.cancel(id); 753 } 754 755 public static String getLocalPhoneNum() { 756 return sLocalPhoneNum; 757 } 758 759 public static String getLocalPhoneName() { 760 return sLocalPhoneName; 761 } 762 763 public static String getRemoteDeviceName() { 764 return sRemoteDeviceName; 765 } 766 767 @Override 768 protected IProfileServiceBinder initBinder() { 769 return new PbapBinder(this); 770 } 771 772 @Override 773 protected boolean start() { 774 Log.v(TAG, "start()"); 775 IntentFilter filter = new IntentFilter(); 776 filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY); 777 filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); 778 filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); 779 filter.addAction(AUTH_RESPONSE_ACTION); 780 filter.addAction(AUTH_CANCELLED_ACTION); 781 782 try { 783 registerReceiver(mPbapReceiver, filter); 784 mIsRegistered = true; 785 } catch (Exception e) { 786 Log.w(TAG, "Unable to register pbap receiver", e); 787 } 788 mInterrupted = false; 789 BluetoothPbapConfig.init(this); 790 mAdapter = BluetoothAdapter.getDefaultAdapter(); 791 return true; 792 } 793 794 @Override 795 protected boolean stop() { 796 Log.v(TAG, "stop()"); 797 if (!mIsRegistered) { 798 Log.i(TAG, "Avoid unregister when receiver it is not registered"); 799 return true; 800 } 801 try { 802 mIsRegistered = false; 803 unregisterReceiver(mPbapReceiver); 804 } catch (Exception e) { 805 Log.w(TAG, "Unable to unregister pbap receiver", e); 806 } 807 setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED); 808 closeService(); 809 if (mSessionStatusHandler != null) { 810 mSessionStatusHandler.removeCallbacksAndMessages(null); 811 } 812 return true; 813 } 814 815 protected void disconnect() { 816 synchronized (this) { 817 if (mState == BluetoothPbap.STATE_CONNECTED) { 818 if (mServerSession != null) { 819 mServerSession.close(); 820 mServerSession = null; 821 } 822 823 closeConnectionSocket(); 824 825 setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED); 826 } 827 } 828 } 829 830 // Has to be a static class or a memory leak can occur. 831 private static class PbapBinder extends IBluetoothPbap.Stub implements IProfileServiceBinder { 832 private BluetoothPbapService mService; 833 834 private BluetoothPbapService getService(String perm) { 835 if (!Utils.checkCaller()) { 836 Log.w(TAG, "not allowed for non-active user"); 837 return null; 838 } 839 if (mService != null && mService.isAvailable()) { 840 mService.enforceCallingOrSelfPermission(perm, "Need " + perm + " permission"); 841 return mService; 842 } 843 return null; 844 } 845 846 PbapBinder(BluetoothPbapService service) { 847 Log.v(TAG, "PbapBinder()"); 848 mService = service; 849 } 850 851 public boolean cleanup() { 852 mService = null; 853 return true; 854 } 855 856 public int getState() { 857 if (DEBUG) Log.d(TAG, "getState = " + mService.getState()); 858 BluetoothPbapService service = getService(BLUETOOTH_PERM); 859 if (service == null) return BluetoothPbap.STATE_DISCONNECTED; 860 861 return service.getState(); 862 } 863 864 public BluetoothDevice getClient() { 865 if (DEBUG) Log.d(TAG, "getClient = " + mService.getRemoteDevice()); 866 BluetoothPbapService service = getService(BLUETOOTH_PERM); 867 if (service == null) return null; 868 return service.getRemoteDevice(); 869 } 870 871 public boolean isConnected(BluetoothDevice device) { 872 if (DEBUG) Log.d(TAG, "isConnected " + device); 873 BluetoothPbapService service = getService(BLUETOOTH_PERM); 874 if (service == null) return false; 875 return service.getState() == BluetoothPbap.STATE_CONNECTED 876 && service.getRemoteDevice().equals(device); 877 } 878 879 public boolean connect(BluetoothDevice device) { 880 BluetoothPbapService service = getService(BLUETOOTH_ADMIN_PERM); 881 return false; 882 } 883 884 public void disconnect() { 885 if (DEBUG) Log.d(TAG, "disconnect"); 886 BluetoothPbapService service = getService(BLUETOOTH_ADMIN_PERM); 887 if (service == null) return; 888 service.disconnect(); 889 } 890 } 891} 892