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 package com.android.bluetooth.mapclient;
18 
19 import android.Manifest;
20 import android.app.PendingIntent;
21 import android.bluetooth.BluetoothAdapter;
22 import android.bluetooth.BluetoothDevice;
23 import android.bluetooth.BluetoothProfile;
24 import android.bluetooth.BluetoothUuid;
25 import android.bluetooth.IBluetoothMapClient;
26 import android.bluetooth.SdpMasRecord;
27 import android.content.BroadcastReceiver;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.net.Uri;
32 import android.os.ParcelUuid;
33 import android.util.Log;
34 
35 import com.android.bluetooth.Utils;
36 import com.android.bluetooth.btservice.AdapterService;
37 import com.android.bluetooth.btservice.ProfileService;
38 import com.android.bluetooth.btservice.storage.DatabaseManager;
39 import com.android.internal.annotations.VisibleForTesting;
40 
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 import java.util.Iterator;
44 import java.util.List;
45 import java.util.Map;
46 import java.util.Objects;
47 import java.util.Set;
48 import java.util.concurrent.ConcurrentHashMap;
49 
50 public class MapClientService extends ProfileService {
51     private static final String TAG = "MapClientService";
52 
53     static final boolean DBG = false;
54     static final boolean VDBG = false;
55 
56     static final int MAXIMUM_CONNECTED_DEVICES = 4;
57 
58     private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
59 
60     private Map<BluetoothDevice, MceStateMachine> mMapInstanceMap = new ConcurrentHashMap<>(1);
61     private MnsService mMnsServer;
62     private BluetoothAdapter mAdapter;
63     private DatabaseManager mDatabaseManager;
64     private static MapClientService sMapClientService;
65     private MapBroadcastReceiver mMapReceiver;
66 
getMapClientService()67     public static synchronized MapClientService getMapClientService() {
68         if (sMapClientService == null) {
69             Log.w(TAG, "getMapClientService(): service is null");
70             return null;
71         }
72         if (!sMapClientService.isAvailable()) {
73             Log.w(TAG, "getMapClientService(): service is not available ");
74             return null;
75         }
76         return sMapClientService;
77     }
78 
setMapClientService(MapClientService instance)79     private static synchronized void setMapClientService(MapClientService instance) {
80         if (DBG) {
81             Log.d(TAG, "setMapClientService(): set to: " + instance);
82         }
83         sMapClientService = instance;
84     }
85 
86     @VisibleForTesting
getInstanceMap()87     Map<BluetoothDevice, MceStateMachine> getInstanceMap() {
88         return mMapInstanceMap;
89     }
90 
91     /**
92      * Connect the given Bluetooth device.
93      *
94      * @param device
95      * @return true if connection is successful, false otherwise.
96      */
connect(BluetoothDevice device)97     public synchronized boolean connect(BluetoothDevice device) {
98         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
99                 "Need BLUETOOTH_PRIVILEGED permission");
100         if (device == null) {
101             throw new IllegalArgumentException("Null device");
102         }
103         if (DBG) {
104             StringBuilder sb = new StringBuilder();
105             dump(sb);
106             Log.d(TAG, "MAP connect device: " + device
107                     + ", InstanceMap start state: " + sb.toString());
108         }
109         if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
110             Log.w(TAG, "Connection not allowed: <" + device.getAddress()
111                     + "> is CONNECTION_POLICY_FORBIDDEN");
112             return false;
113         }
114         MceStateMachine mapStateMachine = mMapInstanceMap.get(device);
115         if (mapStateMachine == null) {
116             // a map state machine instance doesn't exist yet, create a new one if we can.
117             if (mMapInstanceMap.size() < MAXIMUM_CONNECTED_DEVICES) {
118                 addDeviceToMapAndConnect(device);
119                 return true;
120             } else {
121                 // Maxed out on the number of allowed connections.
122                 // see if some of the current connections can be cleaned-up, to make room.
123                 removeUncleanAccounts();
124                 if (mMapInstanceMap.size() < MAXIMUM_CONNECTED_DEVICES) {
125                     addDeviceToMapAndConnect(device);
126                     return true;
127                 } else {
128                     Log.e(TAG, "Maxed out on the number of allowed MAP connections. "
129                             + "Connect request rejected on " + device);
130                     return false;
131                 }
132             }
133         }
134 
135         // statemachine already exists in the map.
136         int state = getConnectionState(device);
137         if (state == BluetoothProfile.STATE_CONNECTED
138                 || state == BluetoothProfile.STATE_CONNECTING) {
139             Log.w(TAG, "Received connect request while already connecting/connected.");
140             return true;
141         }
142 
143         // Statemachine exists but not in connecting or connected state! it should
144         // have been removed form the map. lets get rid of it and add a new one.
145         if (DBG) {
146             Log.d(TAG, "Statemachine exists for a device in unexpected state: " + state);
147         }
148         mMapInstanceMap.remove(device);
149         addDeviceToMapAndConnect(device);
150         if (DBG) {
151             StringBuilder sb = new StringBuilder();
152             dump(sb);
153             Log.d(TAG, "MAP connect device: " + device
154                     + ", InstanceMap end state: " + sb.toString());
155         }
156         return true;
157     }
158 
addDeviceToMapAndConnect(BluetoothDevice device)159     private synchronized void addDeviceToMapAndConnect(BluetoothDevice device) {
160         // When creating a new statemachine, its state is set to CONNECTING - which will trigger
161         // connect.
162         MceStateMachine mapStateMachine = new MceStateMachine(this, device);
163         mMapInstanceMap.put(device, mapStateMachine);
164     }
165 
disconnect(BluetoothDevice device)166     public synchronized boolean disconnect(BluetoothDevice device) {
167         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
168                 "Need BLUETOOTH_PRIVILEGED permission");
169         if (DBG) {
170             StringBuilder sb = new StringBuilder();
171             dump(sb);
172             Log.d(TAG, "MAP disconnect device: " + device
173                     + ", InstanceMap start state: " + sb.toString());
174         }
175         MceStateMachine mapStateMachine = mMapInstanceMap.get(device);
176         // a map state machine instance doesn't exist. maybe it is already gone?
177         if (mapStateMachine == null) {
178             return false;
179         }
180         int connectionState = mapStateMachine.getState();
181         if (connectionState != BluetoothProfile.STATE_CONNECTED
182                 && connectionState != BluetoothProfile.STATE_CONNECTING) {
183             return false;
184         }
185         mapStateMachine.disconnect();
186         if (DBG) {
187             StringBuilder sb = new StringBuilder();
188             dump(sb);
189             Log.d(TAG, "MAP disconnect device: " + device
190                     + ", InstanceMap start state: " + sb.toString());
191         }
192         return true;
193     }
194 
getConnectedDevices()195     public List<BluetoothDevice> getConnectedDevices() {
196         return getDevicesMatchingConnectionStates(new int[]{BluetoothAdapter.STATE_CONNECTED});
197     }
198 
getMceStateMachineForDevice(BluetoothDevice device)199     MceStateMachine getMceStateMachineForDevice(BluetoothDevice device) {
200         return mMapInstanceMap.get(device);
201     }
202 
getDevicesMatchingConnectionStates(int[] states)203     public synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
204         if (DBG) Log.d(TAG, "getDevicesMatchingConnectionStates" + Arrays.toString(states));
205         List<BluetoothDevice> deviceList = new ArrayList<>();
206         Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
207         int connectionState;
208         for (BluetoothDevice device : bondedDevices) {
209             connectionState = getConnectionState(device);
210             if (DBG) Log.d(TAG, "Device: " + device + "State: " + connectionState);
211             for (int i = 0; i < states.length; i++) {
212                 if (connectionState == states[i]) {
213                     deviceList.add(device);
214                 }
215             }
216         }
217         if (DBG) Log.d(TAG, deviceList.toString());
218         return deviceList;
219     }
220 
getConnectionState(BluetoothDevice device)221     public synchronized int getConnectionState(BluetoothDevice device) {
222         MceStateMachine mapStateMachine = mMapInstanceMap.get(device);
223         // a map state machine instance doesn't exist yet, create a new one if we can.
224         return (mapStateMachine == null) ? BluetoothProfile.STATE_DISCONNECTED
225                 : mapStateMachine.getState();
226     }
227 
228     /**
229      * Set connection policy of the profile and connects it if connectionPolicy is
230      * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is
231      * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
232      *
233      * <p> The device should already be paired.
234      * Connection policy can be one of:
235      * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
236      * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
237      * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
238      *
239      * @param device Paired bluetooth device
240      * @param connectionPolicy is the connection policy to set to for this profile
241      * @return true if connectionPolicy is set, false on error
242      */
setConnectionPolicy(BluetoothDevice device, int connectionPolicy)243     public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
244         if (VDBG) {
245             Log.v(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
246         }
247         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
248                 "Need BLUETOOTH_PRIVILEGED permission");
249 
250         if (!mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.MAP_CLIENT,
251                   connectionPolicy)) {
252             return false;
253         }
254         if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
255             connect(device);
256         } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
257             disconnect(device);
258         }
259         return true;
260     }
261 
262     /**
263      * Get the connection policy of the profile.
264      *
265      * <p> The connection policy can be any of:
266      * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
267      * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
268      * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
269      *
270      * @param device Bluetooth device
271      * @return connection policy of the device
272      * @hide
273      */
getConnectionPolicy(BluetoothDevice device)274     public int getConnectionPolicy(BluetoothDevice device) {
275         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
276                 "Need BLUETOOTH_PRIVILEGED permission");
277         return mDatabaseManager
278                 .getProfileConnectionPolicy(device, BluetoothProfile.MAP_CLIENT);
279     }
280 
sendMessage(BluetoothDevice device, Uri[] contacts, String message, PendingIntent sentIntent, PendingIntent deliveredIntent)281     public synchronized boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message,
282             PendingIntent sentIntent, PendingIntent deliveredIntent) {
283         MceStateMachine mapStateMachine = mMapInstanceMap.get(device);
284         return mapStateMachine != null
285                 && mapStateMachine.sendMapMessage(contacts, message, sentIntent, deliveredIntent);
286     }
287 
288     @Override
initBinder()289     protected IProfileServiceBinder initBinder() {
290         return new Binder(this);
291     }
292 
293     @Override
start()294     protected synchronized boolean start() {
295         Log.e(TAG, "start()");
296 
297         mDatabaseManager = Objects.requireNonNull(AdapterService.getAdapterService().getDatabase(),
298                 "DatabaseManager cannot be null when MapClientService starts");
299 
300         if (mMnsServer == null) {
301             mMnsServer = MapUtils.newMnsServiceInstance(this);
302             if (mMnsServer == null) {
303                 // this can't happen
304                 Log.w(TAG, "MnsService is *not* created!");
305                 return false;
306             }
307         }
308 
309         mAdapter = BluetoothAdapter.getDefaultAdapter();
310 
311         mMapReceiver = new MapBroadcastReceiver();
312         IntentFilter filter = new IntentFilter();
313         filter.addAction(BluetoothDevice.ACTION_SDP_RECORD);
314         filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
315         registerReceiver(mMapReceiver, filter);
316         removeUncleanAccounts();
317         setMapClientService(this);
318         return true;
319     }
320 
321     @Override
stop()322     protected synchronized boolean stop() {
323         if (DBG) {
324             Log.d(TAG, "stop()");
325         }
326 
327         if (mMapReceiver != null) {
328             unregisterReceiver(mMapReceiver);
329             mMapReceiver = null;
330         }
331         if (mMnsServer != null) {
332             mMnsServer.stop();
333         }
334         for (MceStateMachine stateMachine : mMapInstanceMap.values()) {
335             if (stateMachine.getState() == BluetoothAdapter.STATE_CONNECTED) {
336                 stateMachine.disconnect();
337             }
338             stateMachine.doQuit();
339         }
340         return true;
341     }
342 
343     @Override
cleanup()344     protected void cleanup() {
345         if (DBG) {
346             Log.d(TAG, "in Cleanup");
347         }
348         removeUncleanAccounts();
349         // TODO(b/72948646): should be moved to stop()
350         setMapClientService(null);
351     }
352 
353     /**
354      * cleanupDevice removes the associated state machine from the instance map
355      *
356      * @param device BluetoothDevice address of remote device
357      */
358     @VisibleForTesting
cleanupDevice(BluetoothDevice device)359     public void cleanupDevice(BluetoothDevice device) {
360         if (DBG) {
361             StringBuilder sb = new StringBuilder();
362             dump(sb);
363             Log.d(TAG, "Cleanup device: " + device + ", InstanceMap start state: "
364                     + sb.toString());
365         }
366         synchronized (mMapInstanceMap) {
367             MceStateMachine stateMachine = mMapInstanceMap.get(device);
368             if (stateMachine != null) {
369                 mMapInstanceMap.remove(device);
370             }
371         }
372         if (DBG) {
373             StringBuilder sb = new StringBuilder();
374             dump(sb);
375             Log.d(TAG, "Cleanup device: " + device + ", InstanceMap end state: "
376                     + sb.toString());
377         }
378     }
379 
380     @VisibleForTesting
removeUncleanAccounts()381     void removeUncleanAccounts() {
382         if (DBG) {
383             StringBuilder sb = new StringBuilder();
384             dump(sb);
385             Log.d(TAG, "removeUncleanAccounts:InstanceMap end state: "
386                     + sb.toString());
387         }
388         Iterator iterator = mMapInstanceMap.entrySet().iterator();
389         while (iterator.hasNext()) {
390             Map.Entry<BluetoothDevice, MceStateMachine> profileConnection =
391                     (Map.Entry) iterator.next();
392             if (profileConnection.getValue().getState() == BluetoothProfile.STATE_DISCONNECTED) {
393                 iterator.remove();
394             }
395         }
396         if (DBG) {
397             StringBuilder sb = new StringBuilder();
398             dump(sb);
399             Log.d(TAG, "removeUncleanAccounts:InstanceMap end state: "
400                     + sb.toString());
401         }
402     }
403 
getUnreadMessages(BluetoothDevice device)404     public synchronized boolean getUnreadMessages(BluetoothDevice device) {
405         MceStateMachine mapStateMachine = mMapInstanceMap.get(device);
406         if (mapStateMachine == null) {
407             return false;
408         }
409         return mapStateMachine.getUnreadMessages();
410     }
411 
412     /**
413      * Returns the SDP record's MapSupportedFeatures field (see Bluetooth MAP 1.4 spec, page 114).
414      * @param device The Bluetooth device to get this value for.
415      * @return the SDP record's MapSupportedFeatures field.
416      */
getSupportedFeatures(BluetoothDevice device)417     public synchronized int getSupportedFeatures(BluetoothDevice device) {
418         MceStateMachine mapStateMachine = mMapInstanceMap.get(device);
419         if (mapStateMachine == null) {
420             if (DBG) Log.d(TAG, "in getSupportedFeatures, returning 0");
421             return 0;
422         }
423         return mapStateMachine.getSupportedFeatures();
424     }
425 
setMessageStatus(BluetoothDevice device, String handle, int status)426     public synchronized boolean setMessageStatus(BluetoothDevice device, String handle, int status) {
427         MceStateMachine mapStateMachine = mMapInstanceMap.get(device);
428         if (mapStateMachine == null) {
429             return false;
430         }
431         return mapStateMachine.setMessageStatus(handle, status);
432     }
433 
434     @Override
dump(StringBuilder sb)435     public void dump(StringBuilder sb) {
436         super.dump(sb);
437         for (MceStateMachine stateMachine : mMapInstanceMap.values()) {
438             stateMachine.dump(sb);
439         }
440     }
441 
442     //Binder object: Must be static class or memory leak may occur
443 
444     /**
445      * This class implements the IClient interface - or actually it validates the
446      * preconditions for calling the actual functionality in the MapClientService, and calls it.
447      */
448     private static class Binder extends IBluetoothMapClient.Stub implements IProfileServiceBinder {
449         private MapClientService mService;
450 
Binder(MapClientService service)451         Binder(MapClientService service) {
452             if (VDBG) {
453                 Log.v(TAG, "Binder()");
454             }
455             mService = service;
456         }
457 
getService()458         private MapClientService getService() {
459             if (!Utils.checkCaller()) {
460                 Log.w(TAG, "MAP call not allowed for non-active user");
461                 return null;
462             }
463 
464             if (mService != null && mService.isAvailable()) {
465                 mService.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
466                         "Need BLUETOOTH permission");
467                 return mService;
468             }
469             return null;
470         }
471 
472         @Override
cleanup()473         public void cleanup() {
474             mService = null;
475         }
476 
477         @Override
isConnected(BluetoothDevice device)478         public boolean isConnected(BluetoothDevice device) {
479             if (VDBG) {
480                 Log.v(TAG, "isConnected()");
481             }
482             MapClientService service = getService();
483             if (service == null) {
484                 return false;
485             }
486             return service.getConnectionState(device) == BluetoothProfile.STATE_CONNECTED;
487         }
488 
489         @Override
connect(BluetoothDevice device)490         public boolean connect(BluetoothDevice device) {
491             if (VDBG) {
492                 Log.v(TAG, "connect()");
493             }
494             MapClientService service = getService();
495             if (service == null) {
496                 return false;
497             }
498             return service.connect(device);
499         }
500 
501         @Override
disconnect(BluetoothDevice device)502         public boolean disconnect(BluetoothDevice device) {
503             if (VDBG) {
504                 Log.v(TAG, "disconnect()");
505             }
506             MapClientService service = getService();
507             if (service == null) {
508                 return false;
509             }
510             return service.disconnect(device);
511         }
512 
513         @Override
getConnectedDevices()514         public List<BluetoothDevice> getConnectedDevices() {
515             if (VDBG) {
516                 Log.v(TAG, "getConnectedDevices()");
517             }
518             MapClientService service = getService();
519             if (service == null) {
520                 return new ArrayList<BluetoothDevice>(0);
521             }
522             return service.getConnectedDevices();
523         }
524 
525         @Override
getDevicesMatchingConnectionStates(int[] states)526         public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
527             if (VDBG) {
528                 Log.v(TAG, "getDevicesMatchingConnectionStates()");
529             }
530             MapClientService service = getService();
531             if (service == null) {
532                 return new ArrayList<BluetoothDevice>(0);
533             }
534             return service.getDevicesMatchingConnectionStates(states);
535         }
536 
537         @Override
getConnectionState(BluetoothDevice device)538         public int getConnectionState(BluetoothDevice device) {
539             if (VDBG) {
540                 Log.v(TAG, "getConnectionState()");
541             }
542             MapClientService service = getService();
543             if (service == null) {
544                 return BluetoothProfile.STATE_DISCONNECTED;
545             }
546             return service.getConnectionState(device);
547         }
548 
549         @Override
setConnectionPolicy(BluetoothDevice device, int connectionPolicy)550         public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
551             MapClientService service = getService();
552             if (service == null) {
553                 return false;
554             }
555             return service.setConnectionPolicy(device, connectionPolicy);
556         }
557 
558         @Override
getConnectionPolicy(BluetoothDevice device)559         public int getConnectionPolicy(BluetoothDevice device) {
560             MapClientService service = getService();
561             if (service == null) {
562                 return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
563             }
564             return service.getConnectionPolicy(device);
565         }
566 
567         @Override
sendMessage(BluetoothDevice device, Uri[] contacts, String message, PendingIntent sentIntent, PendingIntent deliveredIntent)568         public boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message,
569                 PendingIntent sentIntent, PendingIntent deliveredIntent) {
570             MapClientService service = getService();
571             if (service == null) {
572                 return false;
573             }
574             if (DBG) Log.d(TAG, "Checking Permission of sendMessage");
575             mService.enforceCallingOrSelfPermission(Manifest.permission.SEND_SMS,
576                     "Need SEND_SMS permission");
577 
578             return service.sendMessage(device, contacts, message, sentIntent, deliveredIntent);
579         }
580 
581         @Override
getUnreadMessages(BluetoothDevice device)582         public boolean getUnreadMessages(BluetoothDevice device) {
583             MapClientService service = getService();
584             if (service == null) {
585                 return false;
586             }
587             mService.enforceCallingOrSelfPermission(Manifest.permission.READ_SMS,
588                     "Need READ_SMS permission");
589             return service.getUnreadMessages(device);
590         }
591 
592         @Override
getSupportedFeatures(BluetoothDevice device)593         public int getSupportedFeatures(BluetoothDevice device) {
594             MapClientService service = getService();
595             if (service == null) {
596                 if (DBG) {
597                     Log.d(TAG,
598                             "in MapClientService getSupportedFeatures stub, returning 0");
599                 }
600                 return 0;
601             }
602             mService.enforceCallingOrSelfPermission(Manifest.permission.BLUETOOTH,
603                     "Need BLUETOOTH permission");
604             return service.getSupportedFeatures(device);
605         }
606 
607         @Override
setMessageStatus(BluetoothDevice device, String handle, int status)608         public boolean setMessageStatus(BluetoothDevice device, String handle, int status) {
609             MapClientService service = getService();
610             if (service == null) {
611                 return false;
612             }
613             mService.enforceCallingOrSelfPermission(Manifest.permission.READ_SMS,
614                     "Need READ_SMS permission");
615             return service.setMessageStatus(device, handle, status);
616         }
617     }
618 
619     private class MapBroadcastReceiver extends BroadcastReceiver {
620         @Override
onReceive(Context context, Intent intent)621         public void onReceive(Context context, Intent intent) {
622             String action = intent.getAction();
623             if (DBG) {
624                 Log.d(TAG, "onReceive: " + action);
625             }
626             if (!action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)
627                     && !action.equals(BluetoothDevice.ACTION_SDP_RECORD)) {
628                 // we don't care about this intent
629                 return;
630             }
631             BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
632             if (device == null) {
633                 Log.e(TAG, "broadcast has NO device param!");
634                 return;
635             }
636             if (DBG) {
637                 Log.d(TAG, "broadcast has device: (" + device.getAddress() + ", "
638                         + device.getName() + ")");
639             }
640             MceStateMachine stateMachine = mMapInstanceMap.get(device);
641             if (stateMachine == null) {
642                 Log.e(TAG, "No Statemachine found for the device from broadcast");
643                 return;
644             }
645 
646             if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
647                 if (stateMachine.getState() == BluetoothProfile.STATE_CONNECTED) {
648                     stateMachine.disconnect();
649                 }
650             }
651 
652             if (action.equals(BluetoothDevice.ACTION_SDP_RECORD)) {
653                 ParcelUuid uuid = intent.getParcelableExtra(BluetoothDevice.EXTRA_UUID);
654                 if (DBG) {
655                     Log.d(TAG, "UUID of SDP: " + uuid);
656                 }
657 
658                 if (uuid.equals(BluetoothUuid.MAS)) {
659                     // Check if we have a valid SDP record.
660                     SdpMasRecord masRecord =
661                             intent.getParcelableExtra(BluetoothDevice.EXTRA_SDP_RECORD);
662                     if (DBG) {
663                         Log.d(TAG, "SDP = " + masRecord);
664                     }
665                     int status = intent.getIntExtra(BluetoothDevice.EXTRA_SDP_SEARCH_STATUS, -1);
666                     if (masRecord == null) {
667                         Log.w(TAG, "SDP search ended with no MAS record. Status: " + status);
668                         return;
669                     }
670                     stateMachine.obtainMessage(MceStateMachine.MSG_MAS_SDP_DONE,
671                             masRecord).sendToTarget();
672                 }
673             }
674         }
675     }
676 }
677