BluetoothPbapService.java revision 77e6229d558b52c316fdba8dd29a6b26fb13fd28
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.BluetoothServerSocket; 43import android.bluetooth.BluetoothSocket; 44import android.bluetooth.IBluetooth; 45import android.bluetooth.IBluetoothPbap; 46import android.content.Context; 47import android.content.Intent; 48import android.os.Handler; 49import android.os.IBinder; 50import android.os.Message; 51import android.os.PowerManager; 52import android.os.RemoteException; 53import android.os.ServiceManager; 54import android.telephony.TelephonyManager; 55import android.text.TextUtils; 56import android.util.Log; 57 58import com.android.bluetooth.R; 59 60import java.io.IOException; 61 62import javax.obex.ServerSession; 63 64public class BluetoothPbapService extends Service { 65 private static final String TAG = "BluetoothPbapService"; 66 67 /** 68 * To enable PBAP DEBUG/VERBOSE logging - run below cmd in adb shell, and 69 * restart com.android.bluetooth process. only enable DEBUG log: 70 * "setprop log.tag.BluetoothPbapService DEBUG"; enable both VERBOSE and 71 * DEBUG log: "setprop log.tag.BluetoothPbapService VERBOSE" 72 */ 73 74 public static final boolean DEBUG = false; 75 76 public static final boolean VERBOSE = false; 77 78 /** 79 * Intent indicating incoming obex authentication request which is from 80 * PCE(Carkit) 81 */ 82 public static final String AUTH_CHALL_ACTION = "com.android.bluetooth.pbap.authchall"; 83 84 /** 85 * Intent indicating obex session key input complete by user which is sent 86 * from BluetoothPbapActivity 87 */ 88 public static final String AUTH_RESPONSE_ACTION = "com.android.bluetooth.pbap.authresponse"; 89 90 /** 91 * Intent indicating user canceled obex authentication session key input 92 * which is sent from BluetoothPbapActivity 93 */ 94 public static final String AUTH_CANCELLED_ACTION = "com.android.bluetooth.pbap.authcancelled"; 95 96 /** 97 * Intent indicating timeout for user confirmation, which is sent to 98 * BluetoothPbapActivity 99 */ 100 public static final String USER_CONFIRM_TIMEOUT_ACTION = 101 "com.android.bluetooth.pbap.userconfirmtimeout"; 102 103 /** 104 * Intent Extra name indicating session key which is sent from 105 * BluetoothPbapActivity 106 */ 107 public static final String EXTRA_SESSION_KEY = "com.android.bluetooth.pbap.sessionkey"; 108 109 public static final String THIS_PACKAGE_NAME = "com.android.bluetooth"; 110 111 public static final int MSG_SERVERSESSION_CLOSE = 5000; 112 113 public static final int MSG_SESSION_ESTABLISHED = 5001; 114 115 public static final int MSG_SESSION_DISCONNECTED = 5002; 116 117 public static final int MSG_OBEX_AUTH_CHALL = 5003; 118 119 private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; 120 121 private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; 122 123 private static final int START_LISTENER = 1; 124 125 private static final int USER_TIMEOUT = 2; 126 127 private static final int AUTH_TIMEOUT = 3; 128 129 private static final int PORT_NUM = 19; 130 131 private static final int USER_CONFIRM_TIMEOUT_VALUE = 30000; 132 133 134 // Ensure not conflict with Opp notification ID 135 private static final int NOTIFICATION_ID_ACCESS = -1000001; 136 137 private static final int NOTIFICATION_ID_AUTH = -1000002; 138 139 private PowerManager.WakeLock mWakeLock = null; 140 141 private BluetoothAdapter mAdapter; 142 143 private SocketAcceptThread mAcceptThread = null; 144 145 private BluetoothPbapAuthenticator mAuth = null; 146 147 private BluetoothPbapObexServer mPbapServer; 148 149 private ServerSession mServerSession = null; 150 151 private BluetoothServerSocket mServerSocket = null; 152 153 private BluetoothSocket mConnSocket = null; 154 155 private BluetoothDevice mRemoteDevice = null; 156 157 private static String sLocalPhoneNum = null; 158 159 private static String sLocalPhoneName = null; 160 161 private static String sRemoteDeviceName = null; 162 163 private boolean mHasStarted = false; 164 165 private volatile boolean mInterrupted; 166 167 private int mState; 168 169 private int mStartId = -1; 170 171 private IBluetooth mBluetoothService; 172 173 private boolean isWaitingAuthorization = false; 174 175 // package and class name to which we send intent to check phone book access permission 176 private static final String ACCESS_AUTHORITY_PACKAGE = "com.android.settings"; 177 private static final String ACCESS_AUTHORITY_CLASS = 178 "com.android.settings.bluetooth.BluetoothPermissionRequest"; 179 180 public BluetoothPbapService() { 181 mState = BluetoothPbap.STATE_DISCONNECTED; 182 IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE); 183 if (b == null) { 184 throw new RuntimeException("Bluetooth service not available"); 185 } 186 mBluetoothService = IBluetooth.Stub.asInterface(b); 187 } 188 189 @Override 190 public void onCreate() { 191 super.onCreate(); 192 if (VERBOSE) Log.v(TAG, "Pbap Service onCreate"); 193 194 mInterrupted = false; 195 mAdapter = BluetoothAdapter.getDefaultAdapter(); 196 197 if (!mHasStarted) { 198 mHasStarted = true; 199 if (VERBOSE) Log.v(TAG, "Starting PBAP service"); 200 201 int state = mAdapter.getState(); 202 if (state == BluetoothAdapter.STATE_ON) { 203 mSessionStatusHandler.sendMessage(mSessionStatusHandler 204 .obtainMessage(START_LISTENER)); 205 } 206 } 207 } 208 209 @Override 210 public int onStartCommand(Intent intent, int flags, int startId) { 211 if (VERBOSE) Log.v(TAG, "Pbap Service onStartCommand"); 212 int retCode = super.onStartCommand(intent, flags, startId); 213 if (retCode == START_STICKY) { 214 mStartId = startId; 215 if (mAdapter == null) { 216 Log.w(TAG, "Stopping BluetoothPbapService: " 217 + "device does not have BT or device is not ready"); 218 // Release all resources 219 closeService(); 220 } else { 221 // No need to handle the null intent case, because we have 222 // all restart work done in onCreate() 223 if (intent != null) { 224 parseIntent(intent); 225 } 226 } 227 } 228 return retCode; 229 } 230 231 // process the intent from receiver 232 private void parseIntent(final Intent intent) { 233 String action = intent.getStringExtra("action"); 234 if (VERBOSE) Log.v(TAG, "action: " + action); 235 236 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); 237 boolean removeTimeoutMsg = true; 238 if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { 239 if (state == BluetoothAdapter.STATE_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 sendBroadcast(timeoutIntent); 246 } 247 // Release all resources 248 closeService(); 249 } else { 250 removeTimeoutMsg = false; 251 } 252 } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) { 253 if (!isWaitingAuthorization) { 254 // this reply is not for us 255 return; 256 } 257 258 isWaitingAuthorization = false; 259 260 if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT, 261 BluetoothDevice.CONNECTION_ACCESS_NO) == 262 BluetoothDevice.CONNECTION_ACCESS_YES) { 263 264 if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) { 265 boolean result = mRemoteDevice.setTrust(true); 266 if (VERBOSE) Log.v(TAG, "setTrust() result=" + result); 267 } 268 try { 269 if (mConnSocket != null) { 270 startObexServerSession(); 271 } else { 272 stopObexServerSession(); 273 } 274 } catch (IOException ex) { 275 Log.e(TAG, "Caught the error: " + ex.toString()); 276 } 277 } else { 278 stopObexServerSession(); 279 } 280 } else if (action.equals(AUTH_RESPONSE_ACTION)) { 281 String sessionkey = intent.getStringExtra(EXTRA_SESSION_KEY); 282 notifyAuthKeyInput(sessionkey); 283 } else if (action.equals(AUTH_CANCELLED_ACTION)) { 284 notifyAuthCancelled(); 285 } else { 286 removeTimeoutMsg = false; 287 } 288 289 if (removeTimeoutMsg) { 290 mSessionStatusHandler.removeMessages(USER_TIMEOUT); 291 } 292 } 293 294 @Override 295 public void onDestroy() { 296 if (VERBOSE) Log.v(TAG, "Pbap Service onDestroy"); 297 298 super.onDestroy(); 299 setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED); 300 if (mWakeLock != null) { 301 mWakeLock.release(); 302 mWakeLock = null; 303 } 304 closeService(); 305 } 306 307 @Override 308 public IBinder onBind(Intent intent) { 309 if (VERBOSE) Log.v(TAG, "Pbap Service onBind"); 310 return mBinder; 311 } 312 313 private void startRfcommSocketListener() { 314 if (VERBOSE) Log.v(TAG, "Pbap Service startRfcommSocketListener"); 315 316 if (mServerSocket == null) { 317 if (!initSocket()) { 318 closeService(); 319 return; 320 } 321 } 322 if (mAcceptThread == null) { 323 mAcceptThread = new SocketAcceptThread(); 324 mAcceptThread.setName("BluetoothPbapAcceptThread"); 325 mAcceptThread.start(); 326 } 327 } 328 329 private final boolean initSocket() { 330 if (VERBOSE) Log.v(TAG, "Pbap Service initSocket"); 331 332 boolean initSocketOK = true; 333 final int CREATE_RETRY_TIME = 10; 334 335 // It's possible that create will fail in some cases. retry for 10 times 336 for (int i = 0; i < CREATE_RETRY_TIME && !mInterrupted; i++) { 337 try { 338 // It is mandatory for PSE to support initiation of bonding and 339 // encryption. 340 mServerSocket = mAdapter.listenUsingEncryptedRfcommOn(PORT_NUM); 341 } catch (IOException e) { 342 Log.e(TAG, "Error create RfcommServerSocket " + e.toString()); 343 initSocketOK = false; 344 } 345 if (!initSocketOK) { 346 synchronized (this) { 347 try { 348 if (VERBOSE) Log.v(TAG, "wait 3 seconds"); 349 Thread.sleep(3000); 350 } catch (InterruptedException e) { 351 Log.e(TAG, "socketAcceptThread thread was interrupted (3)"); 352 mInterrupted = true; 353 } 354 } 355 } else { 356 break; 357 } 358 } 359 360 if (initSocketOK) { 361 if (VERBOSE) Log.v(TAG, "Succeed to create listening socket on channel " + PORT_NUM); 362 363 } else { 364 Log.e(TAG, "Error to create listening socket after " + CREATE_RETRY_TIME + " try"); 365 } 366 return initSocketOK; 367 } 368 369 private final void closeSocket(boolean server, boolean accept) throws IOException { 370 if (server == true) { 371 // Stop the possible trying to init serverSocket 372 mInterrupted = true; 373 374 if (mServerSocket != null) { 375 mServerSocket.close(); 376 } 377 } 378 379 if (accept == true) { 380 if (mConnSocket != null) { 381 mConnSocket.close(); 382 } 383 } 384 } 385 386 private final void closeService() { 387 if (VERBOSE) Log.v(TAG, "Pbap Service closeService"); 388 389 try { 390 closeSocket(true, true); 391 } catch (IOException ex) { 392 Log.e(TAG, "CloseSocket error: " + ex); 393 } 394 395 if (mAcceptThread != null) { 396 try { 397 mAcceptThread.shutdown(); 398 mAcceptThread.join(); 399 mAcceptThread = null; 400 } catch (InterruptedException ex) { 401 Log.w(TAG, "mAcceptThread close error" + ex); 402 } 403 } 404 mServerSocket = null; 405 mConnSocket = null; 406 407 if (mServerSession != null) { 408 mServerSession.close(); 409 mServerSession = null; 410 } 411 412 mHasStarted = false; 413 if (stopSelfResult(mStartId)) { 414 if (VERBOSE) Log.v(TAG, "successfully stopped pbap service"); 415 } 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 BluetoothPbapRfcommTransport transport = new BluetoothPbapRfcommTransport(mConnSocket); 445 mServerSession = new ServerSession(transport, mPbapServer, mAuth); 446 setState(BluetoothPbap.STATE_CONNECTED); 447 if (VERBOSE) { 448 Log.v(TAG, "startObexServerSession() success!"); 449 } 450 } 451 452 private void stopObexServerSession() { 453 if (VERBOSE) Log.v(TAG, "Pbap Service stopObexServerSession"); 454 455 // Release the wake lock if obex transaction is over 456 if (mWakeLock != null) { 457 mWakeLock.release(); 458 mWakeLock = null; 459 } 460 461 if (mServerSession != null) { 462 mServerSession.close(); 463 mServerSession = null; 464 } 465 466 mAcceptThread = null; 467 468 try { 469 closeSocket(false, true); 470 mConnSocket = null; 471 } catch (IOException e) { 472 Log.e(TAG, "closeSocket error: " + e.toString()); 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 while (!stopped) { 512 try { 513 mConnSocket = mServerSocket.accept(); 514 515 mRemoteDevice = mConnSocket.getRemoteDevice(); 516 if (mRemoteDevice == null) { 517 Log.i(TAG, "getRemoteDevice() = null"); 518 break; 519 } 520 sRemoteDeviceName = mRemoteDevice.getName(); 521 // In case getRemoteName failed and return null 522 if (TextUtils.isEmpty(sRemoteDeviceName)) { 523 sRemoteDeviceName = getString(R.string.defaultname); 524 } 525 boolean trust = mRemoteDevice.getTrustState(); 526 if (VERBOSE) Log.v(TAG, "GetTrustState() = " + trust); 527 528 if (trust) { 529 try { 530 if (VERBOSE) Log.v(TAG, "incomming connection accepted from: " 531 + sRemoteDeviceName + " automatically as trusted device"); 532 startObexServerSession(); 533 } catch (IOException ex) { 534 Log.e(TAG, "catch exception starting obex server session" 535 + ex.toString()); 536 } 537 } else { 538 Intent intent = new 539 Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST); 540 intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS); 541 intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, 542 BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS); 543 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice); 544 intent.putExtra(BluetoothDevice.EXTRA_PACKAGE_NAME, getPackageName()); 545 intent.putExtra(BluetoothDevice.EXTRA_CLASS_NAME, 546 BluetoothPbapReceiver.class.getName()); 547 sendBroadcast(intent, BLUETOOTH_PERM); 548 isWaitingAuthorization = true; 549 550 if (VERBOSE) Log.v(TAG, "waiting for authorization for connection from: " 551 + sRemoteDeviceName); 552 553 // In case car kit time out and try to use HFP for 554 // phonebook 555 // access, while UI still there waiting for user to 556 // confirm 557 mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler 558 .obtainMessage(USER_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE); 559 } 560 stopped = true; // job done ,close this thread; 561 } catch (IOException ex) { 562 if (stopped) { 563 break; 564 } 565 if (VERBOSE) Log.v(TAG, "Accept exception: " + ex.toString()); 566 } 567 } 568 } 569 570 void shutdown() { 571 stopped = true; 572 interrupt(); 573 } 574 } 575 576 private final Handler mSessionStatusHandler = new Handler() { 577 @Override 578 public void handleMessage(Message msg) { 579 if (VERBOSE) Log.v(TAG, "Handler(): got msg=" + msg.what); 580 581 switch (msg.what) { 582 case START_LISTENER: 583 if (mAdapter.isEnabled()) { 584 startRfcommSocketListener(); 585 } else { 586 closeService();// release all resources 587 } 588 break; 589 case USER_TIMEOUT: 590 Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL); 591 intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS); 592 sendBroadcast(intent); 593 isWaitingAuthorization = false; 594 stopObexServerSession(); 595 break; 596 case AUTH_TIMEOUT: 597 Intent i = new Intent(USER_CONFIRM_TIMEOUT_ACTION); 598 sendBroadcast(i); 599 removePbapNotification(NOTIFICATION_ID_AUTH); 600 notifyAuthCancelled(); 601 break; 602 case MSG_SERVERSESSION_CLOSE: 603 stopObexServerSession(); 604 break; 605 case MSG_SESSION_ESTABLISHED: 606 break; 607 case MSG_SESSION_DISCONNECTED: 608 // case MSG_SERVERSESSION_CLOSE will handle ,so just skip 609 break; 610 case MSG_OBEX_AUTH_CHALL: 611 createPbapNotification(AUTH_CHALL_ACTION); 612 mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler 613 .obtainMessage(AUTH_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE); 614 break; 615 default: 616 break; 617 } 618 } 619 }; 620 621 private void setState(int state) { 622 setState(state, BluetoothPbap.RESULT_SUCCESS); 623 } 624 625 private synchronized void setState(int state, int result) { 626 if (state != mState) { 627 if (DEBUG) Log.d(TAG, "Pbap state " + mState + " -> " + state + ", result = " 628 + result); 629 Intent intent = new Intent(BluetoothPbap.PBAP_STATE_CHANGED_ACTION); 630 intent.putExtra(BluetoothPbap.PBAP_PREVIOUS_STATE, mState); 631 mState = state; 632 intent.putExtra(BluetoothPbap.PBAP_STATE, mState); 633 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice); 634 sendBroadcast(intent, BLUETOOTH_PERM); 635 try { 636 mBluetoothService.sendConnectionStateChange(mRemoteDevice, mState, state); 637 } catch (RemoteException e) { 638 Log.e(TAG, "RemoteException in sendConnectionStateChange"); 639 } 640 } 641 } 642 643 private void createPbapNotification(String action) { 644 645 NotificationManager nm = (NotificationManager) 646 getSystemService(Context.NOTIFICATION_SERVICE); 647 648 // Create an intent triggered by clicking on the status icon. 649 Intent clickIntent = new Intent(); 650 clickIntent.setClass(this, BluetoothPbapActivity.class); 651 clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 652 clickIntent.setAction(action); 653 654 // Create an intent triggered by clicking on the 655 // "Clear All Notifications" button 656 Intent deleteIntent = new Intent(); 657 deleteIntent.setClass(this, BluetoothPbapReceiver.class); 658 659 Notification notification = null; 660 String name = getRemoteDeviceName(); 661 662 if (action.equals(AUTH_CHALL_ACTION)) { 663 deleteIntent.setAction(AUTH_CANCELLED_ACTION); 664 notification = new Notification(android.R.drawable.stat_sys_data_bluetooth, 665 getString(R.string.auth_notif_ticker), System.currentTimeMillis()); 666 notification.setLatestEventInfo(this, getString(R.string.auth_notif_title), 667 getString(R.string.auth_notif_message, name), PendingIntent 668 .getActivity(this, 0, clickIntent, 0)); 669 670 notification.flags |= Notification.FLAG_AUTO_CANCEL; 671 notification.flags |= Notification.FLAG_ONLY_ALERT_ONCE; 672 notification.defaults = Notification.DEFAULT_SOUND; 673 notification.deleteIntent = PendingIntent.getBroadcast(this, 0, deleteIntent, 0); 674 nm.notify(NOTIFICATION_ID_AUTH, notification); 675 } 676 } 677 678 private void removePbapNotification(int id) { 679 NotificationManager nm = (NotificationManager) 680 getSystemService(Context.NOTIFICATION_SERVICE); 681 nm.cancel(id); 682 } 683 684 public static String getLocalPhoneNum() { 685 return sLocalPhoneNum; 686 } 687 688 public static String getLocalPhoneName() { 689 return sLocalPhoneName; 690 } 691 692 public static String getRemoteDeviceName() { 693 return sRemoteDeviceName; 694 } 695 696 /** 697 * Handlers for incoming service calls 698 */ 699 private final IBluetoothPbap.Stub mBinder = new IBluetoothPbap.Stub() { 700 public int getState() { 701 if (DEBUG) Log.d(TAG, "getState " + mState); 702 703 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 704 return mState; 705 } 706 707 public BluetoothDevice getClient() { 708 if (DEBUG) Log.d(TAG, "getClient" + mRemoteDevice); 709 710 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 711 if (mState == BluetoothPbap.STATE_DISCONNECTED) { 712 return null; 713 } 714 return mRemoteDevice; 715 } 716 717 public boolean isConnected(BluetoothDevice device) { 718 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 719 return mState == BluetoothPbap.STATE_CONNECTED && mRemoteDevice.equals(device); 720 } 721 722 public boolean connect(BluetoothDevice device) { 723 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 724 "Need BLUETOOTH_ADMIN permission"); 725 return false; 726 } 727 728 public void disconnect() { 729 if (DEBUG) Log.d(TAG, "disconnect"); 730 731 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 732 "Need BLUETOOTH_ADMIN permission"); 733 synchronized (BluetoothPbapService.this) { 734 switch (mState) { 735 case BluetoothPbap.STATE_CONNECTED: 736 if (mServerSession != null) { 737 mServerSession.close(); 738 mServerSession = null; 739 } 740 try { 741 closeSocket(false, true); 742 mConnSocket = null; 743 } catch (IOException ex) { 744 Log.e(TAG, "Caught the error: " + ex); 745 } 746 setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED); 747 break; 748 default: 749 break; 750 } 751 } 752 } 753 }; 754} 755