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 /** 18 * Bluetooth MAP MCE StateMachine (Disconnected) | ^ CONNECT | | DISCONNECTED V | (Connecting) 19 * (Disconnecting) | ^ CONNECTED | | DISCONNECT V | (Connected) 20 * 21 * <p>Valid Transitions: State + Event -> Transition: 22 * 23 * <p>Disconnected + CONNECT -> Connecting Connecting + CONNECTED -> Connected Connecting + TIMEOUT 24 * -> Disconnecting Connecting + DISCONNECT/CONNECT -> Defer Message Connected + DISCONNECT -> 25 * Disconnecting Connected + CONNECT -> Disconnecting + Defer Message Disconnecting + DISCONNECTED 26 * -> (Safe) Disconnected Disconnecting + TIMEOUT -> (Force) Disconnected Disconnecting + 27 * DISCONNECT/CONNECT : Defer Message 28 */ 29 package com.android.bluetooth.mapclient; 30 31 import static android.Manifest.permission.BLUETOOTH_CONNECT; 32 import static android.Manifest.permission.BLUETOOTH_PRIVILEGED; 33 import static android.Manifest.permission.RECEIVE_SMS; 34 35 import android.app.Activity; 36 import android.app.PendingIntent; 37 import android.bluetooth.BluetoothDevice; 38 import android.bluetooth.BluetoothMapClient; 39 import android.bluetooth.BluetoothProfile; 40 import android.bluetooth.BluetoothUuid; 41 import android.bluetooth.SdpMasRecord; 42 import android.content.Intent; 43 import android.net.Uri; 44 import android.os.Looper; 45 import android.os.Message; 46 import android.os.SystemProperties; 47 import android.provider.Telephony; 48 import android.telecom.PhoneAccount; 49 import android.telephony.SmsManager; 50 import android.util.Log; 51 52 import com.android.bluetooth.BluetoothMetricsProto; 53 import com.android.bluetooth.Utils; 54 import com.android.bluetooth.btservice.AdapterService; 55 import com.android.bluetooth.btservice.MetricsLogger; 56 import com.android.bluetooth.btservice.ProfileService; 57 import com.android.bluetooth.flags.Flags; 58 import com.android.bluetooth.map.BluetoothMapbMessageMime; 59 import com.android.internal.annotations.VisibleForTesting; 60 import com.android.internal.util.State; 61 import com.android.internal.util.StateMachine; 62 import com.android.vcard.VCardConstants; 63 import com.android.vcard.VCardEntry; 64 import com.android.vcard.VCardProperty; 65 66 import java.time.Instant; 67 import java.util.ArrayList; 68 import java.util.Calendar; 69 import java.util.HashMap; 70 import java.util.HashSet; 71 import java.util.List; 72 import java.util.Set; 73 import java.util.concurrent.ConcurrentHashMap; 74 75 /* The MceStateMachine is responsible for setting up and maintaining a connection to a single 76 * specific Messaging Server Equipment endpoint. Upon connect command an SDP record is retrieved, 77 * a connection to the Message Access Server is created and a request to enable notification of new 78 * messages is sent. 79 */ 80 class MceStateMachine extends StateMachine { 81 private static final String TAG = MceStateMachine.class.getSimpleName(); 82 83 // Messages for events handled by the StateMachine 84 static final int MSG_MAS_CONNECTED = 1001; 85 static final int MSG_MAS_DISCONNECTED = 1002; 86 static final int MSG_MAS_REQUEST_COMPLETED = 1003; 87 static final int MSG_MAS_REQUEST_FAILED = 1004; 88 static final int MSG_MAS_SDP_DONE = 1005; 89 static final int MSG_MAS_SDP_UNSUCCESSFUL = 1006; 90 static final int MSG_OUTBOUND_MESSAGE = 2001; 91 static final int MSG_INBOUND_MESSAGE = 2002; 92 static final int MSG_NOTIFICATION = 2003; 93 static final int MSG_GET_LISTING = 2004; 94 static final int MSG_GET_MESSAGE_LISTING = 2005; 95 // Set message status to read or deleted 96 static final int MSG_SET_MESSAGE_STATUS = 2006; 97 static final int MSG_SEARCH_OWN_NUMBER_TIMEOUT = 2007; 98 99 // SAVE_OUTBOUND_MESSAGES defaults to true to place the responsibility of managing content on 100 // Bluetooth, to work with the default Car Messenger. This may need to be set to false if the 101 // messaging app takes that responsibility. 102 private static final Boolean SAVE_OUTBOUND_MESSAGES = true; 103 private static final int DISCONNECT_TIMEOUT = 3000; 104 private static final int CONNECT_TIMEOUT = 10000; 105 private static final int MAX_MESSAGES = 20; 106 private static final int MSG_CONNECT = 1; 107 private static final int MSG_DISCONNECT = 2; 108 static final int MSG_CONNECTING_TIMEOUT = 3; 109 private static final int MSG_DISCONNECTING_TIMEOUT = 4; 110 111 // Constants for SDP. Note that these values come from the native stack, but no centralized 112 // constants exist for them as part of the various SDP APIs. 113 public static final int SDP_SUCCESS = 0; 114 public static final int SDP_FAILED = 1; 115 public static final int SDP_BUSY = 2; 116 117 private static final boolean MESSAGE_SEEN = true; 118 private static final boolean MESSAGE_NOT_SEEN = false; 119 120 // Folder names as defined in Bluetooth.org MAP spec V10 121 private static final String FOLDER_TELECOM = "telecom"; 122 private static final String FOLDER_MSG = "msg"; 123 private static final String FOLDER_OUTBOX = "outbox"; 124 static final String FOLDER_INBOX = "inbox"; 125 static final String FOLDER_SENT = "sent"; 126 private static final String INBOX_PATH = "telecom/msg/inbox"; 127 128 // URI Scheme for messages with email contact 129 private static final String SCHEME_MAILTO = "mailto"; 130 131 private static final String FETCH_MESSAGE_TYPE = 132 "persist.bluetooth.pts.mapclient.fetchmessagetype"; 133 private static final String SEND_MESSAGE_TYPE = 134 "persist.bluetooth.pts.mapclient.sendmessagetype"; 135 136 // Connectivity States 137 private int mPreviousState = BluetoothProfile.STATE_DISCONNECTED; 138 private int mMostRecentState = BluetoothProfile.STATE_DISCONNECTED; 139 private State mDisconnected; 140 private State mConnecting; 141 private State mConnected; 142 private State mDisconnecting; 143 144 private final BluetoothDevice mDevice; 145 private MapClientService mService; 146 private MasClient mMasClient; 147 private MapClientContent mDatabase; 148 private HashMap<String, Bmessage> mSentMessageLog = new HashMap<>(MAX_MESSAGES); 149 private HashMap<Bmessage, PendingIntent> mSentReceiptRequested = new HashMap<>(MAX_MESSAGES); 150 private HashMap<Bmessage, PendingIntent> mDeliveryReceiptRequested = 151 new HashMap<>(MAX_MESSAGES); 152 private Bmessage.Type mDefaultMessageType = Bmessage.Type.SMS_CDMA; 153 154 // The amount of time for MCE to search for remote device's own phone number before: 155 // (1) MCE registering itself for being notified of the arrival of new messages; and 156 // (2) MCE start downloading existing messages off of MSE. 157 // NOTE: the value is not "final" so that it can be modified in the unit tests 158 @VisibleForTesting static int sOwnNumberSearchTimeoutMs = 3_000; 159 160 /** 161 * An object to hold the necessary meta-data for each message so we can broadcast it alongside 162 * the message content. 163 * 164 * <p>This is necessary because the metadata is inferred or received separately from the actual 165 * message content. 166 * 167 * <p>Note: In the future it may be best to use the entries from the MessageListing in full 168 * instead of this small subset. 169 */ 170 @VisibleForTesting 171 static class MessageMetadata { 172 private final String mHandle; 173 private final Long mTimestamp; 174 private boolean mRead; 175 private boolean mSeen; 176 MessageMetadata(String handle, Long timestamp, boolean read, boolean seen)177 MessageMetadata(String handle, Long timestamp, boolean read, boolean seen) { 178 mHandle = handle; 179 mTimestamp = timestamp; 180 mRead = read; 181 mSeen = seen; 182 } 183 getHandle()184 public String getHandle() { 185 return mHandle; 186 } 187 getTimestamp()188 public Long getTimestamp() { 189 return mTimestamp; 190 } 191 getRead()192 public synchronized boolean getRead() { 193 return mRead; 194 } 195 setRead(boolean read)196 public synchronized void setRead(boolean read) { 197 mRead = read; 198 } 199 getSeen()200 public synchronized boolean getSeen() { 201 return mSeen; 202 } 203 } 204 205 // Map each message to its metadata via the handle 206 @VisibleForTesting 207 ConcurrentHashMap<String, MessageMetadata> mMessages = 208 new ConcurrentHashMap<String, MessageMetadata>(); 209 MceStateMachine(MapClientService service, BluetoothDevice device)210 MceStateMachine(MapClientService service, BluetoothDevice device) { 211 this(service, device, null, null); 212 } 213 MceStateMachine(MapClientService service, BluetoothDevice device, Looper looper)214 MceStateMachine(MapClientService service, BluetoothDevice device, Looper looper) { 215 this(service, device, null, null, looper); 216 } 217 218 @VisibleForTesting MceStateMachine( MapClientService service, BluetoothDevice device, MasClient masClient, MapClientContent database)219 MceStateMachine( 220 MapClientService service, 221 BluetoothDevice device, 222 MasClient masClient, 223 MapClientContent database) { 224 super(TAG); 225 mService = service; 226 mMasClient = masClient; 227 mDevice = device; 228 mDatabase = database; 229 initStateMachine(); 230 } 231 232 @VisibleForTesting MceStateMachine( MapClientService service, BluetoothDevice device, MasClient masClient, MapClientContent database, Looper looper)233 MceStateMachine( 234 MapClientService service, 235 BluetoothDevice device, 236 MasClient masClient, 237 MapClientContent database, 238 Looper looper) { 239 super(TAG, looper); 240 mService = service; 241 mMasClient = masClient; 242 mDevice = device; 243 mDatabase = database; 244 initStateMachine(); 245 } 246 initStateMachine()247 private void initStateMachine() { 248 mPreviousState = BluetoothProfile.STATE_DISCONNECTED; 249 250 mDisconnected = new Disconnected(); 251 mConnecting = new Connecting(); 252 mDisconnecting = new Disconnecting(); 253 mConnected = new Connected(); 254 255 addState(mDisconnected); 256 addState(mConnecting); 257 addState(mDisconnecting); 258 addState(mConnected); 259 setInitialState(mConnecting); 260 start(); 261 } 262 doQuit()263 public void doQuit() { 264 quitNow(); 265 } 266 267 @Override onQuitting()268 protected void onQuitting() { 269 if (mService != null) { 270 mService.cleanupDevice(mDevice, this); 271 } 272 } 273 getDevice()274 synchronized BluetoothDevice getDevice() { 275 return mDevice; 276 } 277 onConnectionStateChanged(int prevState, int state)278 private void onConnectionStateChanged(int prevState, int state) { 279 if (mMostRecentState == state) { 280 return; 281 } 282 // mDevice == null only at setInitialState 283 if (mDevice == null) { 284 return; 285 } 286 Log.d( 287 TAG, 288 Utils.getLoggableAddress(mDevice) 289 + ": Connection state changed, prev=" 290 + prevState 291 + ", new=" 292 + state); 293 if (prevState != state && state == BluetoothProfile.STATE_CONNECTED) { 294 MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.MAP_CLIENT); 295 } 296 setState(state); 297 298 AdapterService adapterService = AdapterService.getAdapterService(); 299 if (adapterService != null) { 300 adapterService.updateProfileConnectionAdapterProperties( 301 mDevice, BluetoothProfile.MAP_CLIENT, state, prevState); 302 } 303 304 Intent intent = new Intent(BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED); 305 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); 306 intent.putExtra(BluetoothProfile.EXTRA_STATE, state); 307 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); 308 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 309 mService.sendBroadcastMultiplePermissions( 310 intent, 311 new String[] {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}, 312 Utils.getTempBroadcastOptions()); 313 } 314 setState(int state)315 private synchronized void setState(int state) { 316 mMostRecentState = state; 317 } 318 getState()319 public synchronized int getState() { 320 return mMostRecentState; 321 } 322 323 /** Notify of SDP completion. */ sendSdpResult(int status, SdpMasRecord record)324 public void sendSdpResult(int status, SdpMasRecord record) { 325 Log.d(TAG, "Received SDP Result, status=" + status + ", record=" + record); 326 if (status != SDP_SUCCESS || record == null) { 327 Log.w(TAG, "SDP unsuccessful, status: " + status + ", record: " + record); 328 sendMessage(MceStateMachine.MSG_MAS_SDP_UNSUCCESSFUL, status); 329 return; 330 } 331 sendMessage(MceStateMachine.MSG_MAS_SDP_DONE, record); 332 } 333 disconnect()334 public boolean disconnect() { 335 Log.d(TAG, "Disconnect Request " + mDevice); 336 sendMessage(MSG_DISCONNECT, mDevice); 337 return true; 338 } 339 sendMapMessage( Uri[] contacts, String message, PendingIntent sentIntent, PendingIntent deliveredIntent)340 public synchronized boolean sendMapMessage( 341 Uri[] contacts, 342 String message, 343 PendingIntent sentIntent, 344 PendingIntent deliveredIntent) { 345 Log.d(TAG, Utils.getLoggableAddress(mDevice) + ": Send, message=" + message); 346 if (contacts == null || contacts.length <= 0) { 347 return false; 348 } 349 if (mMostRecentState == BluetoothProfile.STATE_CONNECTED) { 350 Bmessage bmsg = new Bmessage(); 351 // Set type and status. 352 bmsg.setType(getDefaultMessageType()); 353 bmsg.setStatus(Bmessage.Status.READ); 354 355 for (Uri contact : contacts) { 356 // Who to send the message to. 357 Log.v(TAG, "Scheme " + contact.getScheme()); 358 if (PhoneAccount.SCHEME_TEL.equals(contact.getScheme())) { 359 String path = contact.getPath(); 360 if (path != null && path.contains(Telephony.Threads.CONTENT_URI.toString())) { 361 mDatabase.addThreadContactsToEntries(bmsg, contact.getLastPathSegment()); 362 } else { 363 VCardEntry destEntry = new VCardEntry(); 364 VCardProperty destEntryPhone = new VCardProperty(); 365 destEntryPhone.setName(VCardConstants.PROPERTY_TEL); 366 destEntryPhone.addValues(contact.getSchemeSpecificPart()); 367 destEntry.addProperty(destEntryPhone); 368 bmsg.addRecipient(destEntry); 369 Log.v(TAG, "Sending to phone numbers " + destEntryPhone.getValueList()); 370 } 371 } else if (SCHEME_MAILTO.equals(contact.getScheme())) { 372 VCardEntry destEntry = new VCardEntry(); 373 VCardProperty destEntryContact = new VCardProperty(); 374 destEntryContact.setName(VCardConstants.PROPERTY_EMAIL); 375 destEntryContact.addValues(contact.getSchemeSpecificPart()); 376 destEntry.addProperty(destEntryContact); 377 bmsg.addRecipient(destEntry); 378 Log.d(TAG, "SPECIFIC: " + contact.getSchemeSpecificPart()); 379 Log.d(TAG, "Sending to emails " + destEntryContact.getValueList()); 380 } else { 381 Log.w(TAG, "Scheme " + contact.getScheme() + " not supported."); 382 return false; 383 } 384 } 385 386 // Message of the body. 387 bmsg.setBodyContent(message); 388 if (sentIntent != null) { 389 mSentReceiptRequested.put(bmsg, sentIntent); 390 } 391 if (deliveredIntent != null) { 392 mDeliveryReceiptRequested.put(bmsg, deliveredIntent); 393 } 394 sendMessage(MSG_OUTBOUND_MESSAGE, bmsg); 395 return true; 396 } 397 return false; 398 } 399 getMessage(String handle)400 synchronized boolean getMessage(String handle) { 401 Log.d(TAG, "getMessage" + handle); 402 if (mMostRecentState == BluetoothProfile.STATE_CONNECTED) { 403 sendMessage(MSG_INBOUND_MESSAGE, handle); 404 return true; 405 } 406 return false; 407 } 408 getUnreadMessages()409 synchronized boolean getUnreadMessages() { 410 Log.d(TAG, "getMessage"); 411 if (mMostRecentState == BluetoothProfile.STATE_CONNECTED) { 412 sendMessage(MSG_GET_MESSAGE_LISTING, FOLDER_INBOX); 413 return true; 414 } 415 return false; 416 } 417 getSupportedFeatures()418 synchronized int getSupportedFeatures() { 419 if (mMostRecentState == BluetoothProfile.STATE_CONNECTED && mMasClient != null) { 420 Log.d(TAG, "returning getSupportedFeatures from SDP record"); 421 return mMasClient.getSdpMasRecord().getSupportedFeatures(); 422 } 423 Log.d(TAG, "getSupportedFeatures: no connection, returning 0"); 424 return 0; 425 } 426 setMessageStatus(String handle, int status)427 synchronized boolean setMessageStatus(String handle, int status) { 428 Log.d(TAG, "setMessageStatus(" + handle + ", " + status + ")"); 429 if (mMostRecentState == BluetoothProfile.STATE_CONNECTED) { 430 RequestSetMessageStatus.StatusIndicator statusIndicator; 431 byte value; 432 switch (status) { 433 case BluetoothMapClient.UNREAD: 434 statusIndicator = RequestSetMessageStatus.StatusIndicator.READ; 435 value = RequestSetMessageStatus.STATUS_NO; 436 break; 437 438 case BluetoothMapClient.READ: 439 statusIndicator = RequestSetMessageStatus.StatusIndicator.READ; 440 value = RequestSetMessageStatus.STATUS_YES; 441 break; 442 443 case BluetoothMapClient.UNDELETED: 444 statusIndicator = RequestSetMessageStatus.StatusIndicator.DELETED; 445 value = RequestSetMessageStatus.STATUS_NO; 446 break; 447 448 case BluetoothMapClient.DELETED: 449 statusIndicator = RequestSetMessageStatus.StatusIndicator.DELETED; 450 value = RequestSetMessageStatus.STATUS_YES; 451 break; 452 453 default: 454 Log.e(TAG, "Invalid parameter for status" + status); 455 return false; 456 } 457 sendMessage( 458 MSG_SET_MESSAGE_STATUS, 459 0, 460 0, 461 new RequestSetMessageStatus(handle, statusIndicator, value)); 462 return true; 463 } 464 return false; 465 } 466 getContactURIFromPhone(String number)467 private String getContactURIFromPhone(String number) { 468 return PhoneAccount.SCHEME_TEL + ":" + number; 469 } 470 getContactURIFromEmail(String email)471 private String getContactURIFromEmail(String email) { 472 return SCHEME_MAILTO + "://" + email; 473 } 474 getDefaultMessageType()475 Bmessage.Type getDefaultMessageType() { 476 synchronized (mDefaultMessageType) { 477 if (Utils.isPtsTestMode()) { 478 int messageType = SystemProperties.getInt(SEND_MESSAGE_TYPE, -1); 479 if (messageType > 0 && messageType < Bmessage.Type.values().length) { 480 return Bmessage.Type.values()[messageType]; 481 } 482 } 483 return mDefaultMessageType; 484 } 485 } 486 setDefaultMessageType(SdpMasRecord sdpMasRecord)487 void setDefaultMessageType(SdpMasRecord sdpMasRecord) { 488 int supportedMessageTypes = sdpMasRecord.getSupportedMessageTypes(); 489 synchronized (mDefaultMessageType) { 490 if ((supportedMessageTypes & SdpMasRecord.MessageType.MMS) > 0) { 491 mDefaultMessageType = Bmessage.Type.MMS; 492 } else if ((supportedMessageTypes & SdpMasRecord.MessageType.SMS_CDMA) > 0) { 493 mDefaultMessageType = Bmessage.Type.SMS_CDMA; 494 } else if ((supportedMessageTypes & SdpMasRecord.MessageType.SMS_GSM) > 0) { 495 mDefaultMessageType = Bmessage.Type.SMS_GSM; 496 } 497 } 498 } 499 dump(StringBuilder sb)500 public void dump(StringBuilder sb) { 501 ProfileService.println( 502 sb, 503 "mCurrentDevice: " 504 + mDevice 505 + "(" 506 + Utils.getName(mDevice) 507 + ") " 508 + this.toString()); 509 if (mDatabase != null) { 510 mDatabase.dump(sb); 511 } else { 512 ProfileService.println(sb, " Device Message DB: null"); 513 } 514 sb.append("\n"); 515 } 516 517 class Disconnected extends State { 518 @Override enter()519 public void enter() { 520 Log.d( 521 TAG, 522 Utils.getLoggableAddress(mDevice) 523 + " [Disconnected]: Entered, message=" 524 + getMessageName(getCurrentMessage().what)); 525 onConnectionStateChanged(mPreviousState, BluetoothProfile.STATE_DISCONNECTED); 526 mPreviousState = BluetoothProfile.STATE_DISCONNECTED; 527 quit(); 528 } 529 530 @Override exit()531 public void exit() { 532 mPreviousState = BluetoothProfile.STATE_DISCONNECTED; 533 } 534 } 535 536 class Connecting extends State { 537 @Override enter()538 public void enter() { 539 Log.d( 540 TAG, 541 Utils.getLoggableAddress(mDevice) 542 + " [Connecting]: Entered, message=" 543 + getMessageName(getCurrentMessage().what)); 544 onConnectionStateChanged(mPreviousState, BluetoothProfile.STATE_CONNECTING); 545 546 // When commanded to connect begin SDP to find the MAS server. 547 mDevice.sdpSearch(BluetoothUuid.MAS); 548 sendMessageDelayed(MSG_CONNECTING_TIMEOUT, CONNECT_TIMEOUT); 549 Log.i(TAG, Utils.getLoggableAddress(mDevice) + " [Connecting]: Await SDP results"); 550 } 551 552 @Override processMessage(Message message)553 public boolean processMessage(Message message) { 554 Log.d( 555 TAG, 556 Utils.getLoggableAddress(mDevice) 557 + " [Connecting]: Received " 558 + getMessageName(message.what)); 559 560 switch (message.what) { 561 case MSG_MAS_SDP_DONE: 562 Log.i(TAG, Utils.getLoggableAddress(mDevice) + " [Connecting]: SDP Complete"); 563 if (mMasClient == null) { 564 SdpMasRecord record = (SdpMasRecord) message.obj; 565 if (record == null) { 566 Log.e( 567 TAG, 568 Utils.getLoggableAddress(mDevice) 569 + " [Connecting]: SDP record is null"); 570 return NOT_HANDLED; 571 } 572 mMasClient = new MasClient(mDevice, MceStateMachine.this, record); 573 setDefaultMessageType(record); 574 } 575 break; 576 577 case MSG_MAS_SDP_UNSUCCESSFUL: 578 int sdpStatus = message.arg1; 579 Log.i( 580 TAG, 581 Utils.getLoggableAddress(mDevice) 582 + " [Connecting]: SDP unsuccessful, status=" 583 + sdpStatus); 584 if (sdpStatus == SDP_BUSY) { 585 Log.d( 586 TAG, 587 Utils.getLoggableAddress(mDevice) 588 + " [Connecting]: SDP was busy, try again"); 589 mDevice.sdpSearch(BluetoothUuid.MAS); 590 } else { 591 // This means the status is 0 (success, but no record) or 1 (organic 592 // failure). We historically have never retried SDP in failure cases, so we 593 // don't need to wait for the timeout anymore. 594 Log.d( 595 TAG, 596 Utils.getLoggableAddress(mDevice) 597 + " [Connecting]: SDP failed completely, disconnecting"); 598 transitionTo(mDisconnecting); 599 } 600 break; 601 602 case MSG_MAS_CONNECTED: 603 transitionTo(mConnected); 604 break; 605 606 case MSG_MAS_DISCONNECTED: 607 if (mMasClient != null) { 608 mMasClient.shutdown(); 609 } 610 transitionTo(mDisconnected); 611 break; 612 613 case MSG_CONNECTING_TIMEOUT: 614 transitionTo(mDisconnecting); 615 break; 616 617 case MSG_CONNECT: 618 case MSG_DISCONNECT: 619 deferMessage(message); 620 break; 621 622 default: 623 Log.w( 624 TAG, 625 Utils.getLoggableAddress(mDevice) 626 + " [Connecting]: Unexpected message: " 627 + getMessageName(message.what)); 628 return NOT_HANDLED; 629 } 630 return HANDLED; 631 } 632 633 @Override exit()634 public void exit() { 635 mPreviousState = BluetoothProfile.STATE_CONNECTING; 636 removeMessages(MSG_CONNECTING_TIMEOUT); 637 } 638 } 639 640 class Connected extends State { 641 @Override enter()642 public void enter() { 643 Log.d( 644 TAG, 645 Utils.getLoggableAddress(mDevice) 646 + " [Connected]: Entered, message=" 647 + getMessageName(getCurrentMessage().what)); 648 649 MapClientContent.Callbacks callbacks = 650 new MapClientContent.Callbacks() { 651 @Override 652 public void onMessageStatusChanged(String handle, int status) { 653 setMessageStatus(handle, status); 654 } 655 }; 656 // Keeps mock database from being overwritten in tests 657 if (mDatabase == null) { 658 mDatabase = new MapClientContent(mService, callbacks, mDevice); 659 } 660 onConnectionStateChanged(mPreviousState, BluetoothProfile.STATE_CONNECTED); 661 if (Utils.isPtsTestMode()) return; 662 663 mMasClient.makeRequest(new RequestSetPath(FOLDER_TELECOM)); 664 mMasClient.makeRequest(new RequestSetPath(FOLDER_MSG)); 665 mMasClient.makeRequest(new RequestSetPath(FOLDER_INBOX)); 666 mMasClient.makeRequest(new RequestGetFolderListing(0, 0)); 667 mMasClient.makeRequest(new RequestSetPath(false)); 668 // Start searching for remote device's own phone number. Only until either: 669 // (a) the search completes (with or without finding the number), or 670 // (b) the timeout expires, 671 // does the MCE: 672 // (a) register itself for being notified of the arrival of new messages, and 673 // (b) start downloading existing messages off of MSE. 674 // In other words, the MCE shouldn't handle any messages (new or existing) until after 675 // it has tried obtaining the remote's own phone number. 676 RequestGetMessagesListingForOwnNumber requestForOwnNumber = 677 new RequestGetMessagesListingForOwnNumber(); 678 mMasClient.makeRequest(requestForOwnNumber); 679 sendMessageDelayed( 680 MSG_SEARCH_OWN_NUMBER_TIMEOUT, requestForOwnNumber, sOwnNumberSearchTimeoutMs); 681 Log.i(TAG, Utils.getLoggableAddress(mDevice) + "[Connected]: Find phone number"); 682 } 683 684 @Override processMessage(Message message)685 public boolean processMessage(Message message) { 686 Log.d( 687 TAG, 688 Utils.getLoggableAddress(mDevice) 689 + " [Connected]: Received " 690 + getMessageName(message.what)); 691 switch (message.what) { 692 case MSG_DISCONNECT: 693 if (mDevice.equals(message.obj)) { 694 transitionTo(mDisconnecting); 695 } 696 break; 697 698 case MSG_MAS_DISCONNECTED: 699 deferMessage(message); 700 transitionTo(mDisconnecting); 701 break; 702 703 case MSG_OUTBOUND_MESSAGE: 704 mMasClient.makeRequest( 705 new RequestPushMessage( 706 FOLDER_OUTBOX, (Bmessage) message.obj, null, false, false)); 707 break; 708 709 case MSG_INBOUND_MESSAGE: 710 mMasClient.makeRequest( 711 new RequestGetMessage( 712 (String) message.obj, MasClient.CharsetType.UTF_8, false)); 713 break; 714 715 case MSG_NOTIFICATION: 716 EventReport notification = (EventReport) message.obj; 717 processNotification(notification); 718 break; 719 720 case MSG_GET_LISTING: 721 mMasClient.makeRequest(new RequestGetFolderListing(0, 0)); 722 break; 723 724 case MSG_GET_MESSAGE_LISTING: 725 // Get the 50 most recent messages from the last week 726 Calendar calendar = Calendar.getInstance(); 727 calendar.add(Calendar.DATE, -7); 728 byte messageType; 729 if (Utils.isPtsTestMode()) { 730 messageType = 731 (byte) 732 SystemProperties.getInt( 733 FETCH_MESSAGE_TYPE, 734 MessagesFilter.MESSAGE_TYPE_ALL); 735 } else { 736 messageType = MessagesFilter.MESSAGE_TYPE_ALL; 737 } 738 739 mMasClient.makeRequest( 740 new RequestGetMessagesListing( 741 (String) message.obj, 742 0, 743 new MessagesFilter.Builder() 744 .setPeriod(calendar.getTime(), null) 745 .setMessageType(messageType) 746 .build(), 747 0, 748 50, 749 0)); 750 break; 751 752 case MSG_SET_MESSAGE_STATUS: 753 if (message.obj instanceof RequestSetMessageStatus) { 754 mMasClient.makeRequest((RequestSetMessageStatus) message.obj); 755 } 756 break; 757 758 case MSG_MAS_REQUEST_COMPLETED: 759 if (message.obj instanceof RequestGetMessage) { 760 processInboundMessage((RequestGetMessage) message.obj); 761 } else if (message.obj instanceof RequestPushMessage) { 762 RequestPushMessage requestPushMessage = (RequestPushMessage) message.obj; 763 String messageHandle = requestPushMessage.getMsgHandle(); 764 Log.i( 765 TAG, 766 Utils.getLoggableAddress(mDevice) 767 + " [Connected]: Message Sent, handle=" 768 + messageHandle); 769 // ignore the top-order byte (converted to string) in the handle for now 770 // some test devices don't populate messageHandle field. 771 // in such cases, no need to wait up for response for such messages. 772 if (messageHandle != null && messageHandle.length() > 2) { 773 if (SAVE_OUTBOUND_MESSAGES) { 774 mDatabase.storeMessage( 775 requestPushMessage.getBMsg(), 776 messageHandle, 777 System.currentTimeMillis(), 778 MESSAGE_SEEN); 779 } 780 mSentMessageLog.put( 781 messageHandle.substring(2), requestPushMessage.getBMsg()); 782 } 783 } else if (message.obj instanceof RequestGetMessagesListing) { 784 processMessageListing((RequestGetMessagesListing) message.obj); 785 } else if (message.obj instanceof RequestSetMessageStatus) { 786 processSetMessageStatus((RequestSetMessageStatus) message.obj); 787 } else if (message.obj instanceof RequestGetMessagesListingForOwnNumber) { 788 processMessageListingForOwnNumber( 789 (RequestGetMessagesListingForOwnNumber) message.obj); 790 } 791 break; 792 793 case MSG_CONNECT: 794 if (!mDevice.equals(message.obj)) { 795 deferMessage(message); 796 transitionTo(mDisconnecting); 797 } 798 break; 799 800 case MSG_SEARCH_OWN_NUMBER_TIMEOUT: 801 Log.w(TAG, "Timeout while searching for own phone number."); 802 // Abort any outstanding Request so it doesn't execute on MasClient 803 RequestGetMessagesListingForOwnNumber request = 804 (RequestGetMessagesListingForOwnNumber) message.obj; 805 mMasClient.abortRequest(request); 806 // Remove any executed/completed Request that MasClient has passed back to 807 // state machine. Note: {@link StateMachine} doesn't provide a {@code 808 // removeMessages(int what, Object obj)}, nor direct access to {@link 809 // mSmHandler}, so this will remove *all* {@code MSG_MAS_REQUEST_COMPLETED} 810 // messages. However, {@link RequestGetMessagesListingForOwnNumber} should be 811 // the only MAS Request enqueued at this point, since none of the other MAS 812 // Requests should trigger/start until after getOwnNumber has completed. 813 removeMessages(MSG_MAS_REQUEST_COMPLETED); 814 // If failed to complete search for remote device's own phone number, 815 // proceed without it (i.e., register MCE for MNS and start download 816 // of existing messages from MSE). 817 notificationRegistrationAndStartDownloadMessages(); 818 break; 819 820 default: 821 Log.w( 822 TAG, 823 Utils.getLoggableAddress(mDevice) 824 + " [Connected]: Unexpected message: " 825 + getMessageName(message.what)); 826 return NOT_HANDLED; 827 } 828 return HANDLED; 829 } 830 831 @Override exit()832 public void exit() { 833 mDatabase.cleanUp(); 834 mDatabase = null; 835 mPreviousState = BluetoothProfile.STATE_CONNECTED; 836 } 837 838 /** 839 * Given a message notification event, will ensure message caching and updating and update 840 * interested applications. 841 * 842 * <p>Message notifications arrive for both remote message reception and Message-Listing 843 * object updates that are triggered by the server side. 844 * 845 * @param event - object describing the remote event 846 */ processNotification(EventReport event)847 private void processNotification(EventReport event) { 848 Log.i( 849 TAG, 850 Utils.getLoggableAddress(mDevice) 851 + " [Connected]: Received Notification, event=" 852 + event); 853 854 if (event == null) { 855 Log.w( 856 TAG, 857 Utils.getLoggableAddress(mDevice) 858 + "[Connected]: Notification event is null"); 859 return; 860 } 861 862 switch (event.getType()) { 863 case NEW_MESSAGE: 864 if (!mMessages.containsKey(event.getHandle())) { 865 Long timestamp = event.getTimestamp(); 866 if (timestamp == null) { 867 // Infer the timestamp for this message as 'now' and read status 868 // false instead of getting the message listing data for it 869 timestamp = Instant.now().toEpochMilli(); 870 } 871 MessageMetadata metadata = 872 new MessageMetadata( 873 event.getHandle(), timestamp, false, MESSAGE_NOT_SEEN); 874 mMessages.put(event.getHandle(), metadata); 875 } 876 mMasClient.makeRequest( 877 new RequestGetMessage( 878 event.getHandle(), MasClient.CharsetType.UTF_8, false)); 879 break; 880 case DELIVERY_FAILURE: 881 // fall through 882 case SENDING_FAILURE: 883 if (!Flags.handleDeliverySendingFailureEvents()) { 884 break; 885 } 886 // fall through 887 case DELIVERY_SUCCESS: 888 // fall through 889 case SENDING_SUCCESS: 890 notifySentMessageStatus(event.getHandle(), event.getType()); 891 break; 892 case READ_STATUS_CHANGED: 893 mDatabase.markRead(event.getHandle()); 894 break; 895 case MESSAGE_DELETED: 896 mDatabase.deleteMessage(event.getHandle()); 897 break; 898 default: 899 Log.d(TAG, "processNotification: ignoring event type=" + event.getType()); 900 } 901 } 902 903 /** 904 * Given the result of a Message Listing request, will cache the contents of each Message in 905 * the Message Listing Object and kick off requests to retrieve message contents from the 906 * remote device. 907 * 908 * @param request - A request object that has been resolved and returned with a message list 909 */ processMessageListing(RequestGetMessagesListing request)910 private void processMessageListing(RequestGetMessagesListing request) { 911 Log.i( 912 TAG, 913 Utils.getLoggableAddress(mDevice) 914 + " [Connected]: Received Message Listing, listing=" 915 + (request != null 916 ? (request.getList() != null 917 ? String.valueOf(request.getList().size()) 918 : "null list") 919 : "null request")); 920 921 ArrayList<com.android.bluetooth.mapclient.Message> messageListing = request.getList(); 922 if (messageListing != null) { 923 // Message listings by spec arrive ordered newest first but we wish to broadcast as 924 // oldest first. Iterate in reverse order so we initiate requests oldest first. 925 for (int i = messageListing.size() - 1; i >= 0; i--) { 926 com.android.bluetooth.mapclient.Message msg = messageListing.get(i); 927 Log.d( 928 TAG, 929 Utils.getLoggableAddress(mDevice) 930 + " [Connected]: fetch message content, handle=" 931 + msg.getHandle()); 932 // A message listing coming from the server should always have up to date data 933 if (msg.getDateTime() == null) { 934 Log.w( 935 TAG, 936 "message with handle " 937 + msg.getHandle() 938 + " has a null datetime, ignoring"); 939 continue; 940 } 941 mMessages.put( 942 msg.getHandle(), 943 new MessageMetadata( 944 msg.getHandle(), 945 msg.getDateTime().getTime(), 946 msg.isRead(), 947 MESSAGE_SEEN)); 948 getMessage(msg.getHandle()); 949 } 950 } 951 } 952 953 /** 954 * Process the result of a MessageListing request that was made specifically to obtain the 955 * remote device's own phone number. 956 * 957 * @param request - A request object that has been resolved and returned with: - a phone 958 * number (possibly null if a number wasn't found) - a flag indicating whether there are 959 * still messages that can be searched/requested. - the request will automatically 960 * update itself if a number wasn't found and there are still messages that can be 961 * searched. 962 */ processMessageListingForOwnNumber( RequestGetMessagesListingForOwnNumber request)963 private void processMessageListingForOwnNumber( 964 RequestGetMessagesListingForOwnNumber request) { 965 966 if (request.isSearchCompleted()) { 967 Log.d(TAG, "processMessageListingForOwnNumber: search completed"); 968 if (request.getOwnNumber() != null) { 969 // A phone number was found (should be the remote device's). 970 Log.d( 971 TAG, 972 "processMessageListingForOwnNumber: number found = " 973 + request.getOwnNumber()); 974 mDatabase.setRemoteDeviceOwnNumber(request.getOwnNumber()); 975 } 976 // Remove any outstanding timeouts from state machine queue 977 removeDeferredMessages(MSG_SEARCH_OWN_NUMBER_TIMEOUT); 978 removeMessages(MSG_SEARCH_OWN_NUMBER_TIMEOUT); 979 // Move on to next stage of connection process 980 notificationRegistrationAndStartDownloadMessages(); 981 } else { 982 // A phone number wasn't found, but there are still additional messages that can 983 // be requested and searched. 984 Log.d(TAG, "processMessageListingForOwnNumber: continuing search"); 985 mMasClient.makeRequest(request); 986 } 987 } 988 989 /** 990 * (1) MCE registering itself for being notified of the arrival of new messages; and (2) MCE 991 * downloading existing messages of off MSE. 992 */ notificationRegistrationAndStartDownloadMessages()993 private void notificationRegistrationAndStartDownloadMessages() { 994 Log.i(TAG, Utils.getLoggableAddress(mDevice) + "[Connected]: Queue Message downloads"); 995 mMasClient.makeRequest(new RequestSetNotificationRegistration(true)); 996 sendMessage(MSG_GET_MESSAGE_LISTING, FOLDER_SENT); 997 sendMessage(MSG_GET_MESSAGE_LISTING, FOLDER_INBOX); 998 } 999 processSetMessageStatus(RequestSetMessageStatus request)1000 private void processSetMessageStatus(RequestSetMessageStatus request) { 1001 Log.d(TAG, "processSetMessageStatus"); 1002 int result = BluetoothMapClient.RESULT_SUCCESS; 1003 if (!request.isSuccess()) { 1004 Log.e(TAG, "Set message status failed"); 1005 result = BluetoothMapClient.RESULT_FAILURE; 1006 } 1007 RequestSetMessageStatus.StatusIndicator status = request.getStatusIndicator(); 1008 switch (status) { 1009 case READ: 1010 { 1011 Intent intent = 1012 new Intent(BluetoothMapClient.ACTION_MESSAGE_READ_STATUS_CHANGED); 1013 intent.putExtra( 1014 BluetoothMapClient.EXTRA_MESSAGE_READ_STATUS, 1015 request.getValue() == RequestSetMessageStatus.STATUS_YES 1016 ? true 1017 : false); 1018 intent.putExtra( 1019 BluetoothMapClient.EXTRA_MESSAGE_HANDLE, request.getHandle()); 1020 intent.putExtra(BluetoothMapClient.EXTRA_RESULT_CODE, result); 1021 mService.sendBroadcast(intent, BLUETOOTH_CONNECT); 1022 break; 1023 } 1024 case DELETED: 1025 { 1026 Intent intent = 1027 new Intent( 1028 BluetoothMapClient.ACTION_MESSAGE_DELETED_STATUS_CHANGED); 1029 intent.putExtra( 1030 BluetoothMapClient.EXTRA_MESSAGE_DELETED_STATUS, 1031 request.getValue() == RequestSetMessageStatus.STATUS_YES 1032 ? true 1033 : false); 1034 intent.putExtra( 1035 BluetoothMapClient.EXTRA_MESSAGE_HANDLE, request.getHandle()); 1036 intent.putExtra(BluetoothMapClient.EXTRA_RESULT_CODE, result); 1037 mService.sendBroadcast(intent, BLUETOOTH_CONNECT); 1038 break; 1039 } 1040 default: 1041 Log.e(TAG, "Unknown status indicator " + status); 1042 return; 1043 } 1044 } 1045 1046 /** 1047 * Given the response of a GetMessage request, will broadcast the bMessage contents on to 1048 * all registered applications. 1049 * 1050 * <p>Inbound messages arrive as bMessage objects following a GetMessage request. GetMessage 1051 * uses a message handle that can arrive from both a GetMessageListing request or a Message 1052 * Notification event. 1053 * 1054 * @param request - A request object that has been resolved and returned with message data 1055 */ processInboundMessage(RequestGetMessage request)1056 private void processInboundMessage(RequestGetMessage request) { 1057 Bmessage message = request.getMessage(); 1058 Log.d(TAG, "Notify inbound Message" + message); 1059 1060 if (message == null) { 1061 return; 1062 } 1063 mDatabase.storeMessage( 1064 message, 1065 request.getHandle(), 1066 mMessages.get(request.getHandle()).getTimestamp(), 1067 mMessages.get(request.getHandle()).getSeen()); 1068 if (!INBOX_PATH.equalsIgnoreCase(message.getFolder())) { 1069 Log.d(TAG, "Ignoring message received in " + message.getFolder() + "."); 1070 return; 1071 } 1072 switch (message.getType()) { 1073 case SMS_CDMA: 1074 case SMS_GSM: 1075 case MMS: 1076 Log.d(TAG, "Body: " + message.getBodyContent()); 1077 Log.d(TAG, message.toString()); 1078 Log.d(TAG, "Recipients" + message.getRecipients().toString()); 1079 1080 // Grab the message metadata and update the cached read status from the bMessage 1081 MessageMetadata metadata = mMessages.get(request.getHandle()); 1082 metadata.setRead(request.getMessage().getStatus() == Bmessage.Status.READ); 1083 1084 Intent intent = new Intent(); 1085 intent.setAction(BluetoothMapClient.ACTION_MESSAGE_RECEIVED); 1086 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); 1087 intent.putExtra(BluetoothMapClient.EXTRA_MESSAGE_HANDLE, request.getHandle()); 1088 intent.putExtra( 1089 BluetoothMapClient.EXTRA_MESSAGE_TIMESTAMP, metadata.getTimestamp()); 1090 intent.putExtra( 1091 BluetoothMapClient.EXTRA_MESSAGE_READ_STATUS, metadata.getRead()); 1092 intent.putExtra(android.content.Intent.EXTRA_TEXT, message.getBodyContent()); 1093 VCardEntry originator = message.getOriginator(); 1094 if (originator != null) { 1095 Log.d(TAG, originator.toString()); 1096 List<VCardEntry.PhoneData> phoneData = originator.getPhoneList(); 1097 List<VCardEntry.EmailData> emailData = originator.getEmailList(); 1098 if (phoneData != null && phoneData.size() > 0) { 1099 String phoneNumber = phoneData.get(0).getNumber(); 1100 Log.d(TAG, "Originator number: " + phoneNumber); 1101 intent.putExtra( 1102 BluetoothMapClient.EXTRA_SENDER_CONTACT_URI, 1103 getContactURIFromPhone(phoneNumber)); 1104 } else if (emailData != null && emailData.size() > 0) { 1105 String email = emailData.get(0).getAddress(); 1106 Log.d(TAG, "Originator email: " + email); 1107 intent.putExtra( 1108 BluetoothMapClient.EXTRA_SENDER_CONTACT_URI, 1109 getContactURIFromEmail(email)); 1110 } 1111 intent.putExtra( 1112 BluetoothMapClient.EXTRA_SENDER_CONTACT_NAME, 1113 originator.getDisplayName()); 1114 } 1115 if (message.getType() == Bmessage.Type.MMS) { 1116 BluetoothMapbMessageMime mmsBmessage = new BluetoothMapbMessageMime(); 1117 mmsBmessage.parseMsgPart(message.getBodyContent()); 1118 intent.putExtra( 1119 android.content.Intent.EXTRA_TEXT, mmsBmessage.getMessageAsText()); 1120 ArrayList<VCardEntry> recipients = message.getRecipients(); 1121 if (recipients != null && !recipients.isEmpty()) { 1122 intent.putExtra( 1123 android.content.Intent.EXTRA_CC, getRecipientsUri(recipients)); 1124 } 1125 } 1126 String defaultMessagingPackage = Telephony.Sms.getDefaultSmsPackage(mService); 1127 if (defaultMessagingPackage == null) { 1128 // Broadcast to all RECEIVE_SMS recipients, including the SMS receiver 1129 // package defined in system properties if one exists 1130 mService.sendBroadcast(intent, RECEIVE_SMS); 1131 } else { 1132 String smsReceiverPackageName = 1133 SystemProperties.get( 1134 "bluetooth.profile.map_client.sms_receiver_package", null); 1135 if (smsReceiverPackageName != null && !smsReceiverPackageName.isEmpty()) { 1136 // Clone intent and broadcast to SMS receiver package if one exists 1137 Intent messageNotificationIntent = (Intent) intent.clone(); 1138 messageNotificationIntent.setPackage(smsReceiverPackageName); 1139 mService.sendBroadcast(messageNotificationIntent, RECEIVE_SMS); 1140 } 1141 // Broadcast to default messaging package 1142 intent.setPackage(defaultMessagingPackage); 1143 mService.sendBroadcast(intent, RECEIVE_SMS); 1144 } 1145 break; 1146 case EMAIL: 1147 default: 1148 Log.e(TAG, "Received unhandled type" + message.getType().toString()); 1149 break; 1150 } 1151 } 1152 1153 /** 1154 * Retrieves the URIs of all the participants of a group conversation, besides the sender of 1155 * the message. 1156 */ getRecipientsUri(ArrayList<VCardEntry> recipients)1157 private String[] getRecipientsUri(ArrayList<VCardEntry> recipients) { 1158 Set<String> uris = new HashSet<>(); 1159 1160 for (VCardEntry recipient : recipients) { 1161 List<VCardEntry.PhoneData> phoneData = recipient.getPhoneList(); 1162 if (phoneData != null && phoneData.size() > 0) { 1163 String phoneNumber = phoneData.get(0).getNumber(); 1164 Log.d(TAG, "CC Recipient number: " + phoneNumber); 1165 uris.add(getContactURIFromPhone(phoneNumber)); 1166 } 1167 } 1168 String[] stringUris = new String[uris.size()]; 1169 return uris.toArray(stringUris); 1170 } 1171 notifySentMessageStatus(String handle, EventReport.Type status)1172 private void notifySentMessageStatus(String handle, EventReport.Type status) { 1173 Log.d(TAG, "got a status for " + handle + " Status = " + status); 1174 // some test devices don't populate messageHandle field. 1175 // in such cases, ignore such messages. 1176 if (handle == null || handle.length() <= 2) return; 1177 PendingIntent intentToSend = null; 1178 // ignore the top-order byte (converted to string) in the handle for now 1179 String shortHandle = handle.substring(2); 1180 if (status == EventReport.Type.SENDING_FAILURE 1181 || status == EventReport.Type.SENDING_SUCCESS) { 1182 intentToSend = mSentReceiptRequested.remove(mSentMessageLog.get(shortHandle)); 1183 } else if (status == EventReport.Type.DELIVERY_SUCCESS 1184 || status == EventReport.Type.DELIVERY_FAILURE) { 1185 intentToSend = mDeliveryReceiptRequested.remove(mSentMessageLog.get(shortHandle)); 1186 } 1187 1188 if (intentToSend != null) { 1189 try { 1190 Log.d(TAG, "*******Sending " + intentToSend); 1191 int result = Activity.RESULT_OK; 1192 if (status == EventReport.Type.SENDING_FAILURE 1193 || status == EventReport.Type.DELIVERY_FAILURE) { 1194 result = SmsManager.RESULT_ERROR_GENERIC_FAILURE; 1195 } 1196 intentToSend.send(result); 1197 } catch (PendingIntent.CanceledException e) { 1198 Log.w(TAG, "Notification Request Canceled" + e); 1199 } 1200 } else { 1201 Log.e( 1202 TAG, 1203 "Received a notification on message with handle = " 1204 + handle 1205 + ", but it is NOT found in mSentMessageLog! where did it go?"); 1206 } 1207 } 1208 } 1209 1210 class Disconnecting extends State { 1211 @Override enter()1212 public void enter() { 1213 Log.d( 1214 TAG, 1215 Utils.getLoggableAddress(mDevice) 1216 + " [Disconnecting]: Entered, message=" 1217 + getMessageName(getCurrentMessage().what)); 1218 1219 onConnectionStateChanged(mPreviousState, BluetoothProfile.STATE_DISCONNECTING); 1220 1221 if (mMasClient != null) { 1222 mMasClient.makeRequest(new RequestSetNotificationRegistration(false)); 1223 mMasClient.shutdown(); 1224 sendMessageDelayed(MSG_DISCONNECTING_TIMEOUT, DISCONNECT_TIMEOUT); 1225 } else { 1226 // MAP was never connected 1227 transitionTo(mDisconnected); 1228 } 1229 } 1230 1231 @Override processMessage(Message message)1232 public boolean processMessage(Message message) { 1233 Log.d( 1234 TAG, 1235 Utils.getLoggableAddress(mDevice) 1236 + " [Disconnecting]: Received " 1237 + getMessageName(message.what)); 1238 switch (message.what) { 1239 case MSG_DISCONNECTING_TIMEOUT: 1240 case MSG_MAS_DISCONNECTED: 1241 mMasClient = null; 1242 transitionTo(mDisconnected); 1243 break; 1244 1245 case MSG_CONNECT: 1246 case MSG_DISCONNECT: 1247 deferMessage(message); 1248 break; 1249 1250 default: 1251 Log.w( 1252 TAG, 1253 Utils.getLoggableAddress(mDevice) 1254 + " [Disconnecting]: Unexpected message: " 1255 + getMessageName(message.what)); 1256 return NOT_HANDLED; 1257 } 1258 return HANDLED; 1259 } 1260 1261 @Override exit()1262 public void exit() { 1263 mPreviousState = BluetoothProfile.STATE_DISCONNECTING; 1264 removeMessages(MSG_DISCONNECTING_TIMEOUT); 1265 } 1266 } 1267 receiveEvent(EventReport ev)1268 void receiveEvent(EventReport ev) { 1269 Log.d(TAG, "Message Type = " + ev.getType() + ", Message handle = " + ev.getHandle()); 1270 sendMessage(MSG_NOTIFICATION, ev); 1271 } 1272 getMessageName(int what)1273 private String getMessageName(int what) { 1274 switch (what) { 1275 case MSG_MAS_CONNECTED: 1276 return "MSG_MAS_CONNECTED"; 1277 case MSG_MAS_DISCONNECTED: 1278 return "MSG_MAS_DISCONNECTED"; 1279 case MSG_MAS_REQUEST_COMPLETED: 1280 return "MSG_MAS_REQUEST_COMPLETED"; 1281 case MSG_MAS_REQUEST_FAILED: 1282 return "MSG_MAS_REQUEST_FAILED"; 1283 case MSG_MAS_SDP_DONE: 1284 return "MSG_MAS_SDP_DONE"; 1285 case MSG_MAS_SDP_UNSUCCESSFUL: 1286 return "MSG_MAS_SDP_UNSUCCESSFUL"; 1287 case MSG_OUTBOUND_MESSAGE: 1288 return "MSG_OUTBOUND_MESSAGE"; 1289 case MSG_INBOUND_MESSAGE: 1290 return "MSG_INBOUND_MESSAGE"; 1291 case MSG_NOTIFICATION: 1292 return "MSG_NOTIFICATION"; 1293 case MSG_GET_LISTING: 1294 return "MSG_GET_LISTING"; 1295 case MSG_GET_MESSAGE_LISTING: 1296 return "MSG_GET_MESSAGE_LISTING"; 1297 case MSG_SET_MESSAGE_STATUS: 1298 return "MSG_SET_MESSAGE_STATUS"; 1299 case DISCONNECT_TIMEOUT: 1300 return "DISCONNECT_TIMEOUT"; 1301 case CONNECT_TIMEOUT: 1302 return "CONNECT_TIMEOUT"; 1303 case MSG_CONNECT: 1304 return "MSG_CONNECT"; 1305 case MSG_DISCONNECT: 1306 return "MSG_DISCONNECT"; 1307 case MSG_CONNECTING_TIMEOUT: 1308 return "MSG_CONNECTING_TIMEOUT"; 1309 case MSG_DISCONNECTING_TIMEOUT: 1310 return "MSG_DISCONNECTING_TIMEOUT"; 1311 } 1312 return "UNKNOWN"; 1313 } 1314 } 1315