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