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