• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 android.bluetooth.cts;
18 
19 import static android.Manifest.permission.BLUETOOTH_CONNECT;
20 import static android.Manifest.permission.BLUETOOTH_PRIVILEGED;
21 
22 import android.bluetooth.BluetoothAdapter;
23 import android.content.BroadcastReceiver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.util.Log;
28 import android.util.SparseIntArray;
29 
30 import androidx.test.platform.app.InstrumentationRegistry;
31 
32 import java.util.Set;
33 import java.util.concurrent.TimeUnit;
34 import java.util.concurrent.locks.Condition;
35 import java.util.concurrent.locks.ReentrantLock;
36 
37 /**
38  * Utility for controlling the Bluetooth adapter from CTS test.
39  */
40 public class BTAdapterUtils {
41     private static final String TAG = "BTAdapterUtils";
42     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
43 
44     // ADAPTER_ENABLE_TIMEOUT_MS = AdapterState.BLE_START_TIMEOUT_DELAY +
45     //                              AdapterState.BREDR_START_TIMEOUT_DELAY
46     private static final int ADAPTER_ENABLE_TIMEOUT_MS = 8000;
47     // ADAPTER_DISABLE_TIMEOUT_MS = AdapterState.BLE_STOP_TIMEOUT_DELAY +
48     //                                  AdapterState.BREDR_STOP_TIMEOUT_DELAY
49     private static final int ADAPTER_DISABLE_TIMEOUT_MS = 5000;
50 
51     public static final int STATE_BLE_TURNING_ON = 14;
52     public static final int STATE_BLE_ON = 15;
53     public static final int STATE_BLE_TURNING_OFF = 16;
54 
55     private static final SparseIntArray sStateTimeouts = new SparseIntArray();
56     static {
sStateTimeouts.put(BluetoothAdapter.STATE_OFF, ADAPTER_DISABLE_TIMEOUT_MS)57         sStateTimeouts.put(BluetoothAdapter.STATE_OFF, ADAPTER_DISABLE_TIMEOUT_MS);
sStateTimeouts.put(BluetoothAdapter.STATE_TURNING_ON, ADAPTER_ENABLE_TIMEOUT_MS)58         sStateTimeouts.put(BluetoothAdapter.STATE_TURNING_ON, ADAPTER_ENABLE_TIMEOUT_MS);
sStateTimeouts.put(BluetoothAdapter.STATE_ON, ADAPTER_ENABLE_TIMEOUT_MS)59         sStateTimeouts.put(BluetoothAdapter.STATE_ON, ADAPTER_ENABLE_TIMEOUT_MS);
sStateTimeouts.put(BluetoothAdapter.STATE_TURNING_OFF, ADAPTER_DISABLE_TIMEOUT_MS)60         sStateTimeouts.put(BluetoothAdapter.STATE_TURNING_OFF, ADAPTER_DISABLE_TIMEOUT_MS);
sStateTimeouts.put(STATE_BLE_TURNING_ON, ADAPTER_ENABLE_TIMEOUT_MS)61         sStateTimeouts.put(STATE_BLE_TURNING_ON, ADAPTER_ENABLE_TIMEOUT_MS);
sStateTimeouts.put(STATE_BLE_ON, ADAPTER_ENABLE_TIMEOUT_MS)62         sStateTimeouts.put(STATE_BLE_ON, ADAPTER_ENABLE_TIMEOUT_MS);
sStateTimeouts.put(STATE_BLE_TURNING_OFF, ADAPTER_DISABLE_TIMEOUT_MS)63         sStateTimeouts.put(STATE_BLE_TURNING_OFF, ADAPTER_DISABLE_TIMEOUT_MS);
64     }
65 
66     private static BluetoothAdapterReceiver sAdapterReceiver;
67 
68     private static boolean sAdapterVarsInitialized;
69     private static ReentrantLock sBluetoothAdapterLock;
70     private static Condition sConditionAdapterStateReached;
71     private static int sDesiredState;
72     private static int sAdapterState;
73 
74     /**
75      * Handles BluetoothAdapter state changes and signals when we have reached a desired state
76      */
77     private static class BluetoothAdapterReceiver extends BroadcastReceiver {
78         @Override
onReceive(Context context, Intent intent)79         public void onReceive(Context context, Intent intent) {
80             String action = intent.getAction();
81             if (BluetoothAdapter.ACTION_BLE_STATE_CHANGED.equals(action)) {
82                 int newState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
83                 if (DBG) {
84                     Log.d(TAG, "Bluetooth adapter state changed: " + newState);
85                 }
86 
87                 // Signal if the state is set to the one we are waiting on
88                 sBluetoothAdapterLock.lock();
89                 sAdapterState = newState;
90                 try {
91                     if (sDesiredState == newState) {
92                         if (DBG) {
93                             Log.d(TAG, "Adapter has reached desired state: " + sDesiredState);
94                         }
95                         sConditionAdapterStateReached.signal();
96                     }
97                 } finally {
98                     sBluetoothAdapterLock.unlock();
99                 }
100             }
101         }
102     }
103 
104     /**
105      * Initialize all static state variables
106      */
initAdapterStateVariables(Context context)107     private static void initAdapterStateVariables(Context context) {
108         if (DBG) {
109             Log.d(TAG, "Initializing adapter state variables");
110         }
111         sAdapterReceiver = new BluetoothAdapterReceiver();
112         sBluetoothAdapterLock = new ReentrantLock();
113         sConditionAdapterStateReached = sBluetoothAdapterLock.newCondition();
114         sDesiredState = -1;
115         sAdapterState = -1;
116         IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED);
117         context.registerReceiver(sAdapterReceiver, filter);
118         sAdapterVarsInitialized = true;
119     }
120 
121     /**
122      * Wait for the bluetooth adapter to be in a given state
123      *
124      * Assumes all state variables are initialized. Assumes it's being run with
125      * sBluetoothAdapterLock in the locked state.
126      */
waitForAdapterStateLocked(int desiredState, BluetoothAdapter adapter)127     private static boolean waitForAdapterStateLocked(int desiredState, BluetoothAdapter adapter)
128             throws InterruptedException {
129         int timeout = sStateTimeouts.get(desiredState, ADAPTER_ENABLE_TIMEOUT_MS);
130 
131         if (DBG) {
132             Log.d(TAG, "Waiting for adapter state " + desiredState);
133         }
134         sDesiredState = desiredState;
135 
136         // Wait until we have reached the desired state
137         while (desiredState != sAdapterState) {
138             if (!sConditionAdapterStateReached.await(timeout, TimeUnit.MILLISECONDS)) {
139                 // Handle situation where state change occurs, but we don't receive the broadcast
140                 if (desiredState >= BluetoothAdapter.STATE_OFF
141                         && desiredState <= BluetoothAdapter.STATE_TURNING_OFF) {
142                     return adapter.getState() == desiredState;
143                 } else if (desiredState == STATE_BLE_ON) {
144                     Log.d(TAG, "adapter isLeEnabled: " + adapter.isLeEnabled());
145                     return adapter.isLeEnabled();
146                 }
147                 Log.e(TAG, "Timeout while waiting for Bluetooth adapter state " + desiredState
148                         + " while current state is " + sAdapterState);
149                 break;
150             }
151         }
152 
153         if (DBG) {
154             Log.d(TAG, "Final state while waiting: " + sAdapterState);
155         }
156 
157         return sAdapterState == desiredState;
158     }
159 
160     /**
161      * Utility method to wait on any specific adapter state
162      */
waitForAdapterState(int desiredState, BluetoothAdapter adapter)163     public static boolean waitForAdapterState(int desiredState, BluetoothAdapter adapter) {
164         sBluetoothAdapterLock.lock();
165         try {
166             return waitForAdapterStateLocked(desiredState, adapter);
167         } catch (InterruptedException e) {
168             Log.w(TAG, "waitForAdapterState(): interrupted", e);
169         } finally {
170             sBluetoothAdapterLock.unlock();
171         }
172         return false;
173     }
174 
175     /**
176      * Enables Bluetooth to a Low Energy only mode
177      */
enableBLE(BluetoothAdapter bluetoothAdapter, Context context)178     public static boolean enableBLE(BluetoothAdapter bluetoothAdapter, Context context) {
179         if (!sAdapterVarsInitialized) {
180             initAdapterStateVariables(context);
181         }
182 
183         if (bluetoothAdapter.isLeEnabled()) {
184             return true;
185         }
186 
187         sBluetoothAdapterLock.lock();
188         try {
189             if (DBG) {
190                 Log.d(TAG, "Enabling Bluetooth low energy only mode");
191             }
192             if (!bluetoothAdapter.enableBLE()) {
193                 Log.e(TAG, "Unable to enable Bluetooth low energy only mode");
194                 return false;
195             }
196             return waitForAdapterStateLocked(STATE_BLE_ON, bluetoothAdapter);
197         } catch (InterruptedException e) {
198             Log.w(TAG, "enableBLE(): interrupted", e);
199         } finally {
200             sBluetoothAdapterLock.unlock();
201         }
202         return false;
203     }
204 
205     /**
206      * Disable Bluetooth Low Energy mode
207      */
disableBLE(BluetoothAdapter bluetoothAdapter, Context context)208     public static boolean disableBLE(BluetoothAdapter bluetoothAdapter, Context context) {
209         if (!sAdapterVarsInitialized) {
210             initAdapterStateVariables(context);
211         }
212 
213         if (bluetoothAdapter.getState() == BluetoothAdapter.STATE_OFF) {
214             return true;
215         }
216 
217         sBluetoothAdapterLock.lock();
218         try {
219             if (DBG) {
220                 Log.d(TAG, "Disabling Bluetooth low energy");
221             }
222             bluetoothAdapter.disableBLE();
223             return waitForAdapterStateLocked(BluetoothAdapter.STATE_OFF, bluetoothAdapter);
224         } catch (InterruptedException e) {
225             Log.w(TAG, "disableBLE(): interrupted", e);
226         } finally {
227             sBluetoothAdapterLock.unlock();
228         }
229         return false;
230     }
231 
232     /**
233      * Enables the Bluetooth Adapter. Return true if it is already enabled or is enabled.
234      */
enableAdapter(BluetoothAdapter bluetoothAdapter, Context context)235     public static boolean enableAdapter(BluetoothAdapter bluetoothAdapter, Context context) {
236         if (!sAdapterVarsInitialized) {
237             initAdapterStateVariables(context);
238         }
239 
240         if (bluetoothAdapter.isEnabled()) {
241             return true;
242         }
243 
244         Set<String> permissionsAdopted = getPermissionsAdoptedAsShellUid();
245         sBluetoothAdapterLock.lock();
246         try {
247             if (DBG) {
248                 Log.d(TAG, "Enabling Bluetooth adapter");
249             }
250             adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
251             bluetoothAdapter.enable();
252             return waitForAdapterStateLocked(BluetoothAdapter.STATE_ON, bluetoothAdapter);
253         } catch (InterruptedException e) {
254             Log.w(TAG, "enableAdapter(): interrupted", e);
255         } finally {
256             adoptPermissionAsShellUid(permissionsAdopted.toArray(new String[0]));
257             sBluetoothAdapterLock.unlock();
258         }
259         return false;
260     }
261 
262     /**
263      * Disable the Bluetooth Adapter. Return true if it is already disabled or is disabled.
264      */
disableAdapter(BluetoothAdapter bluetoothAdapter, Context context)265     public static boolean disableAdapter(BluetoothAdapter bluetoothAdapter, Context context) {
266         if (!sAdapterVarsInitialized) {
267             initAdapterStateVariables(context);
268         }
269 
270         if (bluetoothAdapter.getState() == BluetoothAdapter.STATE_OFF) {
271             return true;
272         }
273 
274         if (DBG) {
275             Log.d(TAG, "Disabling Bluetooth adapter");
276         }
277 
278         Set<String> permissionsAdopted = getPermissionsAdoptedAsShellUid();
279         sBluetoothAdapterLock.lock();
280         try {
281             adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
282             bluetoothAdapter.disable();
283             if (waitForAdapterStateLocked(BluetoothAdapter.STATE_OFF, bluetoothAdapter)) {
284                 //TODO b/234892968
285                 Thread.sleep(2000);
286                 return true;
287             }
288             return false;
289         } catch (InterruptedException e) {
290             Log.w(TAG, "disableAdapter(): interrupted", e);
291         } finally {
292             adoptPermissionAsShellUid(permissionsAdopted.toArray(new String[0]));
293             sBluetoothAdapterLock.unlock();
294         }
295         return false;
296     }
297 
298     /**
299      * Disable the Bluetooth Adapter with then option to persist the off state or not.
300      *
301      * Returns true if the adapter is already disabled or was disabled.
302      */
disableAdapter(BluetoothAdapter bluetoothAdapter, boolean persist, Context context)303     public static boolean disableAdapter(BluetoothAdapter bluetoothAdapter, boolean persist,
304             Context context) {
305         if (!sAdapterVarsInitialized) {
306             initAdapterStateVariables(context);
307         }
308 
309         if (bluetoothAdapter.getState() == BluetoothAdapter.STATE_OFF) {
310             return true;
311         }
312 
313         Set<String> permissionsAdopted = getPermissionsAdoptedAsShellUid();
314         sBluetoothAdapterLock.lock();
315         try {
316             if (DBG) {
317                 Log.d(TAG, "Disabling Bluetooth adapter, persist=" + persist);
318             }
319             adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
320             bluetoothAdapter.disable(persist);
321             return waitForAdapterStateLocked(BluetoothAdapter.STATE_OFF, bluetoothAdapter);
322         } catch (InterruptedException e) {
323             Log.w(TAG, "disableAdapter(persist=" + persist + "): interrupted", e);
324         } finally {
325             adoptPermissionAsShellUid(permissionsAdopted.toArray(new String[0]));
326             sBluetoothAdapterLock.unlock();
327         }
328         return false;
329     }
330 
331     /**
332      * Adopt shell UID's permission via {@link android.app.UiAutomation}
333      * @param permission permission to adopt
334      */
adoptPermissionAsShellUid(String... permission)335     private static void adoptPermissionAsShellUid(String... permission) {
336         InstrumentationRegistry.getInstrumentation().getUiAutomation()
337                 .adoptShellPermissionIdentity(permission);
338     }
339 
340     /**
341      * Gets all the permissions adopted as the shell UID
342      *
343      * @return a {@link java.util.Set} of the adopted shell permissions
344      */
getPermissionsAdoptedAsShellUid()345     private static Set<String> getPermissionsAdoptedAsShellUid() {
346         return InstrumentationRegistry.getInstrumentation().getUiAutomation()
347                 .getAdoptedShellPermissions();
348     }
349 }