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