Android USB串口通信实现 以及绕过USB弹框验证

这个类用于向USB设备发送和接收数据,以及控制消息。 它的实例由openDevice(UsbDevice)这个方法创建。

在这个时候,我们已经可以和设备进行数据传输了(理论上)。在大部分情况下还需要对USB串口进行一些配置,比如波特率,停止位,数据控制等,不然两边配置不同,收到的数据会乱码。具体怎么配置,需要看串口设备的芯片是什么了,现在主流的基本上就是PL2303,我使用的转接线也是PL2303的。幸运的是github上有个专门的库UsbSerial,将这些繁琐的配置都打包好了,我们直接用就好了,使用方法可以去github上看,写得很详细。

发送命令

那怎么给usb外设发送数据呢?UsbDeviceConnection有一个方法用于发送数据:

int bulkTransfer(outEndpoint, data, data.length, TIMEOUT);

第一个参数是数据传输的端口,这个端口可不是随便设置的,我们要找到具有数据传输功能的接口UsbInterface,从它里面找到数据输入和输出端口UsbEndpoint 。

mInterface = device.getInterface(0); //一般第一个就是我们需要的
int numberEndpoints = mInterface.getEndpointCount();
for(int i=0;i<=numberEndpoints-1;i++)
{
UsbEndpoint endpoint = mInterface.getEndpoint(i);
if(endpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK
&& endpoint.getDirection() == UsbConstants.USB_DIR_IN)
inEndpoint = endpoint;
else if(endpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK
&& endpoint.getDirection() == UsbConstants.USB_DIR_OUT)
outEndpoint = endpoint;
}

第二个参数是发送的数据

第三个参数是数据的大小,最后一个参数是设置超时时间。这个方法的返回值是int类型,它表示本次发送数据成功的字节数,如果失败的话,就返回-1。

接收数据

我们已经找到了数据输入端口usbEndpointIn,因为数据的输入是不定时的,因此我们可以另开一个线程,来专门接受数据。

int maxSize = inEndpoint.getMaxPacketSize();
ByteBuffer byteBuffer = ByteBuffer.allocate(maxSize); //创建一个缓冲区接收数据
UsbRequest usbRequest = new UsbRequest(); //注意UsbRequest是异步处理的
usbRequest.initialize(connection, inEndpoint);
usbRequest.queue(byteBuffer, maxSize);
if(connection.requestWait() == usbRequest){
byte[] retData = byteBuffer.array();
for(Byte byte1 : retData){
Log.d(TAG,byte1)
}
}

绕过USB系统授权

不知道是Android的bug还是什么,给usb授权的时候会有一个弹框提醒,虽然可以勾选不再提示,但是没有任何用,关机重启之后,还是会重新弹出来。因为是Android开发板,就算外接显示屏,也不会触屏呀!一两台设备还好,外接鼠标搞定,要是上百上千台,那不得累死!

所以有没有什么方法,可以跳过USB授权验证呢?答案是有的。

我们不需要这个弹框,可以看看点击弹框确认按钮之后做了什么操作。我们可以模仿点击确认之后的流程,骗过系统。

当弹框出现的时候,可以通过adb shell查看当前的activity:

adb shell dumpsys activity | grep -i run

可以清楚的看到当前的activity是UsbPermissionActivity,AndroidSdk里面是可以搜得到这个activity的,我的开发板是6.0的,所以选的android-23,那我们分析一下这个activity做了些什么。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

先把代码全部贴出来:

public class UsbPermissionActivity extends AlertActivity
implements DialogInterface.OnClickListener, CheckBox.OnCheckedChangeListener {

private static final String TAG = “UsbPermissionActivity”;

private CheckBox mAlwaysUse;
private TextView mClearDefaultHint;
private UsbDevice mDevice;
private UsbAccessory mAccessory;
private PendingIntent mPendingIntent;
private String mPackageName;
private int mUid;
private boolean mPermissionGranted;
private UsbDisconnectedReceiver mDisconnectedReceiver;

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);

Intent intent = getIntent();
mDevice = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
mAccessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
mPendingIntent = (PendingIntent)intent.getParcelableExtra(Intent.EXTRA_INTENT);
mUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
mPackageName = intent.getStringExtra(“package”);

PackageManager packageManager = getPackageManager();
ApplicationInfo aInfo;
try {
aInfo = packageManager.getApplicationInfo(mPackageName, 0);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, “unable to look up package name”, e);
finish();
return;
}
String appName = aInfo.loadLabel(packageManager).toString();

final AlertController.AlertParams ap = mAlertParams;
ap.mIcon = aInfo.loadIcon(packageManager);
ap.mTitle = appName;
if (mDevice == null) {
ap.mMessage = getString(R.string.usb_accessory_permission_prompt, appName);
mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mAccessory);
} else {
ap.mMessage = getString(R.string.usb_device_permission_prompt, appName);
mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mDevice);
}
ap.mPositiveButtonText = getString(android.R.string.ok);
ap.mNegativeButtonText = getString(android.R.string.cancel);
ap.mPositiveButtonListener = this;
ap.mNegativeButtonListener = this;

// add “always use” checkbox
LayoutInflater inflater = (LayoutInflater)getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
ap.mView = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null);
mAlwaysUse = (CheckBox)ap.mView.findViewById(com.android.internal.R.id.alwaysUse);
if (mDevice == null) {
mAlwaysUse.setText(R.string.always_use_accessory);
} else {
mAlwaysUse.setText(R.string.always_use_device);
}
mAlwaysUse.setOnCheckedChangeListener(this);
mClearDefaultHint = (TextView)ap.mView.findViewById(
com.android.internal.R.id.clearDefaultHint);
mClearDefaultHint.setVisibility(View.GONE);

setupAlert();

}

@Override
public void onDestroy() {
//根据用户的操作决定是否调用service的方法
IBinder b = ServiceManager.getService(USB_SERVICE);
IUsbManager service = IUsbManager.Stub.asInterface(b);
// send response via pending intent
Intent intent = new Intent();
try {
if (mDevice != null) {
intent.putExtra(UsbManager.EXTRA_DEVICE, mDevice);
if (mPermissionGranted) {
service.grantDevicePermission(mDevice, mUid);
if (mAlwaysUse.isChecked()) {
final int userId = UserHandle.getUserId(mUid);
service.setDevicePackage(mDevice, mPackageName, userId);
}
}
}
if (mAccessory != null) {
intent.putExtra(UsbManager.EXTRA_ACCESSORY, mAccessory);
if (mPermissionGranted) {
service.grantAccessoryPermission(mAccessory, mUid);
if (mAlwaysUse.isChecked()) {
final int userId = UserHandle.getUserId(mUid);
service.setAccessoryPackage(mAccessory, mPackageName, userId);
}
}
}
intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, mPermissionGranted);
mPendingIntent.send(this, 0, intent);
} catch (PendingIntent.CanceledException e) {
Log.w(TAG, “PendingIntent was cancelled”);
} catch (RemoteException e) {
Log.e(TAG, “IUsbService connection failed”, e);
}

if (mDisconnectedReceiver != null) {
unregisterReceiver(mDisconnectedReceiver);
}
super.onDestroy();
}

public void onClick(DialogInterface dialog, int which) {
//记录下用户的操作
if (which == AlertDialog.BUTTON_POSITIVE) {
mPermissionGranted = true;
}
finish();
}

public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (mClearDefaultHint == null) return;

if(isChecked) {
mClearDefaultHint.setVisibility(View.VISIBLE);
} else {
mClearDefaultHint.setVisibility(View.GONE);
}
}
}

可以看到的是继承的是AlertActivity,onCreate方法里主要是初始化布局,还有通过intent获取到device和pendingIntent等,这里盲猜一下,应该是通过//盲猜这个mPendingIntent应该是通过usbManager.requestPermission(device, mPendingIntent);传过来的。

这里我们主要看onDestroy里面做的操作。其实这里已经很清楚的看出来了,如果用户同意授权,就会调用IUsbManager的方法进行跨进程通信,

我们把代码精简一下:

IBinder b = ServiceManager.getService(USB_SERVICE);
IUsbManager service = IUsbManager.Stub.asInterface(b);
Intent intent = new Intent();
intent.putExtra(UsbManager.EXTRA_DEVICE, mDevice);
service.grantDevicePermission(mDevice, mUid);
/*附加信息,可以不要
intent.putExtra(UsbManager.EXTRA_ACCESSORY, mAccessory);
service.grantAccessoryPermission(mAccessory, mUid);
intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, mPermissionGranted);
*/
mPendingIntent.send(this, 0, intent);

也就是说,我们只要模拟上面的代码,即可伪装为我们已经通过了授权。

但是需要注意的一个地方是ServiceManager类和IUsbManager接口都是对开发者隐藏的,不能直接调用,相信走到这一步怎么解决的思路已经很清晰了,无非就是反射调用、修改framework。

这里参考了stackoverflow上面的回答,我使用的方法是创建具有相同包名和完全相同名称的类:

在我的项目中添加一个包: android.hardware.usb 并在其中放入一个名为IUsbManager.java的文件,再添加一个包:android.os,放入ServiceManager.java 文件。

我的项目是基于RK3288开发板做的,按照stackoverflow上面的修改并没有效果,答主也说了他是Android4.0.3的,在其他系统版本上不一定管用。所以我在RK3288官网下载了系统源码,提取了这两个文件,发现还引用了其他文件,总共添加了如下文件:

android.os包下的:
IServiceManager.java
ServiceManager.java
ServiceManagerNative.java

android.hardware.usb包下的:
IUsbManager.java
//还有一个aidl文件,在android.os包下:
IPermissionController.aidl

这几个文件内容比较多,需要的同学可以去github自取:usbserial

添加完成之后,整个工程结构是这样的:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

最后,修改原来申请授权的方式:

private void requestUserPermission() {
Intent intent = new Intent();
intent.setAction(ACTION_USB_PERMISSION);
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_USB_PERMISSION);
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
registerReceiver(mReceiver, filter);
// Request permission
for (UsbDevice device : usbManager.getDeviceList().values()) {
intent.putExtra(UsbManager.EXTRA_DEVICE, device);
intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true);
final PackageManager pm = getPackageManager();
try {
ApplicationInfo aInfo = pm.getApplicationInfo(getPackageName(),
0);
try {
IBinder b = ServiceManager.getService(USB_SERVICE);
IUsbManager service = IUsbManager.Stub.asInterface(b);
service.grantDevicePermission(device, aInfo.uid);
} catch (RemoteException e) {

面试宝典

面试必问知识点、BATJ历年历年面试真题+解析

学习经验总结

(一)调整好心态
心态是一个人能否成功的关键,如果不调整好自己的心态,是很难静下心来学习的,尤其是现在这么浮躁的社会,大部分的程序员的现状就是三点一线,感觉很累,一些大龄的程序员更多的会感到焦虑,而且随着年龄的增长,这种焦虑感会越来越强烈,那么唯一的解决办法就是调整好自己的心态,要做到自信、年轻、勤奋。这样的调整,一方面对自己学习有帮助,另一方面让自己应对面试更从容,更顺利。

(二)时间挤一挤,制定好计划
一旦下定决心要提升自己,那么再忙的情况下也要每天挤一挤时间,切记不可“两天打渔三天晒网”。另外,制定好学习计划也是很有必要的,有逻辑有条理的复习,先查漏补缺,然后再系统复习,这样才能够做到事半功倍,效果才会立竿见影。

(三)不断学习技术知识,更新自己的知识储备
对于一名程序员来说,技术知识方面是非常重要的,可以说是重中之重。要面试大厂,自己的知识储备一定要非常丰富,若缺胳膊少腿,别说在实际工作当中,光是面试这一关就过不了。对于技术方面,首先基础知识一定要扎实,包括自己方向的语言基础、计算机基础、算法以及编程等等。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》
点击传送门,即可获取!

缺,然后再系统复习,这样才能够做到事半功倍,效果才会立竿见影。

(三)不断学习技术知识,更新自己的知识储备
对于一名程序员来说,技术知识方面是非常重要的,可以说是重中之重。要面试大厂,自己的知识储备一定要非常丰富,若缺胳膊少腿,别说在实际工作当中,光是面试这一关就过不了。对于技术方面,首先基础知识一定要扎实,包括自己方向的语言基础、计算机基础、算法以及编程等等。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》
点击传送门,即可获取!

  • 16
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值