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.addAction(BluetoothDevice.ACTION_SDP_RECORD); 325 filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); 326 registerReceiver(mMapReceiver, filter); 327 removeUncleanAccounts(); 328 MapClientContent.clearAllContent(this); 329 setMapClientService(this); 330 mAdapterService.notifyActivityAttributionInfo( 331 getAttributionSource(), 332 AdapterService.ACTIVITY_ATTRIBUTION_NO_ACTIVE_DEVICE_ADDRESS); 333 return true; 334 } 335 336 @Override stop()337 protected synchronized boolean stop() { 338 if (DBG) { 339 Log.d(TAG, "stop()"); 340 } 341 342 if (mAdapterService != null) { 343 mAdapterService.notifyActivityAttributionInfo( 344 getAttributionSource(), 345 AdapterService.ACTIVITY_ATTRIBUTION_NO_ACTIVE_DEVICE_ADDRESS); 346 } 347 if (mMapReceiver != null) { 348 unregisterReceiver(mMapReceiver); 349 mMapReceiver = null; 350 } 351 if (mMnsServer != null) { 352 mMnsServer.stop(); 353 } 354 for (MceStateMachine stateMachine : mMapInstanceMap.values()) { 355 if (stateMachine.getState() == BluetoothAdapter.STATE_CONNECTED) { 356 stateMachine.disconnect(); 357 } 358 stateMachine.doQuit(); 359 } 360 mMapInstanceMap.clear(); 361 return true; 362 } 363 364 @Override cleanup()365 protected void cleanup() { 366 if (DBG) { 367 Log.d(TAG, "in Cleanup"); 368 } 369 removeUncleanAccounts(); 370 // TODO(b/72948646): should be moved to stop() 371 setMapClientService(null); 372 } 373 374 /** 375 * cleanupDevice removes the associated state machine from the instance map 376 * 377 * @param device BluetoothDevice address of remote device 378 */ 379 @VisibleForTesting cleanupDevice(BluetoothDevice device)380 public void cleanupDevice(BluetoothDevice device) { 381 if (DBG) { 382 StringBuilder sb = new StringBuilder(); 383 dump(sb); 384 Log.d(TAG, "Cleanup device: " + device + ", InstanceMap start state: " 385 + sb.toString()); 386 } 387 synchronized (mMapInstanceMap) { 388 MceStateMachine stateMachine = mMapInstanceMap.get(device); 389 if (stateMachine != null) { 390 mMapInstanceMap.remove(device); 391 } 392 } 393 if (DBG) { 394 StringBuilder sb = new StringBuilder(); 395 dump(sb); 396 Log.d(TAG, "Cleanup device: " + device + ", InstanceMap end state: " 397 + sb.toString()); 398 } 399 } 400 401 @VisibleForTesting removeUncleanAccounts()402 void removeUncleanAccounts() { 403 if (DBG) { 404 StringBuilder sb = new StringBuilder(); 405 dump(sb); 406 Log.d(TAG, "removeUncleanAccounts:InstanceMap end state: " 407 + sb.toString()); 408 } 409 Iterator iterator = mMapInstanceMap.entrySet().iterator(); 410 while (iterator.hasNext()) { 411 Map.Entry<BluetoothDevice, MceStateMachine> profileConnection = 412 (Map.Entry) iterator.next(); 413 if (profileConnection.getValue().getState() == BluetoothProfile.STATE_DISCONNECTED) { 414 iterator.remove(); 415 } 416 } 417 if (DBG) { 418 StringBuilder sb = new StringBuilder(); 419 dump(sb); 420 Log.d(TAG, "removeUncleanAccounts:InstanceMap end state: " 421 + sb.toString()); 422 } 423 } 424 getUnreadMessages(BluetoothDevice device)425 public synchronized boolean getUnreadMessages(BluetoothDevice device) { 426 MceStateMachine mapStateMachine = mMapInstanceMap.get(device); 427 if (mapStateMachine == null) { 428 return false; 429 } 430 return mapStateMachine.getUnreadMessages(); 431 } 432 433 /** 434 * Returns the SDP record's MapSupportedFeatures field (see Bluetooth MAP 1.4 spec, page 114). 435 * @param device The Bluetooth device to get this value for. 436 * @return the SDP record's MapSupportedFeatures field. 437 */ getSupportedFeatures(BluetoothDevice device)438 public synchronized int getSupportedFeatures(BluetoothDevice device) { 439 MceStateMachine mapStateMachine = mMapInstanceMap.get(device); 440 if (mapStateMachine == null) { 441 if (DBG) Log.d(TAG, "in getSupportedFeatures, returning 0"); 442 return 0; 443 } 444 return mapStateMachine.getSupportedFeatures(); 445 } 446 setMessageStatus(BluetoothDevice device, String handle, int status)447 public synchronized boolean setMessageStatus(BluetoothDevice device, String handle, int status) { 448 MceStateMachine mapStateMachine = mMapInstanceMap.get(device); 449 if (mapStateMachine == null) { 450 return false; 451 } 452 return mapStateMachine.setMessageStatus(handle, status); 453 } 454 455 @Override dump(StringBuilder sb)456 public void dump(StringBuilder sb) { 457 super.dump(sb); 458 for (MceStateMachine stateMachine : mMapInstanceMap.values()) { 459 stateMachine.dump(sb); 460 } 461 } 462 463 //Binder object: Must be static class or memory leak may occur 464 465 /** 466 * This class implements the IClient interface - or actually it validates the 467 * preconditions for calling the actual functionality in the MapClientService, and calls it. 468 */ 469 @VisibleForTesting 470 static class Binder extends IBluetoothMapClient.Stub implements IProfileServiceBinder { 471 private MapClientService mService; 472 Binder(MapClientService service)473 Binder(MapClientService service) { 474 if (VDBG) { 475 Log.v(TAG, "Binder()"); 476 } 477 mService = service; 478 } 479 480 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getService(AttributionSource source)481 private MapClientService getService(AttributionSource source) { 482 if (Utils.isInstrumentationTestMode()) { 483 return mService; 484 } 485 if (!Utils.checkServiceAvailable(mService, TAG) 486 || !(MapUtils.isSystemUser() 487 || Utils.checkCallerIsSystemOrActiveOrManagedUser(mService, TAG)) 488 || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) { 489 return null; 490 } 491 return mService; 492 } 493 494 @Override cleanup()495 public void cleanup() { 496 mService = null; 497 } 498 499 @Override isConnected(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)500 public void isConnected(BluetoothDevice device, AttributionSource source, 501 SynchronousResultReceiver receiver) { 502 if (VDBG) { 503 Log.v(TAG, "isConnected()"); 504 } 505 try { 506 MapClientService service = getService(source); 507 boolean result = false; 508 if (service != null) { 509 result = service.getConnectionState(device) == BluetoothProfile.STATE_CONNECTED; 510 } 511 receiver.send(result); 512 } catch (RuntimeException e) { 513 receiver.propagateException(e); 514 } 515 } 516 517 @Override connect(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)518 public void connect(BluetoothDevice device, AttributionSource source, 519 SynchronousResultReceiver receiver) { 520 if (VDBG) { 521 Log.v(TAG, "connect()"); 522 } 523 try { 524 MapClientService service = getService(source); 525 boolean result = false; 526 if (service != null) { 527 result = service.connect(device); 528 } 529 receiver.send(result); 530 } catch (RuntimeException e) { 531 receiver.propagateException(e); 532 } 533 } 534 535 @Override disconnect(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)536 public void disconnect(BluetoothDevice device, AttributionSource source, 537 SynchronousResultReceiver receiver) { 538 if (VDBG) { 539 Log.v(TAG, "disconnect()"); 540 } 541 try { 542 MapClientService service = getService(source); 543 boolean result = false; 544 if (service != null) { 545 result = service.disconnect(device); 546 } 547 receiver.send(result); 548 } catch (RuntimeException e) { 549 receiver.propagateException(e); 550 } 551 } 552 553 @Override getConnectedDevices(AttributionSource source, SynchronousResultReceiver receiver)554 public void getConnectedDevices(AttributionSource source, 555 SynchronousResultReceiver receiver) { 556 if (VDBG) { 557 Log.v(TAG, "getConnectedDevices()"); 558 } 559 try { 560 MapClientService service = getService(source); 561 List<BluetoothDevice> connectedDevices = new ArrayList<BluetoothDevice>(0); 562 if (service != null) { 563 connectedDevices = service.getConnectedDevices(); 564 } 565 receiver.send(connectedDevices); 566 } catch (RuntimeException e) { 567 receiver.propagateException(e); 568 } 569 } 570 571 @Override getDevicesMatchingConnectionStates(int[] states, AttributionSource source, SynchronousResultReceiver receiver)572 public void getDevicesMatchingConnectionStates(int[] states, 573 AttributionSource source, SynchronousResultReceiver receiver) { 574 if (VDBG) { 575 Log.v(TAG, "getDevicesMatchingConnectionStates()"); 576 } 577 try { 578 MapClientService service = getService(source); 579 List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>(0); 580 if (service != null) { 581 devices = service.getDevicesMatchingConnectionStates(states); 582 } 583 receiver.send(devices); 584 } catch (RuntimeException e) { 585 receiver.propagateException(e); 586 } 587 } 588 589 @Override getConnectionState(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)590 public void getConnectionState(BluetoothDevice device, AttributionSource source, 591 SynchronousResultReceiver receiver) { 592 if (VDBG) { 593 Log.v(TAG, "getConnectionState()"); 594 } 595 try { 596 MapClientService service = getService(source); 597 int state = BluetoothProfile.STATE_DISCONNECTED; 598 if (service != null) { 599 state = service.getConnectionState(device); 600 } 601 receiver.send(state); 602 } catch (RuntimeException e) { 603 receiver.propagateException(e); 604 } 605 } 606 607 @Override setConnectionPolicy(BluetoothDevice device, int connectionPolicy, AttributionSource source, SynchronousResultReceiver receiver)608 public void setConnectionPolicy(BluetoothDevice device, int connectionPolicy, 609 AttributionSource source, SynchronousResultReceiver receiver) { 610 if (VDBG) { 611 Log.v(TAG, "setConnectionPolicy()"); 612 } 613 try { 614 MapClientService service = getService(source); 615 boolean result = false; 616 if (service != null) { 617 result = service.setConnectionPolicy(device, connectionPolicy); 618 } 619 receiver.send(result); 620 } catch (RuntimeException e) { 621 receiver.propagateException(e); 622 } 623 } 624 625 @Override getConnectionPolicy(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)626 public void getConnectionPolicy(BluetoothDevice device, AttributionSource source, 627 SynchronousResultReceiver receiver) { 628 if (VDBG) { 629 Log.v(TAG, "getConnectionPolicy()"); 630 } 631 try { 632 MapClientService service = getService(source); 633 int policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN; 634 if (service != null) { 635 policy = service.getConnectionPolicy(device); 636 } 637 receiver.send(policy); 638 } catch (RuntimeException e) { 639 receiver.propagateException(e); 640 } 641 } 642 643 @Override sendMessage(BluetoothDevice device, Uri[] contacts, String message, PendingIntent sentIntent, PendingIntent deliveredIntent, AttributionSource source, SynchronousResultReceiver receiver)644 public void sendMessage(BluetoothDevice device, Uri[] contacts, String message, 645 PendingIntent sentIntent, PendingIntent deliveredIntent, AttributionSource source, 646 SynchronousResultReceiver receiver) { 647 if (VDBG) { 648 Log.v(TAG, "sendMessage()"); 649 } 650 try { 651 MapClientService service = getService(source); 652 boolean result = false; 653 if (service != null) { 654 if (DBG) Log.d(TAG, "Checking Permission of sendMessage"); 655 service.enforceCallingOrSelfPermission(Manifest.permission.SEND_SMS, 656 "Need SEND_SMS permission"); 657 result = service.sendMessage(device, contacts, message, sentIntent, 658 deliveredIntent); 659 } 660 receiver.send(result); 661 } catch (RuntimeException e) { 662 receiver.propagateException(e); 663 } 664 } 665 666 @Override getUnreadMessages(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)667 public void getUnreadMessages(BluetoothDevice device, AttributionSource source, 668 SynchronousResultReceiver receiver) { 669 if (VDBG) { 670 Log.v(TAG, "getUnreadMessages()"); 671 } 672 try { 673 MapClientService service = getService(source); 674 boolean result = false; 675 if (service != null) { 676 service.enforceCallingOrSelfPermission(Manifest.permission.READ_SMS, 677 "Need READ_SMS permission"); 678 result = service.getUnreadMessages(device); 679 } 680 receiver.send(result); 681 } catch (RuntimeException e) { 682 receiver.propagateException(e); 683 } 684 } 685 686 @Override getSupportedFeatures(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)687 public void getSupportedFeatures(BluetoothDevice device, AttributionSource source, 688 SynchronousResultReceiver receiver) { 689 if (VDBG) { 690 Log.v(TAG, "getSupportedFeatures()"); 691 } 692 try { 693 MapClientService service = getService(source); 694 int feature = 0; 695 if (service != null) { 696 feature = service.getSupportedFeatures(device); 697 } else if (DBG) { 698 Log.d(TAG, "in MapClientService getSupportedFeatures stub, returning 0"); 699 } 700 receiver.send(feature); 701 } catch (RuntimeException e) { 702 receiver.propagateException(e); 703 } 704 } 705 706 @Override setMessageStatus(BluetoothDevice device, String handle, int status, AttributionSource source, SynchronousResultReceiver receiver)707 public void setMessageStatus(BluetoothDevice device, String handle, int status, 708 AttributionSource source, SynchronousResultReceiver receiver) { 709 if (VDBG) { 710 Log.v(TAG, "setMessageStatus()"); 711 } 712 try { 713 MapClientService service = getService(source); 714 boolean result = false; 715 if (service != null) { 716 service.enforceCallingOrSelfPermission(Manifest.permission.READ_SMS, 717 "Need READ_SMS permission"); 718 result = service.setMessageStatus(device, handle, status); 719 } 720 receiver.send(result); 721 } catch (RuntimeException e) { 722 receiver.propagateException(e); 723 } 724 } 725 } 726 727 @VisibleForTesting 728 class MapBroadcastReceiver extends BroadcastReceiver { 729 @Override onReceive(Context context, Intent intent)730 public void onReceive(Context context, Intent intent) { 731 String action = intent.getAction(); 732 if (DBG) { 733 Log.d(TAG, "onReceive: " + action); 734 } 735 if (!action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED) 736 && !action.equals(BluetoothDevice.ACTION_SDP_RECORD)) { 737 // we don't care about this intent 738 return; 739 } 740 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 741 if (device == null) { 742 Log.e(TAG, "broadcast has NO device param!"); 743 return; 744 } 745 if (DBG) { 746 Log.d(TAG, "broadcast has device: (" + device.getAddress() + ")"); 747 } 748 MceStateMachine stateMachine = mMapInstanceMap.get(device); 749 if (stateMachine == null) { 750 Log.e(TAG, "No Statemachine found for the device from broadcast"); 751 return; 752 } 753 754 if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) { 755 if (stateMachine.getState() == BluetoothProfile.STATE_CONNECTED) { 756 stateMachine.disconnect(); 757 } 758 } 759 760 if (action.equals(BluetoothDevice.ACTION_SDP_RECORD)) { 761 ParcelUuid uuid = intent.getParcelableExtra(BluetoothDevice.EXTRA_UUID); 762 if (DBG) { 763 Log.d(TAG, "UUID of SDP: " + uuid); 764 } 765 766 if (uuid.equals(BluetoothUuid.MAS)) { 767 // Check if we have a valid SDP record. 768 SdpMasRecord masRecord = 769 intent.getParcelableExtra(BluetoothDevice.EXTRA_SDP_RECORD); 770 if (DBG) { 771 Log.d(TAG, "SDP = " + masRecord); 772 } 773 int status = intent.getIntExtra(BluetoothDevice.EXTRA_SDP_SEARCH_STATUS, -1); 774 if (masRecord == null) { 775 Log.w(TAG, "SDP search ended with no MAS record. Status: " + status); 776 return; 777 } 778 stateMachine.obtainMessage(MceStateMachine.MSG_MAS_SDP_DONE, 779 masRecord).sendToTarget(); 780 } 781 } 782 } 783 } 784 } 785