1 /*
2  * Copyright (C) 2017 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 package com.android.car;
17 
18 import android.bluetooth.BluetoothA2dpSink;
19 import android.bluetooth.BluetoothAdapter;
20 import android.bluetooth.BluetoothDevice;
21 import android.bluetooth.BluetoothHeadsetClient;
22 import android.bluetooth.BluetoothMapClient;
23 import android.bluetooth.BluetoothPan;
24 import android.bluetooth.BluetoothPbapClient;
25 import android.bluetooth.BluetoothProfile;
26 import android.car.ICarBluetoothUserService;
27 import android.util.IndentingPrintWriter;
28 import android.util.Log;
29 import android.util.Slog;
30 import android.util.SparseBooleanArray;
31 
32 import com.android.car.bluetooth.FastPairProvider;
33 
34 import java.util.Arrays;
35 import java.util.List;
36 import java.util.Objects;
37 import java.util.concurrent.TimeUnit;
38 import java.util.concurrent.locks.Condition;
39 import java.util.concurrent.locks.ReentrantLock;
40 
41 public class CarBluetoothUserService extends ICarBluetoothUserService.Stub {
42 
43     private static final String TAG = CarLog.tagFor(CarBluetoothUserService.class);
44 
45     private static final int PROXY_OPERATION_TIMEOUT_MS = 8_000;
46 
47     // Profiles we support
48     private static final List<Integer> sProfilesToConnect = Arrays.asList(
49             BluetoothProfile.HEADSET_CLIENT,
50             BluetoothProfile.PBAP_CLIENT,
51             BluetoothProfile.A2DP_SINK,
52             BluetoothProfile.MAP_CLIENT,
53             BluetoothProfile.PAN
54     );
55 
56     private final PerUserCarService mService;
57     private final BluetoothAdapter mBluetoothAdapter;
58 
59     // Profile Proxies Objects to pair with above list. Access to these proxy objects will all be
60     // guarded by the below mBluetoothProxyLock
61     private BluetoothA2dpSink mBluetoothA2dpSink;
62     private BluetoothHeadsetClient mBluetoothHeadsetClient;
63     private BluetoothPbapClient mBluetoothPbapClient;
64     private BluetoothMapClient mBluetoothMapClient;
65     private BluetoothPan mBluetoothPan;
66 
67     // Concurrency variables for waitForProxies. Used so we can best effort block with a timeout
68     // while waiting for services to be bound to the proxy objects.
69     private final ReentrantLock mBluetoothProxyLock;
70     private final Condition mConditionAllProxiesConnected;
71     private final FastPairProvider mFastPairProvider;
72     private SparseBooleanArray mBluetoothProfileStatus;
73     private int mConnectedProfiles;
74 
75     /**
76      * Create a CarBluetoothUserService instance.
77      *
78      * @param service - A reference to a PerUserCarService, so we can use its context to receive
79      *                 updates as a particular user.
80      */
CarBluetoothUserService(PerUserCarService service)81     public CarBluetoothUserService(PerUserCarService service) {
82         mService = service;
83         mConnectedProfiles = 0;
84         mBluetoothProfileStatus = new SparseBooleanArray();
85         for (int profile : sProfilesToConnect) {
86             mBluetoothProfileStatus.put(profile, false);
87         }
88         mBluetoothProxyLock = new ReentrantLock();
89         mConditionAllProxiesConnected = mBluetoothProxyLock.newCondition();
90         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
91         Objects.requireNonNull(mBluetoothAdapter, "Bluetooth adapter cannot be null");
92         mFastPairProvider = new FastPairProvider(service);
93     }
94 
95     /**
96      * Setup connections to the profile proxy objects that talk to the Bluetooth profile services.
97      *
98      * Proxy references are held by the Bluetooth Framework on our behalf. We will be notified each
99      * time the underlying service connects for each proxy we create. Notifications stop when we
100      * close the proxy. As such, each time this is called we clean up any existing proxies before
101      * creating new ones.
102      */
103     @Override
setupBluetoothConnectionProxies()104     public void setupBluetoothConnectionProxies() {
105         logd("Initiate connections to profile proxies");
106 
107         // Clear existing proxy objects
108         closeBluetoothConnectionProxies();
109 
110         // Create proxy for each supported profile. Objects arrive later in the profile listener.
111         // Operations on the proxies expect them to be connected. Functions below should call
112         // waitForProxies() to best effort wait for them to be up if Bluetooth is enabled.
113         for (int profile : sProfilesToConnect) {
114             logd("Creating proxy for %s", Utils.getProfileName(profile));
115             mBluetoothAdapter.getProfileProxy(mService.getApplicationContext(),
116                     mProfileListener, profile);
117         }
118         mFastPairProvider.start();
119     }
120 
121     /**
122      * Close connections to the profile proxy objects
123      */
124     @Override
closeBluetoothConnectionProxies()125     public void closeBluetoothConnectionProxies() {
126         logd("Clean up profile proxy objects");
127         mBluetoothProxyLock.lock();
128         try {
129             mBluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP_SINK, mBluetoothA2dpSink);
130             mBluetoothA2dpSink = null;
131             mBluetoothProfileStatus.put(BluetoothProfile.A2DP_SINK, false);
132 
133             mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET_CLIENT,
134                     mBluetoothHeadsetClient);
135             mBluetoothHeadsetClient = null;
136             mBluetoothProfileStatus.put(BluetoothProfile.HEADSET_CLIENT, false);
137 
138             mBluetoothAdapter.closeProfileProxy(BluetoothProfile.PBAP_CLIENT, mBluetoothPbapClient);
139             mBluetoothPbapClient = null;
140             mBluetoothProfileStatus.put(BluetoothProfile.PBAP_CLIENT, false);
141 
142             mBluetoothAdapter.closeProfileProxy(BluetoothProfile.MAP_CLIENT, mBluetoothMapClient);
143             mBluetoothMapClient = null;
144             mBluetoothProfileStatus.put(BluetoothProfile.MAP_CLIENT, false);
145 
146             mBluetoothAdapter.closeProfileProxy(BluetoothProfile.PAN, mBluetoothPan);
147             mBluetoothPan = null;
148             mBluetoothProfileStatus.put(BluetoothProfile.PAN, false);
149 
150             mConnectedProfiles = 0;
151         } finally {
152             mBluetoothProxyLock.unlock();
153         }
154         mFastPairProvider.stop();
155     }
156 
157     /**
158      * Listen for and collect Bluetooth profile proxy connections and disconnections.
159      */
160     private BluetoothProfile.ServiceListener mProfileListener =
161             new BluetoothProfile.ServiceListener() {
162         public void onServiceConnected(int profile, BluetoothProfile proxy) {
163             logd("onServiceConnected profile: %s", Utils.getProfileName(profile));
164 
165             // Grab the profile proxy object and update the status book keeping in one step so the
166             // book keeping and proxy objects never disagree
167             mBluetoothProxyLock.lock();
168             try {
169                 switch (profile) {
170                     case BluetoothProfile.A2DP_SINK:
171                         mBluetoothA2dpSink = (BluetoothA2dpSink) proxy;
172                         break;
173                     case BluetoothProfile.HEADSET_CLIENT:
174                         mBluetoothHeadsetClient = (BluetoothHeadsetClient) proxy;
175                         break;
176                     case BluetoothProfile.PBAP_CLIENT:
177                         mBluetoothPbapClient = (BluetoothPbapClient) proxy;
178                         break;
179                     case BluetoothProfile.MAP_CLIENT:
180                         mBluetoothMapClient = (BluetoothMapClient) proxy;
181                         break;
182                     case BluetoothProfile.PAN:
183                         mBluetoothPan = (BluetoothPan) proxy;
184                         break;
185                     default:
186                         logd("Unhandled profile connected: %s", Utils.getProfileName(profile));
187                         break;
188                 }
189 
190                 if (!mBluetoothProfileStatus.get(profile, false)) {
191                     mBluetoothProfileStatus.put(profile, true);
192                     mConnectedProfiles++;
193                     if (mConnectedProfiles == sProfilesToConnect.size()) {
194                         logd("All profiles have connected");
195                         mConditionAllProxiesConnected.signal();
196                     }
197                 } else {
198                     Slog.w(TAG, "Received duplicate service connection event for: "
199                             + Utils.getProfileName(profile));
200                 }
201             } finally {
202                 mBluetoothProxyLock.unlock();
203             }
204         }
205 
206         public void onServiceDisconnected(int profile) {
207             logd("onServiceDisconnected profile: %s", Utils.getProfileName(profile));
208             mBluetoothProxyLock.lock();
209             try {
210                 if (mBluetoothProfileStatus.get(profile, false)) {
211                     mBluetoothProfileStatus.put(profile, false);
212                     mConnectedProfiles--;
213                 } else {
214                     Slog.w(TAG, "Received duplicate service disconnection event for: "
215                             + Utils.getProfileName(profile));
216                 }
217             } finally {
218                 mBluetoothProxyLock.unlock();
219             }
220         }
221     };
222 
223     /**
224      * Check if a proxy is available for the given profile to talk to the Profile's bluetooth
225      * service.
226      *
227      * @param profile - Bluetooth profile to check for
228      * @return - true if proxy available, false if not.
229      */
230     @Override
isBluetoothConnectionProxyAvailable(int profile)231     public boolean isBluetoothConnectionProxyAvailable(int profile) {
232         if (!mBluetoothAdapter.isEnabled()) return false;
233         boolean proxyConnected = false;
234         mBluetoothProxyLock.lock();
235         try {
236             proxyConnected = mBluetoothProfileStatus.get(profile, false);
237         } finally {
238             mBluetoothProxyLock.unlock();
239         }
240         return proxyConnected;
241     }
242 
243     /**
244      * Wait for the proxy objects to be up for all profiles, with a timeout.
245      *
246      * @param timeout Amount of time in milliseconds to wait for giving up on the wait operation
247      * @return True if the condition was satisfied within the timeout, False otherwise
248      */
waitForProxies(int timeout )249     private boolean waitForProxies(int timeout /* ms */) {
250         logd("waitForProxies()");
251         // If bluetooth isn't on then the operation waiting on proxies was never meant to actually
252         // work regardless if Bluetooth comes on within the timeout period or not. Return false.
253         if (!mBluetoothAdapter.isEnabled()) return false;
254         try {
255             while (mConnectedProfiles != sProfilesToConnect.size()) {
256                 if (!mConditionAllProxiesConnected.await(
257                         timeout, TimeUnit.MILLISECONDS)) {
258                     Slog.e(TAG, "Timeout while waiting for proxies, Connected: "
259                             + mConnectedProfiles + "/" + sProfilesToConnect.size());
260                     return false;
261                 }
262             }
263         } catch (InterruptedException e) {
264             Slog.w(TAG, "waitForProxies: interrupted", e);
265             Thread.currentThread().interrupt();
266             return false;
267         }
268         return true;
269     }
270 
271     /**
272      * Connect a given remote device on a specific Bluetooth profile
273      *
274      * @param profile BluetoothProfile.* based profile ID
275      * @param device The device you wish to connect
276      */
277     @Override
bluetoothConnectToProfile(int profile, BluetoothDevice device)278     public boolean bluetoothConnectToProfile(int profile, BluetoothDevice device) {
279         if (device == null) {
280             Slog.e(TAG, "Cannot connect to profile on null device");
281             return false;
282         }
283         logd("Trying to connect to %s (%s) Profile: %s", device.getName(), device.getAddress(),
284                 Utils.getProfileName(profile));
285         mBluetoothProxyLock.lock();
286         try {
287             if (!isBluetoothConnectionProxyAvailable(profile)) {
288                 if (!waitForProxies(PROXY_OPERATION_TIMEOUT_MS)
289                         && !isBluetoothConnectionProxyAvailable(profile)) {
290                     Slog.e(TAG, "Cannot connect to Profile. Proxy Unavailable");
291                     return false;
292                 }
293             }
294             switch (profile) {
295                 case BluetoothProfile.A2DP_SINK:
296                     return mBluetoothA2dpSink.connect(device);
297                 case BluetoothProfile.HEADSET_CLIENT:
298                     return mBluetoothHeadsetClient.connect(device);
299                 case BluetoothProfile.MAP_CLIENT:
300                     return mBluetoothMapClient.connect(device);
301                 case BluetoothProfile.PBAP_CLIENT:
302                     return mBluetoothPbapClient.connect(device);
303                 case BluetoothProfile.PAN:
304                     return mBluetoothPan.connect(device);
305                 default:
306                     Slog.w(TAG, "Unknown Profile: " + Utils.getProfileName(profile));
307                     break;
308             }
309         } finally {
310             mBluetoothProxyLock.unlock();
311         }
312         return false;
313     }
314 
315     /**
316      * Disonnect a given remote device from a specific Bluetooth profile
317      *
318      * @param profile BluetoothProfile.* based profile ID
319      * @param device The device you wish to disconnect
320      */
321     @Override
bluetoothDisconnectFromProfile(int profile, BluetoothDevice device)322     public boolean bluetoothDisconnectFromProfile(int profile, BluetoothDevice device) {
323         if (device == null) {
324             Slog.e(TAG, "Cannot disconnect from profile on null device");
325             return false;
326         }
327         logd("Trying to disconnect from %s (%s) Profile: %s", device.getName(), device.getAddress(),
328                 Utils.getProfileName(profile));
329         mBluetoothProxyLock.lock();
330         try {
331             if (!isBluetoothConnectionProxyAvailable(profile)) {
332                 if (!waitForProxies(PROXY_OPERATION_TIMEOUT_MS)
333                         && !isBluetoothConnectionProxyAvailable(profile)) {
334                     Slog.e(TAG, "Cannot disconnect from Profile. Proxy Unavailable");
335                     return false;
336                 }
337             }
338             switch (profile) {
339                 case BluetoothProfile.A2DP_SINK:
340                     return mBluetoothA2dpSink.disconnect(device);
341                 case BluetoothProfile.HEADSET_CLIENT:
342                     return mBluetoothHeadsetClient.disconnect(device);
343                 case BluetoothProfile.MAP_CLIENT:
344                     return mBluetoothMapClient.disconnect(device);
345                 case BluetoothProfile.PBAP_CLIENT:
346                     return mBluetoothPbapClient.disconnect(device);
347                 case BluetoothProfile.PAN:
348                     return mBluetoothPan.disconnect(device);
349                 default:
350                     Slog.w(TAG, "Unknown Profile: " + Utils.getProfileName(profile));
351                     break;
352             }
353         } finally {
354             mBluetoothProxyLock.unlock();
355         }
356         return false;
357     }
358 
359     /**
360      * Get the priority of the given Bluetooth profile for the given remote device
361      *
362      * @param profile - Bluetooth profile
363      * @param device - remote Bluetooth device
364      */
365     @Override
getProfilePriority(int profile, BluetoothDevice device)366     public int getProfilePriority(int profile, BluetoothDevice device) {
367         if (device == null) {
368             Slog.e(TAG, "Cannot get " + Utils.getProfileName(profile)
369                     + " profile priority on null device");
370             return BluetoothProfile.PRIORITY_UNDEFINED;
371         }
372         int priority;
373         mBluetoothProxyLock.lock();
374         try {
375             if (!isBluetoothConnectionProxyAvailable(profile)) {
376                 if (!waitForProxies(PROXY_OPERATION_TIMEOUT_MS)
377                         && !isBluetoothConnectionProxyAvailable(profile)) {
378                     Slog.e(TAG, "Cannot get " + Utils.getProfileName(profile)
379                             + " profile priority. Proxy Unavailable");
380                     return BluetoothProfile.PRIORITY_UNDEFINED;
381                 }
382             }
383             switch (profile) {
384                 case BluetoothProfile.A2DP_SINK:
385                     priority = mBluetoothA2dpSink.getPriority(device);
386                     break;
387                 case BluetoothProfile.HEADSET_CLIENT:
388                     priority = mBluetoothHeadsetClient.getPriority(device);
389                     break;
390                 case BluetoothProfile.MAP_CLIENT:
391                     priority = mBluetoothMapClient.getPriority(device);
392                     break;
393                 case BluetoothProfile.PBAP_CLIENT:
394                     priority = mBluetoothPbapClient.getPriority(device);
395                     break;
396                 default:
397                     Slog.w(TAG, "Unknown Profile: " + Utils.getProfileName(profile));
398                     priority = BluetoothProfile.PRIORITY_UNDEFINED;
399                     break;
400             }
401         } finally {
402             mBluetoothProxyLock.unlock();
403         }
404         logd("%s priority for %s (%s) = %d", Utils.getProfileName(profile), device.getName(),
405                 device.getAddress(), priority);
406         return priority;
407     }
408 
409     /**
410      * Set the priority of the given Bluetooth profile for the given remote device
411      *
412      * @param profile - Bluetooth profile
413      * @param device - remote Bluetooth device
414      * @param priority - priority to set
415      */
416     @Override
setProfilePriority(int profile, BluetoothDevice device, int priority)417     public void setProfilePriority(int profile, BluetoothDevice device, int priority) {
418         if (device == null) {
419             Slog.e(TAG, "Cannot set " + Utils.getProfileName(profile)
420                     + " profile priority on null device");
421             return;
422         }
423         logd("Setting %s priority for %s (%s) to %d", Utils.getProfileName(profile),
424                 device.getName(), device.getAddress(), priority);
425         mBluetoothProxyLock.lock();
426         try {
427             if (!isBluetoothConnectionProxyAvailable(profile)) {
428                 if (!waitForProxies(PROXY_OPERATION_TIMEOUT_MS)
429                         && !isBluetoothConnectionProxyAvailable(profile)) {
430                     Slog.e(TAG, "Cannot set " + Utils.getProfileName(profile)
431                             + " profile priority. Proxy Unavailable");
432                     return;
433                 }
434             }
435             switch (profile) {
436                 case BluetoothProfile.A2DP_SINK:
437                     mBluetoothA2dpSink.setPriority(device, priority);
438                     break;
439                 case BluetoothProfile.HEADSET_CLIENT:
440                     mBluetoothHeadsetClient.setPriority(device, priority);
441                     break;
442                 case BluetoothProfile.MAP_CLIENT:
443                     mBluetoothMapClient.setPriority(device, priority);
444                     break;
445                 case BluetoothProfile.PBAP_CLIENT:
446                     mBluetoothPbapClient.setPriority(device, priority);
447                     break;
448                 default:
449                     Slog.w(TAG, "Unknown Profile: " + Utils.getProfileName(profile));
450                     break;
451             }
452         } finally {
453             mBluetoothProxyLock.unlock();
454         }
455     }
456 
dump(IndentingPrintWriter pw)457     void dump(IndentingPrintWriter pw) {
458         pw.printf("Supported profiles: %s\n", sProfilesToConnect);
459         pw.printf("Number of connected profiles: %d\n", mConnectedProfiles);
460         pw.printf("Profiles status: %s\n", mBluetoothProfileStatus);
461         pw.printf("Proxy operation timeout: %d ms\n", PROXY_OPERATION_TIMEOUT_MS);
462         pw.printf("BluetoothAdapter: %s\n", mBluetoothAdapter);
463         pw.printf("BluetoothA2dpSink: %s\n", mBluetoothA2dpSink);
464         pw.printf("BluetoothHeadsetClient: %s\n", mBluetoothHeadsetClient);
465         pw.printf("BluetoothPbapClient: %s\n", mBluetoothPbapClient);
466         pw.printf("BluetoothMapClient: %s\n", mBluetoothMapClient);
467         pw.printf("BluetoothPan: %s\n", mBluetoothPan);
468         mFastPairProvider.dump(pw);
469     }
470 
471     /**
472      * Log to debug if debug output is enabled
473      */
logd(String message, Object... args)474     private void logd(String message, Object... args) {
475         if (Log.isLoggable(TAG, Log.DEBUG)) {
476             Slog.d(TAG, String.format(message, args));
477         }
478     }
479 }
480