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