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.app.ActivityManager;
19 import android.bluetooth.BluetoothDevice;
20 import android.bluetooth.BluetoothProfile;
21 import android.car.ICarBluetooth;
22 import android.car.ICarBluetoothUserService;
23 import android.car.IPerUserCarService;
24 import android.content.Context;
25 import android.content.pm.PackageManager;
26 import android.os.IBinder;
27 import android.os.RemoteException;
28 import android.os.UserHandle;
29 import android.util.Log;
30 import android.util.SparseArray;
31 
32 import com.android.internal.annotations.GuardedBy;
33 
34 import java.io.PrintWriter;
35 import java.util.ArrayList;
36 import java.util.Arrays;
37 import java.util.List;
38 
39 /**
40  * CarBluetoothService - Maintains the current user's Bluetooth devices and profile connections.
41  *
42  * For each user, creates:
43  *   1) Set of {@link BluetoothProfileDeviceManager} objects, responsible for maintaining a list of
44  *      the user's known devices. These provide an interface to auto-connect devices for the given
45  *      profile.
46  *   2) A {@link BluetoothProfileInhibitManager} object that will maintain a set of inhibited
47  *      profiles for each device, keeping a device from connecting on those profiles. This provides
48  *      an interface to request and release inhibits.
49  *   3) A {@link BluetoothDeviceConnectionPolicy} object, representing a default implementation of
50  *      a policy based method of determining and requesting times to auto-connect devices. This
51  *      default is controllable through a resource overlay if one chooses to implement their own.
52  *
53  * Provides an interface for other programs to request auto connections.
54  */
55 public class CarBluetoothService extends ICarBluetooth.Stub implements CarServiceBase {
56     private static final String TAG = "CarBluetoothService";
57     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
58     private final Context mContext;
59 
60     // The list of profiles we wish to manage
61     private static final List<Integer> sManagedProfiles = Arrays.asList(
62             BluetoothProfile.HEADSET_CLIENT,
63             BluetoothProfile.PBAP_CLIENT,
64             BluetoothProfile.A2DP_SINK,
65             BluetoothProfile.MAP_CLIENT,
66             BluetoothProfile.PAN
67     );
68 
69     // Each time PerUserCarService connects we need to get new Bluetooth profile proxies and refresh
70     // all our internal objects to use them. When it disconnects we're to assume our proxies are
71     // invalid. This lock protects all our internal objects.
72     private final Object mPerUserLock = new Object();
73 
74     // Set of Bluetooth Profile Device Managers, own the priority connection lists, updated on user
75     // switch
76     private final SparseArray<BluetoothProfileDeviceManager> mProfileDeviceManagers =
77             new SparseArray<>();
78 
79     // Profile-Inhibit Manager that will temporarily inhibit connections on profiles, per user
80     @GuardedBy("mPerUserLock")
81     private BluetoothProfileInhibitManager mInhibitManager = null;
82 
83     // Default Bluetooth device connection policy, per user, enabled with an overlay
84     private final boolean mUseDefaultPolicy;
85     @GuardedBy("mPerUserLock")
86     private BluetoothDeviceConnectionPolicy mBluetoothDeviceConnectionPolicy = null;
87 
88     // Listen for user switch events from the PerUserCarService
89     @GuardedBy("mPerUserLock")
90     private int mUserId;
91     @GuardedBy("mPerUserLock")
92     private IPerUserCarService mPerUserCarService;
93     @GuardedBy("mPerUserLock")
94     private ICarBluetoothUserService mCarBluetoothUserService;
95     private final PerUserCarServiceHelper mUserServiceHelper;
96     private final PerUserCarServiceHelper.ServiceCallback mUserServiceCallback =
97             new PerUserCarServiceHelper.ServiceCallback() {
98         @Override
99         public void onServiceConnected(IPerUserCarService perUserCarService) {
100             logd("Connected to PerUserCarService");
101             synchronized (mPerUserLock) {
102                 // Explicitly clear out existing per-user objects since we can't rely on the
103                 // onServiceDisconnected and onPreUnbind calls to always be called before this
104                 destroyUserLocked();
105 
106                 mPerUserCarService = perUserCarService;
107 
108                 // Create new objects with our new set of profile proxies
109                 initializeUserLocked();
110             }
111         }
112 
113         @Override
114         public void onPreUnbind() {
115             logd("Before Unbinding from PerUserCarService");
116             synchronized (mPerUserLock) {
117                 destroyUserLocked();
118             }
119         }
120 
121         @Override
122         public void onServiceDisconnected() {
123             logd("Disconnected from PerUserCarService");
124             synchronized (mPerUserLock) {
125                 destroyUserLocked();
126             }
127         }
128     };
129 
130     /**
131      * Create an instance of CarBluetoothService
132      *
133      * @param context - A Context object representing the context you want this service to run
134      * @param userSwitchService - An instance of PerUserCarServiceHelper that we can bind a listener
135      *                            to in order to receive user switch events
136      */
CarBluetoothService(Context context, PerUserCarServiceHelper userSwitchService)137     public CarBluetoothService(Context context, PerUserCarServiceHelper userSwitchService) {
138         mUserId = UserHandle.USER_NULL;
139         mContext = context;
140         mUserServiceHelper = userSwitchService;
141         mUseDefaultPolicy = mContext.getResources().getBoolean(
142                 R.bool.useDefaultBluetoothConnectionPolicy);
143     }
144 
145     /**
146      * Complete all necessary initialization keeping this service from being running.
147      *
148      * Wait for the user service helper to report a user before initializing a user.
149      */
150     @Override
init()151     public void init() {
152         logd("init()");
153         mUserServiceHelper.registerServiceCallback(mUserServiceCallback);
154     }
155 
156     /**
157      * Release all resources required to run this service and stop running.
158      *
159      * Clean up the user context once we've detached from the user service helper, if any.
160      */
161     @Override
release()162     public void release() {
163         logd("release()");
164         mUserServiceHelper.unregisterServiceCallback(mUserServiceCallback);
165         synchronized (mPerUserLock) {
166             destroyUserLocked();
167         }
168     }
169 
170     /**
171      * Returns the list of profiles that the Autoconnection policy attempts to connect on
172      *
173      * @return The list of managed Bluetooth profiles
174      */
getManagedProfiles()175     public static List<Integer> getManagedProfiles() {
176         return sManagedProfiles;
177     }
178 
179     /**
180      * Initialize the user context using the current active user.
181      *
182      * Only call this following a known user switch once we've connected to the user service helper.
183      */
initializeUserLocked()184     private void initializeUserLocked() {
185         logd("Initializing new user");
186         mUserId = ActivityManager.getCurrentUser();
187         createBluetoothUserServiceLocked();
188         createBluetoothProfileDeviceManagersLocked();
189         createBluetoothProfileInhibitManagerLocked();
190 
191         // Determine if we need to begin the default policy
192         mBluetoothDeviceConnectionPolicy = null;
193         if (mUseDefaultPolicy) {
194             createBluetoothDeviceConnectionPolicyLocked();
195         }
196         logd("Switched to user " + mUserId);
197     }
198 
199     /**
200      * Destroy the current user context, defined by the set of profile proxies, profile device
201      * managers, inhibit manager and the policy.
202      */
destroyUserLocked()203     private void destroyUserLocked() {
204         logd("Destroying user " + mUserId);
205         destroyBluetoothDeviceConnectionPolicyLocked();
206         destroyBluetoothProfileInhibitManagerLocked();
207         destroyBluetoothProfileDeviceManagersLocked();
208         destroyBluetoothUserServiceLocked();
209         mPerUserCarService = null;
210         mUserId = UserHandle.USER_NULL;
211     }
212 
213     /**
214      * Sets the Per User Car Bluetooth Service (ICarBluetoothService) from the PerUserCarService
215      * which acts as a top level Service running in the current user context.
216      * Also sets up the connection proxy objects required to communicate with the Bluetooth
217      * Profile Services.
218      */
createBluetoothUserServiceLocked()219     private void createBluetoothUserServiceLocked() {
220         if (mPerUserCarService != null) {
221             try {
222                 mCarBluetoothUserService = mPerUserCarService.getBluetoothUserService();
223                 mCarBluetoothUserService.setupBluetoothConnectionProxies();
224             } catch (RemoteException e) {
225                 Log.e(TAG, "Remote Service Exception on ServiceConnection Callback: "
226                         + e.getMessage());
227             } catch (java.lang.NullPointerException e) {
228                 Log.e(TAG, "Initialization Failed: " + e.getMessage());
229             }
230         } else {
231             logd("PerUserCarService not connected. Cannot get bluetooth user proxy objects");
232         }
233     }
234 
235     /**
236      * Close out the Per User Car Bluetooth profile proxy connections and destroys the Car Bluetooth
237      * User Service object.
238      */
destroyBluetoothUserServiceLocked()239     private void destroyBluetoothUserServiceLocked() {
240         if (mCarBluetoothUserService == null) return;
241         try {
242             mCarBluetoothUserService.closeBluetoothConnectionProxies();
243         } catch (RemoteException e) {
244             Log.e(TAG, "Remote Service Exception on ServiceConnection Callback: "
245                     + e.getMessage());
246         }
247         mCarBluetoothUserService = null;
248     }
249 
250     /**
251      * Clears out Profile Device Managers and re-creates them for the current user.
252      */
createBluetoothProfileDeviceManagersLocked()253     private void createBluetoothProfileDeviceManagersLocked() {
254         if (mUserId == UserHandle.USER_NULL) {
255             logd("No foreground user, cannot create profile device managers");
256             return;
257         }
258         for (int profileId : sManagedProfiles) {
259             BluetoothProfileDeviceManager deviceManager = mProfileDeviceManagers.get(profileId);
260             if (deviceManager != null) {
261                 deviceManager.stop();
262                 mProfileDeviceManagers.remove(profileId);
263                 logd("Existing device manager removed for profile "
264                         + Utils.getProfileName(profileId));
265             }
266 
267             deviceManager = BluetoothProfileDeviceManager.create(mContext, mUserId,
268                     mCarBluetoothUserService, profileId);
269             if (deviceManager == null) {
270                 logd("Failed to create profile device manager for "
271                         + Utils.getProfileName(profileId));
272                 continue;
273             }
274             mProfileDeviceManagers.put(profileId, deviceManager);
275             logd("Created profile device manager for " + Utils.getProfileName(profileId));
276         }
277 
278         for (int i = 0; i < mProfileDeviceManagers.size(); i++) {
279             int key = mProfileDeviceManagers.keyAt(i);
280             BluetoothProfileDeviceManager deviceManager =
281                     (BluetoothProfileDeviceManager) mProfileDeviceManagers.get(key);
282             deviceManager.start();
283         }
284     }
285 
286     /**
287      * Stops and clears the entire set of Profile Device Managers.
288      */
destroyBluetoothProfileDeviceManagersLocked()289     private void destroyBluetoothProfileDeviceManagersLocked() {
290         for (int i = 0; i < mProfileDeviceManagers.size(); i++) {
291             int key = mProfileDeviceManagers.keyAt(i);
292             BluetoothProfileDeviceManager deviceManager =
293                     (BluetoothProfileDeviceManager) mProfileDeviceManagers.get(key);
294             deviceManager.stop();
295         }
296         mProfileDeviceManagers.clear();
297     }
298 
299     /**
300      * Creates an instance of a BluetoothProfileInhibitManager under the current user
301      */
createBluetoothProfileInhibitManagerLocked()302     private void createBluetoothProfileInhibitManagerLocked() {
303         logd("Creating inhibit manager");
304         if (mUserId == UserHandle.USER_NULL) {
305             logd("No foreground user, cannot create profile inhibit manager");
306             return;
307         }
308         mInhibitManager = new BluetoothProfileInhibitManager(mContext, mUserId,
309                 mCarBluetoothUserService);
310         mInhibitManager.start();
311     }
312 
313     /**
314      * Destroys the current instance of a BluetoothProfileInhibitManager, if one exists
315      */
destroyBluetoothProfileInhibitManagerLocked()316     private void destroyBluetoothProfileInhibitManagerLocked() {
317         logd("Destroying inhibit manager");
318         if (mInhibitManager == null) return;
319         mInhibitManager.stop();
320         mInhibitManager = null;
321     }
322 
323     /**
324      * Creates an instance of a BluetoothDeviceConnectionPolicy under the current user
325      */
createBluetoothDeviceConnectionPolicyLocked()326     private void createBluetoothDeviceConnectionPolicyLocked() {
327         logd("Creating device connection policy");
328         if (mUserId == UserHandle.USER_NULL) {
329             logd("No foreground user, cannot create device connection policy");
330             return;
331         }
332         mBluetoothDeviceConnectionPolicy = BluetoothDeviceConnectionPolicy.create(mContext,
333                 mUserId, this);
334         if (mBluetoothDeviceConnectionPolicy == null) {
335             logd("Failed to create default Bluetooth device connection policy.");
336             return;
337         }
338         mBluetoothDeviceConnectionPolicy.init();
339     }
340 
341     /**
342      * Destroys the current instance of a BluetoothDeviceConnectionPolicy, if one exists
343      */
destroyBluetoothDeviceConnectionPolicyLocked()344     private void destroyBluetoothDeviceConnectionPolicyLocked() {
345         logd("Destroying device connection policy");
346         if (mBluetoothDeviceConnectionPolicy != null) {
347             mBluetoothDeviceConnectionPolicy.release();
348             mBluetoothDeviceConnectionPolicy = null;
349         }
350     }
351 
352      /**
353      * Determine if we are using the default device connection policy or not
354      *
355      * @return true if the default policy is active, false otherwise
356      */
isUsingDefaultConnectionPolicy()357     public boolean isUsingDefaultConnectionPolicy() {
358         synchronized (mPerUserLock) {
359             return mBluetoothDeviceConnectionPolicy != null;
360         }
361     }
362 
363    /**
364      * Initiate automatated connecting of devices based on the prioritized device lists for each
365      * profile.
366      */
367     @Override
connectDevices()368     public void connectDevices() {
369         enforceBluetoothAdminPermission();
370         logd("Connect devices for each profile");
371         synchronized (mPerUserLock) {
372             for (int i = 0; i < mProfileDeviceManagers.size(); i++) {
373                 int key = mProfileDeviceManagers.keyAt(i);
374                 BluetoothProfileDeviceManager deviceManager =
375                         (BluetoothProfileDeviceManager) mProfileDeviceManagers.get(key);
376                 deviceManager.beginAutoConnecting();
377             }
378         }
379     }
380 
381     /**
382      * Get the Auto Connect priority for a paired Bluetooth Device.
383      *
384      * @param profile  - BluetoothProfile to get priority list for
385      * @return A list of BluetoothDevice objects, ordered by highest priority first
386      */
getProfileDevicePriorityList(int profile)387     public List<BluetoothDevice> getProfileDevicePriorityList(int profile) {
388         enforceBluetoothAdminPermission();
389         synchronized (mPerUserLock) {
390             BluetoothProfileDeviceManager deviceManager =
391                     (BluetoothProfileDeviceManager) mProfileDeviceManagers.get(profile);
392             if (deviceManager != null) {
393                 return deviceManager.getDeviceListSnapshot();
394             }
395         }
396         return new ArrayList<BluetoothDevice>();
397     }
398 
399     /**
400      * Get the Auto Connect priority for a paired Bluetooth Device.
401      *
402      * @param profile  - BluetoothProfile to get priority for
403      * @param device   - Device to get priority for
404      * @return integer priority value, or -1 if no priority available.
405      */
getDeviceConnectionPriority(int profile, BluetoothDevice device)406     public int getDeviceConnectionPriority(int profile, BluetoothDevice device) {
407         enforceBluetoothAdminPermission();
408         synchronized (mPerUserLock) {
409             BluetoothProfileDeviceManager deviceManager =
410                     (BluetoothProfileDeviceManager) mProfileDeviceManagers.get(profile);
411             if (deviceManager != null) {
412                 return deviceManager.getDeviceConnectionPriority(device);
413             }
414         }
415         return -1;
416     }
417 
418     /**
419      * Set the Auto Connect priority for a paired Bluetooth Device.
420      *
421      * @param profile  - BluetoothProfile to set priority for
422      * @param device   - Device to set priority (Tag)
423      * @param priority - What priority level to set to
424      */
setDeviceConnectionPriority(int profile, BluetoothDevice device, int priority)425     public void setDeviceConnectionPriority(int profile, BluetoothDevice device, int priority) {
426         enforceBluetoothAdminPermission();
427         synchronized (mPerUserLock) {
428             BluetoothProfileDeviceManager deviceManager =
429                     (BluetoothProfileDeviceManager) mProfileDeviceManagers.get(profile);
430             if (deviceManager != null) {
431                 deviceManager.setDeviceConnectionPriority(device, priority);
432             }
433         }
434         return;
435     }
436 
437     /**
438      * Request to disconnect the given profile on the given device, and prevent it from reconnecting
439      * until either the request is released, or the process owning the given token dies.
440      *
441      * @param device  The device on which to inhibit a profile.
442      * @param profile The {@link android.bluetooth.BluetoothProfile} to inhibit.
443      * @param token   A {@link IBinder} to be used as an identity for the request. If the process
444      *                owning the token dies, the request will automatically be released
445      * @return True if the profile was successfully inhibited, false if an error occurred.
446      */
requestProfileInhibit(BluetoothDevice device, int profile, IBinder token)447     boolean requestProfileInhibit(BluetoothDevice device, int profile, IBinder token) {
448         logd("Request profile inhibit: profile " + Utils.getProfileName(profile)
449                 + ", device " + device.getAddress());
450         synchronized (mPerUserLock) {
451             if (mInhibitManager == null) return false;
452             return mInhibitManager.requestProfileInhibit(device, profile, token);
453         }
454     }
455 
456     /**
457      * Undo a previous call to {@link #requestProfileInhibit} with the same parameters,
458      * and reconnect the profile if no other requests are active.
459      *
460      * @param device  The device on which to release the inhibit request.
461      * @param profile The profile on which to release the inhibit request.
462      * @param token   The token provided in the original call to
463      *                {@link #requestBluetoothProfileInhibit}.
464      * @return True if the request was released, false if an error occurred.
465      */
releaseProfileInhibit(BluetoothDevice device, int profile, IBinder token)466     boolean releaseProfileInhibit(BluetoothDevice device, int profile, IBinder token) {
467         logd("Release profile inhibit: profile " + Utils.getProfileName(profile)
468                 + ", device " + device.getAddress());
469         synchronized (mPerUserLock) {
470             if (mInhibitManager == null) return false;
471             return mInhibitManager.releaseProfileInhibit(device, profile, token);
472         }
473     }
474 
475     /**
476      * Make sure the caller has the Bluetooth permissions that are required to execute any function
477      */
enforceBluetoothAdminPermission()478     private void enforceBluetoothAdminPermission() {
479         if (mContext != null
480                 && PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
481                 android.Manifest.permission.BLUETOOTH_ADMIN)) {
482             return;
483         }
484         if (mContext == null) {
485             Log.e(TAG, "CarBluetoothPrioritySettings does not have a Context");
486         }
487         throw new SecurityException("requires permission "
488                 + android.Manifest.permission.BLUETOOTH_ADMIN);
489     }
490 
491     /**
492      * Print out the verbose debug status of this object
493      */
494     @Override
dump(PrintWriter writer)495     public void dump(PrintWriter writer) {
496         writer.println("*" + TAG + "*");
497         synchronized (mPerUserLock) {
498             writer.println("\tUser ID: " + mUserId);
499             writer.println("\tUser Proxies: " + (mCarBluetoothUserService != null ? "Yes" : "No"));
500 
501             // Profile Device Manager statuses
502             for (int i = 0; i < mProfileDeviceManagers.size(); i++) {
503                 int key = mProfileDeviceManagers.keyAt(i);
504                 BluetoothProfileDeviceManager deviceManager =
505                         (BluetoothProfileDeviceManager) mProfileDeviceManagers.get(key);
506                 deviceManager.dump(writer, "\t");
507             }
508 
509             // Profile Inhibits
510             if (mInhibitManager != null) mInhibitManager.dump(writer, "\t");
511             else writer.println("\tBluetoothProfileInhibitManager: null");
512 
513             // Device Connection Policy
514             writer.println("\tUsing default policy? " + (mUseDefaultPolicy ? "Yes" : "No"));
515             if (mBluetoothDeviceConnectionPolicy == null) {
516                 writer.println("\tBluetoothDeviceConnectionPolicy: null");
517             } else {
518                 mBluetoothDeviceConnectionPolicy.dump(writer, "\t");
519             }
520         }
521     }
522 
523     /**
524      * Log to debug if debug output is enabled
525      */
logd(String msg)526     private static void logd(String msg) {
527         if (DBG) {
528             Log.d(TAG, msg);
529         }
530     }
531 }
532