Skip to content

Commit

Permalink
macOS: check that device has not been disconnected
Browse files Browse the repository at this point in the history
  • Loading branch information
manuelbl committed Apr 20, 2024
1 parent db81a6b commit e019f50
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 20 deletions.
12 changes: 12 additions & 0 deletions java-does-usb/src/main/java/net/codecrete/usb/UsbDevice.java
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,18 @@ public interface UsbDevice {
*/
void attachStandardDrivers();

/**
* Indicates if the device is connected.
* <p>
* When a {@link UsbDevice} instance is initially returned by {@link Usb#getDevices()} and related methods,
* it is connected. When the user unplugs the device, the application can still hold on to instance of
* {@link UsbDevice} even though the actual USB device is gone. This method can be used to check if the
* device is still connected.
* </p>
* @return {@code true} if the device is connected, {@code false} if it is no longer connected
*/
boolean isConnected();

/**
* Opens the device for communication.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public abstract class UsbDeviceImpl implements UsbDevice {
protected int deviceProtocol;
protected Version versionUsb;
protected Version versionDevice;
protected boolean connected;

/**
* Creates a new instance.
Expand All @@ -70,6 +71,7 @@ protected UsbDeviceImpl(Object id, int vendorId, int productId) {
uniqueDeviceId = id;
vid = vendorId;
pid = productId;
connected = true;
}

@Override
Expand Down Expand Up @@ -157,6 +159,11 @@ public Object getUniqueId() {
return uniqueDeviceId;
}

@Override
public boolean isConnected() {
return connected;
}

/**
* Sets the class codes and version for the device descriptor.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,18 +78,16 @@ public class MacosUsbDevice extends UsbDeviceImpl {
}

@Override
public void detachStandardDrivers() {
if (isOpened())
throwException("detachStandardDrivers() must not be called while the device is open");
public synchronized void detachStandardDrivers() {
checkIsClosed("detachStandardDrivers() must not be called while the device is open");
var ret = IoKitUsb.USBDeviceReEnumerate(device, IOKit.kUSBReEnumerateCaptureDeviceMask());
if (ret != 0)
throwException(ret, "detaching standard drivers failed");
}

@Override
public void attachStandardDrivers() {
if (isOpened())
throwException("attachStandardDrivers() must not be called while the device is open");
public synchronized void attachStandardDrivers() {
checkIsClosed("attachStandardDrivers() must not be called while the device is open");
var ret = IoKitUsb.USBDeviceReEnumerate(device, IOKit.kUSBReEnumerateReleaseDeviceMask());
if (ret != 0)
throwException(ret, "attaching standard drivers failed");
Expand All @@ -100,11 +98,17 @@ public boolean isOpened() {
return claimedInterfaces != null;
}

private void checkIsClosed(String message) {
if (!connected)
throwException("device has been disconnected");
if (isOpened())
throwException(message);
}

@SuppressWarnings("java:S2276")
@Override
public synchronized void open() {
if (isOpened())
throwException("device is already open");
checkIsClosed("device is already open");

// open device (several retries if device has just been connected/discovered)
var duration = System.currentTimeMillis() - discoveryTime;
Expand Down Expand Up @@ -161,6 +165,7 @@ public synchronized void close() {
}

synchronized void closeFully() {
connected = false;
close();
IoKitUsb.Release(device);
device = null;
Expand Down Expand Up @@ -580,7 +585,7 @@ protected Transfer createTransfer() {
}

@Override
public void abortTransfers(UsbDirection direction, int endpointNumber) {
public synchronized void abortTransfers(UsbDirection direction, int endpointNumber) {
var epInfo = getEndpointInfo(endpointNumber, direction, UsbTransferType.BULK,
UsbTransferType.INTERRUPT);

Expand All @@ -590,7 +595,7 @@ public void abortTransfers(UsbDirection direction, int endpointNumber) {
}

@Override
public void clearHalt(UsbDirection direction, int endpointNumber) {
public synchronized void clearHalt(UsbDirection direction, int endpointNumber) {
var epInfo = getEndpointInfo(endpointNumber, direction, UsbTransferType.BULK,
UsbTransferType.INTERRUPT);

Expand Down
31 changes: 21 additions & 10 deletions java-does-usb/src/test/java/net/codecrete/usb/special/Unplug.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,27 @@ private static void onUnpluggedDevice(UsbDevice device) {
var worker = activeDevices.remove(device);
worker.setDisconnectTime(System.currentTimeMillis());
worker.join();

// test handling of disconnected devices
new Thread(() -> {
sleep(2000);
try {
device.open();
System.err.println("Device should not be openable after disconnect");
} catch (UsbException e) {
// expected
}
}).start();
}

@SuppressWarnings({"SameParameterValue", "java:S2925"})
private static void sleep(long millis) {
try {
Thread.sleep(millis);

} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}

static class DeviceWorker {
Expand Down Expand Up @@ -241,16 +262,6 @@ private void receiveEcho() {
logWork(1);
}
}

@SuppressWarnings({"SameParameterValue", "java:S2925"})
private static void sleep(long millis) {
try {
Thread.sleep(millis);

} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}

static class Work {
Expand Down

0 comments on commit e019f50

Please sign in to comment.