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