1 /*
2  * Copyright (C) 2022 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.cts.verifier.bluetooth;
18 
19 import static android.content.Context.RECEIVER_EXPORTED;
20 
21 import static com.android.compatibility.common.util.ShellIdentityUtils.invokeWithShellPermissions;
22 
23 import android.bluetooth.BluetoothAdapter;
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.os.Handler;
29 import android.os.HandlerThread;
30 import android.os.Looper;
31 import android.util.Log;
32 
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  * Code mostly copied from android.bluetooth.cts.BTAdapterUtils class.
41  */
42 public class BtAdapterUtils {
43     private static final String TAG = "BtAdapterUtils";
44 
45     // ADAPTER_ENABLE_TIMEOUT_MS = AdapterState.BLE_START_TIMEOUT_DELAY +
46     //                              AdapterState.BREDR_START_TIMEOUT_DELAY
47     private static final int ADAPTER_ENABLE_TIMEOUT_MS = 8000;
48     // ADAPTER_DISABLE_TIMEOUT_MS = AdapterState.BLE_STOP_TIMEOUT_DELAY +
49     //                                  AdapterState.BREDR_STOP_TIMEOUT_DELAY
50     private static final int ADAPTER_DISABLE_TIMEOUT_MS = 5000;
51 
52     private static BroadcastReceiver sAdapterIntentReceiver;
53 
54     private static Condition sConditionAdapterIsEnabled;
55     private static ReentrantLock sAdapterStateEnablingLock;
56 
57     private static Condition sConditionAdapterIsDisabled;
58     private static ReentrantLock sAdapterStateDisablingLock;
59     private static boolean sAdapterVarsInitialized;
60 
61     private static HandlerThread sHandlerThread;
62     private static Looper sLooper;
63     private static Handler sHandler;
64 
65     private static class AdapterIntentReceiver extends BroadcastReceiver {
66         @Override
onReceive(Context context, Intent intent)67         public void onReceive(Context context, Intent intent) {
68             if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) {
69                 int previousState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, -1);
70                 int newState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
71                 Log.d(TAG, "Previous state: " + previousState + " New state: " + newState);
72 
73                 if (newState == BluetoothAdapter.STATE_ON) {
74                     sAdapterStateEnablingLock.lock();
75                     try {
76                         Log.d(TAG, "Signaling to mConditionAdapterIsEnabled");
77                         sConditionAdapterIsEnabled.signal();
78                     } finally {
79                         sAdapterStateEnablingLock.unlock();
80                     }
81                 } else if (newState == BluetoothAdapter.STATE_OFF) {
82                     sAdapterStateDisablingLock.lock();
83                     try {
84                         Log.d(TAG, "Signaling to mConditionAdapterIsDisabled");
85                         sConditionAdapterIsDisabled.signal();
86                     } finally {
87                         sAdapterStateDisablingLock.unlock();
88                     }
89                 }
90             }
91         }
92     }
93 
94     /** Enables the Bluetooth Adapter. Return true if it is already enabled or is enabled. */
enableAdapter(BluetoothAdapter bluetoothAdapter, Context context)95     public static boolean enableAdapter(BluetoothAdapter bluetoothAdapter, Context context) {
96         if (!sAdapterVarsInitialized) {
97             initAdapterStateVariables(context);
98         }
99         registerIntentReceiver(context);
100 
101         if (bluetoothAdapter.isEnabled()) return true;
102 
103         invokeWithShellPermissions(() -> bluetoothAdapter.enable());
104         sAdapterStateEnablingLock.lock();
105         try {
106             // Wait for the Adapter to be enabled
107             while (!bluetoothAdapter.isEnabled()) {
108                 if (!sConditionAdapterIsEnabled.await(
109                         ADAPTER_ENABLE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
110                     // Timeout
111                     Log.e(TAG, "Timeout while waiting for the Bluetooth Adapter enable");
112                     break;
113                 } // else spurious wakeups
114             }
115         } catch (InterruptedException e) {
116             Log.e(TAG, "enableAdapter: interrupted");
117         } finally {
118             sAdapterStateEnablingLock.unlock();
119         }
120         return bluetoothAdapter.isEnabled();
121     }
122 
123     /** Disable the Bluetooth Adapter. Return true if it is already disabled or is disabled. */
disableAdapter(BluetoothAdapter bluetoothAdapter, Context context)124     public static boolean disableAdapter(BluetoothAdapter bluetoothAdapter, Context context) {
125         if (!sAdapterVarsInitialized) {
126             initAdapterStateVariables(context);
127         }
128         registerIntentReceiver(context);
129 
130         if (bluetoothAdapter.getState() == BluetoothAdapter.STATE_OFF) return true;
131 
132         invokeWithShellPermissions(() -> bluetoothAdapter.disable());
133         sAdapterStateDisablingLock.lock();
134         try {
135             // Wait for the Adapter to be disabled
136             while (bluetoothAdapter.getState() != BluetoothAdapter.STATE_OFF) {
137                 if (!sConditionAdapterIsDisabled.await(
138                         ADAPTER_DISABLE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
139                     // Timeout
140                     Log.e(TAG, "Timeout while waiting for the Bluetooth Adapter disable");
141                     break;
142                 } // else spurious wakeups
143             }
144         } catch (InterruptedException e) {
145             Log.e(TAG, "disableAdapter: interrupted");
146         } finally {
147             sAdapterStateDisablingLock.unlock();
148         }
149         return bluetoothAdapter.getState() == BluetoothAdapter.STATE_OFF;
150     }
151 
registerIntentReceiver(Context context)152     private static void registerIntentReceiver(Context context) {
153         sAdapterIntentReceiver = new AdapterIntentReceiver();
154         IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
155         context.registerReceiver(sAdapterIntentReceiver, filter, RECEIVER_EXPORTED);
156     }
157 
158     // Initialize variables required for TestUtils#enableAdapter and TestUtils#disableAdapter
initAdapterStateVariables(Context context)159     private static void initAdapterStateVariables(Context context) {
160         sAdapterStateEnablingLock = new ReentrantLock();
161         sConditionAdapterIsEnabled = sAdapterStateEnablingLock.newCondition();
162         sAdapterStateDisablingLock = new ReentrantLock();
163         sConditionAdapterIsDisabled = sAdapterStateDisablingLock.newCondition();
164 
165         sAdapterVarsInitialized = true;
166     }
167 }
168