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