1 /*
2  * Copyright 2017 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.bluetooth.hid;
18 
19 import static org.mockito.Mockito.*;
20 
21 import android.bluetooth.BluetoothAdapter;
22 import android.bluetooth.BluetoothDevice;
23 import android.bluetooth.BluetoothHidDevice;
24 import android.bluetooth.BluetoothHidDeviceAppSdpSettings;
25 import android.bluetooth.BluetoothProfile;
26 import android.bluetooth.IBluetoothHidDeviceCallback;
27 import android.content.BroadcastReceiver;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.os.Looper;
32 
33 import androidx.test.InstrumentationRegistry;
34 import androidx.test.filters.MediumTest;
35 import androidx.test.rule.ServiceTestRule;
36 import androidx.test.runner.AndroidJUnit4;
37 
38 import com.android.bluetooth.R;
39 import com.android.bluetooth.TestUtils;
40 import com.android.bluetooth.btservice.AdapterService;
41 import com.android.bluetooth.btservice.storage.DatabaseManager;
42 
43 import org.junit.After;
44 import org.junit.Assert;
45 import org.junit.Assume;
46 import org.junit.Before;
47 import org.junit.Rule;
48 import org.junit.Test;
49 import org.junit.runner.RunWith;
50 import org.mockito.Mock;
51 import org.mockito.MockitoAnnotations;
52 
53 import java.lang.reflect.Field;
54 import java.lang.reflect.Method;
55 import java.util.concurrent.BlockingQueue;
56 import java.util.concurrent.LinkedBlockingQueue;
57 import java.util.concurrent.TimeUnit;
58 
59 @MediumTest
60 @RunWith(AndroidJUnit4.class)
61 public class HidDeviceTest {
62     private static final int TIMEOUT_MS = 1000;    // 1s
63     private static final byte[] SAMPLE_HID_REPORT = new byte[]{0x01, 0x00, 0x02};
64     private static final byte SAMPLE_REPORT_ID = 0x00;
65     private static final byte SAMPLE_REPORT_TYPE = 0x00;
66     private static final byte SAMPLE_REPORT_ERROR = 0x02;
67     private static final byte SAMPLE_BUFFER_SIZE = 100;
68 
69     private static final int CALLBACK_APP_REGISTERED = 0;
70     private static final int CALLBACK_APP_UNREGISTERED = 1;
71     private static final int CALLBACK_ON_GET_REPORT = 2;
72     private static final int CALLBACK_ON_SET_REPORT = 3;
73     private static final int CALLBACK_ON_SET_PROTOCOL = 4;
74     private static final int CALLBACK_ON_INTR_DATA = 5;
75     private static final int CALLBACK_ON_VIRTUAL_UNPLUG = 6;
76 
77     @Mock private AdapterService mAdapterService;
78     @Mock private DatabaseManager mDatabaseManager;
79     @Mock private HidDeviceNativeInterface mHidDeviceNativeInterface;
80 
81     private BluetoothAdapter mAdapter;
82     private BluetoothDevice mTestDevice;
83     private HidDeviceService mHidDeviceService;
84     private Context mTargetContext;
85     private BluetoothHidDeviceAppSdpSettings mSettings;
86     private BroadcastReceiver mConnectionStateChangedReceiver;
87     private final BlockingQueue<Intent> mConnectionStateChangedQueue = new LinkedBlockingQueue<>();
88     private final BlockingQueue<Integer> mCallbackQueue = new LinkedBlockingQueue<>();
89 
90     @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
91 
setHidDeviceNativeInterfaceInstance(HidDeviceNativeInterface instance)92     private static void setHidDeviceNativeInterfaceInstance(HidDeviceNativeInterface instance)
93             throws Exception {
94         Method method = HidDeviceNativeInterface.class.getDeclaredMethod("setInstance",
95                 HidDeviceNativeInterface.class);
96         method.setAccessible(true);
97         method.invoke(null, instance);
98     }
99 
100     @Before
setUp()101     public void setUp() throws Exception {
102         mTargetContext = InstrumentationRegistry.getTargetContext();
103         Assume.assumeTrue("Ignore test when HidDeviceService is not enabled",
104                 mTargetContext.getResources().getBoolean(R.bool.profile_supported_hid_device));
105         if (Looper.myLooper() == null) {
106             Looper.prepare();
107         }
108         Assert.assertNotNull(Looper.myLooper());
109 
110         // Set up mocks and test assets
111         MockitoAnnotations.initMocks(this);
112         TestUtils.setAdapterService(mAdapterService);
113         doReturn(mDatabaseManager).when(mAdapterService).getDatabase();
114         doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
115         setHidDeviceNativeInterfaceInstance(mHidDeviceNativeInterface);
116         // This line must be called to make sure relevant objects are initialized properly
117         mAdapter = BluetoothAdapter.getDefaultAdapter();
118         // Get a device for testing
119         mTestDevice = mAdapter.getRemoteDevice("10:11:12:13:14:15");
120 
121         TestUtils.startService(mServiceRule, HidDeviceService.class);
122         mHidDeviceService = HidDeviceService.getHidDeviceService();
123         Assert.assertNotNull(mHidDeviceService);
124 
125         // Force unregister app first
126         mHidDeviceService.unregisterApp();
127 
128         Field field = HidDeviceService.class.getDeclaredField("mHidDeviceNativeInterface");
129         field.setAccessible(true);
130         HidDeviceNativeInterface nativeInterface =
131                 (HidDeviceNativeInterface) field.get(mHidDeviceService);
132         Assert.assertEquals(nativeInterface, mHidDeviceNativeInterface);
133 
134         // Dummy SDP settings
135         mSettings = new BluetoothHidDeviceAppSdpSettings("Unit test", "test", "Android",
136                 BluetoothHidDevice.SUBCLASS1_COMBO, new byte[]{});
137 
138         // Set up the Connection State Changed receiver
139         IntentFilter filter = new IntentFilter();
140         filter.addAction(BluetoothHidDevice.ACTION_CONNECTION_STATE_CHANGED);
141         mConnectionStateChangedReceiver = new ConnectionStateChangedReceiver();
142         mTargetContext.registerReceiver(mConnectionStateChangedReceiver, filter);
143         reset(mHidDeviceNativeInterface, mAdapterService);
144     }
145 
146     @After
tearDown()147     public void tearDown() throws Exception {
148         if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_hid_device)) {
149             return;
150         }
151         TestUtils.stopService(mServiceRule, HidDeviceService.class);
152         mHidDeviceService = HidDeviceService.getHidDeviceService();
153         Assert.assertNull(mHidDeviceService);
154         mTargetContext.unregisterReceiver(mConnectionStateChangedReceiver);
155         mConnectionStateChangedQueue.clear();
156         mCallbackQueue.clear();
157         setHidDeviceNativeInterfaceInstance(null);
158         TestUtils.clearAdapterService(mAdapterService);
159     }
160 
161     private class ConnectionStateChangedReceiver extends BroadcastReceiver {
162         @Override
onReceive(Context context, Intent intent)163         public void onReceive(Context context, Intent intent) {
164             if (!BluetoothHidDevice.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
165                 return;
166             }
167             try {
168                 mConnectionStateChangedQueue.put(intent);
169             } catch (InterruptedException e) {
170                 throw new AssertionError("Cannot add Intent to the queue", e);
171             }
172         }
173     }
174 
waitForIntent(int timeoutMs, BlockingQueue<Intent> queue)175     private Intent waitForIntent(int timeoutMs, BlockingQueue<Intent> queue) {
176         try {
177             Intent intent = queue.poll(timeoutMs, TimeUnit.MILLISECONDS);
178             Assert.assertNotNull(intent);
179             return intent;
180         } catch (InterruptedException e) {
181             throw new AssertionError("Cannot obtain an Intent from the queue", e);
182         }
183     }
184 
verifyConnectionStateIntent(int timeoutMs, BluetoothDevice device, int newState, int prevState)185     private void verifyConnectionStateIntent(int timeoutMs, BluetoothDevice device, int newState,
186             int prevState) {
187         Intent intent = waitForIntent(timeoutMs, mConnectionStateChangedQueue);
188         Assert.assertNotNull(intent);
189         Assert.assertEquals(BluetoothHidDevice.ACTION_CONNECTION_STATE_CHANGED, intent.getAction());
190         Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
191         Assert.assertEquals(newState, intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
192         Assert.assertEquals(prevState,
193                 intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1));
194     }
195 
verifyCallback(int timeoutMs, int callbackType, BlockingQueue<Integer> queue)196     private void verifyCallback(int timeoutMs, int callbackType, BlockingQueue<Integer> queue) {
197         try {
198             Integer lastCallback = queue.poll(timeoutMs, TimeUnit.MILLISECONDS);
199             Assert.assertNotNull(lastCallback);
200             int lastCallbackType = lastCallback;
201             Assert.assertEquals(callbackType, lastCallbackType);
202         } catch (InterruptedException e) {
203             throw new AssertionError("Cannot obtain a callback from the queue", e);
204         }
205     }
206 
207     class BluetoothHidDeviceCallbackTestHelper extends IBluetoothHidDeviceCallback.Stub {
onAppStatusChanged(BluetoothDevice device, boolean registered)208         public void onAppStatusChanged(BluetoothDevice device, boolean registered) {
209             try {
210                 if (registered) {
211                     mCallbackQueue.put(CALLBACK_APP_REGISTERED);
212                 } else {
213                     mCallbackQueue.put(CALLBACK_APP_UNREGISTERED);
214                 }
215             } catch (InterruptedException e) {
216                 throw new AssertionError("Cannot add Intent to the queue", e);
217             }
218         }
219 
onConnectionStateChanged(BluetoothDevice device, int state)220         public void onConnectionStateChanged(BluetoothDevice device, int state) {
221 
222         }
223 
onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize)224         public void onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize) {
225             try {
226                 mCallbackQueue.put(CALLBACK_ON_GET_REPORT);
227             } catch (InterruptedException e) {
228                 throw new AssertionError("Cannot add Intent to the queue", e);
229             }
230         }
231 
onSetReport(BluetoothDevice device, byte type, byte id, byte[] data)232         public void onSetReport(BluetoothDevice device, byte type, byte id, byte[] data) {
233             try {
234                 mCallbackQueue.put(CALLBACK_ON_SET_REPORT);
235             } catch (InterruptedException e) {
236                 throw new AssertionError("Cannot add Intent to the queue", e);
237             }
238         }
239 
onSetProtocol(BluetoothDevice device, byte protocol)240         public void onSetProtocol(BluetoothDevice device, byte protocol) {
241             try {
242                 mCallbackQueue.put(CALLBACK_ON_SET_PROTOCOL);
243             } catch (InterruptedException e) {
244                 throw new AssertionError("Cannot add Intent to the queue", e);
245             }
246         }
247 
onInterruptData(BluetoothDevice device, byte reportId, byte[] data)248         public void onInterruptData(BluetoothDevice device, byte reportId, byte[] data) {
249             try {
250                 mCallbackQueue.put(CALLBACK_ON_INTR_DATA);
251             } catch (InterruptedException e) {
252                 throw new AssertionError("Cannot add Intent to the queue", e);
253 
254             }
255         }
256 
onVirtualCableUnplug(BluetoothDevice device)257         public void onVirtualCableUnplug(BluetoothDevice device) {
258             try {
259                 mCallbackQueue.put(CALLBACK_ON_VIRTUAL_UNPLUG);
260             } catch (InterruptedException e) {
261                 throw new AssertionError("Cannot add Intent to the queue", e);
262             }
263         }
264     }
265 
266     /**
267      * Test getting HidDeviceService: getHidDeviceService().
268      */
269     @Test
testGetHidDeviceService()270     public void testGetHidDeviceService() {
271         Assert.assertEquals(mHidDeviceService, HidDeviceService.getHidDeviceService());
272     }
273 
274     /**
275      * Test the logic in registerApp and unregisterApp. Should get a callback
276      * onApplicationStateChangedFromNative.
277      */
278     @Test
testRegistration()279     public void testRegistration() throws Exception {
280         doReturn(true).when(mHidDeviceNativeInterface)
281                 .registerApp(anyString(), anyString(), anyString(), anyByte(), any(byte[].class),
282                         isNull(), isNull());
283 
284         verify(mHidDeviceNativeInterface, never()).registerApp(anyString(), anyString(),
285                 anyString(), anyByte(), any(byte[].class), isNull(), isNull());
286 
287         // Register app
288         BluetoothHidDeviceCallbackTestHelper helper = new BluetoothHidDeviceCallbackTestHelper();
289         Assert.assertTrue(mHidDeviceService.registerApp(mSettings, null, null, helper));
290 
291         verify(mHidDeviceNativeInterface).registerApp(anyString(), anyString(), anyString(),
292                 anyByte(), any(byte[].class), isNull(), isNull());
293 
294         // App registered
295         mHidDeviceService.onApplicationStateChangedFromNative(mTestDevice, true);
296         verifyCallback(TIMEOUT_MS, CALLBACK_APP_REGISTERED, mCallbackQueue);
297 
298         // Unregister app
299         doReturn(true).when(mHidDeviceNativeInterface).unregisterApp();
300         Assert.assertEquals(true, mHidDeviceService.unregisterApp());
301 
302         verify(mHidDeviceNativeInterface).unregisterApp();
303 
304         mHidDeviceService.onApplicationStateChangedFromNative(mTestDevice, false);
305         verifyCallback(TIMEOUT_MS, CALLBACK_APP_UNREGISTERED, mCallbackQueue);
306 
307     }
308 
309     /**
310      * Test the logic in sendReport(). This should fail when the app is not registered.
311      */
312     @Test
testSendReport()313     public void testSendReport() throws Exception {
314         doReturn(true).when(mHidDeviceNativeInterface).sendReport(anyInt(), any(byte[].class));
315         // sendReport() should fail without app registered
316         Assert.assertEquals(false,
317                 mHidDeviceService.sendReport(mTestDevice, SAMPLE_REPORT_ID, SAMPLE_HID_REPORT));
318 
319         // Register app
320         doReturn(true).when(mHidDeviceNativeInterface)
321                 .registerApp(anyString(), anyString(), anyString(), anyByte(), any(byte[].class),
322                         isNull(), isNull());
323         BluetoothHidDeviceCallbackTestHelper helper = new BluetoothHidDeviceCallbackTestHelper();
324         Assert.assertTrue(mHidDeviceService.registerApp(mSettings, null, null, helper));
325 
326         // App registered
327         mHidDeviceService.onApplicationStateChangedFromNative(mTestDevice, true);
328 
329         // Wait for the app registration callback to complete and verify it
330         verifyCallback(TIMEOUT_MS, CALLBACK_APP_REGISTERED, mCallbackQueue);
331 
332         // sendReport() should work when app is registered
333         Assert.assertEquals(true,
334                 mHidDeviceService.sendReport(mTestDevice, SAMPLE_REPORT_ID, SAMPLE_HID_REPORT));
335 
336         verify(mHidDeviceNativeInterface).sendReport(eq((int) SAMPLE_REPORT_ID),
337                 eq(SAMPLE_HID_REPORT));
338 
339         // Unregister app
340         doReturn(true).when(mHidDeviceNativeInterface).unregisterApp();
341         Assert.assertEquals(true, mHidDeviceService.unregisterApp());
342     }
343 
344     /**
345      * Test the logic in replyReport(). This should fail when the app is not registered.
346      */
347     @Test
testReplyReport()348     public void testReplyReport() throws Exception {
349         doReturn(true).when(mHidDeviceNativeInterface)
350                 .replyReport(anyByte(), anyByte(), any(byte[].class));
351         // replyReport() should fail without app registered
352         Assert.assertEquals(false,
353                 mHidDeviceService.replyReport(mTestDevice, SAMPLE_REPORT_TYPE, SAMPLE_REPORT_ID,
354                         SAMPLE_HID_REPORT));
355 
356         // Register app
357         doReturn(true).when(mHidDeviceNativeInterface)
358                 .registerApp(anyString(), anyString(), anyString(), anyByte(), any(byte[].class),
359                         isNull(), isNull());
360         BluetoothHidDeviceCallbackTestHelper helper = new BluetoothHidDeviceCallbackTestHelper();
361         Assert.assertTrue(mHidDeviceService.registerApp(mSettings, null, null, helper));
362 
363         // App registered
364         mHidDeviceService.onApplicationStateChangedFromNative(mTestDevice, true);
365 
366         // Wait for the app registration callback to complete and verify it
367         verifyCallback(TIMEOUT_MS, CALLBACK_APP_REGISTERED, mCallbackQueue);
368 
369         // replyReport() should work when app is registered
370         Assert.assertEquals(true,
371                 mHidDeviceService.replyReport(mTestDevice, SAMPLE_REPORT_TYPE, SAMPLE_REPORT_ID,
372                         SAMPLE_HID_REPORT));
373 
374         verify(mHidDeviceNativeInterface).replyReport(eq(SAMPLE_REPORT_TYPE), eq(SAMPLE_REPORT_ID),
375                 eq(SAMPLE_HID_REPORT));
376 
377         // Unregister app
378         doReturn(true).when(mHidDeviceNativeInterface).unregisterApp();
379         Assert.assertEquals(true, mHidDeviceService.unregisterApp());
380     }
381 
382     /**
383      * Test the logic in reportError(). This should fail when the app is not registered.
384      */
385     @Test
testReportError()386     public void testReportError() throws Exception {
387         doReturn(true).when(mHidDeviceNativeInterface).reportError(anyByte());
388         // reportError() should fail without app registered
389         Assert.assertEquals(false, mHidDeviceService.reportError(mTestDevice, SAMPLE_REPORT_ERROR));
390 
391         // Register app
392         doReturn(true).when(mHidDeviceNativeInterface)
393                 .registerApp(anyString(), anyString(), anyString(), anyByte(), any(byte[].class),
394                         isNull(), isNull());
395         BluetoothHidDeviceCallbackTestHelper helper = new BluetoothHidDeviceCallbackTestHelper();
396         Assert.assertTrue(mHidDeviceService.registerApp(mSettings, null, null, helper));
397 
398         // App registered
399         mHidDeviceService.onApplicationStateChangedFromNative(mTestDevice, true);
400 
401         // Wait for the app registration callback to complete and verify it
402         verifyCallback(TIMEOUT_MS, CALLBACK_APP_REGISTERED, mCallbackQueue);
403 
404         // reportError() should work when app is registered
405         Assert.assertEquals(true, mHidDeviceService.reportError(mTestDevice, SAMPLE_REPORT_ERROR));
406 
407         verify(mHidDeviceNativeInterface).reportError(eq(SAMPLE_REPORT_ERROR));
408 
409         // Unregister app
410         doReturn(true).when(mHidDeviceNativeInterface).unregisterApp();
411         Assert.assertEquals(true, mHidDeviceService.unregisterApp());
412     }
413 
414     /**
415      * Test that an outgoing connection/disconnection succeeds
416      */
417     @Test
testOutgoingConnectDisconnectSuccess()418     public void testOutgoingConnectDisconnectSuccess() {
419         doReturn(true).when(mHidDeviceNativeInterface).connect(any(BluetoothDevice.class));
420         doReturn(true).when(mHidDeviceNativeInterface).disconnect();
421 
422         // Register app
423         doReturn(true).when(mHidDeviceNativeInterface)
424                 .registerApp(anyString(), anyString(), anyString(), anyByte(), any(byte[].class),
425                         isNull(), isNull());
426         mHidDeviceService.registerApp(mSettings, null, null, null);
427 
428         // App registered
429         mHidDeviceService.onApplicationStateChangedFromNative(mTestDevice, true);
430 
431         // Send a connect request
432         Assert.assertTrue("Connect failed", mHidDeviceService.connect(mTestDevice));
433 
434         mHidDeviceService.onConnectStateChangedFromNative(mTestDevice,
435                 HidDeviceService.HAL_CONN_STATE_CONNECTING);
436         // Verify the connection state broadcast
437         verifyConnectionStateIntent(TIMEOUT_MS, mTestDevice, BluetoothProfile.STATE_CONNECTING,
438                 BluetoothProfile.STATE_DISCONNECTED);
439         Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
440                 mHidDeviceService.getConnectionState(mTestDevice));
441 
442         mHidDeviceService.onConnectStateChangedFromNative(mTestDevice,
443                 HidDeviceService.HAL_CONN_STATE_CONNECTED);
444         // Verify the connection state broadcast
445         verifyConnectionStateIntent(TIMEOUT_MS, mTestDevice, BluetoothProfile.STATE_CONNECTED,
446                 BluetoothProfile.STATE_CONNECTING);
447         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
448                 mHidDeviceService.getConnectionState(mTestDevice));
449 
450         // Verify the list of connected devices
451         Assert.assertTrue(mHidDeviceService.getDevicesMatchingConnectionStates(
452                 new int[]{BluetoothProfile.STATE_CONNECTED}).contains(mTestDevice));
453 
454         // Send a disconnect request
455         Assert.assertTrue("Disconnect failed", mHidDeviceService.disconnect(mTestDevice));
456 
457         mHidDeviceService.onConnectStateChangedFromNative(mTestDevice,
458                 HidDeviceService.HAL_CONN_STATE_DISCONNECTING);
459         // Verify the connection state broadcast
460         verifyConnectionStateIntent(TIMEOUT_MS, mTestDevice, BluetoothProfile.STATE_DISCONNECTING,
461                 BluetoothProfile.STATE_CONNECTED);
462         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTING,
463                 mHidDeviceService.getConnectionState(mTestDevice));
464 
465         mHidDeviceService.onConnectStateChangedFromNative(mTestDevice,
466                 HidDeviceService.HAL_CONN_STATE_DISCONNECTED);
467         // Verify the connection state broadcast
468         verifyConnectionStateIntent(TIMEOUT_MS, mTestDevice, BluetoothProfile.STATE_DISCONNECTED,
469                 BluetoothProfile.STATE_DISCONNECTING);
470         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
471                 mHidDeviceService.getConnectionState(mTestDevice));
472 
473         // Verify the list of connected devices
474         Assert.assertFalse(mHidDeviceService.getDevicesMatchingConnectionStates(
475                 new int[]{BluetoothProfile.STATE_CONNECTED}).contains(mTestDevice));
476 
477         // Unregister app
478         doReturn(true).when(mHidDeviceNativeInterface).unregisterApp();
479         Assert.assertEquals(true, mHidDeviceService.unregisterApp());
480     }
481 
482     /**
483      * Test the logic in callback functions from native stack: onGetReport, onSetReport,
484      * onSetProtocol, onInterruptData, onVirtualCableUnplug. The HID Device server should send the
485      * callback to the user app.
486      */
487     @Test
testCallbacks()488     public void testCallbacks() {
489         doReturn(true).when(mHidDeviceNativeInterface)
490                 .registerApp(anyString(), anyString(), anyString(), anyByte(), any(byte[].class),
491                         isNull(), isNull());
492 
493         verify(mHidDeviceNativeInterface, never()).registerApp(anyString(), anyString(),
494                 anyString(), anyByte(), any(byte[].class), isNull(), isNull());
495 
496         // Register app
497         BluetoothHidDeviceCallbackTestHelper helper = new BluetoothHidDeviceCallbackTestHelper();
498         Assert.assertTrue(mHidDeviceService.registerApp(mSettings, null, null, helper));
499 
500         verify(mHidDeviceNativeInterface).registerApp(anyString(), anyString(), anyString(),
501                 anyByte(), any(byte[].class), isNull(), isNull());
502 
503         // App registered
504         mHidDeviceService.onApplicationStateChangedFromNative(mTestDevice, true);
505         verifyCallback(TIMEOUT_MS, CALLBACK_APP_REGISTERED, mCallbackQueue);
506 
507         // Received callback: onGetReport
508         mHidDeviceService.onGetReportFromNative(SAMPLE_REPORT_TYPE, SAMPLE_REPORT_ID,
509                 SAMPLE_BUFFER_SIZE);
510         verifyCallback(TIMEOUT_MS, CALLBACK_ON_GET_REPORT, mCallbackQueue);
511 
512         // Received callback: onSetReport
513         mHidDeviceService.onSetReportFromNative(SAMPLE_REPORT_TYPE, SAMPLE_REPORT_ID,
514                 SAMPLE_HID_REPORT);
515         verifyCallback(TIMEOUT_MS, CALLBACK_ON_SET_REPORT, mCallbackQueue);
516 
517         // Received callback: onSetProtocol
518         mHidDeviceService.onSetProtocolFromNative(BluetoothHidDevice.PROTOCOL_BOOT_MODE);
519         verifyCallback(TIMEOUT_MS, CALLBACK_ON_SET_PROTOCOL, mCallbackQueue);
520 
521         // Received callback: onInterruptData
522         mHidDeviceService.onInterruptDataFromNative(SAMPLE_REPORT_ID, SAMPLE_HID_REPORT);
523         verifyCallback(TIMEOUT_MS, CALLBACK_ON_INTR_DATA, mCallbackQueue);
524 
525         // Received callback: onVirtualCableUnplug
526         mHidDeviceService.onVirtualCableUnplugFromNative();
527         verifyCallback(TIMEOUT_MS, CALLBACK_ON_VIRTUAL_UNPLUG, mCallbackQueue);
528 
529         // Unregister app
530         doReturn(true).when(mHidDeviceNativeInterface).unregisterApp();
531         Assert.assertEquals(true, mHidDeviceService.unregisterApp());
532 
533         verify(mHidDeviceNativeInterface).unregisterApp();
534 
535         mHidDeviceService.onApplicationStateChangedFromNative(mTestDevice, false);
536         verifyCallback(TIMEOUT_MS, CALLBACK_APP_UNREGISTERED, mCallbackQueue);
537     }
538 }
539