1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.bluetooth.mapclient; 18 19 import android.Manifest; 20 import android.annotation.RequiresPermission; 21 import android.app.PendingIntent; 22 import android.bluetooth.BluetoothAdapter; 23 import android.bluetooth.BluetoothDevice; 24 import android.bluetooth.BluetoothProfile; 25 import android.bluetooth.BluetoothUuid; 26 import android.bluetooth.IBluetoothMapClient; 27 import android.bluetooth.SdpMasRecord; 28 import android.content.AttributionSource; 29 import android.content.BroadcastReceiver; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.content.IntentFilter; 33 import android.net.Uri; 34 import android.os.ParcelUuid; 35 import android.sysprop.BluetoothProperties; 36 import android.util.Log; 37 38 import com.android.bluetooth.Utils; 39 import com.android.bluetooth.btservice.AdapterService; 40 import com.android.bluetooth.btservice.ProfileService; 41 import com.android.bluetooth.btservice.storage.DatabaseManager; 42 import com.android.internal.annotations.VisibleForTesting; 43 import com.android.modules.utils.SynchronousResultReceiver; 44 45 import java.util.ArrayList; 46 import java.util.Arrays; 47 import java.util.Iterator; 48 import java.util.List; 49 import java.util.Map; 50 import java.util.Objects; 51 import java.util.concurrent.ConcurrentHashMap; 52 53 public class MapClientService extends ProfileService { 54 private static final String TAG = "MapClientService"; 55 56 static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); 57 static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE); 58 59 static final int MAXIMUM_CONNECTED_DEVICES = 4; 60 61 private Map<BluetoothDevice, MceStateMachine> mMapInstanceMap = new ConcurrentHashMap<>(1); 62 private MnsService mMnsServer; 63 64 private AdapterService mAdapterService; 65 private DatabaseManager mDatabaseManager; 66 private static MapClientService sMapClientService; 67 @VisibleForTesting 68 MapBroadcastReceiver mMapReceiver; 69 isEnabled()70 public static boolean isEnabled() { 71 return BluetoothProperties.isProfileMapClientEnabled().orElse(false); 72 } 73 getMapClientService()74 public static synchronized MapClientService getMapClientService() { 75 if (sMapClientService == null) { 76 Log.w(TAG, "getMapClientService(): service is null"); 77 return null; 78 } 79 if (!sMapClientService.isAvailable()) { 80 Log.w(TAG, "getMapClientService(): service is not available "); 81 return null; 82 } 83 return sMapClientService; 84 } 85 86 @VisibleForTesting setMapClientService(MapClientService instance)87 static synchronized void setMapClientService(MapClientService instance) { 88 if (DBG) { 89 Log.d(TAG, "setMapClientService(): set to: " + instance); 90 } 91 sMapClientService = instance; 92 } 93 94 @VisibleForTesting getInstanceMap()95 Map<BluetoothDevice, MceStateMachine> getInstanceMap() { 96 return mMapInstanceMap; 97 } 98 99 /** 100 * Connect the given Bluetooth device. 101 * 102 * @param device 103 * @return true if connection is successful, false otherwise. 104 */ 105 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) connect(BluetoothDevice device)106 public synchronized boolean connect(BluetoothDevice device) { 107 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 108 "Need BLUETOOTH_PRIVILEGED permission"); 109 if (device == null) { 110 throw new IllegalArgumentException("Null device"); 111 } 112 if (DBG) { 113 StringBuilder sb = new StringBuilder(); 114 dump(sb); 115 Log.d(TAG, "MAP connect device: " + device 116 + ", InstanceMap start state: " + sb.toString()); 117 } 118 if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { 119 Log.w(TAG, "Connection not allowed: <" + device.getAddress() 120 + "> is CONNECTION_POLICY_FORBIDDEN"); 121 return false; 122 } 123 MceStateMachine mapStateMachine = mMapInstanceMap.get(device); 124 if (mapStateMachine == null) { 125 // a map state machine instance doesn't exist yet, create a new one if we can. 126 if (mMapInstanceMap.size() < MAXIMUM_CONNECTED_DEVICES) { 127 addDeviceToMapAndConnect(device); 128 return true; 129 } else { 130 // Maxed out on the number of allowed connections. 131 // see if some of the current connections can be cleaned-up, to make room. 132 removeUncleanAccounts(); 133 if (mMapInstanceMap.size() < MAXIMUM_CONNECTED_DEVICES) { 134 addDeviceToMapAndConnect(device); 135 return true; 136 } else { 137 Log.e(TAG, "Maxed out on the number of allowed MAP connections. " 138 + "Connect request rejected on " + device); 139 return false; 140 } 141 } 142 } 143 144 // statemachine already exists in the map. 145 int state = getConnectionState(device); 146 if (state == BluetoothProfile.STATE_CONNECTED 147 || state == BluetoothProfile.STATE_CONNECTING) { 148 Log.w(TAG, "Received connect request while already connecting/connected."); 149 return true; 150 } 151 152 // Statemachine exists but not in connecting or connected state! it should 153 // have been removed form the map. lets get rid of it and add a new one. 154 if (DBG) { 155 Log.d(TAG, "Statemachine exists for a device in unexpected state: " + state); 156 } 157 mMapInstanceMap.remove(device); 158 addDeviceToMapAndConnect(device); 159 if (DBG) { 160 StringBuilder sb = new StringBuilder(); 161 dump(sb); 162 Log.d(TAG, "MAP connect device: " + device 163 + ", InstanceMap end state: " + sb.toString()); 164 } 165 return true; 166 } 167 addDeviceToMapAndConnect(BluetoothDevice device)168 private synchronized void addDeviceToMapAndConnect(BluetoothDevice device) { 169 // When creating a new statemachine, its state is set to CONNECTING - which will trigger 170 // connect. 171 MceStateMachine mapStateMachine = new MceStateMachine(this, device); 172 mMapInstanceMap.put(device, mapStateMachine); 173 } 174 175 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) disconnect(BluetoothDevice device)176 public synchronized boolean disconnect(BluetoothDevice device) { 177 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 178 "Need BLUETOOTH_PRIVILEGED permission"); 179 if (DBG) { 180 StringBuilder sb = new StringBuilder(); 181 dump(sb); 182 Log.d(TAG, "MAP disconnect device: " + device 183 + ", InstanceMap start state: " + sb.toString()); 184 } 185 MceStateMachine mapStateMachine = mMapInstanceMap.get(device); 186 // a map state machine instance doesn't exist. maybe it is already gone? 187 if (mapStateMachine == null) { 188 return false; 189 } 190 int connectionState = mapStateMachine.getState(); 191 if (connectionState != BluetoothProfile.STATE_CONNECTED 192 && connectionState != BluetoothProfile.STATE_CONNECTING) { 193 return false; 194 } 195 mapStateMachine.disconnect(); 196 if (DBG) { 197 StringBuilder sb = new StringBuilder(); 198 dump(sb); 199 Log.d(TAG, "MAP disconnect device: " + device 200 + ", InstanceMap start state: " + sb.toString()); 201 } 202 return true; 203 } 204 getConnectedDevices()205 public List<BluetoothDevice> getConnectedDevices() { 206 return getDevicesMatchingConnectionStates(new int[]{BluetoothAdapter.STATE_CONNECTED}); 207 } 208 getMceStateMachineForDevice(BluetoothDevice device)209 MceStateMachine getMceStateMachineForDevice(BluetoothDevice device) { 210 return mMapInstanceMap.get(device); 211 } 212 getDevicesMatchingConnectionStates(int[] states)213 public synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 214 if (DBG) Log.d(TAG, "getDevicesMatchingConnectionStates" + Arrays.toString(states)); 215 List<BluetoothDevice> deviceList = new ArrayList<>(); 216 BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices(); 217 int connectionState; 218 for (BluetoothDevice device : bondedDevices) { 219 connectionState = getConnectionState(device); 220 if (DBG) Log.d(TAG, "Device: " + device + "State: " + connectionState); 221 for (int i = 0; i < states.length; i++) { 222 if (connectionState == states[i]) { 223 deviceList.add(device); 224 } 225 } 226 } 227 if (DBG) Log.d(TAG, deviceList.toString()); 228 return deviceList; 229 } 230 getConnectionState(BluetoothDevice device)231 public synchronized int getConnectionState(BluetoothDevice device) { 232 MceStateMachine mapStateMachine = mMapInstanceMap.get(device); 233 // a map state machine instance doesn't exist yet, create a new one if we can. 234 return (mapStateMachine == null) ? BluetoothProfile.STATE_DISCONNECTED 235 : mapStateMachine.getState(); 236 } 237 238 /** 239 * Set connection policy of the profile and connects it if connectionPolicy is 240 * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is 241 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN} 242 * 243 * <p> The device should already be paired. 244 * Connection policy can be one of: 245 * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, 246 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, 247 * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN} 248 * 249 * @param device Paired bluetooth device 250 * @param connectionPolicy is the connection policy to set to for this profile 251 * @return true if connectionPolicy is set, false on error 252 */ 253 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) setConnectionPolicy(BluetoothDevice device, int connectionPolicy)254 public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) { 255 if (VDBG) { 256 Log.v(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy); 257 } 258 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 259 "Need BLUETOOTH_PRIVILEGED permission"); 260 261 if (!mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.MAP_CLIENT, 262 connectionPolicy)) { 263 return false; 264 } 265 if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 266 connect(device); 267 } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { 268 disconnect(device); 269 } 270 return true; 271 } 272 273 /** 274 * Get the connection policy of the profile. 275 * 276 * <p> The connection policy can be any of: 277 * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, 278 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, 279 * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN} 280 * 281 * @param device Bluetooth device 282 * @return connection policy of the device 283 * @hide 284 */ 285 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) getConnectionPolicy(BluetoothDevice device)286 public int getConnectionPolicy(BluetoothDevice device) { 287 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 288 "Need BLUETOOTH_PRIVILEGED permission"); 289 return mDatabaseManager 290 .getProfileConnectionPolicy(device, BluetoothProfile.MAP_CLIENT); 291 } 292 sendMessage(BluetoothDevice device, Uri[] contacts, String message, PendingIntent sentIntent, PendingIntent deliveredIntent)293 public synchronized boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message, 294 PendingIntent sentIntent, PendingIntent deliveredIntent) { 295 MceStateMachine mapStateMachine = mMapInstanceMap.get(device); 296 return mapStateMachine != null 297 && mapStateMachine.sendMapMessage(contacts, message, sentIntent, deliveredIntent); 298 } 299 300 @Override initBinder()301 public IProfileServiceBinder initBinder() { 302 return new Binder(this); 303 } 304 305 @Override start()306 protected synchronized boolean start() { 307 Log.e(TAG, "start()"); 308 309 mAdapterService = AdapterService.getAdapterService(); 310 mDatabaseManager = Objects.requireNonNull(AdapterService.getAdapterService().getDatabase(), 311 "DatabaseManager cannot be null when MapClientService starts"); 312 313 if (mMnsServer == null) { 314 mMnsServer = MapUtils.newMnsServiceInstance(this); 315 if (mMnsServer == null) { 316 // this can't happen 317 Log.w(TAG, "MnsService is *not* created!"); 318 return false; 319 } 320 } 321 322 mMapReceiver = new MapBroadcastReceiver(); 323 IntentFilter filter = new IntentFilter(); 324 filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 325 filter.addAction(BluetoothDevice.ACTION_SDP_RECORD); 326 filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); 327 registerReceiver(mMapReceiver, filter); 328 removeUncleanAccounts(); 329 MapClientContent.clearAllContent(this); 330 setMapClientService(this); 331 mAdapterService.notifyActivityAttributionInfo( 332 getAttributionSource(), 333 AdapterService.ACTIVITY_ATTRIBUTION_NO_ACTIVE_DEVICE_ADDRESS); 334 return true; 335 } 336 337 @Override stop()338 protected synchronized boolean stop() { 339 if (DBG) { 340 Log.d(TAG, "stop()"); 341 } 342 343 if (mAdapterService != null) { 344 mAdapterService.notifyActivityAttributionInfo( 345 getAttributionSource(), 346 AdapterService.ACTIVITY_ATTRIBUTION_NO_ACTIVE_DEVICE_ADDRESS); 347 } 348 if (mMapReceiver != null) { 349 unregisterReceiver(mMapReceiver); 350 mMapReceiver = null; 351 } 352 if (mMnsServer != null) { 353 mMnsServer.stop(); 354 } 355 for (MceStateMachine stateMachine : mMapInstanceMap.values()) { 356 if (stateMachine.getState() == BluetoothAdapter.STATE_CONNECTED) { 357 stateMachine.disconnect(); 358 } 359 stateMachine.doQuit(); 360 } 361 mMapInstanceMap.clear(); 362 return true; 363 } 364 365 @Override cleanup()366 protected void cleanup() { 367 if (DBG) { 368 Log.d(TAG, "in Cleanup"); 369 } 370 removeUncleanAccounts(); 371 // TODO(b/72948646): should be moved to stop() 372 setMapClientService(null); 373 } 374 375 /** 376 * cleanupDevice removes the associated state machine from the instance map 377 * 378 * @param device BluetoothDevice address of remote device 379 */ 380 @VisibleForTesting cleanupDevice(BluetoothDevice device)381 public void cleanupDevice(BluetoothDevice device) { 382 if (DBG) { 383 StringBuilder sb = new StringBuilder(); 384 dump(sb); 385 Log.d(TAG, "Cleanup device: " + device + ", InstanceMap start state: " 386 + sb.toString()); 387 } 388 synchronized (mMapInstanceMap) { 389 MceStateMachine stateMachine = mMapInstanceMap.get(device); 390 if (stateMachine != null) { 391 mMapInstanceMap.remove(device); 392 } 393 } 394 if (DBG) { 395 StringBuilder sb = new StringBuilder(); 396 dump(sb); 397 Log.d(TAG, "Cleanup device: " + device + ", InstanceMap end state: " 398 + sb.toString()); 399 } 400 } 401 402 @VisibleForTesting removeUncleanAccounts()403 void removeUncleanAccounts() { 404 if (DBG) { 405 StringBuilder sb = new StringBuilder(); 406 dump(sb); 407 Log.d(TAG, "removeUncleanAccounts:InstanceMap end state: " 408 + sb.toString()); 409 } 410 Iterator iterator = mMapInstanceMap.entrySet().iterator(); 411 while (iterator.hasNext()) { 412 Map.Entry<BluetoothDevice, MceStateMachine> profileConnection = 413 (Map.Entry) iterator.next(); 414 if (profileConnection.getValue().getState() == BluetoothProfile.STATE_DISCONNECTED) { 415 iterator.remove(); 416 } 417 } 418 if (DBG) { 419 StringBuilder sb = new StringBuilder(); 420 dump(sb); 421 Log.d(TAG, "removeUncleanAccounts:InstanceMap end state: " 422 + sb.toString()); 423 } 424 } 425 getUnreadMessages(BluetoothDevice device)426 public synchronized boolean getUnreadMessages(BluetoothDevice device) { 427 MceStateMachine mapStateMachine = mMapInstanceMap.get(device); 428 if (mapStateMachine == null) { 429 return false; 430 } 431 return mapStateMachine.getUnreadMessages(); 432 } 433 434 /** 435 * Returns the SDP record's MapSupportedFeatures field (see Bluetooth MAP 1.4 spec, page 114). 436 * @param device The Bluetooth device to get this value for. 437 * @return the SDP record's MapSupportedFeatures field. 438 */ getSupportedFeatures(BluetoothDevice device)439 public synchronized int getSupportedFeatures(BluetoothDevice device) { 440 MceStateMachine mapStateMachine = mMapInstanceMap.get(device); 441 if (mapStateMachine == null) { 442 if (DBG) Log.d(TAG, "in getSupportedFeatures, returning 0"); 443 return 0; 444 } 445 return mapStateMachine.getSupportedFeatures(); 446 } 447 setMessageStatus(BluetoothDevice device, String handle, int status)448 public synchronized boolean setMessageStatus(BluetoothDevice device, String handle, int status) { 449 MceStateMachine mapStateMachine = mMapInstanceMap.get(device); 450 if (mapStateMachine == null) { 451 return false; 452 } 453 return mapStateMachine.setMessageStatus(handle, status); 454 } 455 456 @Override dump(StringBuilder sb)457 public void dump(StringBuilder sb) { 458 super.dump(sb); 459 for (MceStateMachine stateMachine : mMapInstanceMap.values()) { 460 stateMachine.dump(sb); 461 } 462 } 463 464 //Binder object: Must be static class or memory leak may occur 465 466 /** 467 * This class implements the IClient interface - or actually it validates the 468 * preconditions for calling the actual functionality in the MapClientService, and calls it. 469 */ 470 @VisibleForTesting 471 static class Binder extends IBluetoothMapClient.Stub implements IProfileServiceBinder { 472 private MapClientService mService; 473 Binder(MapClientService service)474 Binder(MapClientService service) { 475 if (VDBG) { 476 Log.v(TAG, "Binder()"); 477 } 478 mService = service; 479 } 480 481 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getService(AttributionSource source)482 private MapClientService getService(AttributionSource source) { 483 if (Utils.isInstrumentationTestMode()) { 484 return mService; 485 } 486 if (!Utils.checkServiceAvailable(mService, TAG) 487 || !(MapUtils.isSystemUser() 488 || Utils.checkCallerIsSystemOrActiveOrManagedUser(mService, TAG)) 489 || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) { 490 return null; 491 } 492 return mService; 493 } 494 495 @Override cleanup()496 public void cleanup() { 497 mService = null; 498 } 499 500 @Override isConnected(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)501 public void isConnected(BluetoothDevice device, AttributionSource source, 502 SynchronousResultReceiver receiver) { 503 if (VDBG) { 504 Log.v(TAG, "isConnected()"); 505 } 506 try { 507 MapClientService service = getService(source); 508 boolean result = false; 509 if (service != null) { 510 result = service.getConnectionState(device) == BluetoothProfile.STATE_CONNECTED; 511 } 512 receiver.send(result); 513 } catch (RuntimeException e) { 514 receiver.propagateException(e); 515 } 516 } 517 518 @Override connect(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)519 public void connect(BluetoothDevice device, AttributionSource source, 520 SynchronousResultReceiver receiver) { 521 if (VDBG) { 522 Log.v(TAG, "connect()"); 523 } 524 try { 525 MapClientService service = getService(source); 526 boolean result = false; 527 if (service != null) { 528 result = service.connect(device); 529 } 530 receiver.send(result); 531 } catch (RuntimeException e) { 532 receiver.propagateException(e); 533 } 534 } 535 536 @Override disconnect(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)537 public void disconnect(BluetoothDevice device, AttributionSource source, 538 SynchronousResultReceiver receiver) { 539 if (VDBG) { 540 Log.v(TAG, "disconnect()"); 541 } 542 try { 543 MapClientService service = getService(source); 544 boolean result = false; 545 if (service != null) { 546 result = service.disconnect(device); 547 } 548 receiver.send(result); 549 } catch (RuntimeException e) { 550 receiver.propagateException(e); 551 } 552 } 553 554 @Override getConnectedDevices(AttributionSource source, SynchronousResultReceiver receiver)555 public void getConnectedDevices(AttributionSource source, 556 SynchronousResultReceiver receiver) { 557 if (VDBG) { 558 Log.v(TAG, "getConnectedDevices()"); 559 } 560 try { 561 MapClientService service = getService(source); 562 List<BluetoothDevice> connectedDevices = new ArrayList<BluetoothDevice>(0); 563 if (service != null) { 564 connectedDevices = service.getConnectedDevices(); 565 } 566 receiver.send(connectedDevices); 567 } catch (RuntimeException e) { 568 receiver.propagateException(e); 569 } 570 } 571 572 @Override getDevicesMatchingConnectionStates(int[] states, AttributionSource source, SynchronousResultReceiver receiver)573 public void getDevicesMatchingConnectionStates(int[] states, 574 AttributionSource source, SynchronousResultReceiver receiver) { 575 if (VDBG) { 576 Log.v(TAG, "getDevicesMatchingConnectionStates()"); 577 } 578 try { 579 MapClientService service = getService(source); 580 List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>(0); 581 if (service != null) { 582 devices = service.getDevicesMatchingConnectionStates(states); 583 } 584 receiver.send(devices); 585 } catch (RuntimeException e) { 586 receiver.propagateException(e); 587 } 588 } 589 590 @Override getConnectionState(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)591 public void getConnectionState(BluetoothDevice device, AttributionSource source, 592 SynchronousResultReceiver receiver) { 593 if (VDBG) { 594 Log.v(TAG, "getConnectionState()"); 595 } 596 try { 597 MapClientService service = getService(source); 598 int state = BluetoothProfile.STATE_DISCONNECTED; 599 if (service != null) { 600 state = service.getConnectionState(device); 601 } 602 receiver.send(state); 603 } catch (RuntimeException e) { 604 receiver.propagateException(e); 605 } 606 } 607 608 @Override setConnectionPolicy(BluetoothDevice device, int connectionPolicy, AttributionSource source, SynchronousResultReceiver receiver)609 public void setConnectionPolicy(BluetoothDevice device, int connectionPolicy, 610 AttributionSource source, SynchronousResultReceiver receiver) { 611 if (VDBG) { 612 Log.v(TAG, "setConnectionPolicy()"); 613 } 614 try { 615 MapClientService service = getService(source); 616 boolean result = false; 617 if (service != null) { 618 result = service.setConnectionPolicy(device, connectionPolicy); 619 } 620 receiver.send(result); 621 } catch (RuntimeException e) { 622 receiver.propagateException(e); 623 } 624 } 625 626 @Override getConnectionPolicy(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)627 public void getConnectionPolicy(BluetoothDevice device, AttributionSource source, 628 SynchronousResultReceiver receiver) { 629 if (VDBG) { 630 Log.v(TAG, "getConnectionPolicy()"); 631 } 632 try { 633 MapClientService service = getService(source); 634 int policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN; 635 if (service != null) { 636 policy = service.getConnectionPolicy(device); 637 } 638 receiver.send(policy); 639 } catch (RuntimeException e) { 640 receiver.propagateException(e); 641 } 642 } 643 644 @Override sendMessage(BluetoothDevice device, Uri[] contacts, String message, PendingIntent sentIntent, PendingIntent deliveredIntent, AttributionSource source, SynchronousResultReceiver receiver)645 public void sendMessage(BluetoothDevice device, Uri[] contacts, String message, 646 PendingIntent sentIntent, PendingIntent deliveredIntent, AttributionSource source, 647 SynchronousResultReceiver receiver) { 648 if (VDBG) { 649 Log.v(TAG, "sendMessage()"); 650 } 651 try { 652 MapClientService service = getService(source); 653 boolean result = false; 654 if (service != null) { 655 if (DBG) Log.d(TAG, "Checking Permission of sendMessage"); 656 service.enforceCallingOrSelfPermission(Manifest.permission.SEND_SMS, 657 "Need SEND_SMS permission"); 658 result = service.sendMessage(device, contacts, message, sentIntent, 659 deliveredIntent); 660 } 661 receiver.send(result); 662 } catch (RuntimeException e) { 663 receiver.propagateException(e); 664 } 665 } 666 667 @Override getUnreadMessages(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)668 public void getUnreadMessages(BluetoothDevice device, AttributionSource source, 669 SynchronousResultReceiver receiver) { 670 if (VDBG) { 671 Log.v(TAG, "getUnreadMessages()"); 672 } 673 try { 674 MapClientService service = getService(source); 675 boolean result = false; 676 if (service != null) { 677 service.enforceCallingOrSelfPermission(Manifest.permission.READ_SMS, 678 "Need READ_SMS permission"); 679 result = service.getUnreadMessages(device); 680 } 681 receiver.send(result); 682 } catch (RuntimeException e) { 683 receiver.propagateException(e); 684 } 685 } 686 687 @Override getSupportedFeatures(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)688 public void getSupportedFeatures(BluetoothDevice device, AttributionSource source, 689 SynchronousResultReceiver receiver) { 690 if (VDBG) { 691 Log.v(TAG, "getSupportedFeatures()"); 692 } 693 try { 694 MapClientService service = getService(source); 695 int feature = 0; 696 if (service != null) { 697 feature = service.getSupportedFeatures(device); 698 } else if (DBG) { 699 Log.d(TAG, "in MapClientService getSupportedFeatures stub, returning 0"); 700 } 701 receiver.send(feature); 702 } catch (RuntimeException e) { 703 receiver.propagateException(e); 704 } 705 } 706 707 @Override setMessageStatus(BluetoothDevice device, String handle, int status, AttributionSource source, SynchronousResultReceiver receiver)708 public void setMessageStatus(BluetoothDevice device, String handle, int status, 709 AttributionSource source, SynchronousResultReceiver receiver) { 710 if (VDBG) { 711 Log.v(TAG, "setMessageStatus()"); 712 } 713 try { 714 MapClientService service = getService(source); 715 boolean result = false; 716 if (service != null) { 717 service.enforceCallingOrSelfPermission(Manifest.permission.READ_SMS, 718 "Need READ_SMS permission"); 719 result = service.setMessageStatus(device, handle, status); 720 } 721 receiver.send(result); 722 } catch (RuntimeException e) { 723 receiver.propagateException(e); 724 } 725 } 726 } 727 728 @VisibleForTesting 729 class MapBroadcastReceiver extends BroadcastReceiver { 730 @Override onReceive(Context context, Intent intent)731 public void onReceive(Context context, Intent intent) { 732 String action = intent.getAction(); 733 if (DBG) { 734 Log.d(TAG, "onReceive: " + action); 735 } 736 if (!action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED) 737 && !action.equals(BluetoothDevice.ACTION_SDP_RECORD)) { 738 // we don't care about this intent 739 return; 740 } 741 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 742 if (device == null) { 743 Log.e(TAG, "broadcast has NO device param!"); 744 return; 745 } 746 747 MceStateMachine stateMachine = mMapInstanceMap.get(device); 748 if (stateMachine == null) { 749 Log.e(TAG, "No Statemachine found for the device=" + device.toString()); 750 return; 751 } 752 753 if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) { 754 int transport = 755 intent.getIntExtra(BluetoothDevice.EXTRA_TRANSPORT, BluetoothDevice.ERROR); 756 Log.i(TAG, "Received ACL disconnection event, device=" + device.toString() 757 + ", transport=" + transport); 758 759 if (transport != BluetoothDevice.TRANSPORT_BREDR) { 760 return; 761 } 762 763 if (stateMachine.getState() == BluetoothProfile.STATE_CONNECTED) { 764 stateMachine.disconnect(); 765 } 766 } 767 768 if (action.equals(BluetoothDevice.ACTION_SDP_RECORD)) { 769 ParcelUuid uuid = intent.getParcelableExtra(BluetoothDevice.EXTRA_UUID); 770 if (DBG) { 771 Log.d(TAG, "Received SDP Record event, device=" + device.toString() + ", uuid=" 772 + uuid); 773 } 774 775 if (uuid.equals(BluetoothUuid.MAS)) { 776 // Check if we have a valid SDP record. 777 SdpMasRecord masRecord = 778 intent.getParcelableExtra(BluetoothDevice.EXTRA_SDP_RECORD); 779 if (DBG) { 780 Log.d(TAG, "SDP = " + masRecord); 781 } 782 int status = intent.getIntExtra(BluetoothDevice.EXTRA_SDP_SEARCH_STATUS, -1); 783 if (masRecord == null) { 784 Log.w(TAG, "SDP search ended with no MAS record. Status: " + status); 785 return; 786 } 787 stateMachine.obtainMessage(MceStateMachine.MSG_MAS_SDP_DONE, 788 masRecord).sendToTarget(); 789 } 790 } 791 } 792 } 793 } 794