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