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