commit 12d21c4c90709f2be5d8b5746b6c35452ab0bdbc Author: leon <916117771@qq.com> Date: Tue Mar 31 08:51:00 2026 +0800 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac5aa98 --- /dev/null +++ b/.gitignore @@ -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/ diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..5a5cd8e --- /dev/null +++ b/.metadata @@ -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' diff --git a/01SDK/libs/arm64-v8a/libCommonInterface.so b/01SDK/libs/arm64-v8a/libCommonInterface.so new file mode 100644 index 0000000..f8df50c Binary files /dev/null and b/01SDK/libs/arm64-v8a/libCommonInterface.so differ diff --git a/01SDK/libs/arm64-v8a/libPortCommunication.so b/01SDK/libs/arm64-v8a/libPortCommunication.so new file mode 100644 index 0000000..daac2fd Binary files /dev/null and b/01SDK/libs/arm64-v8a/libPortCommunication.so differ diff --git a/01SDK/libs/arm64-v8a/libTerminalProtocol.so b/01SDK/libs/arm64-v8a/libTerminalProtocol.so new file mode 100644 index 0000000..1eea303 Binary files /dev/null and b/01SDK/libs/arm64-v8a/libTerminalProtocol.so differ diff --git a/01SDK/libs/arm64-v8a/libWltRS.so b/01SDK/libs/arm64-v8a/libWltRS.so new file mode 100644 index 0000000..b319f40 Binary files /dev/null and b/01SDK/libs/arm64-v8a/libWltRS.so differ diff --git a/01SDK/libs/armeabi/libCommonInterface.so b/01SDK/libs/armeabi/libCommonInterface.so new file mode 100644 index 0000000..98ffe93 Binary files /dev/null and b/01SDK/libs/armeabi/libCommonInterface.so differ diff --git a/01SDK/libs/armeabi/libPortCommunication.so b/01SDK/libs/armeabi/libPortCommunication.so new file mode 100644 index 0000000..52fa3f1 Binary files /dev/null and b/01SDK/libs/armeabi/libPortCommunication.so differ diff --git a/01SDK/libs/armeabi/libTerminalProtocol.so b/01SDK/libs/armeabi/libTerminalProtocol.so new file mode 100644 index 0000000..75fda27 Binary files /dev/null and b/01SDK/libs/armeabi/libTerminalProtocol.so differ diff --git a/01SDK/libs/armeabi/libWltRS.so b/01SDK/libs/armeabi/libWltRS.so new file mode 100644 index 0000000..2452f9f Binary files /dev/null and b/01SDK/libs/armeabi/libWltRS.so differ diff --git a/01SDK/src/com/sdses/BlueTooth.java b/01SDK/src/com/sdses/BlueTooth.java new file mode 100644 index 0000000..f4d8579 --- /dev/null +++ b/01SDK/src/com/sdses/BlueTooth.java @@ -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 pairedDevices = mAdapter.getBondedDevices(); + if(pairedDevices.size() <= 0) + { + return -3; + } + + ArrayList ALBluetoothDevice = new ArrayList(); + 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; + } + } + } +} diff --git a/01SDK/src/com/sdses/JniCommonInterface.java b/01SDK/src/com/sdses/JniCommonInterface.java new file mode 100644 index 0000000..107da0a --- /dev/null +++ b/01SDK/src/com/sdses/JniCommonInterface.java @@ -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); +} diff --git a/01SDK/src/com/sdses/UsbHidPort.java b/01SDK/src/com/sdses/UsbHidPort.java new file mode 100644 index 0000000..86c58cd --- /dev/null +++ b/01SDK/src/com/sdses/UsbHidPort.java @@ -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 deviceList = usbManager.getDeviceList(); + Iterator 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(); + } + } +} diff --git a/01SDK/神思标准化接口规范-设备接口层.doc b/01SDK/神思标准化接口规范-设备接口层.doc new file mode 100644 index 0000000..1f1706b Binary files /dev/null and b/01SDK/神思标准化接口规范-设备接口层.doc differ diff --git a/01SDK/身份证接口文档提取.txt b/01SDK/身份证接口文档提取.txt new file mode 100644 index 0000000..9ea7c89 --- /dev/null +++ b/01SDK/身份证接口文档提取.txt @@ -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_PID(261A0011、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表示失败。 + diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..41cc7d8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ba75c69 --- /dev/null +++ b/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6ac09ef --- /dev/null +++ b/README.md @@ -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 + + + + + + + + + + + + + + + + +``` + +### 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 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 result = await _idcardPlugin.readCard( + cardType: 1, + infoEncoding: 0, + timeOut: 30000, +); + +if (result['result'] > 0) { + List 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` | +| `getUsbPermission({int vid, int pid})` | 获取USB权限 | vid: 厂商ID, pid: 产品ID | `Future` | +| `openDevice({String portType, String portPara, String extendPara})` | 打开设备 | portType: 端口类型, portPara: 端口参数, extendPara: 扩展参数 | `Future` - 大于0表示成功(设备句柄),小于等于0表示失败 | +| `closeDevice()` | 关闭设备 | 无 | `Future` - 大于0表示成功,小于等于0表示失败 | +| `findCard()` | 寻卡 | 无 | `Future` - 大于0表示成功,小于等于0表示失败 | +| `selectCard()` | 选卡 | 无 | `Future` - 大于0表示成功,小于等于0表示失败 | +| `readCard({int cardType, int infoEncoding, int timeOut})` | 读取原始数据 | cardType: 卡片类型, infoEncoding: 编码, timeOut: 超时时间 | `Future>` | +| `readCardInfo()` | 读取详细信息 | 无 | `Future` | +| `readCardComplete({int timeOut})` | 完整读卡流程 | timeOut: 超时时间 | `Future` | +| `isDeviceConnected()` | 检查设备连接状态 | 无 | `Future` | + +### 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? photo; // 照片数据(JPEG格式) +} +``` + +## 错误码说明 + +| 错误码 | 说明 | +|--------|------| +| 0 | 成功 | +| -1 | 设备未找到 | +| -2 | 设备接口错误 | +| -3 | 权限被拒绝 | +| 其他 | 具体错误码请参考01SDK文档 | + +## 示例应用 + +查看 `example` 目录中的完整示例应用,演示了如何使用本插件的所有功能。 + +运行示例: + +```bash +cd example +flutter run +``` + +## 故障排除 + +### Android 12+ USB权限问题 + +如果在Android 12或更高版本上遇到"获取USB权限失败"的问题,请尝试以下解决方案: + +#### 1. 检查权限配置 +确保在 `AndroidManifest.xml` 中正确配置了所有必要权限: +```xml + + + +``` + +#### 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和蓝牙连接 + diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..a5744c1 --- /dev/null +++ b/analysis_options.yaml @@ -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 diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..161bdcd --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.cxx diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..7367ad2 --- /dev/null +++ b/android/build.gradle @@ -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 + } + } + } +} diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000..83daae1 --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'idcard' diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..eea1393 --- /dev/null +++ b/android/src/main/AndroidManifest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/src/main/java/com/example/idcard/IdcardPlugin.java b/android/src/main/java/com/example/idcard/IdcardPlugin.java new file mode 100644 index 0000000..e94c0dd --- /dev/null +++ b/android/src/main/java/com/example/idcard/IdcardPlugin.java @@ -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 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 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 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 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 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); + } +} diff --git a/android/src/main/java/com/example/idcard/UsbPermissionActivity.java b/android/src/main/java/com/example/idcard/UsbPermissionActivity.java new file mode 100644 index 0000000..58e4c51 --- /dev/null +++ b/android/src/main/java/com/example/idcard/UsbPermissionActivity.java @@ -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(); + } +} \ No newline at end of file diff --git a/android/src/main/java/com/sdses/BlueTooth.java b/android/src/main/java/com/sdses/BlueTooth.java new file mode 100644 index 0000000..f4d8579 --- /dev/null +++ b/android/src/main/java/com/sdses/BlueTooth.java @@ -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 pairedDevices = mAdapter.getBondedDevices(); + if(pairedDevices.size() <= 0) + { + return -3; + } + + ArrayList ALBluetoothDevice = new ArrayList(); + 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; + } + } + } +} diff --git a/android/src/main/java/com/sdses/JniCommonInterface.java b/android/src/main/java/com/sdses/JniCommonInterface.java new file mode 100644 index 0000000..1b89077 --- /dev/null +++ b/android/src/main/java/com/sdses/JniCommonInterface.java @@ -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); +} diff --git a/android/src/main/java/com/sdses/UsbHidPort.java b/android/src/main/java/com/sdses/UsbHidPort.java new file mode 100644 index 0000000..547f920 --- /dev/null +++ b/android/src/main/java/com/sdses/UsbHidPort.java @@ -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 deviceList = usbManager.getDeviceList(); + Iterator 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(); + } + } +} diff --git a/android/src/main/jniLibs/arm64-v8a/libCommonInterface.so b/android/src/main/jniLibs/arm64-v8a/libCommonInterface.so new file mode 100644 index 0000000..f8df50c Binary files /dev/null and b/android/src/main/jniLibs/arm64-v8a/libCommonInterface.so differ diff --git a/android/src/main/jniLibs/arm64-v8a/libPortCommunication.so b/android/src/main/jniLibs/arm64-v8a/libPortCommunication.so new file mode 100644 index 0000000..daac2fd Binary files /dev/null and b/android/src/main/jniLibs/arm64-v8a/libPortCommunication.so differ diff --git a/android/src/main/jniLibs/arm64-v8a/libTerminalProtocol.so b/android/src/main/jniLibs/arm64-v8a/libTerminalProtocol.so new file mode 100644 index 0000000..1eea303 Binary files /dev/null and b/android/src/main/jniLibs/arm64-v8a/libTerminalProtocol.so differ diff --git a/android/src/main/jniLibs/arm64-v8a/libWltRS.so b/android/src/main/jniLibs/arm64-v8a/libWltRS.so new file mode 100644 index 0000000..b319f40 Binary files /dev/null and b/android/src/main/jniLibs/arm64-v8a/libWltRS.so differ diff --git a/android/src/main/jniLibs/armeabi/libCommonInterface.so b/android/src/main/jniLibs/armeabi/libCommonInterface.so new file mode 100644 index 0000000..98ffe93 Binary files /dev/null and b/android/src/main/jniLibs/armeabi/libCommonInterface.so differ diff --git a/android/src/main/jniLibs/armeabi/libPortCommunication.so b/android/src/main/jniLibs/armeabi/libPortCommunication.so new file mode 100644 index 0000000..52fa3f1 Binary files /dev/null and b/android/src/main/jniLibs/armeabi/libPortCommunication.so differ diff --git a/android/src/main/jniLibs/armeabi/libTerminalProtocol.so b/android/src/main/jniLibs/armeabi/libTerminalProtocol.so new file mode 100644 index 0000000..75fda27 Binary files /dev/null and b/android/src/main/jniLibs/armeabi/libTerminalProtocol.so differ diff --git a/android/src/main/jniLibs/armeabi/libWltRS.so b/android/src/main/jniLibs/armeabi/libWltRS.so new file mode 100644 index 0000000..2452f9f Binary files /dev/null and b/android/src/main/jniLibs/armeabi/libWltRS.so differ diff --git a/android/src/main/res/xml/device_filter.xml b/android/src/main/res/xml/device_filter.xml new file mode 100644 index 0000000..243680b --- /dev/null +++ b/android/src/main/res/xml/device_filter.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/src/test/java/com/example/idcard/IdcardPluginTest.java b/android/src/test/java/com/example/idcard/IdcardPluginTest.java new file mode 100644 index 0000000..955521d --- /dev/null +++ b/android/src/test/java/com/example/idcard/IdcardPluginTest.java @@ -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); + } +} diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 0000000..29a3a50 --- /dev/null +++ b/example/.gitignore @@ -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 diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..3a452f5 --- /dev/null +++ b/example/README.md @@ -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. diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/example/analysis_options.yaml @@ -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 diff --git a/example/android/.gitignore b/example/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/example/android/.gitignore @@ -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 diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle new file mode 100644 index 0000000..483e598 --- /dev/null +++ b/example/android/app/build.gradle @@ -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 = "../.." +} diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..fe06339 --- /dev/null +++ b/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/example/android/app/src/main/java/com/example/idcard_example/MainActivity.java b/example/android/app/src/main/java/com/example/idcard_example/MainActivity.java new file mode 100644 index 0000000..e4e0aab --- /dev/null +++ b/example/android/app/src/main/java/com/example/idcard_example/MainActivity.java @@ -0,0 +1,6 @@ +package com.example.idcard_example; + +import io.flutter.embedding.android.FlutterActivity; + +public class MainActivity extends FlutterActivity { +} diff --git a/example/android/app/src/main/res/drawable-v21/launch_background.xml b/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/android/app/src/main/res/drawable/launch_background.xml b/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/values-night/styles.xml b/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/example/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/android/build.gradle b/example/android/build.gradle new file mode 100644 index 0000000..d2ffbff --- /dev/null +++ b/example/android/build.gradle @@ -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 +} diff --git a/example/android/gradle.properties b/example/android/gradle.properties new file mode 100644 index 0000000..3b5b324 --- /dev/null +++ b/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e1ca574 --- /dev/null +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/example/android/settings.gradle b/example/android/settings.gradle new file mode 100644 index 0000000..536165d --- /dev/null +++ b/example/android/settings.gradle @@ -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" diff --git a/example/integration_test/plugin_integration_test.dart b/example/integration_test/plugin_integration_test.dart new file mode 100644 index 0000000..6774bea --- /dev/null +++ b/example/integration_test/plugin_integration_test.dart @@ -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); + }); +} diff --git a/example/lib/main.dart b/example/lib/main.dart new file mode 100644 index 0000000..ef130d1 --- /dev/null +++ b/example/lib/main.dart @@ -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 createState() => _MyAppState(); +} + +class _MyAppState extends State { + 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 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 _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 _disconnectDevice() async { + try { + await _idcardPlugin.closeDevice(); + setState(() { + _status = '设备已断开'; + _statusMessage = '设备已断开'; + _isConnected = false; + _cardInfo = null; + }); + } catch (e) { + setState(() { + _status = '断开连接失败:$e'; + _statusMessage = '断开连接失败:$e'; + }); + } + } + + /// 读取身份证 + Future _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(Colors.white), + ), + ), + SizedBox(width: 8), + Text('正在读卡...'), + ], + ) + : const Text( + '读取身份证', + style: TextStyle(fontSize: 16), + ), + ), + ), + ), + + // 身份证信息显示 + _buildCardInfoWidget(), + ], + ), + ), + ), + ); + } +} diff --git a/example/pubspec.lock b/example/pubspec.lock new file mode 100644 index 0000000..5acb5f9 --- /dev/null +++ b/example/pubspec.lock @@ -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" diff --git a/example/pubspec.yaml b/example/pubspec.yaml new file mode 100644 index 0000000..dd7d44d --- /dev/null +++ b/example/pubspec.yaml @@ -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 diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart new file mode 100644 index 0000000..14c85fc --- /dev/null +++ b/example/test/widget_test.dart @@ -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, + ); + }); +} diff --git a/lib/idcard.dart b/lib/idcard.dart new file mode 100644 index 0000000..8889558 --- /dev/null +++ b/lib/idcard.dart @@ -0,0 +1,214 @@ + +import 'idcard_platform_interface.dart'; + +/// 身份证读卡器插件主类 +/// 基于神思标准化接口规范实现的Flutter插件 +class Idcard { + /// 获取平台版本 + Future getPlatformVersion() { + return IdcardPlatform.instance.getPlatformVersion(); + } + + /// 获取USB权限 + /// [vid] - USB设备的厂商ID,默认为0x261A(神思USB读卡器厂商ID) + /// [pid] - USB设备的产品ID,默认为0x0011 + Future getUsbPermission({int vid = 0x261A, int pid = 0x0011}) { + return IdcardPlatform.instance.getUsbPermission(vid, pid); + } + + /// 打开设备连接 + /// [portType] - 端口类型,支持"USB"、"COM"、"SKT"、"BTH"、"AUTO" + /// [portPara] - 端口参数,根据端口类型不同而不同 + /// [extendPara] - 扩展参数 + /// 返回值:大于0表示成功(设备句柄),其他为失败 + Future openDevice({String portType = 'USB', String portPara = '', String extendPara = ''}) { + return IdcardPlatform.instance.openDevice( + portType: portType, + portPara: portPara, + extendPara: extendPara, + ); + } + + /// 设置当前设备(多设备操作) + /// [devHandle] - 设备句柄 + /// 返回值:0表示成功;非0表示失败 + Future setCurrentDevice(int devHandle) { + return IdcardPlatform.instance.setCurrentDevice(devHandle); + } + + /// 获取当前设备(多设备操作) + /// 返回值:返回当前设备的句柄 + Future getCurrentDevice() { + return IdcardPlatform.instance.getCurrentDevice(); + } + + /// 获取接口库信息 + /// 返回值:包含version和description的Map + Future> getLibraryInfo() { + return IdcardPlatform.instance.getLibraryInfo(); + } + + /// 获取设备型号 + /// 返回值:设备型号字符串 + Future getTerminalModel() { + return IdcardPlatform.instance.getTerminalModel(); + } + + /// 设备轮询心跳 + /// 用于检测与读卡器是否已建立连接并且通讯正常 + /// 返回值:0表示成功;非0表示失败 + Future terminalHeartBeat() { + return IdcardPlatform.instance.terminalHeartBeat(); + } + + /// 获取接收数据 + /// 获取最后一次通讯收到的数据,一般用于获取读卡器协议层错误信息 + /// 返回值:最后一次通讯收到的数据 + Future> getLastRecvData() { + return IdcardPlatform.instance.getLastRecvData(); + } + + /// 获取设备固件版本 + /// 返回值:包含firmVersion和hardwareVersion的Map + Future> getTerminalFirmVersion() { + return IdcardPlatform.instance.getTerminalFirmVersion(); + } + + /// 获取设备序列号 + /// 返回值:设备序列号字符串 + Future getTerminalSn() { + return IdcardPlatform.instance.getTerminalSn(); + } + + /// 关闭设备连接 + /// 返回值:0表示成功;非0表示失败 + Future closeDevice() { + return IdcardPlatform.instance.closeDevice(); + } + + /// 寻找身份证卡片 + /// 返回值:大于0表示成功找到卡片,小于等于0表示错误码 + Future findCard() { + return IdcardPlatform.instance.findCard(); + } + + /// 选择身份证卡片 + /// 返回值:大于0表示成功选择卡片,小于等于0表示错误码 + Future selectCard() { + return IdcardPlatform.instance.selectCard(); + } + + /// 读取身份证(新版接口,支持多种证件类型) + /// [cardType] - 读取卡类型: + /// 0x00:读取二代证或外国人或港澳台 + /// 0x01:只读二代证 + /// 0x02:只读外国人 + /// 0x03:只读港澳台 + /// 0x10-0x13:对应上述类型但包含指纹信息 + /// [infoEncoding] - 返回信息的编码方式: + /// 0x01:GB18030编码(GBK) + /// 0x02:UTF16-LE编码 + /// 0x03:UTF-8编码 + /// [timeOut] - 读卡超时时间(毫秒),0表示不等待,>0表示等待放卡 + /// 返回值:包含result(错误码)和data(冒号分隔的身份证信息字符串)的Map + Future> 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> readCard({int cardType = 1, int infoEncoding = 0, int timeOut = 30000}) { + return IdcardPlatform.instance.readCard( + cardType: cardType, + infoEncoding: infoEncoding, + timeOut: timeOut, + ); + } + + /// 读取身份证详细信息 + /// 返回值:IdCardInfo对象,包含姓名、性别、民族等详细信息 + /// 抛出异常:如果读取失败会抛出Exception + Future readCardInfo() { + return IdcardPlatform.instance.readCardInfo(); + } + + /// 读取追加住址 + /// 返回值:追加住址信息字符串 + Future readNewAddress() { + return IdcardPlatform.instance.readNewAddress(); + } + + /// 获取SAM模块状态 + /// 返回值:0表示成功;非0表示失败 + Future getSamStatus() { + return IdcardPlatform.instance.getSamStatus(); + } + + /// 获取SAM模块编号字符串 + /// 返回值:SAM模块编号字符串 + Future getSamIdStr() { + return IdcardPlatform.instance.getSamIdStr(); + } + + /// 完整的身份证读取流程 + /// 自动执行寻卡、选卡、读取信息的完整流程 + /// [timeOut] - 超时时间(毫秒),默认30秒 + /// [retryInterval] - 重试间隔时间(毫秒),默认500毫秒 + /// 返回值:IdCardInfo对象 + /// 抛出异常:如果超时或读取失败会抛出Exception + Future 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 isDeviceConnected() async { + try { + final version = await getPlatformVersion(); + return version != null; + } catch (e) { + return false; + } + } +} diff --git a/lib/idcard_method_channel.dart b/lib/idcard_method_channel.dart new file mode 100644 index 0000000..029ddee --- /dev/null +++ b/lib/idcard_method_channel.dart @@ -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 getPlatformVersion() async { + final version = await methodChannel.invokeMethod('getPlatformVersion'); + return version; + } + + @override + Future getUsbPermission(int vid, int pid) async { + final result = await methodChannel.invokeMethod('getUsbPermission', { + 'vid': vid, + 'pid': pid, + }); + return result ?? -1; + } + + @override + Future openDevice({String portType = 'USB', String portPara = '', String extendPara = ''}) async { + final result = await methodChannel.invokeMethod('openDevice', { + 'portType': portType, + 'portPara': portPara, + 'extendPara': extendPara, + }); + return result ?? -1; + } + + @override + Future setCurrentDevice(int devHandle) async { + final result = await methodChannel.invokeMethod('setCurrentDevice', { + 'devHandle': devHandle, + }); + return result ?? -1; + } + + @override + Future getCurrentDevice() async { + final result = await methodChannel.invokeMethod('getCurrentDevice'); + return result ?? -1; + } + + @override + Future> getLibraryInfo() async { + final result = await methodChannel.invokeMethod('getLibraryInfo'); + if (result == null) return {'version': '', 'description': ''}; + // 安全地转换类型 + final Map data = Map.from(result as Map); + return { + 'version': data['version']?.toString() ?? '', + 'description': data['description']?.toString() ?? '', + }; + } + + @override + Future getTerminalModel() async { + final result = await methodChannel.invokeMethod('getTerminalModel'); + return result ?? ''; + } + + @override + Future terminalHeartBeat() async { + final result = await methodChannel.invokeMethod('terminalHeartBeat'); + return result ?? -1; + } + + @override + Future> getLastRecvData() async { + final result = await methodChannel.invokeMethod>('getLastRecvData'); + return result?.cast() ?? []; + } + + @override + Future> getTerminalFirmVersion() async { + final result = await methodChannel.invokeMethod('getTerminalFirmVersion'); + if (result == null) return {'firmVersion': '', 'hardwareVersion': ''}; + // 安全地转换类型 + final Map data = Map.from(result as Map); + return { + 'firmVersion': data['firmVersion']?.toString() ?? '', + 'hardwareVersion': data['hardwareVersion']?.toString() ?? '', + }; + } + + @override + Future getTerminalSn() async { + final result = await methodChannel.invokeMethod('getTerminalSn'); + return result ?? ''; + } + + @override + Future closeDevice() async { + final result = await methodChannel.invokeMethod('closeDevice'); + return result ?? -1; + } + + @override + Future findCard() async { + final result = await methodChannel.invokeMethod('findCard'); + return result ?? -1; + } + + @override + Future selectCard() async { + final result = await methodChannel.invokeMethod('selectCard'); + return result ?? -1; + } + + @override + Future> 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.from(result as Map); + } + + @override + Future> 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.from(result as Map); + } + + @override + Future 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 readNewAddress() async { + final result = await methodChannel.invokeMethod('readNewAddress'); + return result ?? ''; + } + + @override + Future getSamStatus() async { + final result = await methodChannel.invokeMethod('getSamStatus'); + return result ?? -1; + } + + @override + Future getSamIdStr() async { + final result = await methodChannel.invokeMethod('getSamIdStr'); + return result ?? ''; + } +} diff --git a/lib/idcard_platform_interface.dart b/lib/idcard_platform_interface.dart new file mode 100644 index 0000000..a6fd132 --- /dev/null +++ b/lib/idcard_platform_interface.dart @@ -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? 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 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.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 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 getPlatformVersion() { + throw UnimplementedError('platformVersion() has not been implemented.'); + } + + /// 获取USB权限 + /// [vid] - USB设备的厂商ID + /// [pid] - USB设备的产品ID + Future getUsbPermission(int vid, int pid) { + throw UnimplementedError('getUsbPermission() has not been implemented.'); + } + + /// 打开设备 + /// [portType] - 端口类型,如"USB", "COM", "SKT", "BTH", "AUTO" + /// [portPara] - 端口参数 + /// [extendPara] - 扩展参数 + /// 返回值:大于0表示成功(设备句柄),其他为失败 + Future openDevice({String portType = 'USB', String portPara = '', String extendPara = ''}) { + throw UnimplementedError('openDevice() has not been implemented.'); + } + + /// 设置当前设备(多设备操作) + /// [devHandle] - 设备句柄 + /// 返回值:0表示成功;非0表示失败 + Future setCurrentDevice(int devHandle) { + throw UnimplementedError('setCurrentDevice() has not been implemented.'); + } + + /// 获取当前设备(多设备操作) + /// 返回值:返回当前设备的句柄 + Future getCurrentDevice() { + throw UnimplementedError('getCurrentDevice() has not been implemented.'); + } + + /// 获取接口库信息 + /// 返回值:包含version和description的Map + Future> getLibraryInfo() { + throw UnimplementedError('getLibraryInfo() has not been implemented.'); + } + + /// 获取设备型号 + /// 返回值:设备型号字符串 + Future getTerminalModel() { + throw UnimplementedError('getTerminalModel() has not been implemented.'); + } + + /// 设备轮询心跳 + /// 返回值:0表示成功;非0表示失败 + Future terminalHeartBeat() { + throw UnimplementedError('terminalHeartBeat() has not been implemented.'); + } + + /// 获取接收数据 + /// 返回值:最后一次通讯收到的数据 + Future> getLastRecvData() { + throw UnimplementedError('getLastRecvData() has not been implemented.'); + } + + /// 获取设备固件版本 + /// 返回值:包含firmVersion和hardwareVersion的Map + Future> getTerminalFirmVersion() { + throw UnimplementedError('getTerminalFirmVersion() has not been implemented.'); + } + + /// 获取设备序列号 + /// 返回值:设备序列号字符串 + Future getTerminalSn() { + throw UnimplementedError('getTerminalSn() has not been implemented.'); + } + + /// 关闭设备 + /// 返回值:0表示成功;非0表示失败 + Future closeDevice() { + throw UnimplementedError('closeDevice() has not been implemented.'); + } + + /// 寻卡 + Future findCard() { + throw UnimplementedError('findCard() has not been implemented.'); + } + + /// 选卡 + Future selectCard() { + throw UnimplementedError('selectCard() has not been implemented.'); + } + + /// 读取身份证(新版接口,支持多种证件类型) + /// [cardType] - 读取卡类型: + /// 0x00:读取二代证或外国人或港澳台 + /// 0x01:只读二代证 + /// 0x02:只读外国人 + /// 0x03:只读港澳台 + /// 0x10-0x13:对应上述类型但包含指纹信息 + /// [infoEncoding] - 返回信息的编码方式: + /// 0x01:GB18030编码(GBK) + /// 0x02:UTF16-LE编码 + /// 0x03:UTF-8编码 + /// [timeOut] - 读卡超时时间(毫秒),0表示不等待,>0表示等待放卡 + /// 返回值:0表示成功;非0表示失败,成功时返回冒号分隔的身份证信息字符串 + Future> idReadCard({int cardType = 0x00, int infoEncoding = 0x01, int timeOut = 30000}) { + throw UnimplementedError('idReadCard() has not been implemented.'); + } + + /// 读取身份证(兼容旧版接口) + /// [cardType] - 卡片类型 + /// [infoEncoding] - 信息编码 + /// [timeOut] - 超时时间(毫秒) + Future> readCard({int cardType = 1, int infoEncoding = 0, int timeOut = 30000}) { + throw UnimplementedError('readCard() has not been implemented.'); + } + + /// 读取身份证详细信息 + Future readCardInfo() { + throw UnimplementedError('readCardInfo() has not been implemented.'); + } + + /// 读取追加住址 + /// 返回值:追加住址信息字符串 + Future readNewAddress() { + throw UnimplementedError('readNewAddress() has not been implemented.'); + } + + /// 获取SAM模块状态 + /// 返回值:0表示成功;非0表示失败 + Future getSamStatus() { + throw UnimplementedError('getSamStatus() has not been implemented.'); + } + + /// 获取SAM模块编号字符串 + /// 返回值:SAM模块编号字符串 + Future getSamIdStr() { + throw UnimplementedError('getSamIdStr() has not been implemented.'); + } +} diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..ada43d9 --- /dev/null +++ b/pubspec.yaml @@ -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 diff --git a/test/idcard_method_channel_test.dart b/test/idcard_method_channel_test.dart new file mode 100644 index 0000000..d5f6aa4 --- /dev/null +++ b/test/idcard_method_channel_test.dart @@ -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'); + }); +} diff --git a/test/idcard_test.dart b/test/idcard_test.dart new file mode 100644 index 0000000..52a6c5b --- /dev/null +++ b/test/idcard_test.dart @@ -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 getPlatformVersion() => Future.value('42'); +} + +void main() { + final IdcardPlatform initialPlatform = IdcardPlatform.instance; + + test('$MethodChannelIdcard is the default instance', () { + expect(initialPlatform, isInstanceOf()); + }); + + test('getPlatformVersion', () async { + Idcard idcardPlugin = Idcard(); + MockIdcardPlatform fakePlatform = MockIdcardPlatform(); + IdcardPlatform.instance = fakePlatform; + + expect(await idcardPlugin.getPlatformVersion(), '42'); + }); +}