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