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