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