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