之前公司做了一个新项目,需要将身份证读卡器读取到的照片,姓名,地址信息传输到安卓开发板上,开发板执行人脸对比算法,通过自带的相机和身份证照片对比。

读卡器和开发板数据传输通过串口通信实现,这里需要注意的一个地方是,网上搜索Android串口通信,几乎都是使用jni的方式,因为Android SDK并没有在Framework层实现封装关于串口通信的类库,Android是基于Linux内核,所以我们可以像在Linux系统上一样来使用串口。这里可以参照Google已经给出了源码,地址在GitHub android-serialport-api 。嗯。。。12年的运行在eclipse里的代码。

然后再看看我们的硬件设备:

  1. Android开发板,型号为RK3288
  2. 身份证读卡器
  3. PL2303HX 芯片的USB转接线

这里有一个坑,公司没人搞过Android串口开发,而网上搜到的都是使用上面的方式进行通信的。我们的身份证读卡器的接口是TTL RS232模块,和Android开发板连接需要一个USB转接线,就是下面这个玩意儿:

通过USB转换了,所以如果使用上面jni的方式,你给读卡器发送命令发送到死,它都不会回应你的(╯°Д°)╯︵┻━┻。所以这里已经是USB设备之间进行通信了。

Android系统已经提供了android.hardware.usb.host用于USB设备通信。那怎么用的呢?问得好!我也不知道。

配置清单文件

<uses-feature        android:name="android.hardware.usb.host"        android:required="true" />复制代码

required为true的意思是如果用户设备中没有android.hardware.usb.host这个类库,则无法安装该程序。

扫描获取设备列表

枚举当前的所有设备,通过vid和pid判断扫描出来的设备是否是自己所需要的设备,如果是自己需要的设备(UsbDevice),则申请使用权限:

UsbDevice device = null;private void findSerialPortDevice(){    UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);    HashMap usbDevices = usbManager.getDeviceList();    if (!usbDevices.isEmpty()) {        boolean keep = true;        for (Map.Entry entry : usbDevices.entrySet()) {            device = entry.getValue();            int deviceVID = device.getVendorId();            int devicePID = device.getProductId();            if (deviceVID != 0x1d6b && (devicePID != 0x0001 && devicePID != 0x0002 && devicePID != 0x0003)) {                // There is a device connected to our Android device. Try to open it as a Serial Port.                requestUserPermission();                keep = false;            } else {                device = null;            }            if (!keep)                break;        }    }}复制代码

上面的代码运行之后,如果没有问题则会得到一个UsbDevice,先看看google文档给出的这个类的解释:

This class represents a USB device attached to the android device with the android device acting as the USB host. Each device contains one or more UsbInterfaces, each of which contains a number of UsbEndpoints (the channels via which data is transmitted over USB).

此类表示连接到Android设备的USB设备,其中android设备充当USB主机。 每个设备都包含一个或多个UsbInterfaces,每个UsbInterfaces包含许多UsbEndpoints(相当于一个通道,通过USB来进行数据传输的通道)。

其实这个类就是用来描述USB设备的信息的,可以通过这个类获取到设备的输出输入端口,以及设备标识等信息。

获取到需要的设备之后,请求使用权限:

private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION";public static final String ACTION_USB_ATTACHED = "android.hardware.usb.action.USB_DEVICE_ATTACHED";public static final String ACTION_USB_DETACHED = "android.hardware.usb.action.USB_DEVICE_DETACHED";private void requestUserPermission() {       Intent intent = new Intent(ACTION_USB_PERMISSION);       PendingIntent mPermissionIntent = PendingIntent.getBroadcast(context, 0, intent, 0);       IntentFilter permissionFilter = new IntentFilter(ACTION_USB_PERMISSION);       context.registerReceiver(usbPermissionReceiver, permissionFilter);       //申请权限 会弹框提示用户授权       usbManager.requestPermission(usbDevice, mPermissionIntent);}复制代码

这里我们声明一个广播Receiver,当接受到授权成功的广播后做一些其他处理:

private boolean serialPortConnected;private UsbDeviceConnection connection;private final BroadcastReceiver cardReaderReceiver = new BroadcastReceiver() {        @Override        public void onReceive(Context arg0, Intent arg1) {            if (arg1.getAction().equals(ACTION_USB_PERMISSION)) {                boolean granted = arg1.getExtras().getBoolean(UsbManager.EXTRA_PERMISSION_GRANTED);  //用户是否同意授权使用usb                if (granted)                {                    connection = usbManager.openDevice(device); //建立一个连接,通过这个连接读写数据                    new ConnectionThread().start();  //开始读写数据                }            } else if (arg1.getAction().equals(ACTION_USB_ATTACHED)) {                if (!serialPortConnected)                    findSerialPortDevice();            } else if (arg1.getAction().equals(ACTION_USB_DETACHED)) {                serialPortConnected = false;            }        }    };复制代码

收发数据

授权成功之后,就可以建立一个连接来读写数据了。UsbDeviceConnection就是这个连接

google文档给出的解释是:

This class is used for sending and receiving data and control messages to a USB device. Instances of this class are created by openDevice(UsbDevice).

这个类用于向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.javaandroid.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) {                    e.printStackTrace();                }            } catch (PackageManager.NameNotFoundException e) {                e.printStackTrace();            }            sendBroadcast(intent);  //伪装授权成功代码之后,再发送一条广播        }    }复制代码

经测试确实不需要经过弹框授权,直接和USB设备建立连接了。

转载于:https://juejin.im/post/5d07a49af265da1bcd37d8c2

更多相关文章

  1. 没有一行代码,「2020 新冠肺炎记忆」这个项目却登上了 GitHub 中
  2. “罗永浩抖音首秀”销售数据的可视化大屏是怎么做出来的呢?
  3. Nginx系列教程(三)| 一文带你读懂Nginx的负载均衡
  4. 不吹不黑!GitHub 上帮助人们学习编码的 12 个资源,错过血亏...
  5. DelphiXE获取 Android(安卓)上的设备 ID
  6. 浅析Java 对象初始化
  7. 关于Android加载状态视图切换
  8. Android学习之Android(安卓)studio TraceView和lint工具的使用详
  9. Android上蓝牙通信功能开发:BluetoothChat例程分析

随机推荐

  1. Android之ListView和ArrayAdapter的组合
  2. Android(安卓)播放Gif 动画
  3. Android(安卓)TextView使用HTML处理字体
  4. Android(安卓)推荐博客
  5. Android路在何方?
  6. Android(安卓)JUnit 入门指南
  7. Android使用AIDL实现进程间通信
  8. fir.im Weekly - iOS / Android(安卓)动
  9. 文本内容自动朗读
  10. Android业务组件化二