• 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 org.junit.Assert.assertNotNull;
20 import static org.junit.Assert.fail;
21 
22 import android.bluetooth.BluetoothAdapter;
23 import android.bluetooth.BluetoothManager;
24 import android.bluetooth.BluetoothProfile;
25 import android.bluetooth.le.ScanRecord;
26 import android.content.Context;
27 import android.content.pm.PackageManager;
28 import android.provider.Settings;
29 import android.sysprop.BluetoothProperties;
30 import android.util.Log;
31 
32 import androidx.annotation.NonNull;
33 import androidx.annotation.Nullable;
34 import androidx.test.platform.app.InstrumentationRegistry;
35 
36 import java.lang.reflect.InvocationTargetException;
37 import java.lang.reflect.Method;
38 import java.util.Arrays;
39 import java.util.Set;
40 import java.util.concurrent.TimeUnit;
41 import java.util.concurrent.locks.Condition;
42 import java.util.concurrent.locks.ReentrantLock;
43 
44 /**
45  * Utility class for Bluetooth CTS test.
46  */
47 public class TestUtils {
48     /**
49      * Checks whether this device has Bluetooth feature
50      * @return true if this device has Bluetooth feature
51      */
hasBluetooth()52     public static boolean hasBluetooth() {
53         Context context = InstrumentationRegistry.getInstrumentation().getContext();
54         return context.getPackageManager().hasSystemFeature(
55                 PackageManager.FEATURE_BLUETOOTH);
56     }
57 
58     /**
59      * Get the current enabled status of a given profile
60      */
isProfileEnabled(int profile)61     public static boolean isProfileEnabled(int profile) {
62         switch (profile) {
63             case BluetoothProfile.A2DP:
64                 return BluetoothProperties.isProfileA2dpSourceEnabled().orElse(false);
65             case BluetoothProfile.A2DP_SINK:
66                 return BluetoothProperties.isProfileA2dpSinkEnabled().orElse(false);
67             // Hidden profile
68             // case BluetoothProfile.AVRCP:
69             //     return BluetoothProperties.isProfileAvrcpTargetEnabled().orElse(false);
70             case BluetoothProfile.AVRCP_CONTROLLER:
71                 return BluetoothProperties.isProfileAvrcpControllerEnabled().orElse(false);
72             case BluetoothProfile.CSIP_SET_COORDINATOR:
73                 return BluetoothProperties.isProfileCsipSetCoordinatorEnabled().orElse(false);
74             case BluetoothProfile.GATT:
75                 return BluetoothProperties.isProfileGattEnabled().orElse(true);
76             case BluetoothProfile.HAP_CLIENT:
77                 return BluetoothProperties.isProfileHapClientEnabled().orElse(false);
78             case BluetoothProfile.HEADSET:
79                 return BluetoothProperties.isProfileHfpAgEnabled().orElse(false);
80             case BluetoothProfile.HEADSET_CLIENT:
81                 return BluetoothProperties.isProfileHfpHfEnabled().orElse(false);
82             case BluetoothProfile.HEARING_AID:
83                 Context context = InstrumentationRegistry.getInstrumentation().getContext();
84                 if (!isBleSupported(context)) {
85                     return false;
86                 }
87                 boolean default_value = true;
88                 if (isAutomotive(context) || isWatch(context) || isTv(context)) {
89                     default_value = false;
90                 }
91                 return BluetoothProperties.isProfileAshaCentralEnabled().orElse(default_value);
92             case BluetoothProfile.HID_DEVICE:
93                 return BluetoothProperties.isProfileHidDeviceEnabled().orElse(false);
94             case BluetoothProfile.HID_HOST:
95                 return BluetoothProperties.isProfileHidHostEnabled().orElse(false);
96             case BluetoothProfile.LE_AUDIO:
97                 return BluetoothProperties.isProfileBapUnicastClientEnabled().orElse(false);
98             case BluetoothProfile.LE_AUDIO_BROADCAST:
99                 return BluetoothProperties.isProfileBapBroadcastSourceEnabled().orElse(false);
100             case BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT:
101                 return BluetoothProperties.isProfileBapBroadcastAssistEnabled().orElse(false);
102             // Hidden profile
103             // case BluetoothProfile.LE_CALL_CONTROL:
104             //     return BluetoothProperties.isProfileCcpServerEnabled().orElse(false);
105             case BluetoothProfile.MAP:
106                 return BluetoothProperties.isProfileMapServerEnabled().orElse(false);
107             case BluetoothProfile.MAP_CLIENT:
108                 return BluetoothProperties.isProfileMapClientEnabled().orElse(false);
109             // Hidden profile
110             // case BluetoothProfile.MCP_SERVER:
111             //     return BluetoothProperties.isProfileMcpServerEnabled().orElse(false);
112             case BluetoothProfile.OPP:
113                 return BluetoothProperties.isProfileOppEnabled().orElse(false);
114             case BluetoothProfile.PAN:
115                 return BluetoothProperties.isProfilePanNapEnabled().orElse(false)
116                         || BluetoothProperties.isProfilePanPanuEnabled().orElse(false);
117             case BluetoothProfile.PBAP:
118                 return BluetoothProperties.isProfilePbapServerEnabled().orElse(false);
119             case BluetoothProfile.PBAP_CLIENT:
120                 return BluetoothProperties.isProfilePbapClientEnabled().orElse(false);
121             case BluetoothProfile.SAP:
122                 return BluetoothProperties.isProfileSapServerEnabled().orElse(false);
123             case BluetoothProfile.VOLUME_CONTROL:
124                 return BluetoothProperties.isProfileVcpControllerEnabled().orElse(false);
125             default:
126                 return false;
127         }
128     }
129 
130     /**
131      * Adopt shell UID's permission via {@link android.app.UiAutomation}
132      * @param permission permission to adopt
133      */
adoptPermissionAsShellUid(@ullable String... permission)134     public static void adoptPermissionAsShellUid(@Nullable String... permission) {
135         InstrumentationRegistry.getInstrumentation().getUiAutomation()
136                 .adoptShellPermissionIdentity(permission);
137     }
138 
139     /**
140      * Drop all permissions adopted as shell UID
141      */
dropPermissionAsShellUid()142     public static void dropPermissionAsShellUid() {
143         InstrumentationRegistry.getInstrumentation().getUiAutomation()
144                 .dropShellPermissionIdentity();
145     }
146 
147     /**
148      * @return permissions adopted from Shell
149      */
getAdoptedShellPermissions()150     public static Set<String> getAdoptedShellPermissions() {
151         return InstrumentationRegistry.getInstrumentation().getUiAutomation()
152                 .getAdoptedShellPermissions();
153     }
154 
155     /**
156      * Get {@link BluetoothAdapter} via {@link android.bluetooth.BluetoothManager}
157      * Fail the test if {@link BluetoothAdapter} is null
158      * @return instance of {@link BluetoothAdapter}
159      */
getBluetoothAdapterOrDie()160     @NonNull public static BluetoothAdapter getBluetoothAdapterOrDie() {
161         Context context = InstrumentationRegistry.getInstrumentation().getContext();
162         BluetoothManager manager = context.getSystemService(BluetoothManager.class);
163         assertNotNull(manager);
164         BluetoothAdapter adapter = manager.getAdapter();
165         assertNotNull(adapter);
166         return adapter;
167     }
168 
169     /**
170      * Utility method to call hidden ScanRecord.parseFromBytes method.
171      */
parseScanRecord(byte[] bytes)172     public static ScanRecord parseScanRecord(byte[] bytes) {
173         Class<?> scanRecordClass = ScanRecord.class;
174         try {
175             Method method = scanRecordClass.getDeclaredMethod("parseFromBytes", byte[].class);
176             return (ScanRecord) method.invoke(null, bytes);
177         } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException
178                 | InvocationTargetException e) {
179             return null;
180         }
181     }
182 
183     /**
184      * Assert two byte arrays are equal.
185      */
assertArrayEquals(byte[] expected, byte[] actual)186     public static void assertArrayEquals(byte[] expected, byte[] actual) {
187         if (!Arrays.equals(expected, actual)) {
188             fail("expected:<" + Arrays.toString(expected)
189                     + "> but was:<" + Arrays.toString(actual) + ">");
190         }
191     }
192 
193     /**
194      * Get current location mode settings.
195      */
getLocationMode(Context context)196     public static int getLocationMode(Context context) {
197         return Settings.Secure.getInt(context.getContentResolver(),
198                 Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF);
199     }
200 
201     /**
202      * Set location settings mode.
203      */
setLocationMode(Context context, int mode)204     public static void setLocationMode(Context context, int mode) {
205         Settings.Secure.putInt(context.getContentResolver(), Settings.Secure.LOCATION_MODE,
206                 mode);
207     }
208 
209     /**
210      * Return true if location is on.
211      */
isLocationOn(Context context)212     public static boolean isLocationOn(Context context) {
213         return getLocationMode(context) != Settings.Secure.LOCATION_MODE_OFF;
214     }
215 
216     /**
217      * Enable location and set the mode to GPS only.
218      */
enableLocation(Context context)219     public static void enableLocation(Context context) {
220         setLocationMode(context, Settings.Secure.LOCATION_MODE_SENSORS_ONLY);
221     }
222 
223     /**
224      * Disable location.
225      */
disableLocation(Context context)226     public static void disableLocation(Context context) {
227         setLocationMode(context, Settings.Secure.LOCATION_MODE_OFF);
228     }
229 
230     /**
231      * Check if BLE is supported by this platform
232      * @param context current device context
233      * @return true if BLE is supported, false otherwise
234      */
isBleSupported(Context context)235     public static boolean isBleSupported(Context context) {
236         return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
237     }
238 
239     /**
240      * Check if this is an automotive device
241      * @param context current device context
242      * @return true if this Android device is an automotive device, false otherwise
243      */
isAutomotive(Context context)244     public static boolean isAutomotive(Context context) {
245         return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
246     }
247 
248     /**
249      * Check if this is a watch device
250      * @param context current device context
251      * @return true if this Android device is a watch device, false otherwise
252      */
isWatch(Context context)253     public static boolean isWatch(Context context) {
254         return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
255     }
256 
257     /**
258      * Check if this is a TV device
259      * @param context current device context
260      * @return true if this Android device is a TV device, false otherwise
261      */
isTv(Context context)262     public static boolean isTv(Context context) {
263         PackageManager pm = context.getPackageManager();
264         return pm.hasSystemFeature(PackageManager.FEATURE_TELEVISION)
265                 || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
266     }
267 
268     /**
269      * Put the current thread to sleep.
270      * @param sleepMillis number of milliseconds to sleep for
271      */
sleep(int sleepMillis)272     public static void sleep(int sleepMillis) {
273         try {
274             Thread.sleep(sleepMillis);
275         } catch (InterruptedException e) {
276             Log.e(TestUtils.class.getSimpleName(), "interrupted", e);
277         }
278     }
279 
280     /**
281      * Boilerplate class for profile listener
282      */
283     public static class BluetoothCtsServiceConnector {
284         private static final int PROXY_CONNECTION_TIMEOUT_MS = 500;  // ms timeout for Proxy Connect
285         private BluetoothProfile mProfileProxy = null;
286         private boolean mIsProfileReady = false;
287         private boolean mIsProfileConnecting = false;
288         private final Condition mConditionProfileConnection;
289         private final ReentrantLock mProfileConnectionLock;
290         private final String mLogTag;
291         private final int mProfileId;
292         private final BluetoothAdapter mAdapter;
293         private final Context mContext;
BluetoothCtsServiceConnector(String logTag, int profileId, BluetoothAdapter adapter, Context context)294         public BluetoothCtsServiceConnector(String logTag, int profileId, BluetoothAdapter adapter,
295                 Context context) {
296             mLogTag = logTag;
297             mProfileId = profileId;
298             mAdapter = adapter;
299             mContext = context;
300             mProfileConnectionLock = new ReentrantLock();
301             mConditionProfileConnection = mProfileConnectionLock.newCondition();
302             assertNotNull(mLogTag);
303             assertNotNull(mAdapter);
304             assertNotNull(mContext);
305         }
306 
getProfileProxy()307         public BluetoothProfile getProfileProxy() {
308             return mProfileProxy;
309         }
310 
closeProfileProxy()311         public void closeProfileProxy() {
312             if (mProfileProxy != null) {
313                 mAdapter.closeProfileProxy(mProfileId, mProfileProxy);
314                 mProfileProxy = null;
315                 mIsProfileReady = false;
316             }
317         }
318 
openProfileProxyAsync()319         public boolean openProfileProxyAsync() {
320             mIsProfileConnecting = mAdapter.getProfileProxy(mContext, mServiceListener, mProfileId);
321             return mIsProfileConnecting;
322         }
323 
waitForProfileConnect()324         public boolean waitForProfileConnect() {
325             return waitForProfileConnect(PROXY_CONNECTION_TIMEOUT_MS);
326         }
327 
waitForProfileConnect(int timeoutMs)328         public boolean waitForProfileConnect(int timeoutMs) {
329             if (!mIsProfileConnecting) {
330                 mIsProfileConnecting =
331                         mAdapter.getProfileProxy(mContext, mServiceListener, mProfileId);
332             }
333             if (!mIsProfileConnecting) {
334                 return false;
335             }
336             mProfileConnectionLock.lock();
337             try {
338                 // Wait for the Adapter to be disabled
339                 while (!mIsProfileReady) {
340                     if (!mConditionProfileConnection.await(timeoutMs, TimeUnit.MILLISECONDS)) {
341                         // Timeout
342                         Log.e(mLogTag, "Timeout while waiting for Profile Connect");
343                         break;
344                     } // else spurious wake-ups
345                 }
346             } catch (InterruptedException e) {
347                 Log.e(mLogTag, "waitForProfileConnect: interrupted");
348             } finally {
349                 mProfileConnectionLock.unlock();
350             }
351             mIsProfileConnecting = false;
352             return mIsProfileReady;
353         }
354 
355         private final BluetoothProfile.ServiceListener mServiceListener =
356                 new BluetoothProfile.ServiceListener() {
357             @Override
358             public void onServiceConnected(int profile, BluetoothProfile proxy) {
359                 mProfileConnectionLock.lock();
360                 mProfileProxy = proxy;
361                 mIsProfileReady = true;
362                 try {
363                     mConditionProfileConnection.signal();
364                 } finally {
365                     mProfileConnectionLock.unlock();
366                 }
367             }
368 
369             @Override
370             public void onServiceDisconnected(int profile) {
371                 mProfileConnectionLock.lock();
372                 mIsProfileReady = false;
373                 try {
374                     mConditionProfileConnection.signal();
375                 } finally {
376                     mProfileConnectionLock.unlock();
377                 }
378             }
379         };
380     }
381 }
382