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