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