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