Android USB Host U盘

Android USB Host使用详解之一:查看USB设备信息

首先来看一下Google的官方文档中关于Android USB的介绍:Android USB Host and Accessory

Android USB有两种模式Host  Mode和Accessory Mode:

在Host Mode下,Android手机作为主设备,如通过OTG线连接的HID设备或者U盘为从设备;在Accessory Mode下,Android手机作为从设备,如通过USB数据线连接的电脑为主设备。

本文主要介绍在Host Mode下,Android手机与USB设备之间的通信。Android USB Host的介绍可参见Google 官方文档:Android USB Host介绍

关于Android USB相关类的介绍留在下面慢慢展开,先编写一个Android程序:

1)在AndroidManifest.xml文件中添加

[html]  view plain  copy
  1. <uses-feature android:name="android.hardware.usb.host" />  
  2. <uses-sdk android:minSdkVersion="12" />  
 
 有可能你在其它地方看见这样的写法 
 

[html]  view plain  copy
  1. <uses-feature android:name="android.hardware.usb.host" android:required="true"/>  
本人建议不要这样写,因为这样写的话可能在 /system/etc/permissions目录下不能自动创建android.hardware.usb.host.xml文件,而需要手动创建。

2)在<activity ...>添加

[html]  view plain  copy
  1. <intent-filter>  
  2.      <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />  
  3. </intent-filter>  
  4. <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"  
  5.      android:resource="@xml/device_filter" />  
在res/xml文件夹下新建device_filter.xml

[html]  view plain  copy
  1. <resources>  
  2.     <usb-device vendor-id="3544" product-id="8199" />  
  3.     <usb-device vendor-id="5251" product-id="4608" />  
  4. </resources>  
 其中vendor-id和product-id为插入USB设备的生产厂家号和产品号,但插入(attached)上面列出的设备之一时就会弹出选择打开应用程序的对话框。注:上面的id为10进制的,而通过电脑上查看的id为16进制的。

3)获取所有已连接上的USD设备的信息

[java]  view plain  copy
  1. public class MainActivity extends ActionBarActivity {  
  2.     @Override  
  3.     protected void onCreate(Bundle savedInstanceState) {  
  4.         super.onCreate(savedInstanceState);  
  5.         setContentView(R.layout.activity_main);  
  6.         UsbManager mManager = (UsbManager) getSystemService(Context.USB_SERVICE);  
  7.         HashMap<String, UsbDevice> deviceList = mManager.getDeviceList();  
  8.         Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();  
  9.           
  10.         String info = new String();  
  11.         while (deviceIterator.hasNext())  
  12.         {  
  13.             UsbDevice device = deviceIterator.next();  
  14.             info += "Device Name:" + device.getDeviceName() + "\n";  
  15.             info += "Device ID:" + device.getDeviceId() + "\n";  
  16.             info += "Device Class:" + device.getDeviceClass() + "\n";  
  17.             info += "Device Protocl:" + device.getDeviceProtocol() + "\n";  
  18.             info += "Device Vendor ID:" + device.getVendorId() + "\n";  
  19.             info += "Device Product ID:" + device.getProductId() + "\n";  
  20.             info += "Device Interface Count:" + device.getInterfaceCount() + "\n\n";  
  21.             // Get interface details  
  22.             for (int index = 0; index < device.getInterfaceCount(); index++)  
  23.             {  
  24.                 UsbInterface mUsbInterface = device.getInterface(index);  
  25.                 info += "Interface " + index + ":\n";  
  26.                 info += "Interface ID:" + mUsbInterface.getId() + "\n";  
  27.                 info += "Inteface class:" + mUsbInterface.getInterfaceClass() + "\n";  
  28.                 info += "Interface protocol:" + mUsbInterface.getInterfaceProtocol() + "\n";  
  29.                 info += "Endpoint count:" + mUsbInterface.getEndpointCount() + "\n\n";  
  30.                 // Get endpoint details   
  31.                 for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++)  
  32.                 {  
  33.                     UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi);  
  34.                     info += "Endpoint " + epi + ":\n";  
  35.                     info += "Attributes: " + mEndpoint.getAttributes() + "\n";  
  36.                     info += "Direction: " + mEndpoint.getDirection() + "\n";  
  37.                     info += "Number: " + mEndpoint.getEndpointNumber() + "\n";  
  38.                     info += "Interval: " + mEndpoint.getInterval() + "\n";  
  39.                     info += "Packet size: " + mEndpoint.getMaxPacketSize() + "\n";  
  40.                     info += "Type: " + mEndpoint.getType() + "\n\n";  
  41.                 }  
  42.             }  
  43.         }          
  44.         TextView textView = (TextView)findViewById(R.id.info);  
  45.         textView.setText(info);  
  46.     }  
  47. }  

 
 通过上面这段代码可以获得USB设备的所有信息,包括接口(interface)和端点(endpoint)的信息,只有了解设备的类型才能在后面进行通信。注:本人在测试时即使没有连接设备也会有显示一个设备,这个设备即使删除它后系统也会自动生成,这里不用管它,之后把它过滤掉就可以了。 
 

测试结果如下:

上面左图为第一个设备(无对应设备,懂的大神可以解释一下),右图为连接的U盘。Device Name 的值是一个文件路径,在路径下可以看到相应的文件,插入设备时自动生成,拔出时自动删除,懂Linux的应该明白原理,我是不懂,所以就不解释了。


上面介绍了读取Android手机连接的USB设备信息的例子,本篇介绍官方文档中关于USB的API:官方API。注:主要介绍关于Host Mode的。

1)UsbAccessory

用于代表USB从设备的一个类,这个从设备通过USB数据线与android应用进行通信。

 

2)UsbConfiguration

用于代表USB设备(指连接Android手机的USB从设备)配置信息的一个类。

 

3)UsbConstants

这个类主要包含USB协议常量的类,这些常量与Linux内核下linux/usb/ch9.h 里面定义的相对应。

 

4)UsbDevice

这个类代表在Host Mode下,连接Android手机的USB设备的类。

equals(object) : 用于判断本设备是否与object相等,在编程时判断两个设备是否为同一设备时会用到。

其余的get函数都是返回相应的值。如:getDeviceProtocol()返回代表协议的常量,getDeviceProtocol()==UsbConstants.USB_CLASS_HID则说明此设备为HID设备。

getInterfaceCount():返回设备的接口总数。对于U盘的接口数为1。

getInterface():获取接口(UsbInterface)。

 

5)UsbInterface

这个类代表USB设备上的一个接口。一个USB设备可以有1个或多个接口,每个接口提供不同的功能。一个接口又有1个或者多个端点(UsbEndpoint),端点是用于数据或者命令传输的通道。

getEndpointCount():返回此接口的端点数。对于U盘,返回的端点数为2(用上一篇博客介绍的方法可得)。

getEndpoint(index):返回对应的端点。

 

6)UsbEndpoint

这个类代表接口上的一个端点。端点是用于数据传输的通道。典型的bulk端点用于传输大量的数据,interrupt端点用于传输小量数据,如事件,与数据流分开传输。在官方文档中说端点0是用于传输控制信息的,而端点0是所有USB设备都默认拥有的。但是这个端点0并不会被枚举出来,也就是说,要传输control信息直接用controlTransfer()传输即可,不要试图打开端点0后再进行传输。举个例子:大家所熟悉的U盾,为HID设备,拥有一个接口,缺省的控制端点;用getInterface(0)获取接口0,在getEndpointCount()返回的值为0,端点数为0;控制端点没有被枚举出来。再如U盘有一个接口,拥有控制端点,bulk-in端点,bulk-out端点,总共3个,但是getEndpointCount()返回的值是2。还有鼠标键盘等HID设备为单向传输设备,不能被获取到。

注:在Google API中提到的endpoint 0特指控制端点,而不是指端点号为0的端点。

 

7)UsbManager

这个类允许你获取USB的状态并和USB设备进行通信。

openDevice(),打开要进行通信的USB设备,返回一个UsbDeviceConnection对象。

在使用UsbDeviceConnection对象进行通信前,必须获取权限(获取使用者的同意)。使用

[java]  view plain  copy
  1. UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);  
获取UsbManager对象,用hasPremission()判断设备是否拥有权限,如果没有,使用如下方法获取权限

[java]  view plain  copy
  1. mPermissionIntent = PendingIntent.getBroadcast(this0new Intent(ACTION_USB_PERMISSION), 0);  
  2. IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);  
  3. registerReceiver(mUsbReceiver, filter);  
  4. mUsbManager.requestPermission(mUsbDevice, mPermissionIntent);   

 
 
[java]  view plain  copy
  1. private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {  
  2.   
  3.     public void onReceive(Context context, Intent intent) {  
  4.         String action = intent.getAction();  
  5.         if (ACTION_USB_PERMISSION.equals(action)) {  
  6.             synchronized (this) {  
  7.                 if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {  
  8.                     // 获取权限后的操作  
  9.                 }   
  10.                 else {  
  11.                     finish();  
  12.                 }  
  13.             }  
  14.         }  
  15.     }  
  16. };  

8)UsbRequest

一个代表请求传输数据包的类。UsbRequest类可以用于在bulk端点和interrupt端点传输数据。control信息的传输不可使用此类。

9)UsbDeviceConnection

这个类用于在Android手机从USB设备接收和发送控制信息和数据信息。此类由openDevice()创建。

bulkTransfer(UsbEndpoint endpoint, byte[] buffer, int length, int timeout)

这个函数用于数据传输的。

      endpoint 为传输端点,注意端点的方向。

      buffer 为传输的数据,至于传输数据的格式,需要了解手中的设备具体PDF,不然发送的数据是无意义的,并且无法接收数据。

      length 为buffer的长度。

      timeout 为有效时间(设为0可能导致程序意外终止退出)。

返回值传输的数据的长度,小于0说明传输失败。

controlTransfer (int requestType, int request, int value, int index, byte[] buffer, int length, int timeout)

这个函数用于控制信息的传输,后面三个参数与上面相同。

      requestType 为请求类型,低8位为有效的,第7为表示方向如0x00为发送数据,0x80为接收数据,接收数据时,buffer要有足够的长度。其余7位的设置根据手中设备提供的文档进行设置。(具体值都会在设备文档中给出

      request 与上面的request对应。(具体值都会在设备文档中给出

      value 与上面两个参数对应。(具体值都会在设备文档中给出

      index 表示使用的接口号。(具体值都会在设备文档中给出

HID设备文档下载地址:HID设备文档

U盘设备文档下载地址:U盘bulk-only传输文档

(1)建立连接(HID设备)

[java]  view plain  copy
  1. private void makeConnection() {  
  2.     if(mUsbDevice == null) {  
  3.         Toast.makeText(this, R.string.no_device, Toast.LENGTH_SHORT).show();  
  4.         finish();  
  5.         return;  
  6.     }  
  7.     if(mUsbDevice.getInterfaceCount() != 1) {  
  8.         Toast.makeText(this, R.string.interface_error, Toast.LENGTH_SHORT).show();  
  9.         finish();  
  10.         return;  
  11.     }  
  12.       
  13.     UsbInterface intf = mUsbDevice.getInterface(0);  
  14.                       
  15.     if (mUsbDevice != null) {  
  16.            UsbDeviceConnection connection = mUsbManager.openDevice(mUsbDevice);  
  17.            if (connection != null && connection.claimInterface(intf, true)) {  
  18.             Toast.makeText(this, R.string.connection_fine, Toast.LENGTH_SHORT).show();  
  19.                mConnection = connection;  
  20.            } else {  
  21.             Toast.makeText(this, R.string.connection_error, Toast.LENGTH_SHORT).show();  
  22.                mConnection = null;  
  23.                finish();  
  24.            }  
  25.         }  
  26. }  
(2)发送控制信息(mCmd[8]请根据自己的设备进行设置)

[java]  view plain  copy
  1. private void sendCommand() {  
  2.       
  3.     synchronized (this) {  
  4.            if (mConnection != null) {  
  5.             mCmd = new byte[8];  
  6.             mCmd[0] = (byte0x00//请自行设置  
  7.             mCmd[1] = (byte0x00;  
  8.             mCmd[2] = (byte0x00;  
  9.             mCmd[3] = (byte0x00;  
  10.             mCmd[4] = (byte0x00;  
  11.             mCmd[5] = (byte0x00;  
  12.             mCmd[6] = (byte0x00;  
  13.             mCmd[7] = (byte0x00;  
  14.               
  15.                int result = mConnection.controlTransfer(0x210x090x03000x00, mCmd, mCmd.length, 1000);  
  16.                if(result < 0) {  
  17.                 Toast.makeText(this, R.string.send_command_failed, Toast.LENGTH_SHORT).show();  
  18.             } else {  
  19.                 Toast.makeText(this, R.string.send_command_successed, Toast.LENGTH_SHORT).show();  
  20.             }         
  21.            }  
  22.        }  
  23. }  
(3)根据发送的控制信息,接收控制信息的结果(HID设备)

[java]  view plain  copy
  1. private void recvMessage() {  
  2.           
  3.         synchronized (this) {  
  4.             if (mConnection != null) {  
  5.                 byte[] message = new byte[32];  
  6.                 int result = mConnection.controlTransfer(0xA10x010x03000x00, message, message.length, 1000);  
  7.                   
  8.                 if(result < 0) {  
  9.                     Toast.makeText(this, R.string.recv_message_failed, Toast.LENGTH_SHORT).show();  
  10.                     mMessageView.setText(R.string.string_null);  
  11.                 } else {  
  12.                     Toast.makeText(this, R.string.recv_message_successed, Toast.LENGTH_SHORT).show();  
  13.                     String str = new String();  
  14.                     for(int i=0; i<message.length; i++) {  
  15.                         str += Integer.toHexString(message[i]&0x00FF)  + " ";  
  16.                     }  
  17.                     mMessageView.setText(str);  
  18.                 }  
  19.             }  
  20.         }  
  21.     }  

android中,在USB Host Mode下,U盘可以使用的传输数据(或命令)的函数有

[java]  view plain  copy
  1. bulkTransfer(UsbEndpoint endpoint, byte[] buffer,int length, int timeout)  
  2. controlTransfer(int requestType, int request, int value, int index, byte[] buffer, int length, int timeout)   
 
 bulkTransfer()用于传输CBW命令(bulk-out端点),主设备传输数据到从设备(bulk-out端点),从设备传输数据到主设备(bulk-in端点),从设备发送的CSW响应(bulk-in端点)。 
 

controlTransfer()用于传输控制命令(控制端点),包括reset和get max lnu等命令。

下面U盘操作实例进行讲解:

1)布局文件

[html]  view plain  copy
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:paddingBottom="@dimen/activity_vertical_margin"  
  6.     android:paddingLeft="@dimen/activity_horizontal_margin"  
  7.     android:paddingRight="@dimen/activity_horizontal_margin"  
  8.     android:paddingTop="@dimen/activity_vertical_margin"  
  9.     android:orientation="vertical"  
  10.     tools:context="com.example.usbcom.MainActivity" >  
  11.   
  12.     <Button   
  13.         android:id="@+id/btn_reset"  
  14.         android:layout_width="match_parent"  
  15.         android:layout_height="wrap_content"  
  16.         android:gravity="center"  
  17.         android:text="@string/reset" />  
  18.     <Button  
  19.         android:id="@+id/btn_get_max_lnu"  
  20.         android:layout_width="match_parent"  
  21.         android:layout_height="wrap_content"  
  22.         android:gravity="center"  
  23.         android:text="@string/get_max_lnu" />  
  24.     <Button  
  25.         android:id="@+id/btn_send_command"  
  26.         android:layout_width="match_parent"  
  27.         android:layout_height="wrap_content"  
  28.         android:gravity="center"  
  29.         android:text="@string/send_command" />  
  30.     <TextView   
  31.         android:id="@+id/tv_info"  
  32.         android:layout_width="match_parent"  
  33.         android:layout_height="wrap_content"  
  34.         android:text="@string/_null" />  
  35.       
  36. </LinearLayout>  
第一个按钮发送reset命令,第二个按钮接收max lnu结果,第三个按钮发送read format capacities的CBW命令,并将获取的结果在文本视图中显示。

2)AndroidManifest.xml和device_filter.xml

[html]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     package="com.example.usbcom"  
  4.     android:versionCode="1"  
  5.     android:versionName="1.0" >  
  6.   
  7.     <uses-sdk  
  8.         android:minSdkVersion="12"  
  9.         android:targetSdkVersion="21" />  
  10.       
  11.     <uses-feature android:name="android.hardware.usb.host"/>  
  12.   
  13.     <application  
  14.         android:allowBackup="true"  
  15.         android:icon="@drawable/ic_launcher"  
  16.         android:label="@string/app_name"  
  17.         android:theme="@style/AppTheme" >  
  18.         <activity  
  19.             android:name=".MainActivity"  
  20.             android:label="@string/app_name" >  
  21.             <intent-filter>  
  22.                 <action android:name="android.intent.action.MAIN" />  
  23.   
  24.                 <category android:name="android.intent.category.LAUNCHER" />  
  25.             </intent-filter>  
  26.               
  27.             <intent-filter>    
  28.                 <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"/>   
  29.             </intent-filter>  
  30.             <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"  
  31.                 android:resource="@xml/device_filter"/>  
  32.               
  33.         </activity>  
  34.     </application>  
  35.   
  36. </manifest>  
[html]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <resources>  
  3.     <usb-device vendor-id="3544" product-id="8199" />  
  4.     <!--   
  5.     <usb-device vendor-id="5251" product-id="4608" />  
  6.     <usb-device vendor-id="3544" product-id="8199" />  
  7.      -->  
  8. </resources>  
这个就不用解释了。

3)成员变量和控件初始化

[html]  view plain  copy
  1. <pre name="code" class="html">    private final String TAG = "++MainActivity++";  
  2.       
  3.     private static final String ACTION_USB_PERMISSION =  
  4.             "com.android.example.USB_PERMISSION";  
  5.       
  6.     private Button mBtnReset;  
  7.     private Button mBtnGetMaxLnu;  
  8.     private Button mBtnSendCommand;  
  9.     private TextView mTvInfo;  
  10.   
  11.     private UsbManager mUsbManager;  
  12.     private UsbDevice mUsbDevice;  
  13.     private UsbEndpoint mEndpointIn;  
  14.     private UsbEndpoint mEndpointOut;  
  15.     private UsbDeviceConnection mConnection = null;  
  16.       
  17.     private final int mVendorID = 3544;  
  18.     private final int mProductID = 8199;  
  19.       
  20.     private boolean mDetachedRegistered = false;  
  21.       
  22.     @Override  
  23.     protected void onCreate(Bundle savedInstanceState) {  
  24.         super.onCreate(savedInstanceState);  
  25.         setContentView(R.layout.activity_main);  
  26.           
  27.         mBtnReset = (Button)findViewById(R.id.btn_reset);  
  28.         mBtnGetMaxLnu = (Button)findViewById(R.id.btn_get_max_lnu);  
  29.         mBtnSendCommand = (Button)findViewById(R.id.btn_send_command);  
  30.         mTvInfo = (TextView)findViewById(R.id.tv_info);  
  31.           
  32.         mBtnReset.setOnClickListener(this);  
  33.         mBtnGetMaxLnu.setOnClickListener(this);  
  34.         mBtnSendCommand.setOnClickListener(this);  
  35.           
  36.         mUsbManager = (UsbManager)getSystemService(Context.USB_SERVICE);  
  37.     }  
 
 4)找到相应的设备并建立连接 
  
 

[java]  view plain  copy
  1. @Override  
  2. protected void onResume() {  
  3.     super.onResume();  
  4.       
  5.     // 获取启动Activity的USB设备  
  6.     Intent intent = getIntent();  
  7.     String action = intent.getAction();  
  8.                    
  9.     mUsbDevice = null;  
  10.     if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {  
  11.         mUsbDevice = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);  
  12.         if(mVendorID != mUsbDevice.getVendorId() || mProductID != mUsbDevice.getProductId()) {  
  13.             mUsbDevice = null;  
  14.         }  
  15.     }   
  16.       
  17.     if(mUsbDevice == null) {  
  18.         refreshDevice();  
  19.        }  
  20.       
  21.     if(mUsbDevice == null) {    // 插入设备自动启动应用程序,自动获取获取permission  
  22.         Log.d(TAG, "Please insert USB flash disk!");            // 手机请使用Toast  
  23.         Toast.makeText(this"Please insert USB flash disk!", Toast.LENGTH_SHORT).show();  
  24.         finish();  
  25.         return;  
  26.     }   
  27.       
  28.     // 判断是否拥有权限  
  29.     if(!mUsbManager.hasPermission(mUsbDevice)) {  
  30.         PendingIntent permissionIntent = PendingIntent.getBroadcast(this0new Intent(ACTION_USB_PERMISSION), 0);  
  31.         IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);  
  32.         registerReceiver(mPermissionReceiver, filter);  
  33.         mUsbManager.requestPermission(mUsbDevice, permissionIntent);              
  34.        } else {  
  35.         Log.d(TAG, "Correct device!");  
  36.            Toast.makeText(MainActivity.this"Correct device!", Toast.LENGTH_SHORT).show();           
  37.         makeConnection();  
  38.        }  
  39.                       
  40.     registerReceiver(usbDetachedReceiver, usbDetachedFilter);   // 注册弹出通知       
  41.     mDetachedRegistered = true;  
  42. }  
refreshDevice()是手动打开软件是获取设备的方法;然后是获取权限,即通信前必须获得使用者的允许;makeConnection()是初始化bulk-in、bulk-out端点,并建立连接;最后是注册U盘弹出接收器。

5)refreshDevice()

[java]  view plain  copy
  1. // 启动程序前已经插入了设备,需要从设备列表中获取  
  2. private void refreshDevice() {  
  3.     HashMap<String, UsbDevice> deviceList = mUsbManager.getDeviceList();  
  4.     Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();  
  5.     while(deviceIterator.hasNext()){  
  6.         mUsbDevice = deviceIterator.next();  
  7.         if(mVendorID == mUsbDevice.getVendorId() && mProductID == mUsbDevice.getProductId()) {  
  8.             break;  
  9.         } else {  
  10.             mUsbDevice = null;  
  11.         }  
  12.     }     
  13. }  
6)注册弹出通知,弹出设备时关闭程序

[java]  view plain  copy
  1. private IntentFilter usbDetachedFilter = new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED);  
  2.      
  3.    private BroadcastReceiver usbDetachedReceiver = new BroadcastReceiver() {  
  4.   
  5.     @Override  
  6.     public void onReceive(Context context, Intent intent) {  
  7.         UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);  
  8.         if(device != null) {  
  9.             // 确保弹出的设备为指定的  
  10.             if(mVendorID == device.getVendorId() && mProductID == device.getProductId()) {  
  11.                 mUsbDevice = null;  
  12.                 finish();  
  13.             }  
  14.         }  
  15.     }  
  16.    };  
7)建立连接:makeConnection()

[java]  view plain  copy
  1.    private void makeConnection() {  
  2.     if(mUsbDevice == null) {  
  3.         Log.d(TAG, "Please insert USB flash disk!");  
  4.         Toast.makeText(this"Please insert USB flash disk!", Toast.LENGTH_SHORT).show();  
  5.         finish();  
  6.         return;  
  7.     }  
  8.     // U盘接口个数为1  
  9.     if(mUsbDevice.getInterfaceCount() != 1) {  
  10.         Log.d(TAG, "Not a USB flash disk!");  
  11.         Toast.makeText(this"Not a USB flash disk!", Toast.LENGTH_SHORT).show();  
  12.         finish();  
  13.         return;  
  14.     }  
  15.       
  16.     UsbInterface intf = mUsbDevice.getInterface(0);  
  17.       
  18.     // U盘接口0可获取的端点数为2  
  19.     if(intf.getEndpointCount() != 2) {  
  20.         Log.d(TAG, "Not a USB flash disk!");  
  21.         Toast.makeText(this"Not a USB flash disk!", Toast.LENGTH_SHORT).show();  
  22.         finish();  
  23.         return;  
  24.     } else {  
  25.         mEndpointIn = intf.getEndpoint(0);   // Bulk-In端点  
  26.         mEndpointOut = intf.getEndpoint(1);  // Bulk_Out端点  
  27.     }  
  28.                       
  29.     if (mUsbDevice != null) {  
  30.            UsbDeviceConnection connection = mUsbManager.openDevice(mUsbDevice);  
  31.            if (connection != null && connection.claimInterface(intf, true)) {  
  32.             Log.d(TAG, "Make connection succeeded!");  
  33.             Toast.makeText(this"Make connection succeeded!", Toast.LENGTH_SHORT).show();  
  34.                mConnection = connection;  
  35.            } else {  
  36.             Log.d(TAG, "Make connection failed!");  
  37.             Toast.makeText(this"Make connection failed!", Toast.LENGTH_SHORT).show();  
  38.                mConnection = null;  
  39.                finish();  
  40.            }  
  41.         }  
  42. }  
8)各按键的命令处理

[java]  view plain  copy
  1. @Override  
  2. public void onClick(View v) {  
  3.     switch(v.getId()) {  
  4.         case R.id.btn_reset :  
  5.             reset();  
  6.             break;  
  7.         case R.id.btn_get_max_lnu :  
  8.             getMaxLnu();  
  9.             break;  
  10.         case R.id.btn_send_command :  
  11.             sendCommand();  
  12.             break;  
  13.         default :  
  14.             break;  
  15.     }  
  16. }  
9)reset()发送reset命令

[java]  view plain  copy
  1. private void reset() {  
  2.     synchronized (this) {  
  3.            if (mConnection != null) {  
  4.             String str = mTvInfo.getText().toString();  
  5.               
  6.             // 复位命令的设置有USB Mass Storage的定义文档给出  
  7.             int result = mConnection.controlTransfer(0x210xFF0x000x00null01000);  
  8.                if(result < 0) {                      // result<0说明发送失败  
  9.                 Log.d(TAG, "Send reset command failed!");  
  10.                 str += "Send reset command failed!\n";  
  11.             } else {   
  12.                 Log.d(TAG, "Send reset command succeeded!");  
  13.                 str += "Send reset command succeeded!\n";  
  14.             }         
  15.                mTvInfo.setText(str);  
  16.            }  
  17.        }  
  18. }  
reset命令通过控制端点发送,如果发送成功,result的值是大于或等于0的。

10)getMaxLnu()获取最大的LNU

[java]  view plain  copy
  1. private void getMaxLnu() {  
  2.     synchronized (this) {  
  3.            if (mConnection != null) {  
  4.             String str = mTvInfo.getText().toString();  
  5.               
  6.             // 接收的数据只有1个字节  
  7.             byte[] message = new byte[1];  
  8.             // 获取最大LUN命令的设置由USB Mass Storage的定义文档给出  
  9.                int result = mConnection.controlTransfer(0xA10xFE0x000x00, message, 11000);  
  10.                if(result < 0) {  
  11.                 Log.d(TAG,  "Get max lnu failed!");  
  12.                 str += "Get max lnu failed!\n";  
  13.             } else {  
  14.                 Log.d(TAG, "Get max lnu succeeded!");                     
  15.                 str += "Get max lnu succeeded!\nMax LNU : ";  
  16.                 for(int i=0; i<message.length; i++) {  
  17.                     str += Integer.toString(message[i]&0x00FF);  
  18.                 }  
  19.             }  
  20.                str += "\n";  
  21.                mTvInfo.setText(str);  
  22.            }  
  23.        }  
  24. }  
LNU是什么东西自己百度,LNU用于后面的bulk传输的参数设置。

11)sendCommand()发送read format capacities命令

[java]  view plain  copy
  1. private void sendCommand() {  
  2.     String str = mTvInfo.getText().toString();  
  3.       
  4.     byte[] cmd = new byte[] {  
  5.         (byte0x55, (byte0x53, (byte0x42, (byte0x43// 固定值  
  6.         (byte0x28, (byte0xe8, (byte0x3e, (byte0xfe// 自定义,与返回的CSW中的值是一样的  
  7.         (byte0x00, (byte0x02, (byte0x00, (byte0x00// 传输数据长度为512字节  
  8.         (byte0x80// 传入数据  
  9.         (byte0x00// LNU为0,则设为0  
  10.         (byte0x01// 命令长度为1  
  11.         (byte0x23, (byte0x00, (byte0x00, (byte0x00// READ FORMAT CAPACITIES,后面的0x00皆被忽略  
  12.         (byte0x00, (byte0x00, (byte0x00, (byte0x00,  
  13.         (byte0x00, (byte0x00, (byte0x00, (byte0x00,  
  14.         (byte0x00, (byte0x00, (byte0x00, (byte0x00  
  15.     };  
  16.     int result = mConnection.bulkTransfer(mEndpointOut, cmd, cmd.length, 1000);  
  17.     if(result < 0) {  
  18.         Log.d(TAG,  "Send command failed!");  
  19.         str += "Send command failed!\n";  
  20.     } else {  
  21.         Log.d(TAG, "Send command succeeded!");  
  22.         str += "Send command succeeded!\n";  
  23.     }  
  24.       
  25.     byte[] message = new byte[24];      //  需要足够的长度接收数据  
  26.     result = mConnection.bulkTransfer(mEndpointIn, message, message.length, 1000);  
  27.     if(result < 0) {  
  28.         Log.d(TAG,  "Receive message failed!");  
  29.         str += "Receive message failed!\n";  
  30.     } else {  
  31.         Log.d(TAG, "Receive message succeeded!");  
  32.         str += "Receive message succeeded!\nFormat capacities : \n";  
  33.         for(int i=0; i<message.length; i++) {  
  34.             str += Integer.toHexString(message[i]&0x00FF) + " ";  
  35.         }                 
  36.     }  
  37.       
  38.     byte[] csw = new byte[13];  
  39.     result = mConnection.bulkTransfer(mEndpointIn, csw, csw.length, 1000);  
  40.     if(result < 0) {  
  41.         Log.d(TAG,  "Receive CSW failed!");  
  42.         str += "\nReceive CSW failed!";  
  43.     } else {  
  44.         Log.d(TAG, "Receive CSW succeeded!");  
  45.         str += "\nReceive CSW succeeded!\nReceived CSW : ";  
  46.         for(int i=0; i<csw.length; i++) {  
  47.             str += Integer.toHexString(csw[i]&0x00FF) + " ";  
  48.         }                 
  49.     }  
  50.     str += "\n";  
  51.     mTvInfo.setText(str);  
  52. }  
注:上面这段代码直接嵌入在UI线程中的,发送和读取时都会阻塞UI线程,请自行开辟线程
到这里就不得不提一下U盘发送命令的顺序

以Android手机和U盘举例,首先准备工作做好(已建立连接,bulk-in和bulk-out端点),然后Android手机发送一个CBW命令给U盘,告诉U盘要做什么:

      (1)如果是发送数据给U盘,那么U盘准备好接收数据,紧接着Android手机发送数据,U盘接收数据后,返回一个CSW给Android手机,告诉接收数据是否成功,这种情况,对于开发者来说,首先发送CBW命令,判断是否发送成功,如果发送成功,紧接着发送数据(注意时间),发送数据后接收CSW,判断是否成功.......

      (2)如果是要从U盘获取数据,那么U盘准备好数据发送给Android手机,Android手机从bulk-in端点接收数据,然后接收U盘发送CSW。

      (3)如果是纯命令,即不用发数据,那么Android手机就接收CSW响应。

上面为先发送一个read format capacities的命令,然后接收format capacities,最后接收CSW。(没讲清楚,敬请原谅!)

最后来看一下结果吧:

接收的数据皆以16进制的形式给出,至于format capacities结果怎么计算的,我也没搞明白,我的U盘是4GB的(如果大神知道,告诉小弟呗),最后的CSW的值的前八个字节与CBW的前八个一样的,后面的请看文章开头给出的参考文档。