BluetoothMapService.java revision 824929471ee80476e6d6774eedac9f30c5623eb2
15e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux/* 25e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux* Copyright (C) 2013 Samsung System LSI 35e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux* Licensed under the Apache License, Version 2.0 (the "License"); 45e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux* you may not use this file except in compliance with the License. 55e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux* You may obtain a copy of the License at 65e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux* 75e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux* http://www.apache.org/licenses/LICENSE-2.0 85e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux* 95e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux* Unless required by applicable law or agreed to in writing, software 105e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux* distributed under the License is distributed on an "AS IS" BASIS, 115e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 125e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux* See the License for the specific language governing permissions and 135e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux* limitations under the License. 145e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux*/ 155e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux 165e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieuxpackage com.android.bluetooth.map; 175e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux 185e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieuximport java.io.IOException; 195e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieuximport java.util.ArrayList; 205e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieuximport java.util.List; 215e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieuximport java.util.Set; 225e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieux 235e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieuximport javax.obex.ServerSession; 245e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieuximport android.app.Notification; 255e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieuximport android.app.NotificationManager; 265e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieuximport android.app.PendingIntent; 275e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieuximport android.app.Service; 285e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieuximport android.bluetooth.BluetoothAdapter; 295e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieuximport android.bluetooth.BluetoothDevice; 305e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieuximport android.bluetooth.BluetoothProfile; 315e0c5c4846f73565bf07542f64bea8208002f55cJames Lemieuximport android.bluetooth.BluetoothServerSocket; 32import android.bluetooth.IBluetooth; 33import android.bluetooth.IBluetoothMap; 34import android.bluetooth.BluetoothUuid; 35import android.bluetooth.BluetoothMap; 36import android.bluetooth.BluetoothSocket; 37import android.content.Context; 38import android.content.Intent; 39import android.os.Handler; 40import android.os.IBinder; 41import android.os.Message; 42import android.os.PowerManager; 43import android.os.ParcelUuid; 44import android.text.TextUtils; 45import android.util.Log; 46import android.provider.Settings; 47import android.content.IntentFilter; 48import android.content.BroadcastReceiver; 49 50import com.android.bluetooth.R; 51import com.android.bluetooth.Utils; 52import com.android.bluetooth.btservice.AdapterService; 53import com.android.bluetooth.btservice.ProfileService; 54import com.android.bluetooth.btservice.ProfileService.IProfileServiceBinder; 55 56 57public class BluetoothMapService extends ProfileService { 58 private static final String TAG = "BluetoothMapService"; 59 60 /** 61 * To enable MAP DEBUG/VERBOSE logging - run below cmd in adb shell, and 62 * restart com.android.bluetooth process. only enable DEBUG log: 63 * "setprop log.tag.BluetoothMapService DEBUG"; enable both VERBOSE and 64 * DEBUG log: "setprop log.tag.BluetoothMapService VERBOSE" 65 */ 66 67 public static final boolean DEBUG = true; 68 69 public static final boolean VERBOSE = false; 70 71 /** 72 * Intent indicating incoming obex authentication request which is from 73 * PCE(Carkit) 74 */ 75 public static final String AUTH_CHALL_ACTION = "com.android.bluetooth.map.authchall"; 76 77 /** 78 * Intent indicating timeout for user confirmation, which is sent to 79 * BluetoothMapActivity 80 */ 81 public static final String USER_CONFIRM_TIMEOUT_ACTION = 82 "com.android.bluetooth.map.userconfirmtimeout"; 83 84 /** 85 * Intent Extra name indicating session key which is sent from 86 * BluetoothMapActivity 87 */ 88 public static final String EXTRA_SESSION_KEY = "com.android.bluetooth.map.sessionkey"; 89 90 public static final String THIS_PACKAGE_NAME = "com.android.bluetooth"; 91 92 public static final int MSG_SERVERSESSION_CLOSE = 5000; 93 94 public static final int MSG_SESSION_ESTABLISHED = 5001; 95 96 public static final int MSG_SESSION_DISCONNECTED = 5002; 97 98 public static final int MSG_OBEX_AUTH_CHALL = 5003; 99 100 private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; 101 102 private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; 103 104 private static final int START_LISTENER = 1; 105 106 private static final int USER_TIMEOUT = 2; 107 108 private static final int DISCONNECT_MAP = 3; 109 110 private PowerManager.WakeLock mWakeLock = null; 111 112 private BluetoothAdapter mAdapter; 113 114 private SocketAcceptThread mAcceptThread = null; 115 116 private BluetoothMapAuthenticator mAuth = null; 117 118 private BluetoothMapObexServer mMapServer; 119 120 private ServerSession mServerSession = null; 121 122 private BluetoothMnsObexClient mBluetoothMnsObexClient = null; 123 124 private BluetoothServerSocket mServerSocket = null; 125 126 private BluetoothSocket mConnSocket = null; 127 128 private BluetoothDevice mRemoteDevice = null; 129 130 private static String sRemoteDeviceName = null; 131 132 private volatile boolean mInterrupted; 133 134 private int mState; 135 136 private boolean isWaitingAuthorization = false; 137 138 // package and class name to which we send intent to check message access access permission 139 private static final String ACCESS_AUTHORITY_PACKAGE = "com.android.settings"; 140 private static final String ACCESS_AUTHORITY_CLASS = 141 "com.android.settings.bluetooth.BluetoothPermissionRequest"; 142 143 private static final ParcelUuid[] MAP_UUIDS = { 144 BluetoothUuid.MAP, 145 BluetoothUuid.MNS, 146 }; 147 148 public BluetoothMapService() { 149 mState = BluetoothMap.STATE_DISCONNECTED; 150 } 151 152 private void startRfcommSocketListener() { 153 if (DEBUG) Log.d(TAG, "Map Service startRfcommSocketListener"); 154 155 if (mAcceptThread == null) { 156 mAcceptThread = new SocketAcceptThread(); 157 mAcceptThread.setName("BluetoothMapAcceptThread"); 158 mAcceptThread.start(); 159 } 160 } 161 162 private final boolean initSocket() { 163 if (DEBUG) Log.d(TAG, "Map Service initSocket"); 164 165 boolean initSocketOK = false; 166 final int CREATE_RETRY_TIME = 10; 167 168 // It's possible that create will fail in some cases. retry for 10 times 169 for (int i = 0; (i < CREATE_RETRY_TIME) && !mInterrupted; i++) { 170 initSocketOK = true; 171 try { 172 // It is mandatory for MSE to support initiation of bonding and 173 // encryption. 174 mServerSocket = mAdapter.listenUsingEncryptedRfcommWithServiceRecord 175 ("MAP SMS/MMS", BluetoothUuid.MAS.getUuid()); 176 177 } catch (IOException e) { 178 Log.e(TAG, "Error create RfcommServerSocket " + e.toString()); 179 initSocketOK = false; 180 } 181 if (!initSocketOK) { 182 // Need to break out of this loop if BT is being turned off. 183 if (mAdapter == null) break; 184 int state = mAdapter.getState(); 185 if ((state != BluetoothAdapter.STATE_TURNING_ON) && 186 (state != BluetoothAdapter.STATE_ON)) { 187 Log.w(TAG, "initServerSocket failed as BT is (being) turned off"); 188 break; 189 } 190 try { 191 if (VERBOSE) Log.v(TAG, "wait 300 ms"); 192 Thread.sleep(300); 193 } catch (InterruptedException e) { 194 Log.e(TAG, "socketAcceptThread thread was interrupted (3)"); 195 } 196 } else { 197 break; 198 } 199 } 200 if (mInterrupted) { 201 initSocketOK = false; 202 // close server socket to avoid resource leakage 203 closeServerSocket(); 204 } 205 206 if (initSocketOK) { 207 if (VERBOSE) Log.v(TAG, "Succeed to create listening socket "); 208 209 } else { 210 Log.e(TAG, "Error to create listening socket after " + CREATE_RETRY_TIME + " try"); 211 } 212 return initSocketOK; 213 } 214 215 private final synchronized void closeServerSocket() { 216 // exit SocketAcceptThread early 217 if (mServerSocket != null) { 218 try { 219 // this will cause mServerSocket.accept() return early with IOException 220 mServerSocket.close(); 221 mServerSocket = null; 222 } catch (IOException ex) { 223 Log.e(TAG, "Close Server Socket error: " + ex); 224 } 225 } 226 } 227 private final synchronized void closeConnectionSocket() { 228 if (mConnSocket != null) { 229 try { 230 mConnSocket.close(); 231 mConnSocket = null; 232 } catch (IOException e) { 233 Log.e(TAG, "Close Connection Socket error: " + e.toString()); 234 } 235 } 236 } 237 238 private final void closeService() { 239 if (DEBUG) Log.d(TAG, "MAP Service closeService in"); 240 241 // exit initSocket early 242 mInterrupted = true; 243 closeServerSocket(); 244 245 if (mAcceptThread != null) { 246 try { 247 mAcceptThread.shutdown(); 248 mAcceptThread.join(); 249 mAcceptThread = null; 250 } catch (InterruptedException ex) { 251 Log.w(TAG, "mAcceptThread close error" + ex); 252 } 253 } 254 255 if (mWakeLock != null) { 256 mWakeLock.release(); 257 mWakeLock = null; 258 } 259 260 if (mServerSession != null) { 261 mServerSession.close(); 262 mServerSession = null; 263 } 264 265 if (mBluetoothMnsObexClient != null) { 266 mBluetoothMnsObexClient.shutdown(); 267 mBluetoothMnsObexClient = null; 268 } 269 270 closeConnectionSocket(); 271 272 if (mSessionStatusHandler != null) { 273 mSessionStatusHandler.removeCallbacksAndMessages(null); 274 } 275 isWaitingAuthorization = false; 276 277 if (VERBOSE) Log.v(TAG, "MAP Service closeService out"); 278 } 279 280 private final void startObexServerSession() throws IOException { 281 if (DEBUG) Log.d(TAG, "Map Service startObexServerSession"); 282 283 // acquire the wakeLock before start Obex transaction thread 284 if (mWakeLock == null) { 285 PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE); 286 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 287 "StartingObexMapTransaction"); 288 mWakeLock.setReferenceCounted(false); 289 mWakeLock.acquire(); 290 } 291 292 mBluetoothMnsObexClient = new BluetoothMnsObexClient(this, mRemoteDevice); 293 mMapServer = new BluetoothMapObexServer(mSessionStatusHandler, this, 294 mBluetoothMnsObexClient); 295 synchronized (this) { 296 // We need to get authentication now that obex server is up 297 mAuth = new BluetoothMapAuthenticator(mSessionStatusHandler); 298 mAuth.setChallenged(false); 299 mAuth.setCancelled(false); 300 } 301 // setup RFCOMM transport 302 BluetoothMapRfcommTransport transport = new BluetoothMapRfcommTransport(mConnSocket); 303 mServerSession = new ServerSession(transport, mMapServer, mAuth); 304 setState(BluetoothMap.STATE_CONNECTED); 305 if (VERBOSE) { 306 Log.v(TAG, "startObexServerSession() success!"); 307 } 308 } 309 310 private void stopObexServerSession() { 311 if (DEBUG) Log.d(TAG, "MAP Service stopObexServerSession"); 312 313 // Release the wake lock if obex transaction is over 314 if (mWakeLock != null) { 315 mWakeLock.release(); 316 mWakeLock = null; 317 } 318 319 if (mServerSession != null) { 320 mServerSession.close(); 321 mServerSession = null; 322 } 323 324 mAcceptThread = null; 325 326 if(mBluetoothMnsObexClient != null) { 327 mBluetoothMnsObexClient.shutdown(); 328 mBluetoothMnsObexClient = null; 329 } 330 closeConnectionSocket(); 331 332 // Last obex transaction is finished, we start to listen for incoming 333 // connection again 334 if (mAdapter.isEnabled()) { 335 startRfcommSocketListener(); 336 } 337 setState(BluetoothMap.STATE_DISCONNECTED); 338 } 339 340 341 342 /** 343 * A thread that runs in the background waiting for remote rfcomm 344 * connect.Once a remote socket connected, this thread shall be 345 * shutdown.When the remote disconnect,this thread shall run again waiting 346 * for next request. 347 */ 348 private class SocketAcceptThread extends Thread { 349 350 private boolean stopped = false; 351 352 @Override 353 public void run() { 354 BluetoothServerSocket serverSocket; 355 if (mServerSocket == null) { 356 if (!initSocket()) { 357 return; 358 } 359 } 360 361 while (!stopped) { 362 try { 363 if (DEBUG) Log.d(TAG, "Accepting socket connection..."); 364 serverSocket = mServerSocket; 365 if(serverSocket == null) { 366 Log.w(TAG, "mServerSocket is null"); 367 break; 368 } 369 mConnSocket = serverSocket.accept(); 370 if (DEBUG) Log.d(TAG, "Accepted socket connection..."); 371 synchronized (BluetoothMapService.this) { 372 if (mConnSocket == null) { 373 Log.w(TAG, "mConnSocket is null"); 374 break; 375 } 376 mRemoteDevice = mConnSocket.getRemoteDevice(); 377 } 378 if (mRemoteDevice == null) { 379 Log.i(TAG, "getRemoteDevice() = null"); 380 break; 381 } 382 383 sRemoteDeviceName = mRemoteDevice.getName(); 384 // In case getRemoteName failed and return null 385 if (TextUtils.isEmpty(sRemoteDeviceName)) { 386 sRemoteDeviceName = getString(R.string.defaultname); 387 } 388 boolean trust = mRemoteDevice.getTrustState(); 389 if (DEBUG) Log.d(TAG, "GetTrustState() = " + trust); 390 391 392 if (trust) { 393 try { 394 if (DEBUG) Log.d(TAG, "incoming connection accepted from: " 395 + sRemoteDeviceName + " automatically as trusted device"); 396 startObexServerSession(); 397 } catch (IOException ex) { 398 Log.e(TAG, "catch exception starting obex server session" 399 + ex.toString()); 400 } 401 } else { 402 Intent intent = new 403 Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST); 404 intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS); 405 intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, 406 BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS); 407 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice); 408 409 isWaitingAuthorization = true; 410 sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); 411 412 if (DEBUG) Log.d(TAG, "waiting for authorization for connection from: " 413 + sRemoteDeviceName); 414 415 } 416 stopped = true; // job done ,close this thread; 417 } catch (IOException ex) { 418 stopped=true; 419 if (VERBOSE) Log.v(TAG, "Accept exception: " + ex.toString()); 420 } 421 } 422 } 423 424 void shutdown() { 425 stopped = true; 426 interrupt(); 427 } 428 } 429 430 private final Handler mSessionStatusHandler = new Handler() { 431 @Override 432 public void handleMessage(Message msg) { 433 if (VERBOSE) Log.v(TAG, "Handler(): got msg=" + msg.what); 434 435 switch (msg.what) { 436 case START_LISTENER: 437 if (mAdapter.isEnabled()) { 438 startRfcommSocketListener(); 439 } 440 break; 441 case USER_TIMEOUT: 442 Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL); 443 intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS); 444 intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, 445 BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS); 446 sendBroadcast(intent); 447 isWaitingAuthorization = false; 448 stopObexServerSession(); 449 break; 450 case MSG_SERVERSESSION_CLOSE: 451 stopObexServerSession(); 452 break; 453 case MSG_SESSION_ESTABLISHED: 454 break; 455 case MSG_SESSION_DISCONNECTED: 456 // handled elsewhere 457 break; 458 case DISCONNECT_MAP: 459 disconnectMap((BluetoothDevice)msg.obj); 460 break; 461 default: 462 break; 463 } 464 } 465 }; 466 467 468 public int getState() { 469 return mState; 470 } 471 472 public BluetoothDevice getRemoteDevice() { 473 return mRemoteDevice; 474 } 475 private void setState(int state) { 476 setState(state, BluetoothMap.RESULT_SUCCESS); 477 } 478 479 private synchronized void setState(int state, int result) { 480 if (state != mState) { 481 if (DEBUG) Log.d(TAG, "Map state " + mState + " -> " + state + ", result = " 482 + result); 483 int prevState = mState; 484 mState = state; 485 Intent intent = new Intent(BluetoothMap.ACTION_CONNECTION_STATE_CHANGED); 486 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); 487 intent.putExtra(BluetoothProfile.EXTRA_STATE, mState); 488 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice); 489 sendBroadcast(intent, BLUETOOTH_PERM); 490 AdapterService s = AdapterService.getAdapterService(); 491 if (s != null) { 492 s.onProfileConnectionStateChanged(mRemoteDevice, BluetoothProfile.MAP, 493 mState, prevState); 494 } 495 } 496 } 497 498 public static String getRemoteDeviceName() { 499 return sRemoteDeviceName; 500 } 501 502 public boolean disconnect(BluetoothDevice device) { 503 mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(DISCONNECT_MAP, 0, 0, device)); 504 return true; 505 } 506 507 public boolean disconnectMap(BluetoothDevice device) { 508 boolean result = false; 509 if (DEBUG) Log.d(TAG, "disconnectMap"); 510 if (getRemoteDevice().equals(device)) { 511 switch (mState) { 512 case BluetoothMap.STATE_CONNECTED: 513 if (mServerSession != null) { 514 mServerSession.close(); 515 mServerSession = null; 516 } 517 if(mBluetoothMnsObexClient != null) { 518 mBluetoothMnsObexClient.shutdown(); 519 mBluetoothMnsObexClient = null; 520 } 521 closeConnectionSocket(); 522 523 setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED); 524 result = true; 525 break; 526 default: 527 break; 528 } 529 } 530 return result; 531 } 532 533 public List<BluetoothDevice> getConnectedDevices() { 534 List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>(); 535 synchronized(this) { 536 if (mState == BluetoothMap.STATE_CONNECTED && mRemoteDevice != null) { 537 devices.add(mRemoteDevice); 538 } 539 } 540 return devices; 541 } 542 543 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 544 List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>(); 545 Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices(); 546 int connectionState; 547 synchronized (this) { 548 for (BluetoothDevice device : bondedDevices) { 549 ParcelUuid[] featureUuids = device.getUuids(); 550 if (!BluetoothUuid.containsAnyUuid(featureUuids, MAP_UUIDS)) { 551 continue; 552 } 553 connectionState = getConnectionState(device); 554 for(int i = 0; i < states.length; i++) { 555 if (connectionState == states[i]) { 556 deviceList.add(device); 557 } 558 } 559 } 560 } 561 return deviceList; 562 } 563 564 public int getConnectionState(BluetoothDevice device) { 565 synchronized(this) { 566 if (getState() == BluetoothMap.STATE_CONNECTED && getRemoteDevice().equals(device)) { 567 return BluetoothProfile.STATE_CONNECTED; 568 } else { 569 return BluetoothProfile.STATE_DISCONNECTED; 570 } 571 } 572 } 573 574 public boolean setPriority(BluetoothDevice device, int priority) { 575 Settings.Global.putInt(getContentResolver(), 576 Settings.Global.getBluetoothMapPriorityKey(device.getAddress()), 577 priority); 578 if (DEBUG) Log.d(TAG, "Saved priority " + device + " = " + priority); 579 return true; 580 } 581 582 public int getPriority(BluetoothDevice device) { 583 int priority = Settings.Global.getInt(getContentResolver(), 584 Settings.Global.getBluetoothMapPriorityKey(device.getAddress()), 585 BluetoothProfile.PRIORITY_UNDEFINED); 586 return priority; 587 } 588 589 @Override 590 protected IProfileServiceBinder initBinder() { 591 return new BluetoothMapBinder(this); 592 } 593 594 @Override 595 protected boolean start() { 596 if (DEBUG) Log.d(TAG, "start()"); 597 IntentFilter filter = new IntentFilter(); 598 filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY); 599 filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); 600 try { 601 registerReceiver(mMapReceiver, filter); 602 } catch (Exception e) { 603 Log.w(TAG,"Unable to register map receiver",e); 604 } 605 mInterrupted = false; 606 mAdapter = BluetoothAdapter.getDefaultAdapter(); 607 // start RFCOMM listener 608 mSessionStatusHandler.sendMessage(mSessionStatusHandler 609 .obtainMessage(START_LISTENER)); 610 return true; 611 } 612 613 @Override 614 protected boolean stop() { 615 if (DEBUG) Log.d(TAG, "stop()"); 616 try { 617 unregisterReceiver(mMapReceiver); 618 } catch (Exception e) { 619 Log.w(TAG,"Unable to unregister map receiver",e); 620 } 621 622 setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED); 623 closeService(); 624 return true; 625 } 626 627 public boolean cleanup() { 628 if (DEBUG) Log.d(TAG, "cleanup()"); 629 setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED); 630 closeService(); 631 return true; 632 } 633 634 private MapBroadcastReceiver mMapReceiver = new MapBroadcastReceiver(); 635 636 private class MapBroadcastReceiver extends BroadcastReceiver { 637 @Override 638 public void onReceive(Context context, Intent intent) { 639 if (DEBUG) Log.d(TAG, "onReceive"); 640 String action = intent.getAction(); 641 if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { 642 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 643 BluetoothAdapter.ERROR); 644 if (state == BluetoothAdapter.STATE_TURNING_OFF) { 645 if (DEBUG) Log.d(TAG, "STATE_TURNING_OFF"); 646 // Release all resources 647 closeService(); 648 } else if (state == BluetoothAdapter.STATE_ON) { 649 if (DEBUG) Log.d(TAG, "STATE_ON"); 650 mInterrupted = false; 651 // start RFCOMM listener 652 mSessionStatusHandler.sendMessage(mSessionStatusHandler 653 .obtainMessage(START_LISTENER)); 654 } 655 } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) { 656 int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, 657 BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS); 658 if (DEBUG) Log.d(TAG, "Received ACTION_CONNECTION_ACCESS_REPLY:" + 659 requestType + ":" + isWaitingAuthorization); 660 if ((!isWaitingAuthorization) || 661 (requestType != BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS)) { 662 // this reply is not for us 663 return; 664 } 665 666 isWaitingAuthorization = false; 667 668 if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT, 669 BluetoothDevice.CONNECTION_ACCESS_NO) == 670 BluetoothDevice.CONNECTION_ACCESS_YES) { 671 //bluetooth connection accepted by user 672 if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) { 673 boolean result = mRemoteDevice.setTrust(true); 674 if (DEBUG) Log.d(TAG, "setTrust() result=" + result); 675 } 676 try { 677 if (mConnSocket != null) { 678 // start obex server and rfcomm connection 679 startObexServerSession(); 680 } else { 681 stopObexServerSession(); 682 } 683 } catch (IOException ex) { 684 Log.e(TAG, "Caught the error: " + ex.toString()); 685 } 686 } else { 687 stopObexServerSession(); 688 } 689 } 690 } 691 }; 692 693 //Binder object: Must be static class or memory leak may occur 694 /** 695 * This class implements the IBluetoothMap interface - or actually it validates the 696 * preconditions for calling the actual functionality in the MapService, and calls it. 697 */ 698 private static class BluetoothMapBinder extends IBluetoothMap.Stub 699 implements IProfileServiceBinder { 700 private BluetoothMapService mService; 701 702 private BluetoothMapService getService() { 703 if (!Utils.checkCaller()) { 704 Log.w(TAG,"MAP call not allowed for non-active user"); 705 return null; 706 } 707 708 if (mService != null && mService.isAvailable()) { 709 mService.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 710 return mService; 711 } 712 return null; 713 } 714 715 BluetoothMapBinder(BluetoothMapService service) { 716 if (VERBOSE) Log.v(TAG, "BluetoothMapBinder()"); 717 mService = service; 718 } 719 720 public boolean cleanup() { 721 mService = null; 722 return true; 723 } 724 725 public int getState() { 726 if (VERBOSE) Log.v(TAG, "getState()"); 727 BluetoothMapService service = getService(); 728 if (service == null) return BluetoothMap.STATE_DISCONNECTED; 729 return getService().getState(); 730 } 731 732 public BluetoothDevice getClient() { 733 if (VERBOSE) Log.v(TAG, "getClient()"); 734 BluetoothMapService service = getService(); 735 if (service == null) return null; 736 Log.v(TAG, "getClient() - returning " + service.getRemoteDevice()); 737 return service.getRemoteDevice(); 738 } 739 740 public boolean isConnected(BluetoothDevice device) { 741 if (VERBOSE) Log.v(TAG, "isConnected()"); 742 BluetoothMapService service = getService(); 743 if (service == null) return false; 744 return service.getState() == BluetoothMap.STATE_CONNECTED && service.getRemoteDevice().equals(device); 745 } 746 747 public boolean connect(BluetoothDevice device) { 748 if (VERBOSE) Log.v(TAG, "connect()"); 749 BluetoothMapService service = getService(); 750 if (service == null) return false; 751 return false; 752 } 753 754 public boolean disconnect(BluetoothDevice device) { 755 if (VERBOSE) Log.v(TAG, "disconnect()"); 756 BluetoothMapService service = getService(); 757 if (service == null) return false; 758 return service.disconnect(device); 759 } 760 761 public List<BluetoothDevice> getConnectedDevices() { 762 if (VERBOSE) Log.v(TAG, "getConnectedDevices()"); 763 BluetoothMapService service = getService(); 764 if (service == null) return new ArrayList<BluetoothDevice>(0); 765 return service.getConnectedDevices(); 766 } 767 768 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 769 if (VERBOSE) Log.v(TAG, "getDevicesMatchingConnectionStates()"); 770 BluetoothMapService service = getService(); 771 if (service == null) return new ArrayList<BluetoothDevice>(0); 772 return service.getDevicesMatchingConnectionStates(states); 773 } 774 775 public int getConnectionState(BluetoothDevice device) { 776 if (VERBOSE) Log.v(TAG, "getConnectionState()"); 777 BluetoothMapService service = getService(); 778 if (service == null) return BluetoothProfile.STATE_DISCONNECTED; 779 return service.getConnectionState(device); 780 } 781 782 public boolean setPriority(BluetoothDevice device, int priority) { 783 BluetoothMapService service = getService(); 784 if (service == null) return false; 785 return service.setPriority(device, priority); 786 } 787 788 public int getPriority(BluetoothDevice device) { 789 BluetoothMapService service = getService(); 790 if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED; 791 return service.getPriority(device); 792 } 793 }; 794} 795