BluetoothPbapService.java revision 70be005a18a35ec5fcb46152f0dfbe82156efa3a
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.IBluetooth; 46import android.bluetooth.IBluetoothPbap; 47import android.bluetooth.BluetoothUuid; 48import android.content.Context; 49import android.content.Intent; 50import android.os.Handler; 51import android.os.IBinder; 52import android.os.Message; 53import android.os.PowerManager; 54import android.os.RemoteException; 55import android.os.ServiceManager; 56import android.telephony.TelephonyManager; 57import android.text.TextUtils; 58import android.util.Log; 59import com.android.bluetooth.Utils; 60 61 62import com.android.bluetooth.R; 63import com.android.bluetooth.btservice.AdapterService; 64 65import java.io.IOException; 66 67import javax.obex.ServerSession; 68 69public class BluetoothPbapService extends Service { 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 private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; 125 126 private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; 127 128 private static final int START_LISTENER = 1; 129 130 private static final int USER_TIMEOUT = 2; 131 132 private static final int AUTH_TIMEOUT = 3; 133 134 135 private static final int USER_CONFIRM_TIMEOUT_VALUE = 30000; 136 137 138 // Ensure not conflict with Opp notification ID 139 private static final int NOTIFICATION_ID_ACCESS = -1000001; 140 141 private static final int NOTIFICATION_ID_AUTH = -1000002; 142 143 private PowerManager.WakeLock mWakeLock = null; 144 145 private BluetoothAdapter mAdapter; 146 147 private SocketAcceptThread mAcceptThread = null; 148 149 private BluetoothPbapAuthenticator mAuth = null; 150 151 private BluetoothPbapObexServer mPbapServer; 152 153 private ServerSession mServerSession = null; 154 155 private BluetoothServerSocket mServerSocket = null; 156 157 private BluetoothSocket mConnSocket = null; 158 159 private BluetoothDevice mRemoteDevice = null; 160 161 private static String sLocalPhoneNum = null; 162 163 private static String sLocalPhoneName = null; 164 165 private static String sRemoteDeviceName = null; 166 167 private boolean mHasStarted = false; 168 169 private volatile boolean mInterrupted; 170 171 private int mState; 172 173 private int mStartId = -1; 174 175 //private IBluetooth mBluetoothService; 176 177 private boolean isWaitingAuthorization = false; 178 179 // package and class name to which we send intent to check phone book access permission 180 private static final String ACCESS_AUTHORITY_PACKAGE = "com.android.settings"; 181 private static final String ACCESS_AUTHORITY_CLASS = 182 "com.android.settings.bluetooth.BluetoothPermissionRequest"; 183 184 public BluetoothPbapService() { 185 mState = BluetoothPbap.STATE_DISCONNECTED; 186 } 187 188 @Override 189 public void onCreate() { 190 super.onCreate(); 191 if (VERBOSE) Log.v(TAG, "Pbap Service onCreate"); 192 193 mInterrupted = false; 194 mAdapter = BluetoothAdapter.getDefaultAdapter(); 195 196 if (!mHasStarted) { 197 mHasStarted = true; 198 if (VERBOSE) Log.v(TAG, "Starting PBAP service"); 199 BluetoothPbapConfig.init(this); 200 int state = mAdapter.getState(); 201 if (state == BluetoothAdapter.STATE_ON) { 202 mSessionStatusHandler.sendMessage(mSessionStatusHandler 203 .obtainMessage(START_LISTENER)); 204 } 205 } 206 } 207 208 @Override 209 public int onStartCommand(Intent intent, int flags, int startId) { 210 //int retCode = super.onStartCommand(intent, flags, startId); 211 //if (retCode == START_STICKY) { 212 mStartId = startId; 213 if (mAdapter == null) { 214 Log.w(TAG, "Stopping BluetoothPbapService: " 215 + "device does not have BT or device is not ready"); 216 // Release all resources 217 closeService(); 218 } else { 219 // No need to handle the null intent case, because we have 220 // all restart work done in onCreate() 221 if (intent != null) { 222 parseIntent(intent); 223 } 224 } 225 //} 226 return START_NOT_STICKY; 227 } 228 229 // process the intent from receiver 230 private void parseIntent(final Intent intent) { 231 String action = intent.getStringExtra("action"); 232 if (VERBOSE) Log.v(TAG, "action: " + action); 233 234 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); 235 if (VERBOSE) Log.v(TAG, "state: " + state); 236 237 boolean removeTimeoutMsg = true; 238 if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { 239 if (state == BluetoothAdapter.STATE_TURNING_OFF) { 240 // Send any pending timeout now, as this service will be destroyed. 241 if (mSessionStatusHandler.hasMessages(USER_TIMEOUT)) { 242 Intent timeoutIntent = 243 new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL); 244 timeoutIntent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS); 245 timeoutIntent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, 246 BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS); 247 sendBroadcast(timeoutIntent, BLUETOOTH_ADMIN_PERM); 248 } 249 // Release all resources 250 closeService(); 251 } else { 252 removeTimeoutMsg = false; 253 } 254 } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) { 255 int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, 256 BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS); 257 258 if ((!isWaitingAuthorization) || 259 (requestType != BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS)) { 260 // this reply is not for us 261 return; 262 } 263 264 isWaitingAuthorization = false; 265 266 if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT, 267 BluetoothDevice.CONNECTION_ACCESS_NO) == 268 BluetoothDevice.CONNECTION_ACCESS_YES) { 269 270 if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) { 271 boolean result = mRemoteDevice.setTrust(true); 272 if (VERBOSE) Log.v(TAG, "setTrust() result=" + result); 273 } 274 try { 275 if (mConnSocket != null) { 276 startObexServerSession(); 277 } else { 278 stopObexServerSession(); 279 } 280 } catch (IOException ex) { 281 Log.e(TAG, "Caught the error: " + ex.toString()); 282 } 283 } else { 284 stopObexServerSession(); 285 } 286 } else if (action.equals(AUTH_RESPONSE_ACTION)) { 287 String sessionkey = intent.getStringExtra(EXTRA_SESSION_KEY); 288 notifyAuthKeyInput(sessionkey); 289 } else if (action.equals(AUTH_CANCELLED_ACTION)) { 290 notifyAuthCancelled(); 291 } else { 292 removeTimeoutMsg = false; 293 } 294 295 if (removeTimeoutMsg) { 296 mSessionStatusHandler.removeMessages(USER_TIMEOUT); 297 } 298 } 299 300 @Override 301 public void onDestroy() { 302 if (VERBOSE) Log.v(TAG, "Pbap Service onDestroy"); 303 304 super.onDestroy(); 305 setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED); 306 closeService(); 307 if(mSessionStatusHandler != null) { 308 mSessionStatusHandler.removeCallbacksAndMessages(null); 309 } 310 } 311 312 @Override 313 public IBinder onBind(Intent intent) { 314 if (VERBOSE) Log.v(TAG, "Pbap Service onBind"); 315 return mBinder; 316 } 317 318 private void startRfcommSocketListener() { 319 if (VERBOSE) Log.v(TAG, "Pbap Service startRfcommSocketListener"); 320 321 if (mAcceptThread == null) { 322 mAcceptThread = new SocketAcceptThread(); 323 mAcceptThread.setName("BluetoothPbapAcceptThread"); 324 mAcceptThread.start(); 325 } 326 } 327 328 private final boolean initSocket() { 329 if (VERBOSE) Log.v(TAG, "Pbap Service initSocket"); 330 331 boolean initSocketOK = false; 332 final int CREATE_RETRY_TIME = 10; 333 334 // It's possible that create will fail in some cases. retry for 10 times 335 for (int i = 0; i < CREATE_RETRY_TIME && !mInterrupted; i++) { 336 initSocketOK = true; 337 try { 338 // It is mandatory for PSE to support initiation of bonding and 339 // encryption. 340 mServerSocket = mAdapter.listenUsingEncryptedRfcommWithServiceRecord 341 ("OBEX Phonebook Access Server", BluetoothUuid.PBAP_PSE.getUuid()); 342 343 } catch (IOException e) { 344 Log.e(TAG, "Error create RfcommServerSocket " + e.toString()); 345 initSocketOK = false; 346 } 347 if (!initSocketOK) { 348 // Need to break out of this loop if BT is being turned off. 349 if (mAdapter == null) break; 350 int state = mAdapter.getState(); 351 if ((state != BluetoothAdapter.STATE_TURNING_ON) && 352 (state != BluetoothAdapter.STATE_ON)) { 353 Log.w(TAG, "initServerSocket failed as BT is (being) turned off"); 354 break; 355 } 356 try { 357 if (VERBOSE) Log.v(TAG, "wait 300 ms"); 358 Thread.sleep(300); 359 } catch (InterruptedException e) { 360 Log.e(TAG, "socketAcceptThread thread was interrupted (3)"); 361 break; 362 } 363 } else { 364 break; 365 } 366 } 367 368 if (mInterrupted) { 369 initSocketOK = false; 370 // close server socket to avoid resource leakage 371 closeServerSocket(); 372 } 373 374 if (initSocketOK) { 375 if (VERBOSE) Log.v(TAG, "Succeed to create listening socket "); 376 377 } else { 378 Log.e(TAG, "Error to create listening socket after " + CREATE_RETRY_TIME + " try"); 379 } 380 return initSocketOK; 381 } 382 383 private final synchronized void closeServerSocket() { 384 // exit SocketAcceptThread early 385 if (mServerSocket != null) { 386 try { 387 // this will cause mServerSocket.accept() return early with IOException 388 mServerSocket.close(); 389 mServerSocket = null; 390 } catch (IOException ex) { 391 Log.e(TAG, "Close Server Socket error: " + ex); 392 } 393 } 394 } 395 396 private final synchronized void closeConnectionSocket() { 397 if (mConnSocket != null) { 398 try { 399 mConnSocket.close(); 400 mConnSocket = null; 401 } catch (IOException e) { 402 Log.e(TAG, "Close Connection Socket error: " + e.toString()); 403 } 404 } 405 } 406 407 private final void closeService() { 408 if (VERBOSE) Log.v(TAG, "Pbap Service closeService in"); 409 410 // exit initSocket early 411 mInterrupted = true; 412 closeServerSocket(); 413 414 if (mAcceptThread != null) { 415 try { 416 mAcceptThread.shutdown(); 417 mAcceptThread.join(); 418 mAcceptThread = null; 419 } catch (InterruptedException ex) { 420 Log.w(TAG, "mAcceptThread close error" + ex); 421 } 422 } 423 424 if (mWakeLock != null) { 425 mWakeLock.release(); 426 mWakeLock = null; 427 } 428 429 if (mServerSession != null) { 430 mServerSession.close(); 431 mServerSession = null; 432 } 433 434 closeConnectionSocket(); 435 436 mHasStarted = false; 437 if (mStartId != -1 && stopSelfResult(mStartId)) { 438 if (VERBOSE) Log.v(TAG, "successfully stopped pbap service"); 439 mStartId = -1; 440 } 441 if (VERBOSE) Log.v(TAG, "Pbap Service closeService out"); 442 } 443 444 private final void startObexServerSession() throws IOException { 445 if (VERBOSE) Log.v(TAG, "Pbap Service startObexServerSession"); 446 447 // acquire the wakeLock before start Obex transaction thread 448 if (mWakeLock == null) { 449 PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE); 450 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 451 "StartingObexPbapTransaction"); 452 mWakeLock.setReferenceCounted(false); 453 mWakeLock.acquire(); 454 } 455 TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE); 456 if (tm != null) { 457 sLocalPhoneNum = tm.getLine1Number(); 458 sLocalPhoneName = tm.getLine1AlphaTag(); 459 if (TextUtils.isEmpty(sLocalPhoneName)) { 460 sLocalPhoneName = this.getString(R.string.localPhoneName); 461 } 462 } 463 464 mPbapServer = new BluetoothPbapObexServer(mSessionStatusHandler, this); 465 synchronized (this) { 466 mAuth = new BluetoothPbapAuthenticator(mSessionStatusHandler); 467 mAuth.setChallenged(false); 468 mAuth.setCancelled(false); 469 } 470 BluetoothPbapRfcommTransport transport = new BluetoothPbapRfcommTransport(mConnSocket); 471 mServerSession = new ServerSession(transport, mPbapServer, mAuth); 472 setState(BluetoothPbap.STATE_CONNECTED); 473 if (VERBOSE) { 474 Log.v(TAG, "startObexServerSession() success!"); 475 } 476 } 477 478 private void stopObexServerSession() { 479 if (VERBOSE) Log.v(TAG, "Pbap Service stopObexServerSession"); 480 481 // Release the wake lock if obex transaction is over 482 if (mWakeLock != null) { 483 mWakeLock.release(); 484 mWakeLock = null; 485 } 486 487 if (mServerSession != null) { 488 mServerSession.close(); 489 mServerSession = null; 490 } 491 492 mAcceptThread = null; 493 494 closeConnectionSocket(); 495 496 // Last obex transaction is finished, we start to listen for incoming 497 // connection again 498 if (mAdapter.isEnabled()) { 499 startRfcommSocketListener(); 500 } 501 setState(BluetoothPbap.STATE_DISCONNECTED); 502 } 503 504 private void notifyAuthKeyInput(final String key) { 505 synchronized (mAuth) { 506 if (key != null) { 507 mAuth.setSessionKey(key); 508 } 509 mAuth.setChallenged(true); 510 mAuth.notify(); 511 } 512 } 513 514 private void notifyAuthCancelled() { 515 synchronized (mAuth) { 516 mAuth.setCancelled(true); 517 mAuth.notify(); 518 } 519 } 520 521 /** 522 * A thread that runs in the background waiting for remote rfcomm 523 * connect.Once a remote socket connected, this thread shall be 524 * shutdown.When the remote disconnect,this thread shall run again waiting 525 * for next request. 526 */ 527 private class SocketAcceptThread extends Thread { 528 529 private boolean stopped = false; 530 531 @Override 532 public void run() { 533 BluetoothServerSocket serverSocket; 534 if (mServerSocket == null) { 535 if (!initSocket()) { 536 return; 537 } 538 } 539 540 while (!stopped) { 541 try { 542 if (VERBOSE) Log.v(TAG, "Accepting socket connection..."); 543 serverSocket = mServerSocket; 544 if (serverSocket == null) { 545 Log.w(TAG, "mServerSocket is null"); 546 break; 547 } 548 mConnSocket = serverSocket.accept(); 549 if (VERBOSE) Log.v(TAG, "Accepted socket connection..."); 550 551 synchronized (BluetoothPbapService.this) { 552 if (mConnSocket == null) { 553 Log.w(TAG, "mConnSocket is null"); 554 break; 555 } 556 mRemoteDevice = mConnSocket.getRemoteDevice(); 557 } 558 if (mRemoteDevice == null) { 559 Log.i(TAG, "getRemoteDevice() = null"); 560 break; 561 } 562 sRemoteDeviceName = mRemoteDevice.getName(); 563 // In case getRemoteName failed and return null 564 if (TextUtils.isEmpty(sRemoteDeviceName)) { 565 sRemoteDeviceName = getString(R.string.defaultname); 566 } 567 boolean trust = mRemoteDevice.getTrustState(); 568 if (VERBOSE) Log.v(TAG, "GetTrustState() = " + trust); 569 570 if (trust) { 571 try { 572 if (VERBOSE) Log.v(TAG, "incoming connection accepted from: " 573 + sRemoteDeviceName + " automatically as trusted device"); 574 startObexServerSession(); 575 } catch (IOException ex) { 576 Log.e(TAG, "catch exception starting obex server session" 577 + ex.toString()); 578 } 579 } else { 580 Intent intent = new 581 Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST); 582 intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS); 583 intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, 584 BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS); 585 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice); 586 intent.putExtra(BluetoothDevice.EXTRA_PACKAGE_NAME, getPackageName()); 587 intent.putExtra(BluetoothDevice.EXTRA_CLASS_NAME, 588 BluetoothPbapReceiver.class.getName()); 589 sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); 590 isWaitingAuthorization = true; 591 592 if (VERBOSE) Log.v(TAG, "waiting for authorization for connection from: " 593 + sRemoteDeviceName); 594 595 // In case car kit time out and try to use HFP for 596 // phonebook 597 // access, while UI still there waiting for user to 598 // confirm 599 mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler 600 .obtainMessage(USER_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE); 601 } 602 stopped = true; // job done ,close this thread; 603 } catch (IOException ex) { 604 stopped=true; 605 /* 606 if (stopped) { 607 break; 608 } 609 */ 610 if (VERBOSE) Log.v(TAG, "Accept exception: " + ex.toString()); 611 } 612 } 613 } 614 615 void shutdown() { 616 stopped = true; 617 interrupt(); 618 } 619 } 620 621 private final Handler mSessionStatusHandler = new Handler() { 622 @Override 623 public void handleMessage(Message msg) { 624 if (VERBOSE) Log.v(TAG, "Handler(): got msg=" + msg.what); 625 626 switch (msg.what) { 627 case START_LISTENER: 628 if (mAdapter.isEnabled()) { 629 startRfcommSocketListener(); 630 } else { 631 closeService();// release all resources 632 } 633 break; 634 case USER_TIMEOUT: 635 Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL); 636 intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS); 637 intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, 638 BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS); 639 sendBroadcast(intent); 640 isWaitingAuthorization = false; 641 stopObexServerSession(); 642 break; 643 case AUTH_TIMEOUT: 644 Intent i = new Intent(USER_CONFIRM_TIMEOUT_ACTION); 645 sendBroadcast(i); 646 removePbapNotification(NOTIFICATION_ID_AUTH); 647 notifyAuthCancelled(); 648 break; 649 case MSG_SERVERSESSION_CLOSE: 650 stopObexServerSession(); 651 break; 652 case MSG_SESSION_ESTABLISHED: 653 break; 654 case MSG_SESSION_DISCONNECTED: 655 // case MSG_SERVERSESSION_CLOSE will handle ,so just skip 656 break; 657 case MSG_OBEX_AUTH_CHALL: 658 createPbapNotification(AUTH_CHALL_ACTION); 659 mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler 660 .obtainMessage(AUTH_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE); 661 break; 662 default: 663 break; 664 } 665 } 666 }; 667 668 private void setState(int state) { 669 setState(state, BluetoothPbap.RESULT_SUCCESS); 670 } 671 672 private synchronized void setState(int state, int result) { 673 if (state != mState) { 674 if (DEBUG) Log.d(TAG, "Pbap state " + mState + " -> " + state + ", result = " 675 + result); 676 int prevState = mState; 677 mState = state; 678 Intent intent = new Intent(BluetoothPbap.PBAP_STATE_CHANGED_ACTION); 679 intent.putExtra(BluetoothPbap.PBAP_PREVIOUS_STATE, prevState); 680 intent.putExtra(BluetoothPbap.PBAP_STATE, mState); 681 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice); 682 sendBroadcast(intent, BLUETOOTH_PERM); 683 AdapterService s = AdapterService.getAdapterService(); 684 if (s != null) { 685 s.onProfileConnectionStateChanged(mRemoteDevice, BluetoothProfile.PBAP, 686 mState, prevState); 687 } 688 } 689 } 690 691 private void createPbapNotification(String action) { 692 693 NotificationManager nm = (NotificationManager) 694 getSystemService(Context.NOTIFICATION_SERVICE); 695 696 // Create an intent triggered by clicking on the status icon. 697 Intent clickIntent = new Intent(); 698 clickIntent.setClass(this, BluetoothPbapActivity.class); 699 clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 700 clickIntent.setAction(action); 701 702 // Create an intent triggered by clicking on the 703 // "Clear All Notifications" button 704 Intent deleteIntent = new Intent(); 705 deleteIntent.setClass(this, BluetoothPbapReceiver.class); 706 707 Notification notification = null; 708 String name = getRemoteDeviceName(); 709 710 if (action.equals(AUTH_CHALL_ACTION)) { 711 deleteIntent.setAction(AUTH_CANCELLED_ACTION); 712 notification = new Notification(android.R.drawable.stat_sys_data_bluetooth, 713 getString(R.string.auth_notif_ticker), System.currentTimeMillis()); 714 notification.setLatestEventInfo(this, getString(R.string.auth_notif_title), 715 getString(R.string.auth_notif_message, name), PendingIntent 716 .getActivity(this, 0, clickIntent, 0)); 717 718 notification.flags |= Notification.FLAG_AUTO_CANCEL; 719 notification.flags |= Notification.FLAG_ONLY_ALERT_ONCE; 720 notification.defaults = Notification.DEFAULT_SOUND; 721 notification.deleteIntent = PendingIntent.getBroadcast(this, 0, deleteIntent, 0); 722 nm.notify(NOTIFICATION_ID_AUTH, notification); 723 } 724 } 725 726 private void removePbapNotification(int id) { 727 NotificationManager nm = (NotificationManager) 728 getSystemService(Context.NOTIFICATION_SERVICE); 729 nm.cancel(id); 730 } 731 732 public static String getLocalPhoneNum() { 733 return sLocalPhoneNum; 734 } 735 736 public static String getLocalPhoneName() { 737 return sLocalPhoneName; 738 } 739 740 public static String getRemoteDeviceName() { 741 return sRemoteDeviceName; 742 } 743 744 /** 745 * Handlers for incoming service calls 746 */ 747 private final IBluetoothPbap.Stub mBinder = new IBluetoothPbap.Stub() { 748 public int getState() { 749 if (DEBUG) Log.d(TAG, "getState " + mState); 750 751 if (!Utils.checkCaller()) { 752 Log.w(TAG,"getState(): not allowed for non-active user"); 753 return BluetoothPbap.STATE_DISCONNECTED; 754 } 755 756 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 757 return mState; 758 } 759 760 public BluetoothDevice getClient() { 761 if (DEBUG) Log.d(TAG, "getClient" + mRemoteDevice); 762 763 if (!Utils.checkCaller()) { 764 Log.w(TAG,"getClient(): not allowed for non-active user"); 765 return null; 766 } 767 768 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 769 if (mState == BluetoothPbap.STATE_DISCONNECTED) { 770 return null; 771 } 772 return mRemoteDevice; 773 } 774 775 public boolean isConnected(BluetoothDevice device) { 776 if (!Utils.checkCaller()) { 777 Log.w(TAG,"isConnected(): not allowed for non-active user"); 778 return false; 779 } 780 781 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 782 return mState == BluetoothPbap.STATE_CONNECTED && mRemoteDevice.equals(device); 783 } 784 785 public boolean connect(BluetoothDevice device) { 786 if (!Utils.checkCaller()) { 787 Log.w(TAG,"connect(): not allowed for non-active user"); 788 return false; 789 } 790 791 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 792 "Need BLUETOOTH_ADMIN permission"); 793 return false; 794 } 795 796 public void disconnect() { 797 if (DEBUG) Log.d(TAG, "disconnect"); 798 799 if (!Utils.checkCaller()) { 800 Log.w(TAG,"disconnect(): not allowed for non-active user"); 801 return; 802 } 803 804 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 805 "Need BLUETOOTH_ADMIN permission"); 806 synchronized (BluetoothPbapService.this) { 807 switch (mState) { 808 case BluetoothPbap.STATE_CONNECTED: 809 if (mServerSession != null) { 810 mServerSession.close(); 811 mServerSession = null; 812 } 813 814 closeConnectionSocket(); 815 816 setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED); 817 break; 818 default: 819 break; 820 } 821 } 822 } 823 }; 824} 825