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