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 17 package com.android.car.bluetooth; 18 19 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 20 import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED; 21 22 import android.bluetooth.BluetoothAdapter; 23 import android.bluetooth.BluetoothManager; 24 import android.car.builtin.util.Slogf; 25 import android.car.hardware.power.CarPowerPolicy; 26 import android.car.hardware.power.CarPowerPolicyFilter; 27 import android.car.hardware.power.ICarPowerPolicyListener; 28 import android.car.hardware.power.PowerComponent; 29 import android.car.user.CarUserManager; 30 import android.car.user.UserLifecycleEventFilter; 31 import android.content.Context; 32 import android.os.RemoteException; 33 import android.os.UserHandle; 34 import android.os.UserManager; 35 import android.provider.Settings; 36 import android.util.Log; 37 38 import com.android.car.CarLocalServices; 39 import com.android.car.CarLog; 40 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 41 import com.android.car.internal.util.IndentingPrintWriter; 42 import com.android.car.power.CarPowerManagementService; 43 import com.android.car.user.CarUserService; 44 import com.android.internal.annotations.VisibleForTesting; 45 46 import java.util.Objects; 47 48 /** 49 * The car power policy associated with Bluetooth. Uses the CarPowerManager power states and 50 * changes the state of the Bluetooth adapter. 51 */ 52 public final class BluetoothPowerPolicy { 53 private static final String TAG = CarLog.tagFor(BluetoothPowerPolicy.class); 54 private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG); 55 56 // These constants come from BluetoothManagerService.java 57 private static final int BLUETOOTH_OFF = 0; 58 private static final int BLUETOOTH_ON = 1; 59 60 private int mUserId; 61 private final Context mContext; 62 private final BluetoothAdapter mBluetoothAdapter; 63 private CarUserService mCarUserService; 64 private final UserManager mUserManager; 65 private CarPowerManagementService mCarPowerManagementService; 66 67 private final ICarPowerPolicyListener mPowerPolicyListener = 68 new ICarPowerPolicyListener.Stub() { 69 @Override 70 public void onPolicyChanged(CarPowerPolicy appliedPolicy, 71 CarPowerPolicy accumulatedPolicy) { 72 boolean isOn = accumulatedPolicy.isComponentEnabled(PowerComponent.BLUETOOTH); 73 if (!mUserManager.isUserUnlocked(UserHandle.of(mUserId))) { 74 if (DBG) { 75 Slogf.d(TAG, "User %d is locked, ignoring bluetooth power change %s", 76 mUserId, (isOn ? "on" : "off")); 77 } 78 return; 79 } 80 if (isOn) { 81 if (isBluetoothPersistedOn()) { 82 enableBluetooth(); 83 } 84 } else { 85 // we'll turn off Bluetooth to disconnect devices and better the "off" 86 // illusion 87 if (DBG) { 88 Slogf.d(TAG, "Car power policy turns off bluetooth." 89 + " Disable bluetooth adapter"); 90 } 91 disableBluetooth(); 92 } 93 } 94 }; 95 96 private final CarUserManager.UserLifecycleListener mUserLifecycleListener = event -> { 97 try { 98 mPowerPolicyListener.onPolicyChanged(/* appliedPolicy= */ null, 99 mCarPowerManagementService.getCurrentPowerPolicy()); 100 } catch (RemoteException e) { 101 Slogf.e(TAG, "Could not apply current power policy", e); 102 } 103 }; 104 105 @VisibleForTesting getPowerPolicyListener()106 public ICarPowerPolicyListener getPowerPolicyListener() { 107 return mPowerPolicyListener; 108 } 109 110 /** 111 * Create a new BluetoothPowerPolicy object, responsible for encapsulating the 112 * default policy for when to initiate device connections given the list of prioritized devices 113 * for each profile. 114 * 115 * @param context - The context of the creating application 116 * @param userId - The user ID we're operating as 117 * @return A new instance of a BluetoothPowerPolicy, or null on any error 118 */ create(Context context, int userId)119 public static BluetoothPowerPolicy create(Context context, int userId) { 120 try { 121 return new BluetoothPowerPolicy(context, userId); 122 } catch (NullPointerException e) { 123 return null; 124 } 125 } 126 127 /** 128 * Create a new BluetoothPowerPolicy object, responsible for encapsulating the default policy 129 * for when to enable and disable bluetooth based on the Car Power Management power states and 130 * callbacks. 131 * 132 * @param context - The context of the creating application 133 * @param userId - The user ID we're operating as 134 * @return A new instance of a BluetoothPowerPolicy 135 */ BluetoothPowerPolicy(Context context, int userId)136 private BluetoothPowerPolicy(Context context, int userId) { 137 mUserId = userId; 138 mContext = Objects.requireNonNull(context); 139 BluetoothManager bluetoothManager = 140 Objects.requireNonNull(mContext.getSystemService(BluetoothManager.class)); 141 mBluetoothAdapter = Objects.requireNonNull(bluetoothManager.getAdapter()); 142 mUserManager = mContext.getSystemService(UserManager.class); 143 } 144 145 /** 146 * Setup the Bluetooth power policy 147 */ init()148 public void init() { 149 if (DBG) { 150 Slogf.d(TAG, "init()"); 151 } 152 mCarPowerManagementService = CarLocalServices.getService( 153 CarPowerManagementService.class); 154 mCarUserService = CarLocalServices.getService(CarUserService.class); 155 UserLifecycleEventFilter userSwitchingEventFilter = new UserLifecycleEventFilter.Builder() 156 .addEventType(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED) 157 .addUser(UserHandle.of(mUserId)) 158 .build(); 159 mCarUserService.addUserLifecycleListener(userSwitchingEventFilter, mUserLifecycleListener); 160 if (mCarPowerManagementService != null) { 161 CarPowerPolicyFilter filter = new CarPowerPolicyFilter.Builder() 162 .setComponents(PowerComponent.BLUETOOTH).build(); 163 mCarPowerManagementService.addPowerPolicyListener(filter, mPowerPolicyListener); 164 } else { 165 Slogf.w(TAG, "Cannot find CarPowerManagementService"); 166 } 167 } 168 169 /** 170 * Clean up slate. Close the Bluetooth profile service connections and quit the state machine - 171 * {@link BluetoothAutoConnectStateMachine} 172 */ release()173 public void release() { 174 if (DBG) { 175 Slogf.d(TAG, "release()"); 176 } 177 CarPowerManagementService cpms = 178 CarLocalServices.getService(CarPowerManagementService.class); 179 if (cpms != null) { 180 cpms.removePowerPolicyListener(mPowerPolicyListener); 181 } 182 mCarUserService.removeUserLifecycleListener(mUserLifecycleListener); 183 } 184 185 /** 186 * Get the persisted Bluetooth state from Settings 187 * 188 * @return True if the persisted Bluetooth state is on, false otherwise 189 */ isBluetoothPersistedOn()190 private boolean isBluetoothPersistedOn() { 191 // BluetoothManagerService defaults to BLUETOOTH_ON on error as well 192 return (Settings.Global.getInt(mContext.getContentResolver(), 193 Settings.Global.BLUETOOTH_ON, BLUETOOTH_ON) != BLUETOOTH_OFF); 194 } 195 196 /** 197 * Turn on the Bluetooth Adapter. 198 */ enableBluetooth()199 private void enableBluetooth() { 200 if (DBG) { 201 Slogf.d(TAG, "Enable bluetooth adapter"); 202 } 203 if (mBluetoothAdapter == null) { 204 Slogf.e(TAG, "Cannot enable Bluetooth adapter. The object is null."); 205 return; 206 } 207 mBluetoothAdapter.enable(); 208 } 209 210 /** 211 * Turn off the Bluetooth Adapter. 212 * 213 * Tells BluetoothAdapter to shut down _without_ persisting the off state as the desired state 214 * of the Bluetooth adapter for next start up. 215 */ disableBluetooth()216 private void disableBluetooth() { 217 if (DBG) { 218 Slogf.d(TAG, "Disable bluetooth, do not persist state across reboot"); 219 } 220 if (mBluetoothAdapter == null) { 221 Slogf.e(TAG, "Cannot disable Bluetooth adapter. The object is null."); 222 return; 223 } 224 mBluetoothAdapter.disable(false); 225 } 226 227 /** 228 * Print the verbose status of the object 229 */ 230 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)231 public void dump(IndentingPrintWriter writer) { 232 writer.printf("%s:\n", TAG); 233 writer.increaseIndent(); 234 writer.printf("UserId: %d\n", mUserId); 235 writer.decreaseIndent(); 236 } 237 } 238