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 17 package com.android.car; 18 19 import android.bluetooth.BluetoothAdapter; 20 import android.bluetooth.BluetoothDevice; 21 import android.car.hardware.power.CarPowerManager; 22 import android.car.hardware.power.CarPowerManager.CarPowerStateListenerWithCompletion; 23 import android.content.BroadcastReceiver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.os.UserHandle; 28 import android.provider.Settings; 29 import android.util.Log; 30 31 import java.io.PrintWriter; 32 import java.util.Objects; 33 import java.util.concurrent.CompletableFuture; 34 35 /** 36 * A Bluetooth Device Connection policy that is specific to the use cases of a Car. Contains policy 37 * for deciding when to trigger connection and disconnection events. 38 */ 39 40 public class BluetoothDeviceConnectionPolicy { 41 private static final String TAG = "BluetoothDeviceConnectionPolicy"; 42 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); 43 44 private final int mUserId; 45 private final Context mContext; 46 private final BluetoothAdapter mBluetoothAdapter; 47 private final CarBluetoothService mCarBluetoothService; 48 49 private CarPowerManager mCarPowerManager; 50 private final CarPowerStateListenerWithCompletion mCarPowerStateListener = 51 new CarPowerStateListenerWithCompletion() { 52 @Override 53 public void onStateChanged(int state, CompletableFuture<Void> future) { 54 logd("Car power state has changed to " + state); 55 56 // ON is the state when user turned on the car (it can be either ignition or 57 // door unlock) the policy for ON is defined by OEMs and we can rely on that. 58 if (state == CarPowerManager.CarPowerStateListener.ON) { 59 logd("Car is powering on. Enable Bluetooth and auto-connect to devices"); 60 if (isBluetoothPersistedOn()) { 61 enableBluetooth(); 62 } 63 64 // The above isBluetoothPersistedOn() call is always true when the adapter is on and 65 // can be true or false if the adapter is off. If we are turned the adapter back on 66 // then this connectDevices() call would fail at first here but be caught by the 67 // following adapter on broadcast below. We'll only do this if the adapter is on 68 if (mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) { 69 connectDevices(); 70 } 71 return; 72 } 73 74 // Since we're appearing to be off after shutdown prepare, but may stay on in idle mode, 75 // we'll turn off Bluetooth to disconnect devices and better the "off" illusion 76 if (state == CarPowerManager.CarPowerStateListener.SHUTDOWN_PREPARE) { 77 logd("Car is preparing for shutdown. Disable bluetooth adapter"); 78 disableBluetooth(); 79 80 // Let CPMS know we're ready to shutdown. Otherwise, CPMS will get stuck for 81 // up to an hour. 82 if (future != null) { 83 future.complete(null); 84 } 85 return; 86 } 87 } 88 }; 89 90 /** 91 * Get the policy's CarPowerStateListenerWithCompletion object 92 * 93 * For testing purposes only 94 */ getCarPowerStateListener()95 public CarPowerStateListenerWithCompletion getCarPowerStateListener() { 96 return mCarPowerStateListener; 97 } 98 99 /** 100 * A BroadcastReceiver that listens specifically for actions related to the profile we're 101 * tracking and uses them to update the status. 102 * 103 * On BluetoothAdapter.ACTION_STATE_CHANGED: 104 * If the adapter is going into the ON state, then commit trigger auto connection. 105 */ 106 private class BluetoothBroadcastReceiver extends BroadcastReceiver { 107 @Override onReceive(Context context, Intent intent)108 public void onReceive(Context context, Intent intent) { 109 String action = intent.getAction(); 110 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 111 if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) { 112 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); 113 logd("Bluetooth Adapter state changed: " + Utils.getAdapterStateName(state)); 114 if (state == BluetoothAdapter.STATE_ON) { 115 connectDevices(); 116 } 117 } 118 } 119 } 120 private final BluetoothBroadcastReceiver mBluetoothBroadcastReceiver; 121 122 /** 123 * Create a new BluetoothDeviceConnectionPolicy object, responsible for encapsulating the 124 * default policy for when to initiate device connections given the list of prioritized devices 125 * for each profile. 126 * 127 * @param context - The context of the creating application 128 * @param userId - The user ID we're operating as 129 * @param bluetoothService - A reference to CarBluetoothService so we can connect devices 130 * @return A new instance of a BluetoothProfileDeviceManager, or null on any error 131 */ create(Context context, int userId, CarBluetoothService bluetoothService)132 public static BluetoothDeviceConnectionPolicy create(Context context, int userId, 133 CarBluetoothService bluetoothService) { 134 try { 135 return new BluetoothDeviceConnectionPolicy(context, userId, bluetoothService); 136 } catch (NullPointerException e) { 137 return null; 138 } 139 } 140 141 /** 142 * Create a new BluetoothDeviceConnectionPolicy object, responsible for encapsulating the 143 * default policy for when to initiate device connections given the list of prioritized devices 144 * for each profile. 145 * 146 * @param context - The context of the creating application 147 * @param userId - The user ID we're operating as 148 * @param bluetoothService - A reference to CarBluetoothService so we can connect devices 149 * @return A new instance of a BluetoothProfileDeviceManager 150 */ BluetoothDeviceConnectionPolicy(Context context, int userId, CarBluetoothService bluetoothService)151 private BluetoothDeviceConnectionPolicy(Context context, int userId, 152 CarBluetoothService bluetoothService) { 153 mUserId = userId; 154 mContext = Objects.requireNonNull(context); 155 mCarBluetoothService = bluetoothService; 156 mBluetoothBroadcastReceiver = new BluetoothBroadcastReceiver(); 157 mBluetoothAdapter = Objects.requireNonNull(BluetoothAdapter.getDefaultAdapter()); 158 } 159 160 /** 161 * Setup the Bluetooth profile service connections and Vehicle Event listeners. 162 * and start the state machine -{@link BluetoothAutoConnectStateMachine} 163 */ init()164 public void init() { 165 logd("init()"); 166 IntentFilter profileFilter = new IntentFilter(); 167 profileFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); 168 mContext.registerReceiverAsUser(mBluetoothBroadcastReceiver, UserHandle.CURRENT, 169 profileFilter, null, null); 170 mCarPowerManager = CarLocalServices.createCarPowerManager(mContext); 171 // CarLocalServices can fail to return a service. 172 if (mCarPowerManager != null) { 173 mCarPowerManager.setListenerWithCompletion(mCarPowerStateListener); 174 } else { 175 logd("Failed to get car power manager"); 176 } 177 178 // Since we do this only on start up and on user switch, it's safe to kick off a connect on 179 // init. If we have a connect in progress, this won't hurt anything. If we already have 180 // devices connected, this will add on top of it. We _could_ enter this from a crash 181 // recovery, but that would at worst cause more devices to connect and wouldn't change the 182 // existing devices. 183 if (mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) { 184 // CarPowerManager doesn't provide a getState() or that would go here too. 185 connectDevices(); 186 } 187 } 188 189 /** 190 * Clean up slate. Close the Bluetooth profile service connections and quit the state machine - 191 * {@link BluetoothAutoConnectStateMachine} 192 */ release()193 public void release() { 194 logd("release()"); 195 if (mCarPowerManager != null) { 196 mCarPowerManager.clearListener(); 197 mCarPowerManager = null; 198 } 199 if (mBluetoothBroadcastReceiver != null) { 200 mContext.unregisterReceiver(mBluetoothBroadcastReceiver); 201 } 202 } 203 204 /** 205 * Tell each Profile device manager that its time to begin auto connecting devices 206 */ connectDevices()207 public void connectDevices() { 208 logd("Connect devices for each profile"); 209 mCarBluetoothService.connectDevices(); 210 } 211 212 /** 213 * Get the persisted Bluetooth state from Settings 214 * 215 * @return True if the persisted Bluetooth state is on, false otherwise 216 */ isBluetoothPersistedOn()217 private boolean isBluetoothPersistedOn() { 218 return (Settings.Global.getInt( 219 mContext.getContentResolver(), Settings.Global.BLUETOOTH_ON, -1) != 0); 220 } 221 222 /** 223 * Turn on the Bluetooth Adapter. 224 */ enableBluetooth()225 private void enableBluetooth() { 226 logd("Enable bluetooth adapter"); 227 if (mBluetoothAdapter == null) { 228 Log.e(TAG, "Cannot enable Bluetooth adapter. The object is null."); 229 return; 230 } 231 mBluetoothAdapter.enable(); 232 } 233 234 /** 235 * Turn off the Bluetooth Adapter. 236 * 237 * Tells BluetoothAdapter to shut down _without_ persisting the off state as the desired state 238 * of the Bluetooth adapter for next start up. 239 */ disableBluetooth()240 private void disableBluetooth() { 241 logd("Disable bluetooth, do not persist state across reboot"); 242 if (mBluetoothAdapter == null) { 243 Log.e(TAG, "Cannot disable Bluetooth adapter. The object is null."); 244 return; 245 } 246 mBluetoothAdapter.disable(false); 247 } 248 249 /** 250 * Print the verbose status of the object 251 */ dump(PrintWriter writer, String indent)252 public void dump(PrintWriter writer, String indent) { 253 writer.println(indent + TAG + ":"); 254 writer.println(indent + "\tUserId: " + mUserId); 255 } 256 257 /** 258 * Print to debug if debug is enabled 259 */ logd(String msg)260 private static void logd(String msg) { 261 if (DBG) { 262 Log.d(TAG, msg); 263 } 264 } 265 } 266