diff --git a/README.md b/README.md
index 50a9551..e2fb7b2 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
 This is a driver library for communication with Arduinos and other USB serial hardware on
 Android, using the
 [Android USB Host API](http://developer.android.com/guide/topics/connectivity/usb/host.html)
-available on Android 3.1+.
+available since Android 3.1 and asynchronous interrupt transfer working reliably since Android 4.2
 
 No root access, ADK, or special kernel drivers are required; all drivers are implemented in
 Java.  You get a raw serial port with `read()`, `write()`, and other basic
diff --git a/build.gradle b/build.gradle
index f400f0c..f055c90 100644
--- a/build.gradle
+++ b/build.gradle
@@ -6,7 +6,7 @@ buildscript {
         google()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.2.1'
+        classpath 'com.android.tools.build:gradle:3.3.0'
     }
 }
 
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 93a408c..b2a4404 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Sun Nov 11 09:16:07 CET 2018
+#Sun Feb 03 09:37:03 CET 2019
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
diff --git a/usbSerialExamples/build.gradle b/usbSerialExamples/build.gradle
index fbcfcfa..b45091c 100644
--- a/usbSerialExamples/build.gradle
+++ b/usbSerialExamples/build.gradle
@@ -5,7 +5,7 @@ android {
     buildToolsVersion '28.0.3'
 
     defaultConfig {
-        minSdkVersion 14
+        minSdkVersion 17
         targetSdkVersion 28
 
         testInstrumentationRunner "android.test.InstrumentationTestRunner"
diff --git a/usbSerialForAndroid/build.gradle b/usbSerialForAndroid/build.gradle
index 0e66b22..bbb2b57 100644
--- a/usbSerialForAndroid/build.gradle
+++ b/usbSerialForAndroid/build.gradle
@@ -7,7 +7,7 @@ android {
     buildToolsVersion '28.0.3'
 
     defaultConfig {
-        minSdkVersion 14
+        minSdkVersion 17
         targetSdkVersion 28
         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
     }
diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/CdcAcmSerialDriver.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/CdcAcmSerialDriver.java
index 3f4b96d..a3d9a76 100644
--- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/CdcAcmSerialDriver.java
+++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/CdcAcmSerialDriver.java
@@ -27,7 +27,6 @@ import android.hardware.usb.UsbDeviceConnection;
 import android.hardware.usb.UsbEndpoint;
 import android.hardware.usb.UsbInterface;
 import android.hardware.usb.UsbRequest;
-import android.os.Build;
 import android.util.Log;
 
 import java.io.IOException;
@@ -69,7 +68,6 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
 
     class CdcAcmSerialPort extends CommonUsbSerialPort {
 
-        private final boolean mEnableAsyncReads;
         private UsbInterface mControlInterface;
         private UsbInterface mDataInterface;
 
@@ -92,7 +90,6 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
 
         public CdcAcmSerialPort(UsbDevice device, int portNumber) {
             super(device, portNumber);
-            mEnableAsyncReads = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1);
         }
 
         @Override
@@ -118,13 +115,6 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
                     openInterface();
                 }
 
-                if (mEnableAsyncReads) {
-                    Log.d(TAG, "Async reads enabled");
-                } else {
-                    Log.d(TAG, "Async reads disabled.");
-                }
-
-
                 opened = true;
             } finally {
                 if (!opened) {
@@ -269,51 +259,29 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
 
         @Override
         public int read(byte[] dest, int timeoutMillis) throws IOException {
-            if (mEnableAsyncReads) {
-              final UsbRequest request = new UsbRequest();
-              try {
+            final UsbRequest request = new UsbRequest();
+            try {
                 request.initialize(mConnection, mReadEndpoint);
                 final ByteBuffer buf = ByteBuffer.wrap(dest);
                 if (!request.queue(buf, dest.length)) {
-                  throw new IOException("Error queueing request.");
+                    throw new IOException("Error queueing request.");
                 }
 
                 final UsbRequest response = mConnection.requestWait();
                 if (response == null) {
-                  throw new IOException("Null response");
+                    throw new IOException("Null response");
                 }
 
                 final int nread = buf.position();
                 if (nread > 0) {
-                  //Log.d(TAG, HexDump.dumpHexString(dest, 0, Math.min(32, dest.length)));
-                  return nread;
+                    //Log.d(TAG, HexDump.dumpHexString(dest, 0, Math.min(32, dest.length)));
+                    return nread;
                 } else {
-                  return 0;
-                }
-              } finally {
-                request.close();
-              }
-            }
-
-            final int numBytesRead;
-            synchronized (mReadBufferLock) {
-                int readAmt = Math.min(dest.length, mReadBuffer.length);
-                numBytesRead = mConnection.bulkTransfer(mReadEndpoint, mReadBuffer, readAmt,
-                        timeoutMillis);
-                if (numBytesRead < 0) {
-                    // This sucks: we get -1 on timeout, not 0 as preferred.
-                    // We *should* use UsbRequest, except it has a bug/api oversight
-                    // where there is no way to determine the number of bytes read
-                    // in response :\ -- http://b.android.com/28023
-                    if (timeoutMillis == Integer.MAX_VALUE) {
-                        // Hack: Special case "~infinite timeout" as an error.
-                        return -1;
-                    }
                     return 0;
                 }
-                System.arraycopy(mReadBuffer, 0, dest, 0, numBytesRead);
+            } finally {
+                request.close();
             }
-            return numBytesRead;
         }
 
         @Override
diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Ch34xSerialDriver.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Ch34xSerialDriver.java
index 48b26fc..0b0ae97 100644
--- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Ch34xSerialDriver.java
+++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Ch34xSerialDriver.java
@@ -26,7 +26,6 @@ import android.hardware.usb.UsbDeviceConnection;
 import android.hardware.usb.UsbEndpoint;
 import android.hardware.usb.UsbInterface;
 import android.hardware.usb.UsbRequest;
-import android.os.Build;
 import android.util.Log;
 
 import java.io.IOException;
@@ -84,14 +83,12 @@ public class Ch34xSerialDriver implements UsbSerialDriver {
 		private boolean dtr = false;
 		private boolean rts = false;
 
-		private final Boolean mEnableAsyncReads;
 		private UsbEndpoint mReadEndpoint;
 		private UsbEndpoint mWriteEndpoint;
 		private UsbRequest mUsbRequest;
 
 		public Ch340SerialPort(UsbDevice device, int portNumber) {
 			super(device, portNumber);
-			mEnableAsyncReads = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1);
 		}
 
 		@Override
@@ -127,12 +124,6 @@ public class Ch34xSerialDriver implements UsbSerialDriver {
 					}
 				}
 
-				if (mEnableAsyncReads) {
-					Log.d(TAG, "Async reads enabled");
-				} else {
-					Log.d(TAG, "Async reads disabled.");
-				}
-
 				initialize();
 				setBaudRate(DEFAULT_BAUD_RATE);
 
@@ -153,12 +144,10 @@ public class Ch34xSerialDriver implements UsbSerialDriver {
 			if (mConnection == null) {
 				throw new IOException("Already closed");
 			}
-			synchronized (mEnableAsyncReads) {
+			synchronized (this) {
 				if (mUsbRequest != null)
 					mUsbRequest.cancel();
 			}
-			// TODO: nothing sended on close, maybe needed?
-
 			try {
 				mConnection.close();
 			} finally {
@@ -169,50 +158,32 @@ public class Ch34xSerialDriver implements UsbSerialDriver {
 
 		@Override
 		public int read(byte[] dest, int timeoutMillis) throws IOException {
-			if (mEnableAsyncReads) {
-				final UsbRequest request = new UsbRequest();
-				try {
-					request.initialize(mConnection, mReadEndpoint);
-					final ByteBuffer buf = ByteBuffer.wrap(dest);
-					if (!request.queue(buf, dest.length)) {
-						throw new IOException("Error queueing request.");
-					}
-					mUsbRequest = request;
-					final UsbRequest response = mConnection.requestWait();
-					synchronized (mEnableAsyncReads) {
-						mUsbRequest = null;
-					}
-					if (response == null) {
-						throw new IOException("Null response");
-					}
-
-					final int nread = buf.position();
-					if (nread > 0) {
-						//Log.d(TAG, HexDump.dumpHexString(dest, 0, Math.min(32, dest.length)));
-						return nread;
-					} else {
-						return 0;
-					}
-				} finally {
+			final UsbRequest request = new UsbRequest();
+			try {
+				request.initialize(mConnection, mReadEndpoint);
+				final ByteBuffer buf = ByteBuffer.wrap(dest);
+				if (!request.queue(buf, dest.length)) {
+					throw new IOException("Error queueing request.");
+				}
+				mUsbRequest = request;
+				final UsbRequest response = mConnection.requestWait();
+				synchronized (this) {
 					mUsbRequest = null;
-					request.close();
 				}
-			} else {
-				final int numBytesRead;
-				synchronized (mReadBufferLock) {
-					int readAmt = Math.min(dest.length, mReadBuffer.length);
-					numBytesRead = mConnection.bulkTransfer(mReadEndpoint, mReadBuffer, readAmt,
-							timeoutMillis);
-					if (numBytesRead < 0) {
-						// This sucks: we get -1 on timeout, not 0 as preferred.
-						// We *should* use UsbRequest, except it has a bug/api oversight
-						// where there is no way to determine the number of bytes read
-						// in response :\ -- http://b.android.com/28023
-						return 0;
-					}
-					System.arraycopy(mReadBuffer, 0, dest, 0, numBytesRead);
+				if (response == null) {
+					throw new IOException("Null response");
 				}
-				return numBytesRead;
+
+				final int nread = buf.position();
+				if (nread > 0) {
+					//Log.d(TAG, HexDump.dumpHexString(dest, 0, Math.min(32, dest.length)));
+					return nread;
+				} else {
+					return 0;
+				}
+			} finally {
+				mUsbRequest = null;
+				request.close();
 			}
 		}
 
diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Cp21xxSerialDriver.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Cp21xxSerialDriver.java
index 2119130..4bc07ee 100644
--- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Cp21xxSerialDriver.java
+++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Cp21xxSerialDriver.java
@@ -27,7 +27,6 @@ import android.hardware.usb.UsbDeviceConnection;
 import android.hardware.usb.UsbEndpoint;
 import android.hardware.usb.UsbInterface;
 import android.hardware.usb.UsbRequest;
-import android.os.Build;
 import android.util.Log;
 
 import java.io.IOException;
@@ -107,7 +106,6 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
         private static final int CONTROL_WRITE_DTR = 0x0100;
         private static final int CONTROL_WRITE_RTS = 0x0200;
 
-        private final Boolean mEnableAsyncReads;
         private UsbEndpoint mReadEndpoint;
         private UsbEndpoint mWriteEndpoint;
         private UsbRequest mUsbRequest;
@@ -118,7 +116,6 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
 
         public Cp21xxSerialPort(UsbDevice device, int portNumber) {
             super(device, portNumber);
-            mEnableAsyncReads = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1);
         }
 
         @Override
@@ -180,13 +177,16 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
             if (mConnection == null) {
                 throw new IOException("Already closed");
             }
-            synchronized (mEnableAsyncReads) {
+            synchronized (this) {
                 if(mUsbRequest != null) {
                     mUsbRequest.cancel();
                 }
             }
             try {
                 setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_DISABLE);
+            } catch (Exception ignored)
+            {}
+            try {
                 mConnection.close();
             } finally {
                 mConnection = null;
@@ -195,50 +195,32 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
 
         @Override
         public int read(byte[] dest, int timeoutMillis) throws IOException {
-            if (mEnableAsyncReads) {
-                final UsbRequest request = new UsbRequest();
-                try {
-                    request.initialize(mConnection, mReadEndpoint);
-                    final ByteBuffer buf = ByteBuffer.wrap(dest);
-                    if (!request.queue(buf, dest.length)) {
-                        throw new IOException("Error queueing request.");
-                    }
-                    mUsbRequest = request;
-                    final UsbRequest response = mConnection.requestWait();
-                    synchronized (mEnableAsyncReads) {
-                        mUsbRequest = null;
-                    }
-                    if (response == null) {
-                        throw new IOException("Null response");
-                    }
-
-                    final int nread = buf.position();
-                    if (nread > 0) {
-                        //Log.d(TAG, HexDump.dumpHexString(dest, 0, Math.min(32, dest.length)));
-                        return nread;
-                    } else {
-                        return 0;
-                    }
-                } finally {
+            final UsbRequest request = new UsbRequest();
+            try {
+                request.initialize(mConnection, mReadEndpoint);
+                final ByteBuffer buf = ByteBuffer.wrap(dest);
+                if (!request.queue(buf, dest.length)) {
+                    throw new IOException("Error queueing request.");
+                }
+                mUsbRequest = request;
+                final UsbRequest response = mConnection.requestWait();
+                synchronized (this) {
                     mUsbRequest = null;
-                    request.close();
                 }
-            } else {
-                final int numBytesRead;
-                synchronized (mReadBufferLock) {
-                    int readAmt = Math.min(dest.length, mReadBuffer.length);
-                    numBytesRead = mConnection.bulkTransfer(mReadEndpoint, mReadBuffer, readAmt,
-                            timeoutMillis);
-                    if (numBytesRead < 0) {
-                        // This sucks: we get -1 on timeout, not 0 as preferred.
-                        // We *should* use UsbRequest, except it has a bug/api oversight
-                        // where there is no way to determine the number of bytes read
-                        // in response :\ -- http://b.android.com/28023
-                        return 0;
-                    }
-                    System.arraycopy(mReadBuffer, 0, dest, 0, numBytesRead);
+                if (response == null) {
+                    throw new IOException("Null response");
                 }
-                return numBytesRead;
+
+                final int nread = buf.position();
+                if (nread > 0) {
+                    //Log.d(TAG, HexDump.dumpHexString(dest, 0, Math.min(32, dest.length)));
+                    return nread;
+                } else {
+                    return 0;
+                }
+            } finally {
+                mUsbRequest = null;
+                request.close();
             }
         }
 
diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/FtdiSerialDriver.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/FtdiSerialDriver.java
index 757c567..ccdc25e 100644
--- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/FtdiSerialDriver.java
+++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/FtdiSerialDriver.java
@@ -26,7 +26,6 @@ import android.hardware.usb.UsbDevice;
 import android.hardware.usb.UsbDeviceConnection;
 import android.hardware.usb.UsbEndpoint;
 import android.hardware.usb.UsbRequest;
-import android.os.Build;
 import android.util.Log;
 
 import java.io.IOException;
@@ -186,16 +185,8 @@ public class FtdiSerialDriver implements UsbSerialDriver {
 
         private int mIndex = 0;
 
-        /**
-         * Due to http://b.android.com/28023 , we cannot use UsbRequest async reads
-         * since it gives no indication of number of bytes read. Set this to
-         * {@code true} on platforms where it is fixed.
-         */
-        private final boolean mEnableAsyncReads;
-
         public FtdiSerialPort(UsbDevice device, int portNumber) {
             super(device, portNumber);
-            mEnableAsyncReads = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1);
         }
 
         @Override
@@ -286,46 +277,28 @@ public class FtdiSerialDriver implements UsbSerialDriver {
         @Override
         public int read(byte[] dest, int timeoutMillis) throws IOException {
             final UsbEndpoint endpoint = mDevice.getInterface(mPortNumber).getEndpoint(0);
-
-            if (mEnableAsyncReads) {
-                final UsbRequest request = new UsbRequest();
-                final ByteBuffer buf = ByteBuffer.wrap(dest);
-                try {
-                    request.initialize(mConnection, endpoint);
-                    if (!request.queue(buf, dest.length)) {
-                        throw new IOException("Error queueing request.");
-                    }
-
-                    final UsbRequest response = mConnection.requestWait();
-                    if (response == null) {
-                        throw new IOException("Null response");
-                    }
-                } finally {
-                    request.close();
+            final UsbRequest request = new UsbRequest();
+            final ByteBuffer buf = ByteBuffer.wrap(dest);
+            try {
+                request.initialize(mConnection, endpoint);
+                if (!request.queue(buf, dest.length)) {
+                    throw new IOException("Error queueing request.");
                 }
 
-                final int totalBytesRead = buf.position();
-                if (totalBytesRead < MODEM_STATUS_HEADER_LENGTH) {
-                    throw new IOException("Expected at least " + MODEM_STATUS_HEADER_LENGTH + " bytes");
-                }
-
-                return filterStatusBytes(dest, dest, totalBytesRead, endpoint.getMaxPacketSize());
-
-            } else {
-                final int totalBytesRead;
-
-                synchronized (mReadBufferLock) {
-                    final int readAmt = Math.min(dest.length, mReadBuffer.length);
-                    totalBytesRead = mConnection.bulkTransfer(endpoint, mReadBuffer,
-                            readAmt, timeoutMillis);
-
-                    if (totalBytesRead < MODEM_STATUS_HEADER_LENGTH) {
-                        throw new IOException("Expected at least " + MODEM_STATUS_HEADER_LENGTH + " bytes");
-                    }
-
-                    return filterStatusBytes(mReadBuffer, dest, totalBytesRead, endpoint.getMaxPacketSize());
+                final UsbRequest response = mConnection.requestWait();
+                if (response == null) {
+                    throw new IOException("Null response");
                 }
+            } finally {
+                request.close();
             }
+
+            final int totalBytesRead = buf.position();
+            if (totalBytesRead < MODEM_STATUS_HEADER_LENGTH) {
+                throw new IOException("Expected at least " + MODEM_STATUS_HEADER_LENGTH + " bytes");
+            }
+
+            return filterStatusBytes(dest, dest, totalBytesRead, endpoint.getMaxPacketSize());
         }
 
         @Override
diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/ProlificSerialDriver.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/ProlificSerialDriver.java
index 550350c..c3c8b23 100644
--- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/ProlificSerialDriver.java
+++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/ProlificSerialDriver.java
@@ -33,7 +33,6 @@ import android.hardware.usb.UsbDeviceConnection;
 import android.hardware.usb.UsbEndpoint;
 import android.hardware.usb.UsbInterface;
 import android.hardware.usb.UsbRequest;
-import android.os.Build;
 import android.util.Log;
 
 import java.io.IOException;
@@ -112,7 +111,6 @@ public class ProlificSerialDriver implements UsbSerialDriver {
 
         private int mDeviceType = DEVICE_TYPE_HX;
 
-        private final boolean mEnableAsyncReads;
         private UsbEndpoint mReadEndpoint;
         private UsbEndpoint mWriteEndpoint;
         private UsbEndpoint mInterruptEndpoint;
@@ -130,7 +128,6 @@ public class ProlificSerialDriver implements UsbSerialDriver {
 
         public ProlificSerialPort(UsbDevice device, int portNumber) {
             super(device, portNumber);
-            mEnableAsyncReads = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1);
         }
 
         @Override
@@ -373,41 +370,28 @@ public class ProlificSerialDriver implements UsbSerialDriver {
 
         @Override
         public int read(byte[] dest, int timeoutMillis) throws IOException {
-            if (mEnableAsyncReads) {
-                final UsbRequest request = new UsbRequest();
-                try {
-                    request.initialize(mConnection, mReadEndpoint);
-                    final ByteBuffer buf = ByteBuffer.wrap(dest);
-                    if (!request.queue(buf, dest.length)) {
-                        throw new IOException("Error queueing request.");
-                    }
-
-                    final UsbRequest response = mConnection.requestWait();
-                    if (response == null) {
-                        throw new IOException("Null response");
-                    }
-
-                    final int nread = buf.position();
-                    if (nread > 0) {
-                        //Log.d(TAG, HexDump.dumpHexString(dest, 0, Math.min(32, dest.length)));
-                        return nread;
-                    } else {
-                        return 0;
-                    }
-                } finally {
-                    request.close();
+            final UsbRequest request = new UsbRequest();
+            try {
+                request.initialize(mConnection, mReadEndpoint);
+                final ByteBuffer buf = ByteBuffer.wrap(dest);
+                if (!request.queue(buf, dest.length)) {
+                    throw new IOException("Error queueing request.");
                 }
-            } else {
-                synchronized (mReadBufferLock) {
-                    int readAmt = Math.min(dest.length, mReadBuffer.length);
-                    int numBytesRead = mConnection.bulkTransfer(mReadEndpoint, mReadBuffer,
-                            readAmt, timeoutMillis);
-                    if (numBytesRead < 0) {
-                        return 0;
-                    }
-                    System.arraycopy(mReadBuffer, 0, dest, 0, numBytesRead);
-                    return numBytesRead;
+
+                final UsbRequest response = mConnection.requestWait();
+                if (response == null) {
+                    throw new IOException("Null response");
                 }
+
+                final int nread = buf.position();
+                if (nread > 0) {
+                    //Log.d(TAG, HexDump.dumpHexString(dest, 0, Math.min(32, dest.length)));
+                    return nread;
+                } else {
+                    return 0;
+                }
+            } finally {
+                request.close();
             }
         }