BluetoothMapService.java revision e6564029f132077c8a4877431a95899db201e506
1/* 2* Copyright (C) 2014 Samsung System LSI 3* Licensed under the Apache License, Version 2.0 (the "License"); 4* you may not use this file except in compliance with the License. 5* You may obtain a copy of the License at 6* 7* http://www.apache.org/licenses/LICENSE-2.0 8* 9* Unless required by applicable law or agreed to in writing, software 10* distributed under the License is distributed on an "AS IS" BASIS, 11* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12* See the License for the specific language governing permissions and 13* limitations under the License. 14*/ 15 16package com.android.bluetooth.map; 17 18import android.app.AlarmManager; 19import android.app.PendingIntent; 20import android.bluetooth.BluetoothAdapter; 21import android.bluetooth.BluetoothDevice; 22import android.bluetooth.BluetoothMap; 23import android.bluetooth.BluetoothProfile; 24import android.bluetooth.BluetoothUuid; 25import android.bluetooth.IBluetoothMap; 26import android.bluetooth.SdpMnsRecord; 27import android.content.BroadcastReceiver; 28import android.content.Context; 29import android.content.Intent; 30import android.content.IntentFilter; 31import android.content.IntentFilter.MalformedMimeTypeException; 32import android.os.Handler; 33import android.os.Message; 34import android.os.ParcelUuid; 35import android.os.PowerManager; 36import android.os.RemoteException; 37import android.provider.Settings; 38import android.text.TextUtils; 39import android.util.Log; 40import android.util.SparseArray; 41 42import com.android.bluetooth.Utils; 43import com.android.bluetooth.btservice.AdapterService; 44import com.android.bluetooth.btservice.ProfileService; 45import com.android.bluetooth.btservice.ProfileService.IProfileServiceBinder; 46import com.android.bluetooth.R; 47 48import java.io.IOException; 49import java.util.ArrayList; 50import java.util.HashMap; 51import java.util.List; 52import java.util.Set; 53 54public class BluetoothMapService extends ProfileService { 55 private static final String TAG = "BluetoothMapService"; 56 57 /** 58 * To enable MAP DEBUG/VERBOSE logging - run below cmd in adb shell, and 59 * restart com.android.bluetooth process. only enable DEBUG log: 60 * "setprop log.tag.BluetoothMapService DEBUG"; enable both VERBOSE and 61 * DEBUG log: "setprop log.tag.BluetoothMapService VERBOSE" 62 */ 63 64 public static final boolean DEBUG = true; //FIXME set to false; 65 66 public static final boolean VERBOSE = false; 67 68 /** 69 * Intent indicating timeout for user confirmation, which is sent to 70 * BluetoothMapActivity 71 */ 72 public static final String USER_CONFIRM_TIMEOUT_ACTION = 73 "com.android.bluetooth.map.USER_CONFIRM_TIMEOUT"; 74 private static final int USER_CONFIRM_TIMEOUT_VALUE = 25000; 75 76 /** Intent indicating that the email settings activity should be opened*/ 77 public static final String ACTION_SHOW_MAPS_SETTINGS = 78 "android.btmap.intent.action.SHOW_MAPS_SETTINGS"; 79 80 public static final int MSG_SERVERSESSION_CLOSE = 5000; 81 82 public static final int MSG_SESSION_ESTABLISHED = 5001; 83 84 public static final int MSG_SESSION_DISCONNECTED = 5002; 85 86 public static final int MSG_MAS_CONNECT = 5003; // Send at MAS connect, including the MAS_ID 87 public static final int MSG_MAS_CONNECT_CANCEL = 5004; // Send at auth. declined 88 89 public static final int MSG_ACQUIRE_WAKE_LOCK = 5005; 90 91 public static final int MSG_RELEASE_WAKE_LOCK = 5006; 92 93 public static final int MSG_MNS_SDP_SEARCH = 5007; 94 95 public static final int MSG_OBSERVER_REGISTRATION = 5008; 96 97 private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; 98 99 private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; 100 101 private static final int START_LISTENER = 1; 102 103 private static final int USER_TIMEOUT = 2; 104 105 private static final int DISCONNECT_MAP = 3; 106 107 private static final int SHUTDOWN = 4; 108 109 private static final int RELEASE_WAKE_LOCK_DELAY = 10000; 110 111 private PowerManager.WakeLock mWakeLock = null; 112 113 private static final int UPDATE_MAS_INSTANCES = 5; 114 115 public static final int UPDATE_MAS_INSTANCES_ACCOUNT_ADDED = 0; 116 public static final int UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED = 1; 117 public static final int UPDATE_MAS_INSTANCES_ACCOUNT_RENAMED = 2; 118 public static final int UPDATE_MAS_INSTANCES_ACCOUNT_DISCONNECT = 3; 119 120 private static final int MAS_ID_SMS_MMS = 0; 121 122 private BluetoothAdapter mAdapter; 123 124 private BluetoothMnsObexClient mBluetoothMnsObexClient = null; 125 126 /* mMasInstances: A list of the active MasInstances with the key being the MasId */ 127 private SparseArray<BluetoothMapMasInstance> mMasInstances = 128 new SparseArray<BluetoothMapMasInstance>(1); 129 /* mMasInstanceMap: A list of the active MasInstances with the key being the account */ 130 private HashMap<BluetoothMapAccountItem, BluetoothMapMasInstance> mMasInstanceMap = 131 new HashMap<BluetoothMapAccountItem, BluetoothMapMasInstance>(1); 132 133 private BluetoothDevice mRemoteDevice = null; // The remote connected device - protect access 134 135 private ArrayList<BluetoothMapAccountItem> mEnabledAccounts = null; 136 private static String sRemoteDeviceName = null; 137 138 private int mState; 139 private BluetoothMapAppObserver mAppObserver = null; 140 private AlarmManager mAlarmManager = null; 141 142 private boolean mIsWaitingAuthorization = false; 143 private boolean mRemoveTimeoutMsg = false; 144 private int mPermission = BluetoothDevice.ACCESS_UNKNOWN; 145 private boolean mAccountChanged = false; 146 private boolean mSdpSearchInitiated = false; 147 SdpMnsRecord mMnsRecord = null; 148 149 // package and class name to which we send intent to check phone book access permission 150 private static final String ACCESS_AUTHORITY_PACKAGE = "com.android.settings"; 151 private static final String ACCESS_AUTHORITY_CLASS = 152 "com.android.settings.bluetooth.BluetoothPermissionRequest"; 153 154 private static final ParcelUuid[] MAP_UUIDS = { 155 BluetoothUuid.MAP, 156 BluetoothUuid.MNS, 157 }; 158 159 public BluetoothMapService() { 160 mState = BluetoothMap.STATE_DISCONNECTED; 161 162 } 163 164 165 private final void closeService() { 166 if (DEBUG) Log.d(TAG, "MAP Service closeService in"); 167 168 if (mBluetoothMnsObexClient != null) { 169 mBluetoothMnsObexClient.shutdown(); 170 mBluetoothMnsObexClient = null; 171 } 172 173 for(int i=0, c=mMasInstances.size(); i < c; i++) { 174 mMasInstances.valueAt(i).shutdown(); 175 } 176 mMasInstances.clear(); 177 178 if (mSessionStatusHandler != null) { 179 mSessionStatusHandler.removeCallbacksAndMessages(null); 180 } 181 182 mIsWaitingAuthorization = false; 183 mPermission = BluetoothDevice.ACCESS_UNKNOWN; 184 setState(BluetoothMap.STATE_DISCONNECTED); 185 186 if (mWakeLock != null) { 187 mWakeLock.release(); 188 if (VERBOSE) Log.v(TAG, "CloseService(): Release Wake Lock"); 189 mWakeLock = null; 190 } 191 mRemoteDevice = null; 192 193 if (VERBOSE) Log.v(TAG, "MAP Service closeService out"); 194 } 195 196 /** 197 * Starts the RFComm listener threads for each MAS 198 * @throws IOException 199 */ 200 private final void startRfcommSocketListeners(int masId) { 201 if(masId == -1) { 202 for(int i=0, c=mMasInstances.size(); i < c; i++) { 203 mMasInstances.valueAt(i).startRfcommSocketListener(); 204 } 205 } else { 206 BluetoothMapMasInstance masInst = mMasInstances.get(masId); // returns null for -1 207 if(masInst != null) { 208 masInst.startRfcommSocketListener(); 209 } else { 210 Log.w(TAG, "startRfcommSocketListeners(): Invalid MasId: " + masId); 211 } 212 } 213 } 214 215 /** 216 * Start a MAS instance for SMS/MMS and each e-mail account. 217 */ 218 private final void startObexServerSessions() { 219 if (DEBUG) Log.d(TAG, "Map Service START ObexServerSessions()"); 220 221 // acquire the wakeLock before start Obex transaction thread 222 if (mWakeLock == null) { 223 PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE); 224 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 225 "StartingObexMapTransaction"); 226 mWakeLock.setReferenceCounted(false); 227 mWakeLock.acquire(); 228 if (VERBOSE) Log.v(TAG, "startObexSessions(): Acquire Wake Lock"); 229 } 230 231 if(mBluetoothMnsObexClient == null) { 232 mBluetoothMnsObexClient = 233 new BluetoothMnsObexClient(mRemoteDevice, mMnsRecord, mSessionStatusHandler); 234 } 235 236 boolean connected = false; 237 for(int i=0, c=mMasInstances.size(); i < c; i++) { 238 try { 239 if(mMasInstances.valueAt(i) 240 .startObexServerSession(mBluetoothMnsObexClient) == true) { 241 connected = true; 242 } 243 } catch (IOException e) { 244 Log.w(TAG,"IOException occured while starting an obexServerSession restarting" + 245 " the listener",e); 246 mMasInstances.valueAt(i).restartObexServerSession(); 247 } catch (RemoteException e) { 248 Log.w(TAG,"RemoteException occured while starting an obexServerSession restarting" + 249 " the listener",e); 250 mMasInstances.valueAt(i).restartObexServerSession(); 251 } 252 } 253 if(connected) { 254 setState(BluetoothMap.STATE_CONNECTED); 255 } 256 257 mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK); 258 mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler 259 .obtainMessage(MSG_RELEASE_WAKE_LOCK), RELEASE_WAKE_LOCK_DELAY); 260 261 if (VERBOSE) Log.v(TAG, "startObexServerSessions() success!"); 262 } 263 264 public Handler getHandler() { 265 return mSessionStatusHandler; 266 } 267 268 /** 269 * Restart a MAS instances. 270 * @param masId use -1 to stop all instances 271 */ 272 private void stopObexServerSessions(int masId) { 273 if (DEBUG) Log.d(TAG, "MAP Service STOP ObexServerSessions()"); 274 275 boolean lastMasInst = true; 276 277 if(masId != -1) { 278 for(int i=0, c=mMasInstances.size(); i < c; i++) { 279 BluetoothMapMasInstance masInst = mMasInstances.valueAt(i); 280 if(masInst.getMasId() != masId && masInst.isStarted()) { 281 lastMasInst = false; 282 } 283 } 284 } // Else just close down it all 285 286 /* Shutdown the MNS client - currently must happen before MAS close */ 287 if(mBluetoothMnsObexClient != null && lastMasInst) { 288 mBluetoothMnsObexClient.shutdown(); 289 mBluetoothMnsObexClient = null; 290 } 291 292 BluetoothMapMasInstance masInst = mMasInstances.get(masId); // returns null for -1 293 if(masInst != null) { 294 masInst.restartObexServerSession(); 295 } else { 296 for(int i=0, c=mMasInstances.size(); i < c; i++) { 297 mMasInstances.valueAt(i).restartObexServerSession(); 298 } 299 } 300 301 if(lastMasInst) { 302 setState(BluetoothMap.STATE_DISCONNECTED); 303 mPermission = BluetoothDevice.ACCESS_UNKNOWN; 304 mRemoteDevice = null; 305 if(mAccountChanged) { 306 updateMasInstances(UPDATE_MAS_INSTANCES_ACCOUNT_DISCONNECT); 307 } 308 } 309 310 // Release the wake lock at disconnect 311 if (mWakeLock != null && lastMasInst) { 312 mSessionStatusHandler.removeMessages(MSG_ACQUIRE_WAKE_LOCK); 313 mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK); 314 mWakeLock.release(); 315 if (VERBOSE) Log.v(TAG, "stopObexServerSessions(): Release Wake Lock"); 316 } 317 } 318 319 private final Handler mSessionStatusHandler = new Handler() { 320 @Override 321 public void handleMessage(Message msg) { 322 if (VERBOSE) Log.v(TAG, "Handler(): got msg=" + msg.what); 323 324 switch (msg.what) { 325 case UPDATE_MAS_INSTANCES: 326 updateMasInstancesHandler(); 327 break; 328 case START_LISTENER: 329 if (mAdapter.isEnabled()) { 330 startRfcommSocketListeners(msg.arg1); 331 } 332 break; 333 case MSG_MAS_CONNECT: 334 onConnectHandler(msg.arg1); 335 break; 336 case MSG_MAS_CONNECT_CANCEL: 337 /* TODO: We need to handle this by accepting the connection and reject at 338 * OBEX level, by using ObexRejectServer - add timeout to handle clients not 339 * closing the transport channel. 340 */ 341 stopObexServerSessions(-1); 342 break; 343 case USER_TIMEOUT: 344 if (mIsWaitingAuthorization){ 345 Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL); 346 intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS); 347 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice); 348 intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, 349 BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS); 350 sendBroadcast(intent); 351 cancelUserTimeoutAlarm(); 352 mIsWaitingAuthorization = false; 353 stopObexServerSessions(-1); 354 } 355 break; 356 case MSG_SERVERSESSION_CLOSE: 357 stopObexServerSessions(msg.arg1); 358 break; 359 case MSG_SESSION_ESTABLISHED: 360 break; 361 case MSG_SESSION_DISCONNECTED: 362 // handled elsewhere 363 break; 364 case DISCONNECT_MAP: 365 disconnectMap((BluetoothDevice)msg.obj); 366 break; 367 case SHUTDOWN: 368 /* Ensure to call close from this handler to avoid starting new stuff 369 because of pending messages */ 370 closeService(); 371 break; 372 case MSG_ACQUIRE_WAKE_LOCK: 373 if (VERBOSE) Log.v(TAG, "Acquire Wake Lock request message"); 374 if (mWakeLock == null) { 375 PowerManager pm = (PowerManager)getSystemService( 376 Context.POWER_SERVICE); 377 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 378 "StartingObexMapTransaction"); 379 mWakeLock.setReferenceCounted(false); 380 } 381 if(!mWakeLock.isHeld()) { 382 mWakeLock.acquire(); 383 if (DEBUG) Log.d(TAG, " Acquired Wake Lock by message"); 384 } 385 mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK); 386 mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler 387 .obtainMessage(MSG_RELEASE_WAKE_LOCK), RELEASE_WAKE_LOCK_DELAY); 388 break; 389 case MSG_RELEASE_WAKE_LOCK: 390 if (VERBOSE) Log.v(TAG, "Release Wake Lock request message"); 391 if (mWakeLock != null) { 392 mWakeLock.release(); 393 if (DEBUG) Log.d(TAG, " Released Wake Lock by message"); 394 } 395 break; 396 case MSG_MNS_SDP_SEARCH: 397 if (mRemoteDevice != null) { 398 if (DEBUG) Log.d(TAG,"MNS SDP Initiate Search .."); 399 mRemoteDevice.sdpSearch(BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS); 400 } else { 401 Log.w(TAG, "remoteDevice info not available"); 402 } 403 break; 404 case MSG_OBSERVER_REGISTRATION: 405 if (DEBUG) Log.d(TAG,"ContentObserver Registration MASID: " + msg.arg1 406 + " Enable: " + msg.arg2); 407 BluetoothMapMasInstance masInst = mMasInstances.get(msg.arg1); 408 if (masInst != null) { 409 try { 410 if (msg.arg2 == BluetoothMapAppParams.NOTIFICATION_STATUS_YES) { 411 masInst.mObserver.registerObserver(); 412 } else { 413 masInst.mObserver.unregisterObserver(); 414 } 415 } catch (RemoteException e) { 416 Log.e(TAG,"ContentObserverRegistarion Failed: "+ e); 417 } 418 } 419 break; 420 default: 421 break; 422 } 423 } 424 }; 425 426 private void onConnectHandler(int masId) { 427 if (mIsWaitingAuthorization == true || mRemoteDevice == null 428 || mSdpSearchInitiated == true) { 429 return; 430 } 431 BluetoothMapMasInstance masInst = mMasInstances.get(masId); 432 // Need to ensure we are still allowed. 433 if (DEBUG) Log.d(TAG, "mPermission = " + mPermission); 434 if (mPermission == BluetoothDevice.ACCESS_ALLOWED) { 435 try { 436 if (VERBOSE) Log.v(TAG, "incoming connection accepted from: " 437 + sRemoteDeviceName + " automatically as trusted device"); 438 if (mBluetoothMnsObexClient != null && masInst != null) { 439 masInst.startObexServerSession(mBluetoothMnsObexClient); 440 } else { 441 startObexServerSessions(); 442 } 443 } catch (IOException ex) { 444 Log.e(TAG, "catch IOException starting obex server session", ex); 445 } catch (RemoteException ex) { 446 Log.e(TAG, "catch RemoteException starting obex server session", ex); 447 } 448 } 449 } 450 451 public int getState() { 452 return mState; 453 } 454 455 public BluetoothDevice getRemoteDevice() { 456 return mRemoteDevice; 457 } 458 private void setState(int state) { 459 setState(state, BluetoothMap.RESULT_SUCCESS); 460 } 461 462 private synchronized void setState(int state, int result) { 463 if (state != mState) { 464 if (DEBUG) Log.d(TAG, "Map state " + mState + " -> " + state + ", result = " 465 + result); 466 int prevState = mState; 467 mState = state; 468 Intent intent = new Intent(BluetoothMap.ACTION_CONNECTION_STATE_CHANGED); 469 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); 470 intent.putExtra(BluetoothProfile.EXTRA_STATE, mState); 471 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice); 472 sendBroadcast(intent, BLUETOOTH_PERM); 473 AdapterService s = AdapterService.getAdapterService(); 474 if (s != null) { 475 s.onProfileConnectionStateChanged(mRemoteDevice, BluetoothProfile.MAP, 476 mState, prevState); 477 } 478 } 479 } 480 481 public static String getRemoteDeviceName() { 482 return sRemoteDeviceName; 483 } 484 485 public boolean disconnect(BluetoothDevice device) { 486 mSessionStatusHandler.sendMessage(mSessionStatusHandler 487 .obtainMessage(DISCONNECT_MAP, 0, 0, device)); 488 return true; 489 } 490 491 public boolean disconnectMap(BluetoothDevice device) { 492 boolean result = false; 493 if (DEBUG) Log.d(TAG, "disconnectMap"); 494 if (getRemoteDevice().equals(device)) { 495 switch (mState) { 496 case BluetoothMap.STATE_CONNECTED: 497 /* Disconnect all connections and restart all MAS instances */ 498 stopObexServerSessions(-1); 499 result = true; 500 break; 501 default: 502 break; 503 } 504 } 505 return result; 506 } 507 508 public List<BluetoothDevice> getConnectedDevices() { 509 List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>(); 510 synchronized(this) { 511 if (mState == BluetoothMap.STATE_CONNECTED && mRemoteDevice != null) { 512 devices.add(mRemoteDevice); 513 } 514 } 515 return devices; 516 } 517 518 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 519 List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>(); 520 Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices(); 521 int connectionState; 522 synchronized (this) { 523 for (BluetoothDevice device : bondedDevices) { 524 ParcelUuid[] featureUuids = device.getUuids(); 525 if (!BluetoothUuid.containsAnyUuid(featureUuids, MAP_UUIDS)) { 526 continue; 527 } 528 connectionState = getConnectionState(device); 529 for(int i = 0; i < states.length; i++) { 530 if (connectionState == states[i]) { 531 deviceList.add(device); 532 } 533 } 534 } 535 } 536 return deviceList; 537 } 538 539 public int getConnectionState(BluetoothDevice device) { 540 synchronized(this) { 541 if (getState() == BluetoothMap.STATE_CONNECTED && getRemoteDevice().equals(device)) { 542 return BluetoothProfile.STATE_CONNECTED; 543 } else { 544 return BluetoothProfile.STATE_DISCONNECTED; 545 } 546 } 547 } 548 549 public boolean setPriority(BluetoothDevice device, int priority) { 550 Settings.Global.putInt(getContentResolver(), 551 Settings.Global.getBluetoothMapPriorityKey(device.getAddress()), 552 priority); 553 if (VERBOSE) Log.v(TAG, "Saved priority " + device + " = " + priority); 554 return true; 555 } 556 557 public int getPriority(BluetoothDevice device) { 558 int priority = Settings.Global.getInt(getContentResolver(), 559 Settings.Global.getBluetoothMapPriorityKey(device.getAddress()), 560 BluetoothProfile.PRIORITY_UNDEFINED); 561 return priority; 562 } 563 564 @Override 565 protected IProfileServiceBinder initBinder() { 566 return new BluetoothMapBinder(this); 567 } 568 569 @Override 570 protected boolean start() { 571 if (DEBUG) Log.d(TAG, "start()"); 572 IntentFilter filter = new IntentFilter(); 573 filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY); 574 filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); 575 filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); 576 filter.addAction(BluetoothDevice.ACTION_SDP_RECORD); 577 filter.addAction(ACTION_SHOW_MAPS_SETTINGS); 578 filter.addAction(USER_CONFIRM_TIMEOUT_ACTION); 579 580 // We need two filters, since Type only applies to the ACTION_MESSAGE_SENT 581 IntentFilter filterMessageSent = new IntentFilter(); 582 filterMessageSent.addAction(BluetoothMapContentObserver.ACTION_MESSAGE_SENT); 583 try{ 584 filterMessageSent.addDataType("message/*"); 585 } catch (MalformedMimeTypeException e) { 586 Log.e(TAG, "Wrong mime type!!!", e); 587 } 588 589 try { 590 registerReceiver(mMapReceiver, filter); 591 registerReceiver(mMapReceiver, filterMessageSent); 592 } catch (Exception e) { 593 Log.w(TAG,"Unable to register map receiver",e); 594 } 595 mAdapter = BluetoothAdapter.getDefaultAdapter(); 596 mAppObserver = new BluetoothMapAppObserver(this, this); 597 598 mEnabledAccounts = mAppObserver.getEnabledAccountItems(); 599 // Uses mEnabledAccounts, hence getEnabledAccountItems() must be called before this. 600 createMasInstances(); 601 602 // start RFCOMM listener 603 sendStartListenerMessage(-1); 604 return true; 605 } 606 607 /** 608 * Call this to trigger an update of the MAS instance list. 609 * No changes will be applied unless in disconnected state 610 */ 611 public void updateMasInstances(int action) { 612 mSessionStatusHandler.obtainMessage (UPDATE_MAS_INSTANCES, 613 action, 0).sendToTarget(); 614 } 615 616 /** 617 * Update the active MAS Instances according the difference between mEnabledDevices 618 * and the current list of accounts. 619 * Will only make changes if state is disconnected. 620 * 621 * How it works: 622 * 1) Build lists of account changes from last update of mEnabledAccounts. 623 * newAccounts - accounts that have been enabled since mEnabledAccounts 624 * was last updated. 625 * removedAccounts - Accounts that is on mEnabledAccounts, but no longer 626 * enabled. 627 * enabledAccounts - A new list of all enabled accounts. 628 * 2) Stop and remove all MasInstances on the remove list 629 * 3) Add and start MAS instances for accounts on the new list. 630 * Called at: 631 * - Each change in accounts 632 * - Each disconnect - before MasInstances restart. 633 * 634 * @return true is any changes are made, false otherwise. 635 */ 636 private boolean updateMasInstancesHandler(){ 637 if (DEBUG) Log.d(TAG,"updateMasInstancesHandler() state = " + getState()); 638 boolean changed = false; 639 640 if(getState() == BluetoothMap.STATE_DISCONNECTED) { 641 ArrayList<BluetoothMapAccountItem> newAccountList = 642 mAppObserver.getEnabledAccountItems(); 643 ArrayList<BluetoothMapAccountItem> newAccounts = null; 644 ArrayList<BluetoothMapAccountItem> removedAccounts = null; 645 newAccounts = new ArrayList<BluetoothMapAccountItem>(); 646 removedAccounts = mEnabledAccounts; // reuse the current enabled list, to track removed 647 // accounts 648 for(BluetoothMapAccountItem account: newAccountList) { 649 if(!removedAccounts.remove(account)) { 650 newAccounts.add(account); 651 } 652 } 653 654 if(removedAccounts != null) { 655 /* Remove all disabled/removed accounts */ 656 for(BluetoothMapAccountItem account : removedAccounts) { 657 BluetoothMapMasInstance masInst = mMasInstanceMap.remove(account); 658 if (VERBOSE) Log.v(TAG," Removing account: " + account + " masInst = " + masInst); 659 if(masInst != null) { 660 masInst.shutdown(); 661 mMasInstances.remove(masInst.getMasId()); 662 changed = true; 663 } 664 } 665 } 666 667 if(newAccounts != null) { 668 /* Add any newly created accounts */ 669 for(BluetoothMapAccountItem account : newAccounts) { 670 if (VERBOSE) Log.v(TAG," Adding account: " + account); 671 int masId = getNextMasId(); 672 BluetoothMapMasInstance newInst = 673 new BluetoothMapMasInstance(this, 674 this, 675 account, 676 masId, 677 false); 678 mMasInstances.append(masId, newInst); 679 mMasInstanceMap.put(account, newInst); 680 changed = true; 681 /* Start the new instance */ 682 if (mAdapter.isEnabled()) { 683 newInst.startRfcommSocketListener(); 684 } 685 } 686 } 687 mEnabledAccounts = newAccountList; 688 if (VERBOSE) { 689 Log.v(TAG," Enabled accounts:"); 690 for(BluetoothMapAccountItem account : mEnabledAccounts) { 691 Log.v(TAG, " " + account); 692 } 693 Log.v(TAG," Active MAS instances:"); 694 for(int i=0, c=mMasInstances.size(); i < c; i++) { 695 BluetoothMapMasInstance masInst = mMasInstances.valueAt(i); 696 Log.v(TAG, " " + masInst); 697 } 698 } 699 mAccountChanged = false; 700 } else { 701 mAccountChanged = true; 702 } 703 return changed; 704 } 705 706 /** 707 * Will return the next MasId to use. 708 * Will ensure the key returned is greater than the largest key in use. 709 * Unless the key 255 is in use, in which case the first free masId 710 * will be returned. 711 * @return 712 */ 713 private int getNextMasId() { 714 /* Find the largest masId in use */ 715 int largestMasId = 0; 716 for(int i=0, c=mMasInstances.size(); i < c; i++) { 717 int masId = mMasInstances.keyAt(i); 718 if(masId > largestMasId) { 719 largestMasId = masId; 720 } 721 } 722 if(largestMasId < 0xff) { 723 return largestMasId + 1; 724 } 725 /* If 0xff is already in use, wrap and choose the first free 726 * MasId. */ 727 for(int i = 1; i <= 0xff; i++) { 728 if(mMasInstances.get(i) == null) { 729 return i; 730 } 731 } 732 return 0xff; // This will never happen, as we only allow 10 e-mail accounts to be enabled 733 } 734 735 private void createMasInstances() { 736 int masId = MAS_ID_SMS_MMS; 737 738 // Add the SMS/MMS instance 739 BluetoothMapMasInstance smsMmsInst = 740 new BluetoothMapMasInstance(this, 741 this, 742 null, 743 masId, 744 true); 745 mMasInstances.append(masId, smsMmsInst); 746 mMasInstanceMap.put(null, smsMmsInst); 747 748 // get list of accounts already set to be visible through MAP 749 for(BluetoothMapAccountItem account : mEnabledAccounts) { 750 masId++; // SMS/MMS is masId=0, increment before adding next 751 BluetoothMapMasInstance newInst = 752 new BluetoothMapMasInstance(this, 753 this, 754 account, 755 masId, 756 false); 757 mMasInstances.append(masId, newInst); 758 mMasInstanceMap.put(account, newInst); 759 } 760 } 761 762 @Override 763 protected boolean stop() { 764 if (DEBUG) Log.d(TAG, "stop()"); 765 try { 766 unregisterReceiver(mMapReceiver); 767 mAppObserver.shutdown(); 768 } catch (Exception e) { 769 Log.w(TAG,"Unable to unregister map receiver",e); 770 } 771 772 setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED); 773 sendShutdownMessage(); 774 return true; 775 } 776 777 public boolean cleanup() { 778 if (DEBUG) Log.d(TAG, "cleanup()"); 779 setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED); 780 // TODO: Change to use message? - do we need to wait for completion? 781 closeService(); 782 return true; 783 } 784 785 /** 786 * Called from each MAS instance when a connection is received. 787 * @param remoteDevice The device connecting 788 * @param masInst a reference to the calling MAS instance. 789 * @return 790 */ 791 public boolean onConnect(BluetoothDevice remoteDevice, BluetoothMapMasInstance masInst) { 792 boolean sendIntent = false; 793 boolean cancelConnection = false; 794 795 // As this can be called from each MasInstance, we need to lock access to member variables 796 synchronized(this) { 797 if (mRemoteDevice == null) { 798 mRemoteDevice = remoteDevice; 799 sRemoteDeviceName = mRemoteDevice.getName(); 800 // In case getRemoteName failed and return null 801 if (TextUtils.isEmpty(sRemoteDeviceName)) { 802 sRemoteDeviceName = getString(R.string.defaultname); 803 } 804 805 mPermission = mRemoteDevice.getMessageAccessPermission(); 806 if (mPermission == BluetoothDevice.ACCESS_UNKNOWN) { 807 sendIntent = true; 808 mIsWaitingAuthorization = true; 809 setUserTimeoutAlarm(); 810 } else if (mPermission == BluetoothDevice.ACCESS_REJECTED) { 811 cancelConnection = true; 812 } else if(mPermission == BluetoothDevice.ACCESS_ALLOWED) { 813 mRemoteDevice.sdpSearch(BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS); 814 mSdpSearchInitiated = true; 815 } 816 } else if (!mRemoteDevice.equals(remoteDevice)) { 817 Log.w(TAG, "Unexpected connection from a second Remote Device received. name: " + 818 ((remoteDevice==null)?"unknown":remoteDevice.getName())); 819 return false; /* The connecting device is different from what is already 820 connected, reject the connection. */ 821 } // Else second connection to same device, just continue 822 } 823 824 if (sendIntent) { 825 // This will trigger Settings app's dialog. 826 Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST); 827 intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS); 828 intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, 829 BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS); 830 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice); 831 sendOrderedBroadcast(intent, BLUETOOTH_ADMIN_PERM); 832 833 if (VERBOSE) Log.v(TAG, "waiting for authorization for connection from: " 834 + sRemoteDeviceName); 835 //Queue USER_TIMEOUT to disconnect MAP OBEX session. If user doesn't 836 //accept or reject authorization request 837 } else if (cancelConnection) { 838 sendConnectCancelMessage(); 839 } else if (mPermission == BluetoothDevice.ACCESS_ALLOWED) { 840 /* Signal to the service that we have a incoming connection. */ 841 sendConnectMessage(masInst.getMasId()); 842 } 843 return true; 844 }; 845 846 847 private void setUserTimeoutAlarm(){ 848 if (DEBUG) Log.d(TAG,"SetUserTimeOutAlarm()"); 849 if(mAlarmManager == null){ 850 mAlarmManager =(AlarmManager) this.getSystemService (Context.ALARM_SERVICE); 851 } 852 mRemoveTimeoutMsg = true; 853 Intent timeoutIntent = 854 new Intent(USER_CONFIRM_TIMEOUT_ACTION); 855 PendingIntent pIntent = PendingIntent.getBroadcast(this, 0, timeoutIntent, 0); 856 mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 857 USER_CONFIRM_TIMEOUT_VALUE,pIntent); 858 } 859 860 private void cancelUserTimeoutAlarm(){ 861 if (DEBUG) Log.d(TAG,"cancelUserTimeOutAlarm()"); 862 Intent intent = new Intent(this, BluetoothMapService.class); 863 PendingIntent sender = PendingIntent.getBroadcast(this, 0, intent, 0); 864 AlarmManager alarmManager = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE); 865 alarmManager.cancel(sender); 866 mRemoveTimeoutMsg = false; 867 } 868 869 /** 870 * Start the incoming connection listeners for a MAS ID 871 * @param masId the MasID to start. Use -1 to start all listeners. 872 */ 873 public void sendStartListenerMessage(int masId) { 874 if(mSessionStatusHandler != null) { 875 Message msg = mSessionStatusHandler.obtainMessage(START_LISTENER, masId, 0); 876 /* We add a small delay here to ensure the call returns true before this message is 877 * handled. It seems wrong to add a delay, but the alternative is to build a lock 878 * system to handle synchronization, which isn't nice either... */ 879 mSessionStatusHandler.sendMessageDelayed(msg, 20); 880 } // Can only be null during shutdown 881 } 882 883 private void sendConnectMessage(int masId) { 884 if(mSessionStatusHandler != null) { 885 Message msg = mSessionStatusHandler.obtainMessage(MSG_MAS_CONNECT, masId, 0); 886 /* We add a small delay here to ensure onConnect returns true before this message is 887 * handled. It seems wrong, but the alternative is to store a reference to the 888 * connection in this message, which isn't nice either... */ 889 mSessionStatusHandler.sendMessageDelayed(msg, 20); 890 } // Can only be null during shutdown 891 } 892 private void sendConnectTimeoutMessage() { 893 if (DEBUG) Log.d(TAG, "sendConnectTimeoutMessage()"); 894 if(mSessionStatusHandler != null) { 895 Message msg = mSessionStatusHandler.obtainMessage(USER_TIMEOUT); 896 msg.sendToTarget(); 897 } // Can only be null during shutdown 898 } 899 private void sendConnectCancelMessage() { 900 if(mSessionStatusHandler != null) { 901 Message msg = mSessionStatusHandler.obtainMessage(MSG_MAS_CONNECT_CANCEL); 902 msg.sendToTarget(); 903 } // Can only be null during shutdown 904 } 905 906 private void sendShutdownMessage() { 907 /* Any pending messages are no longer valid. 908 To speed up things, simply delete them. */ 909 if (mRemoveTimeoutMsg) { 910 Intent timeoutIntent = 911 new Intent(USER_CONFIRM_TIMEOUT_ACTION); 912 sendBroadcast(timeoutIntent, BLUETOOTH_PERM); 913 mIsWaitingAuthorization = false; 914 cancelUserTimeoutAlarm(); 915 } 916 mSessionStatusHandler.removeCallbacksAndMessages(null); 917 // Request release of all resources 918 mSessionStatusHandler.obtainMessage(SHUTDOWN).sendToTarget(); 919 } 920 921 private MapBroadcastReceiver mMapReceiver = new MapBroadcastReceiver(); 922 923 private class MapBroadcastReceiver extends BroadcastReceiver { 924 @Override 925 public void onReceive(Context context, Intent intent) { 926 if (DEBUG) Log.d(TAG, "onReceive"); 927 String action = intent.getAction(); 928 if (DEBUG) Log.d(TAG, "onReceive: " + action); 929 if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { 930 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 931 BluetoothAdapter.ERROR); 932 if (state == BluetoothAdapter.STATE_TURNING_OFF) { 933 if (DEBUG) Log.d(TAG, "STATE_TURNING_OFF"); 934 sendShutdownMessage(); 935 } else if (state == BluetoothAdapter.STATE_ON) { 936 if (DEBUG) Log.d(TAG, "STATE_ON"); 937 // start ServerSocket listener threads 938 sendStartListenerMessage(-1); 939 } 940 941 }else if (action.equals(USER_CONFIRM_TIMEOUT_ACTION)){ 942 if (DEBUG) Log.d(TAG, "USER_CONFIRM_TIMEOUT ACTION Received."); 943 // send us self a message about the timeout. 944 sendConnectTimeoutMessage(); 945 946 } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) { 947 948 int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, 949 BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS); 950 951 if (DEBUG) Log.d(TAG, "Received ACTION_CONNECTION_ACCESS_REPLY:" + 952 requestType + "isWaitingAuthorization:" + mIsWaitingAuthorization); 953 if ((!mIsWaitingAuthorization) 954 || (requestType != BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS)) { 955 // this reply is not for us 956 return; 957 } 958 959 mIsWaitingAuthorization = false; 960 if (mRemoveTimeoutMsg) { 961 mSessionStatusHandler.removeMessages(USER_TIMEOUT); 962 cancelUserTimeoutAlarm(); 963 setState(BluetoothMap.STATE_DISCONNECTED); 964 } 965 966 if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT, 967 BluetoothDevice.CONNECTION_ACCESS_NO) 968 == BluetoothDevice.CONNECTION_ACCESS_YES) { 969 // Bluetooth connection accepted by user 970 mPermission = BluetoothDevice.ACCESS_ALLOWED; 971 if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) { 972 boolean result = mRemoteDevice.setMessageAccessPermission( 973 BluetoothDevice.ACCESS_ALLOWED); 974 if (DEBUG) { 975 Log.d(TAG, "setMessageAccessPermission(ACCESS_ALLOWED) result=" 976 + result); 977 } 978 } 979 980 mRemoteDevice.sdpSearch(BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS); 981 mSdpSearchInitiated = true; 982 } else { 983 // Auth. declined by user, serverSession should not be running, but 984 // call stop anyway to restart listener. 985 mPermission = BluetoothDevice.ACCESS_REJECTED; 986 if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) { 987 boolean result = mRemoteDevice.setMessageAccessPermission( 988 BluetoothDevice.ACCESS_REJECTED); 989 if (DEBUG) { 990 Log.d(TAG, "setMessageAccessPermission(ACCESS_REJECTED) result=" 991 + result); 992 } 993 } 994 sendConnectCancelMessage(); 995 } 996 } else if (action.equals(BluetoothDevice.ACTION_SDP_RECORD)) { 997 if (DEBUG) Log.d(TAG, "Received ACTION_SDP_RECORD."); 998 ParcelUuid uuid = intent.getParcelableExtra(BluetoothDevice.EXTRA_UUID); 999 if (VERBOSE) { 1000 Log.v(TAG, "Received UUID: " + uuid.toString()); 1001 Log.v(TAG, "expected UUID: " + 1002 BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS.toString()); 1003 } 1004 if (uuid.equals(BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS)) { 1005 mMnsRecord = intent.getParcelableExtra(BluetoothDevice.EXTRA_SDP_RECORD); 1006 int status = intent.getIntExtra(BluetoothDevice.EXTRA_SDP_SEARCH_STATUS, -1); 1007 if (VERBOSE) { 1008 Log.v(TAG, " -> MNS Record:" + mMnsRecord); 1009 Log.v(TAG, " -> status: " + status); 1010 } 1011 if (mBluetoothMnsObexClient != null && !mSdpSearchInitiated) { 1012 mBluetoothMnsObexClient.setMnsRecord(mMnsRecord); 1013 } 1014 if (status != -1 && mMnsRecord != null) { 1015 for (int i = 0, c = mMasInstances.size(); i < c; i++) { 1016 mMasInstances.valueAt(i).setRemoteFeatureMask( 1017 mMnsRecord.getSupportedFeatures()); 1018 } 1019 } 1020 if (mSdpSearchInitiated) { 1021 mSdpSearchInitiated = false; // done searching 1022 sendConnectMessage(-1); // -1 indicates all MAS instances 1023 } 1024 } 1025 } else if (action.equals(ACTION_SHOW_MAPS_SETTINGS)) { 1026 if (VERBOSE) Log.v(TAG, "Received ACTION_SHOW_MAPS_SETTINGS."); 1027 1028 Intent in = new Intent(context, BluetoothMapSettings.class); 1029 in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); 1030 context.startActivity(in); 1031 } else if (action.equals(BluetoothMapContentObserver.ACTION_MESSAGE_SENT)) { 1032 BluetoothMapMasInstance masInst = null; 1033 int result = getResultCode(); 1034 boolean handled = false; 1035 if(mMasInstances != null && (masInst = mMasInstances.get(MAS_ID_SMS_MMS)) != null) { 1036 intent.putExtra(BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_RESULT, result); 1037 if(masInst.handleSmsSendIntent(context, intent)) { 1038 // The intent was handled by the mas instance it self 1039 handled = true; 1040 } 1041 } 1042 if(handled == false) 1043 { 1044 /* We do not have a connection to a device, hence we need to move 1045 the SMS to the correct folder. */ 1046 BluetoothMapContentObserver 1047 .actionMessageSentDisconnected(context, intent, result); 1048 } 1049 } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED) && 1050 mIsWaitingAuthorization) { 1051 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 1052 1053 if (mRemoteDevice == null || device == null) { 1054 Log.e(TAG, "Unexpected error!"); 1055 return; 1056 } 1057 1058 if (VERBOSE) Log.v(TAG,"ACL disconnected for " + device); 1059 1060 if (mRemoteDevice.equals(device)) { 1061 // Send any pending timeout now, as ACL got disconnected. 1062 mSessionStatusHandler.removeMessages(USER_TIMEOUT); 1063 1064 Intent timeoutIntent = 1065 new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL); 1066 timeoutIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice); 1067 timeoutIntent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, 1068 BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS); 1069 sendBroadcast(timeoutIntent, BLUETOOTH_PERM); 1070 mIsWaitingAuthorization = false; 1071 cancelUserTimeoutAlarm(); 1072 mSessionStatusHandler.obtainMessage(MSG_SERVERSESSION_CLOSE, -1, 0) 1073 .sendToTarget(); 1074 } 1075 } 1076 } 1077 }; 1078 1079 //Binder object: Must be static class or memory leak may occur 1080 /** 1081 * This class implements the IBluetoothMap interface - or actually it validates the 1082 * preconditions for calling the actual functionality in the MapService, and calls it. 1083 */ 1084 private static class BluetoothMapBinder extends IBluetoothMap.Stub 1085 implements IProfileServiceBinder { 1086 private BluetoothMapService mService; 1087 1088 private BluetoothMapService getService() { 1089 if (!Utils.checkCaller()) { 1090 Log.w(TAG,"MAP call not allowed for non-active user"); 1091 return null; 1092 } 1093 1094 if (mService != null && mService.isAvailable()) { 1095 mService.enforceCallingOrSelfPermission(BLUETOOTH_PERM,"Need BLUETOOTH permission"); 1096 return mService; 1097 } 1098 return null; 1099 } 1100 1101 BluetoothMapBinder(BluetoothMapService service) { 1102 if (VERBOSE) Log.v(TAG, "BluetoothMapBinder()"); 1103 mService = service; 1104 } 1105 1106 public boolean cleanup() { 1107 mService = null; 1108 return true; 1109 } 1110 1111 public int getState() { 1112 if (VERBOSE) Log.v(TAG, "getState()"); 1113 BluetoothMapService service = getService(); 1114 if (service == null) return BluetoothMap.STATE_DISCONNECTED; 1115 return getService().getState(); 1116 } 1117 1118 public BluetoothDevice getClient() { 1119 if (VERBOSE) Log.v(TAG, "getClient()"); 1120 BluetoothMapService service = getService(); 1121 if (service == null) return null; 1122 if (VERBOSE) Log.v(TAG, "getClient() - returning " + service.getRemoteDevice()); 1123 return service.getRemoteDevice(); 1124 } 1125 1126 public boolean isConnected(BluetoothDevice device) { 1127 if (VERBOSE) Log.v(TAG, "isConnected()"); 1128 BluetoothMapService service = getService(); 1129 if (service == null) return false; 1130 return service.getState() == BluetoothMap.STATE_CONNECTED 1131 && service.getRemoteDevice().equals(device); 1132 } 1133 1134 public boolean connect(BluetoothDevice device) { 1135 if (VERBOSE) Log.v(TAG, "connect()"); 1136 BluetoothMapService service = getService(); 1137 if (service == null) return false; 1138 return false; 1139 } 1140 1141 public boolean disconnect(BluetoothDevice device) { 1142 if (VERBOSE) Log.v(TAG, "disconnect()"); 1143 BluetoothMapService service = getService(); 1144 if (service == null) return false; 1145 return service.disconnect(device); 1146 } 1147 1148 public List<BluetoothDevice> getConnectedDevices() { 1149 if (VERBOSE) Log.v(TAG, "getConnectedDevices()"); 1150 BluetoothMapService service = getService(); 1151 if (service == null) return new ArrayList<BluetoothDevice>(0); 1152 return service.getConnectedDevices(); 1153 } 1154 1155 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 1156 if (VERBOSE) Log.v(TAG, "getDevicesMatchingConnectionStates()"); 1157 BluetoothMapService service = getService(); 1158 if (service == null) return new ArrayList<BluetoothDevice>(0); 1159 return service.getDevicesMatchingConnectionStates(states); 1160 } 1161 1162 public int getConnectionState(BluetoothDevice device) { 1163 if (VERBOSE) Log.v(TAG, "getConnectionState()"); 1164 BluetoothMapService service = getService(); 1165 if (service == null) return BluetoothProfile.STATE_DISCONNECTED; 1166 return service.getConnectionState(device); 1167 } 1168 1169 public boolean setPriority(BluetoothDevice device, int priority) { 1170 BluetoothMapService service = getService(); 1171 if (service == null) return false; 1172 return service.setPriority(device, priority); 1173 } 1174 1175 public int getPriority(BluetoothDevice device) { 1176 BluetoothMapService service = getService(); 1177 if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED; 1178 return service.getPriority(device); 1179 } 1180 } 1181 1182 @Override 1183 public void dump(StringBuilder sb) { 1184 super.dump(sb); 1185 println(sb, "mRemoteDevice: " + mRemoteDevice); 1186 println(sb, "sRemoteDeviceName: " + sRemoteDeviceName); 1187 println(sb, "mState: " + mState); 1188 println(sb, "mAppObserver: " + mAppObserver); 1189 println(sb, "mIsWaitingAuthorization: " + mIsWaitingAuthorization); 1190 println(sb, "mRemoveTimeoutMsg: " + mRemoveTimeoutMsg); 1191 println(sb, "mPermission: " + mPermission); 1192 println(sb, "mAccountChanged: " + mAccountChanged); 1193 println(sb, "mBluetoothMnsObexClient: " + mBluetoothMnsObexClient); 1194 println(sb, "mMasInstanceMap:"); 1195 for (BluetoothMapAccountItem key : mMasInstanceMap.keySet()) { 1196 println(sb, " " + key + " : " + mMasInstanceMap.get(key)); 1197 } 1198 println(sb, "mEnabledAccounts:"); 1199 for (BluetoothMapAccountItem account : mEnabledAccounts) { 1200 println(sb, " " + account); 1201 } 1202 } 1203} 1204