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