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