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文件中添加
-
<uses-feature android:name="android.hardware.usb.host" />
-
<uses-sdk android:minSdkVersion="12" />
有可能你在其它地方看见这样的写法
-
<uses-feature android:name="android.hardware.usb.host" android:required="true"/>
本人建议不要这样写,因为这样写的话可能在/system/etc/permissions目录下不能自动创建android.hardware.usb.host.xml文件,而需要手动创建。
2)在<activity ...>添加
-
<intent-filter>
-
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
-
</intent-filter>
-
<meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
-
android:resource="@xml/device_filter" />
在res/xml文件夹下新建device_filter.xml
-
<resources>
-
<usb-device vendor-id="3544" product-id="8199" />
-
<usb-device vendor-id="5251" product-id="4608" />
-
</resources>
其中vendor-id和product-id为插入USB设备的生产厂家号和产品号,但插入(attached)上面列出的设备之一时就会弹出选择打开应用程序的对话框。注:上面的id为10进制的,而通过电脑上查看的id为16进制的。
3)获取所有已连接上的USD设备的信息
-
public class MainActivity extends ActionBarActivity {
-
@Override
-
protected void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
setContentView(R.layout.activity_main);
-
UsbManager mManager = (UsbManager) getSystemService(Context.USB_SERVICE);
-
HashMap<String, UsbDevice> deviceList = mManager.getDeviceList();
-
Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
-
-
String info = new String();
-
while (deviceIterator.hasNext())
-
{
-
UsbDevice device = deviceIterator.next();
-
info += "Device Name:" + device.getDeviceName() + "\n";
-
info += "Device ID:" + device.getDeviceId() + "\n";
-
info += "Device Class:" + device.getDeviceClass() + "\n";
-
info += "Device Protocl:" + device.getDeviceProtocol() + "\n";
-
info += "Device Vendor ID:" + device.getVendorId() + "\n";
-
info += "Device Product ID:" + device.getProductId() + "\n";
-
info += "Device Interface Count:" + device.getInterfaceCount() + "\n\n";
-
// Get interface details
-
for (int index = 0; index < device.getInterfaceCount(); index++)
-
{
-
UsbInterface mUsbInterface = device.getInterface(index);
-
info += "Interface " + index + ":\n";
-
info += "Interface ID:" + mUsbInterface.getId() + "\n";
-
info += "Inteface class:" + mUsbInterface.getInterfaceClass() + "\n";
-
info += "Interface protocol:" + mUsbInterface.getInterfaceProtocol() + "\n";
-
info += "Endpoint count:" + mUsbInterface.getEndpointCount() + "\n\n";
-
// Get endpoint details
-
for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++)
-
{
-
UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi);
-
info += "Endpoint " + epi + ":\n";
-
info += "Attributes: " + mEndpoint.getAttributes() + "\n";
-
info += "Direction: " + mEndpoint.getDirection() + "\n";
-
info += "Number: " + mEndpoint.getEndpointNumber() + "\n";
-
info += "Interval: " + mEndpoint.getInterval() + "\n";
-
info += "Packet size: " + mEndpoint.getMaxPacketSize() + "\n";
-
info += "Type: " + mEndpoint.getType() + "\n\n";
-
}
-
}
-
}
-
TextView textView = (TextView)findViewById(R.id.info);
-
textView.setText(info);
-
}
-
}
通过上面这段代码可以获得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对象进行通信前,必须获取权限(获取使用者的同意)。使用
-
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
获取UsbManager对象,用hasPremission()判断设备是否拥有权限,如果没有,使用如下方法获取权限
-
mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
-
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
-
registerReceiver(mUsbReceiver, filter);
-
mUsbManager.requestPermission(mUsbDevice, mPermissionIntent);
-
private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
-
-
public void onReceive(Context context, Intent intent) {
-
String action = intent.getAction();
-
if (ACTION_USB_PERMISSION.equals(action)) {
-
synchronized (this) {
-
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
-
// 获取权限后的操作
-
}
-
else {
-
finish();
-
}
-
}
-
}
-
}
-
};
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设备)
-
private void makeConnection() {
-
if(mUsbDevice == null) {
-
Toast.makeText(this, R.string.no_device, Toast.LENGTH_SHORT).show();
-
finish();
-
return;
-
}
-
if(mUsbDevice.getInterfaceCount() != 1) {
-
Toast.makeText(this, R.string.interface_error, Toast.LENGTH_SHORT).show();
-
finish();
-
return;
-
}
-
-
UsbInterface intf = mUsbDevice.getInterface(0);
-
-
if (mUsbDevice != null) {
-
UsbDeviceConnection connection = mUsbManager.openDevice(mUsbDevice);
-
if (connection != null && connection.claimInterface(intf, true)) {
-
Toast.makeText(this, R.string.connection_fine, Toast.LENGTH_SHORT).show();
-
mConnection = connection;
-
} else {
-
Toast.makeText(this, R.string.connection_error, Toast.LENGTH_SHORT).show();
-
mConnection = null;
-
finish();
-
}
-
}
-
}
(2)发送控制信息(mCmd[8]请根据自己的设备进行设置)
-
private void sendCommand() {
-
-
synchronized (this) {
-
if (mConnection != null) {
-
mCmd = new byte[8];
-
mCmd[0] = (byte) 0x00; //请自行设置
-
mCmd[1] = (byte) 0x00;
-
mCmd[2] = (byte) 0x00;
-
mCmd[3] = (byte) 0x00;
-
mCmd[4] = (byte) 0x00;
-
mCmd[5] = (byte) 0x00;
-
mCmd[6] = (byte) 0x00;
-
mCmd[7] = (byte) 0x00;
-
-
int result = mConnection.controlTransfer(0x21, 0x09, 0x0300, 0x00, mCmd, mCmd.length, 1000);
-
if(result < 0) {
-
Toast.makeText(this, R.string.send_command_failed, Toast.LENGTH_SHORT).show();
-
} else {
-
Toast.makeText(this, R.string.send_command_successed, Toast.LENGTH_SHORT).show();
-
}
-
}
-
}
-
}
(3)根据发送的控制信息,接收控制信息的结果(HID设备)
-
private void recvMessage() {
-
-
synchronized (this) {
-
if (mConnection != null) {
-
byte[] message = new byte[32];
-
int result = mConnection.controlTransfer(0xA1, 0x01, 0x0300, 0x00, message, message.length, 1000);
-
-
if(result < 0) {
-
Toast.makeText(this, R.string.recv_message_failed, Toast.LENGTH_SHORT).show();
-
mMessageView.setText(R.string.string_null);
-
} else {
-
Toast.makeText(this, R.string.recv_message_successed, Toast.LENGTH_SHORT).show();
-
String str = new String();
-
for(int i=0; i<message.length; i++) {
-
str += Integer.toHexString(message[i]&0x00FF) + " ";
-
}
-
mMessageView.setText(str);
-
}
-
}
-
}
-
}
android中,在USB Host Mode下,U盘可以使用的传输数据(或命令)的函数有
-
bulkTransfer(UsbEndpoint endpoint, byte[] buffer,int length, int timeout)
-
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)布局文件
-
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-
xmlns:tools="http://schemas.android.com/tools"
-
android:layout_width="match_parent"
-
android:layout_height="match_parent"
-
android:paddingBottom="@dimen/activity_vertical_margin"
-
android:paddingLeft="@dimen/activity_horizontal_margin"
-
android:paddingRight="@dimen/activity_horizontal_margin"
-
android:paddingTop="@dimen/activity_vertical_margin"
-
android:orientation="vertical"
-
tools:context="com.example.usbcom.MainActivity" >
-
-
<Button
-
android:id="@+id/btn_reset"
-
android:layout_width="match_parent"
-
android:layout_height="wrap_content"
-
android:gravity="center"
-
android:text="@string/reset" />
-
<Button
-
android:id="@+id/btn_get_max_lnu"
-
android:layout_width="match_parent"
-
android:layout_height="wrap_content"
-
android:gravity="center"
-
android:text="@string/get_max_lnu" />
-
<Button
-
android:id="@+id/btn_send_command"
-
android:layout_width="match_parent"
-
android:layout_height="wrap_content"
-
android:gravity="center"
-
android:text="@string/send_command" />
-
<TextView
-
android:id="@+id/tv_info"
-
android:layout_width="match_parent"
-
android:layout_height="wrap_content"
-
android:text="@string/_null" />
-
-
</LinearLayout>
第一个按钮发送reset命令,第二个按钮接收max lnu结果,第三个按钮发送read format capacities的CBW命令,并将获取的结果在文本视图中显示。
2)AndroidManifest.xml和device_filter.xml
-
<?xml version="1.0" encoding="utf-8"?>
-
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-
package="com.example.usbcom"
-
android:versionCode="1"
-
android:versionName="1.0" >
-
-
<uses-sdk
-
android:minSdkVersion="12"
-
android:targetSdkVersion="21" />
-
-
<uses-feature android:name="android.hardware.usb.host"/>
-
-
<application
-
android:allowBackup="true"
-
android:icon="@drawable/ic_launcher"
-
android:label="@string/app_name"
-
android:theme="@style/AppTheme" >
-
<activity
-
android:name=".MainActivity"
-
android:label="@string/app_name" >
-
<intent-filter>
-
<action android:name="android.intent.action.MAIN" />
-
-
<category android:name="android.intent.category.LAUNCHER" />
-
</intent-filter>
-
-
<intent-filter>
-
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"/>
-
</intent-filter>
-
<meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
-
android:resource="@xml/device_filter"/>
-
-
</activity>
-
</application>
-
-
</manifest>
-
<?xml version="1.0" encoding="utf-8"?>
-
<resources>
-
<usb-device vendor-id="3544" product-id="8199" />
-
<!--
-
<usb-device vendor-id="5251" product-id="4608" />
-
<usb-device vendor-id="3544" product-id="8199" />
-
-->
-
</resources>
这个就不用解释了。
3)成员变量和控件初始化
-
<pre name="code" class="html"> private final String TAG = "++MainActivity++";
-
-
private static final String ACTION_USB_PERMISSION =
-
"com.android.example.USB_PERMISSION";
-
-
private Button mBtnReset;
-
private Button mBtnGetMaxLnu;
-
private Button mBtnSendCommand;
-
private TextView mTvInfo;
-
-
private UsbManager mUsbManager;
-
private UsbDevice mUsbDevice;
-
private UsbEndpoint mEndpointIn;
-
private UsbEndpoint mEndpointOut;
-
private UsbDeviceConnection mConnection = null;
-
-
private final int mVendorID = 3544;
-
private final int mProductID = 8199;
-
-
private boolean mDetachedRegistered = false;
-
-
@Override
-
protected void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
setContentView(R.layout.activity_main);
-
-
mBtnReset = (Button)findViewById(R.id.btn_reset);
-
mBtnGetMaxLnu = (Button)findViewById(R.id.btn_get_max_lnu);
-
mBtnSendCommand = (Button)findViewById(R.id.btn_send_command);
-
mTvInfo = (TextView)findViewById(R.id.tv_info);
-
-
mBtnReset.setOnClickListener(this);
-
mBtnGetMaxLnu.setOnClickListener(this);
-
mBtnSendCommand.setOnClickListener(this);
-
-
mUsbManager = (UsbManager)getSystemService(Context.USB_SERVICE);
-
}
4)找到相应的设备并建立连接
-
@Override
-
protected void onResume() {
-
super.onResume();
-
-
// 获取启动Activity的USB设备
-
Intent intent = getIntent();
-
String action = intent.getAction();
-
-
mUsbDevice = null;
-
if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
-
mUsbDevice = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
-
if(mVendorID != mUsbDevice.getVendorId() || mProductID != mUsbDevice.getProductId()) {
-
mUsbDevice = null;
-
}
-
}
-
-
if(mUsbDevice == null) {
-
refreshDevice();
-
}
-
-
if(mUsbDevice == null) { // 插入设备自动启动应用程序,自动获取获取permission
-
Log.d(TAG, "Please insert USB flash disk!"); // 手机请使用Toast
-
Toast.makeText(this, "Please insert USB flash disk!", Toast.LENGTH_SHORT).show();
-
finish();
-
return;
-
}
-
-
// 判断是否拥有权限
-
if(!mUsbManager.hasPermission(mUsbDevice)) {
-
PendingIntent permissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
-
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
-
registerReceiver(mPermissionReceiver, filter);
-
mUsbManager.requestPermission(mUsbDevice, permissionIntent);
-
} else {
-
Log.d(TAG, "Correct device!");
-
Toast.makeText(MainActivity.this, "Correct device!", Toast.LENGTH_SHORT).show();
-
makeConnection();
-
}
-
-
registerReceiver(usbDetachedReceiver, usbDetachedFilter); // 注册弹出通知
-
mDetachedRegistered = true;
-
}
refreshDevice()是手动打开软件是获取设备的方法;然后是获取权限,即通信前必须获得使用者的允许;makeConnection()是初始化bulk-in、bulk-out端点,并建立连接;最后是注册U盘弹出接收器。
5)refreshDevice()
-
// 启动程序前已经插入了设备,需要从设备列表中获取
-
private void refreshDevice() {
-
HashMap<String, UsbDevice> deviceList = mUsbManager.getDeviceList();
-
Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
-
while(deviceIterator.hasNext()){
-
mUsbDevice = deviceIterator.next();
-
if(mVendorID == mUsbDevice.getVendorId() && mProductID == mUsbDevice.getProductId()) {
-
break;
-
} else {
-
mUsbDevice = null;
-
}
-
}
-
}
6)注册弹出通知,弹出设备时关闭程序
-
private IntentFilter usbDetachedFilter = new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED);
-
-
private BroadcastReceiver usbDetachedReceiver = new BroadcastReceiver() {
-
-
@Override
-
public void onReceive(Context context, Intent intent) {
-
UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
-
if(device != null) {
-
// 确保弹出的设备为指定的
-
if(mVendorID == device.getVendorId() && mProductID == device.getProductId()) {
-
mUsbDevice = null;
-
finish();
-
}
-
}
-
}
-
};
7)建立连接:makeConnection()
-
private void makeConnection() {
-
if(mUsbDevice == null) {
-
Log.d(TAG, "Please insert USB flash disk!");
-
Toast.makeText(this, "Please insert USB flash disk!", Toast.LENGTH_SHORT).show();
-
finish();
-
return;
-
}
-
// U盘接口个数为1
-
if(mUsbDevice.getInterfaceCount() != 1) {
-
Log.d(TAG, "Not a USB flash disk!");
-
Toast.makeText(this, "Not a USB flash disk!", Toast.LENGTH_SHORT).show();
-
finish();
-
return;
-
}
-
-
UsbInterface intf = mUsbDevice.getInterface(0);
-
-
// U盘接口0可获取的端点数为2
-
if(intf.getEndpointCount() != 2) {
-
Log.d(TAG, "Not a USB flash disk!");
-
Toast.makeText(this, "Not a USB flash disk!", Toast.LENGTH_SHORT).show();
-
finish();
-
return;
-
} else {
-
mEndpointIn = intf.getEndpoint(0); // Bulk-In端点
-
mEndpointOut = intf.getEndpoint(1); // Bulk_Out端点
-
}
-
-
if (mUsbDevice != null) {
-
UsbDeviceConnection connection = mUsbManager.openDevice(mUsbDevice);
-
if (connection != null && connection.claimInterface(intf, true)) {
-
Log.d(TAG, "Make connection succeeded!");
-
Toast.makeText(this, "Make connection succeeded!", Toast.LENGTH_SHORT).show();
-
mConnection = connection;
-
} else {
-
Log.d(TAG, "Make connection failed!");
-
Toast.makeText(this, "Make connection failed!", Toast.LENGTH_SHORT).show();
-
mConnection = null;
-
finish();
-
}
-
}
-
}
8)各按键的命令处理
-
@Override
-
public void onClick(View v) {
-
switch(v.getId()) {
-
case R.id.btn_reset :
-
reset();
-
break;
-
case R.id.btn_get_max_lnu :
-
getMaxLnu();
-
break;
-
case R.id.btn_send_command :
-
sendCommand();
-
break;
-
default :
-
break;
-
}
-
}
9)reset()发送reset命令
-
private void reset() {
-
synchronized (this) {
-
if (mConnection != null) {
-
String str = mTvInfo.getText().toString();
-
-
// 复位命令的设置有USB Mass Storage的定义文档给出
-
int result = mConnection.controlTransfer(0x21, 0xFF, 0x00, 0x00, null, 0, 1000);
-
if(result < 0) { // result<0说明发送失败
-
Log.d(TAG, "Send reset command failed!");
-
str += "Send reset command failed!\n";
-
} else {
-
Log.d(TAG, "Send reset command succeeded!");
-
str += "Send reset command succeeded!\n";
-
}
-
mTvInfo.setText(str);
-
}
-
}
-
}
reset命令通过控制端点发送,如果发送成功,result的值是大于或等于0的。
10)getMaxLnu()获取最大的LNU
-
private void getMaxLnu() {
-
synchronized (this) {
-
if (mConnection != null) {
-
String str = mTvInfo.getText().toString();
-
-
// 接收的数据只有1个字节
-
byte[] message = new byte[1];
-
// 获取最大LUN命令的设置由USB Mass Storage的定义文档给出
-
int result = mConnection.controlTransfer(0xA1, 0xFE, 0x00, 0x00, message, 1, 1000);
-
if(result < 0) {
-
Log.d(TAG, "Get max lnu failed!");
-
str += "Get max lnu failed!\n";
-
} else {
-
Log.d(TAG, "Get max lnu succeeded!");
-
str += "Get max lnu succeeded!\nMax LNU : ";
-
for(int i=0; i<message.length; i++) {
-
str += Integer.toString(message[i]&0x00FF);
-
}
-
}
-
str += "\n";
-
mTvInfo.setText(str);
-
}
-
}
-
}
LNU是什么东西自己百度,LNU用于后面的bulk传输的参数设置。
11)sendCommand()发送read format capacities命令
-
private void sendCommand() {
-
String str = mTvInfo.getText().toString();
-
-
byte[] cmd = new byte[] {
-
(byte) 0x55, (byte) 0x53, (byte) 0x42, (byte) 0x43, // 固定值
-
(byte) 0x28, (byte) 0xe8, (byte) 0x3e, (byte) 0xfe, // 自定义,与返回的CSW中的值是一样的
-
(byte) 0x00, (byte) 0x02, (byte) 0x00, (byte) 0x00, // 传输数据长度为512字节
-
(byte) 0x80, // 传入数据
-
(byte) 0x00, // LNU为0,则设为0
-
(byte) 0x01, // 命令长度为1
-
(byte) 0x23, (byte) 0x00, (byte) 0x00, (byte) 0x00, // READ FORMAT CAPACITIES,后面的0x00皆被忽略
-
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
-
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
-
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00
-
};
-
int result = mConnection.bulkTransfer(mEndpointOut, cmd, cmd.length, 1000);
-
if(result < 0) {
-
Log.d(TAG, "Send command failed!");
-
str += "Send command failed!\n";
-
} else {
-
Log.d(TAG, "Send command succeeded!");
-
str += "Send command succeeded!\n";
-
}
-
-
byte[] message = new byte[24]; // 需要足够的长度接收数据
-
result = mConnection.bulkTransfer(mEndpointIn, message, message.length, 1000);
-
if(result < 0) {
-
Log.d(TAG, "Receive message failed!");
-
str += "Receive message failed!\n";
-
} else {
-
Log.d(TAG, "Receive message succeeded!");
-
str += "Receive message succeeded!\nFormat capacities : \n";
-
for(int i=0; i<message.length; i++) {
-
str += Integer.toHexString(message[i]&0x00FF) + " ";
-
}
-
}
-
-
byte[] csw = new byte[13];
-
result = mConnection.bulkTransfer(mEndpointIn, csw, csw.length, 1000);
-
if(result < 0) {
-
Log.d(TAG, "Receive CSW failed!");
-
str += "\nReceive CSW failed!";
-
} else {
-
Log.d(TAG, "Receive CSW succeeded!");
-
str += "\nReceive CSW succeeded!\nReceived CSW : ";
-
for(int i=0; i<csw.length; i++) {
-
str += Integer.toHexString(csw[i]&0x00FF) + " ";
-
}
-
}
-
str += "\n";
-
mTvInfo.setText(str);
-
}
注:上面这段代码直接嵌入在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的前八个一样的,后面的请看文章开头给出的参考文档。