1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; 4 import static android.os.Build.VERSION_CODES.KITKAT; 5 import static android.os.Build.VERSION_CODES.LOLLIPOP; 6 import static android.os.Build.VERSION_CODES.O; 7 8 import android.hardware.usb.UsbDeviceConnection; 9 import android.hardware.usb.UsbEndpoint; 10 import android.hardware.usb.UsbInterface; 11 import android.hardware.usb.UsbRequest; 12 import java.io.FilterInputStream; 13 import java.io.IOException; 14 import java.io.InputStream; 15 import java.io.PipedInputStream; 16 import java.io.PipedOutputStream; 17 import java.util.concurrent.TimeoutException; 18 import org.robolectric.annotation.Implementation; 19 import org.robolectric.annotation.Implements; 20 21 /** Robolectric implementation of {@link android.hardware.usb.UsbDeviceConnection}. */ 22 @Implements(value = UsbDeviceConnection.class) 23 public class ShadowUsbDeviceConnection { 24 25 private PipedInputStream outgoingDataPipedInputStream; 26 private PipedOutputStream outgoingDataPipedOutputStream; 27 28 private DataListener dataListener; 29 30 @Implementation claimInterface(UsbInterface intf, boolean force)31 protected boolean claimInterface(UsbInterface intf, boolean force) { 32 try { 33 this.outgoingDataPipedInputStream = new PipedInputStream(); 34 this.outgoingDataPipedOutputStream = new PipedOutputStream(outgoingDataPipedInputStream); 35 } catch (IOException e) { 36 return false; 37 } 38 39 return true; 40 } 41 42 @Implementation releaseInterface(UsbInterface intf)43 protected boolean releaseInterface(UsbInterface intf) { 44 try { 45 outgoingDataPipedInputStream.close(); 46 } catch (IOException e) { 47 // ignored 48 } 49 try { 50 outgoingDataPipedOutputStream.close(); 51 } catch (IOException e) { 52 // ignored 53 } 54 55 return true; 56 } 57 58 /** 59 * No-op on Robolectrict. The real implementation would return false on Robolectric and make it 60 * impossible to test callers that expect a successful result. Always returns {@code true}. 61 */ 62 @Implementation(minSdk = LOLLIPOP) setInterface(UsbInterface intf)63 protected boolean setInterface(UsbInterface intf) { 64 return true; 65 } 66 67 @Implementation(minSdk = KITKAT) controlTransfer( int requestType, int request, int value, int index, byte[] buffer, int length, int timeout)68 protected int controlTransfer( 69 int requestType, int request, int value, int index, byte[] buffer, int length, int timeout) { 70 return length; 71 } 72 73 @Implementation(minSdk = KITKAT) controlTransfer( int requestType, int request, int value, int index, byte[] buffer, int offset, int length, int timeout)74 protected int controlTransfer( 75 int requestType, 76 int request, 77 int value, 78 int index, 79 byte[] buffer, 80 int offset, 81 int length, 82 int timeout) { 83 return length; 84 } 85 86 @Implementation requestWait()87 protected UsbRequest requestWait() { 88 if (dataListener == null) { 89 throw new IllegalStateException("No UsbRequest initialized for this UsbDeviceConnection"); 90 } 91 92 return dataListener.getUsbRequest(); 93 } 94 95 @Implementation(minSdk = O) requestWait(long timeout)96 protected UsbRequest requestWait(long timeout) throws TimeoutException { 97 return requestWait(); 98 } 99 100 @Implementation(minSdk = JELLY_BEAN_MR2) bulkTransfer( UsbEndpoint endpoint, byte[] buffer, int offset, int length, int timeout)101 protected int bulkTransfer( 102 UsbEndpoint endpoint, byte[] buffer, int offset, int length, int timeout) { 103 try { 104 outgoingDataPipedOutputStream.write(buffer, offset, length); 105 return length; 106 } catch (IOException e) { 107 return -1; 108 } 109 } 110 111 @Implementation bulkTransfer(UsbEndpoint endpoint, byte[] buffer, int length, int timeout)112 protected int bulkTransfer(UsbEndpoint endpoint, byte[] buffer, int length, int timeout) { 113 try { 114 outgoingDataPipedOutputStream.write(buffer, /* off= */ 0, length); 115 return length; 116 } catch (IOException e) { 117 return -1; 118 } 119 } 120 121 /** 122 * Fills the buffer with data that was written by UsbDeviceConnection#bulkTransfer. 123 * 124 * @deprecated prefer {@link #getOutgoingDataStream()}, which allows callers to know how much data 125 * has been read and when the {@link UsbDeviceConnection} closes. 126 */ 127 @Deprecated readOutgoingData(byte[] buffer)128 public void readOutgoingData(byte[] buffer) throws IOException { 129 getOutgoingDataStream().read(buffer); 130 } 131 132 /** 133 * Provides an {@link InputStream} that allows reading data written by 134 * UsbDeviceConnection#bulkTransfer. Closing this stream has no effect. It is effectively closed 135 * during {@link UsbDeviceConnection#releaseInterface(UsbInterface)}. 136 */ getOutgoingDataStream()137 public InputStream getOutgoingDataStream() { 138 return new FilterInputStream(outgoingDataPipedInputStream) { 139 @Override 140 public void close() throws IOException { 141 // Override close() to prevent clients from closing the piped stream and causing unexpected 142 // side-effects if further writes happen. 143 } 144 }; 145 } 146 147 /** Passes data that can then be read by an initialized UsbRequest#queue(ByteBuffer). */ writeIncomingData(byte[] data)148 public void writeIncomingData(byte[] data) { 149 if (dataListener == null) { 150 throw new IllegalStateException("No UsbRequest initialized for this UsbDeviceConnection"); 151 } 152 153 dataListener.onDataReceived(data); 154 } 155 registerDataListener(DataListener dataListener)156 void registerDataListener(DataListener dataListener) { 157 this.dataListener = dataListener; 158 } 159 unregisterDataListener()160 void unregisterDataListener() { 161 this.dataListener = null; 162 } 163 164 interface DataListener { onDataReceived(byte[] data)165 void onDataReceived(byte[] data); 166 getUsbRequest()167 UsbRequest getUsbRequest(); 168 } 169 } 170