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