Home | History | Annotate | Download | only in mapclient
      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 
    125     MceStateMachine(MapClientService service, BluetoothDevice device) {
    126         this(service, device, null);
    127     }
    128 
    129     @VisibleForTesting
    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 
    151     public void doQuit() {
    152         quitNow();
    153     }
    154 
    155     @Override
    156     protected void onQuitting() {
    157         if (mService != null) {
    158             mService.cleanupDevice(mDevice);
    159         }
    160     }
    161 
    162     synchronized BluetoothDevice getDevice() {
    163         return mDevice;
    164     }
    165 
    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 
    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 
    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 
    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 
    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 
    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 
    283     private String getContactURIFromPhone(String number) {
    284         return PhoneAccount.SCHEME_TEL + ":" + number;
    285     }
    286 
    287     Bmessage.Type getDefaultMessageType() {
    288         synchronized (mDefaultMessageType) {
    289             return mDefaultMessageType;
    290         }
    291     }
    292 
    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 
    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
    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
    321         public void exit() {
    322             mPreviousState = BluetoothProfile.STATE_DISCONNECTED;
    323         }
    324     }
    325 
    326     class Connecting extends State {
    327         @Override
    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
    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
    384         public void exit() {
    385             mPreviousState = BluetoothProfile.STATE_CONNECTING;
    386             removeMessages(MSG_CONNECTING_TIMEOUT);
    387         }
    388     }
    389 
    390     class Connected extends State {
    391         @Override
    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
    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
    482         public void exit() {
    483             mPreviousState = BluetoothProfile.STATE_CONNECTED;
    484         }
    485 
    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)
    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"
    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 
    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 
    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 
    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
    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
    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
    683         public void exit() {
    684             mPreviousState = BluetoothProfile.STATE_DISCONNECTING;
    685             removeMessages(MSG_DISCONNECTING_TIMEOUT);
    686         }
    687     }
    688 
    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