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