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