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