• 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 /**
18  * Bluetooth MAP MCE StateMachine
19  *         (Disconnected)
20  *             |    ^
21  *     CONNECT |    | DISCONNECTED
22  *             V    |
23  *    (Connecting) (Disconnecting)
24  *             |    ^
25  *   CONNECTED |    | DISCONNECT
26  *             V    |
27  *           (Connected)
28  *
29  * Valid Transitions: State + Event -> Transition:
30  *
31  * Disconnected + CONNECT -> Connecting
32  * Connecting + CONNECTED -> Connected
33  * Connecting + TIMEOUT -> Disconnecting
34  * Connecting + DISCONNECT/CONNECT -> Defer Message
35  * Connected + DISCONNECT -> Disconnecting
36  * Connected + CONNECT -> Disconnecting + Defer Message
37  * Disconnecting + DISCONNECTED -> (Safe) Disconnected
38  * Disconnecting + TIMEOUT -> (Force) Disconnected
39  * Disconnecting + DISCONNECT/CONNECT : Defer Message
40  */
41 package com.android.bluetooth.mapclient;
42 
43 import android.app.Activity;
44 import android.app.PendingIntent;
45 import android.bluetooth.BluetoothAdapter;
46 import android.bluetooth.BluetoothDevice;
47 import android.bluetooth.BluetoothMapClient;
48 import android.bluetooth.BluetoothProfile;
49 import android.bluetooth.BluetoothUuid;
50 import android.bluetooth.SdpMasRecord;
51 import android.content.Intent;
52 import android.net.Uri;
53 import android.os.Message;
54 import android.telecom.PhoneAccount;
55 import android.telephony.SmsManager;
56 import android.util.Log;
57 
58 import com.android.bluetooth.BluetoothMetricsProto;
59 import com.android.bluetooth.btservice.MetricsLogger;
60 import com.android.bluetooth.btservice.ProfileService;
61 import com.android.internal.annotations.VisibleForTesting;
62 import com.android.internal.util.IState;
63 import com.android.internal.util.State;
64 import com.android.internal.util.StateMachine;
65 import com.android.vcard.VCardConstants;
66 import com.android.vcard.VCardEntry;
67 import com.android.vcard.VCardProperty;
68 
69 import java.util.ArrayList;
70 import java.util.Calendar;
71 import java.util.HashMap;
72 import java.util.List;
73 
74 /* The MceStateMachine is responsible for setting up and maintaining a connection to a single
75  * specific Messaging Server Equipment endpoint.  Upon connect command an SDP record is retrieved,
76  * a connection to the Message Access Server is created and a request to enable notification of new
77  * messages is sent.
78  */
79 final class MceStateMachine extends StateMachine {
80     // Messages for events handled by the StateMachine
81     static final int MSG_MAS_CONNECTED = 1001;
82     static final int MSG_MAS_DISCONNECTED = 1002;
83     static final int MSG_MAS_REQUEST_COMPLETED = 1003;
84     static final int MSG_MAS_REQUEST_FAILED = 1004;
85     static final int MSG_MAS_SDP_DONE = 1005;
86     static final int MSG_MAS_SDP_FAILED = 1006;
87     static final int MSG_OUTBOUND_MESSAGE = 2001;
88     static final int MSG_INBOUND_MESSAGE = 2002;
89     static final int MSG_NOTIFICATION = 2003;
90     static final int MSG_GET_LISTING = 2004;
91     static final int MSG_GET_MESSAGE_LISTING = 2005;
92 
93     private static final String TAG = "MceSM";
94     private static final Boolean DBG = MapClientService.DBG;
95     private static final int TIMEOUT = 10000;
96     private static final int MAX_MESSAGES = 20;
97     private static final int MSG_CONNECT = 1;
98     private static final int MSG_DISCONNECT = 2;
99     private static final int MSG_CONNECTING_TIMEOUT = 3;
100     private static final int MSG_DISCONNECTING_TIMEOUT = 4;
101     // Folder names as defined in Bluetooth.org MAP spec V10
102     private static final String FOLDER_TELECOM = "telecom";
103     private static final String FOLDER_MSG = "msg";
104     private static final String FOLDER_OUTBOX = "outbox";
105     private static final String FOLDER_INBOX = "inbox";
106     private static final String INBOX_PATH = "telecom/msg/inbox";
107 
108 
109     // Connectivity States
110     private int mPreviousState = BluetoothProfile.STATE_DISCONNECTED;
111     private State mDisconnected;
112     private State mConnecting;
113     private State mConnected;
114     private State mDisconnecting;
115 
116     private final BluetoothDevice mDevice;
117     private MapClientService mService;
118     private MasClient mMasClient;
119     private HashMap<String, Bmessage> mSentMessageLog = new HashMap<>(MAX_MESSAGES);
120     private HashMap<Bmessage, PendingIntent> mSentReceiptRequested = new HashMap<>(MAX_MESSAGES);
121     private HashMap<Bmessage, PendingIntent> mDeliveryReceiptRequested =
122             new HashMap<>(MAX_MESSAGES);
123     private Bmessage.Type mDefaultMessageType = Bmessage.Type.SMS_CDMA;
124 
MceStateMachine(MapClientService service, BluetoothDevice device)125     MceStateMachine(MapClientService service, BluetoothDevice device) {
126         this(service, device, null);
127     }
128 
129     @VisibleForTesting
MceStateMachine(MapClientService service, BluetoothDevice device, MasClient masClient)130     MceStateMachine(MapClientService service, BluetoothDevice device, MasClient masClient) {
131         super(TAG);
132         mMasClient = masClient;
133         mService = service;
134 
135         mPreviousState = BluetoothProfile.STATE_DISCONNECTED;
136 
137         mDevice = device;
138         mDisconnected = new Disconnected();
139         mConnecting = new Connecting();
140         mDisconnecting = new Disconnecting();
141         mConnected = new Connected();
142 
143         addState(mDisconnected);
144         addState(mConnecting);
145         addState(mDisconnecting);
146         addState(mConnected);
147         setInitialState(mConnecting);
148         start();
149     }
150 
doQuit()151     public void doQuit() {
152         quitNow();
153     }
154 
155     @Override
onQuitting()156     protected void onQuitting() {
157         if (mService != null) {
158             mService.cleanupDevice(mDevice);
159         }
160     }
161 
getDevice()162     synchronized BluetoothDevice getDevice() {
163         return mDevice;
164     }
165 
onConnectionStateChanged(int prevState, int state)166     private void onConnectionStateChanged(int prevState, int state) {
167         // mDevice == null only at setInitialState
168         if (mDevice == null) {
169             return;
170         }
171         if (DBG) {
172             Log.d(TAG, "Connection state " + mDevice + ": " + prevState + "->" + state);
173         }
174         if (prevState != state && state == BluetoothProfile.STATE_CONNECTED) {
175             MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.MAP_CLIENT);
176         }
177         Intent intent = new Intent(BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED);
178         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
179         intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
180         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
181         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
182         mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
183     }
184 
getState()185     public synchronized int getState() {
186         IState currentState = this.getCurrentState();
187         if (currentState.getClass() == Disconnected.class) {
188             return BluetoothProfile.STATE_DISCONNECTED;
189         }
190         if (currentState.getClass() == Connected.class) {
191             return BluetoothProfile.STATE_CONNECTED;
192         }
193         if (currentState.getClass() == Connecting.class) {
194             return BluetoothProfile.STATE_CONNECTING;
195         }
196         if (currentState.getClass() == Disconnecting.class) {
197             return BluetoothProfile.STATE_DISCONNECTING;
198         }
199         return BluetoothProfile.STATE_DISCONNECTED;
200     }
201 
disconnect()202     public boolean disconnect() {
203         if (DBG) {
204             Log.d(TAG, "Disconnect Request " + mDevice.getAddress());
205         }
206         sendMessage(MSG_DISCONNECT, mDevice);
207         return true;
208     }
209 
sendMapMessage(Uri[] contacts, String message, PendingIntent sentIntent, PendingIntent deliveredIntent)210     public synchronized boolean sendMapMessage(Uri[] contacts, String message,
211             PendingIntent sentIntent, PendingIntent deliveredIntent) {
212         if (DBG) {
213             Log.d(TAG, "Send Message " + message);
214         }
215         if (contacts == null || contacts.length <= 0) {
216             return false;
217         }
218         if (this.getCurrentState() == mConnected) {
219             Bmessage bmsg = new Bmessage();
220             // Set type and status.
221             bmsg.setType(getDefaultMessageType());
222             bmsg.setStatus(Bmessage.Status.READ);
223 
224             for (Uri contact : contacts) {
225                 // Who to send the message to.
226                 VCardEntry destEntry = new VCardEntry();
227                 VCardProperty destEntryPhone = new VCardProperty();
228                 if (DBG) {
229                     Log.d(TAG, "Scheme " + contact.getScheme());
230                 }
231                 if (PhoneAccount.SCHEME_TEL.equals(contact.getScheme())) {
232                     destEntryPhone.setName(VCardConstants.PROPERTY_TEL);
233                     destEntryPhone.addValues(contact.getSchemeSpecificPart());
234                     if (DBG) {
235                         Log.d(TAG, "Sending to phone numbers " + destEntryPhone.getValueList());
236                     }
237                 } else {
238                     if (DBG) {
239                         Log.w(TAG, "Scheme " + contact.getScheme() + " not supported.");
240                     }
241                     return false;
242                 }
243                 destEntry.addProperty(destEntryPhone);
244                 bmsg.addRecipient(destEntry);
245             }
246 
247             // Message of the body.
248             bmsg.setBodyContent(message);
249             if (sentIntent != null) {
250                 mSentReceiptRequested.put(bmsg, sentIntent);
251             }
252             if (deliveredIntent != null) {
253                 mDeliveryReceiptRequested.put(bmsg, deliveredIntent);
254             }
255             sendMessage(MSG_OUTBOUND_MESSAGE, bmsg);
256             return true;
257         }
258         return false;
259     }
260 
getMessage(String handle)261     synchronized boolean getMessage(String handle) {
262         if (DBG) {
263             Log.d(TAG, "getMessage" + handle);
264         }
265         if (this.getCurrentState() == mConnected) {
266             sendMessage(MSG_INBOUND_MESSAGE, handle);
267             return true;
268         }
269         return false;
270     }
271 
getUnreadMessages()272     synchronized boolean getUnreadMessages() {
273         if (DBG) {
274             Log.d(TAG, "getMessage");
275         }
276         if (this.getCurrentState() == mConnected) {
277             sendMessage(MSG_GET_MESSAGE_LISTING, FOLDER_INBOX);
278             return true;
279         }
280         return false;
281     }
282 
getContactURIFromPhone(String number)283     private String getContactURIFromPhone(String number) {
284         return PhoneAccount.SCHEME_TEL + ":" + number;
285     }
286 
getDefaultMessageType()287     Bmessage.Type getDefaultMessageType() {
288         synchronized (mDefaultMessageType) {
289             return mDefaultMessageType;
290         }
291     }
292 
setDefaultMessageType(SdpMasRecord sdpMasRecord)293     void setDefaultMessageType(SdpMasRecord sdpMasRecord) {
294         int supportedMessageTypes = sdpMasRecord.getSupportedMessageTypes();
295         synchronized (mDefaultMessageType) {
296             if ((supportedMessageTypes & SdpMasRecord.MessageType.SMS_CDMA) > 0) {
297                 mDefaultMessageType = Bmessage.Type.SMS_CDMA;
298             } else if ((supportedMessageTypes & SdpMasRecord.MessageType.SMS_GSM) > 0) {
299                 mDefaultMessageType = Bmessage.Type.SMS_GSM;
300             }
301         }
302     }
303 
dump(StringBuilder sb)304     public void dump(StringBuilder sb) {
305         ProfileService.println(sb, "mCurrentDevice: " + mDevice.getAddress() + " (name = "
306                 + mDevice.getName() + "), StateMachine: " + this.toString());
307     }
308 
309     class Disconnected extends State {
310         @Override
enter()311         public void enter() {
312             if (DBG) {
313                 Log.d(TAG, "Enter Disconnected: " + getCurrentMessage().what);
314             }
315             onConnectionStateChanged(mPreviousState, BluetoothProfile.STATE_DISCONNECTED);
316             mPreviousState = BluetoothProfile.STATE_DISCONNECTED;
317             quit();
318         }
319 
320         @Override
exit()321         public void exit() {
322             mPreviousState = BluetoothProfile.STATE_DISCONNECTED;
323         }
324     }
325 
326     class Connecting extends State {
327         @Override
enter()328         public void enter() {
329             if (DBG) {
330                 Log.d(TAG, "Enter Connecting: " + getCurrentMessage().what);
331             }
332             onConnectionStateChanged(mPreviousState, BluetoothProfile.STATE_CONNECTING);
333 
334             BluetoothAdapter.getDefaultAdapter().cancelDiscovery();
335             // When commanded to connect begin SDP to find the MAS server.
336             mDevice.sdpSearch(BluetoothUuid.MAS);
337             sendMessageDelayed(MSG_CONNECTING_TIMEOUT, TIMEOUT);
338         }
339 
340         @Override
processMessage(Message message)341         public boolean processMessage(Message message) {
342             if (DBG) {
343                 Log.d(TAG, "processMessage" + this.getName() + message.what);
344             }
345 
346             switch (message.what) {
347                 case MSG_MAS_SDP_DONE:
348                     if (DBG) {
349                         Log.d(TAG, "SDP Complete");
350                     }
351                     if (mMasClient == null) {
352                         mMasClient = new MasClient(mDevice, MceStateMachine.this,
353                                 (SdpMasRecord) message.obj);
354                         setDefaultMessageType((SdpMasRecord) message.obj);
355                     }
356                     break;
357 
358                 case MSG_MAS_CONNECTED:
359                     transitionTo(mConnected);
360                     break;
361 
362                 case MSG_MAS_DISCONNECTED:
363                     transitionTo(mDisconnected);
364                     break;
365 
366                 case MSG_CONNECTING_TIMEOUT:
367                     transitionTo(mDisconnecting);
368                     break;
369 
370                 case MSG_CONNECT:
371                 case MSG_DISCONNECT:
372                     deferMessage(message);
373                     break;
374 
375                 default:
376                     Log.w(TAG, "Unexpected message: " + message.what + " from state:"
377                             + this.getName());
378                     return NOT_HANDLED;
379             }
380             return HANDLED;
381         }
382 
383         @Override
exit()384         public void exit() {
385             mPreviousState = BluetoothProfile.STATE_CONNECTING;
386             removeMessages(MSG_CONNECTING_TIMEOUT);
387         }
388     }
389 
390     class Connected extends State {
391         @Override
enter()392         public void enter() {
393             if (DBG) {
394                 Log.d(TAG, "Enter Connected: " + getCurrentMessage().what);
395             }
396             onConnectionStateChanged(mPreviousState, BluetoothProfile.STATE_CONNECTED);
397 
398             mMasClient.makeRequest(new RequestSetPath(FOLDER_TELECOM));
399             mMasClient.makeRequest(new RequestSetPath(FOLDER_MSG));
400             mMasClient.makeRequest(new RequestSetPath(FOLDER_INBOX));
401             mMasClient.makeRequest(new RequestGetFolderListing(0, 0));
402             mMasClient.makeRequest(new RequestSetPath(false));
403             mMasClient.makeRequest(new RequestSetNotificationRegistration(true));
404         }
405 
406         @Override
processMessage(Message message)407         public boolean processMessage(Message message) {
408             switch (message.what) {
409                 case MSG_DISCONNECT:
410                     if (mDevice.equals(message.obj)) {
411                         transitionTo(mDisconnecting);
412                     }
413                     break;
414 
415                 case MSG_OUTBOUND_MESSAGE:
416                     mMasClient.makeRequest(
417                             new RequestPushMessage(FOLDER_OUTBOX, (Bmessage) message.obj, null,
418                                     false, false));
419                     break;
420 
421                 case MSG_INBOUND_MESSAGE:
422                     mMasClient.makeRequest(
423                             new RequestGetMessage((String) message.obj, MasClient.CharsetType.UTF_8,
424                                     false));
425                     break;
426 
427                 case MSG_NOTIFICATION:
428                     processNotification(message);
429                     break;
430 
431                 case MSG_GET_LISTING:
432                     mMasClient.makeRequest(new RequestGetFolderListing(0, 0));
433                     break;
434 
435                 case MSG_GET_MESSAGE_LISTING:
436                     // Get latest 50 Unread messages in the last week
437                     MessagesFilter filter = new MessagesFilter();
438                     filter.setMessageType((byte) 0);
439                     filter.setReadStatus(MessagesFilter.READ_STATUS_UNREAD);
440                     Calendar calendar = Calendar.getInstance();
441                     calendar.add(Calendar.DATE, -7);
442                     filter.setPeriod(calendar.getTime(), null);
443                     mMasClient.makeRequest(new RequestGetMessagesListing(
444                             (String) message.obj, 0, filter, 0, 50, 0));
445                     break;
446 
447                 case MSG_MAS_REQUEST_COMPLETED:
448                     if (DBG) {
449                         Log.d(TAG, "Completed request");
450                     }
451                     if (message.obj instanceof RequestGetMessage) {
452                         processInboundMessage((RequestGetMessage) message.obj);
453                     } else if (message.obj instanceof RequestPushMessage) {
454                         String messageHandle = ((RequestPushMessage) message.obj).getMsgHandle();
455                         if (DBG) {
456                             Log.d(TAG, "Message Sent......." + messageHandle);
457                         }
458                         // ignore the top-order byte (converted to string) in the handle for now
459                         mSentMessageLog.put(messageHandle.substring(2),
460                                 ((RequestPushMessage) message.obj).getBMsg());
461                     } else if (message.obj instanceof RequestGetMessagesListing) {
462                         processMessageListing((RequestGetMessagesListing) message.obj);
463                     }
464                     break;
465 
466                 case MSG_CONNECT:
467                     if (!mDevice.equals(message.obj)) {
468                         deferMessage(message);
469                         transitionTo(mDisconnecting);
470                     }
471                     break;
472 
473                 default:
474                     Log.w(TAG, "Unexpected message: " + message.what + " from state:"
475                             + this.getName());
476                     return NOT_HANDLED;
477             }
478             return HANDLED;
479         }
480 
481         @Override
exit()482         public void exit() {
483             mPreviousState = BluetoothProfile.STATE_CONNECTED;
484         }
485 
processNotification(Message msg)486         private void processNotification(Message msg) {
487             if (DBG) {
488                 Log.d(TAG, "Handler: msg: " + msg.what);
489             }
490 
491             switch (msg.what) {
492                 case MSG_NOTIFICATION:
493                     EventReport ev = (EventReport) msg.obj;
494                     if (DBG) {
495                         Log.d(TAG, "Message Type = " + ev.getType());
496                     }
497                     if (DBG) {
498                         Log.d(TAG, "Message handle = " + ev.getHandle());
499                     }
500                     switch (ev.getType()) {
501 
502                         case NEW_MESSAGE:
503                             //mService.get().sendNewMessageNotification(ev);
504                             mMasClient.makeRequest(new RequestGetMessage(ev.getHandle(),
505                                     MasClient.CharsetType.UTF_8, false));
506                             break;
507 
508                         case DELIVERY_SUCCESS:
509                         case SENDING_SUCCESS:
510                             notifySentMessageStatus(ev.getHandle(), ev.getType());
511                             break;
512                     }
513             }
514         }
515 
516         // Sets the specified message status to "read" (from "unread" status, mostly)
markMessageRead(RequestGetMessage request)517         private void markMessageRead(RequestGetMessage request) {
518             if (DBG) Log.d(TAG, "markMessageRead");
519             mMasClient.makeRequest(new RequestSetMessageStatus(
520                     request.getHandle(), RequestSetMessageStatus.StatusIndicator.READ));
521         }
522 
523         // Sets the specified message status to "deleted"
markMessageDeleted(RequestGetMessage request)524         private void markMessageDeleted(RequestGetMessage request) {
525             if (DBG) Log.d(TAG, "markMessageDeleted");
526             mMasClient.makeRequest(new RequestSetMessageStatus(
527                     request.getHandle(), RequestSetMessageStatus.StatusIndicator.DELETED));
528         }
529 
processMessageListing(RequestGetMessagesListing request)530         private void processMessageListing(RequestGetMessagesListing request) {
531             if (DBG) {
532                 Log.d(TAG, "processMessageListing");
533             }
534             ArrayList<com.android.bluetooth.mapclient.Message> messageHandles = request.getList();
535             if (messageHandles != null) {
536                 for (com.android.bluetooth.mapclient.Message handle : messageHandles) {
537                     if (DBG) {
538                         Log.d(TAG, "getting message ");
539                     }
540                     getMessage(handle.getHandle());
541                 }
542             }
543         }
544 
processInboundMessage(RequestGetMessage request)545         private void processInboundMessage(RequestGetMessage request) {
546             Bmessage message = request.getMessage();
547             if (DBG) {
548                 Log.d(TAG, "Notify inbound Message" + message);
549             }
550 
551             if (message == null) {
552                 return;
553             }
554             if (!INBOX_PATH.equalsIgnoreCase(message.getFolder())) {
555                 if (DBG) {
556                     Log.d(TAG, "Ignoring message received in " + message.getFolder() + ".");
557                 }
558                 return;
559             }
560             switch (message.getType()) {
561                 case SMS_CDMA:
562                 case SMS_GSM:
563                     if (DBG) {
564                         Log.d(TAG, "Body: " + message.getBodyContent());
565                     }
566                     if (DBG) {
567                         Log.d(TAG, message.toString());
568                     }
569                     if (DBG) {
570                         Log.d(TAG, "Recipients" + message.getRecipients().toString());
571                     }
572 
573                     Intent intent = new Intent();
574                     intent.setAction(BluetoothMapClient.ACTION_MESSAGE_RECEIVED);
575                     intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
576                     intent.putExtra(BluetoothMapClient.EXTRA_MESSAGE_HANDLE, request.getHandle());
577                     intent.putExtra(android.content.Intent.EXTRA_TEXT, message.getBodyContent());
578                     VCardEntry originator = message.getOriginator();
579                     if (originator != null) {
580                         if (DBG) {
581                             Log.d(TAG, originator.toString());
582                         }
583                         List<VCardEntry.PhoneData> phoneData = originator.getPhoneList();
584                         if (phoneData != null && phoneData.size() > 0) {
585                             String phoneNumber = phoneData.get(0).getNumber();
586                             if (DBG) {
587                                 Log.d(TAG, "Originator number: " + phoneNumber);
588                             }
589                             intent.putExtra(BluetoothMapClient.EXTRA_SENDER_CONTACT_URI,
590                                     getContactURIFromPhone(phoneNumber));
591                         }
592                         intent.putExtra(BluetoothMapClient.EXTRA_SENDER_CONTACT_NAME,
593                                 originator.getDisplayName());
594                     }
595                     mService.sendBroadcast(intent);
596                     break;
597 
598                 case MMS:
599                 case EMAIL:
600                 default:
601                     Log.e(TAG, "Received unhandled type" + message.getType().toString());
602                     break;
603             }
604         }
605 
notifySentMessageStatus(String handle, EventReport.Type status)606         private void notifySentMessageStatus(String handle, EventReport.Type status) {
607             if (DBG) {
608                 Log.d(TAG, "got a status for " + handle + " Status = " + status);
609             }
610             PendingIntent intentToSend = null;
611             // ignore the top-order byte (converted to string) in the handle for now
612             String shortHandle = handle.substring(2);
613             if (status == EventReport.Type.SENDING_FAILURE
614                     || status == EventReport.Type.SENDING_SUCCESS) {
615                 intentToSend = mSentReceiptRequested.remove(mSentMessageLog.get(shortHandle));
616             } else if (status == EventReport.Type.DELIVERY_SUCCESS
617                     || status == EventReport.Type.DELIVERY_FAILURE) {
618                 intentToSend = mDeliveryReceiptRequested.remove(mSentMessageLog.get(shortHandle));
619             }
620 
621             if (intentToSend != null) {
622                 try {
623                     if (DBG) {
624                         Log.d(TAG, "*******Sending " + intentToSend);
625                     }
626                     int result = Activity.RESULT_OK;
627                     if (status == EventReport.Type.SENDING_FAILURE
628                             || status == EventReport.Type.DELIVERY_FAILURE) {
629                         result = SmsManager.RESULT_ERROR_GENERIC_FAILURE;
630                     }
631                     intentToSend.send(result);
632                 } catch (PendingIntent.CanceledException e) {
633                     Log.w(TAG, "Notification Request Canceled" + e);
634                 }
635             } else {
636                 Log.e(TAG, "Received a notification on message with handle = "
637                         + handle + ", but it is NOT found in mSentMessageLog! where did it go?");
638             }
639         }
640     }
641 
642     class Disconnecting extends State {
643         @Override
enter()644         public void enter() {
645             if (DBG) {
646                 Log.d(TAG, "Enter Disconnecting: " + getCurrentMessage().what);
647             }
648             onConnectionStateChanged(mPreviousState, BluetoothProfile.STATE_DISCONNECTING);
649 
650             if (mMasClient != null) {
651                 mMasClient.makeRequest(new RequestSetNotificationRegistration(false));
652                 mMasClient.shutdown();
653                 sendMessageDelayed(MSG_DISCONNECTING_TIMEOUT, TIMEOUT);
654             } else {
655                 // MAP was never connected
656                 transitionTo(mDisconnected);
657             }
658         }
659 
660         @Override
processMessage(Message message)661         public boolean processMessage(Message message) {
662             switch (message.what) {
663                 case MSG_DISCONNECTING_TIMEOUT:
664                 case MSG_MAS_DISCONNECTED:
665                     mMasClient = null;
666                     transitionTo(mDisconnected);
667                     break;
668 
669                 case MSG_CONNECT:
670                 case MSG_DISCONNECT:
671                     deferMessage(message);
672                     break;
673 
674                 default:
675                     Log.w(TAG, "Unexpected message: " + message.what + " from state:"
676                             + this.getName());
677                     return NOT_HANDLED;
678             }
679             return HANDLED;
680         }
681 
682         @Override
exit()683         public void exit() {
684             mPreviousState = BluetoothProfile.STATE_DISCONNECTING;
685             removeMessages(MSG_DISCONNECTING_TIMEOUT);
686         }
687     }
688 
receiveEvent(EventReport ev)689     void receiveEvent(EventReport ev) {
690         if (DBG) {
691             Log.d(TAG, "Message Type = " + ev.getType());
692         }
693         if (DBG) {
694             Log.d(TAG, "Message handle = " + ev.getHandle());
695         }
696         sendMessage(MSG_NOTIFICATION, ev);
697     }
698 }
699