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