Search code examples
androidusb-otg

Detect when a USB OTG device disconnects


I have an app which interacts with a USB OTG device:

  • When a USB device connects, a helper activity is started to display the Android confirmation dialog. This is done via an IntentFilter in the manifest.
  • The helper activity starts a service is started by sending it an app-specific intent.
  • The service’s onCreate() method populates an IntentFilter, adding the actions to which the service should react when running, including UsbManager.ACTION_USB_DEVICE_DETACHED. Adding extra debug output tells me the method runs when I expect it to, i.e. the IntentFilter is populated when I register the receiver.
  • The service’s onStartCommand() method calls an internal method which registers the BroadcastReceiver for the intent filter (if the service was started with the start intent, and has the necessary permissions—else the service terminates).
  • When the receiver receives UsbManager.ACTION_USB_DEVICE_DETACHED and the device reported is the one that is currently connected, it stops the service.
  • There is also a main activity, which is not involved in handling the USB device.
  • The service also gets called for other reasons, notably when a charger is connected. In this case the service looks for a Bluetooth device (if a USB device is already connected, indicated by a member of the service instance being non-null, this is skipped and the service exits).

Now, if I plug in the USB device, I get the confirmation and the service starts, and when I unplug the device, the service stops again. So far, so good.

However, in some cases the service keeps running even after the device is unplugged. I have noticed this always happens when the main activity was open when I connected the device. Logs show me that the service never receives the UsbManager.ACTION_USB_DEVICE_DETACHED broadcast.

While doing further tests (open main activity and navigate away from it before connecting the device), I found evidence that there may be two instances of the service running for some reason.

What is happening here, and how can I reliably detect that the USB device was disconnected?


Solution

  • This behavior appears to be caused by two factors:

    • As described above, the service gets started not only when a USB device connects, but also on other events, such as the device being connected to an AC adapter or the main activity being opened. In these cases it will look for a Bluetooth device (“autoconnect”) and exit if none is found, or if a USB device is already connected.
    • As a result, when autoconnect is enabled, opening the main activity will always start the first service instance. If a USB device connects after that, we may apparently have two service instances running. I suspect the disconnect broadcast may get picked up by the wrong instance.
    • If I disable autoconnect, the service does receive the disconnection event but ignores it, as the devices are not considered equal. Yet log output shows that the device path for both devices is the same. Further analysis revealed that I had simply used != to compare the two UsbDevice instances, which fails to catch two different class instances referring to the same device.

    So we need to do two things:

    • Use UsbDevice#equals() rather than the equality operator for comparison.
    • Prevent multiple instances of the service from running. Ensure the service exits when no device is found, and Intents are delivered to the existing instance rather than starting a new one.