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