• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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