• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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