blob: 4139c304a6eba76de0bb84666801a239a3edb8ee [file] [log] [blame]
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001/*
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
17package com.android.car;
18
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080019import android.bluetooth.BluetoothAdapter;
20import android.bluetooth.BluetoothDevice;
Andrew Chengb7eaebe2020-10-02 08:31:55 -070021import android.car.VehicleAreaSeat;
22import android.car.VehicleAreaType;
23import android.car.VehiclePropertyIds;
24import android.car.VehicleSeatOccupancyState;
25import android.car.drivingstate.CarDrivingStateEvent;
26import android.car.hardware.CarPropertyValue;
27import android.car.hardware.property.CarPropertyEvent;
28import android.car.hardware.property.CarPropertyManager;
29import android.car.hardware.property.ICarPropertyEventListener;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080030import android.content.BroadcastReceiver;
31import android.content.Context;
32import android.content.Intent;
33import android.content.IntentFilter;
Andrew Chengb7eaebe2020-10-02 08:31:55 -070034import android.os.RemoteException;
Ram Periathiruvadie011a402018-03-22 18:54:33 -070035import android.os.UserHandle;
Joseph Pirozzo6b5c8dd2021-01-22 10:01:17 -080036import android.os.UserManager;
Ram Periathiruvadie011a402018-03-22 18:54:33 -070037import android.provider.Settings;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080038import android.util.Log;
Eric Jeongbd5fb562020-12-21 13:49:40 -080039import android.util.Slog;
Ram Periathiruvadi83ee9012017-07-18 19:21:06 -070040
Jim Kaye9c2a1552020-06-04 13:48:54 -070041import com.android.car.power.SilentModeController;
42import com.android.internal.annotations.VisibleForTesting;
43
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080044import java.io.PrintWriter;
Andrew Chengb7eaebe2020-10-02 08:31:55 -070045import java.util.List;
Justin Pauporec813ead2018-12-28 20:18:25 -080046import java.util.Objects;
Ram Periathiruvadiacb60242017-04-13 16:19:09 -070047
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080048/**
Sal Savage703c46f2019-04-15 08:39:25 -070049 * A Bluetooth Device Connection policy that is specific to the use cases of a Car. Contains policy
50 * for deciding when to trigger connection and disconnection events.
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080051 */
52
53public class BluetoothDeviceConnectionPolicy {
Mayank Garg72c71d22021-02-03 23:54:45 -080054 private static final String TAG = CarLog.tagFor(BluetoothDeviceConnectionPolicy.class);
Sal Savage703c46f2019-04-15 08:39:25 -070055 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
Justin Paupore7b17ea62018-12-28 20:27:33 -080056
Sal Savage703c46f2019-04-15 08:39:25 -070057 private final int mUserId;
Ram Periathiruvadiacb60242017-04-13 16:19:09 -070058 private final Context mContext;
Ram Periathiruvadiee28c002017-02-07 21:35:01 -080059 private final BluetoothAdapter mBluetoothAdapter;
Sal Savage703c46f2019-04-15 08:39:25 -070060 private final CarBluetoothService mCarBluetoothService;
Andrew Chengb7eaebe2020-10-02 08:31:55 -070061 private final CarServicesHelper mCarHelper;
Joseph Pirozzo6b5c8dd2021-01-22 10:01:17 -080062 private final UserManager mUserManager;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080063
Jim Kaye9c2a1552020-06-04 13:48:54 -070064 private final SilentModeController.SilentModeListener mSilentModeListener =
65 new SilentModeController.SilentModeListener() {
66 @Override
67 public void onModeChange(boolean isSilent) {
Joseph Pirozzo6b5c8dd2021-01-22 10:01:17 -080068 if (!mUserManager.isUserUnlocked(mUserId)) {
69 logd("User " + mUserId + " is locked, ignoring silent mode " + isSilent);
70 return;
71 }
Jim Kaye9c2a1552020-06-04 13:48:54 -070072 if (isSilent) {
73 // we'll turn off Bluetooth to disconnect devices and better the "off"
74 // illusion
75 logd("Car is going silent. Disable bluetooth adapter");
76 disableBluetooth();
77 } else {
78 if (isBluetoothPersistedOn()) {
79 enableBluetooth();
80 }
81 // The above isBluetoothPersistedOn() call is always true when the
82 // adapter is on, but can be true or false if the adapter is off. If we
83 // turned the adapter back on then this connectDevices() call would fail
84 // at first here but be caught by the following adapter on broadcast
85 // below. We'll only do this if the adapter is on.
86 if (mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {
87 connectDevices();
88 }
89 }
Sal Savageed6b04e2019-03-15 13:27:27 -070090 }
Sal Savageed6b04e2019-03-15 13:27:27 -070091 };
92
Jim Kaye9c2a1552020-06-04 13:48:54 -070093 @VisibleForTesting
94 public SilentModeController.SilentModeListener getSilentModeListener() {
95 return mSilentModeListener;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080096 }
97
98 /**
Sal Savage703c46f2019-04-15 08:39:25 -070099 * A BroadcastReceiver that listens specifically for actions related to the profile we're
100 * tracking and uses them to update the status.
101 *
102 * On BluetoothAdapter.ACTION_STATE_CHANGED:
103 * If the adapter is going into the ON state, then commit trigger auto connection.
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800104 */
Sal Savage703c46f2019-04-15 08:39:25 -0700105 private class BluetoothBroadcastReceiver extends BroadcastReceiver {
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800106 @Override
107 public void onReceive(Context context, Intent intent) {
108 String action = intent.getAction();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800109 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Sal Savage703c46f2019-04-15 08:39:25 -0700110 if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
111 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
Andrew Chengb7eaebe2020-10-02 08:31:55 -0700112 logd("Bluetooth Adapter state changed: ", Utils.getAdapterStateName(state));
Sal Savage703c46f2019-04-15 08:39:25 -0700113 if (state == BluetoothAdapter.STATE_ON) {
114 connectDevices();
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700115 }
116 }
Sal Savage703c46f2019-04-15 08:39:25 -0700117 }
118 }
Sal Savage6c101292019-11-14 15:30:18 -0800119 private final BluetoothBroadcastReceiver mBluetoothBroadcastReceiver;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800120
Sal Savage703c46f2019-04-15 08:39:25 -0700121 /**
Andrew Chengb7eaebe2020-10-02 08:31:55 -0700122 * A helper class to interact with the VHAL and the rest of the car.
123 */
124 private final class CarServicesHelper {
125 private final CarPropertyService mCarPropertyService;
126 private final CarDrivingStateService mCarDrivingStateService;
127
128 // Location of the driver's seat, e.g., left or right side.
129 private final int mDriverSeat;
130
131 CarServicesHelper() {
132 mCarPropertyService = CarLocalServices.getService(CarPropertyService.class);
Eric Jeongbd5fb562020-12-21 13:49:40 -0800133 if (mCarPropertyService == null) Slog.w(TAG, "Cannot find CarPropertyService");
Andrew Chengb7eaebe2020-10-02 08:31:55 -0700134 mDriverSeat = getDriverSeatLocationFromVhal();
135 mCarDrivingStateService = CarLocalServices.getService(CarDrivingStateService.class);
Eric Jeongbd5fb562020-12-21 13:49:40 -0800136 if (mCarDrivingStateService == null) Slog.w(TAG, "Cannot find mCarDrivingStateService");
Andrew Chengb7eaebe2020-10-02 08:31:55 -0700137 }
138
139 /**
140 * Set up vehicle event listeners. Remember to call {@link release()} when done.
141 */
142 public void init() {
143 if (mCarPropertyService != null) {
144 mCarPropertyService.registerListener(VehiclePropertyIds.SEAT_OCCUPANCY,
145 CarPropertyManager.SENSOR_RATE_ONCHANGE, mSeatOnOccupiedListener);
146 }
147 }
148
149 public void release() {
150 if (mCarPropertyService != null) {
151 mCarPropertyService.unregisterListener(VehiclePropertyIds.SEAT_OCCUPANCY,
152 mSeatOnOccupiedListener);
153 }
154 }
155
156 /**
157 * A {@code ICarPropertyEventListener} that triggers the auto-connection process when
158 * {@code SEAT_OCCUPANCY} is {@code OCCUPIED}.
159 */
160 private final ICarPropertyEventListener mSeatOnOccupiedListener =
161 new ICarPropertyEventListener.Stub() {
162 @Override
163 public void onEvent(List<CarPropertyEvent> events) throws RemoteException {
164 for (CarPropertyEvent event : events) {
165 onSeatOccupancyCarPropertyEvent(event);
166 }
167 }
168 };
169
170 /**
171 * Acts on {@link CarPropertyEvent} events marked with
172 * {@link CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE} and marked with {@link
173 * VehiclePropertyIds.SEAT_OCCUPANCY} by calling {@link connectDevices}.
174 * <p>
175 * Default implementation filters on driver's seat only, but can change to trigger on
176 * any front row seat, or any seat in the car.
177 * <p>
178 * Default implementation also restricts this trigger to when the car is in the
179 * parked state, to discourage drivers from exploiting to connect while driving, and to
180 * also filter out spurious seat sensor signals while driving.
181 * <p>
182 * This method does nothing if the event parameter is {@code null}.
183 *
184 * @param event - The {@link CarPropertyEvent} to be handled.
185 */
186 private void onSeatOccupancyCarPropertyEvent(CarPropertyEvent event) {
187 if ((event == null)
188 || (event.getEventType() != CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE)) {
189 return;
190 }
191 CarPropertyValue value = event.getCarPropertyValue();
192 logd("Car property changed: ", value.toString());
193 if (mBluetoothAdapter.isEnabled()
194 && (value.getPropertyId() == VehiclePropertyIds.SEAT_OCCUPANCY)
195 && ((int) value.getValue() == VehicleSeatOccupancyState.OCCUPIED)
196 && (value.getAreaId() == mDriverSeat)
197 && isParked()) {
198 connectDevices();
199 }
200 }
201
202 /**
203 * Gets the location of the driver's seat (e.g., front-left, front-right) from the VHAL.
204 * <p>
205 * Default implementation sets the driver's seat to front-left if mCarPropertyService is
206 * not found.
207 * <p>
208 * Note, comments for {@link CarPropertyManager#getIntProperty(int, int)} indicate it may
209 * take a couple of seconds to complete, whereas there are no such comments for
210 * {@link CarPropertyService#getProperty(int, int)}, but we assume there is also similar
211 * latency in querying VHAL properties.
212 *
213 * @return An {@code int} representing driver's seat location.
214 */
215 private int getDriverSeatLocationFromVhal() {
216 if (mCarPropertyService == null) {
217 return VehicleAreaSeat.SEAT_ROW_1_LEFT;
218 }
219 return (int) mCarPropertyService.getProperty(VehiclePropertyIds.INFO_DRIVER_SEAT,
220 VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL).getValue();
221 }
222
223 public int getDriverSeatLocation() {
224 return mDriverSeat;
225 }
226
227 /**
228 * Returns {@code true} if the car is in parked gear.
229 * <p>
230 * We are being conservative and only want to trigger when car is in parked state. Extending
231 * this conservative approach, we default return false if {@code mCarDrivingStateService}
232 * is not found.
233 */
234 public boolean isParked() {
235 if (mCarDrivingStateService == null) {
236 return false;
237 }
238 return mCarDrivingStateService.getCurrentDrivingState().eventValue
239 == CarDrivingStateEvent.DRIVING_STATE_PARKED;
240 }
241 }
242
243 /**
Sal Savage703c46f2019-04-15 08:39:25 -0700244 * Create a new BluetoothDeviceConnectionPolicy object, responsible for encapsulating the
245 * default policy for when to initiate device connections given the list of prioritized devices
246 * for each profile.
247 *
248 * @param context - The context of the creating application
249 * @param userId - The user ID we're operating as
250 * @param bluetoothService - A reference to CarBluetoothService so we can connect devices
251 * @return A new instance of a BluetoothProfileDeviceManager, or null on any error
252 */
253 public static BluetoothDeviceConnectionPolicy create(Context context, int userId,
254 CarBluetoothService bluetoothService) {
255 try {
256 return new BluetoothDeviceConnectionPolicy(context, userId, bluetoothService);
257 } catch (NullPointerException e) {
258 return null;
Ram Periathiruvadibe7ea0fe2017-05-31 23:31:40 -0700259 }
260 }
261
262 /**
Sal Savage703c46f2019-04-15 08:39:25 -0700263 * Create a new BluetoothDeviceConnectionPolicy object, responsible for encapsulating the
264 * default policy for when to initiate device connections given the list of prioritized devices
265 * for each profile.
Ram Periathiruvadibe7ea0fe2017-05-31 23:31:40 -0700266 *
Sal Savage703c46f2019-04-15 08:39:25 -0700267 * @param context - The context of the creating application
268 * @param userId - The user ID we're operating as
269 * @param bluetoothService - A reference to CarBluetoothService so we can connect devices
270 * @return A new instance of a BluetoothProfileDeviceManager
Ram Periathiruvadibe7ea0fe2017-05-31 23:31:40 -0700271 */
Sal Savage703c46f2019-04-15 08:39:25 -0700272 private BluetoothDeviceConnectionPolicy(Context context, int userId,
273 CarBluetoothService bluetoothService) {
274 mUserId = userId;
275 mContext = Objects.requireNonNull(context);
276 mCarBluetoothService = bluetoothService;
Sal Savage6c101292019-11-14 15:30:18 -0800277 mBluetoothBroadcastReceiver = new BluetoothBroadcastReceiver();
Sal Savage703c46f2019-04-15 08:39:25 -0700278 mBluetoothAdapter = Objects.requireNonNull(BluetoothAdapter.getDefaultAdapter());
Andrew Chengb7eaebe2020-10-02 08:31:55 -0700279 mCarHelper = new CarServicesHelper();
Joseph Pirozzo6b5c8dd2021-01-22 10:01:17 -0800280 mUserManager = mContext.getSystemService(UserManager.class);
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700281 }
282
283 /**
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800284 * Setup the Bluetooth profile service connections and Vehicle Event listeners.
285 * and start the state machine -{@link BluetoothAutoConnectStateMachine}
286 */
Sal Savage6c101292019-11-14 15:30:18 -0800287 public void init() {
Sal Savage703c46f2019-04-15 08:39:25 -0700288 logd("init()");
Pavel Maltsevc9e86e82017-03-22 11:37:21 -0700289 IntentFilter profileFilter = new IntentFilter();
Pavel Maltsevc9e86e82017-03-22 11:37:21 -0700290 profileFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
Sal Savage703c46f2019-04-15 08:39:25 -0700291 mContext.registerReceiverAsUser(mBluetoothBroadcastReceiver, UserHandle.CURRENT,
292 profileFilter, null, null);
Jim Kaye9c2a1552020-06-04 13:48:54 -0700293 SilentModeController silentModeController = CarLocalServices.getService(
294 SilentModeController.class);
295 if (silentModeController != null) {
296 silentModeController.registerListener(mSilentModeListener);
Ram Periathiruvadie011a402018-03-22 18:54:33 -0700297 } else {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800298 Slog.w(TAG, "Cannot find SilentModeController");
Sal Savage703c46f2019-04-15 08:39:25 -0700299 }
Andrew Chengb7eaebe2020-10-02 08:31:55 -0700300 mCarHelper.init();
Sal Savage703c46f2019-04-15 08:39:25 -0700301
302 // Since we do this only on start up and on user switch, it's safe to kick off a connect on
303 // init. If we have a connect in progress, this won't hurt anything. If we already have
304 // devices connected, this will add on top of it. We _could_ enter this from a crash
305 // recovery, but that would at worst cause more devices to connect and wouldn't change the
306 // existing devices.
307 if (mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {
308 // CarPowerManager doesn't provide a getState() or that would go here too.
309 connectDevices();
Ram Periathiruvadie011a402018-03-22 18:54:33 -0700310 }
311 }
312
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800313 /**
314 * Clean up slate. Close the Bluetooth profile service connections and quit the state machine -
315 * {@link BluetoothAutoConnectStateMachine}
316 */
Sal Savage6c101292019-11-14 15:30:18 -0800317 public void release() {
Sal Savage703c46f2019-04-15 08:39:25 -0700318 logd("release()");
Jim Kaye9c2a1552020-06-04 13:48:54 -0700319 SilentModeController silentModeController =
320 CarLocalServices.getService(SilentModeController.class);
321 if (silentModeController != null) {
322 silentModeController.unregisterListener(mSilentModeListener);
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800323 }
Sal Savage703c46f2019-04-15 08:39:25 -0700324 if (mBluetoothBroadcastReceiver != null) {
325 mContext.unregisterReceiver(mBluetoothBroadcastReceiver);
Ram Periathiruvadia048c0a2017-05-09 07:35:03 -0700326 }
Andrew Chengb7eaebe2020-10-02 08:31:55 -0700327 mCarHelper.release();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800328 }
329
330 /**
Sal Savage703c46f2019-04-15 08:39:25 -0700331 * Tell each Profile device manager that its time to begin auto connecting devices
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800332 */
Sal Savage703c46f2019-04-15 08:39:25 -0700333 public void connectDevices() {
334 logd("Connect devices for each profile");
335 mCarBluetoothService.connectDevices();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800336 }
337
338 /**
Sal Savageed6b04e2019-03-15 13:27:27 -0700339 * Get the persisted Bluetooth state from Settings
Sal Savage703c46f2019-04-15 08:39:25 -0700340 *
341 * @return True if the persisted Bluetooth state is on, false otherwise
Sal Savageed6b04e2019-03-15 13:27:27 -0700342 */
343 private boolean isBluetoothPersistedOn() {
344 return (Settings.Global.getInt(
345 mContext.getContentResolver(), Settings.Global.BLUETOOTH_ON, -1) != 0);
346 }
347
348 /**
349 * Turn on the Bluetooth Adapter.
350 */
Sal Savage703c46f2019-04-15 08:39:25 -0700351 private void enableBluetooth() {
352 logd("Enable bluetooth adapter");
Sal Savageed6b04e2019-03-15 13:27:27 -0700353 if (mBluetoothAdapter == null) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800354 Slog.e(TAG, "Cannot enable Bluetooth adapter. The object is null.");
Sal Savageed6b04e2019-03-15 13:27:27 -0700355 return;
356 }
357 mBluetoothAdapter.enable();
358 }
359
360 /**
361 * Turn off the Bluetooth Adapter.
362 *
363 * Tells BluetoothAdapter to shut down _without_ persisting the off state as the desired state
364 * of the Bluetooth adapter for next start up.
365 */
366 private void disableBluetooth() {
Sal Savage703c46f2019-04-15 08:39:25 -0700367 logd("Disable bluetooth, do not persist state across reboot");
Sal Savageed6b04e2019-03-15 13:27:27 -0700368 if (mBluetoothAdapter == null) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800369 Slog.e(TAG, "Cannot disable Bluetooth adapter. The object is null.");
Sal Savageed6b04e2019-03-15 13:27:27 -0700370 return;
371 }
372 mBluetoothAdapter.disable(false);
373 }
374
375 /**
Sal Savage703c46f2019-04-15 08:39:25 -0700376 * Print the verbose status of the object
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800377 */
Sal Savage6c101292019-11-14 15:30:18 -0800378 public void dump(PrintWriter writer, String indent) {
Sal Savage703c46f2019-04-15 08:39:25 -0700379 writer.println(indent + TAG + ":");
380 writer.println(indent + "\tUserId: " + mUserId);
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700381 }
382
383 /**
Sal Savage703c46f2019-04-15 08:39:25 -0700384 * Print to debug if debug is enabled
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700385 */
Andrew Chengb7eaebe2020-10-02 08:31:55 -0700386 private static void logd(String... msgParts) {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800387 if (DBG) {
Eric Jeongbd5fb562020-12-21 13:49:40 -0800388 Slog.d(TAG, String.join(" ", msgParts));
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800389 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800390 }
391}