This commit is contained in:
2026-03-31 08:51:00 +08:00
commit 12d21c4c90
71 changed files with 4529 additions and 0 deletions

29
.gitignore vendored Normal file
View File

@@ -0,0 +1,29 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
build/

30
.metadata Normal file
View File

@@ -0,0 +1,30 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "b0850beeb25f6d5b10426284f506557f66181b36"
channel: "stable"
project_type: plugin
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: b0850beeb25f6d5b10426284f506557f66181b36
base_revision: b0850beeb25f6d5b10426284f506557f66181b36
- platform: android
create_revision: b0850beeb25f6d5b10426284f506557f66181b36
base_revision: b0850beeb25f6d5b10426284f506557f66181b36
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,170 @@
package com.sdses;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Set;
import java.util.UUID;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
public class BlueTooth
{
private static UUID SS_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
private static BluetoothAdapter mAdapter = null;
private static BluetoothDevice mDevice = null;
private static BluetoothSocket mSocket = null;
private static InputStream mInputStream = null;
private static OutputStream mOutputStream = null;
public static long BthConnect(String BlueToothName)
{
mAdapter = BluetoothAdapter.getDefaultAdapter();
if (mAdapter == null)
{
return -1;
}
if (!mAdapter.isEnabled())
{
return -2;
/*
mAdapter.enable();
if (!mAdapter.isEnabled())
{
return -2;
}
*/
}
//获得已配对的蓝牙设备列表
Set<BluetoothDevice> pairedDevices = mAdapter.getBondedDevices();
if(pairedDevices.size() <= 0)
{
return -3;
}
ArrayList<BluetoothDevice> ALBluetoothDevice = new ArrayList<BluetoothDevice>();
for (BluetoothDevice device : pairedDevices)
{
ALBluetoothDevice.add(device);
}
//mAdapter.cancelDiscovery();
//查找设备
for(int i = 0; i < ALBluetoothDevice.size(); i++)
{
BluetoothDevice cDevice = (BluetoothDevice)ALBluetoothDevice.get(i);
String theBlueToothName = cDevice.getName();
if(theBlueToothName.indexOf(BlueToothName) < 0)
{
continue;
}
//开始连接设备
mDevice = cDevice;
try
{
mSocket = mDevice.createRfcommSocketToServiceRecord(SS_UUID);
mSocket.connect();
mInputStream = mSocket.getInputStream();
mOutputStream = mSocket.getOutputStream();
return 0;
}
catch (Exception e)
{
return -4;
}
}
return -5; //没有配对设备
}
public static long BthSendSocket(byte[] buffer, int len)
{
if(mOutputStream == null)
{
return -1;
}
try
{
mOutputStream.write(buffer, 0, len);
}
catch (IOException e)
{
return -2;
}
return len;
}
public static long BthRecvSocket(byte[] buffer)
{
if(mInputStream == null)
{
return -1;
}
try
{
// int nAvailable = mInputStream.available();
// if(nAvailable <= 0)
// {
// return 0;
// }
return mInputStream.read(buffer, 0, 4096);
}
catch (IOException e)
{
return -2;
}
}
public static void BthDisconnect()
{
if(mInputStream != null)
{
try
{
mInputStream.close();
mInputStream = null;
}
catch (IOException e)
{
return;
}
}
if(mOutputStream != null)
{
try
{
mOutputStream.close();
mOutputStream = null;
}
catch (IOException e)
{
return;
}
}
if(mSocket != null)
{
try
{
mSocket.close();
mSocket = null;
}
catch (IOException e)
{
return;
}
}
}
}

View File

@@ -0,0 +1,122 @@
package com.sdses;
import android.content.Context;
public class JniCommonInterface
{
static
{
System.loadLibrary("WltRS");
System.loadLibrary("PortCommunication");
System.loadLibrary("TerminalProtocol");
System.loadLibrary("CommonInterface");
}
public static long GetUsbPermission(Context ctx, int Vid, int Pid)
{
return UsbHidPort.GetUsbPermission(ctx, Vid, Pid);
}
public static native long SetLogFileEx(String LogFileName);
public static native long SetTerminalLibrary(String LibraryFileName);
public static native long GetLibraryInfo(byte[] Version, byte[] Description);
public static native long SetAutoPara(String PortType, String PortPara, String ExtendPara, String DllName, int UsingGA467);
public static native long OpenDevice(String PortType, String PortPara, String ExtendPara);
public static native long SetCurrentDevice(long DevHandle);
public static native long GetCurrentDevice();
public static native long CloseDevice();
public static native long CommandTransmit(long Command, long CmdDataLength, byte[] CmdData, byte[] StatusWords, long[] RecvDataLength, byte[] RecvData, long TimeOut);
public static native long GA467Transmit(long Command, long CmdDataLength, byte[] CmdData, byte[] StatusWords, long[] RecvDataLength, byte[] RecvData, long TimeOut);
public static native long TerminalGetModel(byte[] TerminalModel);
public static native long TerminalGetFirmVersion(byte[] FirmVersion, byte[]HardwareVersion);
public static native long TerminalHeartBeat();
public static native long LedOnOff(byte LedNo, byte OnOff);
public static native long LedBlink(byte LedNo, long OnTimeMs, long OffTimeMs, byte BlinkCount, byte FinalState);
public static native long GetLastRecvData(byte[] LastRecvData);
public static native long IdFindCard();
public static native long IdSelectCard();
public static native long IdReadBaseMsg(byte[] pucCHMsg, long[] puiCHMsgLen, byte[] pucPHMsg, long[] puiPHMsgLen);
public static native long IdReadBaseFpMsg(byte[] pucCHMsg, long[] puiCHMsgLen, byte[] pucPHMsg, long[] puiPHMsgLen, byte[] pucFPMsg, long[] puiFPMsgLen);
public static native long IdReadNewAppMsg(byte[] pucAppMsg, long[] puiAppMsgLen);
public static native long IdReadSn(byte[] SN, long[] SNLen);
public static native long IdApdu(long SendApduLen, byte[] SendApdu, byte[] RecvApdu, long[] RecvApduLen);
public static native long SdtFindCard(byte[] pucManaInfo);
public static native long SdtSelectCard(byte[] pucManaMsg);
public static native long SdtReadBaseMsg(byte[] pucCHMsg, long[] puiCHMsgLen, byte[] pucPHMsg, long[] puiPHMsgLen);
public static native long SdtReadBaseFpMsg(byte[] pucCHMsg, long[] puiCHMsgLen, byte[] pucPHMsg, long[] puiPHMsgLen, byte[] pucFPMsg, long[] puiFPMsgLen);
public static native long SdtReadNewAppMsg(byte[] pucAppMsg, long[] puiAppMsgLen);
public static native long SamGetStatus();
public static native long SamGetId(byte[] SamId, long[] SamIdLen);
public static native long SamGetIdStr(byte[] SamIdStr);
public static native long SdtSamGetStatus();
public static native long SdtSamGetId(byte[] SamId, long[] SamIdLen);
public static native long SdtSamGetIdStr(byte[] SamIdStr);
public static native long IdReadCard(byte CardType, byte InfoEncoding, byte[] IdCardInfo, long TimeOutMs);
public static native long SdtReadCard(byte CardType, byte InfoEncoding, byte[] IdCardInfo, long TimeOutMs);
public static native long IdReadNewAddress(byte[] NewAddress);
public static native long SdtReadNewAddress(byte[] NewAddress);
public static native long IdCardGetName(byte[] Name);
public static native long IdCardGetNameEn(byte[] NameEn);
public static native long IdCardGetGender(byte[] Gender);
public static native long IdCardGetGenderId(byte[] GenderId);
public static native long IdCardGetNation(byte[] Nation);
public static native long IdCardGetNationId(byte[] NationId);
public static native long IdCardGetBirthDate(byte[] BirthDate);
public static native long IdCardGetAddress(byte[] Address);
public static native long IdCardGetIdNumber(byte[] IdNumber);
public static native long IdCardGetSignOrgan(byte[] SignOrgan);
public static native long IdCardGetBeginTerm(byte[] BeginTerm);
public static native long IdCardGetValidTerm(byte[] ValidTerm);
public static native long IdCardGetFPBuffer(byte[] FPBuffer, long[] FPBufferLen);
public static native long IdCardGetPhotoFile(String PhotoFile);
public static native long IdCardGetPhotoBuffer(byte WltBmpJpg, byte[] PhotoBuffer, long[] PhotoBufferLen);
public static native long MIdSamGetCert(byte[] Cert, long[] CertLen);
public static native long MIdSamEnable(long EnableDataLen, byte[] EnableData);
public static native long MIdSamDisable(long DisableDataLen, byte[] DisableData);
public static native long MIdSamGetEnableState(byte[] EnableState, long[] RemainNum, byte[] EnableTime, byte[] DisableTime, byte[] SecondCert, long[] SecondCertLen, byte[] EncryptCert, long[] EncryptCertLen, byte[] SignCert, long[] SignCertLen);
public static native long MIdSamAuthRequest(byte[] RequestData56);
public static native long MIdSamAuthConfirm(byte[] ConfirmData89);
public static native long MIdReadChkData(byte[] Random16, byte[] RemainAuthNum, byte[] SamId22, byte[] BeginAndValidTerm32, byte[] ShortCode16, byte[] CheckData, long[] CheckDataLen, byte[] Sign64);
public static native long MIdReadChkDataPF(byte[] Random16, byte[] RemainAuthNum, byte[] SamId22, byte[] BeginAndValidTerm32, byte[] ShortCode16, byte[] CheckData, long[] CheckDataLen, byte[] Hash32, byte[] Sign64, byte[] PH, long[] PHLen, byte[] FP, long[] FPLen);
public static native long MIdCheckShortLongCode(byte[] Random16, byte ShortCodeCount, byte[] ShortCode, byte LongCodeCount, byte[] LongCode, byte[] RemainAuthNum, byte[] SamId22, byte[] BeginAndValidTerm32, byte[] MatchCode, byte[] MatchCodeLen, byte[] CheckData, long[] CheckDataLen, byte[] Sign64);
public static native long MIdCheckShortLongCodePF(byte[] Random16, byte ShortCodeCount, byte[] ShortCode, byte LongCodeCount, byte[] LongCode, byte[] RemainAuthNum, byte[] SamId22,byte[] BeginAndValidTerm32, byte[] MatchCode, byte[] MatchCodeLen, byte[] CheckData, long[] CheckDataLen, byte[] Hash32, byte[] Sign64, byte[] PH, long[] PHLen, byte[] FP, long[] FPLen);
public static native long MIdCheckProperty(byte[] Random16, byte PropertyCode, byte PropertyValLen, byte[] PropertyVal, byte[] RemainAuthNum, byte[] SamId22, byte[] CheckResult, byte[] CheckData, long[] CheckDataLen, byte[] Sign64);
public static native long MagRead(byte Tracks, byte[] TrackData1, byte[] TrackData2, byte[] TrackData3, byte TimeOutSec);
public static native long MagWrite(byte Tracks, String TrackData1, String TrackData2, String TrackData3, byte TimeOutSec);
public static native long QrRead(byte[] QrData, byte TimeOutSec);
public static native long QrCancel();
public static native long QrAsynEnable(byte TimeOutSec);
public static native long QrAsynRead(byte[] QrData);
public static native long QrAsynDisable();
public static native long M1FindCard(byte[] UID, long[] UIDLen);
public static native long M1Authentication(byte KeyType, byte SecAddr, byte[] Key, byte[] UID);
public static native long M1ReadBlock(byte BlockAddr, byte[] BlockData, long[] BlockDataLen);
public static native long M1WriteBlock(byte BlockAddr, long BlockDataLen, byte[] BlockData);
public static native long M1Halt();
public static native long FpCapFeature(byte[] Feature, long[] FeatureLen);
public static native long FpMatchFeature(long FeatureLen1, byte[] Feature1, long FeatureLen2, byte[] Feature2, long[] Score);
public static native long SsseReadCard(int iType, byte[] SSCardInfo, byte[] SSErrorInfo);
public static native long SsseReadCard2(int iType, byte[] SSCardInfo, byte[] SSErrorInfo);
public static native long SsseGetCardInfo(String Tag, byte[] SSCardInfo);
public static native long CpuPowerOn(byte Slot, byte[] ATRS, long[] ATRSLen);
public static native long CpuApdu(byte Slot, long SendApduLen, byte[] SendApdu, byte[] RecvApdu, long[] RecvApduLen);
public static native long CpuPowerOff(byte Slot);
public static native long IccGetCardInfo(int ICtype, String AIDList, String TagList, byte[] IcCardInfo);
public static native long IccGetARQC(int ICtype, String trData, String AIDList, byte[] ARQC, byte[] trAppData);
public static native long IccARPCExeScript(int ICtype, String trData, String ARPC, String trAppData, byte[] ScriptResult, byte[] TC);
public static native long IccGetTrDetail(int ICtype, String AIDList, byte[] TrDetail);
public static native long IccGetLoadDetail(int ICtype, String AIDList, byte[] LoadDetail);
public static native long HexToAsc(byte[] Hex, long HexLength, byte[] Asc);
public static native long AscToHex(String Asc, long HexLength, byte[] Hex);
}

View File

@@ -0,0 +1,273 @@
package com.sdses;
import java.util.HashMap;
import java.util.Iterator;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
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.hardware.usb.UsbManager;
public class UsbHidPort
{
private static final String ACTION_USB_PERMISSION = "com.sdses.JniCommonInterface.USB_PERMISSION";
private static final BroadcastReceiver mUsbReceiver = new BroadcastReceiver()
{
public void onReceive(Context context, Intent intent)
{
String action = intent.getAction();
if (action.equals(ACTION_USB_PERMISSION))
{
synchronized (this)
{
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false))
{
if (device != null)
{
}
}
else
{
}
}
}
}
};
public static UsbManager usbManager = null;
public static UsbDevice usbDevice = null;
public static UsbInterface usbInterface = null;
public static UsbDeviceConnection usbConnection = null;
public static UsbEndpoint epOut;
public static UsbEndpoint epIn;
public static int SendBlockSize = 0;
public static int RecvBlockSize = 0;
public static synchronized long GetUsbPermission(Context ctx, int Vid, int Pid)
{
try
{
usbManager = (UsbManager)ctx.getSystemService(Context.USB_SERVICE);
HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();
Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
usbDevice = null;
while (deviceIterator.hasNext())
{
UsbDevice device = deviceIterator.next();
if ((device.getVendorId() == Vid) && (device.getProductId() == Pid))
{
usbDevice = device;
break;
}
}
if (usbDevice == null)
{
return -1;
}
if(usbDevice.getInterfaceCount() <= 0)
{
return -2;
}
usbInterface = usbDevice.getInterface(0);
if(usbInterface == null)
{
return -2;
}
// 判断是否有权限
if(!usbManager.hasPermission(usbDevice))
{
PendingIntent mPermissionIntent = PendingIntent.getBroadcast(ctx, 0, new Intent(ACTION_USB_PERMISSION), 0);
IntentFilter permissionFilter = new IntentFilter(ACTION_USB_PERMISSION);
ctx.registerReceiver(mUsbReceiver, permissionFilter);
usbManager.requestPermission(usbDevice, mPermissionIntent);
System.out.print("Requesting Usb Permission");
while(!usbManager.hasPermission(usbDevice))
{
}
}
return 0;
}
catch(Exception ex)
{
ex.printStackTrace();
return -99;
}
}
public static long UsbOpenPort(int Vid, int Pid)
{
try
{
if (usbDevice == null)
{
return -1;
}
usbConnection = usbManager.openDevice(usbDevice);
if(usbConnection == null)
{
return -1;
}
if(usbInterface == null)
{
return -2;
}
if(!usbConnection.claimInterface(usbInterface, true))
{
usbConnection.close();
usbConnection = null;
return -2;
}
for(int i = 0; i < usbInterface.getEndpointCount(); i++)
{
UsbEndpoint endPoint = usbInterface.getEndpoint(i);
switch(endPoint.getType())
{
case UsbConstants.USB_ENDPOINT_XFER_BULK:
case UsbConstants.USB_ENDPOINT_XFER_INT:
{
switch(endPoint.getDirection())
{
case UsbConstants.USB_DIR_OUT:
{
epOut = endPoint;
break;
}
case UsbConstants.USB_DIR_IN:
{
epIn = endPoint;
break;
}
}
break;
}
case UsbConstants.USB_ENDPOINT_XFER_CONTROL:
{
epOut = endPoint;
epIn = endPoint;
break;
}
}
}
SendBlockSize = epOut.getMaxPacketSize();
RecvBlockSize = epIn.getMaxPacketSize();
return SendBlockSize;
}
catch(Exception ex)
{
ex.printStackTrace();
return -99;
}
}
public static long UsbWrite(byte[] buffer, int len, int timeout)
{
try
{
return usbConnection.bulkTransfer(epOut, buffer, SendBlockSize, timeout);
}
catch(Exception ex)
{
ex.printStackTrace();
return -99;
}
}
public static long UsbRead(byte[] buffer, int timeout)
{
/*
try
{
Thread.sleep(10);
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
*/
try
{
return usbConnection.bulkTransfer(epIn, buffer, RecvBlockSize, timeout);
}
catch(Exception ex)
{
ex.printStackTrace();
return -99;
}
}
public static long UsbCtrlWrite(byte[] buffer, int len, int timeout)
{
try
{
return usbConnection.controlTransfer(0x21, 0x09, 0x0200, 0x0000, buffer, SendBlockSize, timeout);
}
catch(Exception ex)
{
ex.printStackTrace();
return -99;
}
}
public static long UsbCtrlRead(byte[] buffer, int timeout)
{
try
{
try
{
Thread.sleep(10);
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
return usbConnection.controlTransfer(0xA1, 0x01, 0x0100, 0x0000, buffer, RecvBlockSize, timeout);
}
catch(Exception ex)
{
ex.printStackTrace();
return -99;
}
}
public static void UsbClosePort()
{
try
{
usbConnection.releaseInterface(usbInterface);
usbConnection.close();
usbConnection = null;
}
catch(Exception ex)
{
ex.printStackTrace();
}
}
}

Binary file not shown.

View File

@@ -0,0 +1,201 @@
2.3.1 基本接口
2.3.1.1 打开设备
函数名称 打开设备
函数声明 long OpenDevice(char *PortType, char *PortPara, char *ExtendPara);
功能描述 与设备建立通讯连接,返回设备句柄。
参数说明 序号 参数 输入/输出 类型 含义
1 PortType IN 字符串 端口类型(详见参数补充说明)
2 PortPara IN 字符串 端口参数(详见参数补充说明)
3 ExtendPara IN 字符串 扩展参数(详见参数补充说明)
返回值 大于0表示成功数值为设备句柄其他为失败。
参数补充说明:
PortType PortPara ExtendPara
串口 COMn 或 1~1000 波特率9600、115200 串口扩展盒参数1B2541
USB USBn 或 1001~2000 VID_PID261A0011、261A0012 中断传输为MI 控制传输为MC
网口 SKT IP:端口192.168.0.1:8080
蓝牙 BTH 蓝牙名称SS728M801
自动 AUTO
注意事项:
1、COMn和USBn中n∈1~1000即COM1~COM1000(1~1000)和USB1~USB1000(1001~2000)
2、Linux/Android下串口传参为COM+串口文件路径例如COM/dev/ttyS4
3、端口类型为AUTO时是指各参数通过CommonInterface.ini配置文件获取用法详见附录C方案二。
神思USB读卡器的VID_PID对照表请参见附录B
2.3.1.2 关闭设备
函数名称 关闭设备
函数声明 long CloseDevice();
功能描述 断开与设备的通讯连接。
参数说明 序号 参数 输入/输出 类型 含义
返回值 0表示成功非0表示失败。
2.3.1.3 设置当前设备(多设备操作)
函数名称 设置当前设备
函数声明 long SetCurrentDevice(long DevHandle);
功能描述 一台PC连接多台读卡器时通过设备句柄设置接下来要操作的设备。
参数说明 序号 参数 输入/输出 类型 含义
1 PortHandle IN 长整型 设备句柄
返回值 0表示成功非0表示失败。
2.3.1.4 获取当前设备(多设备操作)
函数名称 获取当前设备
函数声明 long GetCurrentDevice();
功能描述 一台PC连接多台读卡器时获取当前操作的设备句柄。
参数说明 序号 参数 输入/输出 类型 含义
返回值 返回当前设备的句柄。
2.3.1.5 获取接口库信息
函数名称 获取接口库信息
函数声明 long GetLibraryInfo(char *Version, char * Description);
功能描述 获取当前已加载的接口库详细信息。
参数说明 序号 参数 输入/输出 类型 含义
1 Version OUT 字符串 接口库版本
2 Description OUT 字符串 接口库描述
返回值 0表示成功非0表示失败。
2.3.1.6 获取设备型号
函数名称 获取设备型号
函数声明 long TerminalGetModel(char *TerminalModel);
功能描述 获取读卡器的型号。
参数说明 序号 参数 输入/输出 类型 含义
1 TerminalModel OUT 字符串 设备型号
返回值 0表示成功非0表示失败。
2.3.1.7 设备轮询心跳
函数名称 设备轮询心跳
函数声明 long TerminalHeartBeat();
功能描述 与设备进行握手通讯,用于检测与读卡器是否已建立连接并且通讯正常。
参数说明 序号 参数 输入/输出 类型 含义
返回值 0表示成功非0表示失败。
2.3.1.8 获取接收数据
函数名称 获取接收数据
函数声明 long GetLastRecvData(unsigned char *LastRecvData);
功能描述 获取最后一次通讯收到的数据,一般用于获取读卡器协议层错误信息。
参数说明 序号 参数 输入/输出 类型 含义
1 LastRecvData OUT 字节数组 返回最后一次通讯收到的数据
返回值 返回收到的数据长度。
2.3.1.9 获取固件版本
函数名称 获取设备固件版本
函数声明 long TerminalGetFirmVersion(char *FirmVersion, char *HardwareVersion);
功能描述 获取读卡器的固件版本号。
参数说明 序号 参数 输入/输出 类型 含义
1 FirmVersion OUT 字符串 设备固件版本号
2 HardwareVersion OUT 字符串 设备硬件版本号
返回值 0表示成功非0表示失败。
2.3.1.10 获取设备序列号
函数名称 获取设备序列号
函数声明 long TerminalGetSn(char *TerminalSn);
功能描述 获取读卡器的固件版本号。
参数说明 序号 参数 输入/输出 类型 含义
1 TerminalSn OUT 字符串 设备序列号
返回值 0表示成功非0表示失败。
2.3.2 二代证接口(二代证/外国人/港澳台)
2.3.2.1 读取二代证
函数名称 读取二代证
函数声明 long IdReadCard(unsigned char CardType, unsigned char InfoEncoding, char *IdCardInfo,
long TimeOutMs);
long SdtReadCard(unsigned char CardType, unsigned char InfoEncoding, char *IdCardInfo,
long TimeOutMs);GA467协议
功能描述 读取第二代居民身份证或外国人永久居留证或港澳台居民居住证
参数说明 序号 参数 输入/输出 类型 含义
1 CardType IN 字节 读取卡类型
0x00 :读取二代证或外国人或港澳台
0x01 :只读二代证
0x02 :只读外国人
0x03 :只读港澳台
以上参数不含指纹信息
以下参数包含指纹信息
0x10 :读取二代证或外国人或港澳台 (含指纹)
0x11 :只读二代证(含指纹)
0x12 :只读外国人(含指纹)
0x13 :只读港澳台(含指纹)
2 InfoEncoding IN 字节 返回信息的编码方式
0x01 GB18030编码GBK
0x02 UTF16-LE编码
0x03 UTF-8编码
3 IdCardInfo OUT 字符串 读取到的二代证/外国人/港澳台信息
至少分配10240字节的内存
4 TimeOutMs IN 长整型 读卡超时时间,单位为毫秒
= 0 :不等待,无卡立即返回
> 0 :等待放卡,指定时间内等待放卡
返回值 0表示成功非0表示失败。
读卡返回信息IdCardInfo格式为以英文冒号分割的信息项具体如下
证件类型:中文姓名:英文姓名:性别:性别代码:民族:民族代码:出生日期:住址:身份证号码:签发机关:发卡日期:卡有效期:证件版本号:头像JPG照片base64编码:指纹特征值base64编码
以上信息为二代证、外国人、港澳台信息项的并集,如果当前类型的证件中不存在该项信息,则该项为空,具体证件中包含的信息项如下:
序号 信息项 二代证 外国人 港澳台 自动解析函数
0 证件类型 A I或Y J IdCardGetTypeFlag
1 中文姓名 姓名 中文姓名 姓名 IdCardGetName
2 英文姓名 英文姓名 IdCardGetNameEn
3 性别 性别 性别 性别 IdCardGetGender
4 性别代码 性别代码 性别代码 性别代码 IdCardGetGenderId
5 民族 民族 国籍或所在地区 IdCardGetNation
6 民族代码 民族代码 国籍或所在地区代码 通行证号码 IdCardGetNationId
7 出生日期 出生日期 出生日期 出生日期 IdCardGetBirthDate
8 住址 住址 /永久居留证号码关联项 住址 IdCardGetAddress
9 身份证号码 公民身份号码 永久居留证号码/证件号码 公民身份号码 IdCardGetIdNumber
10 签发机关 签发机关 当次申请受理机关代码 签发机关 IdCardGetSignOrgan
11 发卡日期 有效期
起始日期 证件签发日期 有效期
起始日期 IdCardGetBeginTerm
12 卡有效期 有效期
截止日期 证件终止日期 有效期
截止日期 IdCardGetValidTerm
13 证件版本号 证件版本号/换证次数 签发次数 IdCardGetVersion
14 头像JPG照片base64编码 头像照片base64编码 头像照片base64编码 头像照片base64编码
15 指纹特征值base64编码 指纹特征值base64编码 指纹特征值base64编码
各信息项建议通过拆分IdCardInfo字符串得到不建议使用以下函数获取
long IdCardGetName(char *Name);
long IdCardGetNameEn(char *NameEn);
long IdCardGetGender(char *Gender);
long IdCardGetGenderId(char *GenderId);
long IdCardGetNation(char *Nation);
long IdCardGetNationId(char *NationId);
long IdCardGetBirthDate(char *BirthDate);
long IdCardGetAddress(char *Address);
long IdCardGetIdNumber(char *IdNumber);
long IdCardGetSignOrgan(char *SignOrgan);
long IdCardGetBeginTerm(char *BeginTerm);
long IdCardGetValidTerm(char *ValidTerm);
long IdCardGetTypeFlag(char *TypeFlag);
long IdCardGetVersion(char *Version);
long IdCardGetFPBuffer(unsigned char *FPBuffer, long *FPBufferLen);//返回1024字节指纹信息两个手指
long IdCardGetPhotoFile(char *PhotoFile);//入参PhotoFile为生成头像文件的全路径支持扩展名wlt/bmp/jpg
long IdCardGetPhotoBuffer(unsigned char WltBmpJpg, unsigned char *PhotoBuffer, long *PhotoBufferLen);
WltBmpJpg入参0x01 wlt格式 0x02 bmp格式 0x03 jpg格式
//获取二代证原始信息
long IdCardGetRawInfo(unsigned char *CHMsg, long *CHMsgLen, //文字信息
unsigned char *PHMsg, long *PHMsgLen, //照片信息
unsigned char *FPMsg, long *FPMsgLen); //指纹信息
2.3.2.2 读取追加住址
函数名称 读取追加住址
函数声明 long IdReadNewAddress(char *NewAddress);
long SdtReadNewAddress(char *NewAddress); GA467协议
功能描述 读取二代证的追加住址信息
参数说明 序号 参数 输入/输出 类型 含义
1 NewAddress OUT 字符串 返回读取到的追加住址信息
返回值 0表示成功非0表示失败。
2.3.2.3 获取SAM模块状态
函数名称 获取SAM模块状态
函数声明 long SamGetStatus();
long SdtSamGetStatus();GA467协议
功能描述 获取公安部SAM安全模块状态。
参数说明 序号 参数 输入/输出 类型 含义
返回值 0表示成功非0表示失败。
2.3.2.4 获取SAM模块编号字符串
函数名称 获取SAM模块编号字符串
函数声明 long SamGetIdStr(char *SamIdStr);
long SdtSamGetIdStr(char *SamIdStr); GA467协议
功能描述 获取公安部SAM安全模块编号字符串。内部将编号转换为字符串
参数说明 序号 参数 输入/输出 类型 含义
1 SamIdStr OUT 字符串 SAM模块编号字符串
返回值 0表示成功非0表示失败。

3
CHANGELOG.md Normal file
View File

@@ -0,0 +1,3 @@
## 0.0.1
* TODO: Describe initial release.

1
LICENSE Normal file
View File

@@ -0,0 +1 @@
TODO: Add your license here.

360
README.md Normal file
View File

@@ -0,0 +1,360 @@
# 身份证读卡器 Flutter 插件
一个用于读取中国二代身份证的 Flutter 插件集成了01SDK身份证识别功能支持USB和蓝牙连接方式。
## 功能特性
- ✅ 支持二代身份证读取
- ✅ 获取完整的身份证信息(姓名、性别、民族、出生日期、地址、身份证号、签发机关、有效期)
- ✅ 支持读取身份证照片
- ✅ 支持USB连接读卡器
- ✅ 支持蓝牙连接读卡器
- ✅ 完整的错误处理和状态管理
- ✅ 简单易用的API接口
## 支持平台
- ✅ Android
- ❌ iOS暂不支持
## 安装
`pubspec.yaml` 文件中添加依赖:
```yaml
dependencies:
idcard: ^0.0.1
```
然后运行:
```bash
flutter pub get
```
## Android 配置
### 权限配置
`android/app/src/main/AndroidManifest.xml` 中添加必要权限:
```xml
<!-- USB权限 -->
<uses-permission android:name="android.permission.USB_PERMISSION" />
<uses-feature android:name="android.hardware.usb.host" android:required="false" />
<!-- Android 12+ USB权限 -->
<uses-permission android:name="android.permission.MANAGE_USB" android:maxSdkVersion="30" />
<!-- 蓝牙权限 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- Android 12+ 蓝牙权限 -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
```
### SDK版本配置
确保 `minSdkVersion` 至少为 21`targetSdkVersion` 为 34
```gradle
android {
compileSdk 34
defaultConfig {
minSdkVersion 21
targetSdkVersion 34
}
}
```
### Android 12+ 兼容性
本插件已针对 Android 12+ 进行了优化,包括:
- **PendingIntent 兼容性**:自动处理 Android 12+ 的 `FLAG_IMMUTABLE` 要求
- **USB 权限优化**:改进了权限请求流程,增加了超时处理
- **错误处理增强**:提供更详细的错误信息和用户指导
## 使用方法
### 基本用法
```dart
import 'package:idcard/idcard.dart';
class IdCardReader {
final _idcardPlugin = Idcard();
/// 连接设备并读取身份证
Future<void> readIdCard() async {
try {
// 1. 获取USB权限
int permissionResult = await _idcardPlugin.getUsbPermission();
if (permissionResult != 0) {
print('获取USB权限失败错误码$permissionResult');
return;
}
// 2. 打开设备
int openResult = await _idcardPlugin.openDevice();
if (openResult <= 0) {
print('打开设备失败,错误码:$openResult');
return;
}
print('设备打开成功,句柄:$openResult');
// 3. 读取身份证信息(完整流程)
IdCardInfo cardInfo = await _idcardPlugin.readCardComplete();
print('姓名:${cardInfo.name}');
print('身份证号:${cardInfo.idNumber}');
print('性别:${cardInfo.gender}');
// ... 其他信息
// 4. 关闭设备
await _idcardPlugin.closeDevice();
} catch (e) {
print('读卡失败:$e');
}
}
}
```
### 高级用法
#### 分步骤读取
```dart
// 手动控制每个步骤
try {
// 寻卡
int findResult = await _idcardPlugin.findCard();
if (findResult <= 0) {
throw Exception('寻卡失败,错误码:$findResult');
}
// 选卡
int selectResult = await _idcardPlugin.selectCard();
if (selectResult <= 0) {
throw Exception('选卡失败,错误码:$selectResult');
}
// 读取详细信息
IdCardInfo cardInfo = await _idcardPlugin.readCardInfo();
print('读取成功:${cardInfo.toString()}');
} catch (e) {
print('读卡过程出错:$e');
}
```
#### 蓝牙连接
```dart
// 使用蓝牙连接
int result = await _idcardPlugin.openDevice(
portType: 'BLUETOOTH',
portPara: 'BLUETOOTH_DEVICE_NAME', // 蓝牙设备名称
);
```
#### 读取原始数据
```dart
// 读取原始二进制数据
Map<String, dynamic> result = await _idcardPlugin.readCard(
cardType: 1,
infoEncoding: 0,
timeOut: 30000,
);
if (result['result'] > 0) {
List<int> rawData = result['data'];
// 处理原始数据
}
```
## 返回值说明
根据神思标准化接口规范,所有设备操作方法的返回值遵循以下规则:
- **大于0**:表示操作成功
- 对于 `openDevice()`:返回值是设备句柄,用于后续操作
- 对于其他方法:返回值表示操作成功的状态码
- **小于等于0**:表示操作失败,返回值为错误码
- 具体错误码含义请参考设备厂商提供的接口文档
### 重要提醒
在判断操作是否成功时,请使用 `> 0` 而不是 `== 0` 来判断成功状态:
```dart
// ✅ 正确的判断方式
int result = await _idcardPlugin.openDevice();
if (result > 0) {
print('设备打开成功,句柄:$result');
} else {
print('设备打开失败,错误码:$result');
}
// ❌ 错误的判断方式
if (result == 0) {
// 这样判断是错误的
}
```
## API 参考
### Idcard 类
#### 方法
| 方法 | 描述 | 参数 | 返回值 |
|------|------|------|--------|
| `getPlatformVersion()` | 获取平台版本 | 无 | `Future<String?>` |
| `getUsbPermission({int vid, int pid})` | 获取USB权限 | vid: 厂商ID, pid: 产品ID | `Future<int>` |
| `openDevice({String portType, String portPara, String extendPara})` | 打开设备 | portType: 端口类型, portPara: 端口参数, extendPara: 扩展参数 | `Future<int>` - 大于0表示成功设备句柄小于等于0表示失败 |
| `closeDevice()` | 关闭设备 | 无 | `Future<int>` - 大于0表示成功小于等于0表示失败 |
| `findCard()` | 寻卡 | 无 | `Future<int>` - 大于0表示成功小于等于0表示失败 |
| `selectCard()` | 选卡 | 无 | `Future<int>` - 大于0表示成功小于等于0表示失败 |
| `readCard({int cardType, int infoEncoding, int timeOut})` | 读取原始数据 | cardType: 卡片类型, infoEncoding: 编码, timeOut: 超时时间 | `Future<Map<String, dynamic>>` |
| `readCardInfo()` | 读取详细信息 | 无 | `Future<IdCardInfo>` |
| `readCardComplete({int timeOut})` | 完整读卡流程 | timeOut: 超时时间 | `Future<IdCardInfo>` |
| `isDeviceConnected()` | 检查设备连接状态 | 无 | `Future<bool>` |
### IdCardInfo 类
身份证信息数据模型:
```dart
class IdCardInfo {
final String name; // 姓名
final String gender; // 性别
final String nation; // 民族
final String birthDate; // 出生日期
final String address; // 地址
final String idNumber; // 身份证号
final String signOrgan; // 签发机关
final String validTerm; // 有效期
final List<int>? photo; // 照片数据JPEG格式
}
```
## 错误码说明
| 错误码 | 说明 |
|--------|------|
| 0 | 成功 |
| -1 | 设备未找到 |
| -2 | 设备接口错误 |
| -3 | 权限被拒绝 |
| 其他 | 具体错误码请参考01SDK文档 |
## 示例应用
查看 `example` 目录中的完整示例应用,演示了如何使用本插件的所有功能。
运行示例:
```bash
cd example
flutter run
```
## 故障排除
### Android 12+ USB权限问题
如果在Android 12或更高版本上遇到"获取USB权限失败"的问题,请尝试以下解决方案:
#### 1. 检查权限配置
确保在 `AndroidManifest.xml` 中正确配置了所有必要权限:
```xml
<uses-permission android:name="android.permission.USB_PERMISSION" />
<uses-permission android:name="android.permission.MANAGE_USB" android:maxSdkVersion="30" />
<uses-feature android:name="android.hardware.usb.host" android:required="false" />
```
#### 2. 手动授权USB权限
1. 进入 **设置** > **应用管理** > **您的应用**
2. 点击 **权限管理**
3. 找到并开启 **USB** 相关权限
4. 重新启动应用
#### 3. 开发者选项设置
1. 进入 **设置** > **开发者选项**
2. 开启 **USB调试**
3. 开启 **USB安装** (如果有)
4. 关闭 **监控ADB安装应用** (如果有)
#### 4. 设备连接顺序
1. 先启动应用
2. 再连接USB设备
3. 在权限对话框中点击"允许"
4. 调用 `getUsbPermission()` 方法
#### 5. 检查设备兼容性
确保您的身份证读卡器设备:
- 支持USB HID协议
- 与Android系统兼容
- VID/PID参数正确
### 常见错误码
| 错误码 | 含义 | 解决方案 |
|--------|------|----------|
| -1 | 设备未找到 | 检查设备连接和VID/PID |
| -2 | 接口错误 | 检查设备驱动和兼容性 |
| -3 | 权限被拒绝 | 手动授权USB权限 |
| -99 | 未知错误 | 重启应用或设备 |
### 其他常见问题
#### Q: 设备连接失败怎么办?
A: 请检查:
1. 设备是否正确连接
2. USB权限是否已授权
3. 设备驱动是否正常
4. VID/PID参数是否正确
#### Q: 读取身份证失败?
A: 请确保:
1. 身份证放置正确
2. 身份证芯片完好
3. 设备工作正常
4. 按正确顺序调用API
#### Q: 在模拟器上无法使用?
A: 身份证读卡功能需要物理USB设备模拟器不支持。请在真机上测试。
### Q: 照片数据如何显示?
A: 照片数据是JPEG格式的字节数组可以使用 `Image.memory()` 显示:
```dart
if (cardInfo.photo != null) {
Image.memory(Uint8List.fromList(cardInfo.photo!))
}
```
## 许可证
本项目采用 MIT 许可证。详见 [LICENSE](LICENSE) 文件。
## 贡献
欢迎提交 Issue 和 Pull Request
## 更新日志
### 0.0.1
- 初始版本
- 支持基本的身份证读取功能
- 集成01SDK
- 支持USB和蓝牙连接

4
analysis_options.yaml Normal file
View File

@@ -0,0 +1,4 @@
include: package:flutter_lints/flutter.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

9
android/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.cxx

55
android/build.gradle Normal file
View File

@@ -0,0 +1,55 @@
group = "com.example.idcard"
version = "1.0"
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:7.3.0")
}
}
rootProject.allprojects {
repositories {
google()
mavenCentral()
}
}
apply plugin: "com.android.library"
android {
if (project.android.hasProperty("namespace")) {
namespace = "com.example.idcard"
}
compileSdk = 34
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
defaultConfig {
minSdk = 21
targetSdk = 34
}
dependencies {
testImplementation("junit:junit:4.13.2")
testImplementation("org.mockito:mockito-core:5.0.0")
}
testOptions {
unitTests.all {
testLogging {
events "passed", "skipped", "failed", "standardOut", "standardError"
outputs.upToDateWhen {false}
showStandardStreams = true
}
}
}
}

1
android/settings.gradle Normal file
View File

@@ -0,0 +1 @@
rootProject.name = 'idcard'

View File

@@ -0,0 +1,32 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.idcard">
<!-- USB权限 -->
<uses-permission android:name="android.permission.USB_PERMISSION" />
<uses-feature android:name="android.hardware.usb.host" android:required="false" />
<!-- Android 12+ USB权限 -->
<uses-permission android:name="android.permission.MANAGE_USB" android:maxSdkVersion="30" />
<application>
<!-- USB设备过滤器 -->
<activity android:name=".UsbPermissionActivity" android:exported="false">
<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>
<!-- 蓝牙权限 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- Android 12+ 蓝牙权限 -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
</manifest>

View File

@@ -0,0 +1,642 @@
package com.example.idcard;
import androidx.annotation.NonNull;
import android.content.Context;
import android.util.Log;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import com.sdses.JniCommonInterface;
import java.util.HashMap;
import java.util.Map;
/**
* IdcardPlugin - Flutter插件用于身份证识别功能
* 集成了01SDK的身份证读取功能
*/
public class IdcardPlugin implements FlutterPlugin, MethodCallHandler {
private static final String TAG = "IdcardPlugin";
/// 与Flutter通信的方法通道
private MethodChannel channel;
private Context context;
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "idcard");
channel.setMethodCallHandler(this);
context = flutterPluginBinding.getApplicationContext();
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
try {
switch (call.method) {
case "getPlatformVersion":
result.success("Android " + android.os.Build.VERSION.RELEASE);
break;
case "openDevice":
handleOpenDevice(call, result);
break;
case "setCurrentDevice":
handleSetCurrentDevice(call, result);
break;
case "getCurrentDevice":
handleGetCurrentDevice(result);
break;
case "getLibraryInfo":
handleGetLibraryInfo(result);
break;
case "getTerminalModel":
handleGetTerminalModel(result);
break;
case "terminalHeartBeat":
handleTerminalHeartBeat(result);
break;
case "getLastRecvData":
handleGetLastRecvData(result);
break;
case "getTerminalFirmVersion":
handleGetTerminalFirmVersion(result);
break;
case "getTerminalSn":
handleGetTerminalSn(result);
break;
case "closeDevice":
handleCloseDevice(result);
break;
case "findCard":
handleFindCard(result);
break;
case "selectCard":
handleSelectCard(result);
break;
case "idReadCard":
handleIdReadCard(call, result);
break;
case "readCard":
handleReadCard(call, result);
break;
case "readCardInfo":
handleReadCardInfo(result);
break;
case "readNewAddress":
handleReadNewAddress(result);
break;
case "getSamStatus":
handleGetSamStatus(result);
break;
case "getSamIdStr":
handleGetSamIdStr(result);
break;
case "getUsbPermission":
handleGetUsbPermission(call, result);
break;
default:
result.notImplemented();
break;
}
} catch (Exception e) {
Log.e(TAG, "Error in method call: " + call.method, e);
result.error("ERROR", "Method call failed: " + e.getMessage(), null);
}
}
/**
* 获取USB权限
*/
private void handleGetUsbPermission(MethodCall call, Result result) {
Integer vid = call.argument("vid");
Integer pid = call.argument("pid");
if (vid == null || pid == null) {
result.error("INVALID_PARAMS", "VID and PID are required", null);
return;
}
try {
long ret = JniCommonInterface.GetUsbPermission(context, vid, pid);
// 根据返回值提供更详细的错误信息
switch ((int)ret) {
case 0:
result.success(ret);
break;
case -1:
result.error("DEVICE_NOT_FOUND", "未找到指定的USB设备 (VID:" + vid + ", PID:" + pid + ")", null);
break;
case -2:
result.error("INTERFACE_ERROR", "USB设备接口错误", null);
break;
case -3:
result.error("PERMISSION_DENIED", "USB权限请求被拒绝或超时请在系统设置中手动授权USB权限", null);
break;
case -99:
result.error("UNKNOWN_ERROR", "获取USB权限时发生未知错误", null);
break;
default:
result.error("ERROR", "获取USB权限失败错误码: " + ret, null);
break;
}
} catch (Exception e) {
result.error("EXCEPTION", "获取USB权限时发生异常: " + e.getMessage(), null);
}
}
/**
* 打开设备
*/
private void handleOpenDevice(MethodCall call, Result result) {
try {
String portType = call.argument("portType");
String portPara = call.argument("portPara");
String extendPara = call.argument("extendPara");
if (portType == null) portType = "USB";
if (portPara == null) portPara = "";
if (extendPara == null) extendPara = "";
long deviceHandle = JniCommonInterface.OpenDevice(portType, portPara, extendPara);
// 根据接口文档返回值大于0表示成功设备句柄小于等于0表示失败
if (deviceHandle > 0) {
result.success(deviceHandle);
} else {
result.error("OPEN_DEVICE_FAILED", "Failed to open device, error code: " + deviceHandle, null);
}
} catch (Exception e) {
Log.e(TAG, "Error opening device", e);
result.error("ERROR", "Failed to open device: " + e.getMessage(), null);
}
}
/**
* 关闭设备
*/
private void handleCloseDevice(Result result) {
try {
long closeResult = JniCommonInterface.CloseDevice();
// 根据接口文档返回值0表示成功非0表示失败
if (closeResult == 0) {
result.success(closeResult);
} else {
result.error("CLOSE_DEVICE_FAILED", "Failed to close device, error code: " + closeResult, null);
}
} catch (Exception e) {
Log.e(TAG, "Error closing device", e);
result.error("ERROR", "Failed to close device: " + e.getMessage(), null);
}
}
/**
* 寻卡
*/
private void handleFindCard(Result result) {
try {
long findResult = JniCommonInterface.IdFindCard();
if (findResult == 0) {
result.success(findResult);
} else {
result.error("FIND_CARD_FAILED", "Failed to find card, error code: " + findResult, null);
}
} catch (Exception e) {
Log.e(TAG, "Error finding card", e);
result.error("ERROR", "Failed to find card: " + e.getMessage(), null);
}
}
/**
* 选卡
*/
private void handleSelectCard(Result result) {
try {
long selectResult = JniCommonInterface.IdSelectCard();
if (selectResult == 0) {
result.success(selectResult);
} else {
result.error("SELECT_CARD_FAILED", "Failed to select card, error code: " + selectResult, null);
}
} catch (Exception e) {
Log.e(TAG, "Error selecting card", e);
result.error("ERROR", "Failed to select card: " + e.getMessage(), null);
}
}
/**
* 读取身份证信息
*/
private void handleReadCard(MethodCall call, Result result) {
Integer cardType = call.argument("cardType");
Integer infoEncoding = call.argument("infoEncoding");
Integer timeOut = call.argument("timeOut");
if (cardType == null) cardType = 1;
if (infoEncoding == null) infoEncoding = 0;
if (timeOut == null) timeOut = 30000;
byte[] idCardInfo = new byte[4096];
long ret = JniCommonInterface.IdReadCard((byte)cardType.intValue(),
(byte)infoEncoding.intValue(),
idCardInfo,
timeOut);
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("result", ret);
// 根据接口文档返回值0表示成功非0表示失败
if (ret == 0) {
resultMap.put("data", idCardInfo);
}
result.success(resultMap);
}
/**
* 读取身份证详细信息
*/
private void handleReadCardInfo(Result result) {
try {
// 读取基本信息和照片
byte[] chMsg = new byte[256];
long[] chMsgLen = new long[1];
byte[] phMsg = new byte[1024];
long[] phMsgLen = new long[1];
long ret = JniCommonInterface.IdReadBaseMsg(chMsg, chMsgLen, phMsg, phMsgLen);
if (ret != 0) {
result.error("READ_failed", "Failed to read card info, error code: " + ret, null);
return;
}
// 解析身份证信息
Map<String, Object> cardInfo = new HashMap<>();
// 获取各个字段
byte[] name = new byte[32];
byte[] gender = new byte[4];
byte[] nation = new byte[32];
byte[] birthDate = new byte[16];
byte[] address = new byte[128];
byte[] idNumber = new byte[32];
byte[] signOrgan = new byte[64];
byte[] validTerm = new byte[32];
JniCommonInterface.IdCardGetName(name);
JniCommonInterface.IdCardGetGender(gender);
JniCommonInterface.IdCardGetNation(nation);
JniCommonInterface.IdCardGetBirthDate(birthDate);
JniCommonInterface.IdCardGetAddress(address);
JniCommonInterface.IdCardGetIdNumber(idNumber);
JniCommonInterface.IdCardGetSignOrgan(signOrgan);
JniCommonInterface.IdCardGetValidTerm(validTerm);
// 获取照片
byte[] photoBuffer = new byte[10240];
long[] photoBufferLen = new long[1];
JniCommonInterface.IdCardGetPhotoBuffer((byte)1, photoBuffer, photoBufferLen);
cardInfo.put("name", new String(name, "GBK").trim());
cardInfo.put("gender", new String(gender, "GBK").trim());
cardInfo.put("nation", new String(nation, "GBK").trim());
cardInfo.put("birthDate", new String(birthDate, "GBK").trim());
cardInfo.put("address", new String(address, "GBK").trim());
cardInfo.put("idNumber", new String(idNumber, "GBK").trim());
cardInfo.put("signOrgan", new String(signOrgan, "GBK").trim());
cardInfo.put("validTerm", new String(validTerm, "GBK").trim());
if (photoBufferLen[0] > 0) {
byte[] photo = new byte[(int)photoBufferLen[0]];
System.arraycopy(photoBuffer, 0, photo, 0, (int)photoBufferLen[0]);
cardInfo.put("photo", photo);
}
result.success(cardInfo);
} catch (Exception e) {
Log.e(TAG, "Error reading card info", e);
result.error("ERROR", "Failed to read card2 info: " + e.getMessage(), null);
}
}
/**
* 设置当前设备(多设备操作)
*/
private void handleSetCurrentDevice(MethodCall call, Result result) {
try {
Integer devHandle = call.argument("devHandle");
if (devHandle == null) {
result.error("INVALID_PARAMS", "Device handle is required", null);
return;
}
long ret = JniCommonInterface.SetCurrentDevice(devHandle);
// 根据接口文档返回值0表示成功非0表示失败
if (ret == 0) {
result.success(ret);
} else {
result.error("SET_CURRENT_DEVICE_FAILED", "Failed to set current device, error code: " + ret, null);
}
} catch (Exception e) {
Log.e(TAG, "Error setting current device", e);
result.error("ERROR", "Failed to set current device: " + e.getMessage(), null);
}
}
/**
* 获取当前设备(多设备操作)
*/
private void handleGetCurrentDevice(Result result) {
try {
long currentDevice = JniCommonInterface.GetCurrentDevice();
result.success(currentDevice);
} catch (Exception e) {
Log.e(TAG, "Error getting current device", e);
result.error("ERROR", "Failed to get current device: " + e.getMessage(), null);
}
}
/**
* 获取接口库信息
*/
private void handleGetLibraryInfo(Result result) {
try {
byte[] version = new byte[64];
byte[] description = new byte[256];
long ret = JniCommonInterface.GetLibraryInfo(version, description);
// 根据接口文档返回值0表示成功非0表示失败
if (ret == 0) {
Map<String, String> info = new HashMap<>();
info.put("version", new String(version, "GBK").trim());
info.put("description", new String(description, "GBK").trim());
result.success(info);
} else {
result.error("GET_LIBRARY_INFO_FAILED", "Failed to get library info, error code: " + ret, null);
}
} catch (Exception e) {
Log.e(TAG, "Error getting library info", e);
result.error("ERROR", "Failed to get library info: " + e.getMessage(), null);
}
}
/**
* 获取设备型号
*/
private void handleGetTerminalModel(Result result) {
try {
byte[] model = new byte[64];
long ret = JniCommonInterface.GetTerminalModel(model);
// 根据接口文档返回值0表示成功非0表示失败
if (ret == 0) {
String modelStr = new String(model, "GBK").trim();
result.success(modelStr);
} else {
result.error("GET_TERMINAL_MODEL_FAILED", "Failed to get terminal model, error code: " + ret, null);
}
} catch (Exception e) {
Log.e(TAG, "Error getting terminal model", e);
result.error("ERROR", "Failed to get terminal model: " + e.getMessage(), null);
}
}
/**
* 设备轮询心跳
*/
private void handleTerminalHeartBeat(Result result) {
try {
long ret = JniCommonInterface.TerminalHeartBeat();
// 根据接口文档返回值0表示成功非0表示失败
if (ret == 0) {
result.success(ret);
} else {
result.error("TERMINAL_HEARTBEAT_FAILED", "Terminal heartbeat failed, error code: " + ret, null);
}
} catch (Exception e) {
Log.e(TAG, "Error in terminal heart beat", e);
result.error("ERROR", "Failed to perform terminal heart beat: " + e.getMessage(), null);
}
}
/**
* 获取接收数据
*/
private void handleGetLastRecvData(Result result) {
try {
byte[] data = new byte[1024];
long[] dataLen = new long[1];
JniCommonInterface.GetLastRecvData(data, dataLen);
if (dataLen[0] > 0) {
byte[] actualData = new byte[(int)dataLen[0]];
System.arraycopy(data, 0, actualData, 0, (int)dataLen[0]);
result.success(actualData);
} else {
result.success(new byte[0]);
}
} catch (Exception e) {
Log.e(TAG, "Error getting last recv data", e);
result.error("ERROR", "Failed to get last recv data: " + e.getMessage(), null);
}
}
/**
* 获取设备固件版本
*/
private void handleGetTerminalFirmVersion(Result result) {
try {
byte[] firmVersion = new byte[64];
byte[] hardwareVersion = new byte[64];
long ret = JniCommonInterface.GetTerminalFirmVersion(firmVersion, hardwareVersion);
// 根据接口文档返回值0表示成功非0表示失败
if (ret == 0) {
Map<String, String> versionInfo = new HashMap<>();
versionInfo.put("firmVersion", new String(firmVersion, "GBK").trim());
versionInfo.put("hardwareVersion", new String(hardwareVersion, "GBK").trim());
result.success(versionInfo);
} else {
result.error("GET_FIRM_VERSION_FAILED", "Failed to get terminal firm version, error code: " + ret, null);
}
} catch (Exception e) {
Log.e(TAG, "Error getting terminal firm version", e);
result.error("ERROR", "Failed to get terminal firm version: " + e.getMessage(), null);
}
}
/**
* 获取设备序列号
*/
private void handleGetTerminalSn(Result result) {
try {
byte[] sn = new byte[64];
long ret = JniCommonInterface.GetTerminalSn(sn);
// 根据接口文档返回值0表示成功非0表示失败
if (ret == 0) {
String snStr = new String(sn, "GBK").trim();
result.success(snStr);
} else {
result.error("GET_TERMINAL_SN_FAILED", "Failed to get terminal SN, error code: " + ret, null);
}
} catch (Exception e) {
Log.e(TAG, "Error getting terminal SN", e);
result.error("ERROR", "Failed to get terminal SN: " + e.getMessage(), null);
}
}
/**
* 读取身份证(新版接口)
*/
private void handleIdReadCard(MethodCall call, Result result) {
try {
Integer cardType = call.argument("cardType");
Integer infoEncoding = call.argument("infoEncoding");
Integer timeOut = call.argument("timeOut");
if (cardType == null) cardType = 0x00;
if (infoEncoding == null) infoEncoding = 0x01;
if (timeOut == null) timeOut = 30000;
byte[] idCardInfo = new byte[4096];
long ret = JniCommonInterface.IdReadCard((byte)cardType.intValue(),
(byte)infoEncoding.intValue(),
idCardInfo,
timeOut);
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("result", ret);
// 根据接口文档返回值0表示成功非0表示失败
if (ret == 0) {
// 将字节数组转换为字符串(根据编码方式)
String encoding = "GBK";
if (infoEncoding == 0x02) {
encoding = "UTF-16LE";
} else if (infoEncoding == 0x03) {
encoding = "UTF-8";
}
// 找到字节数组中第一个null字符的位置
int length = 0;
for (int i = 0; i < idCardInfo.length; i++) {
if (idCardInfo[i] == 0) {
length = i;
break;
}
}
// 如果没有找到null字符使用整个数组长度
if (length == 0) {
length = idCardInfo.length;
}
// 只转换有效的字节数据
String dataStr = new String(idCardInfo, 0, length, encoding).trim();
resultMap.put("data", dataStr);
Log.d(TAG, "IdReadCard success, data length: " + length + ", data: " + dataStr);
} else {
Log.e(TAG, "IdReadCard failed with error code: " + ret);
}
result.success(resultMap);
} catch (Exception e) {
Log.e(TAG, "Error in idReadCard", e);
result.error("ERROR", "Failed to read card: " + e.getMessage(), null);
}
}
/**
* 读取追加住址
*/
private void handleReadNewAddress(Result result) {
try {
byte[] address = new byte[256];
long ret = JniCommonInterface.IdReadNewAddress(address);
// 根据接口文档返回值0表示成功非0表示失败
if (ret == 0) {
String addressStr = new String(address, "GBK").trim();
result.success(addressStr);
} else {
result.error("READ_failed", "Failed to read new address, error code: " + ret, null);
}
} catch (Exception e) {
Log.e(TAG, "Error reading new address", e);
result.error("ERROR", "Failed to read new address: " + e.getMessage(), null);
}
}
/**
* 获取SAM模块状态
*/
private void handleGetSamStatus(Result result) {
try {
long status = JniCommonInterface.GetSamStatus();
// 根据接口文档返回值0表示成功非0表示失败
if (status == 0) {
result.success(0); // 成功时返回0
} else {
result.error("SAM_STATUS_ERROR", "SAM module status error, code: " + status, null);
}
} catch (Exception e) {
Log.e(TAG, "Error getting SAM status", e);
result.error("ERROR", "Failed to get SAM status: " + e.getMessage(), null);
}
}
/**
* 获取SAM模块编号字符串
*/
private void handleGetSamIdStr(Result result) {
try {
byte[] samId = new byte[64];
long ret = JniCommonInterface.GetSamIdStr(samId);
// 根据接口文档返回值0表示成功非0表示失败
if (ret == 0) {
String samIdStr = new String(samId, "GBK").trim();
result.success(samIdStr);
} else {
result.error("GET_SAM_ID_FAILED", "Failed to get SAM ID string, error code: " + ret, null);
}
} catch (Exception e) {
Log.e(TAG, "Error getting SAM ID string", e);
result.error("ERROR", "Failed to get SAM ID string: " + e.getMessage(), null);
}
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
channel.setMethodCallHandler(null);
}
}

View File

@@ -0,0 +1,40 @@
package com.example.idcard;
import android.app.Activity;
import android.content.Intent;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.util.Log;
/**
* USB权限处理Activity
* 用于处理Android 12+的USB权限请求
*/
public class UsbPermissionActivity extends Activity {
private static final String TAG = "UsbPermissionActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
String action = intent.getAction();
if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (device != null) {
Log.d(TAG, "USB设备已连接: " + device.getDeviceName());
Log.d(TAG, "VID: " + device.getVendorId() + ", PID: " + device.getProductId());
// 通知插件USB设备已连接
Intent broadcastIntent = new Intent("com.example.idcard.USB_DEVICE_ATTACHED");
broadcastIntent.putExtra(UsbManager.EXTRA_DEVICE, device);
sendBroadcast(broadcastIntent);
}
}
// 完成后关闭Activity
finish();
}
}

View File

@@ -0,0 +1,170 @@
package com.sdses;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Set;
import java.util.UUID;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
public class BlueTooth
{
private static UUID SS_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
private static BluetoothAdapter mAdapter = null;
private static BluetoothDevice mDevice = null;
private static BluetoothSocket mSocket = null;
private static InputStream mInputStream = null;
private static OutputStream mOutputStream = null;
public static long BthConnect(String BlueToothName)
{
mAdapter = BluetoothAdapter.getDefaultAdapter();
if (mAdapter == null)
{
return -1;
}
if (!mAdapter.isEnabled())
{
return -2;
/*
mAdapter.enable();
if (!mAdapter.isEnabled())
{
return -2;
}
*/
}
//获得已配对的蓝牙设备列表
Set<BluetoothDevice> pairedDevices = mAdapter.getBondedDevices();
if(pairedDevices.size() <= 0)
{
return -3;
}
ArrayList<BluetoothDevice> ALBluetoothDevice = new ArrayList<BluetoothDevice>();
for (BluetoothDevice device : pairedDevices)
{
ALBluetoothDevice.add(device);
}
//mAdapter.cancelDiscovery();
//查找设备
for(int i = 0; i < ALBluetoothDevice.size(); i++)
{
BluetoothDevice cDevice = (BluetoothDevice)ALBluetoothDevice.get(i);
String theBlueToothName = cDevice.getName();
if(theBlueToothName.indexOf(BlueToothName) < 0)
{
continue;
}
//开始连接设备
mDevice = cDevice;
try
{
mSocket = mDevice.createRfcommSocketToServiceRecord(SS_UUID);
mSocket.connect();
mInputStream = mSocket.getInputStream();
mOutputStream = mSocket.getOutputStream();
return 0;
}
catch (Exception e)
{
return -4;
}
}
return -5; //没有配对设备
}
public static long BthSendSocket(byte[] buffer, int len)
{
if(mOutputStream == null)
{
return -1;
}
try
{
mOutputStream.write(buffer, 0, len);
}
catch (IOException e)
{
return -2;
}
return len;
}
public static long BthRecvSocket(byte[] buffer)
{
if(mInputStream == null)
{
return -1;
}
try
{
// int nAvailable = mInputStream.available();
// if(nAvailable <= 0)
// {
// return 0;
// }
return mInputStream.read(buffer, 0, 4096);
}
catch (IOException e)
{
return -2;
}
}
public static void BthDisconnect()
{
if(mInputStream != null)
{
try
{
mInputStream.close();
mInputStream = null;
}
catch (IOException e)
{
return;
}
}
if(mOutputStream != null)
{
try
{
mOutputStream.close();
mOutputStream = null;
}
catch (IOException e)
{
return;
}
}
if(mSocket != null)
{
try
{
mSocket.close();
mSocket = null;
}
catch (IOException e)
{
return;
}
}
}
}

View File

@@ -0,0 +1,126 @@
package com.sdses;
import android.content.Context;
public class JniCommonInterface
{
static
{
System.loadLibrary("WltRS");
System.loadLibrary("PortCommunication");
System.loadLibrary("TerminalProtocol");
System.loadLibrary("CommonInterface");
}
public static long GetUsbPermission(Context ctx, int Vid, int Pid)
{
return UsbHidPort.GetUsbPermission(ctx, Vid, Pid);
}
public static native long SetLogFileEx(String LogFileName);
public static native long SetTerminalLibrary(String LibraryFileName);
public static native long GetLibraryInfo(byte[] Version, byte[] Description);
public static native long SetAutoPara(String PortType, String PortPara, String ExtendPara, String DllName, int UsingGA467);
public static native long OpenDevice(String PortType, String PortPara, String ExtendPara);
public static native long SetCurrentDevice(long DevHandle);
public static native long GetCurrentDevice();
public static native long CloseDevice();
public static native long CommandTransmit(long Command, long CmdDataLength, byte[] CmdData, byte[] StatusWords, long[] RecvDataLength, byte[] RecvData, long TimeOut);
public static native long GA467Transmit(long Command, long CmdDataLength, byte[] CmdData, byte[] StatusWords, long[] RecvDataLength, byte[] RecvData, long TimeOut);
public static native long GetTerminalModel(byte[] TerminalModel);
public static native long GetTerminalFirmVersion(byte[] FirmVersion, byte[]HardwareVersion);
public static native long GetTerminalSn(byte[] TerminalSn);
public static native long TerminalHeartBeat();
public static native long LedOnOff(byte LedNo, byte OnOff);
public static native long LedBlink(byte LedNo, long OnTimeMs, long OffTimeMs, byte BlinkCount, byte FinalState);
public static native long GetLastRecvData(byte[] LastRecvData, long[] LastRecvDataLen);
public static native long IdFindCard();
public static native long IdSelectCard();
public static native long IdReadBaseMsg(byte[] pucCHMsg, long[] puiCHMsgLen, byte[] pucPHMsg, long[] puiPHMsgLen);
public static native long IdReadBaseFpMsg(byte[] pucCHMsg, long[] puiCHMsgLen, byte[] pucPHMsg, long[] puiPHMsgLen, byte[] pucFPMsg, long[] puiFPMsgLen);
public static native long IdReadNewAppMsg(byte[] pucAppMsg, long[] puiAppMsgLen);
public static native long IdReadSn(byte[] SN, long[] SNLen);
public static native long IdApdu(long SendApduLen, byte[] SendApdu, byte[] RecvApdu, long[] RecvApduLen);
public static native long SdtFindCard(byte[] pucManaInfo);
public static native long SdtSelectCard(byte[] pucManaMsg);
public static native long SdtReadBaseMsg(byte[] pucCHMsg, long[] puiCHMsgLen, byte[] pucPHMsg, long[] puiPHMsgLen);
public static native long SdtReadBaseFpMsg(byte[] pucCHMsg, long[] puiCHMsgLen, byte[] pucPHMsg, long[] puiPHMsgLen, byte[] pucFPMsg, long[] puiFPMsgLen);
public static native long SdtReadNewAppMsg(byte[] pucAppMsg, long[] puiAppMsgLen);
public static native long GetSamStatus();
public static native long GetSamId(byte[] SamId, long[] SamIdLen);
public static native long GetSamIdStr(byte[] SamIdStr);
public static native long SamGetStatus();
public static native long SamGetId(byte[] SamId, long[] SamIdLen);
public static native long SamGetIdStr(byte[] SamIdStr);
public static native long SdtSamGetStatus();
public static native long SdtSamGetId(byte[] SamId, long[] SamIdLen);
public static native long SdtSamGetIdStr(byte[] SamIdStr);
public static native long IdReadCard(byte CardType, byte InfoEncoding, byte[] IdCardInfo, long TimeOutMs);
public static native long SdtReadCard(byte CardType, byte InfoEncoding, byte[] IdCardInfo, long TimeOutMs);
public static native long IdReadNewAddress(byte[] NewAddress);
public static native long SdtReadNewAddress(byte[] NewAddress);
public static native long IdCardGetName(byte[] Name);
public static native long IdCardGetNameEn(byte[] NameEn);
public static native long IdCardGetGender(byte[] Gender);
public static native long IdCardGetGenderId(byte[] GenderId);
public static native long IdCardGetNation(byte[] Nation);
public static native long IdCardGetNationId(byte[] NationId);
public static native long IdCardGetBirthDate(byte[] BirthDate);
public static native long IdCardGetAddress(byte[] Address);
public static native long IdCardGetIdNumber(byte[] IdNumber);
public static native long IdCardGetSignOrgan(byte[] SignOrgan);
public static native long IdCardGetBeginTerm(byte[] BeginTerm);
public static native long IdCardGetValidTerm(byte[] ValidTerm);
public static native long IdCardGetFPBuffer(byte[] FPBuffer, long[] FPBufferLen);
public static native long IdCardGetPhotoFile(String PhotoFile);
public static native long IdCardGetPhotoBuffer(byte WltBmpJpg, byte[] PhotoBuffer, long[] PhotoBufferLen);
public static native long MIdSamGetCert(byte[] Cert, long[] CertLen);
public static native long MIdSamEnable(long EnableDataLen, byte[] EnableData);
public static native long MIdSamDisable(long DisableDataLen, byte[] DisableData);
public static native long MIdSamGetEnableState(byte[] EnableState, long[] RemainNum, byte[] EnableTime, byte[] DisableTime, byte[] SecondCert, long[] SecondCertLen, byte[] EncryptCert, long[] EncryptCertLen, byte[] SignCert, long[] SignCertLen);
public static native long MIdSamAuthRequest(byte[] RequestData56);
public static native long MIdSamAuthConfirm(byte[] ConfirmData89);
public static native long MIdReadChkData(byte[] Random16, byte[] RemainAuthNum, byte[] SamId22, byte[] BeginAndValidTerm32, byte[] ShortCode16, byte[] CheckData, long[] CheckDataLen, byte[] Sign64);
public static native long MIdReadChkDataPF(byte[] Random16, byte[] RemainAuthNum, byte[] SamId22, byte[] BeginAndValidTerm32, byte[] ShortCode16, byte[] CheckData, long[] CheckDataLen, byte[] Hash32, byte[] Sign64, byte[] PH, long[] PHLen, byte[] FP, long[] FPLen);
public static native long MIdCheckShortLongCode(byte[] Random16, byte ShortCodeCount, byte[] ShortCode, byte LongCodeCount, byte[] LongCode, byte[] RemainAuthNum, byte[] SamId22, byte[] BeginAndValidTerm32, byte[] MatchCode, byte[] MatchCodeLen, byte[] CheckData, long[] CheckDataLen, byte[] Sign64);
public static native long MIdCheckShortLongCodePF(byte[] Random16, byte ShortCodeCount, byte[] ShortCode, byte LongCodeCount, byte[] LongCode, byte[] RemainAuthNum, byte[] SamId22,byte[] BeginAndValidTerm32, byte[] MatchCode, byte[] MatchCodeLen, byte[] CheckData, long[] CheckDataLen, byte[] Hash32, byte[] Sign64, byte[] PH, long[] PHLen, byte[] FP, long[] FPLen);
public static native long MIdCheckProperty(byte[] Random16, byte PropertyCode, byte PropertyValLen, byte[] PropertyVal, byte[] RemainAuthNum, byte[] SamId22, byte[] CheckResult, byte[] CheckData, long[] CheckDataLen, byte[] Sign64);
public static native long MagRead(byte Tracks, byte[] TrackData1, byte[] TrackData2, byte[] TrackData3, byte TimeOutSec);
public static native long MagWrite(byte Tracks, String TrackData1, String TrackData2, String TrackData3, byte TimeOutSec);
public static native long QrRead(byte[] QrData, byte TimeOutSec);
public static native long QrCancel();
public static native long QrAsynEnable(byte TimeOutSec);
public static native long QrAsynRead(byte[] QrData);
public static native long QrAsynDisable();
public static native long M1FindCard(byte[] UID, long[] UIDLen);
public static native long M1Authentication(byte KeyType, byte SecAddr, byte[] Key, byte[] UID);
public static native long M1ReadBlock(byte BlockAddr, byte[] BlockData, long[] BlockDataLen);
public static native long M1WriteBlock(byte BlockAddr, long BlockDataLen, byte[] BlockData);
public static native long M1Halt();
public static native long FpCapFeature(byte[] Feature, long[] FeatureLen);
public static native long FpMatchFeature(long FeatureLen1, byte[] Feature1, long FeatureLen2, byte[] Feature2, long[] Score);
public static native long SsseReadCard(int iType, byte[] SSCardInfo, byte[] SSErrorInfo);
public static native long SsseReadCard2(int iType, byte[] SSCardInfo, byte[] SSErrorInfo);
public static native long SsseGetCardInfo(String Tag, byte[] SSCardInfo);
public static native long CpuPowerOn(byte Slot, byte[] ATRS, long[] ATRSLen);
public static native long CpuApdu(byte Slot, long SendApduLen, byte[] SendApdu, byte[] RecvApdu, long[] RecvApduLen);
public static native long CpuPowerOff(byte Slot);
public static native long IccGetCardInfo(int ICtype, String AIDList, String TagList, byte[] IcCardInfo);
public static native long IccGetARQC(int ICtype, String trData, String AIDList, byte[] ARQC, byte[] trAppData);
public static native long IccARPCExeScript(int ICtype, String trData, String ARPC, String trAppData, byte[] ScriptResult, byte[] TC);
public static native long IccGetTrDetail(int ICtype, String AIDList, byte[] TrDetail);
public static native long IccGetLoadDetail(int ICtype, String AIDList, byte[] LoadDetail);
public static native long HexToAsc(byte[] Hex, long HexLength, byte[] Asc);
public static native long AscToHex(String Asc, long HexLength, byte[] Hex);
}

View File

@@ -0,0 +1,300 @@
package com.sdses;
import java.util.HashMap;
import java.util.Iterator;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
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.hardware.usb.UsbManager;
import android.os.Build;
public class UsbHidPort
{
private static final String ACTION_USB_PERMISSION = "com.sdses.JniCommonInterface.USB_PERMISSION";
private static final BroadcastReceiver mUsbReceiver = new BroadcastReceiver()
{
public void onReceive(Context context, Intent intent)
{
String action = intent.getAction();
if (action.equals(ACTION_USB_PERMISSION))
{
synchronized (this)
{
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false))
{
if (device != null)
{
}
}
else
{
}
}
}
}
};
public static UsbManager usbManager = null;
public static UsbDevice usbDevice = null;
public static UsbInterface usbInterface = null;
public static UsbDeviceConnection usbConnection = null;
public static UsbEndpoint epOut;
public static UsbEndpoint epIn;
public static int SendBlockSize = 0;
public static int RecvBlockSize = 0;
public static synchronized long GetUsbPermission(Context ctx, int Vid, int Pid)
{
try
{
usbManager = (UsbManager)ctx.getSystemService(Context.USB_SERVICE);
HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();
Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
usbDevice = null;
while (deviceIterator.hasNext())
{
UsbDevice device = deviceIterator.next();
if ((device.getVendorId() == Vid) && (device.getProductId() == Pid))
{
usbDevice = device;
break;
}
}
if (usbDevice == null)
{
return -1;
}
if(usbDevice.getInterfaceCount() <= 0)
{
return -2;
}
usbInterface = usbDevice.getInterface(0);
if(usbInterface == null)
{
return -2;
}
// 判断是否有权限
if(!usbManager.hasPermission(usbDevice))
{
// Android 12+ 需要明确指定 PendingIntent 的 flag
int flags = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
flags = PendingIntent.FLAG_IMMUTABLE;
}
PendingIntent mPermissionIntent = PendingIntent.getBroadcast(ctx, 0, new Intent(ACTION_USB_PERMISSION), flags);
IntentFilter permissionFilter = new IntentFilter(ACTION_USB_PERMISSION);
ctx.registerReceiver(mUsbReceiver, permissionFilter);
usbManager.requestPermission(usbDevice, mPermissionIntent);
System.out.print("Requesting Usb Permission");
// 等待权限授权最多等待10秒
int waitCount = 0;
while(!usbManager.hasPermission(usbDevice) && waitCount < 100)
{
try {
Thread.sleep(100); // 等待100ms
waitCount++;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
// 清理广播接收器
try {
ctx.unregisterReceiver(mUsbReceiver);
} catch (Exception e) {
// 忽略取消注册时的异常
}
// 检查是否获得权限
if (!usbManager.hasPermission(usbDevice)) {
return -3; // 权限请求失败或超时
}
}
return 0;
}
catch(Exception ex)
{
ex.printStackTrace();
return -99;
}
}
public static long UsbOpenPort(int Vid, int Pid)
{
try
{
if (usbDevice == null)
{
return -1;
}
usbConnection = usbManager.openDevice(usbDevice);
if(usbConnection == null)
{
return -1;
}
if(usbInterface == null)
{
return -2;
}
if(!usbConnection.claimInterface(usbInterface, true))
{
usbConnection.close();
usbConnection = null;
return -2;
}
for(int i = 0; i < usbInterface.getEndpointCount(); i++)
{
UsbEndpoint endPoint = usbInterface.getEndpoint(i);
switch(endPoint.getType())
{
case UsbConstants.USB_ENDPOINT_XFER_BULK:
case UsbConstants.USB_ENDPOINT_XFER_INT:
{
switch(endPoint.getDirection())
{
case UsbConstants.USB_DIR_OUT:
{
epOut = endPoint;
break;
}
case UsbConstants.USB_DIR_IN:
{
epIn = endPoint;
break;
}
}
break;
}
case UsbConstants.USB_ENDPOINT_XFER_CONTROL:
{
epOut = endPoint;
epIn = endPoint;
break;
}
}
}
SendBlockSize = epOut.getMaxPacketSize();
RecvBlockSize = epIn.getMaxPacketSize();
return SendBlockSize;
}
catch(Exception ex)
{
ex.printStackTrace();
return -99;
}
}
public static long UsbWrite(byte[] buffer, int len, int timeout)
{
try
{
return usbConnection.bulkTransfer(epOut, buffer, SendBlockSize, timeout);
}
catch(Exception ex)
{
ex.printStackTrace();
return -99;
}
}
public static long UsbRead(byte[] buffer, int timeout)
{
/*
try
{
Thread.sleep(10);
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
*/
try
{
return usbConnection.bulkTransfer(epIn, buffer, RecvBlockSize, timeout);
}
catch(Exception ex)
{
ex.printStackTrace();
return -99;
}
}
public static long UsbCtrlWrite(byte[] buffer, int len, int timeout)
{
try
{
return usbConnection.controlTransfer(0x21, 0x09, 0x0200, 0x0000, buffer, SendBlockSize, timeout);
}
catch(Exception ex)
{
ex.printStackTrace();
return -99;
}
}
public static long UsbCtrlRead(byte[] buffer, int timeout)
{
try
{
try
{
Thread.sleep(10);
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
return usbConnection.controlTransfer(0xA1, 0x01, 0x0100, 0x0000, buffer, RecvBlockSize, timeout);
}
catch(Exception ex)
{
ex.printStackTrace();
return -99;
}
}
public static void UsbClosePort()
{
try
{
usbConnection.releaseInterface(usbInterface);
usbConnection.close();
usbConnection = null;
}
catch(Exception ex)
{
ex.printStackTrace();
}
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- 身份证读卡器设备过滤器 -->
<!-- 可以根据实际设备的VID/PID进行配置 -->
<usb-device vendor-id="1234" product-id="5678" />
<!-- 通用HID设备过滤器 -->
<usb-device class="3" subclass="0" protocol="0" />
<!-- 如果知道具体的设备VID/PID可以添加更多配置 -->
<usb-device vendor-id="1024" product-id="50010" />
</resources>

View File

@@ -0,0 +1,29 @@
package com.example.idcard;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import org.junit.Test;
/**
* This demonstrates a simple unit test of the Java portion of this plugin's implementation.
*
* Once you have built the plugin's example app, you can run these tests from the command
* line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or
* you can run them directly from IDEs that support JUnit such as Android Studio.
*/
public class IdcardPluginTest {
@Test
public void onMethodCall_getPlatformVersion_returnsExpectedValue() {
IdcardPlugin plugin = new IdcardPlugin();
final MethodCall call = new MethodCall("getPlatformVersion", null);
MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
plugin.onMethodCall(call, mockResult);
verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE);
}
}

43
example/.gitignore vendored Normal file
View File

@@ -0,0 +1,43 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

16
example/README.md Normal file
View File

@@ -0,0 +1,16 @@
# idcard_example
Demonstrates how to use the idcard plugin.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View File

@@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

13
example/android/.gitignore vendored Normal file
View File

@@ -0,0 +1,13 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
**/*.keystore
**/*.jks

View File

@@ -0,0 +1,58 @@
plugins {
id "com.android.application"
id "kotlin-android"
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file("local.properties")
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader("UTF-8") { reader ->
localProperties.load(reader)
}
}
def flutterVersionCode = localProperties.getProperty("flutter.versionCode")
if (flutterVersionCode == null) {
flutterVersionCode = "1"
}
def flutterVersionName = localProperties.getProperty("flutter.versionName")
if (flutterVersionName == null) {
flutterVersionName = "1.0"
}
android {
namespace = "com.example.idcard_example"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.idcard_example"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutterVersionCode.toInteger()
versionName = flutterVersionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.debug
}
}
}
flutter {
source = "../.."
}

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,45 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="idcard_example"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>

View File

@@ -0,0 +1,6 @@
package com.example.idcard_example;
import io.flutter.embedding.android.FlutterActivity;
public class MainActivity extends FlutterActivity {
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,18 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = "../build"
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register("clean", Delete) {
delete rootProject.buildDir
}

View File

@@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip

View File

@@ -0,0 +1,25 @@
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}()
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "7.3.0" apply false
id "org.jetbrains.kotlin.android" version "1.7.10" apply false
}
include ":app"

View File

@@ -0,0 +1,25 @@
// This is a basic Flutter integration test.
//
// Since integration tests run in a full Flutter application, they can interact
// with the host side of a plugin implementation, unlike Dart unit tests.
//
// For more information about Flutter integration tests, please see
// https://docs.flutter.dev/cookbook/testing/integration/introduction
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:idcard/idcard.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('getPlatformVersion test', (WidgetTester tester) async {
final Idcard plugin = Idcard();
final String? version = await plugin.getPlatformVersion();
// The version string depends on the host platform running the test, so
// just assert that some non-empty string is returned.
expect(version?.isNotEmpty, true);
});
}

366
example/lib/main.dart Normal file
View File

@@ -0,0 +1,366 @@
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:idcard/idcard.dart';
import 'package:idcard/idcard_platform_interface.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
String _status = '未连接';
IdCardInfo? _cardInfo;
bool _isReading = false;
bool _isLoading = false;
bool _isConnected = false;
String _statusMessage = '未连接';
final _idcardPlugin = Idcard();
final String _portType = "USB";
final String _portPara = "0400C35A";
final String _portExtend = "MI";
@override
void initState() {
super.initState();
initPlatformState();
}
/// 初始化平台状态
Future<void> initPlatformState() async {
String platformVersion;
try {
platformVersion = await _idcardPlugin.getPlatformVersion() ?? 'Unknown platform version';
} on PlatformException {
platformVersion = 'Failed to get platform version.';
}
if (!mounted) return;
setState(() {
_platformVersion = platformVersion;
});
}
/// 连接设备
Future<void> _connectDevice() async {
setState(() {
_isLoading = true;
_statusMessage = '正在连接设备...';
_status = '正在连接设备...';
});
try {
// 首先获取USB权限
setState(() {
_statusMessage = '正在请求USB权限...';
_status = '正在请求USB权限...';
});
// int vid = int.parse(portPara.substring(0, 4), radix: 16);
// int pid = int.parse(portPara.substring(4, 8), radix: 16);
final permissionResult = await _idcardPlugin.getUsbPermission(vid: int.parse(_portPara.substring(0, 4), radix: 16), pid: int.parse(_portPara.substring(4, 8), radix: 16));
if (permissionResult != 0) {
throw Exception('获取USB权限失败: $permissionResult');
}
// 打开设备
setState(() {
_statusMessage = '正在打开设备...';
_status = '正在打开设备...';
});
// "USB", "0400C35A", "MI";
final openResult = await _idcardPlugin.openDevice(portType: _portType, portPara: _portPara, extendPara: _portExtend);
print("设备句柄:$openResult");
if (openResult <= 0) {
throw Exception('打开设备失败: $openResult');
}
setState(() {
_isConnected = true;
_statusMessage = '设备连接成功';
_status = '设备连接成功';
});
} on PlatformException catch (e) {
String errorMessage = '连接失败: ${e.message}';
// 针对不同错误提供用户友好的提示
if (e.code == 'PERMISSION_DENIED') {
errorMessage = 'USB权限被拒绝。\n\n解决方法:\n1. 请在弹出的权限对话框中点击"允许"\n2. 或者在系统设置中手动授权USB权限\n3. 确保设备已正确连接';
} else if (e.code == 'DEVICE_NOT_FOUND') {
errorMessage = '未找到身份证读卡器设备。\n\n请检查:\n1. 设备是否已连接\n2. 设备驱动是否正常\n3. VID/PID是否正确';
}
setState(() {
_statusMessage = errorMessage;
_status = errorMessage;
_isConnected = false;
});
} catch (e) {
setState(() {
_statusMessage = '连接失败: $e';
_status = '连接失败: $e';
_isConnected = false;
});
} finally {
setState(() {
_isLoading = false;
});
}
}
/// 断开设备连接
Future<void> _disconnectDevice() async {
try {
await _idcardPlugin.closeDevice();
setState(() {
_status = '设备已断开';
_statusMessage = '设备已断开';
_isConnected = false;
_cardInfo = null;
});
} catch (e) {
setState(() {
_status = '断开连接失败:$e';
_statusMessage = '断开连接失败:$e';
});
}
}
/// 读取身份证
Future<void> _readCard() async {
if (_isReading) return;
setState(() {
_isReading = true;
_status = '请将身份证放在读卡器上...';
_cardInfo = null;
});
try {
// 使用完整的读卡流程
IdCardInfo cardInfo = await _idcardPlugin.readCardComplete();
print(cardInfo);
setState(() {
_cardInfo = cardInfo;
_status = '读卡成功';
_isReading = false;
});
} catch (e) {
setState(() {
_status = '读卡失败:$e';
_isReading = false;
});
}
}
/// 构建身份证信息显示卡片
Widget _buildCardInfoWidget() {
if (_cardInfo == null) {
return const SizedBox.shrink();
}
return Card(
margin: const EdgeInsets.all(16),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'身份证信息',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
_buildInfoRow('姓名', _cardInfo!.name),
_buildInfoRow('性别', _cardInfo!.gender),
_buildInfoRow('民族', _cardInfo!.nation),
_buildInfoRow('出生日期', _cardInfo!.birthDate),
_buildInfoRow('身份证号', _cardInfo!.idNumber),
_buildInfoRow('地址', _cardInfo!.address),
_buildInfoRow('签发机关', _cardInfo!.signOrgan),
_buildInfoRow('有效期', _cardInfo!.validTerm),
if (_cardInfo!.photo != null) ...[
const SizedBox(height: 16),
const Text('照片:'),
const SizedBox(height: 8),
Container(
width: 120,
height: 160,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(8),
),
child: Image.memory(
Uint8List.fromList(_cardInfo!.photo!),
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return const Center(
child: Text('照片加载失败'),
);
},
),
),
],
],
),
),
);
}
/// 构建信息行
Widget _buildInfoRow(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 80,
child: Text(
'$label',
style: const TextStyle(fontWeight: FontWeight.w500),
),
),
Expanded(
child: Text(value),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '身份证读卡器示例',
theme: ThemeData(
primarySwatch: Colors.blue,
useMaterial3: true,
),
home: Scaffold(
appBar: AppBar(
title: const Text('身份证读卡器示例'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: SingleChildScrollView(
child: Column(
children: [
// 平台信息
Card(
margin: const EdgeInsets.all(16),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'平台信息',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Text('运行平台:$_platformVersion'),
const SizedBox(height: 8),
Text('设备状态:$_status'),
],
),
),
),
// 操作按钮
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: (_isLoading || _isConnected) ? null : _connectDevice,
child: _isLoading
? const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2,
),
),
SizedBox(width: 8),
Text('连接中...'),
],
)
: const Text('连接设备'),
),
),
const SizedBox(width: 16),
Expanded(
child: ElevatedButton(
onPressed: (_isLoading || !_isConnected) ? null : _disconnectDevice,
child: const Text('断开连接'),
),
),
],
),
),
const SizedBox(height: 16),
// 读卡按钮
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: (_isReading || !_isConnected || _isLoading) ? null : _readCard,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
),
child: _isReading
? const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
),
SizedBox(width: 8),
Text('正在读卡...'),
],
)
: const Text(
'读取身份证',
style: TextStyle(fontSize: 16),
),
),
),
),
// 身份证信息显示
_buildCardInfoWidget(),
],
),
),
),
);
}
}

283
example/pubspec.lock Normal file
View File

@@ -0,0 +1,283 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
url: "https://pub.dev"
source: hosted
version: "2.11.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
characters:
dependency: transitive
description:
name: characters
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
clock:
dependency: transitive
description:
name: clock
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
url: "https://pub.dev"
source: hosted
version: "1.1.1"
collection:
dependency: transitive
description:
name: collection
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted
version: "1.18.0"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
url: "https://pub.dev"
source: hosted
version: "1.0.8"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
file:
dependency: transitive
description:
name: file
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_driver:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
fuchsia_remote_debug_protocol:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
idcard:
dependency: "direct main"
description:
path: ".."
relative: true
source: path
version: "0.0.1"
integration_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
url: "https://pub.dev"
source: hosted
version: "10.0.4"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
url: "https://pub.dev"
source: hosted
version: "3.0.3"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
lints:
dependency: transitive
description:
name: lints
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
url: "https://pub.dev"
source: hosted
version: "3.0.0"
matcher:
dependency: transitive
description:
name: matcher
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev"
source: hosted
version: "0.12.16+1"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev"
source: hosted
version: "0.8.0"
meta:
dependency: transitive
description:
name: meta
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
url: "https://pub.dev"
source: hosted
version: "1.12.0"
path:
dependency: transitive
description:
name: path
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev"
source: hosted
version: "1.9.0"
platform:
dependency: transitive
description:
name: platform
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
url: "https://pub.dev"
source: hosted
version: "3.1.4"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
process:
dependency: transitive
description:
name: process
sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32"
url: "https://pub.dev"
source: hosted
version: "5.0.2"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.10.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted
version: "1.11.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.2"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
sync_http:
dependency: transitive
description:
name: sync_http
sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961"
url: "https://pub.dev"
source: hosted
version: "0.3.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
url: "https://pub.dev"
source: hosted
version: "1.2.1"
test_api:
dependency: transitive
description:
name: test_api
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
url: "https://pub.dev"
source: hosted
version: "0.7.0"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
url: "https://pub.dev"
source: hosted
version: "14.2.1"
webdriver:
dependency: transitive
description:
name: webdriver
sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e"
url: "https://pub.dev"
source: hosted
version: "3.0.3"
sdks:
dart: ">=3.4.4 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"

85
example/pubspec.yaml Normal file
View File

@@ -0,0 +1,85 @@
name: idcard_example
description: "Demonstrates how to use the idcard plugin."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
environment:
sdk: '>=3.4.4 <4.0.0'
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
idcard:
# When depending on this package from a real application you should use:
# idcard: ^x.y.z
# See https://dart.dev/tools/pub/dependencies#version-constraints
# The example app is bundled with the plugin so we use a path dependency on
# the parent directory to use the current plugin's version.
path: ../
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.6
dev_dependencies:
integration_test:
sdk: flutter
flutter_test:
sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^3.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages

View File

@@ -0,0 +1,27 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:idcard_example/main.dart';
void main() {
testWidgets('Verify Platform version', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
// Verify that platform version is retrieved.
expect(
find.byWidgetPredicate(
(Widget widget) => widget is Text &&
widget.data!.startsWith('Running on:'),
),
findsOneWidget,
);
});
}

214
lib/idcard.dart Normal file
View File

@@ -0,0 +1,214 @@
import 'idcard_platform_interface.dart';
/// 身份证读卡器插件主类
/// 基于神思标准化接口规范实现的Flutter插件
class Idcard {
/// 获取平台版本
Future<String?> getPlatformVersion() {
return IdcardPlatform.instance.getPlatformVersion();
}
/// 获取USB权限
/// [vid] - USB设备的厂商ID默认为0x261A神思USB读卡器厂商ID
/// [pid] - USB设备的产品ID默认为0x0011
Future<int> getUsbPermission({int vid = 0x261A, int pid = 0x0011}) {
return IdcardPlatform.instance.getUsbPermission(vid, pid);
}
/// 打开设备连接
/// [portType] - 端口类型,支持"USB"、"COM"、"SKT"、"BTH"、"AUTO"
/// [portPara] - 端口参数,根据端口类型不同而不同
/// [extendPara] - 扩展参数
/// 返回值大于0表示成功设备句柄其他为失败
Future<int> openDevice({String portType = 'USB', String portPara = '', String extendPara = ''}) {
return IdcardPlatform.instance.openDevice(
portType: portType,
portPara: portPara,
extendPara: extendPara,
);
}
/// 设置当前设备(多设备操作)
/// [devHandle] - 设备句柄
/// 返回值0表示成功非0表示失败
Future<int> setCurrentDevice(int devHandle) {
return IdcardPlatform.instance.setCurrentDevice(devHandle);
}
/// 获取当前设备(多设备操作)
/// 返回值:返回当前设备的句柄
Future<int> getCurrentDevice() {
return IdcardPlatform.instance.getCurrentDevice();
}
/// 获取接口库信息
/// 返回值包含version和description的Map
Future<Map<String, String>> getLibraryInfo() {
return IdcardPlatform.instance.getLibraryInfo();
}
/// 获取设备型号
/// 返回值:设备型号字符串
Future<String> getTerminalModel() {
return IdcardPlatform.instance.getTerminalModel();
}
/// 设备轮询心跳
/// 用于检测与读卡器是否已建立连接并且通讯正常
/// 返回值0表示成功非0表示失败
Future<int> terminalHeartBeat() {
return IdcardPlatform.instance.terminalHeartBeat();
}
/// 获取接收数据
/// 获取最后一次通讯收到的数据,一般用于获取读卡器协议层错误信息
/// 返回值:最后一次通讯收到的数据
Future<List<int>> getLastRecvData() {
return IdcardPlatform.instance.getLastRecvData();
}
/// 获取设备固件版本
/// 返回值包含firmVersion和hardwareVersion的Map
Future<Map<String, String>> getTerminalFirmVersion() {
return IdcardPlatform.instance.getTerminalFirmVersion();
}
/// 获取设备序列号
/// 返回值:设备序列号字符串
Future<String> getTerminalSn() {
return IdcardPlatform.instance.getTerminalSn();
}
/// 关闭设备连接
/// 返回值0表示成功非0表示失败
Future<int> closeDevice() {
return IdcardPlatform.instance.closeDevice();
}
/// 寻找身份证卡片
/// 返回值大于0表示成功找到卡片小于等于0表示错误码
Future<int> findCard() {
return IdcardPlatform.instance.findCard();
}
/// 选择身份证卡片
/// 返回值大于0表示成功选择卡片小于等于0表示错误码
Future<int> selectCard() {
return IdcardPlatform.instance.selectCard();
}
/// 读取身份证(新版接口,支持多种证件类型)
/// [cardType] - 读取卡类型:
/// 0x00读取二代证或外国人或港澳台
/// 0x01只读二代证
/// 0x02只读外国人
/// 0x03只读港澳台
/// 0x10-0x13对应上述类型但包含指纹信息
/// [infoEncoding] - 返回信息的编码方式:
/// 0x01GB18030编码GBK
/// 0x02UTF16-LE编码
/// 0x03UTF-8编码
/// [timeOut] - 读卡超时时间毫秒0表示不等待>0表示等待放卡
/// 返回值包含result错误码和data冒号分隔的身份证信息字符串的Map
Future<Map<String, dynamic>> idReadCard({int cardType = 0x00, int infoEncoding = 0x01, int timeOut = 30000}) {
return IdcardPlatform.instance.idReadCard(
cardType: cardType,
infoEncoding: infoEncoding,
timeOut: timeOut,
);
}
/// 读取身份证原始数据(兼容旧版接口)
/// [cardType] - 卡片类型1表示身份证
/// [infoEncoding] - 信息编码0表示默认编码
/// [timeOut] - 超时时间毫秒默认30秒
/// 返回值包含result错误码和data原始数据的Map
Future<Map<String, dynamic>> readCard({int cardType = 1, int infoEncoding = 0, int timeOut = 30000}) {
return IdcardPlatform.instance.readCard(
cardType: cardType,
infoEncoding: infoEncoding,
timeOut: timeOut,
);
}
/// 读取身份证详细信息
/// 返回值IdCardInfo对象包含姓名、性别、民族等详细信息
/// 抛出异常如果读取失败会抛出Exception
Future<IdCardInfo> readCardInfo() {
return IdcardPlatform.instance.readCardInfo();
}
/// 读取追加住址
/// 返回值:追加住址信息字符串
Future<String> readNewAddress() {
return IdcardPlatform.instance.readNewAddress();
}
/// 获取SAM模块状态
/// 返回值0表示成功非0表示失败
Future<int> getSamStatus() {
return IdcardPlatform.instance.getSamStatus();
}
/// 获取SAM模块编号字符串
/// 返回值SAM模块编号字符串
Future<String> getSamIdStr() {
return IdcardPlatform.instance.getSamIdStr();
}
/// 完整的身份证读取流程
/// 自动执行寻卡、选卡、读取信息的完整流程
/// [timeOut] - 超时时间毫秒默认30秒
/// [retryInterval] - 重试间隔时间毫秒默认500毫秒
/// 返回值IdCardInfo对象
/// 抛出异常如果超时或读取失败会抛出Exception
Future<IdCardInfo> readCardComplete({int timeOut = 30000, int retryInterval = 500}) async {
final startTime = DateTime.now().millisecondsSinceEpoch;
while (true) {
try {
// 1. 寻卡
int findResult = await findCard();
print("寻卡结果:$findResult");
if (findResult == 0) {
// 2. 选卡
int selectResult = await selectCard();
print("选卡结果:$selectResult");
if (selectResult == 0) {
// 3. 读取信息
return await readCardInfo();
} else {
print('选卡失败,错误码:$selectResult,继续重试...');
}
} else {
print('寻卡失败,错误码:$findResult,继续重试...');
}
} catch (e) {
print('读卡过程中发生异常:$e,继续重试...');
}
// 检查是否超时
final currentTime = DateTime.now().millisecondsSinceEpoch;
if (currentTime - startTime >= timeOut) {
throw Exception('读卡超时,超时时间:$timeOut毫秒');
}
// 等待重试间隔
await Future.delayed(Duration(milliseconds: retryInterval));
}
}
/// 检查设备是否已连接
/// 通过尝试获取平台版本来检查设备连接状态
Future<bool> isDeviceConnected() async {
try {
final version = await getPlatformVersion();
return version != null;
} catch (e) {
return false;
}
}
}

View File

@@ -0,0 +1,180 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'idcard_platform_interface.dart';
/// 使用方法通道的IdcardPlatform实现
class MethodChannelIdcard extends IdcardPlatform {
/// 与原生平台交互的方法通道
@visibleForTesting
final methodChannel = const MethodChannel('idcard');
@override
Future<String?> getPlatformVersion() async {
final version = await methodChannel.invokeMethod<String>('getPlatformVersion');
return version;
}
@override
Future<int> getUsbPermission(int vid, int pid) async {
final result = await methodChannel.invokeMethod<int>('getUsbPermission', {
'vid': vid,
'pid': pid,
});
return result ?? -1;
}
@override
Future<int> openDevice({String portType = 'USB', String portPara = '', String extendPara = ''}) async {
final result = await methodChannel.invokeMethod<int>('openDevice', {
'portType': portType,
'portPara': portPara,
'extendPara': extendPara,
});
return result ?? -1;
}
@override
Future<int> setCurrentDevice(int devHandle) async {
final result = await methodChannel.invokeMethod<int>('setCurrentDevice', {
'devHandle': devHandle,
});
return result ?? -1;
}
@override
Future<int> getCurrentDevice() async {
final result = await methodChannel.invokeMethod<int>('getCurrentDevice');
return result ?? -1;
}
@override
Future<Map<String, String>> getLibraryInfo() async {
final result = await methodChannel.invokeMethod('getLibraryInfo');
if (result == null) return {'version': '', 'description': ''};
// 安全地转换类型
final Map<String, dynamic> data = Map<String, dynamic>.from(result as Map);
return {
'version': data['version']?.toString() ?? '',
'description': data['description']?.toString() ?? '',
};
}
@override
Future<String> getTerminalModel() async {
final result = await methodChannel.invokeMethod<String>('getTerminalModel');
return result ?? '';
}
@override
Future<int> terminalHeartBeat() async {
final result = await methodChannel.invokeMethod<int>('terminalHeartBeat');
return result ?? -1;
}
@override
Future<List<int>> getLastRecvData() async {
final result = await methodChannel.invokeMethod<List<dynamic>>('getLastRecvData');
return result?.cast<int>() ?? [];
}
@override
Future<Map<String, String>> getTerminalFirmVersion() async {
final result = await methodChannel.invokeMethod('getTerminalFirmVersion');
if (result == null) return {'firmVersion': '', 'hardwareVersion': ''};
// 安全地转换类型
final Map<String, dynamic> data = Map<String, dynamic>.from(result as Map);
return {
'firmVersion': data['firmVersion']?.toString() ?? '',
'hardwareVersion': data['hardwareVersion']?.toString() ?? '',
};
}
@override
Future<String> getTerminalSn() async {
final result = await methodChannel.invokeMethod<String>('getTerminalSn');
return result ?? '';
}
@override
Future<int> closeDevice() async {
final result = await methodChannel.invokeMethod<int>('closeDevice');
return result ?? -1;
}
@override
Future<int> findCard() async {
final result = await methodChannel.invokeMethod<int>('findCard');
return result ?? -1;
}
@override
Future<int> selectCard() async {
final result = await methodChannel.invokeMethod<int>('selectCard');
return result ?? -1;
}
@override
Future<Map<String, dynamic>> idReadCard({int cardType = 0x00, int infoEncoding = 0x01, int timeOut = 30000}) async {
final result = await methodChannel.invokeMethod('idReadCard', {
'cardType': cardType,
'infoEncoding': infoEncoding,
'timeOut': timeOut,
});
if (result == null) {
return {'result': -1};
}
// 安全地转换类型
return Map<String, dynamic>.from(result as Map);
}
@override
Future<Map<String, dynamic>> readCard({int cardType = 1, int infoEncoding = 0, int timeOut = 30000}) async {
final result = await methodChannel.invokeMethod('readCard', {
'cardType': cardType,
'infoEncoding': infoEncoding,
'timeOut': timeOut,
});
if (result == null) {
return {'result': -1};
}
// 安全地转换类型
return Map<String, dynamic>.from(result as Map);
}
@override
Future<IdCardInfo> readCardInfo() async {
// 使用idReadCard方法读取身份证信息
final result = await idReadCard();
if (result['result'] != 0) {
throw Exception('Failed to read card info, error code: ${result['result']}');
}
final String? dataStr = result['data'];
if (dataStr == null || dataStr.isEmpty) {
throw Exception('Card data is empty');
}
// 根据文档,数据是冒号分隔的字符串格式
return IdCardInfo.fromColonString(dataStr);
}
@override
Future<String> readNewAddress() async {
final result = await methodChannel.invokeMethod<String>('readNewAddress');
return result ?? '';
}
@override
Future<int> getSamStatus() async {
final result = await methodChannel.invokeMethod<int>('getSamStatus');
return result ?? -1;
}
@override
Future<String> getSamIdStr() async {
final result = await methodChannel.invokeMethod<String>('getSamIdStr');
return result ?? '';
}
}

View File

@@ -0,0 +1,276 @@
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'idcard_method_channel.dart';
/// 身份证信息数据模型
class IdCardInfo {
final String cardType; // 证件类型 (A-二代证, I/Y-外国人, J-港澳台)
final String name; // 中文姓名
final String nameEn; // 英文姓名
final String gender; // 性别
final String genderId; // 性别代码
final String nation; // 民族/国籍
final String nationId; // 民族代码/国籍代码/通行证号码
final String birthDate; // 出生日期
final String address; // 住址
final String idNumber; // 身份证号码/永久居留证号码
final String signOrgan; // 签发机关
final String beginTerm; // 有效期起始日期
final String validTerm; // 有效期截止日期
final String version; // 证件版本号
final String? photoBase64; // 头像JPG照片base64编码
final String? fingerprintBase64; // 指纹特征值base64编码
final List<int>? photo; // 照片数据(原始格式)
IdCardInfo({
required this.cardType,
required this.name,
this.nameEn = '',
required this.gender,
this.genderId = '',
required this.nation,
this.nationId = '',
required this.birthDate,
required this.address,
required this.idNumber,
required this.signOrgan,
this.beginTerm = '',
required this.validTerm,
this.version = '',
this.photoBase64,
this.fingerprintBase64,
this.photo,
});
/// 从Map创建IdCardInfo对象
factory IdCardInfo.fromMap(Map<String, dynamic> map) {
return IdCardInfo(
cardType: map['cardType'] ?? '',
name: map['name'] ?? '',
nameEn: map['nameEn'] ?? '',
gender: map['gender'] ?? '',
genderId: map['genderId'] ?? '',
nation: map['nation'] ?? '',
nationId: map['nationId'] ?? '',
birthDate: map['birthDate'] ?? '',
address: map['address'] ?? '',
idNumber: map['idNumber'] ?? '',
signOrgan: map['signOrgan'] ?? '',
beginTerm: map['beginTerm'] ?? '',
validTerm: map['validTerm'] ?? '',
version: map['version'] ?? '',
photoBase64: map['photoBase64'],
fingerprintBase64: map['fingerprintBase64'],
photo: map['photo'] != null ? List<int>.from(map['photo']) : null,
);
}
/// 从冒号分隔的字符串创建IdCardInfo对象
/// 根据接口文档格式:证件类型:中文姓名:英文姓名:性别:性别代码:民族:民族代码:出生日期:住址:身份证号码:签发机关:发卡日期:卡有效期:证件版本号:头像JPG照片base64编码:指纹特征值base64编码
factory IdCardInfo.fromColonString(String colonString) {
final parts = colonString.split(':');
// 确保至少有16个部分
while (parts.length < 16) {
parts.add('');
}
return IdCardInfo(
cardType: parts[0],
name: parts[1],
nameEn: parts[2],
gender: parts[3],
genderId: parts[4],
nation: parts[5],
nationId: parts[6],
birthDate: parts[7],
address: parts[8],
idNumber: parts[9],
signOrgan: parts[10],
beginTerm: parts[11],
validTerm: parts[12],
version: parts[13],
photoBase64: parts[14].isNotEmpty ? parts[14] : null,
fingerprintBase64: parts[15].isNotEmpty ? parts[15] : null,
);
}
/// 转换为Map
Map<String, dynamic> toMap() {
return {
'cardType': cardType,
'name': name,
'nameEn': nameEn,
'gender': gender,
'genderId': genderId,
'nation': nation,
'nationId': nationId,
'birthDate': birthDate,
'address': address,
'idNumber': idNumber,
'signOrgan': signOrgan,
'beginTerm': beginTerm,
'validTerm': validTerm,
'version': version,
'photoBase64': photoBase64,
'fingerprintBase64': fingerprintBase64,
'photo': photo,
};
}
@override
String toString() {
return 'IdCardInfo{cardType: $cardType, name: $name, nameEn: $nameEn, gender: $gender, genderId: $genderId, nation: $nation, nationId: $nationId, birthDate: $birthDate, address: $address, idNumber: $idNumber, signOrgan: $signOrgan, beginTerm: $beginTerm, validTerm: $validTerm, version: $version}';
}
}
/// 身份证读卡器平台接口
abstract class IdcardPlatform extends PlatformInterface {
/// 构造函数
IdcardPlatform() : super(token: _token);
static final Object _token = Object();
static IdcardPlatform _instance = MethodChannelIdcard();
/// 默认实例
static IdcardPlatform get instance => _instance;
/// 设置平台特定的实现
static set instance(IdcardPlatform instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
/// 获取平台版本
Future<String?> getPlatformVersion() {
throw UnimplementedError('platformVersion() has not been implemented.');
}
/// 获取USB权限
/// [vid] - USB设备的厂商ID
/// [pid] - USB设备的产品ID
Future<int> getUsbPermission(int vid, int pid) {
throw UnimplementedError('getUsbPermission() has not been implemented.');
}
/// 打开设备
/// [portType] - 端口类型,如"USB", "COM", "SKT", "BTH", "AUTO"
/// [portPara] - 端口参数
/// [extendPara] - 扩展参数
/// 返回值大于0表示成功设备句柄其他为失败
Future<int> openDevice({String portType = 'USB', String portPara = '', String extendPara = ''}) {
throw UnimplementedError('openDevice() has not been implemented.');
}
/// 设置当前设备(多设备操作)
/// [devHandle] - 设备句柄
/// 返回值0表示成功非0表示失败
Future<int> setCurrentDevice(int devHandle) {
throw UnimplementedError('setCurrentDevice() has not been implemented.');
}
/// 获取当前设备(多设备操作)
/// 返回值:返回当前设备的句柄
Future<int> getCurrentDevice() {
throw UnimplementedError('getCurrentDevice() has not been implemented.');
}
/// 获取接口库信息
/// 返回值包含version和description的Map
Future<Map<String, String>> getLibraryInfo() {
throw UnimplementedError('getLibraryInfo() has not been implemented.');
}
/// 获取设备型号
/// 返回值:设备型号字符串
Future<String> getTerminalModel() {
throw UnimplementedError('getTerminalModel() has not been implemented.');
}
/// 设备轮询心跳
/// 返回值0表示成功非0表示失败
Future<int> terminalHeartBeat() {
throw UnimplementedError('terminalHeartBeat() has not been implemented.');
}
/// 获取接收数据
/// 返回值:最后一次通讯收到的数据
Future<List<int>> getLastRecvData() {
throw UnimplementedError('getLastRecvData() has not been implemented.');
}
/// 获取设备固件版本
/// 返回值包含firmVersion和hardwareVersion的Map
Future<Map<String, String>> getTerminalFirmVersion() {
throw UnimplementedError('getTerminalFirmVersion() has not been implemented.');
}
/// 获取设备序列号
/// 返回值:设备序列号字符串
Future<String> getTerminalSn() {
throw UnimplementedError('getTerminalSn() has not been implemented.');
}
/// 关闭设备
/// 返回值0表示成功非0表示失败
Future<int> closeDevice() {
throw UnimplementedError('closeDevice() has not been implemented.');
}
/// 寻卡
Future<int> findCard() {
throw UnimplementedError('findCard() has not been implemented.');
}
/// 选卡
Future<int> selectCard() {
throw UnimplementedError('selectCard() has not been implemented.');
}
/// 读取身份证(新版接口,支持多种证件类型)
/// [cardType] - 读取卡类型:
/// 0x00读取二代证或外国人或港澳台
/// 0x01只读二代证
/// 0x02只读外国人
/// 0x03只读港澳台
/// 0x10-0x13对应上述类型但包含指纹信息
/// [infoEncoding] - 返回信息的编码方式:
/// 0x01GB18030编码GBK
/// 0x02UTF16-LE编码
/// 0x03UTF-8编码
/// [timeOut] - 读卡超时时间毫秒0表示不等待>0表示等待放卡
/// 返回值0表示成功非0表示失败成功时返回冒号分隔的身份证信息字符串
Future<Map<String, dynamic>> idReadCard({int cardType = 0x00, int infoEncoding = 0x01, int timeOut = 30000}) {
throw UnimplementedError('idReadCard() has not been implemented.');
}
/// 读取身份证(兼容旧版接口)
/// [cardType] - 卡片类型
/// [infoEncoding] - 信息编码
/// [timeOut] - 超时时间(毫秒)
Future<Map<String, dynamic>> readCard({int cardType = 1, int infoEncoding = 0, int timeOut = 30000}) {
throw UnimplementedError('readCard() has not been implemented.');
}
/// 读取身份证详细信息
Future<IdCardInfo> readCardInfo() {
throw UnimplementedError('readCardInfo() has not been implemented.');
}
/// 读取追加住址
/// 返回值:追加住址信息字符串
Future<String> readNewAddress() {
throw UnimplementedError('readNewAddress() has not been implemented.');
}
/// 获取SAM模块状态
/// 返回值0表示成功非0表示失败
Future<int> getSamStatus() {
throw UnimplementedError('getSamStatus() has not been implemented.');
}
/// 获取SAM模块编号字符串
/// 返回值SAM模块编号字符串
Future<String> getSamIdStr() {
throw UnimplementedError('getSamIdStr() has not been implemented.');
}
}

72
pubspec.yaml Normal file
View File

@@ -0,0 +1,72 @@
name: idcard
description: "一个用于读取中国二代身份证的 Flutter 插件集成了01SDK身份证识别功能支持USB和蓝牙连接方式。"
version: 0.0.1
homepage: https://github.com/your-username/idcard
repository: https://github.com/your-username/idcard
issue_tracker: https://github.com/your-username/idcard/issues
environment:
sdk: '>=3.4.4 <4.0.0'
flutter: '>=3.3.0'
dependencies:
flutter:
sdk: flutter
plugin_platform_interface: ^2.0.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# This section identifies this Flutter project as a plugin project.
# The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.)
# which should be registered in the plugin registry. This is required for
# using method channels.
# The Android 'package' specifies package in which the registered class is.
# This is required for using method channels on Android.
# The 'ffiPlugin' specifies that native code should be built and bundled.
# This is required for using `dart:ffi`.
# All these are used by the tooling to maintain consistency when
# adding or updating assets for this project.
plugin:
platforms:
android:
package: com.example.idcard
pluginClass: IdcardPlugin
# To add assets to your plugin package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
#
# For details regarding assets in packages, see
# https://flutter.dev/assets-and-images/#from-packages
#
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware
# To add custom fonts to your plugin package, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts in packages, see
# https://flutter.dev/custom-fonts/#from-packages

View File

@@ -0,0 +1,27 @@
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:idcard/idcard_method_channel.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
MethodChannelIdcard platform = MethodChannelIdcard();
const MethodChannel channel = MethodChannel('idcard');
setUp(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
channel,
(MethodCall methodCall) async {
return '42';
},
);
});
tearDown(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, null);
});
test('getPlatformVersion', () async {
expect(await platform.getPlatformVersion(), '42');
});
}

29
test/idcard_test.dart Normal file
View File

@@ -0,0 +1,29 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:idcard/idcard.dart';
import 'package:idcard/idcard_platform_interface.dart';
import 'package:idcard/idcard_method_channel.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
class MockIdcardPlatform
with MockPlatformInterfaceMixin
implements IdcardPlatform {
@override
Future<String?> getPlatformVersion() => Future.value('42');
}
void main() {
final IdcardPlatform initialPlatform = IdcardPlatform.instance;
test('$MethodChannelIdcard is the default instance', () {
expect(initialPlatform, isInstanceOf<MethodChannelIdcard>());
});
test('getPlatformVersion', () async {
Idcard idcardPlugin = Idcard();
MockIdcardPlatform fakePlatform = MockIdcardPlatform();
IdcardPlatform.instance = fakePlatform;
expect(await idcardPlugin.getPlatformVersion(), '42');
});
}