• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Log;
28 import android.util.SparseBooleanArray;
29 
30 import com.android.internal.util.Preconditions;
31 
32 import java.util.Arrays;
33 import java.util.List;
34 import java.util.concurrent.TimeUnit;
35 import java.util.concurrent.locks.Condition;
36 import java.util.concurrent.locks.ReentrantLock;
37 
38 public class CarBluetoothUserService extends ICarBluetoothUserService.Stub {
39     private static final String TAG = "CarBluetoothUserService";
40     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
41     private final PerUserCarService mService;
42     private final BluetoothAdapter mBluetoothAdapter;
43 
44     // Profiles we support
45     private static final List<Integer> sProfilesToConnect = Arrays.asList(
46             BluetoothProfile.HEADSET_CLIENT,
47             BluetoothProfile.PBAP_CLIENT,
48             BluetoothProfile.A2DP_SINK,
49             BluetoothProfile.MAP_CLIENT,
50             BluetoothProfile.PAN
51     );
52 
53     // Profile Proxies Objects to pair with above list. Access to these proxy objects will all be
54     // guarded by this classes implicit monitor lock.
55     private BluetoothA2dpSink mBluetoothA2dpSink = null;
56     private BluetoothHeadsetClient mBluetoothHeadsetClient = null;
57     private BluetoothPbapClient mBluetoothPbapClient = null;
58     private BluetoothMapClient mBluetoothMapClient = null;
59     private BluetoothPan mBluetoothPan = null;
60 
61     // Concurrency variables for waitForProxyConnections. Used so we can block with a timeout while
62     // setting up or closing down proxy connections.
63     private final ReentrantLock mBluetoothProxyStatusLock;
64     private final Condition mConditionAllProxiesConnected;
65     private final Condition mConditionAllProxiesDisconnected;
66     private SparseBooleanArray mBluetoothProfileStatus;
67     private int mConnectedProfiles;
68     private static final int PROXY_OPERATION_TIMEOUT_MS = 8000;
69 
70     /**
71      * Create a CarBluetoothUserService instance.
72      *
73      * @param serice - A reference to a PerUserCarService, so we can use its context to receive
74      *                 updates as a particular user.
75      */
CarBluetoothUserService(PerUserCarService service)76     public CarBluetoothUserService(PerUserCarService service) {
77         mService = service;
78         mConnectedProfiles = 0;
79         mBluetoothProfileStatus = new SparseBooleanArray();
80         for (int profile : sProfilesToConnect) {
81             mBluetoothProfileStatus.put(profile, false);
82         }
83         mBluetoothProxyStatusLock = new ReentrantLock();
84         mConditionAllProxiesConnected = mBluetoothProxyStatusLock.newCondition();
85         mConditionAllProxiesDisconnected = mBluetoothProxyStatusLock.newCondition();
86         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
87     }
88 
89     /**
90      * Setup connections to the profile proxy objects that talk to the Bluetooth profile services.
91      *
92      * Connection requests are asynchronous in nature and return through the ProfileServiceListener
93      * below. Since callers expect that the proxies are initialized by the time we call this, we
94      * will block (with a timeout) until all proxies are connected.
95      */
96     @Override
setupBluetoothConnectionProxies()97     public void setupBluetoothConnectionProxies() {
98         logd("Initiate connections to profile proxies");
99         Preconditions.checkNotNull(mBluetoothAdapter, "Bluetooth adapter cannot be null");
100         mBluetoothProxyStatusLock.lock();
101         try {
102 
103             // Connect all the profiles that are unconnected, keep count so we can wait below
104             for (int profile : sProfilesToConnect) {
105                 if (mBluetoothProfileStatus.get(profile, false)) {
106                     logd(Utils.getProfileName(profile) + " is already connected");
107                     continue;
108                 }
109                 logd("Connecting " + Utils.getProfileName(profile));
110                 mBluetoothAdapter.getProfileProxy(mService.getApplicationContext(),
111                         mProfileListener, profile);
112             }
113 
114             // Wait for all the profiles to connect with a generous timeout just in case
115             while (mConnectedProfiles != sProfilesToConnect.size()) {
116                 if (!mConditionAllProxiesConnected.await(
117                         PROXY_OPERATION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
118                     Log.e(TAG, "Timeout while waiting for all proxies to connect. Connected only "
119                             + mConnectedProfiles + "/" + sProfilesToConnect.size());
120                     break;
121                 }
122             }
123         } catch (InterruptedException e) {
124             Log.w(TAG, "setupBluetoothConnectionProxies: interrupted", e);
125         } finally {
126             mBluetoothProxyStatusLock.unlock();
127         }
128     }
129 
130     /**
131      * Close connections to the profile proxy objects
132      *
133      * Proxy disconnection requests are asynchronous in nature and return through the
134      * ProfileServiceListener below. This method will block (with a timeout) until all proxies have
135      * disconnected.
136      */
137     @Override
closeBluetoothConnectionProxies()138     public synchronized void closeBluetoothConnectionProxies() {
139         logd("Tear down profile proxy connections");
140         Preconditions.checkNotNull(mBluetoothAdapter, "Bluetooth adapter cannot be null");
141         mBluetoothProxyStatusLock.lock();
142         try {
143             if (mBluetoothA2dpSink != null) {
144                 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP_SINK, mBluetoothA2dpSink);
145             }
146             if (mBluetoothHeadsetClient != null) {
147                 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET_CLIENT,
148                         mBluetoothHeadsetClient);
149             }
150             if (mBluetoothPbapClient != null) {
151                 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.PBAP_CLIENT,
152                         mBluetoothPbapClient);
153             }
154             if (mBluetoothMapClient != null) {
155                 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.MAP_CLIENT,
156                         mBluetoothMapClient);
157             }
158             if (mBluetoothPan != null) {
159                 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.PAN, mBluetoothPan);
160             }
161 
162             while (mConnectedProfiles != 0) {
163                 if (!mConditionAllProxiesDisconnected.await(
164                         PROXY_OPERATION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
165                     Log.e(TAG, "Timeout while waiting for all proxies to disconnect. There are "
166                             + mConnectedProfiles + "/" + sProfilesToConnect.size() + "still "
167                             + "connected");
168                     break;
169                 }
170             }
171 
172         } catch (InterruptedException e) {
173             Log.w(TAG, "closeBluetoothConnectionProxies: interrupted", e);
174         } finally {
175             mBluetoothProxyStatusLock.unlock();
176         }
177     }
178 
179     /**
180      * Listen for and collect Bluetooth profile proxy connections and disconnections.
181      */
182     private BluetoothProfile.ServiceListener mProfileListener =
183             new BluetoothProfile.ServiceListener() {
184         public void onServiceConnected(int profile, BluetoothProfile proxy) {
185             logd("OnServiceConnected profile: " + Utils.getProfileName(profile));
186 
187             // Grab the profile proxy object and update the status book keeping in one step so the
188             // book keeping and proxy objects never disagree
189             synchronized (this) {
190                 switch (profile) {
191                     case BluetoothProfile.A2DP_SINK:
192                         mBluetoothA2dpSink = (BluetoothA2dpSink) proxy;
193                         break;
194                     case BluetoothProfile.HEADSET_CLIENT:
195                         mBluetoothHeadsetClient = (BluetoothHeadsetClient) proxy;
196                         break;
197                     case BluetoothProfile.PBAP_CLIENT:
198                         mBluetoothPbapClient = (BluetoothPbapClient) proxy;
199                         break;
200                     case BluetoothProfile.MAP_CLIENT:
201                         mBluetoothMapClient = (BluetoothMapClient) proxy;
202                         break;
203                     case BluetoothProfile.PAN:
204                         mBluetoothPan = (BluetoothPan) proxy;
205                         break;
206                     default:
207                         logd("Unhandled profile connected: " + Utils.getProfileName(profile));
208                         break;
209                 }
210 
211                 mBluetoothProxyStatusLock.lock();
212                 try {
213                     if (!mBluetoothProfileStatus.get(profile, false)) {
214                         mBluetoothProfileStatus.put(profile, true);
215                         mConnectedProfiles++;
216                         if (mConnectedProfiles == sProfilesToConnect.size()) {
217                             logd("All profiles have connected");
218                             mConditionAllProxiesConnected.signal();
219                         }
220                     }
221                 } finally {
222                     mBluetoothProxyStatusLock.unlock();
223                 }
224             }
225         }
226 
227         public void onServiceDisconnected(int profile) {
228             logd("onServiceDisconnected profile: " + Utils.getProfileName(profile));
229 
230             // Null the profile proxy object and update the status book keeping in one step so the
231             // book keeping and proxy objects never disagree
232             synchronized (this) {
233                 switch (profile) {
234                     case BluetoothProfile.A2DP_SINK:
235                         mBluetoothA2dpSink = null;
236                         break;
237                     case BluetoothProfile.HEADSET_CLIENT:
238                         mBluetoothHeadsetClient = null;
239                         break;
240                     case BluetoothProfile.PBAP_CLIENT:
241                         mBluetoothPbapClient = null;
242                         break;
243                     case BluetoothProfile.MAP_CLIENT:
244                         mBluetoothMapClient = null;
245                         break;
246                     case BluetoothProfile.PAN:
247                         mBluetoothPan = null;
248                         break;
249                     default:
250                         logd("Unhandled profile disconnected: " + Utils.getProfileName(profile));
251                         break;
252                 }
253 
254                 mBluetoothProxyStatusLock.lock();
255                 try {
256                     if (mBluetoothProfileStatus.get(profile, false)) {
257                         mBluetoothProfileStatus.put(profile, false);
258                         mConnectedProfiles--;
259                         if (mConnectedProfiles == 0) {
260                             logd("All profiles have disconnected");
261                             mConditionAllProxiesDisconnected.signal();
262                         }
263                     }
264                 } finally {
265                     mBluetoothProxyStatusLock.unlock();
266                 }
267             }
268         }
269     };
270 
271     /**
272      * Check if a proxy is available for the given profile to talk to the Profile's bluetooth
273      * service.
274      * @param profile - Bluetooth profile to check for
275      * @return - true if proxy available, false if not.
276      */
277     @Override
isBluetoothConnectionProxyAvailable(int profile)278     public boolean isBluetoothConnectionProxyAvailable(int profile) {
279         boolean proxyConnected = false;
280         mBluetoothProxyStatusLock.lock();
281         try {
282             proxyConnected = mBluetoothProfileStatus.get(profile, false);
283         } finally {
284             mBluetoothProxyStatusLock.unlock();
285         }
286         if (!proxyConnected) {
287             setupBluetoothConnectionProxies();
288             return isBluetoothConnectionProxyAvailable(profile);
289         }
290         return proxyConnected;
291     }
292 
293     @Override
bluetoothConnectToProfile(int profile, BluetoothDevice device)294     public boolean bluetoothConnectToProfile(int profile, BluetoothDevice device) {
295         if (device == null) {
296             Log.e(TAG, "Cannot connect to profile on null device");
297             return false;
298         }
299         logd("Trying to connect to " + device.getName() + " (" + device.getAddress() + ") Profile: "
300                 + Utils.getProfileName(profile));
301         synchronized (this) {
302             if (!isBluetoothConnectionProxyAvailable(profile)) {
303                 Log.e(TAG, "Cannot connect to Profile. Proxy Unavailable");
304                 return false;
305             }
306             switch (profile) {
307                 case BluetoothProfile.A2DP_SINK:
308                     return mBluetoothA2dpSink.connect(device);
309                 case BluetoothProfile.HEADSET_CLIENT:
310                     return mBluetoothHeadsetClient.connect(device);
311                 case BluetoothProfile.MAP_CLIENT:
312                     return mBluetoothMapClient.connect(device);
313                 case BluetoothProfile.PBAP_CLIENT:
314                     return mBluetoothPbapClient.connect(device);
315                 case BluetoothProfile.PAN:
316                     return mBluetoothPan.connect(device);
317                 default:
318                     Log.w(TAG, "Unknown Profile: " + Utils.getProfileName(profile));
319                     break;
320             }
321         }
322         return false;
323     }
324 
325     @Override
bluetoothDisconnectFromProfile(int profile, BluetoothDevice device)326     public boolean bluetoothDisconnectFromProfile(int profile, BluetoothDevice device) {
327         if (device == null) {
328             Log.e(TAG, "Cannot disconnect from profile on null device");
329             return false;
330         }
331         logd("Trying to disconnect from " + device.getName() + " (" + device.getAddress()
332                 + ") Profile: " + Utils.getProfileName(profile));
333         synchronized (this) {
334             if (!isBluetoothConnectionProxyAvailable(profile)) {
335                 Log.e(TAG, "Cannot disconnect from profile. Proxy Unavailable");
336                 return false;
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                     Log.w(TAG, "Unknown Profile: " + Utils.getProfileName(profile));
351                     break;
352             }
353         }
354         return false;
355     }
356 
357     /**
358      * Get the priority of the given Bluetooth profile for the given remote device
359      * @param profile - Bluetooth profile
360      * @param device - remote Bluetooth device
361      */
362     @Override
getProfilePriority(int profile, BluetoothDevice device)363     public int getProfilePriority(int profile, BluetoothDevice device) {
364         if (device == null) {
365             Log.e(TAG, "Cannot get " + Utils.getProfileName(profile)
366                     + " profile priority on null device");
367             return BluetoothProfile.PRIORITY_UNDEFINED;
368         }
369         int priority;
370         synchronized (this) {
371             if (!isBluetoothConnectionProxyAvailable(profile)) {
372                 Log.e(TAG, "Cannot get " + Utils.getProfileName(profile)
373                         + " profile priority. Proxy Unavailable");
374                 return BluetoothProfile.PRIORITY_UNDEFINED;
375             }
376             switch (profile) {
377                 case BluetoothProfile.A2DP_SINK:
378                     priority = mBluetoothA2dpSink.getPriority(device);
379                     break;
380                 case BluetoothProfile.HEADSET_CLIENT:
381                     priority = mBluetoothHeadsetClient.getPriority(device);
382                     break;
383                 case BluetoothProfile.MAP_CLIENT:
384                     priority = mBluetoothMapClient.getPriority(device);
385                     break;
386                 case BluetoothProfile.PBAP_CLIENT:
387                     priority = mBluetoothPbapClient.getPriority(device);
388                     break;
389                 default:
390                     Log.w(TAG, "Unknown Profile: " + Utils.getProfileName(profile));
391                     priority = BluetoothProfile.PRIORITY_UNDEFINED;
392                     break;
393             }
394         }
395         logd(Utils.getProfileName(profile) + " priority for " + device.getName() + " ("
396                 + device.getAddress() + ") = " + priority);
397         return priority;
398     }
399 
400     /**
401      * Set the priority of the given Bluetooth profile for the given remote device
402      * @param profile - Bluetooth profile
403      * @param device - remote Bluetooth device
404      * @param priority - priority to set
405      */
406     @Override
setProfilePriority(int profile, BluetoothDevice device, int priority)407     public void setProfilePriority(int profile, BluetoothDevice device, int priority) {
408         if (device == null) {
409             Log.e(TAG, "Cannot set " + Utils.getProfileName(profile)
410                     + " profile priority on null device");
411             return;
412         }
413         logd("Setting " + Utils.getProfileName(profile) + " priority for " + device.getName() + " ("
414                 + device.getAddress() + ") to " + priority);
415         synchronized (this) {
416             if (!isBluetoothConnectionProxyAvailable(profile)) {
417                 Log.e(TAG, "Cannot set " + Utils.getProfileName(profile)
418                         + " profile priority. Proxy Unavailable");
419                 return;
420             }
421             switch (profile) {
422                 case BluetoothProfile.A2DP_SINK:
423                     mBluetoothA2dpSink.setPriority(device, priority);
424                     break;
425                 case BluetoothProfile.HEADSET_CLIENT:
426                     mBluetoothHeadsetClient.setPriority(device, priority);
427                     break;
428                 case BluetoothProfile.MAP_CLIENT:
429                     mBluetoothMapClient.setPriority(device, priority);
430                     break;
431                 case BluetoothProfile.PBAP_CLIENT:
432                     mBluetoothPbapClient.setPriority(device, priority);
433                     break;
434                 default:
435                     Log.w(TAG, "Unknown Profile: " + Utils.getProfileName(profile));
436                     break;
437             }
438         }
439     }
440 
441     /**
442      * Log to debug if debug output is enabled
443      */
logd(String msg)444     private void logd(String msg) {
445         if (DBG) {
446             Log.d(TAG, msg);
447         }
448     }
449 }
450