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