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