1 /* 2 * Copyright (C) 2014 Samsung System LSI 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 package com.android.bluetooth.map; 17 18 import android.app.AlarmManager; 19 import android.app.PendingIntent; 20 import android.bluetooth.BluetoothAdapter; 21 import android.bluetooth.BluetoothDevice; 22 import android.bluetooth.BluetoothMap; 23 import android.bluetooth.BluetoothProfile; 24 import android.bluetooth.BluetoothUuid; 25 import android.bluetooth.IBluetoothMap; 26 import android.bluetooth.SdpMnsRecord; 27 import android.content.BroadcastReceiver; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.content.IntentFilter.MalformedMimeTypeException; 32 import android.Manifest; 33 import android.os.Handler; 34 import android.os.HandlerThread; 35 import android.os.Looper; 36 import android.os.Message; 37 import android.os.ParcelUuid; 38 import android.os.PowerManager; 39 import android.os.RemoteException; 40 import android.provider.Settings; 41 import android.text.TextUtils; 42 import android.util.Log; 43 import android.util.SparseArray; 44 45 import com.android.bluetooth.Utils; 46 import com.android.bluetooth.btservice.AdapterService; 47 import com.android.bluetooth.btservice.ProfileService; 48 import com.android.bluetooth.btservice.ProfileService.IProfileServiceBinder; 49 import com.android.bluetooth.R; 50 51 import java.io.IOException; 52 import java.util.ArrayList; 53 import java.util.HashMap; 54 import java.util.List; 55 import java.util.Set; 56 57 public 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 return true; 832 } 833 834 public boolean cleanup() { 835 if (DEBUG) Log.d(TAG, "cleanup()"); 836 setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED); 837 //Cleanup already handled in Stop(). 838 //Move this extra check to Handler. 839 sendShutdownMessage(); 840 return true; 841 } 842 843 /** 844 * Called from each MAS instance when a connection is received. 845 * @param remoteDevice The device connecting 846 * @param masInst a reference to the calling MAS instance. 847 * @return 848 */ 849 public boolean onConnect(BluetoothDevice remoteDevice, BluetoothMapMasInstance masInst) { 850 boolean sendIntent = false; 851 boolean cancelConnection = false; 852 853 // As this can be called from each MasInstance, we need to lock access to member variables 854 synchronized(this) { 855 if (mRemoteDevice == null) { 856 mRemoteDevice = remoteDevice; 857 sRemoteDeviceName = mRemoteDevice.getName(); 858 // In case getRemoteName failed and return null 859 if (TextUtils.isEmpty(sRemoteDeviceName)) { 860 sRemoteDeviceName = getString(R.string.defaultname); 861 } 862 863 mPermission = mRemoteDevice.getMessageAccessPermission(); 864 if (mPermission == BluetoothDevice.ACCESS_UNKNOWN) { 865 sendIntent = true; 866 mIsWaitingAuthorization = true; 867 setUserTimeoutAlarm(); 868 } else if (mPermission == BluetoothDevice.ACCESS_REJECTED) { 869 cancelConnection = true; 870 } else if(mPermission == BluetoothDevice.ACCESS_ALLOWED) { 871 mRemoteDevice.sdpSearch(BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS); 872 mSdpSearchInitiated = true; 873 } 874 } else if (!mRemoteDevice.equals(remoteDevice)) { 875 Log.w(TAG, "Unexpected connection from a second Remote Device received. name: " + 876 ((remoteDevice==null)?"unknown":remoteDevice.getName())); 877 return false; /* The connecting device is different from what is already 878 connected, reject the connection. */ 879 } // Else second connection to same device, just continue 880 } 881 882 if (sendIntent) { 883 // This will trigger Settings app's dialog. 884 Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST); 885 intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS); 886 intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, 887 BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS); 888 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice); 889 sendOrderedBroadcast(intent, BLUETOOTH_ADMIN_PERM); 890 891 if (VERBOSE) Log.v(TAG, "waiting for authorization for connection from: " 892 + sRemoteDeviceName); 893 //Queue USER_TIMEOUT to disconnect MAP OBEX session. If user doesn't 894 //accept or reject authorization request 895 } else if (cancelConnection) { 896 sendConnectCancelMessage(); 897 } else if (mPermission == BluetoothDevice.ACCESS_ALLOWED) { 898 /* Signal to the service that we have a incoming connection. */ 899 sendConnectMessage(masInst.getMasId()); 900 } 901 return true; 902 }; 903 904 905 private void setUserTimeoutAlarm(){ 906 if (DEBUG) Log.d(TAG,"SetUserTimeOutAlarm()"); 907 if(mAlarmManager == null){ 908 mAlarmManager =(AlarmManager) this.getSystemService (Context.ALARM_SERVICE); 909 } 910 mRemoveTimeoutMsg = true; 911 Intent timeoutIntent = 912 new Intent(USER_CONFIRM_TIMEOUT_ACTION); 913 PendingIntent pIntent = PendingIntent.getBroadcast(this, 0, timeoutIntent, 0); 914 mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 915 USER_CONFIRM_TIMEOUT_VALUE,pIntent); 916 } 917 918 private void cancelUserTimeoutAlarm(){ 919 if (DEBUG) Log.d(TAG,"cancelUserTimeOutAlarm()"); 920 Intent timeoutIntent = new Intent(USER_CONFIRM_TIMEOUT_ACTION); 921 PendingIntent pIntent = PendingIntent.getBroadcast(this, 0, timeoutIntent, 0); 922 pIntent.cancel(); 923 924 AlarmManager alarmManager = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE); 925 alarmManager.cancel(pIntent); 926 mRemoveTimeoutMsg = false; 927 } 928 929 /** 930 * Start the incoming connection listeners for a MAS ID 931 * @param masId the MasID to start. Use -1 to start all listeners. 932 */ 933 public void sendStartListenerMessage(int masId) { 934 if (mSessionStatusHandler != null && ! mSessionStatusHandler.hasMessages(START_LISTENER)) { 935 Message msg = mSessionStatusHandler.obtainMessage(START_LISTENER, masId, 0); 936 /* We add a small delay here to ensure the call returns true before this message is 937 * handled. It seems wrong to add a delay, but the alternative is to build a lock 938 * system to handle synchronization, which isn't nice either... */ 939 mSessionStatusHandler.sendMessageDelayed(msg, 20); 940 } else if (mSessionStatusHandler != null) { 941 if(DEBUG) 942 Log.w(TAG, "mSessionStatusHandler START_LISTENER message already in Queue"); 943 } 944 } 945 946 private void sendConnectMessage(int masId) { 947 if(mSessionStatusHandler != null) { 948 Message msg = mSessionStatusHandler.obtainMessage(MSG_MAS_CONNECT, masId, 0); 949 /* We add a small delay here to ensure onConnect returns true before this message is 950 * handled. It seems wrong, but the alternative is to store a reference to the 951 * connection in this message, which isn't nice either... */ 952 mSessionStatusHandler.sendMessageDelayed(msg, 20); 953 } // Can only be null during shutdown 954 } 955 private void sendConnectTimeoutMessage() { 956 if (DEBUG) Log.d(TAG, "sendConnectTimeoutMessage()"); 957 if(mSessionStatusHandler != null) { 958 Message msg = mSessionStatusHandler.obtainMessage(USER_TIMEOUT); 959 msg.sendToTarget(); 960 } // Can only be null during shutdown 961 } 962 private void sendConnectCancelMessage() { 963 if(mSessionStatusHandler != null) { 964 Message msg = mSessionStatusHandler.obtainMessage(MSG_MAS_CONNECT_CANCEL); 965 msg.sendToTarget(); 966 } // Can only be null during shutdown 967 } 968 969 private void sendShutdownMessage() { 970 /* Any pending messages are no longer valid. 971 To speed up things, simply delete them. */ 972 if (mRemoveTimeoutMsg) { 973 Intent timeoutIntent = 974 new Intent(USER_CONFIRM_TIMEOUT_ACTION); 975 sendBroadcast(timeoutIntent, BLUETOOTH_PERM); 976 mIsWaitingAuthorization = false; 977 cancelUserTimeoutAlarm(); 978 } 979 if (mSessionStatusHandler != null && !mSessionStatusHandler.hasMessages(SHUTDOWN)) { 980 mSessionStatusHandler.removeCallbacksAndMessages(null); 981 // Request release of all resources 982 Message msg = mSessionStatusHandler.obtainMessage(SHUTDOWN); 983 if( mSessionStatusHandler.sendMessage(msg) == false) { 984 /* most likely caused by shutdown being called from multiple sources - e.g.BT off 985 * signaled through intent and a service shutdown simultaneously. 986 * Intended behavior not documented, hence we need to be able to handle all cases*/ 987 } else { 988 if(DEBUG) 989 Log.e(TAG, "mSessionStatusHandler.sendMessage() dispatched shutdown message"); 990 } 991 } else if (mSessionStatusHandler != null) { 992 if(DEBUG) 993 Log.w(TAG, "mSessionStatusHandler shutdown message already in Queue"); 994 } 995 if (VERBOSE) Log.d(TAG, "sendShutdownMessage() Out"); 996 } 997 998 private MapBroadcastReceiver mMapReceiver = new MapBroadcastReceiver(); 999 1000 private class MapBroadcastReceiver extends BroadcastReceiver { 1001 @Override 1002 public void onReceive(Context context, Intent intent) { 1003 if (DEBUG) Log.d(TAG, "onReceive"); 1004 String action = intent.getAction(); 1005 if (DEBUG) Log.d(TAG, "onReceive: " + action); 1006 if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { 1007 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 1008 BluetoothAdapter.ERROR); 1009 if (state == BluetoothAdapter.STATE_TURNING_OFF) { 1010 if (DEBUG) Log.d(TAG, "STATE_TURNING_OFF"); 1011 sendShutdownMessage(); 1012 } else if (state == BluetoothAdapter.STATE_ON) { 1013 if (DEBUG) Log.d(TAG, "STATE_ON"); 1014 // start ServerSocket listener threads 1015 sendStartListenerMessage(-1); 1016 } 1017 1018 }else if (action.equals(USER_CONFIRM_TIMEOUT_ACTION)){ 1019 if (DEBUG) Log.d(TAG, "USER_CONFIRM_TIMEOUT ACTION Received."); 1020 // send us self a message about the timeout. 1021 sendConnectTimeoutMessage(); 1022 1023 } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) { 1024 1025 int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, 1026 BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS); 1027 1028 if (DEBUG) Log.d(TAG, "Received ACTION_CONNECTION_ACCESS_REPLY:" + 1029 requestType + "isWaitingAuthorization:" + mIsWaitingAuthorization); 1030 if ((!mIsWaitingAuthorization) 1031 || (requestType != BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS)) { 1032 // this reply is not for us 1033 return; 1034 } 1035 1036 mIsWaitingAuthorization = false; 1037 if (mRemoveTimeoutMsg) { 1038 mSessionStatusHandler.removeMessages(USER_TIMEOUT); 1039 cancelUserTimeoutAlarm(); 1040 setState(BluetoothMap.STATE_DISCONNECTED); 1041 } 1042 1043 if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT, 1044 BluetoothDevice.CONNECTION_ACCESS_NO) 1045 == BluetoothDevice.CONNECTION_ACCESS_YES) { 1046 // Bluetooth connection accepted by user 1047 mPermission = BluetoothDevice.ACCESS_ALLOWED; 1048 if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) { 1049 boolean result = mRemoteDevice.setMessageAccessPermission( 1050 BluetoothDevice.ACCESS_ALLOWED); 1051 if (DEBUG) { 1052 Log.d(TAG, "setMessageAccessPermission(ACCESS_ALLOWED) result=" 1053 + result); 1054 } 1055 } 1056 1057 mRemoteDevice.sdpSearch(BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS); 1058 mSdpSearchInitiated = true; 1059 } else { 1060 // Auth. declined by user, serverSession should not be running, but 1061 // call stop anyway to restart listener. 1062 mPermission = BluetoothDevice.ACCESS_REJECTED; 1063 if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) { 1064 boolean result = mRemoteDevice.setMessageAccessPermission( 1065 BluetoothDevice.ACCESS_REJECTED); 1066 if (DEBUG) { 1067 Log.d(TAG, "setMessageAccessPermission(ACCESS_REJECTED) result=" 1068 + result); 1069 } 1070 } 1071 sendConnectCancelMessage(); 1072 } 1073 } else if (action.equals(BluetoothDevice.ACTION_SDP_RECORD)) { 1074 if (DEBUG) Log.d(TAG, "Received ACTION_SDP_RECORD."); 1075 ParcelUuid uuid = intent.getParcelableExtra(BluetoothDevice.EXTRA_UUID); 1076 if (VERBOSE) { 1077 Log.v(TAG, "Received UUID: " + uuid.toString()); 1078 Log.v(TAG, "expected UUID: " + 1079 BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS.toString()); 1080 } 1081 if (uuid.equals(BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS)) { 1082 mMnsRecord = intent.getParcelableExtra(BluetoothDevice.EXTRA_SDP_RECORD); 1083 int status = intent.getIntExtra(BluetoothDevice.EXTRA_SDP_SEARCH_STATUS, -1); 1084 if (VERBOSE) { 1085 Log.v(TAG, " -> MNS Record:" + mMnsRecord); 1086 Log.v(TAG, " -> status: " + status); 1087 } 1088 if (mBluetoothMnsObexClient != null && !mSdpSearchInitiated) { 1089 mBluetoothMnsObexClient.setMnsRecord(mMnsRecord); 1090 } 1091 if (status != -1 && mMnsRecord != null) { 1092 for (int i = 0, c = mMasInstances.size(); i < c; i++) { 1093 mMasInstances.valueAt(i).setRemoteFeatureMask( 1094 mMnsRecord.getSupportedFeatures()); 1095 } 1096 } 1097 if (mSdpSearchInitiated) { 1098 mSdpSearchInitiated = false; // done searching 1099 sendConnectMessage(-1); // -1 indicates all MAS instances 1100 } 1101 } 1102 } else if (action.equals(ACTION_SHOW_MAPS_SETTINGS)) { 1103 if (VERBOSE) Log.v(TAG, "Received ACTION_SHOW_MAPS_SETTINGS."); 1104 1105 Intent in = new Intent(context, BluetoothMapSettings.class); 1106 in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); 1107 context.startActivity(in); 1108 } else if (action.equals(BluetoothMapContentObserver.ACTION_MESSAGE_SENT)) { 1109 BluetoothMapMasInstance masInst = null; 1110 int result = getResultCode(); 1111 boolean handled = false; 1112 if(mSmsCapable && mMasInstances != null && 1113 (masInst = mMasInstances.get(MAS_ID_SMS_MMS)) != null) { 1114 intent.putExtra(BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_RESULT, result); 1115 if(masInst.handleSmsSendIntent(context, intent)) { 1116 // The intent was handled by the mas instance it self 1117 handled = true; 1118 } 1119 } 1120 if(handled == false) 1121 { 1122 /* We do not have a connection to a device, hence we need to move 1123 the SMS to the correct folder. */ 1124 try { 1125 BluetoothMapContentObserver 1126 .actionMessageSentDisconnected(context, intent, result); 1127 } catch(IllegalArgumentException e) { 1128 return; 1129 } 1130 } 1131 } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED) && 1132 mIsWaitingAuthorization) { 1133 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 1134 1135 if (mRemoteDevice == null || device == null) { 1136 Log.e(TAG, "Unexpected error!"); 1137 return; 1138 } 1139 1140 if (VERBOSE) Log.v(TAG,"ACL disconnected for " + device); 1141 1142 if (mRemoteDevice.equals(device)) { 1143 // Send any pending timeout now, as ACL got disconnected. 1144 mSessionStatusHandler.removeMessages(USER_TIMEOUT); 1145 1146 Intent timeoutIntent = 1147 new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL); 1148 timeoutIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice); 1149 timeoutIntent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, 1150 BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS); 1151 sendBroadcast(timeoutIntent, BLUETOOTH_PERM); 1152 mIsWaitingAuthorization = false; 1153 cancelUserTimeoutAlarm(); 1154 mSessionStatusHandler.obtainMessage(MSG_SERVERSESSION_CLOSE, -1, 0) 1155 .sendToTarget(); 1156 } 1157 } 1158 } 1159 }; 1160 1161 //Binder object: Must be static class or memory leak may occur 1162 /** 1163 * This class implements the IBluetoothMap interface - or actually it validates the 1164 * preconditions for calling the actual functionality in the MapService, and calls it. 1165 */ 1166 private static class BluetoothMapBinder extends IBluetoothMap.Stub 1167 implements IProfileServiceBinder { 1168 private BluetoothMapService mService; 1169 1170 private BluetoothMapService getService() { 1171 if (!Utils.checkCaller()) { 1172 Log.w(TAG,"MAP call not allowed for non-active user"); 1173 return null; 1174 } 1175 1176 if (mService != null && mService.isAvailable()) { 1177 mService.enforceCallingOrSelfPermission(BLUETOOTH_PERM,"Need BLUETOOTH permission"); 1178 return mService; 1179 } 1180 return null; 1181 } 1182 1183 BluetoothMapBinder(BluetoothMapService service) { 1184 if (VERBOSE) Log.v(TAG, "BluetoothMapBinder()"); 1185 mService = service; 1186 } 1187 1188 public boolean cleanup() { 1189 mService = null; 1190 return true; 1191 } 1192 1193 public int getState() { 1194 if (VERBOSE) Log.v(TAG, "getState()"); 1195 BluetoothMapService service = getService(); 1196 if (service == null) return BluetoothMap.STATE_DISCONNECTED; 1197 return getService().getState(); 1198 } 1199 1200 public BluetoothDevice getClient() { 1201 if (VERBOSE) Log.v(TAG, "getClient()"); 1202 BluetoothMapService service = getService(); 1203 if (service == null) return null; 1204 if (VERBOSE) Log.v(TAG, "getClient() - returning " + service.getRemoteDevice()); 1205 return service.getRemoteDevice(); 1206 } 1207 1208 public boolean isConnected(BluetoothDevice device) { 1209 if (VERBOSE) Log.v(TAG, "isConnected()"); 1210 BluetoothMapService service = getService(); 1211 if (service == null) return false; 1212 return service.getState() == BluetoothMap.STATE_CONNECTED 1213 && service.getRemoteDevice().equals(device); 1214 } 1215 1216 public boolean connect(BluetoothDevice device) { 1217 if (VERBOSE) Log.v(TAG, "connect()"); 1218 BluetoothMapService service = getService(); 1219 if (service == null) return false; 1220 return false; 1221 } 1222 1223 public boolean disconnect(BluetoothDevice device) { 1224 if (VERBOSE) Log.v(TAG, "disconnect()"); 1225 BluetoothMapService service = getService(); 1226 if (service == null) return false; 1227 return service.disconnect(device); 1228 } 1229 1230 public List<BluetoothDevice> getConnectedDevices() { 1231 if (VERBOSE) Log.v(TAG, "getConnectedDevices()"); 1232 BluetoothMapService service = getService(); 1233 if (service == null) return new ArrayList<BluetoothDevice>(0); 1234 return service.getConnectedDevices(); 1235 } 1236 1237 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 1238 if (VERBOSE) Log.v(TAG, "getDevicesMatchingConnectionStates()"); 1239 BluetoothMapService service = getService(); 1240 if (service == null) return new ArrayList<BluetoothDevice>(0); 1241 return service.getDevicesMatchingConnectionStates(states); 1242 } 1243 1244 public int getConnectionState(BluetoothDevice device) { 1245 if (VERBOSE) Log.v(TAG, "getConnectionState()"); 1246 BluetoothMapService service = getService(); 1247 if (service == null) return BluetoothProfile.STATE_DISCONNECTED; 1248 return service.getConnectionState(device); 1249 } 1250 1251 public boolean setPriority(BluetoothDevice device, int priority) { 1252 BluetoothMapService service = getService(); 1253 if (service == null) return false; 1254 return service.setPriority(device, priority); 1255 } 1256 1257 public int getPriority(BluetoothDevice device) { 1258 BluetoothMapService service = getService(); 1259 if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED; 1260 return service.getPriority(device); 1261 } 1262 } 1263 1264 @Override 1265 public void dump(StringBuilder sb) { 1266 super.dump(sb); 1267 println(sb, "mRemoteDevice: " + mRemoteDevice); 1268 println(sb, "sRemoteDeviceName: " + sRemoteDeviceName); 1269 println(sb, "mState: " + mState); 1270 println(sb, "mAppObserver: " + mAppObserver); 1271 println(sb, "mIsWaitingAuthorization: " + mIsWaitingAuthorization); 1272 println(sb, "mRemoveTimeoutMsg: " + mRemoveTimeoutMsg); 1273 println(sb, "mPermission: " + mPermission); 1274 println(sb, "mAccountChanged: " + mAccountChanged); 1275 println(sb, "mBluetoothMnsObexClient: " + mBluetoothMnsObexClient); 1276 println(sb, "mMasInstanceMap:"); 1277 for (BluetoothMapAccountItem key : mMasInstanceMap.keySet()) { 1278 println(sb, " " + key + " : " + mMasInstanceMap.get(key)); 1279 } 1280 println(sb, "mEnabledAccounts:"); 1281 for (BluetoothMapAccountItem account : mEnabledAccounts) { 1282 println(sb, " " + account); 1283 } 1284 } 1285 } 1286