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