BluetoothPbapService.java revision acab258c177d82338b1696360cd0b8c9821b0f03
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.Service; 38import android.content.Context; 39import android.content.Intent; 40import android.bluetooth.BluetoothAdapter; 41import android.bluetooth.BluetoothDevice; 42import android.bluetooth.BluetoothError; 43import android.bluetooth.BluetoothIntent; 44import android.bluetooth.BluetoothPbap; 45import android.bluetooth.BluetoothSocket; 46import android.bluetooth.BluetoothServerSocket; 47import android.bluetooth.IBluetoothPbap; 48import android.os.Handler; 49import android.os.IBinder; 50import android.os.Message; 51import android.os.PowerManager; 52import android.telephony.TelephonyManager; 53import android.util.Log; 54import android.widget.Toast; 55 56import java.io.IOException; 57import java.util.ArrayList; 58 59import javax.obex.ServerSession; 60 61public class BluetoothPbapService extends Service { 62 63 private static final String TAG = "BluetoothPbapService"; 64 65 public static final boolean DBG = false; 66 67 /** 68 * Intent indicating incoming connection request which is sent to 69 * BluetoothPbapActivity 70 */ 71 public static final String ACCESS_REQUEST_ACTION = "com.android.bluetooth.pbap.accessrequest"; 72 73 /** 74 * Intent indicating incoming connection request accepted by user which is 75 * sent from BluetoothPbapActivity 76 */ 77 public static final String ACCESS_ALLOWED_ACTION = "com.android.bluetooth.pbap.accessallowed"; 78 79 /** 80 * Intent indicating incoming connection request denied by user which is 81 * sent from BluetoothPbapActivity 82 */ 83 public static final String ACCESS_DISALLOWED_ACTION = 84 "com.android.bluetooth.pbap.accessdisallowed"; 85 86 /** 87 * Intent indicating incoming obex authentication request which is from 88 * PCE(Carkit) 89 */ 90 public static final String AUTH_CHALL_ACTION = "com.android.bluetooth.pbap.authchall"; 91 92 /** 93 * Intent indicating obex session key input complete by user which is sent 94 * from BluetoothPbapActivity 95 */ 96 public static final String AUTH_RESPONSE_ACTION = "com.android.bluetooth.pbap.authresponse"; 97 98 /** 99 * Intent indicating user canceled obex authentication session key input 100 * which is sent from BluetoothPbapActivity 101 */ 102 public static final String AUTH_CANCELLED_ACTION = "com.android.bluetooth.pbap.authcancelled"; 103 104 /** 105 * Intent indicating timeout for user confirmation, which is sent to 106 * BluetoothPbapActivity 107 */ 108 public static final String USER_CONFIRM_TIMEOUT_ACTION = 109 "com.android.bluetooth.pbap.userconfirmtimeout"; 110 111 /** 112 * Intent Extra name indicating always allowed which is sent from 113 * BluetoothPbapActivity 114 */ 115 public static final String EXTRA_ALWAYS_ALLOWED = "com.android.bluetooth.pbap.alwaysallowed"; 116 117 /** 118 * Intent Extra name indicating session key which is sent from 119 * BluetoothPbapActivity 120 */ 121 public static final String EXTRA_SESSION_KEY = "com.android.bluetooth.pbap.sessionkey"; 122 123 public static final String THIS_PACKAGE_NAME = "com.android.bluetooth"; 124 125 public static final int MSG_SERVERSESSION_CLOSE = 5000; 126 127 public static final int MSG_SESSION_ESTABLISHED = 5001; 128 129 public static final int MSG_SESSION_DISCONNECTED = 5002; 130 131 public static final int MSG_OBEX_AUTH_CHALL = 5003; 132 133 private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; 134 135 private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; 136 137 private static final int START_LISTENER = 1; 138 139 private static final int USER_TIMEOUT = 2; 140 141 private static final int AUTH_TIMEOUT = 3; 142 143 private PowerManager.WakeLock mWakeLock = null; 144 145 private BluetoothAdapter mAdapter; 146 147 private SocketAcceptThread mAcceptThread = null; 148 149 private BluetoothPbapObexServer mPbapServer; 150 151 private ServerSession mServerSession = null; 152 153 private BluetoothServerSocket mServerSocket = null; 154 155 private BluetoothSocket mConnSocket = null; 156 157 private String mDeviceAddr = null; 158 159 private static String sLocalPhoneNum = null; 160 161 private static String sLocalPhoneName = null; 162 163 private static String sRemoteDeviceName = null; 164 165 private BluetoothDevice mRemoteDevice = null; 166 167 private boolean mHasStarted = false; 168 169 private int mState; 170 171 private int mStartId = -1; 172 173 private static final int PORT_NUM = 19; 174 175 private static final int USER_CONFIRM_TIMEOUT_VALUE = 30000; 176 177 private static final int SOCKET_ACCEPT_TIMEOUT_VALUE = 5000; 178 179 private static final int TIME_TO_WAIT_VALUE = 6000; 180 181 private static BluetoothPbapVcardManager sVcardManager; 182 183 private CharSequence mTmpTxt; 184 185 private BluetoothPbapAuthenticator mAuth = null; 186 187 public BluetoothPbapService() { 188 mState = BluetoothPbap.STATE_DISCONNECTED; 189 } 190 191 @Override 192 public void onCreate() { 193 super.onCreate(); 194 mAdapter = (BluetoothAdapter) getSystemService(Context.BLUETOOTH_SERVICE); 195 if (mAdapter != null) { 196 mDeviceAddr = mAdapter.getAddress(); 197 } 198 sVcardManager = new BluetoothPbapVcardManager(BluetoothPbapService.this); 199 if (!mHasStarted && mDeviceAddr != null) { 200 mHasStarted = true; 201 Log.i(TAG, "Starting PBAP service"); 202 int state = mAdapter.getBluetoothState(); 203 if (state == BluetoothAdapter.BLUETOOTH_STATE_ON) { 204 mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler 205 .obtainMessage(START_LISTENER), TIME_TO_WAIT_VALUE); 206 } 207 } 208 } 209 210 @Override 211 public void onStart(Intent intent, int startId) { 212 mStartId = startId; 213 if (mAdapter == null || mDeviceAddr == null) { 214 Log.w(TAG, "Stopping BluetoothPbapService: " 215 + "device does not have BT or device is not ready"); 216 closeService(); // release all resources 217 } else { 218 parseIntent(intent); 219 } 220 } 221 222 // process the intent from receiver 223 private void parseIntent(final Intent intent) { 224 String action = intent.getExtras().getString("action"); 225 int state = intent.getIntExtra(BluetoothIntent.BLUETOOTH_STATE, BluetoothError.ERROR); 226 if (action.equals(BluetoothIntent.BLUETOOTH_STATE_CHANGED_ACTION)) { 227 if (state == BluetoothAdapter.BLUETOOTH_STATE_OFF) { 228 closeService(); // release all resources 229 } 230 } else if (action.equals(ACCESS_ALLOWED_ACTION)) { 231 mSessionStatusHandler.removeMessages(USER_TIMEOUT); 232 if (intent.getBooleanExtra(EXTRA_ALWAYS_ALLOWED, false)) { 233 boolean result = mRemoteDevice.setTrust(true); 234 if (DBG) { 235 Log.v(TAG, "setTrust() result=" + result); 236 } 237 } 238 try { 239 if (mConnSocket != null) { 240 startObexServerSession(); 241 } else { 242 obexServerSessionClose(); 243 } 244 } catch (IOException ex) { 245 if (DBG) { 246 Log.e(TAG, "Caught the error: " + ex.toString()); 247 } 248 } 249 } else if (action.equals(ACCESS_DISALLOWED_ACTION)) { 250 mSessionStatusHandler.removeMessages(USER_TIMEOUT); 251 obexServerSessionClose(); 252 } else if (action.equals(AUTH_RESPONSE_ACTION)) { 253 mSessionStatusHandler.removeMessages(USER_TIMEOUT); 254 String sessionkey = intent.getStringExtra(EXTRA_SESSION_KEY); 255 notifyAuthKeyInput(sessionkey); 256 } else if (action.equals(AUTH_CANCELLED_ACTION)) { 257 mSessionStatusHandler.removeMessages(USER_TIMEOUT); 258 notifyAuthCancelled(); 259 } 260 } 261 262 @Override 263 public void onDestroy() { 264 super.onDestroy(); 265 setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED); 266 } 267 268 @Override 269 public IBinder onBind(Intent intent) { 270 return mBinder; 271 } 272 273 public static int getPhonebookSize(final int type) { 274 if (DBG) { 275 Log.d(TAG, "getPhonebookSzie type=" + type); 276 } 277 switch (type) { 278 case BluetoothPbapObexServer.ContentType.PHONEBOOK: 279 return sVcardManager.getPhonebookSize(); 280 default: 281 return sVcardManager.getCallHistorySize(type); 282 } 283 } 284 285 public static String getPhonebook(final int type, final int pos, final boolean vcardType) { 286 if (DBG) { 287 Log.d(TAG, "getPhonebook type=" + type + " pos=" + pos + " vcardType=" + vcardType); 288 } 289 switch (type) { 290 case BluetoothPbapObexServer.ContentType.PHONEBOOK: 291 return sVcardManager.getPhonebook(pos, vcardType); 292 default: 293 return sVcardManager.getCallHistory(pos, type, vcardType); 294 } 295 } 296 297 public static String getLocalPhoneNum() { 298 return sLocalPhoneNum; 299 } 300 301 public static String getLocalPhoneName() { 302 return sLocalPhoneName; 303 } 304 305 public static String getRemoteDeviceName() { 306 return sRemoteDeviceName; 307 } 308 309 // Used for phone book listing by name 310 public static ArrayList<String> getPhonebookNameList() { 311 return sVcardManager.loadNameList(); 312 } 313 314 // Used for phone book listing by number 315 public static ArrayList<String> getPhonebookNumberList() { 316 return sVcardManager.loadNumberList(); 317 } 318 319 // Used for call history listing 320 public static ArrayList<String> getCallLogList(final int type) { 321 return sVcardManager.loadCallHistoryList(type); 322 } 323 324 private void notifyAuthKeyInput(final String key) { 325 synchronized (mAuth) { 326 mAuth.setSessionKey(key); 327 mAuth.setChallenged(true); 328 mAuth.notify(); 329 } 330 } 331 332 private void notifyAuthCancelled() { 333 synchronized (mAuth) { 334 mAuth.setCancelled(true); 335 mAuth.notify(); 336 } 337 } 338 339 private final boolean initSocket() { 340 try { 341 // It is mandatory for PSE to support initiation of bonding and 342 // encryption. 343 mServerSocket = mAdapter.listenUsingRfcommOn(PORT_NUM); 344 } catch (IOException ex) { 345 Log.e(TAG, "initSocket " + ex.toString()); 346 return false; 347 } 348 return true; 349 } 350 351 private final void startObexServerSession() throws IOException { 352 // acquire the wakeLock before start Obex transaction thread 353 if (mWakeLock == null) { 354 PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE); 355 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 356 "StartingObexPbapTransaction"); 357 mWakeLock.setReferenceCounted(false); 358 mWakeLock.acquire(); 359 } 360 TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE); 361 if (tm != null) { 362 sLocalPhoneNum = tm.getLine1Number(); 363 sLocalPhoneName = tm.getLine1AlphaTag(); 364 } 365 mPbapServer = new BluetoothPbapObexServer(mSessionStatusHandler); 366 mAuth = new BluetoothPbapAuthenticator(mSessionStatusHandler); 367 synchronized (mAuth) { 368 mAuth.setChallenged(false); 369 mAuth.setCancelled(false); 370 } 371 BluetoothPbapRfcommTransport transport = new BluetoothPbapRfcommTransport(mConnSocket); 372 mServerSession = new ServerSession(transport, mPbapServer, mAuth); 373 setState(BluetoothPbap.STATE_CONNECTED); 374 if (DBG) { 375 Log.v(TAG, "startObexServerSession() success!"); 376 } 377 } 378 379 private final void closeSocket(boolean server, boolean accept) throws IOException { 380 if (server == true) { 381 if (mServerSocket != null) { 382 mServerSocket.close(); 383 } 384 mServerSocket = null; 385 } 386 387 if (accept == true) { 388 if (mConnSocket != null) { 389 mConnSocket.close(); 390 } 391 mConnSocket = null; 392 } 393 } 394 395 private final void closeService() { 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 if (mServerSession != null) { 406 mServerSession.close(); 407 mServerSession = null; 408 } 409 try { 410 closeSocket(true, true); 411 } catch (IOException ex) { 412 if (DBG) { 413 Log.e(TAG, "Caught the error: " + ex); 414 } 415 } 416 mHasStarted = false; 417 BluetoothPbapReceiver.finishStartingService(BluetoothPbapService.this, mStartId); 418 } 419 420 private void obexServerSessionClose() { 421 // Release the wake lock if obex transaction is over 422 if (mWakeLock != null) { 423 mWakeLock.release(); 424 mWakeLock = null; 425 } 426 mServerSession = null; 427 mAcceptThread = null; 428 try { 429 closeSocket(false, true); 430 } catch (IOException e) { 431 if (DBG) { 432 Log.e(TAG, "Caught the error: " + e.toString()); 433 } 434 } 435 // Last obex transaction is finished,we start to listen for incoming 436 // connection again 437 if (mAdapter.isEnabled()) { 438 startRfcommSocketListener(); 439 } 440 setState(BluetoothPbap.STATE_DISCONNECTED); 441 } 442 443 /** 444 * A thread that runs in the background waiting for remote rfcomm 445 * connect.Once a remote socket connected, this thread shall be 446 * shutdown.When the remote disconnect,this thread shall run again waiting 447 * for next request. 448 */ 449 private class SocketAcceptThread extends Thread { 450 451 private boolean stopped = false; 452 453 @Override 454 public void run() { 455 while (!stopped) { 456 try { 457 mConnSocket = mServerSocket.accept(SOCKET_ACCEPT_TIMEOUT_VALUE); 458 } catch (IOException ex) { 459 if (stopped) { 460 break; 461 } 462 if (DBG) { 463 Log.v(TAG, "Caught the error in socketAcceptThread: " + ex); 464 } 465 } 466 if (mConnSocket != null) { 467 mRemoteDevice = mConnSocket.getRemoteDevice(); 468 if (mRemoteDevice != null) { 469 sRemoteDeviceName = mRemoteDevice.getName(); 470 // In case getRemoteName failed and return null 471 if (sRemoteDeviceName == null) { 472 sRemoteDeviceName = getString(R.string.defaultname); 473 } 474 } 475 boolean trust = mRemoteDevice.getTrustState(); 476 if (DBG) { 477 Log.v(TAG, "GetTrustState() = " + trust); 478 } 479 if (trust) { 480 try { 481 if (DBG) { 482 Log.v(TAG, "Trusted device, incomming connection accepted auto."); 483 } 484 startObexServerSession(); 485 } catch (IOException ex) { 486 Log.e(TAG, "catch exception starting obex server session" 487 + ex.toString()); 488 } 489 } else { 490 BluetoothPbapReceiver.makeNewPbapNotification(getApplicationContext(), 491 ACCESS_REQUEST_ACTION); 492 if (DBG) { 493 Log.v(TAG, "Incomming connection accepted from:" + sRemoteDeviceName); 494 } 495 // In case car kit time out and try to use HFP for phonebook 496 // access, while UI still there waiting for user to confirm 497 mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler 498 .obtainMessage(USER_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE); 499 } 500 stopped = true; // job done ,close this thread; 501 } 502 } 503 } 504 505 void shutdown() { 506 stopped = true; 507 interrupt(); 508 } 509 } 510 511 private void startRfcommSocketListener() { 512 if (mServerSocket == null) { 513 if (!initSocket()) { 514 closeService(); 515 return; 516 } 517 } 518 if (mAcceptThread == null) { 519 mAcceptThread = new SocketAcceptThread(); 520 mAcceptThread.setName("BluetoothPbapAcceptThread"); 521 mAcceptThread.start(); 522 } 523 } 524 525 private void setState(int state) { 526 setState(state, BluetoothPbap.RESULT_SUCCESS); 527 } 528 529 private synchronized void setState(int state, int result) { 530 if (state != mState) { 531 if (DBG) 532 Log.d(TAG, "Pbap state " + mState + " -> " + state + ", result = " + result); 533 Intent intent = new Intent(BluetoothPbap.PBAP_STATE_CHANGED_ACTION); 534 intent.putExtra(BluetoothPbap.PBAP_PREVIOUS_STATE, mState); 535 mState = state; 536 intent.putExtra(BluetoothPbap.PBAP_STATE, mState); 537 intent.putExtra(BluetoothIntent.DEVICE, mRemoteDevice); 538 sendBroadcast(intent, BLUETOOTH_PERM); 539 } 540 } 541 542 /** 543 * Handlers for incoming service calls 544 */ 545 private final IBluetoothPbap.Stub mBinder = new IBluetoothPbap.Stub() { 546 public int getState() { 547 if (DBG) { 548 Log.d(TAG, "getState " + mState); 549 } 550 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 551 return mState; 552 } 553 554 public BluetoothDevice getClient() { 555 if (DBG) { 556 Log.d(TAG, "getClient" + mRemoteDevice); 557 } 558 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 559 if (mState == BluetoothPbap.STATE_DISCONNECTED) { 560 return null; 561 } 562 return mRemoteDevice; 563 } 564 565 public boolean isConnected(BluetoothDevice device) { 566 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 567 return mState == BluetoothPbap.STATE_CONNECTED && mRemoteDevice.equals(device); 568 } 569 570 public boolean connect(BluetoothDevice device) { 571 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 572 "Need BLUETOOTH_ADMIN permission"); 573 return false; 574 } 575 576 public void disconnect() { 577 if (DBG) { 578 Log.d(TAG, "disconnect"); 579 } 580 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 581 "Need BLUETOOTH_ADMIN permission"); 582 synchronized (BluetoothPbapService.this) { 583 switch (mState) { 584 case BluetoothPbap.STATE_CONNECTED: 585 if (mServerSession != null) { 586 mServerSession.close(); 587 mServerSession = null; 588 } 589 try { 590 closeSocket(false, true); 591 } catch (IOException ex) { 592 Log.e(TAG, "Caught the error: " + ex); 593 } 594 setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED); 595 break; 596 } 597 } 598 } 599 }; 600 601 private final Handler mSessionStatusHandler = new Handler() { 602 @Override 603 public void handleMessage(Message msg) { 604 switch (msg.what) { 605 case START_LISTENER: 606 if (mAdapter.isEnabled()) { 607 startRfcommSocketListener(); 608 } else { 609 closeService();// release all resources 610 } 611 break; 612 case USER_TIMEOUT: 613 Intent intent = new Intent(USER_CONFIRM_TIMEOUT_ACTION); 614 sendBroadcast(intent); 615 BluetoothPbapReceiver.removePbapNotification(getApplicationContext(), 616 BluetoothPbapReceiver.NOTIFICATION_ID_ACCESS); 617 obexServerSessionClose(); 618 break; 619 case AUTH_TIMEOUT: 620 Intent i = new Intent(USER_CONFIRM_TIMEOUT_ACTION); 621 sendBroadcast(i); 622 BluetoothPbapReceiver.removePbapNotification(getApplicationContext(), 623 BluetoothPbapReceiver.NOTIFICATION_ID_AUTH); 624 notifyAuthCancelled(); 625 break; 626 case MSG_SERVERSESSION_CLOSE: 627 obexServerSessionClose(); 628 mTmpTxt = getString(R.string.toast_disconnected, sRemoteDeviceName); 629 Toast.makeText(BluetoothPbapService.this, mTmpTxt, Toast.LENGTH_SHORT).show(); 630 break; 631 case MSG_SESSION_ESTABLISHED: 632 mTmpTxt = getString(R.string.toast_connected, sRemoteDeviceName); 633 Toast.makeText(BluetoothPbapService.this, mTmpTxt, Toast.LENGTH_SHORT).show(); 634 break; 635 case MSG_SESSION_DISCONNECTED: 636 // case MSG_SERVERSESSION_CLOSE will handle ,so just skip 637 break; 638 case MSG_OBEX_AUTH_CHALL: 639 BluetoothPbapReceiver.makeNewPbapNotification(getApplicationContext(), 640 AUTH_CHALL_ACTION); 641 mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler 642 .obtainMessage(AUTH_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE); 643 break; 644 } 645 } 646 }; 647} 648