• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 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 package com.android.bluetooth;
17 
18 import static com.google.common.truth.Truth.assertThat;
19 import static com.google.common.truth.Truth.assertWithMessage;
20 
21 import static org.mockito.ArgumentMatchers.eq;
22 import static org.mockito.Mockito.*;
23 
24 import android.bluetooth.BluetoothAdapter;
25 import android.bluetooth.BluetoothDevice;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.pm.PackageManager;
29 import android.content.res.Resources;
30 import android.os.Handler;
31 import android.os.Looper;
32 import android.os.MessageQueue;
33 import android.service.media.MediaBrowserService;
34 
35 import androidx.test.InstrumentationRegistry;
36 import androidx.test.rule.ServiceTestRule;
37 import androidx.test.uiautomator.UiDevice;
38 
39 import com.android.bluetooth.avrcpcontroller.BluetoothMediaBrowserService;
40 import com.android.bluetooth.btservice.AdapterService;
41 import com.android.bluetooth.btservice.ProfileService;
42 
43 import org.junit.Assert;
44 import org.mockito.ArgumentCaptor;
45 import org.mockito.internal.util.MockUtil;
46 
47 import java.io.BufferedReader;
48 import java.io.FileReader;
49 import java.io.IOException;
50 import java.lang.reflect.Field;
51 import java.lang.reflect.InvocationTargetException;
52 import java.lang.reflect.Method;
53 import java.util.HashMap;
54 import java.util.concurrent.BlockingQueue;
55 import java.util.concurrent.TimeUnit;
56 import java.util.concurrent.TimeoutException;
57 
58 /**
59  * A set of methods useful in Bluetooth instrumentation tests
60  */
61 public class TestUtils {
62     private static final int SERVICE_TOGGLE_TIMEOUT_MS = 1000;    // 1s
63 
64     /**
65      * Utility method to replace obj.fieldName with newValue where obj is of type c
66      *
67      * @param c type of obj
68      * @param fieldName field name to be replaced
69      * @param obj instance of type c whose fieldName is to be replaced, null for static fields
70      * @param newValue object used to replace fieldName
71      * @return the old value of fieldName that got replaced, caller is responsible for restoring
72      *         it back to obj
73      * @throws NoSuchFieldException when fieldName is not found in type c
74      * @throws IllegalAccessException when fieldName cannot be accessed in type c
75      */
replaceField(final Class c, final String fieldName, final Object obj, final Object newValue)76     public static Object replaceField(final Class c, final String fieldName, final Object obj,
77             final Object newValue) throws NoSuchFieldException, IllegalAccessException {
78         Field field = c.getDeclaredField(fieldName);
79         field.setAccessible(true);
80 
81         Object oldValue = field.get(obj);
82         field.set(obj, newValue);
83         return oldValue;
84     }
85 
86     /**
87      * Set the return value of {@link AdapterService#getAdapterService()} to a test specified value
88      *
89      * @param adapterService the designated {@link AdapterService} in test, must not be null, can
90      * be mocked or spied
91      * @throws NoSuchMethodException when setAdapterService method is not found
92      * @throws IllegalAccessException when setAdapterService method cannot be accessed
93      * @throws InvocationTargetException when setAdapterService method cannot be invoked, which
94      * should never happen since setAdapterService is a static method
95      */
setAdapterService(AdapterService adapterService)96     public static void setAdapterService(AdapterService adapterService)
97             throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
98         Assert.assertNull("AdapterService.getAdapterService() must be null before setting another"
99                 + " AdapterService", AdapterService.getAdapterService());
100         Assert.assertNotNull("Adapter service should not be null", adapterService);
101         // We cannot mock AdapterService.getAdapterService() with Mockito.
102         // Hence we need to use reflection to call a private method to
103         // initialize properly the AdapterService.sAdapterService field.
104         Method method =
105                 AdapterService.class.getDeclaredMethod("setAdapterService", AdapterService.class);
106         method.setAccessible(true);
107         method.invoke(null, adapterService);
108     }
109 
110     /**
111      * Clear the return value of {@link AdapterService#getAdapterService()} to null
112      *
113      * @param adapterService the {@link AdapterService} used when calling
114      * {@link TestUtils#setAdapterService(AdapterService)}
115      * @throws NoSuchMethodException when clearAdapterService method is not found
116      * @throws IllegalAccessException when clearAdapterService method cannot be accessed
117      * @throws InvocationTargetException when clearAdappterService method cannot be invoked,
118      * which should never happen since clearAdapterService is a static method
119      */
clearAdapterService(AdapterService adapterService)120     public static void clearAdapterService(AdapterService adapterService)
121             throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
122         Assert.assertSame("AdapterService.getAdapterService() must return the same object as the"
123                         + " supplied adapterService in this method", adapterService,
124                 AdapterService.getAdapterService());
125         Assert.assertNotNull("Adapter service should not be null", adapterService);
126         Method method =
127                 AdapterService.class.getDeclaredMethod("clearAdapterService", AdapterService.class);
128         method.setAccessible(true);
129         method.invoke(null, adapterService);
130     }
131 
132     /**
133      * Start a profile service using the given {@link ServiceTestRule} and verify through
134      * {@link AdapterService#getAdapterService()} that the service is actually started within
135      * {@link TestUtils#SERVICE_TOGGLE_TIMEOUT_MS} milliseconds.
136      * {@link #setAdapterService(AdapterService)} must be called with a mocked
137      * {@link AdapterService} before calling this method
138      *
139      * @param serviceTestRule the {@link ServiceTestRule} used to execute the service start request
140      * @param profileServiceClass a class from one of {@link ProfileService}'s child classes
141      * @throws TimeoutException when service failed to start within either default timeout of
142      * {@link ServiceTestRule#DEFAULT_TIMEOUT} (normally 5s) or user specified time when creating
143      * {@link ServiceTestRule} through {@link ServiceTestRule#withTimeout(long, TimeUnit)} method
144      */
startService(ServiceTestRule serviceTestRule, Class<T> profileServiceClass)145     public static <T extends ProfileService> void startService(ServiceTestRule serviceTestRule,
146             Class<T> profileServiceClass) throws TimeoutException {
147         AdapterService adapterService = AdapterService.getAdapterService();
148         Assert.assertNotNull("Adapter service should not be null", adapterService);
149         Assert.assertTrue("AdapterService.getAdapterService() must return a mocked or spied object"
150                 + " before calling this method", MockUtil.isMock(adapterService));
151         Intent startIntent =
152                 new Intent(InstrumentationRegistry.getTargetContext(), profileServiceClass);
153         startIntent.putExtra(AdapterService.EXTRA_ACTION,
154                 AdapterService.ACTION_SERVICE_STATE_CHANGED);
155         startIntent.putExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_ON);
156         serviceTestRule.startService(startIntent);
157         ArgumentCaptor<ProfileService> profile = ArgumentCaptor.forClass(profileServiceClass);
158         verify(adapterService, timeout(SERVICE_TOGGLE_TIMEOUT_MS)).onProfileServiceStateChanged(
159                 profile.capture(), eq(BluetoothAdapter.STATE_ON));
160         Assert.assertEquals(profileServiceClass.getName(), profile.getValue().getClass().getName());
161     }
162 
163     /**
164      * Stop a profile service using the given {@link ServiceTestRule} and verify through
165      * {@link AdapterService#getAdapterService()} that the service is actually stopped within
166      * {@link TestUtils#SERVICE_TOGGLE_TIMEOUT_MS} milliseconds.
167      * {@link #setAdapterService(AdapterService)} must be called with a mocked
168      * {@link AdapterService} before calling this method
169      *
170      * @param serviceTestRule the {@link ServiceTestRule} used to execute the service start request
171      * @param profileServiceClass a class from one of {@link ProfileService}'s child classes
172      * @throws TimeoutException when service failed to start within either default timeout of
173      * {@link ServiceTestRule#DEFAULT_TIMEOUT} (normally 5s) or user specified time when creating
174      * {@link ServiceTestRule} through {@link ServiceTestRule#withTimeout(long, TimeUnit)} method
175      */
stopService(ServiceTestRule serviceTestRule, Class<T> profileServiceClass)176     public static <T extends ProfileService> void stopService(ServiceTestRule serviceTestRule,
177             Class<T> profileServiceClass) throws TimeoutException {
178         AdapterService adapterService = AdapterService.getAdapterService();
179         Assert.assertNotNull(adapterService);
180         Assert.assertTrue("AdapterService.getAdapterService() must return a mocked or spied object"
181                 + " before calling this method", MockUtil.isMock(adapterService));
182         Intent stopIntent =
183                 new Intent(InstrumentationRegistry.getTargetContext(), profileServiceClass);
184         stopIntent.putExtra(AdapterService.EXTRA_ACTION,
185                 AdapterService.ACTION_SERVICE_STATE_CHANGED);
186         stopIntent.putExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF);
187         serviceTestRule.startService(stopIntent);
188         ArgumentCaptor<ProfileService> profile = ArgumentCaptor.forClass(profileServiceClass);
189         verify(adapterService, timeout(SERVICE_TOGGLE_TIMEOUT_MS)).onProfileServiceStateChanged(
190                 profile.capture(), eq(BluetoothAdapter.STATE_OFF));
191         Assert.assertEquals(profileServiceClass.getName(), profile.getValue().getClass().getName());
192         ArgumentCaptor<ProfileService> profile2 = ArgumentCaptor.forClass(profileServiceClass);
193         verify(adapterService, timeout(SERVICE_TOGGLE_TIMEOUT_MS)).removeProfile(profile2.capture());
194         Assert.assertEquals(profileServiceClass.getName(), profile2.getValue().getClass().getName());
195     }
196 
197     /**
198      * Create a test device.
199      *
200      * @param bluetoothAdapter the Bluetooth adapter to use
201      * @param id the test device ID. It must be an integer in the interval [0, 0xFF].
202      * @return {@link BluetoothDevice} test device for the device ID
203      */
getTestDevice(BluetoothAdapter bluetoothAdapter, int id)204     public static BluetoothDevice getTestDevice(BluetoothAdapter bluetoothAdapter, int id) {
205         Assert.assertTrue(id <= 0xFF);
206         Assert.assertNotNull(bluetoothAdapter);
207         BluetoothDevice testDevice =
208                 bluetoothAdapter.getRemoteDevice(String.format("00:01:02:03:04:%02X", id));
209         Assert.assertNotNull(testDevice);
210         return testDevice;
211     }
212 
getTestApplicationResources(Context context)213     public static Resources getTestApplicationResources(Context context) {
214         try {
215             return context.getPackageManager().getResourcesForApplication("com.android.bluetooth.tests");
216         } catch (PackageManager.NameNotFoundException e) {
217             assertWithMessage("Setup Failure: Unable to get test application resources"
218                     + e.toString()).fail();
219             return null;
220         }
221     }
222 
223     /**
224      * Wait and verify that an intent has been received.
225      *
226      * @param timeoutMs the time (in milliseconds) to wait for the intent
227      * @param queue the queue for the intent
228      * @return the received intent
229      */
waitForIntent(int timeoutMs, BlockingQueue<Intent> queue)230     public static Intent waitForIntent(int timeoutMs, BlockingQueue<Intent> queue) {
231         try {
232             Intent intent = queue.poll(timeoutMs, TimeUnit.MILLISECONDS);
233             Assert.assertNotNull(intent);
234             return intent;
235         } catch (InterruptedException e) {
236             Assert.fail("Cannot obtain an Intent from the queue: " + e.getMessage());
237         }
238         return null;
239     }
240 
241     /**
242      * Wait and verify that no intent has been received.
243      *
244      * @param timeoutMs the time (in milliseconds) to wait and verify no intent
245      * has been received
246      * @param queue the queue for the intent
247      * @return the received intent. Should be null under normal circumstances
248      */
waitForNoIntent(int timeoutMs, BlockingQueue<Intent> queue)249     public static Intent waitForNoIntent(int timeoutMs, BlockingQueue<Intent> queue) {
250         try {
251             Intent intent = queue.poll(timeoutMs, TimeUnit.MILLISECONDS);
252             Assert.assertNull(intent);
253             return intent;
254         } catch (InterruptedException e) {
255             Assert.fail("Cannot obtain an Intent from the queue: " + e.getMessage());
256         }
257         return null;
258     }
259 
260     /**
261      * Wait for looper to finish its current task and all tasks schedule before this
262      *
263      * @param looper looper of interest
264      */
waitForLooperToFinishScheduledTask(Looper looper)265     public static void waitForLooperToFinishScheduledTask(Looper looper) {
266         runOnLooperSync(looper, () -> {
267             // do nothing, just need to make sure looper finishes current task
268         });
269     }
270 
271     /**
272      * Wait for looper to become idle
273      *
274      * @param looper looper of interest
275      */
waitForLooperToBeIdle(Looper looper)276     public static void waitForLooperToBeIdle(Looper looper) {
277         class Idler implements MessageQueue.IdleHandler {
278             private boolean mIdle = false;
279 
280             @Override
281             public boolean queueIdle() {
282                 synchronized (this) {
283                     mIdle = true;
284                     notifyAll();
285                 }
286                 return false;
287             }
288 
289             public synchronized void waitForIdle() {
290                 while (!mIdle) {
291                     try {
292                         wait();
293                     } catch (InterruptedException e) {
294                     }
295                 }
296             }
297         }
298 
299         Idler idle = new Idler();
300         looper.getQueue().addIdleHandler(idle);
301         // Ensure we are not Idle to begin with so the idle handler will run
302         waitForLooperToFinishScheduledTask(looper);
303         idle.waitForIdle();
304     }
305 
306     /**
307      * Run synchronously a runnable action on a looper.
308      * The method will return after the action has been execution to completion.
309      *
310      * Example:
311      * <pre>
312      * {@code
313      * TestUtils.runOnMainSync(new Runnable() {
314      *       public void run() {
315      *           Assert.assertTrue(mA2dpService.stop());
316      *       }
317      *   });
318      * }
319      * </pre>
320      *
321      * @param looper the looper used to run the action
322      * @param action the action to run
323      */
runOnLooperSync(Looper looper, Runnable action)324     public static void runOnLooperSync(Looper looper, Runnable action) {
325         if (Looper.myLooper() == looper) {
326             // requested thread is the same as the current thread. call directly.
327             action.run();
328         } else {
329             Handler handler = new Handler(looper);
330             SyncRunnable sr = new SyncRunnable(action);
331             handler.post(sr);
332             sr.waitForComplete();
333         }
334     }
335 
336     /**
337      * Read Bluetooth adapter configuration from the filesystem
338      *
339      * @return A {@link HashMap} of Bluetooth configs in the format:
340      *  section -> key1 -> value1
341      *          -> key2 -> value2
342      *  Assume no empty section name, no duplicate keys in the same section
343      */
readAdapterConfig()344     public static HashMap<String, HashMap<String, String>> readAdapterConfig() {
345         HashMap<String, HashMap<String, String>> adapterConfig = new HashMap<>();
346         try (BufferedReader reader =
347                 new BufferedReader(new FileReader("/data/misc/bluedroid/bt_config.conf"))) {
348             String section = "";
349             for (String line; (line = reader.readLine()) != null;) {
350                 line = line.trim();
351                 if (line.isEmpty() || line.startsWith("#")) {
352                     continue;
353                 }
354                 if (line.startsWith("[")) {
355                     if (line.charAt(line.length() - 1) != ']') {
356                         return null;
357                     }
358                     section = line.substring(1, line.length() - 1);
359                     adapterConfig.put(section, new HashMap<>());
360                 } else {
361                     String[] keyValue = line.split("=");
362                     adapterConfig.get(section).put(keyValue[0].trim(),
363                             keyValue.length == 1 ? "" : keyValue[1].trim());
364                 }
365             }
366         } catch (IOException e) {
367             return null;
368         }
369         return adapterConfig;
370     }
371 
372     /**
373      * Prepare the intent to start bluetooth browser media service.
374      *
375      * @return intent with the appropriate component & action set.
376      */
prepareIntentToStartBluetoothBrowserMediaService()377     public static Intent prepareIntentToStartBluetoothBrowserMediaService() {
378         final Intent intent = new Intent(InstrumentationRegistry.getTargetContext(),
379                 BluetoothMediaBrowserService.class);
380         intent.setAction(MediaBrowserService.SERVICE_INTERFACE);
381         return intent;
382     }
383 
wakeUpAndDismissKeyGuard()384     public static void wakeUpAndDismissKeyGuard() throws Exception {
385         final UiDevice device = UiDevice.getInstance(
386                 androidx.test.platform.app.InstrumentationRegistry.getInstrumentation());
387         device.wakeUp();
388         device.executeShellCommand("wm dismiss-keyguard");
389     }
390 
391     /**
392      * Helper class used to run synchronously a runnable action on a looper.
393      */
394     private static final class SyncRunnable implements Runnable {
395         private final Runnable mTarget;
396         private volatile boolean mComplete = false;
397 
SyncRunnable(Runnable target)398         SyncRunnable(Runnable target) {
399             mTarget = target;
400         }
401 
402         @Override
run()403         public void run() {
404             mTarget.run();
405             synchronized (this) {
406                 mComplete = true;
407                 notifyAll();
408             }
409         }
410 
waitForComplete()411         public void waitForComplete() {
412             synchronized (this) {
413                 while (!mComplete) {
414                     try {
415                         wait();
416                     } catch (InterruptedException e) {
417                     }
418                 }
419             }
420         }
421     }
422 }
423