mirror of
				https://github.com/mik3y/usb-serial-for-android
				synced 2025-10-31 02:17:23 +00:00 
			
		
		
		
	Compare commits
	
		
			60 Commits
		
	
	
		
			6ffb666b33
			...
			c608aadc59
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | c608aadc59 | ||
|  | 06faad5622 | ||
|  | 52042f8c3e | ||
|  | 068fe80c07 | ||
|  | e1018ab31c | ||
|  | 026355f61e | ||
|  | 150174573c | ||
|  | 9911e141a7 | ||
|  | 2673407f1d | ||
|  | 8584fe4cb8 | ||
|  | 0b5950c991 | ||
|  | 9f93e192ca | ||
|  | f5380975ce | ||
|  | 0a32c3f9e3 | ||
|  | 88ca3f57c4 | ||
|  | 843792001f | ||
|  | 275590027b | ||
|  | b6e1833270 | ||
|  | b794092c81 | ||
|  | b1362416f0 | ||
|  | 0c0275675f | ||
|  | cab862599d | ||
|  | 2fbceb6cc7 | ||
|  | a4ee5c2158 | ||
|  | 9bc3834eff | ||
|  | 28506a9bf9 | ||
|  | 8b9ad7efdf | ||
|  | 1245293888 | ||
|  | 26a2f9363e | ||
|  | 83646d6955 | ||
|  | 573c7e41ca | ||
|  | 880c0070cb | ||
|  | a1709c3911 | ||
|  | 9c30dc5ffa | ||
|  | b06118b156 | ||
|  | de6d5aa384 | ||
|  | 11ccb5b949 | ||
|  | d585ca8be7 | ||
|  | 2a2463cd12 | ||
|  | 80a555a189 | ||
|  | 34e6d989fd | ||
|  | 35fdeb1e13 | ||
|  | 399d3c9c2f | ||
|  | 54ff9bfa44 | ||
|  | 7aecce7943 | ||
|  | d15f4d52bb | ||
|  | fd8c155ca5 | ||
|  | 88b74d716c | ||
|  | e9a38ca891 | ||
|  | a9c835bcb0 | ||
|  | 9bd1f25773 | ||
|  | 083b9ae7fe | ||
|  | fd551970be | ||
|  | 5db45548ba | ||
|  | 85f64aff96 | ||
|  | 6c648e9f56 | ||
|  | dd1b95b852 | ||
|  | 7ea76f8899 | ||
|  | fbe64fe4be | ||
|  | 8d3326ed66 | 
							
								
								
									
										7
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @ -8,11 +8,10 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
| 
 | ||||
|     steps: | ||||
|     - uses: actions/checkout@v2 | ||||
|     - name: set up JDK 1.11 | ||||
|       uses: actions/setup-java@v2 | ||||
|     - uses: actions/checkout@v3 | ||||
|     - uses: actions/setup-java@v3 | ||||
|       with: | ||||
|         distribution: 'temurin' | ||||
|         java-version: '11' | ||||
|         java-version: '17' | ||||
|     - name: Build with Gradle | ||||
|       run: ./gradlew assembleDebug | ||||
|  | ||||
							
								
								
									
										5
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -2,3 +2,8 @@ caches | ||||
| codeStyles | ||||
| libraries | ||||
| workspace.xml | ||||
| androidTestResultsUserPreferences.xml | ||||
| appInsightsSettings.xml | ||||
| deploymentTargetDropDown.xml | ||||
| deploymentTargetSelector.xml | ||||
| other.xml | ||||
|  | ||||
							
								
								
									
										6
									
								
								.idea/AndroidProjectSystem.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.idea/AndroidProjectSystem.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="AndroidProjectSystem"> | ||||
|     <option name="providerId" value="com.android.tools.idea.GradleProjectSystem" /> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										2
									
								
								.idea/compiler.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								.idea/compiler.xml
									
									
									
										generated
									
									
									
								
							| @ -1,6 +1,6 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="CompilerConfiguration"> | ||||
|     <bytecodeTargetLevel target="11" /> | ||||
|     <bytecodeTargetLevel target="21" /> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										12
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										12
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
								
							| @ -6,7 +6,7 @@ | ||||
|     <option name="myDefaultNotNull" value="androidx.annotation.NonNull" /> | ||||
|     <option name="myNullables"> | ||||
|       <value> | ||||
|         <list size="15"> | ||||
|         <list size="18"> | ||||
|           <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" /> | ||||
|           <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" /> | ||||
|           <item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" /> | ||||
| @ -22,12 +22,15 @@ | ||||
|           <item index="12" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.Nullable" /> | ||||
|           <item index="13" class="java.lang.String" itemvalue="io.reactivex.annotations.Nullable" /> | ||||
|           <item index="14" class="java.lang.String" itemvalue="io.reactivex.rxjava3.annotations.Nullable" /> | ||||
|           <item index="15" class="java.lang.String" itemvalue="org.jspecify.nullness.Nullable" /> | ||||
|           <item index="16" class="java.lang.String" itemvalue="jakarta.annotation.Nullable" /> | ||||
|           <item index="17" class="java.lang.String" itemvalue="org.jspecify.annotations.Nullable" /> | ||||
|         </list> | ||||
|       </value> | ||||
|     </option> | ||||
|     <option name="myNotNulls"> | ||||
|       <value> | ||||
|         <list size="14"> | ||||
|         <list size="17"> | ||||
|           <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" /> | ||||
|           <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" /> | ||||
|           <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" /> | ||||
| @ -42,11 +45,14 @@ | ||||
|           <item index="11" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.NonNull" /> | ||||
|           <item index="12" class="java.lang.String" itemvalue="io.reactivex.annotations.NonNull" /> | ||||
|           <item index="13" class="java.lang.String" itemvalue="io.reactivex.rxjava3.annotations.NonNull" /> | ||||
|           <item index="14" class="java.lang.String" itemvalue="jakarta.annotation.Nonnull" /> | ||||
|           <item index="15" class="java.lang.String" itemvalue="org.jspecify.nullness.NonNull" /> | ||||
|           <item index="16" class="java.lang.String" itemvalue="org.jspecify.annotations.NonNull" /> | ||||
|         </list> | ||||
|       </value> | ||||
|     </option> | ||||
|   </component> | ||||
|   <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK"> | ||||
|   <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK"> | ||||
|     <output url="file://$PROJECT_DIR$/build/classes" /> | ||||
|   </component> | ||||
|   <component name="ProjectType"> | ||||
|  | ||||
							
								
								
									
										17
									
								
								.idea/runConfigurations.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								.idea/runConfigurations.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="RunConfigurationProducerService"> | ||||
|     <option name="ignoredProducers"> | ||||
|       <set> | ||||
|         <option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" /> | ||||
|         <option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" /> | ||||
|         <option value="com.intellij.execution.junit.PatternConfigurationProducer" /> | ||||
|         <option value="com.intellij.execution.junit.TestInClassConfigurationProducer" /> | ||||
|         <option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" /> | ||||
|         <option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" /> | ||||
|         <option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" /> | ||||
|         <option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" /> | ||||
|       </set> | ||||
|     </option> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										44
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										44
									
								
								README.md
									
									
									
									
									
								
							| @ -1,6 +1,6 @@ | ||||
| [](https://github.com/mik3y/usb-serial-for-android/actions) | ||||
| [](https://jitpack.io/#mik3y/usb-serial-for-android) | ||||
| [](https://www.codacy.com/gh/kai-morich/usb-serial-for-android-mik3y/dashboard?utm_source=github.com&utm_medium=referral&utm_content=kai-morich/usb-serial-for-android-mik3y&utm_campaign=Badge_Grade) | ||||
| [](https://app.codacy.com/gh/kai-morich/usb-serial-for-android-mik3y/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) | ||||
| [](https://codecov.io/gh/mik3y/usb-serial-for-android) | ||||
| 
 | ||||
| # usb-serial-for-android | ||||
| @ -37,10 +37,15 @@ dependencyResolutionManagement { | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| If using gradle kotlin  use line | ||||
| ```gradle.kts | ||||
|         maven(url = "https://jitpack.io") | ||||
| ``` | ||||
| 
 | ||||
| Add library to dependencies | ||||
| ```gradle | ||||
| dependencies { | ||||
|     implementation 'com.github.mik3y:usb-serial-for-android:3.4.6' | ||||
|     implementation 'com.github.mik3y:usb-serial-for-android:3.9.0' | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| @ -110,9 +115,10 @@ For a simple example, see | ||||
| [UsbSerialExamples](https://github.com/mik3y/usb-serial-for-android/blob/master/usbSerialExamples) | ||||
| folder in this project. | ||||
| 
 | ||||
| For a more complete example with background service to stay connected while | ||||
| the app is not visible or rotating, see separate github project  | ||||
| [SimpleUsbTerminal](https://github.com/kai-morich/SimpleUsbTerminal). | ||||
| See separate github project [SimpleUsbTerminal](https://github.com/kai-morich/SimpleUsbTerminal)  | ||||
| for a more complete example with: | ||||
| * Background service to stay connected while the app is not visible or rotating | ||||
| * Flow control  | ||||
| 
 | ||||
| ## Probing for Unrecognized Devices | ||||
| 
 | ||||
| @ -124,22 +130,23 @@ new device or for one using a custom VID/PID pair. | ||||
| UsbSerialProber is a class to help you find and instantiate compatible | ||||
| UsbSerialDrivers from the tree of connected UsbDevices.  Normally, you will use | ||||
| the default prober returned by ``UsbSerialProber.getDefaultProber()``, which | ||||
| uses the built-in list of well-known VIDs and PIDs that are supported by our | ||||
| drivers. | ||||
| uses USB interface types and the built-in list of well-known VIDs and PIDs that | ||||
| are supported by our drivers. | ||||
| 
 | ||||
| To use your own set of rules, create and use a custom prober: | ||||
| 
 | ||||
| ```java | ||||
| // Probe for our custom CDC devices, which use VID 0x1234 | ||||
| // and PIDS 0x0001 and 0x0002. | ||||
| // Probe for our custom FTDI device, which use VID 0x1234 and PID 0x0001 and 0x0002. | ||||
| ProbeTable customTable = new ProbeTable(); | ||||
| customTable.addProduct(0x1234, 0x0001, CdcAcmSerialDriver.class); | ||||
| customTable.addProduct(0x1234, 0x0002, CdcAcmSerialDriver.class); | ||||
| customTable.addProduct(0x1234, 0x0001, FtdiSerialDriver.class); | ||||
| customTable.addProduct(0x1234, 0x0002, FtdiSerialDriver.class); | ||||
| 
 | ||||
| UsbSerialProber prober = new UsbSerialProber(customTable); | ||||
| List<UsbSerialDriver> drivers = prober.findAllDrivers(usbManager); | ||||
| // ... | ||||
| ``` | ||||
| *Note*: as of v3.5.0 this library detects CDC/ACM devices by USB interface types instead of fixed VID+PID, | ||||
| so custom probers are typically not required any more for CDC/ACM devices. | ||||
| 
 | ||||
| Of course, nothing requires you to use UsbSerialProber at all: you can | ||||
| instantiate driver classes directly if you know what you're doing; just supply | ||||
| @ -147,16 +154,21 @@ a compatible UsbDevice. | ||||
| 
 | ||||
| ## Compatible Devices | ||||
| 
 | ||||
| This library supports USB to serial converter chips: | ||||
| This library supports USB to serial converter chips with specific drivers | ||||
| * FTDI FT232R, FT232H, FT2232H, FT4232H, FT230X, FT231X, FT234XD | ||||
| * Prolific PL2303 | ||||
| * Silabs CP2102 and all other CP210x | ||||
| * Qinheng CH340, CH341A, CH9102 | ||||
| * Silabs CP2102, CP210* | ||||
| * Qinheng CH340, CH341A | ||||
| 
 | ||||
| and devices implementing the CDC/ACM protocol like | ||||
| some other device specific drivers | ||||
| * GsmModem devices, e.g. for Unisoc based Fibocom GSM modems | ||||
| * Chrome OS CCD (Closed Case Debugging) | ||||
| 
 | ||||
| and devices implementing the generic CDC/ACM protocol like | ||||
| * Qinheng CH9102 | ||||
| * Microchip MCP2221 | ||||
| * Arduino using ATmega32U4 | ||||
| * Digispark using V-USB software USB | ||||
| * BBC micro:bit using ARM mbed DAPLink firmware | ||||
| * ... | ||||
| 
 | ||||
| ## Help & Discussion | ||||
|  | ||||
| @ -6,7 +6,7 @@ buildscript { | ||||
|         google() | ||||
|     } | ||||
|     dependencies { | ||||
|         classpath 'com.android.tools.build:gradle:7.0.4' | ||||
|         classpath 'com.android.tools.build:gradle:8.9.1' | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										5
									
								
								codecov.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								codecov.yml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| codecov: | ||||
|   max_report_age: off | ||||
|   require_ci_to_pass: no | ||||
|   notify: | ||||
|     wait_for_ci: no | ||||
| @ -1,2 +1,3 @@ | ||||
| android.enableJetifier=true | ||||
| android.useAndroidX=true | ||||
| android.defaults.buildfeatures.buildconfig=true | ||||
|  | ||||
							
								
								
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							| @ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME | ||||
| distributionPath=wrapper/dists | ||||
| zipStoreBase=GRADLE_USER_HOME | ||||
| zipStorePath=wrapper/dists | ||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip | ||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| jdk: | ||||
|   - openjdk11 | ||||
|   - openjdk17 | ||||
| install: | ||||
|   - ./gradlew :usbSerialForAndroid:publishToMavenLocal | ||||
|  | ||||
							
								
								
									
										6
									
								
								test/pi_pico/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								test/pi_pico/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| 
 | ||||
| # `tinyusb_dev_cdc_dual_ports.uf2` | ||||
| 
 | ||||
| compiled from `C:/Program Files/Raspberry Pi/Pico SDK v1.5.1/pico-sdk/lib/tinyusb/examples/device/cdc_dual_ports` | ||||
| to `C:/Users/` _user_`/Documents/Pico-v1.5.1/pico-examples/build/usb/device/tinyusb_device_examples/cdc_dual_ports/tinyusb_dev_cdc_dual_ports.uf2` | ||||
| 
 | ||||
							
								
								
									
										
											BIN
										
									
								
								test/pi_pico/tinyusb_dev_cdc_dual_ports.uf2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								test/pi_pico/tinyusb_dev_cdc_dual_ports.uf2
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| @ -3,7 +3,7 @@ plugins { | ||||
| } | ||||
| 
 | ||||
| android { | ||||
|     compileSdkVersion 32 | ||||
|     compileSdkVersion 35 | ||||
| 
 | ||||
|     compileOptions { | ||||
|         sourceCompatibility JavaVersion.VERSION_1_8 | ||||
| @ -12,7 +12,7 @@ android { | ||||
| 
 | ||||
|     defaultConfig { | ||||
|         minSdkVersion 17 | ||||
|         targetSdkVersion 32 | ||||
|         targetSdkVersion 35 | ||||
|         vectorDrawables.useSupportLibrary = true | ||||
| 
 | ||||
|         missingDimensionStrategy 'device', 'anyDevice' | ||||
| @ -23,10 +23,11 @@ android { | ||||
|             minifyEnabled true | ||||
|         } | ||||
|     } | ||||
|     namespace 'com.hoho.android.usbserial.examples' | ||||
| } | ||||
| 
 | ||||
| dependencies { | ||||
|     implementation project(':usbSerialForAndroid') | ||||
|     implementation 'androidx.appcompat:appcompat:1.4.1' | ||||
|     implementation 'com.google.android.material:material:1.5.0' | ||||
|     implementation 'androidx.appcompat:appcompat:1.6.1' // later versions have minsdk 21 | ||||
|     implementation 'com.google.android.material:material:1.11.0' // later versions have minsdk 19 | ||||
| } | ||||
|  | ||||
| @ -1,7 +1,6 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     package="com.hoho.android.usbserial.examples"> | ||||
|     xmlns:tools="http://schemas.android.com/tools"> | ||||
| 
 | ||||
|     <!-- mipmap/ic_launcher created with Android Studio -> New -> Image Asset using @color/colorPrimary and USB clip art --> | ||||
|     <application | ||||
| @ -15,7 +14,6 @@ | ||||
|              when the settings activity is currently shown --> | ||||
|         <activity | ||||
|             android:name=".MainActivity" | ||||
|             android:label="@string/app_name" | ||||
|             android:launchMode="singleTask" | ||||
|             android:windowSoftInputMode="stateHidden|adjustResize" | ||||
|             android:exported="true"> | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| package com.hoho.android.usbserial.examples; | ||||
| 
 | ||||
| import com.hoho.android.usbserial.driver.CdcAcmSerialDriver; | ||||
| import com.hoho.android.usbserial.driver.FtdiSerialDriver; | ||||
| import com.hoho.android.usbserial.driver.ProbeTable; | ||||
| import com.hoho.android.usbserial.driver.UsbSerialProber; | ||||
| 
 | ||||
| @ -14,7 +14,8 @@ class CustomProber { | ||||
| 
 | ||||
|     static UsbSerialProber getCustomProber() { | ||||
|         ProbeTable customTable = new ProbeTable(); | ||||
|         customTable.addProduct(0x16d0, 0x087e, CdcAcmSerialDriver.class); // e.g. Digispark CDC | ||||
|         customTable.addProduct(0x1234, 0x0001, FtdiSerialDriver.class); // e.g. device with custom VID+PID | ||||
|         customTable.addProduct(0x1234, 0x0002, FtdiSerialDriver.class); // e.g. device with custom VID+PID | ||||
|         return new UsbSerialProber(customTable); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -28,6 +28,7 @@ import android.widget.ToggleButton; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.core.content.ContextCompat; | ||||
| import androidx.fragment.app.Fragment; | ||||
| 
 | ||||
| import com.hoho.android.usbserial.driver.UsbSerialDriver; | ||||
| @ -89,12 +90,22 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag | ||||
|         withIoManager = getArguments().getBoolean("withIoManager"); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onStart() { | ||||
|         super.onStart(); | ||||
|         ContextCompat.registerReceiver(getActivity(), broadcastReceiver, new IntentFilter(INTENT_ACTION_GRANT_USB), ContextCompat.RECEIVER_NOT_EXPORTED); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onStop() { | ||||
|         getActivity().unregisterReceiver(broadcastReceiver); | ||||
|         super.onStop(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onResume() { | ||||
|         super.onResume(); | ||||
|         getActivity().registerReceiver(broadcastReceiver, new IntentFilter(INTENT_ACTION_GRANT_USB)); | ||||
| 
 | ||||
|         if(usbPermission == UsbPermission.Unknown || usbPermission == UsbPermission.Granted) | ||||
|         if(!connected && (usbPermission == UsbPermission.Unknown || usbPermission == UsbPermission.Granted)) | ||||
|             mainLooper.post(this::connect); | ||||
|     } | ||||
| 
 | ||||
| @ -104,7 +115,6 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag | ||||
|             status("disconnected"); | ||||
|             disconnect(); | ||||
|         } | ||||
|         getActivity().unregisterReceiver(broadcastReceiver); | ||||
|         super.onPause(); | ||||
|     } | ||||
| 
 | ||||
| @ -204,7 +214,7 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag | ||||
|             status("connection failed: no driver for device"); | ||||
|             return; | ||||
|         } | ||||
|         if(driver.getPorts().size() < portNum) { | ||||
|         if(portNum >= driver.getPorts().size()) { | ||||
|             status("connection failed: not enough ports at device"); | ||||
|             return; | ||||
|         } | ||||
| @ -212,8 +222,10 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag | ||||
|         UsbDeviceConnection usbConnection = usbManager.openDevice(driver.getDevice()); | ||||
|         if(usbConnection == null && usbPermission == UsbPermission.Unknown && !usbManager.hasPermission(driver.getDevice())) { | ||||
|             usbPermission = UsbPermission.Requested; | ||||
|             int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0; | ||||
|             PendingIntent usbPermissionIntent = PendingIntent.getBroadcast(getActivity(), 0, new Intent(INTENT_ACTION_GRANT_USB), flags); | ||||
|             int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_MUTABLE : 0; | ||||
|             Intent intent = new Intent(INTENT_ACTION_GRANT_USB); | ||||
|             intent.setPackage(getActivity().getPackageName()); | ||||
|             PendingIntent usbPermissionIntent = PendingIntent.getBroadcast(getActivity(), 0, intent, flags); | ||||
|             usbManager.requestPermission(driver.getDevice(), usbPermissionIntent); | ||||
|             return; | ||||
|         } | ||||
| @ -227,7 +239,11 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag | ||||
| 
 | ||||
|         try { | ||||
|             usbSerialPort.open(usbConnection); | ||||
|             try{ | ||||
|                 usbSerialPort.setParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE); | ||||
|             }catch (UnsupportedOperationException e){ | ||||
|                 status("unsupport setparameters"); | ||||
|             } | ||||
|             if(withIoManager) { | ||||
|                 usbIoManager = new SerialInputOutputManager(usbSerialPort, this); | ||||
|                 usbIoManager.start(); | ||||
| @ -351,7 +367,7 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag | ||||
|                 cdBtn.setChecked(controlLines.contains(UsbSerialPort.ControlLine.CD)); | ||||
|                 riBtn.setChecked(controlLines.contains(UsbSerialPort.ControlLine.RI)); | ||||
|                 mainLooper.postDelayed(runnable, refreshInterval); | ||||
|             } catch (IOException e) { | ||||
|             } catch (Exception e) { | ||||
|                 status("getControlLines() failed: " + e.getMessage() + " -> stopped control line refresh"); | ||||
|             } | ||||
|         } | ||||
| @ -368,8 +384,15 @@ public class TerminalFragment extends Fragment implements SerialInputOutputManag | ||||
|                 if (!controlLines.contains(UsbSerialPort.ControlLine.CD))   cdBtn.setVisibility(View.INVISIBLE); | ||||
|                 if (!controlLines.contains(UsbSerialPort.ControlLine.RI))   riBtn.setVisibility(View.INVISIBLE); | ||||
|                 run(); | ||||
|             } catch (IOException e) { | ||||
|             } catch (Exception e) { | ||||
|                 Toast.makeText(getActivity(), "getSupportedControlLines() failed: " + e.getMessage(), Toast.LENGTH_SHORT).show(); | ||||
|                 rtsBtn.setVisibility(View.INVISIBLE); | ||||
|                 ctsBtn.setVisibility(View.INVISIBLE); | ||||
|                 dtrBtn.setVisibility(View.INVISIBLE); | ||||
|                 dsrBtn.setVisibility(View.INVISIBLE); | ||||
|                 cdBtn.setVisibility(View.INVISIBLE); | ||||
|                 cdBtn.setVisibility(View.INVISIBLE); | ||||
|                 riBtn.setVisibility(View.INVISIBLE); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -4,16 +4,16 @@ plugins { | ||||
| } | ||||
| 
 | ||||
| android { | ||||
|     compileSdkVersion 32 | ||||
|     compileSdkVersion 35 | ||||
| 
 | ||||
|     defaultConfig { | ||||
|         minSdkVersion 17 | ||||
|         targetSdkVersion 32 | ||||
|         targetSdkVersion 35 | ||||
|         consumerProguardFiles 'proguard-rules.pro' | ||||
|          | ||||
|         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | ||||
|         testInstrumentationRunnerArguments = [                    // Raspi   Windows   LinuxVM   ... | ||||
|                 'rfc2217_server_host': '192.168.0.186', | ||||
|                 'rfc2217_server_host': '192.168.0.78', | ||||
|                 'rfc2217_server_nonstandard_baudrates': 'true',   // true    false     false | ||||
|         ] | ||||
|     } | ||||
| @ -21,16 +21,24 @@ android { | ||||
|         sourceCompatibility JavaVersion.VERSION_1_8 | ||||
|         targetCompatibility JavaVersion.VERSION_1_8 | ||||
|     } | ||||
|     namespace 'com.hoho.android.usbserial' | ||||
|     publishing { | ||||
|         // if coverage is enabled, change 'release' to 'anyDeviceRelease' or comment out publishing rule | ||||
|         singleVariant('release') { | ||||
|             withSourcesJar() | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| dependencies { | ||||
|     implementation "androidx.annotation:annotation:1.3.0" | ||||
|     implementation 'androidx.test:core:1.4.0' | ||||
|     implementation "androidx.annotation:annotation:1.9.1" | ||||
|     testImplementation 'junit:junit:4.13.2' | ||||
|     testImplementation 'org.mockito:mockito-core:1.10.19' | ||||
|     androidTestImplementation 'androidx.test:runner:1.4.0' | ||||
|     androidTestImplementation 'commons-net:commons-net:3.8.0' | ||||
|     androidTestImplementation 'org.apache.commons:commons-lang3:3.11' | ||||
|     testImplementation 'org.mockito:mockito-core:5.15.2' | ||||
|     androidTestImplementation 'androidx.appcompat:appcompat:1.6.1' // later versions have minsdk 21 | ||||
|     androidTestImplementation 'androidx.test:core:1.5.0' // later versions have minsdk 19 | ||||
|     androidTestImplementation 'androidx.test:runner:1.5.2' // later versions have minsdk 19 | ||||
|     androidTestImplementation 'commons-net:commons-net:3.9.0' // later versions fail on old Android devices with missing java.time.Duration class | ||||
|     androidTestImplementation 'org.apache.commons:commons-lang3:3.14.0' | ||||
| } | ||||
| 
 | ||||
| // gradle task: publishToMavenLocal | ||||
| @ -38,21 +46,15 @@ project.afterEvaluate { | ||||
|     publishing { | ||||
|         publications { | ||||
|             release(MavenPublication) { | ||||
|                 from components.release // change to anyDeviceRelease if coverage is enabled | ||||
|                 artifact androidSourcesJar | ||||
|                 from components.release | ||||
| 
 | ||||
|                 // values used for local maven repo, jitpack uses github release: | ||||
|                 groupId 'com.github.mik3y' | ||||
|                 artifactId 'usb-serial-for-android' | ||||
|                 version 'v3.5.0beta' | ||||
|                 version '3.8.0beta' | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| task androidSourcesJar(type: Jar) { | ||||
|     classifier 'sources' | ||||
|     from android.sourceSets.main.java.srcDirs | ||||
| } | ||||
| 
 | ||||
| //apply from: 'coverage.gradle' | ||||
|  | ||||
| @ -2,13 +2,13 @@ | ||||
| apply plugin: 'jacoco' | ||||
| 
 | ||||
| android { | ||||
|     flavorDimensions 'device' | ||||
|     flavorDimensions += 'device' | ||||
|     productFlavors { | ||||
|         anyDevice { | ||||
|             // Used as fallback in usbSerialExample/build.gradle -> missingDimensionStrategy, but not for coverage report | ||||
|             dimension 'device' | ||||
|         } | ||||
|         arduino { | ||||
|         mcp2221 { | ||||
|             dimension 'device' | ||||
|             testInstrumentationRunnerArguments = ['test_device_driver': 'CdcAcm'] | ||||
|         } | ||||
| @ -18,7 +18,7 @@ android { | ||||
|         } | ||||
|         cp2102 { // and cp2105 first port | ||||
|             dimension 'device' | ||||
|             testInstrumentationRunnerArguments = ['test_device_driver': 'Cp21xx'] | ||||
|             testInstrumentationRunnerArguments = ['test_device_driver': 'Cp21xx', 'test_device_port': '0'] | ||||
|         } | ||||
|         cp2105 { // second port | ||||
|             dimension 'device' | ||||
| @ -26,7 +26,7 @@ android { | ||||
|         } | ||||
|         ft232 { // and ft2232 first port | ||||
|             dimension 'device' | ||||
|             testInstrumentationRunnerArguments = ['test_device_driver': 'Ftdi'] | ||||
|             testInstrumentationRunnerArguments = ['test_device_driver': 'Ftdi', 'test_device_port': '0'] | ||||
|         } | ||||
|         ft2232 { // second port | ||||
|             dimension 'device' | ||||
| @ -48,7 +48,8 @@ android { | ||||
| 
 | ||||
|     buildTypes { | ||||
|         debug { | ||||
|             testCoverageEnabled true // disable for testAnyDeviceDebugUnitTest | ||||
|             enableUnitTestCoverage true | ||||
|             enableAndroidTestCoverage true | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -60,20 +61,18 @@ project.gradle.taskGraph.whenReady { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| task jacocoTestReport(type: JacocoReport , | ||||
|         dependsOn: ['compileAnyDeviceDebugSources' /*'testAnyDeviceDebugUnitTest' , 'create<device>DebugCoverageReport'*/]) { | ||||
|     reports { | ||||
|         xml.enabled = true | ||||
|         html.enabled = true | ||||
|     } | ||||
| 
 | ||||
| task jacocoTestReport(type: JacocoReport, dependsOn: ['compileAnyDeviceDebugSources' | ||||
|                                                   /*, 'testAnyDeviceDebugUnitTest' */ | ||||
|                                                   /*, 'create<device>DebugCoverageReport' */]) { | ||||
|     def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*'] | ||||
|     def debugTree = fileTree(dir: "$project.buildDir/intermediates/javac/anyDeviceDebug", excludes: fileFilter) | ||||
|     def mainSrc = "$project.projectDir/src/main/java" | ||||
| 
 | ||||
|     reports.xml.required = true | ||||
|     sourceDirectories.from files([mainSrc]) | ||||
|     classDirectories.from files([debugTree]) | ||||
|     executionData.from fileTree(dir: project.buildDir, includes: [ | ||||
|             'jacoco/testAnyDeviceDebugUnitTest.exec', 'outputs/code_coverage/*AndroidTest/connected/*.ec' | ||||
|             'outputs/unit_test_code_coverage/anyDeviceDebugUnitTest/testAnyDeviceDebugUnitTest.exec', | ||||
|             'outputs/code_coverage/*DebugAndroidTest/connected/*/coverage.ec' | ||||
|     ]) | ||||
| } | ||||
|  | ||||
| @ -1,6 +1,5 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|       package="com.hoho.android.usbserial" | ||||
|       android:versionCode="1" | ||||
|       android:versionName="1.0"> | ||||
|     <uses-permission android:name="android.permission.INTERNET"/> | ||||
|  | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -18,6 +18,7 @@ import com.hoho.android.usbserial.driver.CommonUsbSerialPort; | ||||
| import com.hoho.android.usbserial.driver.Cp21xxSerialDriver; | ||||
| import com.hoho.android.usbserial.driver.FtdiSerialDriver; | ||||
| import com.hoho.android.usbserial.driver.ProlificSerialDriver; | ||||
| import com.hoho.android.usbserial.driver.ProlificSerialPortWrapper; | ||||
| import com.hoho.android.usbserial.driver.UsbId; | ||||
| import com.hoho.android.usbserial.driver.UsbSerialDriver; | ||||
| import com.hoho.android.usbserial.driver.UsbSerialPort; | ||||
| @ -32,6 +33,8 @@ import java.util.concurrent.Callable; | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| 
 | ||||
| import androidx.core.content.ContextCompat; | ||||
| 
 | ||||
| public class UsbWrapper implements SerialInputOutputManager.Listener { | ||||
| 
 | ||||
|     public final static int     USB_READ_WAIT = 500; | ||||
| @ -56,11 +59,13 @@ public class UsbWrapper implements SerialInputOutputManager.Listener { | ||||
| 
 | ||||
|     // device properties | ||||
|     public boolean isCp21xxRestrictedPort; // second port of Cp2105 has limited dataBits, stopBits, parity | ||||
|     public boolean outputLinesSupported; | ||||
|     public boolean inputLinesSupported; | ||||
|     public boolean inputLinesConnected; | ||||
|     public boolean inputLinesOnlyRtsCts; | ||||
|     public int writePacketSize = -1; | ||||
|     public int writeBufferSize = -1; | ||||
|     public int readBufferSize = -1; | ||||
| 
 | ||||
|     public UsbWrapper(Context context, UsbSerialDriver serialDriver, int devicePort) { | ||||
|         this.context = context; | ||||
| @ -84,10 +89,12 @@ public class UsbWrapper implements SerialInputOutputManager.Listener { | ||||
|                     granted[0] = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false); | ||||
|                 } | ||||
|             }; | ||||
|             int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0; | ||||
|             PendingIntent permissionIntent = PendingIntent.getBroadcast(context, 0, new Intent("com.android.example.USB_PERMISSION"), flags); | ||||
|             int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_MUTABLE : 0; | ||||
|             Intent intent = new Intent("com.android.example.USB_PERMISSION"); | ||||
|             intent.setPackage(context.getPackageName()); | ||||
|             PendingIntent permissionIntent = PendingIntent.getBroadcast(context, 0, intent, flags); | ||||
|             IntentFilter filter = new IntentFilter("com.android.example.USB_PERMISSION"); | ||||
|             context.registerReceiver(usbReceiver, filter); | ||||
|             ContextCompat.registerReceiver(context, usbReceiver, filter, Context.RECEIVER_NOT_EXPORTED); | ||||
|             usbManager.requestPermission(serialDriver.getDevice(), permissionIntent); | ||||
|             for(int i=0; i<5000; i++) { | ||||
|                 if(granted[0] != null) break; | ||||
| @ -99,9 +106,10 @@ public class UsbWrapper implements SerialInputOutputManager.Listener { | ||||
| 
 | ||||
|         // extract some device properties: | ||||
|         isCp21xxRestrictedPort = serialDriver instanceof Cp21xxSerialDriver && serialDriver.getPorts().size()==2 && serialPort.getPortNumber() == 1; | ||||
|         // output lines are supported by all drivers | ||||
|         // input lines are supported by all drivers except CDC | ||||
|         // output lines are supported by all common drivers | ||||
|         // input lines are supported by all common drivers except CDC | ||||
|         if (serialDriver instanceof FtdiSerialDriver) { | ||||
|             outputLinesSupported = true; | ||||
|             inputLinesSupported = true; | ||||
|             if(serialDriver.getDevice().getProductId() == UsbId.FTDI_FT2232H) | ||||
|                 inputLinesConnected = true; // I only have 74LS138 connected at FT2232, not at FT232 | ||||
| @ -110,16 +118,21 @@ public class UsbWrapper implements SerialInputOutputManager.Listener { | ||||
|                 inputLinesOnlyRtsCts = true; // I only test with FT230X that has only these 2 control lines. DTR is silently ignored | ||||
|             } | ||||
|         } else if (serialDriver instanceof Cp21xxSerialDriver) { | ||||
|             outputLinesSupported = true; | ||||
|             inputLinesSupported = true; | ||||
|             if(serialDriver.getPorts().size() == 1) | ||||
|                 inputLinesConnected = true; // I only have 74LS138 connected at CP2102, not at CP2105 | ||||
|         } else if (serialDriver instanceof ProlificSerialDriver) { | ||||
|             outputLinesSupported = true; | ||||
|             inputLinesSupported = true; | ||||
|             inputLinesConnected = true; | ||||
|         } else if (serialDriver instanceof Ch34xSerialDriver) { | ||||
|             outputLinesSupported = true; | ||||
|             inputLinesSupported = true; | ||||
|             if(serialDriver.getDevice().getProductId() == UsbId.QINHENG_CH340) | ||||
|                 inputLinesConnected = true;  // I only have 74LS138 connected at CH340, not connected at CH341A | ||||
|         } else if (serialDriver instanceof CdcAcmSerialDriver) { | ||||
|             outputLinesSupported = true; | ||||
|         } | ||||
| 
 | ||||
|         if (serialDriver instanceof Cp21xxSerialDriver) { | ||||
| @ -136,10 +149,18 @@ public class UsbWrapper implements SerialInputOutputManager.Listener { | ||||
|                 case 2: writePacketSize = 512; writeBufferSize = 4096; break; | ||||
|                 case 4: writePacketSize = 512; writeBufferSize = 2048; break; | ||||
|             } | ||||
|             if(serialDriver.getDevice().getProductId() == UsbId.FTDI_FT231X) | ||||
|                 writeBufferSize = 512; | ||||
|         } else if (serialDriver instanceof CdcAcmSerialDriver) { | ||||
|             writePacketSize = 64; writeBufferSize = 128; | ||||
|             writePacketSize = 16; writeBufferSize = 32; // MCP2221 values, other devices might be different | ||||
|         } | ||||
| 
 | ||||
|         readBufferSize = writeBufferSize; | ||||
|         if (serialDriver instanceof Cp21xxSerialDriver && serialDriver.getPorts().size() == 2) { | ||||
|             readBufferSize = 256; | ||||
|         } else if (serialDriver instanceof FtdiSerialDriver && serialDriver.getPorts().size() == 1 && serialDriver.getDevice().getProductId() != UsbId.FTDI_FT231X) { | ||||
|             readBufferSize = 256; | ||||
|         } // PL2303 HXN checked in open() | ||||
|     } | ||||
| 
 | ||||
|     public void tearDown() { | ||||
| @ -168,6 +189,8 @@ public class UsbWrapper implements SerialInputOutputManager.Listener { | ||||
|                 if(!flags.contains(OpenCloseFlags.NO_CONTROL_LINE_INIT)) { | ||||
|                     serialPort.setDTR(false); | ||||
|                     serialPort.setRTS(false); | ||||
|                     if (serialPort.getFlowControl() != UsbSerialPort.FlowControl.NONE) | ||||
|                         serialPort.setFlowControl(UsbSerialPort.FlowControl.NONE); | ||||
|                 } | ||||
|             } catch (Exception ignored) { | ||||
|             } | ||||
| @ -217,6 +240,10 @@ public class UsbWrapper implements SerialInputOutputManager.Listener { | ||||
|             readBuffer.clear(); | ||||
|         } | ||||
|         readError = null; | ||||
| 
 | ||||
|         if (serialDriver instanceof ProlificSerialDriver && ProlificSerialPortWrapper.isDeviceTypeHxn(serialPort)) { | ||||
|             readBufferSize = 768; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public void waitForIoManagerStarted() throws IOException { | ||||
| @ -231,12 +258,15 @@ public class UsbWrapper implements SerialInputOutputManager.Listener { | ||||
|         throw new IOException("IoManager not started"); | ||||
|     } | ||||
| 
 | ||||
|     public boolean hasIoManagerThread() { | ||||
|     public boolean hasIoManagerThreads() { | ||||
|         int c = 0; | ||||
|         for (Thread thread : Thread.getAllStackTraces().keySet()) { | ||||
|             if (thread.getName().equals(SerialInputOutputManager.class.getSimpleName())) | ||||
|                 return true; | ||||
|             if (thread.getName().equals(SerialInputOutputManager.class.getSimpleName() + "_read")) | ||||
|                 c += 1; | ||||
|             if (thread.getName().equals(SerialInputOutputManager.class.getSimpleName() + "_write")) | ||||
|                 c += 1; | ||||
|         } | ||||
|         return false; | ||||
|         return c == 2; | ||||
|     } | ||||
| 
 | ||||
|     // wait full time | ||||
|  | ||||
| @ -1,4 +1,3 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|       package="com.hoho.android.usbserial"> | ||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
| </manifest> | ||||
|  | ||||
| @ -8,11 +8,13 @@ package com.hoho.android.usbserial.driver; | ||||
| 
 | ||||
| import android.hardware.usb.UsbConstants; | ||||
| import android.hardware.usb.UsbDevice; | ||||
| import android.hardware.usb.UsbDeviceConnection; | ||||
| import android.hardware.usb.UsbEndpoint; | ||||
| import android.hardware.usb.UsbInterface; | ||||
| import android.util.Log; | ||||
| 
 | ||||
| import com.hoho.android.usbserial.util.HexDump; | ||||
| import com.hoho.android.usbserial.util.UsbUtils; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.EnumSet; | ||||
| @ -30,6 +32,8 @@ import java.util.Map; | ||||
|  */ | ||||
| public class CdcAcmSerialDriver implements UsbSerialDriver { | ||||
| 
 | ||||
|     public static final int USB_SUBCLASS_ACM = 2; | ||||
| 
 | ||||
|     private final String TAG = CdcAcmSerialDriver.class.getSimpleName(); | ||||
| 
 | ||||
|     private final UsbDevice mDevice; | ||||
| @ -38,16 +42,8 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { | ||||
|     public CdcAcmSerialDriver(UsbDevice device) { | ||||
|         mDevice = device; | ||||
|         mPorts = new ArrayList<>(); | ||||
| 
 | ||||
|         int controlInterfaceCount = 0; | ||||
|         int dataInterfaceCount = 0; | ||||
|         for( int i = 0; i < device.getInterfaceCount(); i++) { | ||||
|             if(device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_COMM) | ||||
|                 controlInterfaceCount++; | ||||
|             if(device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) | ||||
|                 dataInterfaceCount++; | ||||
|         } | ||||
|         for( int port = 0; port < Math.min(controlInterfaceCount, dataInterfaceCount); port++) { | ||||
|         int ports = countPorts(device); | ||||
|         for (int port = 0; port < ports; port++) { | ||||
|             mPorts.add(new CdcAcmSerialPort(mDevice, port)); | ||||
|         } | ||||
|         if (mPorts.size() == 0) { | ||||
| @ -55,6 +51,24 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @SuppressWarnings({"unused"}) | ||||
|     public static boolean probe(UsbDevice device) { | ||||
|         return countPorts(device) > 0; | ||||
|     } | ||||
| 
 | ||||
|     private static int countPorts(UsbDevice device) { | ||||
|         int controlInterfaceCount = 0; | ||||
|         int dataInterfaceCount = 0; | ||||
|         for (int i = 0; i < device.getInterfaceCount(); i++) { | ||||
|             if (device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_COMM && | ||||
|                     device.getInterface(i).getInterfaceSubclass() == USB_SUBCLASS_ACM) | ||||
|                 controlInterfaceCount++; | ||||
|             if (device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) | ||||
|                 dataInterfaceCount++; | ||||
|         } | ||||
|         return Math.min(controlInterfaceCount, dataInterfaceCount); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public UsbDevice getDevice() { | ||||
|         return mDevice; | ||||
| @ -95,7 +109,11 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         protected void openInt(UsbDeviceConnection connection) throws IOException { | ||||
|         protected void openInt() throws IOException { | ||||
|             Log.d(TAG, "interfaces:"); | ||||
|             for (int i = 0; i < mDevice.getInterfaceCount(); i++) { | ||||
|                 Log.d(TAG, mDevice.getInterface(i).toString()); | ||||
|             } | ||||
|             if (mPortNumber == -1) { | ||||
|                 Log.d(TAG,"device might be castrated ACM device, trying single interface logic"); | ||||
|                 openSingleInterface(); | ||||
| @ -131,17 +149,36 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { | ||||
|         } | ||||
| 
 | ||||
|         private void openInterface() throws IOException { | ||||
|             Log.d(TAG, "claiming interfaces, count=" + mDevice.getInterfaceCount()); | ||||
| 
 | ||||
|             int controlInterfaceCount = 0; | ||||
|             int dataInterfaceCount = 0; | ||||
|             mControlInterface = null; | ||||
|             mDataInterface = null; | ||||
|             int j = getInterfaceIdFromDescriptors(); | ||||
|             Log.d(TAG, "interface count=" + mDevice.getInterfaceCount() + ", IAD=" + j); | ||||
|             if (j >= 0) { | ||||
|                 for (int i = 0; i < mDevice.getInterfaceCount(); i++) { | ||||
|                     UsbInterface usbInterface = mDevice.getInterface(i); | ||||
|                 if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_COMM) { | ||||
|                     if (usbInterface.getId() == j || usbInterface.getId() == j+1) { | ||||
|                         if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_COMM && | ||||
|                                 usbInterface.getInterfaceSubclass() == USB_SUBCLASS_ACM) { | ||||
|                             mControlIndex = usbInterface.getId(); | ||||
|                             mControlInterface = usbInterface; | ||||
|                         } | ||||
|                         if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) { | ||||
|                             mDataInterface = usbInterface; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             if (mControlInterface == null || mDataInterface == null) { | ||||
|                 Log.d(TAG, "no IAD fallback"); | ||||
|                 int controlInterfaceCount = 0; | ||||
|                 int dataInterfaceCount = 0; | ||||
|                 for (int i = 0; i < mDevice.getInterfaceCount(); i++) { | ||||
|                     UsbInterface usbInterface = mDevice.getInterface(i); | ||||
|                     if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_COMM && | ||||
|                             usbInterface.getInterfaceSubclass() == USB_SUBCLASS_ACM) { | ||||
|                         if (controlInterfaceCount == mPortNumber) { | ||||
|                         mControlIndex = i; | ||||
|                             mControlIndex = usbInterface.getId(); | ||||
|                             mControlInterface = usbInterface; | ||||
|                         } | ||||
|                         controlInterfaceCount++; | ||||
| @ -153,16 +190,16 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { | ||||
|                         dataInterfaceCount++; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if(mControlInterface == null) { | ||||
|                 throw new IOException("No control interface"); | ||||
|             } | ||||
|             Log.d(TAG, "Control iface=" + mControlInterface); | ||||
|             Log.d(TAG, "Control interface id " + mControlInterface.getId()); | ||||
| 
 | ||||
|             if (!mConnection.claimInterface(mControlInterface, true)) { | ||||
|                 throw new IOException("Could not claim control interface"); | ||||
|             } | ||||
| 
 | ||||
|             mControlEndpoint = mControlInterface.getEndpoint(0); | ||||
|             if (mControlEndpoint.getDirection() != UsbConstants.USB_DIR_IN || mControlEndpoint.getType() != UsbConstants.USB_ENDPOINT_XFER_INT) { | ||||
|                 throw new IOException("Invalid control endpoint"); | ||||
| @ -171,12 +208,10 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { | ||||
|             if(mDataInterface == null) { | ||||
|                 throw new IOException("No data interface"); | ||||
|             } | ||||
|             Log.d(TAG, "data iface=" + mDataInterface); | ||||
| 
 | ||||
|             Log.d(TAG, "data interface id " + mDataInterface.getId()); | ||||
|             if (!mConnection.claimInterface(mDataInterface, true)) { | ||||
|                 throw new IOException("Could not claim data interface"); | ||||
|             } | ||||
| 
 | ||||
|             for (int i = 0; i < mDataInterface.getEndpointCount(); i++) { | ||||
|                 UsbEndpoint ep = mDataInterface.getEndpoint(i); | ||||
|                 if (ep.getDirection() == UsbConstants.USB_DIR_IN && ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) | ||||
| @ -186,6 +221,36 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private int getInterfaceIdFromDescriptors() { | ||||
|             ArrayList<byte[]> descriptors = UsbUtils.getDescriptors(mConnection); | ||||
|             Log.d(TAG, "USB descriptor:"); | ||||
|             for(byte[] descriptor : descriptors) | ||||
|                 Log.d(TAG, HexDump.toHexString(descriptor)); | ||||
| 
 | ||||
|             if (descriptors.size() > 0 && | ||||
|                     descriptors.get(0).length == 18 && | ||||
|                     descriptors.get(0)[1] == 1 && // bDescriptorType | ||||
|                     descriptors.get(0)[4] == (byte)(UsbConstants.USB_CLASS_MISC) && //bDeviceClass | ||||
|                     descriptors.get(0)[5] == 2 && // bDeviceSubClass | ||||
|                     descriptors.get(0)[6] == 1) { // bDeviceProtocol | ||||
|                 // is IAD device, see https://www.usb.org/sites/default/files/iadclasscode_r10.pdf | ||||
|                 int port = -1; | ||||
|                 for (int d = 1; d < descriptors.size(); d++) { | ||||
|                     if (descriptors.get(d).length == 8 && | ||||
|                             descriptors.get(d)[1] == 0x0b && // bDescriptorType == IAD | ||||
|                             descriptors.get(d)[4] == UsbConstants.USB_CLASS_COMM && // bFunctionClass == CDC | ||||
|                             descriptors.get(d)[5] == USB_SUBCLASS_ACM) { // bFunctionSubClass == ACM | ||||
|                         port++; | ||||
|                         if (port == mPortNumber && | ||||
|                                 descriptors.get(d)[3] == 2) { // bInterfaceCount | ||||
|                             return descriptors.get(d)[2]; // bFirstInterface | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             return -1; | ||||
|         } | ||||
| 
 | ||||
|         private int sendAcmControlMessage(int request, int value, byte[] buf) throws IOException { | ||||
|             int len = mConnection.controlTransfer( | ||||
|                     USB_RT_ACM, request, value, mControlIndex, buf, buf != null ? buf.length : 0, 5000); | ||||
| @ -286,51 +351,9 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @SuppressWarnings({"unused"}) | ||||
|     public static Map<Integer, int[]> getSupportedDevices() { | ||||
|         final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>(); | ||||
|         supportedDevices.put(UsbId.VENDOR_ARDUINO, | ||||
|                 new int[] { | ||||
|                         UsbId.ARDUINO_UNO, | ||||
|                         UsbId.ARDUINO_UNO_R3, | ||||
|                         UsbId.ARDUINO_MEGA_2560, | ||||
|                         UsbId.ARDUINO_MEGA_2560_R3, | ||||
|                         UsbId.ARDUINO_SERIAL_ADAPTER, | ||||
|                         UsbId.ARDUINO_SERIAL_ADAPTER_R3, | ||||
|                         UsbId.ARDUINO_MEGA_ADK, | ||||
|                         UsbId.ARDUINO_MEGA_ADK_R3, | ||||
|                         UsbId.ARDUINO_LEONARDO, | ||||
|                         UsbId.ARDUINO_MICRO, | ||||
|                 }); | ||||
|         supportedDevices.put(UsbId.VENDOR_VAN_OOIJEN_TECH, | ||||
|                 new int[] { | ||||
|                         UsbId.VAN_OOIJEN_TECH_TEENSYDUINO_SERIAL, | ||||
|                 }); | ||||
|         supportedDevices.put(UsbId.VENDOR_ATMEL, | ||||
|                 new int[] { | ||||
|                         UsbId.ATMEL_LUFA_CDC_DEMO_APP, | ||||
|                 }); | ||||
|         supportedDevices.put(UsbId.VENDOR_LEAFLABS, | ||||
|                 new int[] { | ||||
|                         UsbId.LEAFLABS_MAPLE, | ||||
|                 }); | ||||
|         supportedDevices.put(UsbId.VENDOR_ARM, | ||||
|                 new int[] { | ||||
|                         UsbId.ARM_MBED, | ||||
|                 }); | ||||
|         supportedDevices.put(UsbId.VENDOR_ST, | ||||
|                 new int[] { | ||||
|                         UsbId.ST_CDC, | ||||
|                 }); | ||||
|         supportedDevices.put(UsbId.VENDOR_RASPBERRY_PI, | ||||
|                 new int[] { | ||||
|                         UsbId.RASPBERRY_PI_PICO_MICROPYTHON, | ||||
|                         UsbId.RASPBERRY_PI_PICO_SDK, | ||||
|                 }); | ||||
|         supportedDevices.put(UsbId.VENDOR_QINHENG, | ||||
|                 new int[] { | ||||
|                         UsbId.QINHENG_CH9102F, | ||||
|                 }); | ||||
|         return supportedDevices; | ||||
|         return new LinkedHashMap<>(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -7,7 +7,6 @@ package com.hoho.android.usbserial.driver; | ||||
| 
 | ||||
| import android.hardware.usb.UsbConstants; | ||||
| import android.hardware.usb.UsbDevice; | ||||
| import android.hardware.usb.UsbDeviceConnection; | ||||
| import android.hardware.usb.UsbEndpoint; | ||||
| import android.hardware.usb.UsbInterface; | ||||
| import android.util.Log; | ||||
| @ -80,7 +79,7 @@ public class Ch34xSerialDriver implements UsbSerialDriver { | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
| 		protected void openInt(UsbDeviceConnection connection) throws IOException { | ||||
|         protected void openInt() throws IOException { | ||||
|             for (int i = 0; i < mDevice.getInterfaceCount(); i++) { | ||||
|                 UsbInterface usbIface = mDevice.getInterface(i); | ||||
|                 if (!mConnection.claimInterface(usbIface, true)) { | ||||
| @ -375,6 +374,7 @@ public class Ch34xSerialDriver implements UsbSerialDriver { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @SuppressWarnings({"unused"}) | ||||
|     public static Map<Integer, int[]> getSupportedDevices() { | ||||
|         final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>(); | ||||
|         supportedDevices.put(UsbId.VENDOR_QINHENG, new int[]{ | ||||
|  | ||||
| @ -0,0 +1,91 @@ | ||||
| package com.hoho.android.usbserial.driver; | ||||
| 
 | ||||
| import android.hardware.usb.UsbConstants; | ||||
| import android.hardware.usb.UsbDevice; | ||||
| import android.hardware.usb.UsbEndpoint; | ||||
| import android.hardware.usb.UsbInterface; | ||||
| import android.util.Log; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.util.Collections; | ||||
| import java.util.EnumSet; | ||||
| import java.util.LinkedHashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.ArrayList; | ||||
| 
 | ||||
| public class ChromeCcdSerialDriver implements UsbSerialDriver{ | ||||
| 
 | ||||
|     private final String TAG = ChromeCcdSerialDriver.class.getSimpleName(); | ||||
| 
 | ||||
|     private final UsbDevice mDevice; | ||||
|     private final List<UsbSerialPort> mPorts; | ||||
| 
 | ||||
|     @Override | ||||
|     public UsbDevice getDevice() { | ||||
|         return mDevice; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public List<UsbSerialPort> getPorts() { | ||||
|         return mPorts; | ||||
|     } | ||||
| 
 | ||||
|     public ChromeCcdSerialDriver(UsbDevice mDevice) { | ||||
|         this.mDevice = mDevice; | ||||
|         mPorts = new ArrayList<UsbSerialPort>(); | ||||
|         for (int i = 0; i < 3; i++) | ||||
|             mPorts.add(new ChromeCcdSerialPort(mDevice, i)); | ||||
|     } | ||||
| 
 | ||||
|     public class ChromeCcdSerialPort extends CommonUsbSerialPort { | ||||
|         private UsbInterface mDataInterface; | ||||
| 
 | ||||
|         public ChromeCcdSerialPort(UsbDevice device, int portNumber) { | ||||
|             super(device, portNumber); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         protected void openInt() throws IOException { | ||||
|             Log.d(TAG, "claiming interfaces, count=" + mDevice.getInterfaceCount()); | ||||
|             mDataInterface = mDevice.getInterface(mPortNumber); | ||||
|             if (!mConnection.claimInterface(mDataInterface, true)) { | ||||
|                 throw new IOException("Could not claim shared control/data interface"); | ||||
|             } | ||||
|             Log.d(TAG, "endpoint count=" + mDataInterface.getEndpointCount()); | ||||
|             for (int i = 0; i < mDataInterface.getEndpointCount(); ++i) { | ||||
|                 UsbEndpoint ep = mDataInterface.getEndpoint(i); | ||||
|                 if ((ep.getDirection() == UsbConstants.USB_DIR_IN) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) { | ||||
|                     mReadEndpoint = ep; | ||||
|                 } else if ((ep.getDirection() == UsbConstants.USB_DIR_OUT) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) { | ||||
|                     mWriteEndpoint = ep; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         protected void closeInt() { | ||||
|             try { | ||||
|                 mConnection.releaseInterface(mDataInterface); | ||||
|             } catch(Exception ignored) {} | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public UsbSerialDriver getDriver() { | ||||
|             return ChromeCcdSerialDriver.this; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException { | ||||
|             throw new UnsupportedOperationException(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static Map<Integer, int[]> getSupportedDevices() { | ||||
|         final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>(); | ||||
|         supportedDevices.put(UsbId.VENDOR_GOOGLE, new int[]{ | ||||
|                 UsbId.GOOGLE_CR50, | ||||
|         }); | ||||
|         return supportedDevices; | ||||
|     } | ||||
| } | ||||
| @ -10,6 +10,7 @@ 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 com.hoho.android.usbserial.util.MonotonicClock; | ||||
| @ -28,16 +29,17 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort { | ||||
|     public static boolean DEBUG = false; | ||||
| 
 | ||||
|     private static final String TAG = CommonUsbSerialPort.class.getSimpleName(); | ||||
|     private static final int MAX_READ_SIZE = 16 * 1024; // = old bulkTransfer limit | ||||
|     private static final int MAX_READ_SIZE = 16 * 1024; // = old bulkTransfer limit prior to Android 9 | ||||
| 
 | ||||
|     protected final UsbDevice mDevice; | ||||
|     protected final int mPortNumber; | ||||
| 
 | ||||
|     // non-null when open() | ||||
|     protected UsbDeviceConnection mConnection = null; | ||||
|     protected UsbDeviceConnection mConnection; | ||||
|     protected UsbEndpoint mReadEndpoint; | ||||
|     protected UsbEndpoint mWriteEndpoint; | ||||
|     protected UsbRequest mUsbRequest; | ||||
|     protected FlowControl mFlowControl = FlowControl.NONE; | ||||
| 
 | ||||
|     /** | ||||
|      * Internal write buffer. | ||||
| @ -117,32 +119,36 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort { | ||||
|             throw new IllegalArgumentException("Connection is null"); | ||||
|         } | ||||
|         mConnection = connection; | ||||
|         boolean ok = false; | ||||
|         try { | ||||
|             openInt(connection); | ||||
|             openInt(); | ||||
|             if (mReadEndpoint == null || mWriteEndpoint == null) { | ||||
|                 throw new IOException("Could not get read & write endpoints"); | ||||
|             } | ||||
|             mUsbRequest = new UsbRequest(); | ||||
|             mUsbRequest.initialize(mConnection, mReadEndpoint); | ||||
|         } catch(Exception e) { | ||||
|             ok = true; | ||||
|         } finally { | ||||
|             if (!ok) { | ||||
|                 try { | ||||
|                     close(); | ||||
|                 } catch (Exception ignored) {} | ||||
|             throw e; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     protected abstract void openInt(UsbDeviceConnection connection) throws IOException; | ||||
|     protected abstract void openInt() throws IOException; | ||||
| 
 | ||||
|     @Override | ||||
|     public void close() throws IOException { | ||||
|         if (mConnection == null) { | ||||
|             throw new IOException("Already closed"); | ||||
|         } | ||||
|         try { | ||||
|             mUsbRequest.cancel(); | ||||
|         } catch(Exception ignored) {} | ||||
|         UsbRequest usbRequest = mUsbRequest; | ||||
|         mUsbRequest = null; | ||||
|         try { | ||||
|             usbRequest.cancel(); | ||||
|         } catch(Exception ignored) {} | ||||
|         try { | ||||
|             closeInt(); | ||||
|         } catch(Exception ignored) {} | ||||
| @ -157,25 +163,40 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort { | ||||
|     /** | ||||
|      * use simple USB request supported by all devices to test if connection is still valid | ||||
|      */ | ||||
|     protected void testConnection() throws IOException { | ||||
|     protected void testConnection(boolean full) throws IOException { | ||||
|         testConnection(full, "USB get_status request failed"); | ||||
|     } | ||||
| 
 | ||||
|     protected void testConnection(boolean full, String msg) throws IOException { | ||||
|         if(mUsbRequest == null) { | ||||
|             throw new IOException("Connection closed"); | ||||
|         } | ||||
|         if(!full) { | ||||
|             return; | ||||
|         } | ||||
|         byte[] buf = new byte[2]; | ||||
|         int len = mConnection.controlTransfer(0x80 /*DEVICE*/, 0 /*GET_STATUS*/, 0, 0, buf, buf.length, 200); | ||||
|         if(len < 0) | ||||
|             throw new IOException("USB get_status request failed"); | ||||
|             throw new IOException(msg); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int read(final byte[] dest, final int timeout) throws IOException { | ||||
|         return read(dest, timeout, true); | ||||
|         if(dest.length == 0) { | ||||
|             throw new IllegalArgumentException("Read buffer too small"); | ||||
|         } | ||||
|         return read(dest, dest.length, timeout); | ||||
|     } | ||||
| 
 | ||||
|     protected int read(final byte[] dest, final int timeout, boolean testConnection) throws IOException { | ||||
|         if(mConnection == null) { | ||||
|             throw new IOException("Connection closed"); | ||||
|         } | ||||
|         if(dest.length <= 0) { | ||||
|             throw new IllegalArgumentException("Read buffer to small"); | ||||
|     @Override | ||||
|     public int read(final byte[] dest, final int length, final int timeout) throws IOException {return read(dest, length, timeout, true);} | ||||
| 
 | ||||
|     protected int read(final byte[] dest, int length, final int timeout, boolean testConnection) throws IOException { | ||||
|         testConnection(false); | ||||
|         if(length <= 0) { | ||||
|             throw new IllegalArgumentException("Read length too small"); | ||||
|         } | ||||
|         length = Math.min(length, dest.length); | ||||
|         final int nread; | ||||
|         if (timeout != 0) { | ||||
|             // bulkTransfer will cause data loss with short timeout + high baud rates + continuous transfer | ||||
| @ -187,16 +208,16 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort { | ||||
|             //     /system/lib64/libandroid_runtime.so (android_hardware_UsbDeviceConnection_request_wait(_JNIEnv*, _jobject*, long)+84) | ||||
|             // data loss / crashes were observed with timeout up to 200 msec | ||||
|             long endTime = testConnection ? MonotonicClock.millis() + timeout : 0; | ||||
|             int readMax = Math.min(dest.length, MAX_READ_SIZE); | ||||
|             int readMax = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) ? length : Math.min(length, MAX_READ_SIZE); | ||||
|             nread = mConnection.bulkTransfer(mReadEndpoint, dest, readMax, timeout); | ||||
|             // Android error propagation is improvable: | ||||
|             //  nread == -1 can be: timeout, connection lost, buffer to small, ??? | ||||
|             if(nread == -1 && testConnection && MonotonicClock.millis() < endTime) | ||||
|                 testConnection(); | ||||
|             if(nread == -1 && testConnection) | ||||
|                 testConnection(MonotonicClock.millis() < endTime); | ||||
| 
 | ||||
|         } else { | ||||
|             final ByteBuffer buf = ByteBuffer.wrap(dest); | ||||
|             if (!mUsbRequest.queue(buf, dest.length)) { | ||||
|             final ByteBuffer buf = ByteBuffer.wrap(dest, 0, length); | ||||
|             if (!mUsbRequest.queue(buf, length)) { | ||||
|                 throw new IOException("Queueing USB request failed"); | ||||
|             } | ||||
|             final UsbRequest response = mConnection.requestWait(); | ||||
| @ -207,21 +228,23 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort { | ||||
|             // Android error propagation is improvable: | ||||
|             //   response != null & nread == 0 can be: connection lost, buffer to small, ??? | ||||
|             if(nread == 0) { | ||||
|                 testConnection(); | ||||
|                 testConnection(true); | ||||
|             } | ||||
|         } | ||||
|         return Math.max(nread, 0); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void write(final byte[] src, final int timeout) throws IOException { | ||||
|         int offset = 0; | ||||
|         final long endTime = (timeout == 0) ? 0 : (MonotonicClock.millis() + timeout); | ||||
|     public void write(byte[] src, int timeout) throws IOException {write(src, src.length, timeout);} | ||||
| 
 | ||||
|         if(mConnection == null) { | ||||
|             throw new IOException("Connection closed"); | ||||
|         } | ||||
|         while (offset < src.length) { | ||||
|     @Override | ||||
|     public void write(final byte[] src, int length, final int timeout) throws IOException { | ||||
|         int offset = 0; | ||||
|         long startTime = MonotonicClock.millis(); | ||||
|         length = Math.min(length, src.length); | ||||
| 
 | ||||
|         testConnection(false); | ||||
|         while (offset < length) { | ||||
|             int requestTimeout; | ||||
|             final int requestLength; | ||||
|             final int actualLength; | ||||
| @ -232,7 +255,7 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort { | ||||
|                 if (mWriteBuffer == null) { | ||||
|                     mWriteBuffer = new byte[mWriteEndpoint.getMaxPacketSize()]; | ||||
|                 } | ||||
|                 requestLength = Math.min(src.length - offset, mWriteBuffer.length); | ||||
|                 requestLength = Math.min(length - offset, mWriteBuffer.length); | ||||
|                 if (offset == 0) { | ||||
|                     writeBuffer = src; | ||||
|                 } else { | ||||
| @ -243,7 +266,7 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort { | ||||
|                 if (timeout == 0 || offset == 0) { | ||||
|                     requestTimeout = timeout; | ||||
|                 } else { | ||||
|                     requestTimeout = (int)(endTime - MonotonicClock.millis()); | ||||
|                     requestTimeout = (int)(startTime + timeout - MonotonicClock.millis()); | ||||
|                     if(requestTimeout == 0) | ||||
|                         requestTimeout = -1; | ||||
|                 } | ||||
| @ -253,16 +276,19 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort { | ||||
|                     actualLength = mConnection.bulkTransfer(mWriteEndpoint, writeBuffer, requestLength, requestTimeout); | ||||
|                 } | ||||
|             } | ||||
|             long elapsed = MonotonicClock.millis() - startTime; | ||||
|             if (DEBUG) { | ||||
|                 Log.d(TAG, "Wrote " + actualLength + "/" + requestLength + " offset " + offset + "/" + src.length + " timeout " + requestTimeout); | ||||
|                 Log.d(TAG, "Wrote " + actualLength + "/" + requestLength + " offset " + offset + "/" + length + " time " + elapsed + "/" + requestTimeout); | ||||
|             } | ||||
|             if (actualLength <= 0) { | ||||
|                 if (timeout != 0 && MonotonicClock.millis() >= endTime) { | ||||
|                     SerialTimeoutException ex = new SerialTimeoutException("Error writing " + requestLength + " bytes at offset " + offset + " of total " + src.length + ", rc=" + actualLength); | ||||
|                     ex.bytesTransferred = offset; | ||||
|                     throw ex; | ||||
|                 String msg = "Error writing " + requestLength + " bytes at offset " + offset + " of total " + src.length + " after " + elapsed + "msec, rc=" + actualLength; | ||||
|                 if (timeout != 0) { | ||||
|                     // could be buffer full because: writing to fast, stopped by flow control | ||||
|                     testConnection(elapsed < timeout, msg); | ||||
|                     throw new SerialTimeoutException(msg, offset); | ||||
|                 } else { | ||||
|                     throw new IOException("Error writing " + requestLength + " bytes at offset " + offset + " of total " + src.length); | ||||
|                     throw new IOException(msg); | ||||
| 
 | ||||
|                 } | ||||
|             } | ||||
|             offset += actualLength; | ||||
| @ -271,7 +297,7 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort { | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean isOpen() { | ||||
|         return mConnection != null; | ||||
|         return mUsbRequest != null; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
| @ -302,16 +328,29 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort { | ||||
|     public void setRTS(boolean value) throws IOException { throw new UnsupportedOperationException(); } | ||||
| 
 | ||||
|     @Override | ||||
|     public abstract EnumSet<ControlLine> getControlLines() throws IOException; | ||||
|     public EnumSet<ControlLine> getControlLines() throws IOException { throw new UnsupportedOperationException(); } | ||||
| 
 | ||||
|     @Override | ||||
|     public abstract EnumSet<ControlLine> getSupportedControlLines() throws IOException; | ||||
|     public EnumSet<ControlLine> getSupportedControlLines() throws IOException { return EnumSet.noneOf(ControlLine.class); } | ||||
| 
 | ||||
|     @Override | ||||
|     public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { | ||||
|     public void setFlowControl(FlowControl flowcontrol) throws IOException { | ||||
|         if (flowcontrol != FlowControl.NONE) | ||||
|             throw new UnsupportedOperationException(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public FlowControl getFlowControl() { return mFlowControl; } | ||||
| 
 | ||||
|     @Override | ||||
|     public EnumSet<FlowControl> getSupportedFlowControl() { return EnumSet.of(FlowControl.NONE); } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean getXON() throws IOException { throw new UnsupportedOperationException(); } | ||||
| 
 | ||||
|     @Override | ||||
|     public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { throw new UnsupportedOperationException(); } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setBreak(boolean value) throws IOException { throw new UnsupportedOperationException(); } | ||||
| 
 | ||||
|  | ||||
| @ -8,7 +8,6 @@ package com.hoho.android.usbserial.driver; | ||||
| 
 | ||||
| import android.hardware.usb.UsbConstants; | ||||
| import android.hardware.usb.UsbDevice; | ||||
| import android.hardware.usb.UsbDeviceConnection; | ||||
| import android.hardware.usb.UsbEndpoint; | ||||
| import android.hardware.usb.UsbInterface; | ||||
| 
 | ||||
| @ -61,9 +60,14 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { | ||||
|         private static final int SILABSER_SET_LINE_CTL_REQUEST_CODE = 0x03; | ||||
|         private static final int SILABSER_SET_BREAK_REQUEST_CODE = 0x05; | ||||
|         private static final int SILABSER_SET_MHS_REQUEST_CODE = 0x07; | ||||
|         private static final int SILABSER_SET_BAUDRATE = 0x1E; | ||||
|         private static final int SILABSER_FLUSH_REQUEST_CODE = 0x12; | ||||
|         private static final int SILABSER_GET_MDMSTS_REQUEST_CODE = 0x08; | ||||
|         private static final int SILABSER_SET_XON_REQUEST_CODE = 0x09; | ||||
|         private static final int SILABSER_SET_XOFF_REQUEST_CODE = 0x0A; | ||||
|         private static final int SILABSER_GET_COMM_STATUS_REQUEST_CODE = 0x10; | ||||
|         private static final int SILABSER_FLUSH_REQUEST_CODE = 0x12; | ||||
|         private static final int SILABSER_SET_FLOW_REQUEST_CODE = 0x13; | ||||
|         private static final int SILABSER_SET_CHARS_REQUEST_CODE = 0x19; | ||||
|         private static final int SILABSER_SET_BAUDRATE_REQUEST_CODE = 0x1E; | ||||
| 
 | ||||
|         private static final int FLUSH_READ_CODE = 0x0a; | ||||
|         private static final int FLUSH_WRITE_CODE = 0x05; | ||||
| @ -85,6 +89,8 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { | ||||
|         /* | ||||
|         * SILABSER_GET_MDMSTS_REQUEST_CODE | ||||
|          */ | ||||
|         private static final int STATUS_DTR = 0x01; | ||||
|         private static final int STATUS_RTS = 0x02; | ||||
|         private static final int STATUS_CTS = 0x10; | ||||
|         private static final int STATUS_DSR = 0x20; | ||||
|         private static final int STATUS_RI = 0x40; | ||||
| @ -119,14 +125,14 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { | ||||
|             byte[] buffer = new byte[1]; | ||||
|             int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, SILABSER_GET_MDMSTS_REQUEST_CODE, 0, | ||||
|                     mPortNumber, buffer, buffer.length, USB_WRITE_TIMEOUT_MILLIS); | ||||
|             if (result != 1) { | ||||
|             if (result != buffer.length) { | ||||
|                 throw new IOException("Control transfer failed: " + SILABSER_GET_MDMSTS_REQUEST_CODE + " / " + 0 + " -> " + result); | ||||
|             } | ||||
|             return buffer[0]; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         protected void openInt(UsbDeviceConnection connection) throws IOException { | ||||
|         protected void openInt() throws IOException { | ||||
|             mIsRestrictedPort = mDevice.getInterfaceCount() == 2 && mPortNumber == 1; | ||||
|             if(mPortNumber >= mDevice.getInterfaceCount()) { | ||||
|                 throw new IOException("Unknown port number"); | ||||
| @ -148,6 +154,7 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { | ||||
| 
 | ||||
|             setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_ENABLE); | ||||
|             setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, (dtr ? DTR_ENABLE : DTR_DISABLE) | (rts ? RTS_ENABLE : RTS_DISABLE)); | ||||
|             setFlowControl(mFlowControl); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
| @ -167,7 +174,7 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { | ||||
|                     (byte) ((baudRate >> 16) & 0xff), | ||||
|                     (byte) ((baudRate >> 24) & 0xff) | ||||
|             }; | ||||
|             int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_BAUDRATE, | ||||
|             int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_BAUDRATE_REQUEST_CODE, | ||||
|                     0, mPortNumber, data, 4, USB_WRITE_TIMEOUT_MILLIS); | ||||
|             if (ret < 0) { | ||||
|                 throw new IOException("Error setting baud rate"); | ||||
| @ -290,9 +297,11 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { | ||||
|         public EnumSet<ControlLine> getControlLines() throws IOException { | ||||
|             byte status = getStatus(); | ||||
|             EnumSet<ControlLine> set = EnumSet.noneOf(ControlLine.class); | ||||
|             if(rts) set.add(ControlLine.RTS); | ||||
|             //if(rts) set.add(ControlLine.RTS);                      // configured value | ||||
|             if((status & STATUS_RTS) != 0) set.add(ControlLine.RTS); // actual value | ||||
|             if((status & STATUS_CTS) != 0) set.add(ControlLine.CTS); | ||||
|             if(dtr) set.add(ControlLine.DTR); | ||||
|             //if(dtr) set.add(ControlLine.DTR);                      // configured value | ||||
|             if((status & STATUS_DTR) != 0) set.add(ControlLine.DTR); // actual value | ||||
|             if((status & STATUS_DSR) != 0) set.add(ControlLine.DSR); | ||||
|             if((status & STATUS_CD) != 0) set.add(ControlLine.CD); | ||||
|             if((status & STATUS_RI) != 0) set.add(ControlLine.RI); | ||||
| @ -304,6 +313,73 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { | ||||
|             return EnumSet.allOf(ControlLine.class); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public boolean getXON() throws IOException { | ||||
|             byte[] buffer = new byte[0x13]; | ||||
|             int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, SILABSER_GET_COMM_STATUS_REQUEST_CODE, 0, | ||||
|                     mPortNumber, buffer, buffer.length, USB_WRITE_TIMEOUT_MILLIS); | ||||
|             if (result != buffer.length) { | ||||
|                 throw new IOException("Control transfer failed: " + SILABSER_GET_COMM_STATUS_REQUEST_CODE + " -> " + result); | ||||
|             } | ||||
|             return (buffer[4] & 8) == 0; | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * emulate external XON/OFF | ||||
|          * @throws IOException | ||||
|          */ | ||||
|         public void setXON(boolean value) throws IOException { | ||||
|             setConfigSingle(value ? SILABSER_SET_XON_REQUEST_CODE : SILABSER_SET_XOFF_REQUEST_CODE, 0); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void setFlowControl(FlowControl flowControl) throws IOException { | ||||
|             byte[] data = new byte[16]; | ||||
|             if(flowControl == FlowControl.RTS_CTS) { | ||||
|                 data[4] |=  0b1000_0000; // RTS | ||||
|                 data[0] |=  0b0000_1000; // CTS | ||||
|             } else { | ||||
|                 if(rts) | ||||
|                     data[4] |= 0b0100_0000; | ||||
|             } | ||||
|             if(flowControl == FlowControl.DTR_DSR) { | ||||
|                 data[0] |= 0b0000_0010; // DTR | ||||
|                 data[0] |= 0b0001_0000; // DSR | ||||
|             } else { | ||||
|                 if(dtr) | ||||
|                     data[0] |= 0b0000_0001; | ||||
|             } | ||||
|             if(flowControl == FlowControl.XON_XOFF) { | ||||
|                 byte[] chars = new byte[]{0, 0, 0, 0, CHAR_XON, CHAR_XOFF}; | ||||
|                 int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_CHARS_REQUEST_CODE, | ||||
|                         0, mPortNumber, chars, chars.length, USB_WRITE_TIMEOUT_MILLIS); | ||||
|                 if (ret != chars.length) { | ||||
|                     throw new IOException("Error setting XON/XOFF chars"); | ||||
|                 } | ||||
|                 data[4] |= 0b0000_0011; | ||||
|                 data[7] |= 0b1000_0000; | ||||
|                 data[8] = (byte)128; | ||||
|                 data[12] = (byte)128; | ||||
|             } | ||||
|             if(flowControl == FlowControl.XON_XOFF_INLINE) { | ||||
|                 throw new UnsupportedOperationException(); | ||||
|             } | ||||
|             int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_FLOW_REQUEST_CODE, | ||||
|                     0, mPortNumber, data, data.length, USB_WRITE_TIMEOUT_MILLIS); | ||||
|             if (ret != data.length) { | ||||
|                 throw new IOException("Error setting flow control"); | ||||
|             } | ||||
|             if(flowControl == FlowControl.XON_XOFF) { | ||||
|                 setXON(true); | ||||
|             } | ||||
|             mFlowControl = flowControl; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public EnumSet<FlowControl> getSupportedFlowControl() { | ||||
|             return EnumSet.of(FlowControl.NONE, FlowControl.RTS_CTS, FlowControl.DTR_DSR, FlowControl.XON_XOFF); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         // note: only working on some devices, on other devices ignored w/o error | ||||
|         public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { | ||||
| @ -321,6 +397,7 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @SuppressWarnings({"unused"}) | ||||
|     public static Map<Integer, int[]> getSupportedDevices() { | ||||
|         final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>(); | ||||
|         supportedDevices.put(UsbId.VENDOR_SILABS, | ||||
|  | ||||
| @ -9,7 +9,6 @@ package com.hoho.android.usbserial.driver; | ||||
| 
 | ||||
| import android.hardware.usb.UsbConstants; | ||||
| import android.hardware.usb.UsbDevice; | ||||
| import android.hardware.usb.UsbDeviceConnection; | ||||
| import android.util.Log; | ||||
| 
 | ||||
| import com.hoho.android.usbserial.util.MonotonicClock; | ||||
| @ -65,6 +64,7 @@ public class FtdiSerialDriver implements UsbSerialDriver { | ||||
| 
 | ||||
|         private static final int RESET_REQUEST = 0; | ||||
|         private static final int MODEM_CONTROL_REQUEST = 1; | ||||
|         private static final int SET_FLOW_CONTROL_REQUEST = 2; | ||||
|         private static final int SET_BAUD_RATE_REQUEST = 3; | ||||
|         private static final int SET_DATA_REQUEST = 4; | ||||
|         private static final int GET_MODEM_STATUS_REQUEST = 5; | ||||
| @ -99,8 +99,8 @@ public class FtdiSerialDriver implements UsbSerialDriver { | ||||
| 
 | ||||
| 
 | ||||
|         @Override | ||||
|         protected void openInt(UsbDeviceConnection connection) throws IOException { | ||||
|             if (!connection.claimInterface(mDevice.getInterface(mPortNumber), true)) { | ||||
|         protected void openInt() throws IOException { | ||||
|             if (!mConnection.claimInterface(mDevice.getInterface(mPortNumber), true)) { | ||||
|                 throw new IOException("Could not claim interface " + mPortNumber); | ||||
|             } | ||||
|             if (mDevice.getInterface(mPortNumber).getEndpointCount() < 2) { | ||||
| @ -121,9 +121,10 @@ public class FtdiSerialDriver implements UsbSerialDriver { | ||||
|             if (result != 0) { | ||||
|                 throw new IOException("Init RTS,DTR failed: result=" + result); | ||||
|             } | ||||
|             setFlowControl(mFlowControl); | ||||
| 
 | ||||
|             // mDevice.getVersion() would require API 23 | ||||
|             byte[] rawDescriptors = connection.getRawDescriptors(); | ||||
|             byte[] rawDescriptors = mConnection.getRawDescriptors(); | ||||
|             if(rawDescriptors == null || rawDescriptors.length < 14) { | ||||
|                 throw new IOException("Could not get device descriptors"); | ||||
|             } | ||||
| @ -140,24 +141,37 @@ public class FtdiSerialDriver implements UsbSerialDriver { | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public int read(final byte[] dest, final int timeout) throws IOException { | ||||
|         public int read(final byte[] dest, final int timeout) throws IOException | ||||
|         { | ||||
|             if(dest.length <= READ_HEADER_LENGTH) { | ||||
|                 throw new IllegalArgumentException("Read buffer to small"); | ||||
|                 throw new IllegalArgumentException("Read buffer too small"); | ||||
|                 // could allocate larger buffer, including space for 2 header bytes, but this would | ||||
|                 // result in buffers not being 64 byte aligned any more, causing data loss at continuous | ||||
|                 // data transfer at high baud rates when buffers are fully filled. | ||||
|             } | ||||
|             return read(dest, dest.length, timeout); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public int read(final byte[] dest, int length, final int timeout) throws IOException { | ||||
|             if(length <= READ_HEADER_LENGTH) { | ||||
|                 throw new IllegalArgumentException("Read length too small"); | ||||
|                 // could allocate larger buffer, including space for 2 header bytes, but this would | ||||
|                 // result in buffers not being 64 byte aligned any more, causing data loss at continuous | ||||
|                 // data transfer at high baud rates when buffers are fully filled. | ||||
|             } | ||||
|             length = Math.min(length, dest.length); | ||||
|             int nread; | ||||
|             if (timeout != 0) { | ||||
|                 long endTime = MonotonicClock.millis() + timeout; | ||||
|                 do { | ||||
|                     nread = super.read(dest, Math.max(1, (int)(endTime - MonotonicClock.millis())), false); | ||||
|                     nread = super.read(dest, length, Math.max(1, (int)(endTime - MonotonicClock.millis())), false); | ||||
|                 } while (nread == READ_HEADER_LENGTH && MonotonicClock.millis() < endTime); | ||||
|                 if(nread <= 0 && MonotonicClock.millis() < endTime) | ||||
|                     testConnection(); | ||||
|                 if(nread <= 0) | ||||
|                     testConnection(MonotonicClock.millis() < endTime); | ||||
|             } else { | ||||
|                 do { | ||||
|                     nread = super.read(dest, timeout, false); | ||||
|                     nread = super.read(dest, length, timeout); | ||||
|                 } while (nread == READ_HEADER_LENGTH); | ||||
|             } | ||||
|             return readFilter(dest, nread); | ||||
| @ -291,7 +305,7 @@ public class FtdiSerialDriver implements UsbSerialDriver { | ||||
|             byte[] data = new byte[2]; | ||||
|             int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, GET_MODEM_STATUS_REQUEST, | ||||
|                     0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS); | ||||
|             if (result != 2) { | ||||
|             if (result != data.length) { | ||||
|                 throw new IOException("Get modem status failed: result=" + result); | ||||
|             } | ||||
|             return data[0]; | ||||
| @ -365,6 +379,38 @@ public class FtdiSerialDriver implements UsbSerialDriver { | ||||
|             return EnumSet.allOf(ControlLine.class); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void setFlowControl(FlowControl flowControl) throws IOException { | ||||
|             int value = 0; | ||||
|             int index = mPortNumber+1; | ||||
|             switch (flowControl) { | ||||
|                 case NONE: | ||||
|                     break; | ||||
|                 case RTS_CTS: | ||||
|                     index |= 0x100; | ||||
|                     break; | ||||
|                 case DTR_DSR: | ||||
|                     index |= 0x200; | ||||
|                     break; | ||||
|                 case XON_XOFF_INLINE: | ||||
|                     value = CHAR_XON + (CHAR_XOFF << 8); | ||||
|                     index |= 0x400; | ||||
|                     break; | ||||
|                 default: | ||||
|                     throw new UnsupportedOperationException(); | ||||
|             } | ||||
|             int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_FLOW_CONTROL_REQUEST, | ||||
|                     value, index, null, 0, USB_WRITE_TIMEOUT_MILLIS); | ||||
|             if (result != 0) | ||||
|                 throw new IOException("Set flow control failed: result=" + result); | ||||
|             mFlowControl = flowControl; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public EnumSet<FlowControl> getSupportedFlowControl() { | ||||
|             return EnumSet.of(FlowControl.NONE, FlowControl.RTS_CTS, FlowControl.DTR_DSR, FlowControl.XON_XOFF_INLINE); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { | ||||
|             if (purgeWriteBuffers) { | ||||
| @ -407,7 +453,7 @@ public class FtdiSerialDriver implements UsbSerialDriver { | ||||
|             byte[] data = new byte[1]; | ||||
|             int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, GET_LATENCY_TIMER_REQUEST, | ||||
|                     0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS); | ||||
|             if (result != 1) { | ||||
|             if (result != data.length) { | ||||
|                 throw new IOException("Get latency timer failed: result=" + result); | ||||
|             } | ||||
|             return data[0]; | ||||
| @ -415,6 +461,7 @@ public class FtdiSerialDriver implements UsbSerialDriver { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @SuppressWarnings({"unused"}) | ||||
|     public static Map<Integer, int[]> getSupportedDevices() { | ||||
|         final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>(); | ||||
|         supportedDevices.put(UsbId.VENDOR_FTDI, | ||||
|  | ||||
| @ -0,0 +1,102 @@ | ||||
| package com.hoho.android.usbserial.driver; | ||||
| 
 | ||||
| import android.hardware.usb.UsbConstants; | ||||
| import android.hardware.usb.UsbDevice; | ||||
| import android.hardware.usb.UsbEndpoint; | ||||
| import android.hardware.usb.UsbInterface; | ||||
| import android.util.Log; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.util.Collections; | ||||
| import java.util.EnumSet; | ||||
| import java.util.LinkedHashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| public class GsmModemSerialDriver implements UsbSerialDriver{ | ||||
| 
 | ||||
|     private final String TAG = GsmModemSerialDriver.class.getSimpleName(); | ||||
| 
 | ||||
|     private final UsbDevice mDevice; | ||||
|     private final UsbSerialPort mPort; | ||||
| 
 | ||||
|     @Override | ||||
|     public UsbDevice getDevice() { | ||||
|         return mDevice; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public List<UsbSerialPort> getPorts() { | ||||
|         return Collections.singletonList(mPort); | ||||
|     } | ||||
| 
 | ||||
|     public GsmModemSerialDriver(UsbDevice mDevice) { | ||||
|         this.mDevice = mDevice; | ||||
|         mPort = new GsmModemSerialPort(mDevice, 0); | ||||
|     } | ||||
| 
 | ||||
|     public class GsmModemSerialPort extends CommonUsbSerialPort { | ||||
| 
 | ||||
|         private UsbInterface mDataInterface; | ||||
| 
 | ||||
|         public GsmModemSerialPort(UsbDevice device, int portNumber) { | ||||
|             super(device, portNumber); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         protected void openInt() throws IOException { | ||||
|             Log.d(TAG, "claiming interfaces, count=" + mDevice.getInterfaceCount()); | ||||
|             mDataInterface = mDevice.getInterface(0); | ||||
|             if (!mConnection.claimInterface(mDataInterface, true)) { | ||||
|                 throw new IOException("Could not claim shared control/data interface"); | ||||
|             } | ||||
|             Log.d(TAG, "endpoint count=" + mDataInterface.getEndpointCount()); | ||||
|             for (int i = 0; i < mDataInterface.getEndpointCount(); ++i) { | ||||
|                 UsbEndpoint ep = mDataInterface.getEndpoint(i); | ||||
|                 if ((ep.getDirection() == UsbConstants.USB_DIR_IN) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) { | ||||
|                     mReadEndpoint = ep; | ||||
|                 } else if ((ep.getDirection() == UsbConstants.USB_DIR_OUT) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) { | ||||
|                     mWriteEndpoint = ep; | ||||
|                 } | ||||
|             } | ||||
|             initGsmModem(); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         protected void closeInt() { | ||||
|             try { | ||||
|                 mConnection.releaseInterface(mDataInterface); | ||||
|             } catch(Exception ignored) {} | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         private int initGsmModem() throws IOException { | ||||
|             int len = mConnection.controlTransfer( | ||||
|                     0x21, 0x22, 0x01, 0, null, 0, 5000); | ||||
|             if(len < 0) { | ||||
|                 throw new IOException("init failed"); | ||||
|             } | ||||
|             return len; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public UsbSerialDriver getDriver() { | ||||
|             return GsmModemSerialDriver.this; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException { | ||||
|             throw new UnsupportedOperationException(); | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public static Map<Integer, int[]> getSupportedDevices() { | ||||
|         final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>(); | ||||
|         supportedDevices.put(UsbId.VENDOR_UNISOC, new int[]{ | ||||
|                 UsbId.FIBOCOM_L610, | ||||
|                 UsbId.FIBOCOM_L612, | ||||
|         }); | ||||
|         return supportedDevices; | ||||
|     } | ||||
| } | ||||
| @ -6,6 +6,7 @@ | ||||
| 
 | ||||
| package com.hoho.android.usbserial.driver; | ||||
| 
 | ||||
| import android.hardware.usb.UsbDevice; | ||||
| import android.util.Pair; | ||||
| 
 | ||||
| import java.lang.reflect.InvocationTargetException; | ||||
| @ -14,14 +15,14 @@ import java.util.LinkedHashMap; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| /** | ||||
|  * Maps (vendor id, product id) pairs to the corresponding serial driver. | ||||
|  * | ||||
|  * @author mike wakerly (opensource@hoho.com) | ||||
|  * Maps (vendor id, product id) pairs to the corresponding serial driver, | ||||
|  * or invoke 'probe' method to check actual USB devices for matching interfaces. | ||||
|  */ | ||||
| public class ProbeTable { | ||||
| 
 | ||||
|     private final Map<Pair<Integer, Integer>, Class<? extends UsbSerialDriver>> mProbeTable = | ||||
|     private final Map<Pair<Integer, Integer>, Class<? extends UsbSerialDriver>> mVidPidProbeTable = | ||||
|             new LinkedHashMap<>(); | ||||
|     private final Map<Method, Class<? extends UsbSerialDriver>> mMethodProbeTable = new LinkedHashMap<>(); | ||||
| 
 | ||||
|     /** | ||||
|      * Adds or updates a (vendor, product) pair in the table. | ||||
| @ -33,7 +34,7 @@ public class ProbeTable { | ||||
|      */ | ||||
|     public ProbeTable addProduct(int vendorId, int productId, | ||||
|             Class<? extends UsbSerialDriver> driverClass) { | ||||
|         mProbeTable.put(Pair.create(vendorId, productId), driverClass); | ||||
|         mVidPidProbeTable.put(Pair.create(vendorId, productId), driverClass); | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
| @ -41,12 +42,11 @@ public class ProbeTable { | ||||
|      * Internal method to add all supported products from | ||||
|      * {@code getSupportedProducts} static method. | ||||
|      * | ||||
|      * @param driverClass | ||||
|      * @return | ||||
|      * @param driverClass to be added | ||||
|      */ | ||||
|     @SuppressWarnings("unchecked") | ||||
|     ProbeTable addDriver(Class<? extends UsbSerialDriver> driverClass) { | ||||
|         final Method method; | ||||
|     void addDriver(Class<? extends UsbSerialDriver> driverClass) { | ||||
|         Method method; | ||||
| 
 | ||||
|         try { | ||||
|             method = driverClass.getMethod("getSupportedDevices"); | ||||
| @ -68,20 +68,35 @@ public class ProbeTable { | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return this; | ||||
|         try { | ||||
|             method = driverClass.getMethod("probe", UsbDevice.class); | ||||
|             mMethodProbeTable.put(method, driverClass); | ||||
|         } catch (SecurityException | NoSuchMethodException ignored) { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the driver for the given (vendor, product) pair, or {@code null} | ||||
|      * if no match. | ||||
|      * Returns the driver for the given USB device, or {@code null} if no match. | ||||
|      * | ||||
|      * @param vendorId the USB vendor id | ||||
|      * @param productId the USB product id | ||||
|      * @param usbDevice the USB device to be probed | ||||
|      * @return the driver class matching this pair, or {@code null} | ||||
|      */ | ||||
|     public Class<? extends UsbSerialDriver> findDriver(int vendorId, int productId) { | ||||
|         final Pair<Integer, Integer> pair = Pair.create(vendorId, productId); | ||||
|         return mProbeTable.get(pair); | ||||
|     public Class<? extends UsbSerialDriver> findDriver(final UsbDevice usbDevice) { | ||||
|         final Pair<Integer, Integer> pair = Pair.create(usbDevice.getVendorId(), usbDevice.getProductId()); | ||||
|         Class<? extends UsbSerialDriver> driverClass = mVidPidProbeTable.get(pair); | ||||
|         if (driverClass != null) | ||||
|             return driverClass; | ||||
|         for (Map.Entry<Method, Class<? extends UsbSerialDriver>> entry : mMethodProbeTable.entrySet()) { | ||||
|             try { | ||||
|                 Method method = entry.getKey(); | ||||
|                 Object o = method.invoke(null, usbDevice); | ||||
|                 if((boolean)o) | ||||
|                     return entry.getValue(); | ||||
|             } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { | ||||
|                 throw new RuntimeException(e); | ||||
|             } | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -11,7 +11,6 @@ package com.hoho.android.usbserial.driver; | ||||
| 
 | ||||
| import android.hardware.usb.UsbConstants; | ||||
| import android.hardware.usb.UsbDevice; | ||||
| import android.hardware.usb.UsbDeviceConnection; | ||||
| import android.hardware.usb.UsbEndpoint; | ||||
| import android.hardware.usb.UsbInterface; | ||||
| import android.util.Log; | ||||
| @ -123,7 +122,7 @@ public class ProlificSerialDriver implements UsbSerialDriver { | ||||
|         private volatile Thread mReadStatusThread = null; | ||||
|         private final Object mReadStatusThreadLock = new Object(); | ||||
|         private boolean mStopReadStatusThread = false; | ||||
|         private IOException mReadStatusException = null; | ||||
|         private Exception mReadStatusException = null; | ||||
| 
 | ||||
| 
 | ||||
|         public ProlificSerialPort(UsbDevice device, int portNumber) { | ||||
| @ -202,12 +201,12 @@ public class ProlificSerialDriver implements UsbSerialDriver { | ||||
| 
 | ||||
|         private void readStatusThreadFunction() { | ||||
|             try { | ||||
|                 while (!mStopReadStatusThread) { | ||||
|                 byte[] buffer = new byte[STATUS_BUFFER_SIZE]; | ||||
|                 while (!mStopReadStatusThread) { | ||||
|                     long endTime = MonotonicClock.millis() + 500; | ||||
|                     int readBytesCount = mConnection.bulkTransfer(mInterruptEndpoint, buffer, STATUS_BUFFER_SIZE, 500); | ||||
|                     if(readBytesCount == -1 && MonotonicClock.millis() < endTime) | ||||
|                         testConnection(); | ||||
|                     if(readBytesCount == -1) | ||||
|                         testConnection(MonotonicClock.millis() < endTime); | ||||
|                     if (readBytesCount > 0) { | ||||
|                         if (readBytesCount != STATUS_BUFFER_SIZE) { | ||||
|                             throw new IOException("Invalid status notification, expected " + STATUS_BUFFER_SIZE + " bytes, got " + readBytesCount); | ||||
| @ -218,7 +217,8 @@ public class ProlificSerialDriver implements UsbSerialDriver { | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } catch (IOException e) { | ||||
|             } catch (Exception e) { | ||||
|                 if (isOpen()) | ||||
|                     mReadStatusException = e; | ||||
|             } | ||||
|             //Log.d(TAG, "end control line status thread " + mStopReadStatusThread + " " + (mReadStatusException == null ? "-" : mReadStatusException.getMessage())); | ||||
| @ -250,8 +250,8 @@ public class ProlificSerialDriver implements UsbSerialDriver { | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             /* throw and clear an exception which occured in the status read thread */ | ||||
|             IOException readStatusException = mReadStatusException; | ||||
|             /* throw and clear an exception which occurred in the status read thread */ | ||||
|             Exception readStatusException = mReadStatusException; | ||||
|             if (mReadStatusException != null) { | ||||
|                 mReadStatusException = null; | ||||
|                 throw new IOException(readStatusException); | ||||
| @ -265,10 +265,10 @@ public class ProlificSerialDriver implements UsbSerialDriver { | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void openInt(UsbDeviceConnection connection) throws IOException { | ||||
|         public void openInt() throws IOException { | ||||
|             UsbInterface usbInterface = mDevice.getInterface(0); | ||||
| 
 | ||||
|             if (!connection.claimInterface(usbInterface, true)) { | ||||
|             if (!mConnection.claimInterface(usbInterface, true)) { | ||||
|                 throw new IOException("Error claiming Prolific interface 0"); | ||||
|             } | ||||
| 
 | ||||
| @ -290,7 +290,7 @@ public class ProlificSerialDriver implements UsbSerialDriver { | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             byte[] rawDescriptors = connection.getRawDescriptors(); | ||||
|             byte[] rawDescriptors = mConnection.getRawDescriptors(); | ||||
|             if(rawDescriptors == null || rawDescriptors.length < 14) { | ||||
|                 throw new IOException("Could not get device descriptors"); | ||||
|             } | ||||
| @ -315,6 +315,7 @@ public class ProlificSerialDriver implements UsbSerialDriver { | ||||
|             resetDevice(); | ||||
|             doBlackMagic(); | ||||
|             setControlLines(mControlLinesValue); | ||||
|             setFlowControl(mFlowControl); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
| @ -526,7 +527,6 @@ public class ProlificSerialDriver implements UsbSerialDriver { | ||||
|             setControlLines(newControlLinesValue); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         @Override | ||||
|         public EnumSet<ControlLine> getControlLines() throws IOException { | ||||
|             int status = getStatus(); | ||||
| @ -545,6 +545,39 @@ public class ProlificSerialDriver implements UsbSerialDriver { | ||||
|             return EnumSet.allOf(ControlLine.class); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void setFlowControl(FlowControl flowControl) throws IOException { | ||||
|             // vendorOut values from https://www.mail-archive.com/linux-usb@vger.kernel.org/msg110968.html | ||||
|             switch (flowControl) { | ||||
|                 case NONE: | ||||
|                     if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) | ||||
|                         vendorOut(0x0a, 0xff, null); | ||||
|                     else | ||||
|                         vendorOut(0, 0, null); | ||||
|                     break; | ||||
|                 case RTS_CTS: | ||||
|                     if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) | ||||
|                         vendorOut(0x0a, 0xfa, null); | ||||
|                     else | ||||
|                         vendorOut(0, 0x61, null); | ||||
|                     break; | ||||
|                 case XON_XOFF_INLINE: | ||||
|                     if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) | ||||
|                         vendorOut(0x0a, 0xee, null); | ||||
|                     else | ||||
|                         vendorOut(0, 0xc1, null); | ||||
|                     break; | ||||
|                 default: | ||||
|                     throw new UnsupportedOperationException(); | ||||
|             } | ||||
|             mFlowControl = flowControl; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public EnumSet<FlowControl> getSupportedFlowControl() { | ||||
|             return EnumSet.of(FlowControl.NONE, FlowControl.RTS_CTS, FlowControl.XON_XOFF_INLINE); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { | ||||
|             if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) { | ||||
| @ -567,6 +600,7 @@ public class ProlificSerialDriver implements UsbSerialDriver { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @SuppressWarnings({"unused"}) | ||||
|     public static Map<Integer, int[]> getSupportedDevices() { | ||||
|         final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>(); | ||||
|         supportedDevices.put(UsbId.VENDOR_PROLIFIC, | ||||
|  | ||||
| @ -9,7 +9,8 @@ import java.io.InterruptedIOException; | ||||
|  * {@see InterruptedIOException#bytesTransferred} may contain bytes transferred | ||||
|  */ | ||||
| public class SerialTimeoutException extends InterruptedIOException { | ||||
|     public SerialTimeoutException(String s) { | ||||
|     public SerialTimeoutException(String s, int bytesTransferred) { | ||||
|         super(s); | ||||
|         this.bytesTransferred = bytesTransferred; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -23,27 +23,6 @@ public final class UsbId { | ||||
|     public static final int FTDI_FT232H = 0x6014; | ||||
|     public static final int FTDI_FT231X = 0x6015; // same ID for FT230X, FT231X, FT234XD | ||||
| 
 | ||||
|     public static final int VENDOR_ATMEL = 0x03EB; | ||||
|     public static final int ATMEL_LUFA_CDC_DEMO_APP = 0x2044; | ||||
| 
 | ||||
|     public static final int VENDOR_ARDUINO = 0x2341; | ||||
|     public static final int ARDUINO_UNO = 0x0001; | ||||
|     public static final int ARDUINO_MEGA_2560 = 0x0010; | ||||
|     public static final int ARDUINO_SERIAL_ADAPTER = 0x003b; | ||||
|     public static final int ARDUINO_MEGA_ADK = 0x003f; | ||||
|     public static final int ARDUINO_MEGA_2560_R3 = 0x0042; | ||||
|     public static final int ARDUINO_UNO_R3 = 0x0043; | ||||
|     public static final int ARDUINO_MEGA_ADK_R3 = 0x0044; | ||||
|     public static final int ARDUINO_SERIAL_ADAPTER_R3 = 0x0044; | ||||
|     public static final int ARDUINO_LEONARDO = 0x8036; | ||||
|     public static final int ARDUINO_MICRO = 0x8037; | ||||
| 
 | ||||
|     public static final int VENDOR_VAN_OOIJEN_TECH = 0x16c0; | ||||
|     public static final int VAN_OOIJEN_TECH_TEENSYDUINO_SERIAL = 0x0483; | ||||
| 
 | ||||
|     public static final int VENDOR_LEAFLABS = 0x1eaf; | ||||
|     public static final int LEAFLABS_MAPLE = 0x0004; | ||||
| 
 | ||||
|     public static final int VENDOR_SILABS = 0x10c4; | ||||
|     public static final int SILABS_CP2102 = 0xea60; // same ID for CP2101, CP2103, CP2104, CP2109 | ||||
|     public static final int SILABS_CP2105 = 0xea70; | ||||
| @ -58,21 +37,17 @@ public final class UsbId { | ||||
|     public static final int PROLIFIC_PL2303GE = 0x23e3; // " | ||||
|     public static final int PROLIFIC_PL2303GS = 0x23f3; // " | ||||
| 
 | ||||
|     public static final int VENDOR_GOOGLE = 0x18d1; | ||||
|     public static final int GOOGLE_CR50 = 0x5014; | ||||
| 
 | ||||
|     public static final int VENDOR_QINHENG = 0x1a86; | ||||
|     public static final int QINHENG_CH340 = 0x7523; | ||||
|     public static final int QINHENG_CH341A = 0x5523; | ||||
|     public static final int QINHENG_CH9102F = 0x55D4; | ||||
| 
 | ||||
|     // at www.linux-usb.org/usb.ids listed for NXP/LPC1768, but all processors supported by ARM mbed DAPLink firmware report these ids | ||||
|     public static final int VENDOR_ARM = 0x0d28; | ||||
|     public static final int ARM_MBED = 0x0204; | ||||
|     public static final int VENDOR_UNISOC = 0x1782; | ||||
|     public static final int FIBOCOM_L610 = 0x4D10; | ||||
|     public static final int FIBOCOM_L612 = 0x4D12; | ||||
| 
 | ||||
|     public static final int VENDOR_ST = 0x0483; | ||||
|     public static final int ST_CDC = 0x5740; | ||||
| 
 | ||||
|     public static final int VENDOR_RASPBERRY_PI = 0x2e8a; | ||||
|     public static final int RASPBERRY_PI_PICO_MICROPYTHON = 0x0005; | ||||
|     public static final int RASPBERRY_PI_PICO_SDK = 0x000a; | ||||
| 
 | ||||
|     private UsbId() { | ||||
|         throw new IllegalAccessError("Non-instantiable class"); | ||||
|  | ||||
| @ -10,12 +10,17 @@ import android.hardware.usb.UsbDevice; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| /** | ||||
|  * | ||||
|  * @author mike wakerly (opensource@hoho.com) | ||||
|  */ | ||||
| public interface UsbSerialDriver { | ||||
| 
 | ||||
|     /* | ||||
|      * Additional interface properties. Invoked thru reflection. | ||||
|      * | ||||
|         UsbSerialDriver(UsbDevice device);                  // constructor with device | ||||
|         static Map<Integer, int[]> getSupportedDevices(); | ||||
|         static boolean probe(UsbDevice device);             // optional | ||||
|      */ | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the raw {@link UsbDevice} backing this port. | ||||
|      * | ||||
|  | ||||
| @ -60,6 +60,15 @@ public interface UsbSerialPort extends Closeable { | ||||
|     /** Values for get[Supported]ControlLines() */ | ||||
|     enum ControlLine { RTS, CTS, DTR, DSR, CD, RI } | ||||
| 
 | ||||
|     /** Values for (set|get|getSupported)FlowControl() */ | ||||
|     enum FlowControl { NONE, RTS_CTS, DTR_DSR, XON_XOFF, XON_XOFF_INLINE } | ||||
| 
 | ||||
|     /** XON character used with flow control XON/XOFF */ | ||||
|     char CHAR_XON = 17; | ||||
|     /** XOFF character used with flow control XON/XOFF */ | ||||
|     char CHAR_XOFF = 19; | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the driver used by this port. | ||||
|      */ | ||||
| @ -122,6 +131,17 @@ public interface UsbSerialPort extends Closeable { | ||||
|      */ | ||||
|     int read(final byte[] dest, final int timeout) throws IOException; | ||||
| 
 | ||||
|     /** | ||||
|      * Reads bytes with specified length into the destination buffer. | ||||
|      * | ||||
|      * @param dest the destination byte buffer | ||||
|      * @param length the maximum length of the data to read | ||||
|      * @param timeout the timeout for reading in milliseconds, 0 is infinite | ||||
|      * @return the actual number of bytes read | ||||
|      * @throws IOException if an error occurred during reading | ||||
|      */ | ||||
|     int read(final byte[] dest, int length, final int timeout) throws IOException; | ||||
| 
 | ||||
|     /** | ||||
|      * Writes as many bytes as possible from the source buffer. | ||||
|      * | ||||
| @ -133,6 +153,18 @@ public interface UsbSerialPort extends Closeable { | ||||
|      */ | ||||
|     void write(final byte[] src, final int timeout) throws IOException; | ||||
| 
 | ||||
|     /** | ||||
|      * Writes bytes with specified length from the source buffer. | ||||
|      * | ||||
|      * @param src the source byte buffer | ||||
|      * @param length the length of the data to write | ||||
|      * @param timeout the timeout for writing in milliseconds, 0 is infinite | ||||
|      * @throws SerialTimeoutException if timeout reached before sending all data. | ||||
|      *                                ex.bytesTransferred may contain bytes transferred | ||||
|      * @throws IOException if an error occurred during writing | ||||
|      */ | ||||
|     void write(final byte[] src, int length, final int timeout) throws IOException; | ||||
| 
 | ||||
|     /** | ||||
|      * Sets various serial port parameters. | ||||
|      * | ||||
| @ -143,7 +175,7 @@ public interface UsbSerialPort extends Closeable { | ||||
|      * @param parity one of {@link #PARITY_NONE}, {@link #PARITY_ODD}, | ||||
|      *               {@link #PARITY_EVEN}, {@link #PARITY_MARK}, or {@link #PARITY_SPACE}. | ||||
|      * @throws IOException on error setting the port parameters | ||||
|      * @throws UnsupportedOperationException if values are not supported by a specific device | ||||
|      * @throws UnsupportedOperationException if not supported or values are not supported by a specific device | ||||
|      */ | ||||
|     void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException; | ||||
| 
 | ||||
| @ -225,6 +257,7 @@ public interface UsbSerialPort extends Closeable { | ||||
|      * | ||||
|      * @return EnumSet.contains(...) is {@code true} if set, else {@code false} | ||||
|      * @throws IOException if an error occurred during reading | ||||
|      * @throws UnsupportedOperationException if not supported | ||||
|      */ | ||||
|     EnumSet<ControlLine> getControlLines() throws IOException; | ||||
| 
 | ||||
| @ -236,6 +269,36 @@ public interface UsbSerialPort extends Closeable { | ||||
|      */ | ||||
|     EnumSet<ControlLine> getSupportedControlLines() throws IOException; | ||||
| 
 | ||||
|     /** | ||||
|      * Set flow control mode, if supported | ||||
|      * @param flowControl @FlowControl | ||||
|      * @throws IOException if an error occurred during writing | ||||
|      * @throws UnsupportedOperationException if not supported | ||||
|      */ | ||||
|     void setFlowControl(FlowControl flowControl) throws IOException; | ||||
| 
 | ||||
|     /** | ||||
|      * Get flow control mode. | ||||
|      * @return FlowControl | ||||
|      */ | ||||
|     FlowControl getFlowControl(); | ||||
| 
 | ||||
|     /** | ||||
|      * Get supported flow control modes | ||||
|      * @return EnumSet.contains(...) is {@code true} if supported, else {@code false} | ||||
|      */ | ||||
|     EnumSet<FlowControl> getSupportedFlowControl(); | ||||
| 
 | ||||
|     /** | ||||
|      * If flow control = XON_XOFF, indicates that send is enabled by XON. | ||||
|      * Devices supporting flow control = XON_XOFF_INLINE return CHAR_XON/CHAR_XOFF in read() data. | ||||
|      * | ||||
|      * @return the current state | ||||
|      * @throws IOException if an error occurred during reading | ||||
|      * @throws UnsupportedOperationException if not supported | ||||
|      */ | ||||
|     boolean getXON() throws IOException; | ||||
| 
 | ||||
|     /** | ||||
|      * Purge non-transmitted output data and / or non-read input data. | ||||
|      * | ||||
|  | ||||
| @ -37,6 +37,8 @@ public class UsbSerialProber { | ||||
|         probeTable.addDriver(FtdiSerialDriver.class); | ||||
|         probeTable.addDriver(ProlificSerialDriver.class); | ||||
|         probeTable.addDriver(Ch34xSerialDriver.class); | ||||
|         probeTable.addDriver(GsmModemSerialDriver.class); | ||||
|         probeTable.addDriver(ChromeCcdSerialDriver.class); | ||||
|         return probeTable; | ||||
|     } | ||||
| 
 | ||||
| @ -69,11 +71,7 @@ public class UsbSerialProber { | ||||
|      *         {@code null} if none available. | ||||
|      */ | ||||
|     public UsbSerialDriver probeDevice(final UsbDevice usbDevice) { | ||||
|         final int vendorId = usbDevice.getVendorId(); | ||||
|         final int productId = usbDevice.getProductId(); | ||||
| 
 | ||||
|         final Class<? extends UsbSerialDriver> driverClass = | ||||
|                 mProbeTable.findDriver(vendorId, productId); | ||||
|         final Class<? extends UsbSerialDriver> driverClass = mProbeTable.findDriver(usbDevice); | ||||
|         if (driverClass != null) { | ||||
|             final UsbSerialDriver driver; | ||||
|             try { | ||||
|  | ||||
| @ -19,14 +19,17 @@ package com.hoho.android.usbserial.util; | ||||
| import java.security.InvalidParameterException; | ||||
| 
 | ||||
| /** | ||||
|  * Clone of Android's HexDump class, for use in debugging. Cosmetic changes | ||||
|  * only. | ||||
|  * Clone of Android's /core/java/com/android/internal/util/HexDump class, for use in debugging. | ||||
|  * Changes: space separated hex strings | ||||
|  */ | ||||
| public class HexDump { | ||||
|     private final static char[] HEX_DIGITS = { | ||||
|             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' | ||||
|     }; | ||||
| 
 | ||||
|     private HexDump() { | ||||
|     } | ||||
| 
 | ||||
|     public static String dumpHexString(byte[] array) { | ||||
|         return dumpHexString(array, 0, array.length); | ||||
|     } | ||||
| @ -82,10 +85,12 @@ public class HexDump { | ||||
|     } | ||||
| 
 | ||||
|     public static String toHexString(byte[] array, int offset, int length) { | ||||
|         char[] buf = new char[length * 2]; | ||||
|         char[] buf = new char[length > 0 ? length * 3 - 1 : 0]; | ||||
| 
 | ||||
|         int bufIndex = 0; | ||||
|         for (int i = offset; i < offset + length; i++) { | ||||
|             if (i > offset) | ||||
|                 buf[bufIndex++] = ' '; | ||||
|             byte b = array[i]; | ||||
|             buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F]; | ||||
|             buf[bufIndex++] = HEX_DIGITS[b & 0x0F]; | ||||
| @ -139,13 +144,13 @@ public class HexDump { | ||||
|         throw new InvalidParameterException("Invalid hex char '" + c + "'"); | ||||
|     } | ||||
| 
 | ||||
|     /** accepts any separator, e.g. space or newline */ | ||||
|     public static byte[] hexStringToByteArray(String hexString) { | ||||
|         int length = hexString.length(); | ||||
|         byte[] buffer = new byte[length / 2]; | ||||
|         byte[] buffer = new byte[(length + 1) / 3]; | ||||
| 
 | ||||
|         for (int i = 0; i < length; i += 2) { | ||||
|             buffer[i / 2] = (byte) ((toByte(hexString.charAt(i)) << 4) | toByte(hexString | ||||
|                     .charAt(i + 1))); | ||||
|         for (int i = 0; i < length; i += 3) { | ||||
|             buffer[i / 3] = (byte) ((toByte(hexString.charAt(i)) << 4) | toByte(hexString.charAt(i + 1))); | ||||
|         } | ||||
| 
 | ||||
|         return buffer; | ||||
| @ -13,16 +13,19 @@ import com.hoho.android.usbserial.driver.UsbSerialPort; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.nio.ByteBuffer; | ||||
| import java.util.concurrent.CountDownLatch; | ||||
| import java.util.concurrent.atomic.AtomicReference; | ||||
| 
 | ||||
| /** | ||||
|  * Utility class which services a {@link UsbSerialPort} in its {@link #run()} method. | ||||
|  * Utility class which services a {@link UsbSerialPort} in its {@link #runWrite()} ()} and {@link #runRead()} ()} ()} methods. | ||||
|  * | ||||
|  * @author mike wakerly (opensource@hoho.com) | ||||
|  */ | ||||
| public class SerialInputOutputManager implements Runnable { | ||||
| public class SerialInputOutputManager { | ||||
| 
 | ||||
|     public enum State { | ||||
|         STOPPED, | ||||
|         STARTING, | ||||
|         RUNNING, | ||||
|         STOPPING | ||||
|     } | ||||
| @ -32,9 +35,6 @@ public class SerialInputOutputManager implements Runnable { | ||||
|     private static final String TAG = SerialInputOutputManager.class.getSimpleName(); | ||||
|     private static final int BUFSIZ = 4096; | ||||
| 
 | ||||
|     /** | ||||
|      * default read timeout is infinite, to avoid data loss with bulkTransfer API | ||||
|      */ | ||||
|     private int mReadTimeout = 0; | ||||
|     private int mWriteTimeout = 0; | ||||
| 
 | ||||
| @ -45,7 +45,8 @@ public class SerialInputOutputManager implements Runnable { | ||||
|     private ByteBuffer mWriteBuffer = ByteBuffer.allocate(BUFSIZ); | ||||
| 
 | ||||
|     private int mThreadPriority = Process.THREAD_PRIORITY_URGENT_AUDIO; | ||||
|     private State mState = State.STOPPED; // Synchronized by 'this' | ||||
|     private final AtomicReference<State> mState = new AtomicReference<>(State.STOPPED); | ||||
|     private CountDownLatch mStartuplatch = new CountDownLatch(2); | ||||
|     private Listener mListener; // Synchronized by 'this' | ||||
|     private final UsbSerialPort mSerialPort; | ||||
| 
 | ||||
| @ -56,7 +57,7 @@ public class SerialInputOutputManager implements Runnable { | ||||
|         void onNewData(byte[] data); | ||||
| 
 | ||||
|         /** | ||||
|          * Called when {@link SerialInputOutputManager#run()} aborts due to an error. | ||||
|          * Called when {@link SerialInputOutputManager#runRead()} ()} or {@link SerialInputOutputManager#runWrite()} ()} ()} aborts due to an error. | ||||
|          */ | ||||
|         void onRunError(Exception e); | ||||
|     } | ||||
| @ -86,8 +87,9 @@ public class SerialInputOutputManager implements Runnable { | ||||
|      * @param threadPriority  see {@link Process#setThreadPriority(int)} | ||||
|      * */ | ||||
|     public void setThreadPriority(int threadPriority) { | ||||
|         if (mState != State.STOPPED) | ||||
|         if (!mState.compareAndSet(State.STOPPED, State.STOPPED)) { | ||||
|             throw new IllegalStateException("threadPriority only configurable before SerialInputOutputManager is started"); | ||||
|         } | ||||
|         mThreadPriority = threadPriority; | ||||
|     } | ||||
| 
 | ||||
| @ -96,7 +98,7 @@ public class SerialInputOutputManager implements Runnable { | ||||
|      */ | ||||
|     public void setReadTimeout(int timeout) { | ||||
|         // when set if already running, read already blocks and the new value will not become effective now | ||||
|         if(mReadTimeout == 0 && timeout != 0 && mState != State.STOPPED) | ||||
|         if(mReadTimeout == 0 && timeout != 0 && mState.get() != State.STOPPED) | ||||
|             throw new IllegalStateException("readTimeout only configurable before SerialInputOutputManager is started"); | ||||
|         mReadTimeout = timeout; | ||||
|     } | ||||
| @ -144,79 +146,152 @@ public class SerialInputOutputManager implements Runnable { | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * when using writeAsync, it is recommended to use readTimeout != 0, | ||||
|      * else the write will be delayed until read data is available | ||||
|      * write data asynchronously | ||||
|      */ | ||||
|     public void writeAsync(byte[] data) { | ||||
|         synchronized (mWriteBufferLock) { | ||||
|             mWriteBuffer.put(data); | ||||
|             mWriteBufferLock.notifyAll(); // Notify waiting threads | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * start SerialInputOutputManager in separate thread | ||||
|      * start SerialInputOutputManager in separate threads | ||||
|      */ | ||||
|     public void start() { | ||||
|         if(mState != State.STOPPED) | ||||
|         if(mState.compareAndSet(State.STOPPED, State.STARTING)) { | ||||
|             mStartuplatch = new CountDownLatch(2); | ||||
|             new Thread(this::runRead, this.getClass().getSimpleName() + "_read").start(); | ||||
|             new Thread(this::runWrite, this.getClass().getSimpleName() + "_write").start(); | ||||
|             try { | ||||
|                 mStartuplatch.await(); | ||||
|                 mState.set(State.RUNNING); | ||||
|             } catch (InterruptedException e) { | ||||
|                 Thread.currentThread().interrupt(); | ||||
|             } | ||||
|         } else { | ||||
|             throw new IllegalStateException("already started"); | ||||
|         new Thread(this, this.getClass().getSimpleName()).start(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * stop SerialInputOutputManager thread | ||||
|      * stop SerialInputOutputManager threads | ||||
|      * | ||||
|      * when using readTimeout == 0 (default), additionally use usbSerialPort.close() to | ||||
|      * interrupt blocking read | ||||
|      */ | ||||
|     public synchronized void stop() { | ||||
|         if (getState() == State.RUNNING) { | ||||
|     public void stop() { | ||||
|         if(mState.compareAndSet(State.RUNNING, State.STOPPING)) { | ||||
|             synchronized (mWriteBufferLock) { | ||||
|                 mWriteBufferLock.notifyAll(); // wake up write thread to check the stop condition | ||||
|             } | ||||
|             Log.i(TAG, "Stop requested"); | ||||
|             mState = State.STOPPING; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public synchronized State getState() { | ||||
|         return mState; | ||||
|     public State getState() { | ||||
|         return mState.get(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Continuously services the read and write buffers until {@link #stop()} is | ||||
|      * called, or until a driver exception is raised. | ||||
|      * @return true if the thread is still running | ||||
|      */ | ||||
|     @Override | ||||
|     public void run() { | ||||
|         synchronized (this) { | ||||
|             if (getState() != State.STOPPED) { | ||||
|                 throw new IllegalStateException("Already running"); | ||||
|     private boolean isStillRunning() { | ||||
|         State state = mState.get(); | ||||
|         return ((state == State.RUNNING) || (state == State.STARTING)) | ||||
|             && !Thread.currentThread().isInterrupted(); | ||||
|     } | ||||
|             mState = State.RUNNING; | ||||
|         } | ||||
|         Log.i(TAG, "Running ..."); | ||||
|         try { | ||||
|             if(mThreadPriority != Process.THREAD_PRIORITY_DEFAULT) | ||||
|                 Process.setThreadPriority(mThreadPriority); | ||||
|             while (true) { | ||||
|                 if (getState() != State.RUNNING) { | ||||
|                     Log.i(TAG, "Stopping mState=" + getState()); | ||||
|                     break; | ||||
|                 } | ||||
|                 step(); | ||||
|             } | ||||
|         } catch (Exception e) { | ||||
|             Log.w(TAG, "Run ending due to exception: " + e.getMessage(), e); | ||||
|             final Listener listener = getListener(); | ||||
| 
 | ||||
|     /** | ||||
|      * Notify listener of an error | ||||
|      * | ||||
|      * @param e the exception | ||||
|      */ | ||||
|     private void notifyErrorListener(Throwable e) { | ||||
|         Listener listener = getListener(); | ||||
|         if (listener != null) { | ||||
|               listener.onRunError(e); | ||||
|             } | ||||
|         } finally { | ||||
|             synchronized (this) { | ||||
|                 mState = State.STOPPED; | ||||
|                 Log.i(TAG, "Stopped"); | ||||
|             try { | ||||
|                 listener.onRunError(e instanceof Exception ? (Exception) e : new Exception(e)); | ||||
|             } catch (Throwable t) { | ||||
|                 Log.w(TAG, "Exception in onRunError: " + t.getMessage(), t); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void step() throws IOException { | ||||
|     /** | ||||
|      * Set the thread priority | ||||
|      */ | ||||
|     private void setThreadPriority() { | ||||
|         if (mThreadPriority != Process.THREAD_PRIORITY_DEFAULT) { | ||||
|             Process.setThreadPriority(mThreadPriority); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Continuously services the read buffers until {@link #stop()} is called, or until a driver exception is | ||||
|      * raised. | ||||
|      */ | ||||
|     void runRead() { | ||||
|         Log.i(TAG, "runRead running ..."); | ||||
|         try { | ||||
|             setThreadPriority(); | ||||
|             mStartuplatch.countDown(); | ||||
|             do { | ||||
|                 stepRead(); | ||||
|             } while (isStillRunning()); | ||||
|             Log.i(TAG, "runRead: Stopping mState=" + getState()); | ||||
|         } catch (Throwable e) { | ||||
|             if (Thread.currentThread().isInterrupted()) { | ||||
|                 Log.w(TAG, "runRead: interrupted"); | ||||
|             } else if(mSerialPort.isOpen()) { | ||||
|                 Log.w(TAG, "runRead ending due to exception: " + e.getMessage(), e); | ||||
|             } else { | ||||
|                 Log.i(TAG, "runRead: Socket closed"); | ||||
|             } | ||||
|             notifyErrorListener(e); | ||||
|         } finally { | ||||
|             if (mState.compareAndSet(State.RUNNING, State.STOPPING)) { | ||||
|                 synchronized (mWriteBufferLock) { | ||||
|                     mWriteBufferLock.notifyAll(); // wake up write thread to check the stop condition | ||||
|                 } | ||||
|             } else if (mState.compareAndSet(State.STOPPING, State.STOPPED)) { | ||||
|                 Log.i(TAG, "runRead: Stopped mState=" + getState()); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Continuously services the write buffers until {@link #stop()} is called, or until a driver exception is | ||||
|      * raised. | ||||
|      */ | ||||
|     void runWrite() { | ||||
|         Log.i(TAG, "runWrite running ..."); | ||||
|         try { | ||||
|             setThreadPriority(); | ||||
|             mStartuplatch.countDown(); | ||||
|             do { | ||||
|                 stepWrite(); | ||||
|             } while (isStillRunning()); | ||||
|             Log.i(TAG, "runWrite: Stopping mState=" + getState()); | ||||
|         } catch (Throwable e) { | ||||
|             if (Thread.currentThread().isInterrupted()) { | ||||
|                 Log.w(TAG, "runWrite: interrupted"); | ||||
|             } else if(mSerialPort.isOpen()) { | ||||
|                 Log.w(TAG, "runWrite ending due to exception: " + e.getMessage(), e); | ||||
|             } else { | ||||
|                 Log.i(TAG, "runWrite: Socket closed"); | ||||
|             } | ||||
|             notifyErrorListener(e); | ||||
|         } finally { | ||||
|             if (!mState.compareAndSet(State.RUNNING, State.STOPPING)) { | ||||
|                 if (mState.compareAndSet(State.STOPPING, State.STOPPED)) { | ||||
|                     Log.i(TAG, "runWrite: Stopped mState=" + getState()); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void stepRead() throws IOException { | ||||
|         // Handle incoming data. | ||||
|         byte[] buffer; | ||||
|         synchronized (mReadBufferLock) { | ||||
| @ -234,21 +309,26 @@ public class SerialInputOutputManager implements Runnable { | ||||
|                 listener.onNewData(data); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void stepWrite() throws IOException, InterruptedException { | ||||
|         // Handle outgoing data. | ||||
|         buffer = null; | ||||
|         byte[] buffer = null; | ||||
|         synchronized (mWriteBufferLock) { | ||||
|             len = mWriteBuffer.position(); | ||||
|             int len = mWriteBuffer.position(); | ||||
|             if (len > 0) { | ||||
|                 buffer = new byte[len]; | ||||
|                 mWriteBuffer.rewind(); | ||||
|                 mWriteBuffer.get(buffer, 0, len); | ||||
|                 mWriteBuffer.clear(); | ||||
|                 mWriteBufferLock.notifyAll(); // Notify writeAsync that there is space in the buffer | ||||
|             } else { | ||||
|                 mWriteBufferLock.wait(); | ||||
|             } | ||||
|         } | ||||
|         if (buffer != null) { | ||||
|             if (DEBUG) { | ||||
|                 Log.d(TAG, "Writing data len=" + len); | ||||
|                 Log.d(TAG, "Writing data len=" + buffer.length); | ||||
|             } | ||||
|             mSerialPort.write(buffer, mWriteTimeout); | ||||
|         } | ||||
|  | ||||
| @ -0,0 +1,33 @@ | ||||
| package com.hoho.android.usbserial.util; | ||||
| 
 | ||||
| import android.hardware.usb.UsbDeviceConnection; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| 
 | ||||
| public class UsbUtils { | ||||
| 
 | ||||
|     private UsbUtils() { | ||||
|     } | ||||
| 
 | ||||
|     public static  ArrayList<byte[]> getDescriptors(UsbDeviceConnection connection) { | ||||
|         ArrayList<byte[]> descriptors = new ArrayList<>(); | ||||
|         byte[] rawDescriptors = connection.getRawDescriptors(); | ||||
|         if (rawDescriptors != null) { | ||||
|             int pos = 0; | ||||
|             while (pos < rawDescriptors.length) { | ||||
|                 int len = rawDescriptors[pos] & 0xFF; | ||||
|                 if (len == 0) | ||||
|                     break; | ||||
|                 if (pos + len > rawDescriptors.length) | ||||
|                     len = rawDescriptors.length - pos; | ||||
|                 byte[] descriptor = new byte[len]; | ||||
|                 System.arraycopy(rawDescriptors, pos, descriptor, 0, len); | ||||
|                 descriptors.add(descriptor); | ||||
|                 pos += len; | ||||
|             } | ||||
|         } | ||||
|         return descriptors; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,47 @@ | ||||
| package com.hoho.android.usbserial.util; | ||||
| 
 | ||||
| import com.hoho.android.usbserial.driver.UsbSerialPort; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| /** | ||||
|  * Some devices return XON and XOFF characters inline in read() data. | ||||
|  * Other devices return XON / XOFF condition thru getXOFF() method. | ||||
|  */ | ||||
| 
 | ||||
| public class XonXoffFilter { | ||||
|     private boolean xon = true; | ||||
| 
 | ||||
|     public XonXoffFilter() { | ||||
|     } | ||||
| 
 | ||||
|     public boolean getXON()  { | ||||
|         return xon; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Filter XON/XOFF from read() data and remember | ||||
|      * | ||||
|      * @param data unfiltered data | ||||
|      * @return filtered data | ||||
|      */ | ||||
|     public byte[] filter(byte[] data) { | ||||
|         int found = 0; | ||||
|         for (int i=0; i<data.length; i++) { | ||||
|             if (data[i] == UsbSerialPort.CHAR_XON || data[i] == UsbSerialPort.CHAR_XOFF) | ||||
|                 found++; | ||||
|         } | ||||
|         if(found == 0) | ||||
|             return data; | ||||
|         byte[] filtered = new byte[data.length - found]; | ||||
|         for (int i=0, j=0; i<data.length; i++) { | ||||
|             if (data[i] == UsbSerialPort.CHAR_XON) | ||||
|                 xon = true; | ||||
|             else if(data[i] == UsbSerialPort.CHAR_XOFF) | ||||
|                 xon = false; | ||||
|             else | ||||
|                 filtered[j++] = data[i]; | ||||
|         } | ||||
|         return filtered; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										29
									
								
								usbSerialForAndroid/src/test/java/android/util/Log.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								usbSerialForAndroid/src/test/java/android/util/Log.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| // without this class Mockito complains about non-mocked Log methods | ||||
| package android.util; | ||||
| 
 | ||||
| public class Log { | ||||
|     public static int d(String tag, String msg) { | ||||
|         System.out.println("DEBUG: " + tag + ": " + msg); | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     public static int i(String tag, String msg) { | ||||
|         System.out.println("INFO: " + tag + ": " + msg); | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     public static int w(String tag, String msg) { | ||||
|         System.out.println("WARN: " + tag + ": " + msg); | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     public static int w(String tag, String msg, Throwable tr) { | ||||
|         System.out.println("WARN: " + tag + ": " + msg + " / " + tr.getMessage()); | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     public static int e(String tag, String msg) { | ||||
|         System.out.println("ERROR: " + tag + ": " + msg); | ||||
|         return 0; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										85
									
								
								usbSerialForAndroid/src/test/java/android/util/Pair.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								usbSerialForAndroid/src/test/java/android/util/Pair.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,85 @@ | ||||
| /* | ||||
|  * Copyright (C) 2009 The Android Open Source Project | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *      http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package android.util; | ||||
| 
 | ||||
| 
 | ||||
| import androidx.annotation.Nullable; | ||||
| 
 | ||||
| import java.util.Objects; | ||||
| 
 | ||||
| /** | ||||
|  * Container to ease passing around a tuple of two objects. This object provides a sensible | ||||
|  * implementation of equals(), returning true if equals() is true on each of the contained | ||||
|  * objects. | ||||
|  */ | ||||
| public class Pair<F, S> { | ||||
|     public final F first; | ||||
|     public final S second; | ||||
| 
 | ||||
|     /** | ||||
|      * Constructor for a Pair. | ||||
|      * | ||||
|      * @param first the first object in the Pair | ||||
|      * @param second the second object in the pair | ||||
|      */ | ||||
|     public Pair(F first, S second) { | ||||
|         this.first = first; | ||||
|         this.second = second; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Checks the two objects for equality by delegating to their respective | ||||
|      * {@link Object#equals(Object)} methods. | ||||
|      * | ||||
|      * @param o the {@link Pair} to which this one is to be checked for equality | ||||
|      * @return true if the underlying objects of the Pair are both considered | ||||
|      *         equal | ||||
|      */ | ||||
|     @Override | ||||
|     public boolean equals(@Nullable Object o) { | ||||
|         if (!(o instanceof Pair)) { | ||||
|             return false; | ||||
|         } | ||||
|         Pair<?, ?> p = (Pair<?, ?>) o; | ||||
|         return Objects.equals(p.first, first) && Objects.equals(p.second, second); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Compute a hash code using the hash codes of the underlying objects | ||||
|      * | ||||
|      * @return a hashcode of the Pair | ||||
|      */ | ||||
|     @Override | ||||
|     public int hashCode() { | ||||
|         return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode()); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String toString() { | ||||
|         return "Pair{" + String.valueOf(first) + " " + String.valueOf(second) + "}"; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Convenience method for creating an appropriately typed pair. | ||||
|      * @param a the first object in the Pair | ||||
|      * @param b the second object in the pair | ||||
|      * @return a Pair that is templatized with the types of a and b | ||||
|      */ | ||||
|     public static <A, B> Pair <A, B> create(A a, B b) { | ||||
|         return new Pair<A, B>(a, b); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,546 @@ | ||||
| package com.hoho.android.usbserial.driver; | ||||
| 
 | ||||
| import static com.hoho.android.usbserial.driver.CdcAcmSerialDriver.USB_SUBCLASS_ACM; | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertNull; | ||||
| import static org.junit.Assert.assertThrows; | ||||
| import static org.mockito.Mockito.clearInvocations; | ||||
| import static org.mockito.Mockito.mock; | ||||
| import static org.mockito.Mockito.times; | ||||
| import static org.mockito.Mockito.verify; | ||||
| import static org.mockito.Mockito.when; | ||||
| 
 | ||||
| import android.hardware.usb.UsbConstants; | ||||
| import android.hardware.usb.UsbDevice; | ||||
| import android.hardware.usb.UsbDeviceConnection; | ||||
| import android.hardware.usb.UsbEndpoint; | ||||
| import android.hardware.usb.UsbInterface; | ||||
| 
 | ||||
| import com.hoho.android.usbserial.util.HexDump; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| public class CdcAcmSerialDriverTest { | ||||
| 
 | ||||
|     @Test | ||||
|     public void standardDevice() throws Exception { | ||||
|         UsbDeviceConnection usbDeviceConnection = mock(UsbDeviceConnection.class); | ||||
|         UsbDevice usbDevice = mock(UsbDevice.class); | ||||
|         UsbInterface controlInterface = mock(UsbInterface.class); | ||||
|         UsbInterface dataInterface = mock(UsbInterface.class); | ||||
|         UsbEndpoint controlEndpoint = mock(UsbEndpoint.class); | ||||
|         UsbEndpoint readEndpoint = mock(UsbEndpoint.class); | ||||
|         UsbEndpoint writeEndpoint = mock(UsbEndpoint.class); | ||||
| 
 | ||||
|         /* | ||||
|          * digispark - no IAD | ||||
|          *   UsbInterface[mId=0,mAlternateSetting=0,mName=null,mClass=2,mSubclass=2,mProtocol=1,mEndpoints=[ | ||||
|          *     UsbEndpoint[mAddress=131,mAttributes=3,mMaxPacketSize=8,mInterval=255]] | ||||
|          *   UsbInterface[mId=1,mAlternateSetting=0,mName=null,mClass=10,mSubclass=0,mProtocol=0,mEndpoints=[ | ||||
|          *     UsbEndpoint[mAddress=1,mAttributes=2,mMaxPacketSize=8,mInterval=0] | ||||
|          *     UsbEndpoint[mAddress=129,mAttributes=2,mMaxPacketSize=8,mInterval=0]] | ||||
|          */ | ||||
|         when(usbDeviceConnection.getRawDescriptors()).thenReturn(HexDump.hexStringToByteArray( | ||||
|                 "12 01 10 01 02 00 00 08 D0 16 7E 08 00 01 01 02 00 01\n" + | ||||
|                 "09 02 43 00 02 01 00 80 32\n" + | ||||
|                 "09 04 00 00 01 02 02 01 00\n" + | ||||
|                 "05 24 00 10 01\n" + | ||||
|                 "04 24 02 02\n" + | ||||
|                 "05 24 06 00 01\n" + | ||||
|                 "05 24 01 03 01\n" + | ||||
|                 "07 05 83 03 08 00 FF\n" + | ||||
|                 "09 04 01 00 02 0A 00 00 00\n" + | ||||
|                 "07 05 01 02 08 00 00\n" + | ||||
|                 "07 05 81 02 08 00 00")); | ||||
|         when(usbDeviceConnection.claimInterface(controlInterface,true)).thenReturn(true); | ||||
|         when(usbDeviceConnection.claimInterface(dataInterface,true)).thenReturn(true); | ||||
|         when(usbDevice.getInterfaceCount()).thenReturn(2); | ||||
|         when(usbDevice.getInterface(0)).thenReturn(controlInterface); | ||||
|         when(usbDevice.getInterface(1)).thenReturn(dataInterface); | ||||
|         when(controlInterface.getId()).thenReturn(0); | ||||
|         when(controlInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_COMM); | ||||
|         when(controlInterface.getInterfaceSubclass()).thenReturn(USB_SUBCLASS_ACM); | ||||
|         when(controlInterface.getEndpointCount()).thenReturn(1); | ||||
|         when(controlInterface.getEndpoint(0)).thenReturn(controlEndpoint); | ||||
|         when(dataInterface.getId()).thenReturn(1); | ||||
|         when(dataInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_CDC_DATA); | ||||
|         when(dataInterface.getEndpointCount()).thenReturn(2); | ||||
|         when(dataInterface.getEndpoint(0)).thenReturn(writeEndpoint); | ||||
|         when(dataInterface.getEndpoint(1)).thenReturn(readEndpoint); | ||||
|         when(controlEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||
|         when(controlEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_INT); | ||||
|         when(readEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||
|         when(readEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||
|         when(writeEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_OUT); | ||||
|         when(writeEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||
| 
 | ||||
|         CdcAcmSerialDriver driver = new CdcAcmSerialDriver(usbDevice); | ||||
|         CdcAcmSerialDriver.CdcAcmSerialPort port = (CdcAcmSerialDriver.CdcAcmSerialPort) driver.getPorts().get(0); | ||||
|         port.mConnection = usbDeviceConnection; | ||||
|         port.openInt(); | ||||
|         assertEquals(readEndpoint, port.mReadEndpoint); | ||||
|         assertEquals(writeEndpoint, port.mWriteEndpoint); | ||||
| 
 | ||||
|         ProbeTable probeTable = UsbSerialProber.getDefaultProbeTable(); | ||||
|         Class<? extends UsbSerialDriver> probeDriver = probeTable.findDriver(usbDevice); | ||||
|         assertEquals(driver.getClass(), probeDriver); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void singleInterfaceDevice() throws Exception { | ||||
|         UsbDeviceConnection usbDeviceConnection = mock(UsbDeviceConnection.class); | ||||
|         UsbDevice usbDevice = mock(UsbDevice.class); | ||||
|         UsbInterface usbInterface = mock(UsbInterface.class); | ||||
|         UsbEndpoint controlEndpoint = mock(UsbEndpoint.class); | ||||
|         UsbEndpoint readEndpoint = mock(UsbEndpoint.class); | ||||
|         UsbEndpoint writeEndpoint = mock(UsbEndpoint.class); | ||||
| 
 | ||||
|         when(usbDeviceConnection.claimInterface(usbInterface,true)).thenReturn(true); | ||||
|         when(usbDevice.getInterfaceCount()).thenReturn(1); | ||||
|         when(usbDevice.getInterface(0)).thenReturn(usbInterface); | ||||
|         when(usbInterface.getEndpointCount()).thenReturn(3); | ||||
|         when(usbInterface.getEndpoint(0)).thenReturn(controlEndpoint); | ||||
|         when(usbInterface.getEndpoint(1)).thenReturn(readEndpoint); | ||||
|         when(usbInterface.getEndpoint(2)).thenReturn(writeEndpoint); | ||||
|         when(controlEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||
|         when(controlEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_INT); | ||||
|         when(readEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||
|         when(readEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||
|         when(writeEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_OUT); | ||||
|         when(writeEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||
| 
 | ||||
|         CdcAcmSerialDriver driver = new CdcAcmSerialDriver(usbDevice); | ||||
|         CdcAcmSerialDriver.CdcAcmSerialPort port = (CdcAcmSerialDriver.CdcAcmSerialPort) driver.getPorts().get(0); | ||||
|         port.mConnection = usbDeviceConnection; | ||||
|         port.openInt(); | ||||
|         assertEquals(readEndpoint, port.mReadEndpoint); | ||||
|         assertEquals(writeEndpoint, port.mWriteEndpoint); | ||||
| 
 | ||||
|         ProbeTable probeTable = UsbSerialProber.getDefaultProbeTable(); | ||||
|         Class<? extends UsbSerialDriver> probeDriver = probeTable.findDriver(usbDevice); | ||||
|         assertNull(probeDriver); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void multiPortDevice() throws Exception { | ||||
|         int n = 2; | ||||
|         UsbDeviceConnection usbDeviceConnection = mock(UsbDeviceConnection.class); | ||||
|         UsbDevice usbDevice = mock(UsbDevice.class); | ||||
|         UsbInterface[] controlInterfaces = new UsbInterface[n]; | ||||
|         UsbInterface[] dataInterfaces = new UsbInterface[n]; | ||||
|         UsbEndpoint[] controlEndpoints = new UsbEndpoint[n]; | ||||
|         UsbEndpoint[] readEndpoints = new UsbEndpoint[n]; | ||||
|         UsbEndpoint[] writeEndpoints = new UsbEndpoint[n]; | ||||
| 
 | ||||
|         /* | ||||
|          * pi zero - dual port | ||||
|          *   UsbInterface[mId=0,mAlternateSetting=0,mName=TinyUSB CDC,mClass=2,mSubclass=2,mProtocol=0,mEndpoints=[ | ||||
|          *     UsbEndpoint[mAddress=129,mAttributes=3,mMaxPacketSize=8,mInterval=16]] | ||||
|          *   UsbInterface[mId=1,mAlternateSetting=0,mName=null,mClass=10,mSubclass=0,mProtocol=0,mEndpoints=[ | ||||
|          *     UsbEndpoint[mAddress=2,mAttributes=2,mMaxPacketSize=64,mInterval=0] | ||||
|          *     UsbEndpoint[mAddress=130,mAttributes=2,mMaxPacketSize=64,mInterval=0]] | ||||
|          *   UsbInterface[mId=2,mAlternateSetting=0,mName=TinyUSB CDC,mClass=2,mSubclass=2,mProtocol=0,mEndpoints=[ | ||||
|          *     UsbEndpoint[mAddress=131,mAttributes=3,mMaxPacketSize=8,mInterval=16]] | ||||
|          *   UsbInterface[mId=3,mAlternateSetting=0,mName=null,mClass=10,mSubclass=0,mProtocol=0,mEndpoints=[ | ||||
|          *     UsbEndpoint[mAddress=4,mAttributes=2,mMaxPacketSize=64,mInterval=0] | ||||
|          *      UsbEndpoint[mAddress=132,mAttributes=2,mMaxPacketSize=64,mInterval=0]] | ||||
|          */ | ||||
|         when(usbDeviceConnection.getRawDescriptors()).thenReturn(HexDump.hexStringToByteArray( | ||||
|                 "12 01 00 02 EF 02 01 40 FE CA 02 40 00 01 01 02 03 01\n" + | ||||
|                 "09 02 8D 00 04 01 00 80 32\n" + | ||||
|                 "08 0B 00 02 02 02 00 00\n" + | ||||
|                 "09 04 00 00 01 02 02 00 04\n" + | ||||
|                 "05 24 00 20 01\n" + | ||||
|                 "05 24 01 00 01\n" + | ||||
|                 "04 24 02 02\n" + | ||||
|                 "05 24 06 00 01\n" + | ||||
|                 "07 05 81 03 08 00 10\n" + | ||||
|                 "09 04 01 00 02 0A 00 00 00\n" + | ||||
|                 "07 05 02 02 40 00 00\n" + | ||||
|                 "07 05 82 02 40 00 00\n" + | ||||
|                 "08 0B 02 02 02 02 00 00\n" + | ||||
|                 "09 04 02 00 01 02 02 00 04\n" + | ||||
|                 "05 24 00 20 01\n" + | ||||
|                 "05 24 01 00 03\n" + | ||||
|                 "04 24 02 02\n" + | ||||
|                 "05 24 06 02 03\n" + | ||||
|                 "07 05 83 03 08 00 10\n" + | ||||
|                 "09 04 03 00 02 0A 00 00 00\n" + | ||||
|                 "07 05 04 02 40 00 00\n" + | ||||
|                 "07 05 84 02 40 00 00\n")); | ||||
|         when(usbDevice.getInterfaceCount()).thenReturn(2*n); | ||||
|         for(int i=0; i<n; i++) { | ||||
|             controlInterfaces[i] = mock(UsbInterface.class); | ||||
|             dataInterfaces[i] = mock(UsbInterface.class); | ||||
|             controlEndpoints[i] = mock(UsbEndpoint.class); | ||||
|             readEndpoints[i] = mock(UsbEndpoint.class); | ||||
|             writeEndpoints[i] = mock(UsbEndpoint.class); | ||||
|             when(usbDeviceConnection.claimInterface(controlInterfaces[i], true)).thenReturn(true); | ||||
|             when(usbDeviceConnection.claimInterface(dataInterfaces[i], true)).thenReturn(true); | ||||
|             when(usbDevice.getInterface(2*i  )).thenReturn(controlInterfaces[i]); | ||||
|             when(usbDevice.getInterface(2*i+1)).thenReturn(dataInterfaces[i]); | ||||
|             when(controlInterfaces[i].getId()).thenReturn(2*i); | ||||
|             when(controlInterfaces[i].getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_COMM); | ||||
|             when(controlInterfaces[i].getInterfaceSubclass()).thenReturn(USB_SUBCLASS_ACM); | ||||
|             when(controlInterfaces[i].getEndpointCount()).thenReturn(1); | ||||
|             when(controlInterfaces[i].getEndpoint(0)).thenReturn(controlEndpoints[i]); | ||||
|             when(dataInterfaces[i].getId()).thenReturn(2*i+1); | ||||
|             when(dataInterfaces[i].getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_CDC_DATA); | ||||
|             when(dataInterfaces[i].getEndpointCount()).thenReturn(2); | ||||
|             when(dataInterfaces[i].getEndpoint(0)).thenReturn(writeEndpoints[i]); | ||||
|             when(dataInterfaces[i].getEndpoint(1)).thenReturn(readEndpoints[i]); | ||||
|             when(controlEndpoints[i].getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||
|             when(controlEndpoints[i].getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_INT); | ||||
|             when(readEndpoints[i].getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||
|             when(readEndpoints[i].getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||
|             when(writeEndpoints[i].getDirection()).thenReturn(UsbConstants.USB_DIR_OUT); | ||||
|             when(writeEndpoints[i].getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||
|         } | ||||
|         int i = 1; | ||||
|         CdcAcmSerialDriver driver = new CdcAcmSerialDriver(usbDevice); | ||||
|         CdcAcmSerialDriver.CdcAcmSerialPort port = (CdcAcmSerialDriver.CdcAcmSerialPort) driver.getPorts().get(i); | ||||
|         port.mConnection = usbDeviceConnection; | ||||
|         // reset invocations from countPorts() | ||||
|         clearInvocations(controlInterfaces[0]); | ||||
|         clearInvocations(controlInterfaces[1]); | ||||
| 
 | ||||
|         port.openInt(); | ||||
|         assertEquals(readEndpoints[i], port.mReadEndpoint); | ||||
|         assertEquals(writeEndpoints[i], port.mWriteEndpoint); | ||||
|         verify(controlInterfaces[0], times(0)).getInterfaceClass(); // not openInterface with 'no IAD fallback' | ||||
|         verify(controlInterfaces[1], times(2)).getInterfaceClass(); // openInterface with IAD | ||||
|         port.closeInt(); | ||||
|         clearInvocations(controlInterfaces[0]); | ||||
|         clearInvocations(controlInterfaces[1]); | ||||
| 
 | ||||
|         when(usbDeviceConnection.getRawDescriptors()).thenReturn(null); | ||||
|         port.openInt(); | ||||
|         verify(controlInterfaces[0], times(2)).getInterfaceClass(); // openInterface with 'no IAD fallback' | ||||
|         verify(controlInterfaces[1], times(2)).getInterfaceClass(); // openInterface with 'no IAD fallback' | ||||
|         port.closeInt(); | ||||
|         clearInvocations(controlInterfaces[0]); | ||||
|         clearInvocations(controlInterfaces[1]); | ||||
| 
 | ||||
|         when(usbDeviceConnection.getRawDescriptors()).thenReturn(HexDump.hexStringToByteArray("01 02 02 82 02")); // truncated descriptor | ||||
|         port.openInt(); | ||||
|         verify(controlInterfaces[0], times(2)).getInterfaceClass(); // openInterface with 'no IAD fallback' | ||||
|         verify(controlInterfaces[1], times(2)).getInterfaceClass(); // openInterface with 'no IAD fallback' | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void compositeDevice() throws Exception { | ||||
|         UsbDeviceConnection usbDeviceConnection = mock(UsbDeviceConnection.class); | ||||
|         UsbDevice usbDevice = mock(UsbDevice.class); | ||||
|         UsbInterface massStorageInterface = mock(UsbInterface.class); | ||||
|         UsbInterface controlInterface = mock(UsbInterface.class); | ||||
|         UsbInterface dataInterface = mock(UsbInterface.class); | ||||
|         UsbInterface hidInterface = mock(UsbInterface.class); | ||||
|         UsbInterface vendorInterface = mock(UsbInterface.class); | ||||
|         UsbEndpoint controlEndpoint = mock(UsbEndpoint.class); | ||||
|         UsbEndpoint readEndpoint = mock(UsbEndpoint.class); | ||||
|         UsbEndpoint writeEndpoint = mock(UsbEndpoint.class); | ||||
| 
 | ||||
|         /* | ||||
|          * BBC micro:bit | ||||
|          *   UsbInterface[mId=0,mAlternateSetting=0,mName=USB_MSC,mClass=8,mSubclass=6,mProtocol=80,mEndpoints=[ | ||||
|          *     UsbEndpoint[mAddress=130,mAttributes=2,mMaxPacketSize=64,mInterval=0] | ||||
|          *     UsbEndpoint[mAddress=2,mAttributes=2,mMaxPacketSize=64,mInterval=0]] | ||||
|          *   UsbInterface[mId=1,mAlternateSetting=0,mName=mbed Serial Port,mClass=2,mSubclass=2,mProtocol=1,mEndpoints=[ | ||||
|          *     UsbEndpoint[mAddress=131,mAttributes=3,mMaxPacketSize=16,mInterval=32]] | ||||
|          *   UsbInterface[mId=2,mAlternateSetting=0,mName=mbed Serial Port,mClass=10,mSubclass=0,mProtocol=0,mEndpoints=[ | ||||
|          *     UsbEndpoint[mAddress=4,mAttributes=2,mMaxPacketSize=64,mInterval=0] | ||||
|          *     UsbEndpoint[mAddress=132,mAttributes=2,mMaxPacketSize=64,mInterval=0]] | ||||
|          *   UsbInterface[mId=3,mAlternateSetting=0,mName=CMSIS-DAP,mClass=3,mSubclass=0,mProtocol=0,mEndpoints=[ | ||||
|          *     UsbEndpoint[mAddress=129,mAttributes=3,mMaxPacketSize=64,mInterval=1] | ||||
|          *     UsbEndpoint[mAddress=1,mAttributes=3,mMaxPacketSize=64,mInterval=1]] | ||||
|          *   UsbInterface[mId=4,mAlternateSetting=0,mName=WebUSB: CMSIS-DAP,mClass=255,mSubclass=3,mProtocol=0,mEndpoints=[] | ||||
|          */ | ||||
|         when(usbDeviceConnection.getRawDescriptors()).thenReturn(HexDump.hexStringToByteArray( | ||||
|                 "12 01 10 02 EF 02 01 40 28 0D 04 02 00 10 01 02 03 01\n" + | ||||
|                 "09 02 8B 00 05 01 00 80 FA\n" + | ||||
|                 "09 04 00 00 02 08 06 50 08\n" + | ||||
|                 "07 05 82 02 40 00 00\n" + | ||||
|                 "07 05 02 02 40 00 00\n" + | ||||
|                 "08 0B 01 02 02 02 01 04\n" + | ||||
|                 "09 04 01 00 01 02 02 01 04\n" + | ||||
|                 "05 24 00 10 01\n" + | ||||
|                 "05 24 01 03 02\n" + | ||||
|                 "04 24 02 06\n" + | ||||
|                 "05 24 06 01 02\n" + | ||||
|                 "07 05 83 03 10 00 20\n" + | ||||
|                 "09 04 02 00 02 0A 00 00 05\n" + | ||||
|                 "07 05 04 02 40 00 00\n" + | ||||
|                 "07 05 84 02 40 00 00\n" + | ||||
|                 "09 04 03 00 02 03 00 00 06\n" + | ||||
|                 "09 21 00 01 00 01 22 21 00\n" + | ||||
|                 "07 05 81 03 40 00 01\n" + | ||||
|                 "07 05 01 03 40 00 01\n" + | ||||
|                 "09 04 04 00 00 FF 03 00 07")); | ||||
|         when(usbDeviceConnection.claimInterface(controlInterface,true)).thenReturn(true); | ||||
|         when(usbDeviceConnection.claimInterface(dataInterface,true)).thenReturn(true); | ||||
|         when(usbDevice.getInterfaceCount()).thenReturn(5); | ||||
|         when(usbDevice.getInterface(0)).thenReturn(massStorageInterface); | ||||
|         when(usbDevice.getInterface(1)).thenReturn(controlInterface); | ||||
|         when(usbDevice.getInterface(2)).thenReturn(dataInterface); | ||||
|         when(usbDevice.getInterface(3)).thenReturn(hidInterface); | ||||
|         when(usbDevice.getInterface(4)).thenReturn(vendorInterface); | ||||
|         when(massStorageInterface.getId()).thenReturn(0); | ||||
|         when(massStorageInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_MASS_STORAGE); | ||||
|         when(controlInterface.getId()).thenReturn(1); | ||||
|         when(controlInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_COMM); | ||||
|         when(controlInterface.getInterfaceSubclass()).thenReturn(USB_SUBCLASS_ACM); | ||||
|         when(dataInterface.getId()).thenReturn(2); | ||||
|         when(dataInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_CDC_DATA); | ||||
|         when(hidInterface.getId()).thenReturn(3); | ||||
|         when(hidInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_HID); | ||||
|         when(vendorInterface.getId()).thenReturn(4); | ||||
|         when(vendorInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_VENDOR_SPEC); | ||||
| 
 | ||||
|         when(controlInterface.getEndpointCount()).thenReturn(1); | ||||
|         when(controlInterface.getEndpoint(0)).thenReturn(controlEndpoint); | ||||
|         when(dataInterface.getEndpointCount()).thenReturn(2); | ||||
|         when(dataInterface.getEndpoint(0)).thenReturn(writeEndpoint); | ||||
|         when(dataInterface.getEndpoint(1)).thenReturn(readEndpoint); | ||||
|         when(controlEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||
|         when(controlEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_INT); | ||||
|         when(readEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||
|         when(readEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||
|         when(writeEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_OUT); | ||||
|         when(writeEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||
| 
 | ||||
|         CdcAcmSerialDriver driver = new CdcAcmSerialDriver(usbDevice); | ||||
|         CdcAcmSerialDriver.CdcAcmSerialPort port = (CdcAcmSerialDriver.CdcAcmSerialPort) driver.getPorts().get(0); | ||||
|         port.mConnection = usbDeviceConnection; | ||||
|         port.openInt(); | ||||
|         assertEquals(readEndpoint, port.mReadEndpoint); | ||||
|         assertEquals(writeEndpoint, port.mWriteEndpoint); | ||||
| 
 | ||||
|         ProbeTable probeTable = UsbSerialProber.getDefaultProbeTable(); | ||||
|         Class<? extends UsbSerialDriver> probeDriver = probeTable.findDriver(usbDevice); | ||||
|         assertEquals(driver.getClass(), probeDriver); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void compositeRndisDevice() throws Exception { | ||||
|         UsbDeviceConnection usbDeviceConnection = mock(UsbDeviceConnection.class); | ||||
|         UsbDevice usbDevice = mock(UsbDevice.class); | ||||
|         UsbInterface rndisControlInterface = mock(UsbInterface.class); | ||||
|         UsbInterface rndisDataInterface = mock(UsbInterface.class); | ||||
|         UsbInterface controlInterface = mock(UsbInterface.class); | ||||
|         UsbInterface dataInterface = mock(UsbInterface.class); | ||||
|         UsbEndpoint controlEndpoint = mock(UsbEndpoint.class); | ||||
|         UsbEndpoint readEndpoint = mock(UsbEndpoint.class); | ||||
|         UsbEndpoint writeEndpoint = mock(UsbEndpoint.class); | ||||
| 
 | ||||
|         // has multiple USB_CLASS_CDC_DATA interfaces => get correct with IAD | ||||
|         when(usbDeviceConnection.getRawDescriptors()).thenReturn(HexDump.hexStringToByteArray( | ||||
|                 "12 01 00 02 EF 02 01 40 FE CA 02 40 00 01 01 02 03 01\n" + | ||||
|                 "09 02 8D 00 04 01 00 80 32\n" + | ||||
|                 "08 0B 00 02 E0 01 03 00\n" + | ||||
|                 "09 04 00 00 01 E0 01 03 04\n" + | ||||
|                 "05 24 00 10 01\n" + | ||||
|                 "05 24 01 00 01\n" + | ||||
|                 "04 24 02 00\n" + | ||||
|                 "05 24 06 00 01\n" + | ||||
|                 "07 05 81 03 08 00 01\n" + | ||||
|                 "09 04 01 00 02 0A 00 00 00\n" + | ||||
|                 "07 05 82 02 40 00 00\n" + | ||||
|                 "07 05 02 02 40 00 00\n" + | ||||
|                 "08 0B 02 02 02 02 00 00\n" + | ||||
|                 "09 04 02 00 01 02 02 00 04\n" + | ||||
|                 "05 24 00 20 01\n" + | ||||
|                 "05 24 01 00 03\n" + | ||||
|                 "04 24 02 02\n" + | ||||
|                 "05 24 06 02 03\n" + | ||||
|                 "07 05 83 03 08 00 10\n" + | ||||
|                 "09 04 03 00 02 0A 00 00 00\n" + | ||||
|                 "07 05 04 02 40 00 00\n" + | ||||
|                 "07 05 84 02 40 00 00")); | ||||
|         when(usbDeviceConnection.claimInterface(controlInterface,true)).thenReturn(true); | ||||
|         when(usbDeviceConnection.claimInterface(dataInterface,true)).thenReturn(true); | ||||
|         when(usbDevice.getInterfaceCount()).thenReturn(4); | ||||
|         when(usbDevice.getInterface(0)).thenReturn(rndisControlInterface); | ||||
|         when(usbDevice.getInterface(1)).thenReturn(rndisDataInterface); | ||||
|         when(usbDevice.getInterface(2)).thenReturn(controlInterface); | ||||
|         when(usbDevice.getInterface(3)).thenReturn(dataInterface); | ||||
|         when(rndisControlInterface.getId()).thenReturn(0); | ||||
|         when(rndisControlInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_WIRELESS_CONTROLLER); | ||||
|         when(rndisControlInterface.getInterfaceSubclass()).thenReturn(1); | ||||
|         when(rndisControlInterface.getInterfaceProtocol()).thenReturn(3); | ||||
|         when(rndisDataInterface.getId()).thenReturn(1); | ||||
|         when(rndisDataInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_CDC_DATA); | ||||
|         when(controlInterface.getId()).thenReturn(2); | ||||
|         when(controlInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_COMM); | ||||
|         when(controlInterface.getInterfaceSubclass()).thenReturn(USB_SUBCLASS_ACM); | ||||
|         when(dataInterface.getId()).thenReturn(3); | ||||
|         when(dataInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_CDC_DATA); | ||||
| 
 | ||||
|         when(controlInterface.getEndpointCount()).thenReturn(1); | ||||
|         when(controlInterface.getEndpoint(0)).thenReturn(controlEndpoint); | ||||
|         when(dataInterface.getEndpointCount()).thenReturn(2); | ||||
|         when(dataInterface.getEndpoint(0)).thenReturn(writeEndpoint); | ||||
|         when(dataInterface.getEndpoint(1)).thenReturn(readEndpoint); | ||||
|         when(controlEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||
|         when(controlEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_INT); | ||||
|         when(readEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||
|         when(readEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||
|         when(writeEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_OUT); | ||||
|         when(writeEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||
| 
 | ||||
|         CdcAcmSerialDriver driver = new CdcAcmSerialDriver(usbDevice); | ||||
|         CdcAcmSerialDriver.CdcAcmSerialPort port = (CdcAcmSerialDriver.CdcAcmSerialPort) driver.getPorts().get(0); | ||||
|         port.mConnection = usbDeviceConnection; | ||||
|         port.openInt(); | ||||
|         assertEquals(readEndpoint, port.mReadEndpoint); | ||||
|         assertEquals(writeEndpoint, port.mWriteEndpoint); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void compositeAlternateSettingDevice() throws Exception { | ||||
|         UsbDeviceConnection usbDeviceConnection = mock(UsbDeviceConnection.class); | ||||
|         UsbDevice usbDevice = mock(UsbDevice.class); | ||||
|         UsbInterface ethernetControlInterface = mock(UsbInterface.class); | ||||
|         UsbInterface ethernetDummyInterface = mock(UsbInterface.class); | ||||
|         UsbInterface ethernetDataInterface = mock(UsbInterface.class); | ||||
|         UsbInterface controlInterface = mock(UsbInterface.class); | ||||
|         UsbInterface dataInterface = mock(UsbInterface.class); | ||||
|         UsbEndpoint controlEndpoint = mock(UsbEndpoint.class); | ||||
|         UsbEndpoint readEndpoint = mock(UsbEndpoint.class); | ||||
|         UsbEndpoint writeEndpoint = mock(UsbEndpoint.class); | ||||
| 
 | ||||
|         // has multiple USB_CLASS_CDC_DATA interfaces => get correct with IAD | ||||
|         when(usbDeviceConnection.getRawDescriptors()).thenReturn(HexDump.hexStringToByteArray( | ||||
|                 "12 01 00 02 EF 02 01 40 FE CA 02 40 00 01 01 02 03 01\n" + | ||||
|                 "09 02 9A 00 04 01 00 80 32\n" + | ||||
|                 "08 0B 00 02 02 06 00 00\n" + | ||||
|                 "09 04 00 00 01 02 06 00 04\n" + | ||||
|                 "05 24 00 20 01\n" + | ||||
|                 "05 24 06 00 01\n" + | ||||
|                 "0D 24 0F 04 00 00 00 00 DC 05 00 00 00\n" + | ||||
|                 "07 05 81 03 08 00 01\n" + | ||||
|                 "09 04 01 00 00 0A 00 00 00\n" + | ||||
|                 "09 04 01 01 02 0A 00 00 00\n" + | ||||
|                 "07 05 82 02 40 00 00\n" + | ||||
|                 "07 05 02 02 40 00 00\n" + | ||||
|                 "08 0B 02 02 02 02 00 00\n" + | ||||
|                 "09 04 02 00 01 02 02 00 04\n" + | ||||
|                 "05 24 00 20 01\n" + | ||||
|                 "05 24 01 00 03\n" + | ||||
|                 "04 24 02 02\n" + | ||||
|                 "05 24 06 02 03\n" + | ||||
|                 "07 05 83 03 08 00 10\n" + | ||||
|                 "09 04 03 00 02 0A 00 00 00\n" + | ||||
|                 "07 05 04 02 40 00 00\n" + | ||||
|                 "07 05 84 02 40 00 00")); | ||||
|         when(usbDeviceConnection.claimInterface(controlInterface,true)).thenReturn(true); | ||||
|         when(usbDeviceConnection.claimInterface(dataInterface,true)).thenReturn(true); | ||||
|         when(usbDevice.getInterfaceCount()).thenReturn(5); | ||||
|         when(usbDevice.getInterface(0)).thenReturn(ethernetControlInterface); | ||||
|         when(usbDevice.getInterface(1)).thenReturn(ethernetDummyInterface); | ||||
|         when(usbDevice.getInterface(2)).thenReturn(ethernetDataInterface); | ||||
|         when(usbDevice.getInterface(3)).thenReturn(controlInterface); | ||||
|         when(usbDevice.getInterface(4)).thenReturn(dataInterface); | ||||
|         when(ethernetControlInterface.getId()).thenReturn(0); | ||||
|         when(ethernetControlInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_COMM); | ||||
|         when(ethernetControlInterface.getInterfaceSubclass()).thenReturn(6); | ||||
|         when(ethernetDummyInterface.getId()).thenReturn(1); | ||||
|         when(ethernetDummyInterface.getAlternateSetting()).thenReturn(0); | ||||
|         when(ethernetDummyInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_CDC_DATA); | ||||
|         when(ethernetDataInterface.getId()).thenReturn(1); | ||||
|         when(ethernetDataInterface.getAlternateSetting()).thenReturn(1); | ||||
|         when(ethernetDataInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_CDC_DATA); | ||||
|         when(controlInterface.getId()).thenReturn(2); | ||||
|         when(controlInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_COMM); | ||||
|         when(controlInterface.getInterfaceSubclass()).thenReturn(USB_SUBCLASS_ACM); | ||||
|         when(dataInterface.getId()).thenReturn(3); | ||||
|         when(dataInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_CDC_DATA); | ||||
| 
 | ||||
|         when(controlInterface.getEndpointCount()).thenReturn(1); | ||||
|         when(controlInterface.getEndpoint(0)).thenReturn(controlEndpoint); | ||||
|         when(dataInterface.getEndpointCount()).thenReturn(2); | ||||
|         when(dataInterface.getEndpoint(0)).thenReturn(writeEndpoint); | ||||
|         when(dataInterface.getEndpoint(1)).thenReturn(readEndpoint); | ||||
|         when(controlEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||
|         when(controlEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_INT); | ||||
|         when(readEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||
|         when(readEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||
|         when(writeEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_OUT); | ||||
|         when(writeEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||
| 
 | ||||
|         CdcAcmSerialDriver driver = new CdcAcmSerialDriver(usbDevice); | ||||
|         CdcAcmSerialDriver.CdcAcmSerialPort port = (CdcAcmSerialDriver.CdcAcmSerialPort) driver.getPorts().get(0); | ||||
|         port.mConnection = usbDeviceConnection; | ||||
|         port.openInt(); | ||||
|         assertEquals(readEndpoint, port.mReadEndpoint); | ||||
|         assertEquals(writeEndpoint, port.mWriteEndpoint); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void invalidStandardDevice() throws Exception { | ||||
|         UsbDeviceConnection usbDeviceConnection = mock(UsbDeviceConnection.class); | ||||
|         UsbDevice usbDevice = mock(UsbDevice.class); | ||||
|         UsbInterface controlInterface = mock(UsbInterface.class); | ||||
|         UsbInterface dataInterface = mock(UsbInterface.class); | ||||
|         UsbEndpoint controlEndpoint = mock(UsbEndpoint.class); | ||||
|         UsbEndpoint readEndpoint = mock(UsbEndpoint.class); | ||||
|         UsbEndpoint writeEndpoint = mock(UsbEndpoint.class); | ||||
| 
 | ||||
|         when(usbDeviceConnection.claimInterface(controlInterface,true)).thenReturn(true); | ||||
|         when(usbDeviceConnection.claimInterface(dataInterface,true)).thenReturn(true); | ||||
|         when(usbDevice.getInterfaceCount()).thenReturn(2); | ||||
|         when(usbDevice.getInterface(0)).thenReturn(controlInterface); | ||||
|         when(usbDevice.getInterface(1)).thenReturn(dataInterface); | ||||
|         when(controlInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_COMM); | ||||
|         when(controlInterface.getInterfaceSubclass()).thenReturn(USB_SUBCLASS_ACM); | ||||
|         when(controlInterface.getEndpointCount()).thenReturn(1); | ||||
|         when(controlInterface.getEndpoint(0)).thenReturn(controlEndpoint); | ||||
|         when(dataInterface.getInterfaceClass()).thenReturn(UsbConstants.USB_CLASS_CDC_DATA); | ||||
|         when(dataInterface.getEndpointCount()).thenReturn(2); | ||||
|         when(dataInterface.getEndpoint(0)).thenReturn(writeEndpoint); | ||||
|         when(dataInterface.getEndpoint(1)).thenReturn(readEndpoint); | ||||
|         //when(controlEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||
|         //when(controlEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_INT); | ||||
|         when(readEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||
|         when(readEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||
|         when(writeEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_OUT); | ||||
|         when(writeEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||
| 
 | ||||
|         CdcAcmSerialDriver driver = new CdcAcmSerialDriver(usbDevice); | ||||
|         CdcAcmSerialDriver.CdcAcmSerialPort port = (CdcAcmSerialDriver.CdcAcmSerialPort) driver.getPorts().get(0); | ||||
|         port.mConnection = usbDeviceConnection; | ||||
|         assertThrows(IOException.class, port::openInt); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void invalidSingleInterfaceDevice() throws Exception { | ||||
|         UsbDeviceConnection usbDeviceConnection = mock(UsbDeviceConnection.class); | ||||
|         UsbDevice usbDevice = mock(UsbDevice.class); | ||||
|         UsbInterface usbInterface = mock(UsbInterface.class); | ||||
|         UsbEndpoint controlEndpoint = mock(UsbEndpoint.class); | ||||
|         UsbEndpoint readEndpoint = mock(UsbEndpoint.class); | ||||
|         //UsbEndpoint writeEndpoint = mock(UsbEndpoint.class); | ||||
| 
 | ||||
|         when(usbDeviceConnection.claimInterface(usbInterface,true)).thenReturn(true); | ||||
|         when(usbDevice.getInterfaceCount()).thenReturn(1); | ||||
|         when(usbDevice.getInterface(0)).thenReturn(usbInterface); | ||||
|         when(usbInterface.getEndpointCount()).thenReturn(2); | ||||
|         when(usbInterface.getEndpoint(0)).thenReturn(controlEndpoint); | ||||
|         when(usbInterface.getEndpoint(1)).thenReturn(readEndpoint); | ||||
|         //when(usbInterface.getEndpoint(2)).thenReturn(writeEndpoint); | ||||
|         when(controlEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||
|         when(controlEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_INT); | ||||
|         when(readEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_IN); | ||||
|         when(readEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||
|         //when(writeEndpoint.getDirection()).thenReturn(UsbConstants.USB_DIR_OUT); | ||||
|         //when(writeEndpoint.getType()).thenReturn(UsbConstants.USB_ENDPOINT_XFER_BULK); | ||||
| 
 | ||||
|         CdcAcmSerialDriver driver = new CdcAcmSerialDriver(usbDevice); | ||||
|         CdcAcmSerialDriver.CdcAcmSerialPort port = (CdcAcmSerialDriver.CdcAcmSerialPort) driver.getPorts().get(0); | ||||
|         port.mConnection = usbDeviceConnection; | ||||
|         port.openInt(); | ||||
|         assertNull(port.mWriteEndpoint); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,44 @@ | ||||
| package com.hoho.android.usbserial.util; | ||||
| 
 | ||||
| import static org.hamcrest.CoreMatchers.equalTo; | ||||
| import static org.hamcrest.MatcherAssert.assertThat; | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertThrows; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| 
 | ||||
| import java.security.InvalidParameterException; | ||||
| 
 | ||||
| public class HexDumpText { | ||||
| 
 | ||||
|     @Test | ||||
|     public void toByteArray() throws Exception { | ||||
|         assertThat(HexDump.toByteArray((byte)0x4a), equalTo(new byte[]{ 0x4A})); | ||||
|         assertThat(HexDump.toByteArray((short)0x4a5b), equalTo(new byte[]{ 0x4A, 0x5B})); | ||||
|         assertThat(HexDump.toByteArray((int)0x4a5b6c7d), equalTo(new byte[]{ 0x4A, 0x5B, 0x6C, 0x7D})); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void toHexString() throws Exception { | ||||
|         assertEquals("4A", HexDump.toHexString((byte)0x4a)); | ||||
|         assertEquals("4A 5B", HexDump.toHexString((short)0x4a5b)); | ||||
|         assertEquals("4A 5B 6C 7D", HexDump.toHexString((int)0x4a5b6c7d)); | ||||
|         assertEquals("4A 5B 6C 7D", HexDump.toHexString(new byte[]{ 0x4A, 0x5B, 0x6C, 0x7D})); | ||||
|         assertEquals("5B 6C", HexDump.toHexString(new byte[]{ 0x4A, 0x5B, 0x6C, 0x7D}, 1, 2)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void dumpHexString() throws Exception { | ||||
|         assertEquals("10 31 32 33 34 35 36 37 .1234567\n18 39                   .9", HexDump.dumpHexString(new byte[]{ 0x10, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x18, 0x39})); | ||||
|         assertEquals("31 32                   12", HexDump.dumpHexString(new byte[]{ 0x30, 0x31, 0x32, 0x33}, 1, 2)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void toByte() throws Exception { | ||||
|         assertThat(HexDump.hexStringToByteArray("4a 5B-6c\n7d"), equalTo(new byte[]{ 0x4A, 0x5B, 0x6C, 0x7D})); | ||||
|         assertThrows(InvalidParameterException.class, () -> HexDump.hexStringToByteArray("3 ")); | ||||
|         assertThrows(InvalidParameterException.class, () -> HexDump.hexStringToByteArray("3z")); | ||||
|         assertThrows(InvalidParameterException.class, () -> HexDump.hexStringToByteArray("3Z")); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,55 @@ | ||||
| package com.hoho.android.usbserial.util; | ||||
| 
 | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.mockito.Mockito.mock; | ||||
| import static org.mockito.Mockito.when; | ||||
| 
 | ||||
| import android.hardware.usb.UsbEndpoint; | ||||
| import android.os.Process; | ||||
| 
 | ||||
| import com.hoho.android.usbserial.driver.CommonUsbSerialPort; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| 
 | ||||
| public class SerialInputOutputManagerTest { | ||||
| 
 | ||||
| 
 | ||||
|     // catch all Throwables in onNewData() and onRunError() | ||||
|     @Test | ||||
|     public void throwable() throws Exception { | ||||
| 
 | ||||
|         class ExceptionListener implements SerialInputOutputManager.Listener { | ||||
|             public Exception e; | ||||
|             @Override public void onNewData(byte[] data) { throw new RuntimeException("exception1"); } | ||||
|             @Override public void onRunError(Exception e) { this.e = e; throw new RuntimeException("exception2"); } | ||||
|         } | ||||
|         class ErrorListener implements SerialInputOutputManager.Listener { | ||||
|             public Exception e; | ||||
|             @Override public void onNewData(byte[] data) { throw new UnknownError("error1"); } | ||||
|             @Override public void onRunError(Exception e) { this.e = e; throw new UnknownError("error2");} | ||||
|         } | ||||
| 
 | ||||
|         UsbEndpoint readEndpoint = mock(UsbEndpoint.class); | ||||
|         when(readEndpoint.getMaxPacketSize()).thenReturn(16); | ||||
|         CommonUsbSerialPort port = mock(CommonUsbSerialPort.class); | ||||
|         when(port.getReadEndpoint()).thenReturn(readEndpoint); | ||||
|         when(port.read(new byte[16], 0)).thenReturn(1); | ||||
|         when(port.isOpen()).thenReturn(true); | ||||
|         SerialInputOutputManager manager = new SerialInputOutputManager(port); | ||||
|         manager.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); | ||||
| 
 | ||||
|         ExceptionListener exceptionListener = new ExceptionListener(); | ||||
|         manager.setListener(exceptionListener); | ||||
|         manager.runRead(); | ||||
|         assertEquals(RuntimeException.class, exceptionListener.e.getClass()); | ||||
|         assertEquals("exception1", exceptionListener.e.getMessage()); | ||||
| 
 | ||||
|         ErrorListener errorListener = new ErrorListener(); | ||||
|         manager.setListener(errorListener); | ||||
|         manager.runRead(); | ||||
|         assertEquals(Exception.class, errorListener.e.getClass()); | ||||
|         assertEquals("java.lang.UnknownError: error1", errorListener.e.getMessage()); | ||||
|         assertEquals(UnknownError.class, errorListener.e.getCause().getClass()); | ||||
|         assertEquals("error1", errorListener.e.getCause().getMessage()); | ||||
|     } | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user