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 android.bluetooth; 18 19 import android.Manifest; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.RequiresPermission; 23 import android.annotation.SdkConstant; 24 import android.annotation.SdkConstant.SdkConstantType; 25 import android.annotation.SystemApi; 26 import android.app.PendingIntent; 27 import android.bluetooth.annotations.RequiresBluetoothConnectPermission; 28 import android.compat.annotation.UnsupportedAppUsage; 29 import android.content.Attributable; 30 import android.content.AttributionSource; 31 import android.content.Context; 32 import android.net.Uri; 33 import android.os.Binder; 34 import android.os.Build; 35 import android.os.IBinder; 36 import android.os.RemoteException; 37 import android.util.Log; 38 39 import java.util.ArrayList; 40 import java.util.Collection; 41 import java.util.List; 42 43 /** 44 * This class provides the APIs to control the Bluetooth MAP MCE Profile. 45 * 46 * @hide 47 */ 48 @SystemApi 49 public final class BluetoothMapClient implements BluetoothProfile { 50 51 private static final String TAG = "BluetoothMapClient"; 52 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); 53 private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE); 54 55 /** @hide */ 56 @RequiresBluetoothConnectPermission 57 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 58 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 59 public static final String ACTION_CONNECTION_STATE_CHANGED = 60 "android.bluetooth.mapmce.profile.action.CONNECTION_STATE_CHANGED"; 61 /** @hide */ 62 @RequiresPermission(android.Manifest.permission.RECEIVE_SMS) 63 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 64 public static final String ACTION_MESSAGE_RECEIVED = 65 "android.bluetooth.mapmce.profile.action.MESSAGE_RECEIVED"; 66 /* Actions to be used for pending intents */ 67 /** @hide */ 68 @RequiresBluetoothConnectPermission 69 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 70 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 71 public static final String ACTION_MESSAGE_SENT_SUCCESSFULLY = 72 "android.bluetooth.mapmce.profile.action.MESSAGE_SENT_SUCCESSFULLY"; 73 /** @hide */ 74 @RequiresBluetoothConnectPermission 75 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 76 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 77 public static final String ACTION_MESSAGE_DELIVERED_SUCCESSFULLY = 78 "android.bluetooth.mapmce.profile.action.MESSAGE_DELIVERED_SUCCESSFULLY"; 79 80 /** 81 * Action to notify read status changed 82 * 83 * @hide 84 */ 85 @RequiresBluetoothConnectPermission 86 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 87 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 88 public static final String ACTION_MESSAGE_READ_STATUS_CHANGED = 89 "android.bluetooth.mapmce.profile.action.MESSAGE_READ_STATUS_CHANGED"; 90 91 /** 92 * Action to notify deleted status changed 93 * 94 * @hide 95 */ 96 @RequiresBluetoothConnectPermission 97 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 98 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 99 public static final String ACTION_MESSAGE_DELETED_STATUS_CHANGED = 100 "android.bluetooth.mapmce.profile.action.MESSAGE_DELETED_STATUS_CHANGED"; 101 102 /** 103 * Extras used in ACTION_MESSAGE_RECEIVED intent. 104 * NOTE: HANDLE is only valid for a single session with the device. 105 */ 106 /** @hide */ 107 public static final String EXTRA_MESSAGE_HANDLE = 108 "android.bluetooth.mapmce.profile.extra.MESSAGE_HANDLE"; 109 /** @hide */ 110 public static final String EXTRA_MESSAGE_TIMESTAMP = 111 "android.bluetooth.mapmce.profile.extra.MESSAGE_TIMESTAMP"; 112 /** @hide */ 113 public static final String EXTRA_MESSAGE_READ_STATUS = 114 "android.bluetooth.mapmce.profile.extra.MESSAGE_READ_STATUS"; 115 /** @hide */ 116 public static final String EXTRA_SENDER_CONTACT_URI = 117 "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_URI"; 118 /** @hide */ 119 public static final String EXTRA_SENDER_CONTACT_NAME = 120 "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_NAME"; 121 122 /** 123 * Used as a boolean extra in ACTION_MESSAGE_DELETED_STATUS_CHANGED 124 * Contains the MAP message deleted status 125 * Possible values are: 126 * true: deleted 127 * false: undeleted 128 * 129 * @hide 130 */ 131 public static final String EXTRA_MESSAGE_DELETED_STATUS = 132 "android.bluetooth.mapmce.profile.extra.MESSAGE_DELETED_STATUS"; 133 134 /** 135 * Extra used in ACTION_MESSAGE_READ_STATUS_CHANGED or ACTION_MESSAGE_DELETED_STATUS_CHANGED 136 * Possible values are: 137 * 0: failure 138 * 1: success 139 * 140 * @hide 141 */ 142 public static final String EXTRA_RESULT_CODE = 143 "android.bluetooth.device.extra.RESULT_CODE"; 144 145 /** 146 * There was an error trying to obtain the state 147 * @hide 148 */ 149 public static final int STATE_ERROR = -1; 150 151 /** @hide */ 152 public static final int RESULT_FAILURE = 0; 153 /** @hide */ 154 public static final int RESULT_SUCCESS = 1; 155 /** 156 * Connection canceled before completion. 157 * @hide 158 */ 159 public static final int RESULT_CANCELED = 2; 160 /** @hide */ 161 private static final int UPLOADING_FEATURE_BITMASK = 0x08; 162 163 /* 164 * UNREAD, READ, UNDELETED, DELETED are passed as parameters 165 * to setMessageStatus to indicate the messages new state. 166 */ 167 168 /** @hide */ 169 public static final int UNREAD = 0; 170 /** @hide */ 171 public static final int READ = 1; 172 /** @hide */ 173 public static final int UNDELETED = 2; 174 /** @hide */ 175 public static final int DELETED = 3; 176 177 private final BluetoothAdapter mAdapter; 178 private final AttributionSource mAttributionSource; 179 private final BluetoothProfileConnector<IBluetoothMapClient> mProfileConnector = 180 new BluetoothProfileConnector(this, BluetoothProfile.MAP_CLIENT, 181 "BluetoothMapClient", IBluetoothMapClient.class.getName()) { 182 @Override 183 public IBluetoothMapClient getServiceInterface(IBinder service) { 184 return IBluetoothMapClient.Stub.asInterface(Binder.allowBlocking(service)); 185 } 186 }; 187 188 /** 189 * Create a BluetoothMapClient proxy object. 190 */ BluetoothMapClient(Context context, ServiceListener listener, BluetoothAdapter adapter)191 /* package */ BluetoothMapClient(Context context, ServiceListener listener, 192 BluetoothAdapter adapter) { 193 if (DBG) Log.d(TAG, "Create BluetoothMapClient proxy object"); 194 mAdapter = adapter; 195 mAttributionSource = adapter.getAttributionSource(); 196 mProfileConnector.connect(context, listener); 197 } 198 199 /** 200 * Close the connection to the backing service. 201 * Other public functions of BluetoothMap will return default error 202 * results once close() has been called. Multiple invocations of close() 203 * are ok. 204 * @hide 205 */ close()206 public void close() { 207 mProfileConnector.disconnect(); 208 } 209 getService()210 private IBluetoothMapClient getService() { 211 return mProfileConnector.getService(); 212 } 213 214 /** 215 * Returns true if the specified Bluetooth device is connected. 216 * Returns false if not connected, or if this proxy object is not 217 * currently connected to the Map service. 218 * @hide 219 */ 220 @RequiresBluetoothConnectPermission 221 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) isConnected(BluetoothDevice device)222 public boolean isConnected(BluetoothDevice device) { 223 if (VDBG) Log.d(TAG, "isConnected(" + device + ")"); 224 final IBluetoothMapClient service = getService(); 225 if (service != null) { 226 try { 227 return service.isConnected(device, mAttributionSource); 228 } catch (RemoteException e) { 229 Log.e(TAG, e.toString()); 230 } 231 } else { 232 Log.w(TAG, "Proxy not attached to service"); 233 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 234 } 235 return false; 236 } 237 238 /** 239 * Initiate connection. Initiation of outgoing connections is not 240 * supported for MAP server. 241 * 242 * @hide 243 */ 244 @RequiresBluetoothConnectPermission 245 @RequiresPermission(allOf = { 246 android.Manifest.permission.BLUETOOTH_CONNECT, 247 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 248 }) connect(BluetoothDevice device)249 public boolean connect(BluetoothDevice device) { 250 if (DBG) Log.d(TAG, "connect(" + device + ")" + "for MAPS MCE"); 251 final IBluetoothMapClient service = getService(); 252 if (service != null) { 253 try { 254 return service.connect(device, mAttributionSource); 255 } catch (RemoteException e) { 256 Log.e(TAG, e.toString()); 257 } 258 } else { 259 Log.w(TAG, "Proxy not attached to service"); 260 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 261 } 262 return false; 263 } 264 265 /** 266 * Initiate disconnect. 267 * 268 * @param device Remote Bluetooth Device 269 * @return false on error, true otherwise 270 * 271 * @hide 272 */ 273 @RequiresBluetoothConnectPermission 274 @RequiresPermission(allOf = { 275 android.Manifest.permission.BLUETOOTH_CONNECT, 276 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 277 }) disconnect(BluetoothDevice device)278 public boolean disconnect(BluetoothDevice device) { 279 if (DBG) Log.d(TAG, "disconnect(" + device + ")"); 280 final IBluetoothMapClient service = getService(); 281 if (service != null && isEnabled() && isValidDevice(device)) { 282 try { 283 return service.disconnect(device, mAttributionSource); 284 } catch (RemoteException e) { 285 Log.e(TAG, Log.getStackTraceString(new Throwable())); 286 } 287 } 288 if (service == null) Log.w(TAG, "Proxy not attached to service"); 289 return false; 290 } 291 292 /** 293 * Get the list of connected devices. Currently at most one. 294 * 295 * @return list of connected devices 296 * @hide 297 */ 298 @Override 299 @RequiresBluetoothConnectPermission 300 @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) getConnectedDevices()301 public List<BluetoothDevice> getConnectedDevices() { 302 if (DBG) Log.d(TAG, "getConnectedDevices()"); 303 final IBluetoothMapClient service = getService(); 304 if (service != null && isEnabled()) { 305 try { 306 return Attributable.setAttributionSource( 307 service.getConnectedDevices(mAttributionSource), mAttributionSource); 308 } catch (RemoteException e) { 309 Log.e(TAG, Log.getStackTraceString(new Throwable())); 310 return new ArrayList<>(); 311 } 312 } 313 if (service == null) Log.w(TAG, "Proxy not attached to service"); 314 return new ArrayList<>(); 315 } 316 317 /** 318 * Get the list of devices matching specified states. Currently at most one. 319 * 320 * @return list of matching devices 321 * @hide 322 */ 323 @Override 324 @RequiresBluetoothConnectPermission 325 @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) getDevicesMatchingConnectionStates(int[] states)326 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 327 if (DBG) Log.d(TAG, "getDevicesMatchingStates()"); 328 final IBluetoothMapClient service = getService(); 329 if (service != null && isEnabled()) { 330 try { 331 return Attributable.setAttributionSource( 332 service.getDevicesMatchingConnectionStates(states, mAttributionSource), 333 mAttributionSource); 334 } catch (RemoteException e) { 335 Log.e(TAG, Log.getStackTraceString(new Throwable())); 336 return new ArrayList<>(); 337 } 338 } 339 if (service == null) Log.w(TAG, "Proxy not attached to service"); 340 return new ArrayList<>(); 341 } 342 343 /** 344 * Get connection state of device 345 * 346 * @return device connection state 347 * @hide 348 */ 349 @Override 350 @RequiresBluetoothConnectPermission 351 @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) getConnectionState(BluetoothDevice device)352 public int getConnectionState(BluetoothDevice device) { 353 if (DBG) Log.d(TAG, "getConnectionState(" + device + ")"); 354 final IBluetoothMapClient service = getService(); 355 if (service != null && isEnabled() && isValidDevice(device)) { 356 try { 357 return service.getConnectionState(device, mAttributionSource); 358 } catch (RemoteException e) { 359 Log.e(TAG, Log.getStackTraceString(new Throwable())); 360 return BluetoothProfile.STATE_DISCONNECTED; 361 } 362 } 363 if (service == null) Log.w(TAG, "Proxy not attached to service"); 364 return BluetoothProfile.STATE_DISCONNECTED; 365 } 366 367 /** 368 * Set priority of the profile 369 * 370 * <p> The device should already be paired. 371 * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF}, 372 * 373 * @param device Paired bluetooth device 374 * @param priority 375 * @return true if priority is set, false on error 376 * @hide 377 */ 378 @RequiresBluetoothConnectPermission 379 @RequiresPermission(allOf = { 380 android.Manifest.permission.BLUETOOTH_CONNECT, 381 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 382 }) setPriority(BluetoothDevice device, int priority)383 public boolean setPriority(BluetoothDevice device, int priority) { 384 if (DBG) Log.d(TAG, "setPriority(" + device + ", " + priority + ")"); 385 return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); 386 } 387 388 /** 389 * Set connection policy of the profile 390 * 391 * <p> The device should already be paired. 392 * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, 393 * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} 394 * 395 * @param device Paired bluetooth device 396 * @param connectionPolicy is the connection policy to set to for this profile 397 * @return true if connectionPolicy is set, false on error 398 * @hide 399 */ 400 @RequiresBluetoothConnectPermission 401 @RequiresPermission(allOf = { 402 android.Manifest.permission.BLUETOOTH_CONNECT, 403 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 404 }) setConnectionPolicy(@onNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy)405 public boolean setConnectionPolicy(@NonNull BluetoothDevice device, 406 @ConnectionPolicy int connectionPolicy) { 407 if (DBG) Log.d(TAG, "setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); 408 final IBluetoothMapClient service = getService(); 409 if (service != null && isEnabled() && isValidDevice(device)) { 410 if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN 411 && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 412 return false; 413 } 414 try { 415 return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource); 416 } catch (RemoteException e) { 417 Log.e(TAG, Log.getStackTraceString(new Throwable())); 418 return false; 419 } 420 } 421 if (service == null) Log.w(TAG, "Proxy not attached to service"); 422 return false; 423 } 424 425 /** 426 * Get the priority of the profile. 427 * 428 * <p> The priority can be any of: 429 * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} 430 * 431 * @param device Bluetooth device 432 * @return priority of the device 433 * @hide 434 */ 435 @RequiresBluetoothConnectPermission 436 @RequiresPermission(allOf = { 437 android.Manifest.permission.BLUETOOTH_CONNECT, 438 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 439 }) getPriority(BluetoothDevice device)440 public int getPriority(BluetoothDevice device) { 441 if (VDBG) Log.d(TAG, "getPriority(" + device + ")"); 442 return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); 443 } 444 445 /** 446 * Get the connection policy of the profile. 447 * 448 * <p> The connection policy can be any of: 449 * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, 450 * {@link #CONNECTION_POLICY_UNKNOWN} 451 * 452 * @param device Bluetooth device 453 * @return connection policy of the device 454 * @hide 455 */ 456 @RequiresBluetoothConnectPermission 457 @RequiresPermission(allOf = { 458 android.Manifest.permission.BLUETOOTH_CONNECT, 459 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 460 }) getConnectionPolicy(@onNull BluetoothDevice device)461 public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { 462 if (VDBG) Log.d(TAG, "getConnectionPolicy(" + device + ")"); 463 final IBluetoothMapClient service = getService(); 464 if (service != null && isEnabled() && isValidDevice(device)) { 465 try { 466 return service.getConnectionPolicy(device, mAttributionSource); 467 } catch (RemoteException e) { 468 Log.e(TAG, Log.getStackTraceString(new Throwable())); 469 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 470 } 471 } 472 if (service == null) Log.w(TAG, "Proxy not attached to service"); 473 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 474 } 475 476 /** 477 * Send a message. 478 * 479 * Send an SMS message to either the contacts primary number or the telephone number specified. 480 * 481 * @param device Bluetooth device 482 * @param contacts Uri Collection of the contacts 483 * @param message Message to be sent 484 * @param sentIntent intent issued when message is sent 485 * @param deliveredIntent intent issued when message is delivered 486 * @return true if the message is enqueued, false on error 487 * @hide 488 */ 489 @SystemApi 490 @RequiresBluetoothConnectPermission 491 @RequiresPermission(allOf = { 492 android.Manifest.permission.BLUETOOTH_CONNECT, 493 android.Manifest.permission.SEND_SMS, 494 }) sendMessage(@onNull BluetoothDevice device, @NonNull Collection<Uri> contacts, @NonNull String message, @Nullable PendingIntent sentIntent, @Nullable PendingIntent deliveredIntent)495 public boolean sendMessage(@NonNull BluetoothDevice device, @NonNull Collection<Uri> contacts, 496 @NonNull String message, @Nullable PendingIntent sentIntent, 497 @Nullable PendingIntent deliveredIntent) { 498 if (DBG) Log.d(TAG, "sendMessage(" + device + ", " + contacts + ", " + message); 499 final IBluetoothMapClient service = getService(); 500 if (service != null && isEnabled() && isValidDevice(device)) { 501 try { 502 return service.sendMessage(device, contacts.toArray(new Uri[contacts.size()]), 503 message, sentIntent, deliveredIntent, mAttributionSource); 504 } catch (RemoteException e) { 505 Log.e(TAG, Log.getStackTraceString(new Throwable())); 506 return false; 507 } 508 } 509 return false; 510 } 511 512 /** 513 * Send a message. 514 * 515 * Send an SMS message to either the contacts primary number or the telephone number specified. 516 * 517 * @param device Bluetooth device 518 * @param contacts Uri[] of the contacts 519 * @param message Message to be sent 520 * @param sentIntent intent issued when message is sent 521 * @param deliveredIntent intent issued when message is delivered 522 * @return true if the message is enqueued, false on error 523 * @hide 524 */ 525 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 526 @RequiresBluetoothConnectPermission 527 @RequiresPermission(allOf = { 528 android.Manifest.permission.BLUETOOTH_CONNECT, 529 android.Manifest.permission.SEND_SMS, 530 }) sendMessage(BluetoothDevice device, Uri[] contacts, String message, PendingIntent sentIntent, PendingIntent deliveredIntent)531 public boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message, 532 PendingIntent sentIntent, PendingIntent deliveredIntent) { 533 if (DBG) Log.d(TAG, "sendMessage(" + device + ", " + contacts + ", " + message); 534 final IBluetoothMapClient service = getService(); 535 if (service != null && isEnabled() && isValidDevice(device)) { 536 try { 537 return service.sendMessage(device, contacts, message, sentIntent, deliveredIntent, 538 mAttributionSource); 539 } catch (RemoteException e) { 540 Log.e(TAG, Log.getStackTraceString(new Throwable())); 541 return false; 542 } 543 } 544 return false; 545 } 546 547 /** 548 * Get unread messages. Unread messages will be published via {@link #ACTION_MESSAGE_RECEIVED}. 549 * 550 * @param device Bluetooth device 551 * @return true if the message is enqueued, false on error 552 * @hide 553 */ 554 @RequiresBluetoothConnectPermission 555 @RequiresPermission(allOf = { 556 android.Manifest.permission.BLUETOOTH_CONNECT, 557 android.Manifest.permission.READ_SMS, 558 }) getUnreadMessages(BluetoothDevice device)559 public boolean getUnreadMessages(BluetoothDevice device) { 560 if (DBG) Log.d(TAG, "getUnreadMessages(" + device + ")"); 561 final IBluetoothMapClient service = getService(); 562 if (service != null && isEnabled() && isValidDevice(device)) { 563 try { 564 return service.getUnreadMessages(device, mAttributionSource); 565 } catch (RemoteException e) { 566 Log.e(TAG, Log.getStackTraceString(new Throwable())); 567 return false; 568 } 569 } 570 return false; 571 } 572 573 /** 574 * Returns the "Uploading" feature bit value from the SDP record's 575 * MapSupportedFeatures field (see Bluetooth MAP 1.4 spec, page 114). 576 * @param device The Bluetooth device to get this value for. 577 * @return Returns true if the Uploading bit value in SDP record's 578 * MapSupportedFeatures field is set. False is returned otherwise. 579 * @hide 580 */ 581 @RequiresBluetoothConnectPermission 582 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) isUploadingSupported(BluetoothDevice device)583 public boolean isUploadingSupported(BluetoothDevice device) { 584 final IBluetoothMapClient service = getService(); 585 try { 586 return (service != null && isEnabled() && isValidDevice(device)) 587 && ((service.getSupportedFeatures(device, mAttributionSource) 588 & UPLOADING_FEATURE_BITMASK) > 0); 589 } catch (RemoteException e) { 590 Log.e(TAG, e.getMessage()); 591 } 592 return false; 593 } 594 595 /** 596 * Set message status of message on MSE 597 * <p> 598 * When read status changed, the result will be published via 599 * {@link #ACTION_MESSAGE_READ_STATUS_CHANGED} 600 * When deleted status changed, the result will be published via 601 * {@link #ACTION_MESSAGE_DELETED_STATUS_CHANGED} 602 * 603 * @param device Bluetooth device 604 * @param handle message handle 605 * @param status <code>UNREAD</code> for "unread", <code>READ</code> for 606 * "read", <code>UNDELETED</code> for "undeleted", <code>DELETED</code> for 607 * "deleted", otherwise return error 608 * @return <code>true</code> if request has been sent, <code>false</code> on error 609 * @hide 610 */ 611 @RequiresBluetoothConnectPermission 612 @RequiresPermission(allOf = { 613 android.Manifest.permission.BLUETOOTH_CONNECT, 614 android.Manifest.permission.READ_SMS, 615 }) setMessageStatus(BluetoothDevice device, String handle, int status)616 public boolean setMessageStatus(BluetoothDevice device, String handle, int status) { 617 if (DBG) Log.d(TAG, "setMessageStatus(" + device + ", " + handle + ", " + status + ")"); 618 final IBluetoothMapClient service = getService(); 619 if (service != null && isEnabled() && isValidDevice(device) && handle != null && 620 (status == READ || status == UNREAD || status == UNDELETED || status == DELETED)) { 621 try { 622 return service.setMessageStatus(device, handle, status, mAttributionSource); 623 } catch (RemoteException e) { 624 Log.e(TAG, Log.getStackTraceString(new Throwable())); 625 return false; 626 } 627 } 628 return false; 629 } 630 isEnabled()631 private boolean isEnabled() { 632 return mAdapter.isEnabled(); 633 } 634 isValidDevice(BluetoothDevice device)635 private static boolean isValidDevice(BluetoothDevice device) { 636 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); 637 } 638 } 639