• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 /**
18  * Bluetooth MAP MCE StateMachine
19  *         (Disconnected)
20  *             |    ^
21  *     CONNECT |    | DISCONNECTED
22  *             V    |
23  *    (Connecting) (Disconnecting)
24  *             |    ^
25  *   CONNECTED |    | DISCONNECT
26  *             V    |
27  *           (Connected)
28  *
29  * Valid Transitions: State + Event -> Transition:
30  *
31  * Disconnected + CONNECT -> Connecting
32  * Connecting + CONNECTED -> Connected
33  * Connecting + TIMEOUT -> Disconnecting
34  * Connecting + DISCONNECT/CONNECT -> Defer Message
35  * Connected + DISCONNECT -> Disconnecting
36  * Connected + CONNECT -> Disconnecting + Defer Message
37  * Disconnecting + DISCONNECTED -> (Safe) Disconnected
38  * Disconnecting + TIMEOUT -> (Force) Disconnected
39  * Disconnecting + DISCONNECT/CONNECT : Defer Message
40  */
41 package com.android.bluetooth.mapclient;
42 
43 import android.app.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 
MceStateMachine(MapClientService service)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 
doQuit()146     public void doQuit() {
147         quitNow();
148     }
149 
getDevice()150     synchronized BluetoothDevice getDevice() {
151         return mDevice;
152     }
153 
onConnectionStateChanged(int prevState, int state)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 
getState()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 
connect(BluetoothDevice device)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 
disconnect(BluetoothDevice device)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 
sendMapMessage(Uri[] contacts, String message, PendingIntent sentIntent, PendingIntent deliveredIntent)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 
getMessage(String handle)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 
getUnreadMessages()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 
getContactURIFromPhone(String number)258     private String getContactURIFromPhone(String number) {
259         return PhoneAccount.SCHEME_TEL + ":" + number;
260     }
261 
getDefaultMessageType()262     Bmessage.Type getDefaultMessageType() {
263         synchronized (mDefaultMessageType) {
264             return mDefaultMessageType;
265         }
266     }
267 
setDefaultMessageType(SdpMasRecord sdpMasRecord)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
enter()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
processMessage(Message message)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
exit()307         public void exit() {
308             mPreviousState = BluetoothProfile.STATE_DISCONNECTED;
309         }
310     }
311 
312     class Connecting extends State {
313         @Override
enter()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
processMessage(Message message)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
exit()368         public void exit() {
369             mPreviousState = BluetoothProfile.STATE_CONNECTING;
370             removeMessages(MSG_CONNECTING_TIMEOUT);
371         }
372     }
373 
374     class Connected extends State {
375         @Override
enter()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
processMessage(Message message)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
exit()455         public void exit() {
456             mPreviousState = BluetoothProfile.STATE_CONNECTED;
457         }
458 
processNotification(Message msg)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 
processMessageListing(RequestGetMessagesListing request)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 
processInboundMessage(RequestGetMessage request)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 
notifySentMessageStatus(String handle, EventReport.Type status)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
enter()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
processMessage(Message message)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
exit()603         public void exit() {
604             mPreviousState = BluetoothProfile.STATE_DISCONNECTING;
605             removeMessages(MSG_DISCONNECTING_TIMEOUT);
606         }
607     }
608 
receiveEvent(EventReport ev)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
onReceive(Context context, Intent intent)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