1 /*
2  * Copyright (C) 2021 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.bluetooth;
17 
18 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
19 
20 import android.bluetooth.BluetoothA2dpSink;
21 import android.bluetooth.BluetoothAdapter;
22 import android.bluetooth.BluetoothDevice;
23 import android.bluetooth.BluetoothHeadsetClient;
24 import android.bluetooth.BluetoothManager;
25 import android.bluetooth.BluetoothProfile;
26 import android.car.ICarBluetoothUserService;
27 import android.car.builtin.bluetooth.BluetoothHeadsetClientHelper;
28 import android.car.builtin.util.Slogf;
29 import android.telecom.PhoneAccountHandle;
30 import android.telecom.TelecomManager;
31 import android.util.Log;
32 import android.util.SparseBooleanArray;
33 
34 import com.android.car.CarLog;
35 import com.android.car.CarPerUserServiceImpl;
36 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
37 import com.android.car.internal.util.IndentingPrintWriter;
38 
39 import java.util.Arrays;
40 import java.util.List;
41 import java.util.Objects;
42 import java.util.concurrent.TimeUnit;
43 import java.util.concurrent.locks.Condition;
44 import java.util.concurrent.locks.ReentrantLock;
45 
46 /**
47  * Manages Bluetooth for user
48  */
49 public class CarBluetoothUserService extends ICarBluetoothUserService.Stub {
50 
51     private static final String TAG = CarLog.tagFor(CarBluetoothUserService.class);
52     private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG);
53 
54     private static final int PROXY_OPERATION_TIMEOUT_MS = 8_000;
55 
56     // Profiles we support
57     private static final List<Integer> sProfilesToConnect = Arrays.asList(
58             BluetoothProfile.HEADSET_CLIENT,
59             BluetoothProfile.A2DP_SINK
60     );
61 
62     private final CarPerUserServiceImpl mService;
63     private final BluetoothAdapter mBluetoothAdapter;
64     private final TelecomManager mTelecomManager;
65 
66     // Profile Proxies Objects to pair with above list. Access to these proxy objects will all be
67     // guarded by the below mBluetoothProxyLock
68     private BluetoothA2dpSink mBluetoothA2dpSink;
69     private BluetoothHeadsetClient mBluetoothHeadsetClient;
70 
71     // Concurrency variables for waitForProxies. Used so we can best effort block with a timeout
72     // while waiting for services to be bound to the proxy objects.
73     private final ReentrantLock mBluetoothProxyLock;
74     private final Condition mConditionAllProxiesConnected;
75     private final FastPairProvider mFastPairProvider;
76     private SparseBooleanArray mBluetoothProfileStatus;
77     private int mConnectedProfiles;
78 
79     /**
80      * Create a CarBluetoothUserService instance.
81      *
82      * @param service - A reference to a CarPerUserService, so we can use its context to receive
83      *                 updates as a particular user.
84      */
CarBluetoothUserService(CarPerUserServiceImpl service)85     public CarBluetoothUserService(CarPerUserServiceImpl service) {
86         mService = service;
87         mConnectedProfiles = 0;
88         mBluetoothProfileStatus = new SparseBooleanArray();
89         for (int profile : sProfilesToConnect) {
90             mBluetoothProfileStatus.put(profile, false);
91         }
92         mBluetoothProxyLock = new ReentrantLock();
93         mConditionAllProxiesConnected = mBluetoothProxyLock.newCondition();
94         mBluetoothAdapter = mService.getApplicationContext()
95                 .getSystemService(BluetoothManager.class).getAdapter();
96         Objects.requireNonNull(mBluetoothAdapter, "Bluetooth adapter cannot be null");
97         mTelecomManager = mService.getApplicationContext().getSystemService(TelecomManager.class);
98         mFastPairProvider = new FastPairProvider(service);
99     }
100 
101     /**
102      * Setup connections to the profile proxy objects that talk to the Bluetooth profile services.
103      *
104      * Proxy references are held by the Bluetooth Framework on our behalf. We will be notified each
105      * time the underlying service connects for each proxy we create. Notifications stop when we
106      * close the proxy. As such, each time this is called we clean up any existing proxies before
107      * creating new ones.
108      */
109     @Override
setupBluetoothConnectionProxies()110     public void setupBluetoothConnectionProxies() {
111         if (DBG) {
112             Slogf.d(TAG, "Initiate connections to profile proxies");
113         }
114 
115         // Clear existing proxy objects
116         closeBluetoothConnectionProxies();
117 
118         // Create proxy for each supported profile. Objects arrive later in the profile listener.
119         // Operations on the proxies expect them to be connected. Functions below should call
120         // waitForProxies() to best effort wait for them to be up if Bluetooth is enabled.
121         for (int profile : sProfilesToConnect) {
122             if (DBG) {
123                 Slogf.d(TAG, "Creating proxy for %s", BluetoothUtils.getProfileName(profile));
124             }
125             mBluetoothAdapter.getProfileProxy(mService.getApplicationContext(),
126                     mProfileListener, profile);
127         }
128         mFastPairProvider.start();
129     }
130 
131     /**
132      * Close connections to the profile proxy objects
133      */
134     @Override
closeBluetoothConnectionProxies()135     public void closeBluetoothConnectionProxies() {
136         if (DBG) {
137             Slogf.d(TAG, "Clean up profile proxy objects");
138         }
139         mBluetoothProxyLock.lock();
140         try {
141             mBluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP_SINK, mBluetoothA2dpSink);
142             mBluetoothA2dpSink = null;
143             mBluetoothProfileStatus.put(BluetoothProfile.A2DP_SINK, false);
144 
145             mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET_CLIENT,
146                     mBluetoothHeadsetClient);
147             mBluetoothHeadsetClient = null;
148             mBluetoothProfileStatus.put(BluetoothProfile.HEADSET_CLIENT, 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             if (DBG) {
164                 Slogf.d(TAG, "onServiceConnected profile: %s",
165                         BluetoothUtils.getProfileName(profile));
166             }
167 
168             // Grab the profile proxy object and update the status book keeping in one step so the
169             // book keeping and proxy objects never disagree
170             mBluetoothProxyLock.lock();
171             try {
172                 switch (profile) {
173                     case BluetoothProfile.A2DP_SINK:
174                         mBluetoothA2dpSink = (BluetoothA2dpSink) proxy;
175                         break;
176                     case BluetoothProfile.HEADSET_CLIENT:
177                         mBluetoothHeadsetClient = (BluetoothHeadsetClient) proxy;
178                         break;
179                     default:
180                         if (DBG) {
181                             Slogf.d(TAG, "Unsupported profile connected: %s",
182                                     BluetoothUtils.getProfileName(profile));
183                         }
184                         break;
185                 }
186 
187                 if (!mBluetoothProfileStatus.get(profile, false)) {
188                     mBluetoothProfileStatus.put(profile, true);
189                     mConnectedProfiles++;
190                     if (mConnectedProfiles == sProfilesToConnect.size()) {
191                         if (DBG) {
192                             Slogf.d(TAG, "All profiles have connected");
193                         }
194                         mConditionAllProxiesConnected.signal();
195                     }
196                 } else {
197                     Slogf.w(TAG, "Received duplicate service connection event for: %s",
198                             BluetoothUtils.getProfileName(profile));
199                 }
200             } finally {
201                 mBluetoothProxyLock.unlock();
202             }
203         }
204 
205         public void onServiceDisconnected(int profile) {
206             if (DBG) {
207                 Slogf.d(TAG, "onServiceDisconnected profile: %s",
208                         BluetoothUtils.getProfileName(profile));
209             }
210             mBluetoothProxyLock.lock();
211             try {
212                 if (mBluetoothProfileStatus.get(profile, false)) {
213                     mBluetoothProfileStatus.put(profile, false);
214                     mConnectedProfiles--;
215                 } else {
216                     Slogf.w(TAG, "Received duplicate service disconnection event for: %s",
217                             BluetoothUtils.getProfileName(profile));
218                 }
219             } finally {
220                 mBluetoothProxyLock.unlock();
221             }
222         }
223     };
224 
225     /**
226      * Check if a proxy is available for the given profile to talk to the Profile's bluetooth
227      * service.
228      *
229      * @param profile - Bluetooth profile to check for
230      * @return - true if proxy available, false if not.
231      */
232     @Override
isBluetoothConnectionProxyAvailable(int profile)233     public boolean isBluetoothConnectionProxyAvailable(int profile) {
234         if (!mBluetoothAdapter.isEnabled()) return false;
235         boolean proxyConnected = false;
236         mBluetoothProxyLock.lock();
237         try {
238             proxyConnected = mBluetoothProfileStatus.get(profile, false);
239         } finally {
240             mBluetoothProxyLock.unlock();
241         }
242         return proxyConnected;
243     }
244 
245     /**
246      * Wait for the proxy objects to be up for all profiles, with a timeout.
247      *
248      * @param timeout Amount of time in milliseconds to wait for giving up on the wait operation
249      * @return True if the condition was satisfied within the timeout, False otherwise
250      */
waitForProxies(int timeout )251     private boolean waitForProxies(int timeout /* ms */) {
252         if (DBG) {
253             Slogf.d(TAG, "waitForProxies()");
254         }
255         // If bluetooth isn't on then the operation waiting on proxies was never meant to actually
256         // work regardless if Bluetooth comes on within the timeout period or not. Return false.
257         if (!mBluetoothAdapter.isEnabled()) return false;
258         try {
259             while (mConnectedProfiles != sProfilesToConnect.size()) {
260                 if (!mConditionAllProxiesConnected.await(
261                         timeout, TimeUnit.MILLISECONDS)) {
262                     Slogf.e(TAG, "Timeout while waiting for proxies, Connected: %d/%d",
263                             mConnectedProfiles, sProfilesToConnect.size());
264                     return false;
265                 }
266             }
267         } catch (InterruptedException e) {
268             Slogf.w(TAG, "waitForProxies: interrupted", e);
269             Thread.currentThread().interrupt();
270             return false;
271         }
272         return true;
273     }
274 
275     /**
276      * Get the connection policy of the given Bluetooth profile for the given remote device
277      *
278      * @param profile - Bluetooth profile
279      * @param device - remote Bluetooth device
280      */
281     @Override
getConnectionPolicy(int profile, BluetoothDevice device)282     public int getConnectionPolicy(int profile, BluetoothDevice device) {
283         if (device == null) {
284             Slogf.e(TAG, "Cannot get %s profile connection policy on null device",
285                     BluetoothUtils.getProfileName(profile));
286             return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
287         }
288         int policy;
289         mBluetoothProxyLock.lock();
290         try {
291             if (!isBluetoothConnectionProxyAvailable(profile)) {
292                 if (!waitForProxies(PROXY_OPERATION_TIMEOUT_MS)
293                         && !isBluetoothConnectionProxyAvailable(profile)) {
294                     Slogf.e(TAG, "Cannot get %s profile connection policy. Proxy Unavailable",
295                             BluetoothUtils.getProfileName(profile));
296                     return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
297                 }
298             }
299             switch (profile) {
300                 case BluetoothProfile.A2DP_SINK:
301                     policy = mBluetoothA2dpSink.getConnectionPolicy(device);
302                     break;
303                 default:
304                     Slogf.w(TAG, "Unsupported Profile: %s", BluetoothUtils.getProfileName(profile));
305                     policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
306                     break;
307             }
308         } finally {
309             mBluetoothProxyLock.unlock();
310         }
311         if (DBG) {
312             Slogf.d(TAG, "%s connection policy for %s (%s) = %d",
313                     BluetoothUtils.getProfileName(profile),
314                     device.getName(), device.getAddress(), policy);
315         }
316         return policy;
317     }
318 
319     /**
320      * Set the connection policy of the given Bluetooth profile for the given remote device
321      *
322      * @param profile - Bluetooth profile
323      * @param device - remote Bluetooth device
324      * @param policy - connection policy to set
325      */
326     @Override
setConnectionPolicy(int profile, BluetoothDevice device, int policy)327     public void setConnectionPolicy(int profile, BluetoothDevice device, int policy) {
328         if (device == null) {
329             Slogf.e(TAG, "Cannot set %s profile connection policy on null device",
330                     BluetoothUtils.getProfileName(profile));
331             return;
332         }
333         if (DBG) {
334             Slogf.d(TAG, "Setting %s connection policy for %s (%s) to %d",
335                     BluetoothUtils.getProfileName(profile), device.getName(), device.getAddress(),
336                     policy);
337         }
338         mBluetoothProxyLock.lock();
339         try {
340             if (!isBluetoothConnectionProxyAvailable(profile)) {
341                 if (!waitForProxies(PROXY_OPERATION_TIMEOUT_MS)
342                         && !isBluetoothConnectionProxyAvailable(profile)) {
343                     Slogf.e(TAG, "Cannot set %s profile connection policy. Proxy Unavailable",
344                             BluetoothUtils.getProfileName(profile));
345                     return;
346                 }
347             }
348             switch (profile) {
349                 case BluetoothProfile.A2DP_SINK:
350                     mBluetoothA2dpSink.setConnectionPolicy(device, policy);
351                     break;
352                 default:
353                     Slogf.w(TAG, "Unsupported Profile: %s", BluetoothUtils.getProfileName(profile));
354                     break;
355             }
356         } finally {
357             mBluetoothProxyLock.unlock();
358         }
359     }
360 
361     /**
362      * Triggers Bluetooth to start a BVRA session.
363      */
startBluetoothVoiceRecognition()364     public boolean startBluetoothVoiceRecognition() {
365         mBluetoothProxyLock.lock();
366         try {
367             if (mBluetoothHeadsetClient == null) {
368                 Slogf.e(TAG, "HFP BVRA, no headsetclient proxy found.");
369                 return false;
370             }
371             List<BluetoothDevice> devices = BluetoothHeadsetClientHelper.getConnectedBvraDevices(
372                     mBluetoothHeadsetClient);
373             if (devices != null && !devices.isEmpty()) {
374                 // Until a UI has been agreed upon that allows a user to select from multiple
375                 // devices, a BVRA device will be chosen as follows:
376                 //   1. Use the device corresponding to the default phone account.
377                 //   2. If that device doesn't support BVRA or if there is no default account, use
378                 //      the first device that supports BVRA.
379                 BluetoothDevice bvraDevice = devices.get(0);
380 
381                 // {@link TelecomManager#getUserSelectedOutgoingPhoneAccount} returns the
382                 // user-chosen default for making outgoing phone calls. This default is set when
383                 // {@link HfpClientConnectionService} creates a phone account for a device, via
384                 // {@link HfpClientDeviceBlock}.
385                 PhoneAccountHandle defaultPhone =
386                         mTelecomManager.getUserSelectedOutgoingPhoneAccount();
387                 if (defaultPhone != null) {
388                     // When {@link HfpClientConnectionService#createAccount} creates a {@link
389                     // PhoneAccountHandle}, it sets the ID to the device's {@code BD_ADDR}.
390                     String defaultPhoneBdAddr = defaultPhone.getId();
391                     if (defaultPhoneBdAddr != null) {
392                         for (int i = 0; i < devices.size(); i++) {
393                             BluetoothDevice d = devices.get(i);
394                             if (defaultPhoneBdAddr.equals(d.getAddress())) {
395                                 bvraDevice = d;
396                                 break;
397                             }
398                         }
399                     }
400                 }
401 
402                 if (BluetoothHeadsetClientHelper.startVoiceRecognition(
403                         mBluetoothHeadsetClient, bvraDevice)) {
404                     if (DBG) {
405                         Slogf.d(TAG, "HFP BVRA started for %s", bvraDevice.getAddress());
406                     }
407                     return true;
408                 } else {
409                     Slogf.w(TAG, "Unable to start HFP BVRA for %s", bvraDevice.getAddress());
410                 }
411             } else {
412                 Slogf.w(TAG, "No devices supporting BVRA found.");
413             }
414         } finally {
415             mBluetoothProxyLock.unlock();
416         }
417         return false;
418     }
419 
420     /** Dump for debugging */
421     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter pw)422     public void dump(IndentingPrintWriter pw) {
423         pw.printf("Supported profiles: %s\n", sProfilesToConnect);
424         pw.printf("Number of connected profiles: %d\n", mConnectedProfiles);
425         pw.printf("Profiles status: %s\n", mBluetoothProfileStatus);
426         pw.printf("Proxy operation timeout: %d ms\n", PROXY_OPERATION_TIMEOUT_MS);
427         pw.printf("BluetoothAdapter: %s\n", mBluetoothAdapter);
428         pw.printf("BluetoothA2dpSink: %s\n", mBluetoothA2dpSink);
429         pw.printf("BluetoothHeadsetClient: %s\n", mBluetoothHeadsetClient);
430         mFastPairProvider.dump(pw);
431     }
432 }
433