init
This commit is contained in:
29
.gitignore
vendored
Normal file
29
.gitignore
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
/pubspec.lock
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
build/
|
||||
30
.metadata
Normal file
30
.metadata
Normal file
@@ -0,0 +1,30 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "b0850beeb25f6d5b10426284f506557f66181b36"
|
||||
channel: "stable"
|
||||
|
||||
project_type: plugin
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: b0850beeb25f6d5b10426284f506557f66181b36
|
||||
base_revision: b0850beeb25f6d5b10426284f506557f66181b36
|
||||
- platform: android
|
||||
create_revision: b0850beeb25f6d5b10426284f506557f66181b36
|
||||
base_revision: b0850beeb25f6d5b10426284f506557f66181b36
|
||||
|
||||
# User provided section
|
||||
|
||||
# List of Local paths (relative to this file) that should be
|
||||
# ignored by the migrate tool.
|
||||
#
|
||||
# Files that are not part of the templates will be ignored by default.
|
||||
unmanaged_files:
|
||||
- 'lib/main.dart'
|
||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||
BIN
01SDK/libs/arm64-v8a/libCommonInterface.so
Normal file
BIN
01SDK/libs/arm64-v8a/libCommonInterface.so
Normal file
Binary file not shown.
BIN
01SDK/libs/arm64-v8a/libPortCommunication.so
Normal file
BIN
01SDK/libs/arm64-v8a/libPortCommunication.so
Normal file
Binary file not shown.
BIN
01SDK/libs/arm64-v8a/libTerminalProtocol.so
Normal file
BIN
01SDK/libs/arm64-v8a/libTerminalProtocol.so
Normal file
Binary file not shown.
BIN
01SDK/libs/arm64-v8a/libWltRS.so
Normal file
BIN
01SDK/libs/arm64-v8a/libWltRS.so
Normal file
Binary file not shown.
BIN
01SDK/libs/armeabi/libCommonInterface.so
Normal file
BIN
01SDK/libs/armeabi/libCommonInterface.so
Normal file
Binary file not shown.
BIN
01SDK/libs/armeabi/libPortCommunication.so
Normal file
BIN
01SDK/libs/armeabi/libPortCommunication.so
Normal file
Binary file not shown.
BIN
01SDK/libs/armeabi/libTerminalProtocol.so
Normal file
BIN
01SDK/libs/armeabi/libTerminalProtocol.so
Normal file
Binary file not shown.
BIN
01SDK/libs/armeabi/libWltRS.so
Normal file
BIN
01SDK/libs/armeabi/libWltRS.so
Normal file
Binary file not shown.
170
01SDK/src/com/sdses/BlueTooth.java
Normal file
170
01SDK/src/com/sdses/BlueTooth.java
Normal file
@@ -0,0 +1,170 @@
|
||||
package com.sdses;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothSocket;
|
||||
|
||||
public class BlueTooth
|
||||
{
|
||||
private static UUID SS_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
|
||||
private static BluetoothAdapter mAdapter = null;
|
||||
private static BluetoothDevice mDevice = null;
|
||||
private static BluetoothSocket mSocket = null;
|
||||
private static InputStream mInputStream = null;
|
||||
private static OutputStream mOutputStream = null;
|
||||
|
||||
public static long BthConnect(String BlueToothName)
|
||||
{
|
||||
mAdapter = BluetoothAdapter.getDefaultAdapter();
|
||||
if (mAdapter == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!mAdapter.isEnabled())
|
||||
{
|
||||
return -2;
|
||||
/*
|
||||
mAdapter.enable();
|
||||
if (!mAdapter.isEnabled())
|
||||
{
|
||||
return -2;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
//获得已配对的蓝牙设备列表
|
||||
Set<BluetoothDevice> pairedDevices = mAdapter.getBondedDevices();
|
||||
if(pairedDevices.size() <= 0)
|
||||
{
|
||||
return -3;
|
||||
}
|
||||
|
||||
ArrayList<BluetoothDevice> ALBluetoothDevice = new ArrayList<BluetoothDevice>();
|
||||
for (BluetoothDevice device : pairedDevices)
|
||||
{
|
||||
ALBluetoothDevice.add(device);
|
||||
}
|
||||
|
||||
//mAdapter.cancelDiscovery();
|
||||
|
||||
//查找设备
|
||||
for(int i = 0; i < ALBluetoothDevice.size(); i++)
|
||||
{
|
||||
BluetoothDevice cDevice = (BluetoothDevice)ALBluetoothDevice.get(i);
|
||||
String theBlueToothName = cDevice.getName();
|
||||
if(theBlueToothName.indexOf(BlueToothName) < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//开始连接设备
|
||||
mDevice = cDevice;
|
||||
try
|
||||
{
|
||||
mSocket = mDevice.createRfcommSocketToServiceRecord(SS_UUID);
|
||||
mSocket.connect();
|
||||
mInputStream = mSocket.getInputStream();
|
||||
mOutputStream = mSocket.getOutputStream();
|
||||
return 0;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return -4;
|
||||
}
|
||||
}
|
||||
|
||||
return -5; //没有配对设备
|
||||
}
|
||||
|
||||
public static long BthSendSocket(byte[] buffer, int len)
|
||||
{
|
||||
if(mOutputStream == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
mOutputStream.write(buffer, 0, len);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
return -2;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
public static long BthRecvSocket(byte[] buffer)
|
||||
{
|
||||
if(mInputStream == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// int nAvailable = mInputStream.available();
|
||||
// if(nAvailable <= 0)
|
||||
// {
|
||||
// return 0;
|
||||
// }
|
||||
|
||||
return mInputStream.read(buffer, 0, 4096);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
|
||||
public static void BthDisconnect()
|
||||
{
|
||||
if(mInputStream != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
mInputStream.close();
|
||||
mInputStream = null;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(mOutputStream != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
mOutputStream.close();
|
||||
mOutputStream = null;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(mSocket != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
mSocket.close();
|
||||
mSocket = null;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
122
01SDK/src/com/sdses/JniCommonInterface.java
Normal file
122
01SDK/src/com/sdses/JniCommonInterface.java
Normal file
@@ -0,0 +1,122 @@
|
||||
package com.sdses;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public class JniCommonInterface
|
||||
{
|
||||
static
|
||||
{
|
||||
System.loadLibrary("WltRS");
|
||||
System.loadLibrary("PortCommunication");
|
||||
System.loadLibrary("TerminalProtocol");
|
||||
System.loadLibrary("CommonInterface");
|
||||
}
|
||||
|
||||
public static long GetUsbPermission(Context ctx, int Vid, int Pid)
|
||||
{
|
||||
return UsbHidPort.GetUsbPermission(ctx, Vid, Pid);
|
||||
}
|
||||
|
||||
public static native long SetLogFileEx(String LogFileName);
|
||||
public static native long SetTerminalLibrary(String LibraryFileName);
|
||||
public static native long GetLibraryInfo(byte[] Version, byte[] Description);
|
||||
public static native long SetAutoPara(String PortType, String PortPara, String ExtendPara, String DllName, int UsingGA467);
|
||||
public static native long OpenDevice(String PortType, String PortPara, String ExtendPara);
|
||||
public static native long SetCurrentDevice(long DevHandle);
|
||||
public static native long GetCurrentDevice();
|
||||
public static native long CloseDevice();
|
||||
public static native long CommandTransmit(long Command, long CmdDataLength, byte[] CmdData, byte[] StatusWords, long[] RecvDataLength, byte[] RecvData, long TimeOut);
|
||||
public static native long GA467Transmit(long Command, long CmdDataLength, byte[] CmdData, byte[] StatusWords, long[] RecvDataLength, byte[] RecvData, long TimeOut);
|
||||
public static native long TerminalGetModel(byte[] TerminalModel);
|
||||
public static native long TerminalGetFirmVersion(byte[] FirmVersion, byte[]HardwareVersion);
|
||||
public static native long TerminalHeartBeat();
|
||||
public static native long LedOnOff(byte LedNo, byte OnOff);
|
||||
public static native long LedBlink(byte LedNo, long OnTimeMs, long OffTimeMs, byte BlinkCount, byte FinalState);
|
||||
public static native long GetLastRecvData(byte[] LastRecvData);
|
||||
|
||||
public static native long IdFindCard();
|
||||
public static native long IdSelectCard();
|
||||
public static native long IdReadBaseMsg(byte[] pucCHMsg, long[] puiCHMsgLen, byte[] pucPHMsg, long[] puiPHMsgLen);
|
||||
public static native long IdReadBaseFpMsg(byte[] pucCHMsg, long[] puiCHMsgLen, byte[] pucPHMsg, long[] puiPHMsgLen, byte[] pucFPMsg, long[] puiFPMsgLen);
|
||||
public static native long IdReadNewAppMsg(byte[] pucAppMsg, long[] puiAppMsgLen);
|
||||
public static native long IdReadSn(byte[] SN, long[] SNLen);
|
||||
public static native long IdApdu(long SendApduLen, byte[] SendApdu, byte[] RecvApdu, long[] RecvApduLen);
|
||||
public static native long SdtFindCard(byte[] pucManaInfo);
|
||||
public static native long SdtSelectCard(byte[] pucManaMsg);
|
||||
public static native long SdtReadBaseMsg(byte[] pucCHMsg, long[] puiCHMsgLen, byte[] pucPHMsg, long[] puiPHMsgLen);
|
||||
public static native long SdtReadBaseFpMsg(byte[] pucCHMsg, long[] puiCHMsgLen, byte[] pucPHMsg, long[] puiPHMsgLen, byte[] pucFPMsg, long[] puiFPMsgLen);
|
||||
public static native long SdtReadNewAppMsg(byte[] pucAppMsg, long[] puiAppMsgLen);
|
||||
public static native long SamGetStatus();
|
||||
public static native long SamGetId(byte[] SamId, long[] SamIdLen);
|
||||
public static native long SamGetIdStr(byte[] SamIdStr);
|
||||
public static native long SdtSamGetStatus();
|
||||
public static native long SdtSamGetId(byte[] SamId, long[] SamIdLen);
|
||||
public static native long SdtSamGetIdStr(byte[] SamIdStr);
|
||||
|
||||
public static native long IdReadCard(byte CardType, byte InfoEncoding, byte[] IdCardInfo, long TimeOutMs);
|
||||
public static native long SdtReadCard(byte CardType, byte InfoEncoding, byte[] IdCardInfo, long TimeOutMs);
|
||||
public static native long IdReadNewAddress(byte[] NewAddress);
|
||||
public static native long SdtReadNewAddress(byte[] NewAddress);
|
||||
public static native long IdCardGetName(byte[] Name);
|
||||
public static native long IdCardGetNameEn(byte[] NameEn);
|
||||
public static native long IdCardGetGender(byte[] Gender);
|
||||
public static native long IdCardGetGenderId(byte[] GenderId);
|
||||
public static native long IdCardGetNation(byte[] Nation);
|
||||
public static native long IdCardGetNationId(byte[] NationId);
|
||||
public static native long IdCardGetBirthDate(byte[] BirthDate);
|
||||
public static native long IdCardGetAddress(byte[] Address);
|
||||
public static native long IdCardGetIdNumber(byte[] IdNumber);
|
||||
public static native long IdCardGetSignOrgan(byte[] SignOrgan);
|
||||
public static native long IdCardGetBeginTerm(byte[] BeginTerm);
|
||||
public static native long IdCardGetValidTerm(byte[] ValidTerm);
|
||||
public static native long IdCardGetFPBuffer(byte[] FPBuffer, long[] FPBufferLen);
|
||||
public static native long IdCardGetPhotoFile(String PhotoFile);
|
||||
public static native long IdCardGetPhotoBuffer(byte WltBmpJpg, byte[] PhotoBuffer, long[] PhotoBufferLen);
|
||||
|
||||
public static native long MIdSamGetCert(byte[] Cert, long[] CertLen);
|
||||
public static native long MIdSamEnable(long EnableDataLen, byte[] EnableData);
|
||||
public static native long MIdSamDisable(long DisableDataLen, byte[] DisableData);
|
||||
public static native long MIdSamGetEnableState(byte[] EnableState, long[] RemainNum, byte[] EnableTime, byte[] DisableTime, byte[] SecondCert, long[] SecondCertLen, byte[] EncryptCert, long[] EncryptCertLen, byte[] SignCert, long[] SignCertLen);
|
||||
public static native long MIdSamAuthRequest(byte[] RequestData56);
|
||||
public static native long MIdSamAuthConfirm(byte[] ConfirmData89);
|
||||
public static native long MIdReadChkData(byte[] Random16, byte[] RemainAuthNum, byte[] SamId22, byte[] BeginAndValidTerm32, byte[] ShortCode16, byte[] CheckData, long[] CheckDataLen, byte[] Sign64);
|
||||
public static native long MIdReadChkDataPF(byte[] Random16, byte[] RemainAuthNum, byte[] SamId22, byte[] BeginAndValidTerm32, byte[] ShortCode16, byte[] CheckData, long[] CheckDataLen, byte[] Hash32, byte[] Sign64, byte[] PH, long[] PHLen, byte[] FP, long[] FPLen);
|
||||
public static native long MIdCheckShortLongCode(byte[] Random16, byte ShortCodeCount, byte[] ShortCode, byte LongCodeCount, byte[] LongCode, byte[] RemainAuthNum, byte[] SamId22, byte[] BeginAndValidTerm32, byte[] MatchCode, byte[] MatchCodeLen, byte[] CheckData, long[] CheckDataLen, byte[] Sign64);
|
||||
public static native long MIdCheckShortLongCodePF(byte[] Random16, byte ShortCodeCount, byte[] ShortCode, byte LongCodeCount, byte[] LongCode, byte[] RemainAuthNum, byte[] SamId22,byte[] BeginAndValidTerm32, byte[] MatchCode, byte[] MatchCodeLen, byte[] CheckData, long[] CheckDataLen, byte[] Hash32, byte[] Sign64, byte[] PH, long[] PHLen, byte[] FP, long[] FPLen);
|
||||
public static native long MIdCheckProperty(byte[] Random16, byte PropertyCode, byte PropertyValLen, byte[] PropertyVal, byte[] RemainAuthNum, byte[] SamId22, byte[] CheckResult, byte[] CheckData, long[] CheckDataLen, byte[] Sign64);
|
||||
|
||||
public static native long MagRead(byte Tracks, byte[] TrackData1, byte[] TrackData2, byte[] TrackData3, byte TimeOutSec);
|
||||
public static native long MagWrite(byte Tracks, String TrackData1, String TrackData2, String TrackData3, byte TimeOutSec);
|
||||
|
||||
public static native long QrRead(byte[] QrData, byte TimeOutSec);
|
||||
public static native long QrCancel();
|
||||
public static native long QrAsynEnable(byte TimeOutSec);
|
||||
public static native long QrAsynRead(byte[] QrData);
|
||||
public static native long QrAsynDisable();
|
||||
|
||||
public static native long M1FindCard(byte[] UID, long[] UIDLen);
|
||||
public static native long M1Authentication(byte KeyType, byte SecAddr, byte[] Key, byte[] UID);
|
||||
public static native long M1ReadBlock(byte BlockAddr, byte[] BlockData, long[] BlockDataLen);
|
||||
public static native long M1WriteBlock(byte BlockAddr, long BlockDataLen, byte[] BlockData);
|
||||
public static native long M1Halt();
|
||||
|
||||
public static native long FpCapFeature(byte[] Feature, long[] FeatureLen);
|
||||
public static native long FpMatchFeature(long FeatureLen1, byte[] Feature1, long FeatureLen2, byte[] Feature2, long[] Score);
|
||||
|
||||
public static native long SsseReadCard(int iType, byte[] SSCardInfo, byte[] SSErrorInfo);
|
||||
public static native long SsseReadCard2(int iType, byte[] SSCardInfo, byte[] SSErrorInfo);
|
||||
public static native long SsseGetCardInfo(String Tag, byte[] SSCardInfo);
|
||||
|
||||
public static native long CpuPowerOn(byte Slot, byte[] ATRS, long[] ATRSLen);
|
||||
public static native long CpuApdu(byte Slot, long SendApduLen, byte[] SendApdu, byte[] RecvApdu, long[] RecvApduLen);
|
||||
public static native long CpuPowerOff(byte Slot);
|
||||
|
||||
public static native long IccGetCardInfo(int ICtype, String AIDList, String TagList, byte[] IcCardInfo);
|
||||
public static native long IccGetARQC(int ICtype, String trData, String AIDList, byte[] ARQC, byte[] trAppData);
|
||||
public static native long IccARPCExeScript(int ICtype, String trData, String ARPC, String trAppData, byte[] ScriptResult, byte[] TC);
|
||||
public static native long IccGetTrDetail(int ICtype, String AIDList, byte[] TrDetail);
|
||||
public static native long IccGetLoadDetail(int ICtype, String AIDList, byte[] LoadDetail);
|
||||
|
||||
public static native long HexToAsc(byte[] Hex, long HexLength, byte[] Asc);
|
||||
public static native long AscToHex(String Asc, long HexLength, byte[] Hex);
|
||||
}
|
||||
273
01SDK/src/com/sdses/UsbHidPort.java
Normal file
273
01SDK/src/com/sdses/UsbHidPort.java
Normal file
@@ -0,0 +1,273 @@
|
||||
package com.sdses;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.hardware.usb.UsbConstants;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.hardware.usb.UsbDeviceConnection;
|
||||
import android.hardware.usb.UsbEndpoint;
|
||||
import android.hardware.usb.UsbInterface;
|
||||
import android.hardware.usb.UsbManager;
|
||||
|
||||
public class UsbHidPort
|
||||
{
|
||||
private static final String ACTION_USB_PERMISSION = "com.sdses.JniCommonInterface.USB_PERMISSION";
|
||||
|
||||
private static final BroadcastReceiver mUsbReceiver = new BroadcastReceiver()
|
||||
{
|
||||
public void onReceive(Context context, Intent intent)
|
||||
{
|
||||
String action = intent.getAction();
|
||||
if (action.equals(ACTION_USB_PERMISSION))
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false))
|
||||
{
|
||||
if (device != null)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static UsbManager usbManager = null;
|
||||
public static UsbDevice usbDevice = null;
|
||||
public static UsbInterface usbInterface = null;
|
||||
public static UsbDeviceConnection usbConnection = null;
|
||||
public static UsbEndpoint epOut;
|
||||
public static UsbEndpoint epIn;
|
||||
public static int SendBlockSize = 0;
|
||||
public static int RecvBlockSize = 0;
|
||||
|
||||
public static synchronized long GetUsbPermission(Context ctx, int Vid, int Pid)
|
||||
{
|
||||
try
|
||||
{
|
||||
usbManager = (UsbManager)ctx.getSystemService(Context.USB_SERVICE);
|
||||
HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();
|
||||
Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
|
||||
|
||||
usbDevice = null;
|
||||
while (deviceIterator.hasNext())
|
||||
{
|
||||
UsbDevice device = deviceIterator.next();
|
||||
if ((device.getVendorId() == Vid) && (device.getProductId() == Pid))
|
||||
{
|
||||
usbDevice = device;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (usbDevice == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(usbDevice.getInterfaceCount() <= 0)
|
||||
{
|
||||
return -2;
|
||||
}
|
||||
|
||||
usbInterface = usbDevice.getInterface(0);
|
||||
if(usbInterface == null)
|
||||
{
|
||||
return -2;
|
||||
}
|
||||
|
||||
// 判断是否有权限
|
||||
if(!usbManager.hasPermission(usbDevice))
|
||||
{
|
||||
PendingIntent mPermissionIntent = PendingIntent.getBroadcast(ctx, 0, new Intent(ACTION_USB_PERMISSION), 0);
|
||||
IntentFilter permissionFilter = new IntentFilter(ACTION_USB_PERMISSION);
|
||||
ctx.registerReceiver(mUsbReceiver, permissionFilter);
|
||||
usbManager.requestPermission(usbDevice, mPermissionIntent);
|
||||
System.out.print("Requesting Usb Permission");
|
||||
while(!usbManager.hasPermission(usbDevice))
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
return -99;
|
||||
}
|
||||
}
|
||||
|
||||
public static long UsbOpenPort(int Vid, int Pid)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (usbDevice == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
usbConnection = usbManager.openDevice(usbDevice);
|
||||
if(usbConnection == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(usbInterface == null)
|
||||
{
|
||||
return -2;
|
||||
}
|
||||
|
||||
if(!usbConnection.claimInterface(usbInterface, true))
|
||||
{
|
||||
usbConnection.close();
|
||||
usbConnection = null;
|
||||
return -2;
|
||||
}
|
||||
|
||||
for(int i = 0; i < usbInterface.getEndpointCount(); i++)
|
||||
{
|
||||
UsbEndpoint endPoint = usbInterface.getEndpoint(i);
|
||||
switch(endPoint.getType())
|
||||
{
|
||||
case UsbConstants.USB_ENDPOINT_XFER_BULK:
|
||||
case UsbConstants.USB_ENDPOINT_XFER_INT:
|
||||
{
|
||||
switch(endPoint.getDirection())
|
||||
{
|
||||
case UsbConstants.USB_DIR_OUT:
|
||||
{
|
||||
epOut = endPoint;
|
||||
break;
|
||||
}
|
||||
case UsbConstants.USB_DIR_IN:
|
||||
{
|
||||
epIn = endPoint;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UsbConstants.USB_ENDPOINT_XFER_CONTROL:
|
||||
{
|
||||
epOut = endPoint;
|
||||
epIn = endPoint;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SendBlockSize = epOut.getMaxPacketSize();
|
||||
RecvBlockSize = epIn.getMaxPacketSize();
|
||||
|
||||
return SendBlockSize;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
return -99;
|
||||
}
|
||||
}
|
||||
|
||||
public static long UsbWrite(byte[] buffer, int len, int timeout)
|
||||
{
|
||||
try
|
||||
{
|
||||
return usbConnection.bulkTransfer(epOut, buffer, SendBlockSize, timeout);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
return -99;
|
||||
}
|
||||
}
|
||||
|
||||
public static long UsbRead(byte[] buffer, int timeout)
|
||||
{
|
||||
/*
|
||||
try
|
||||
{
|
||||
Thread.sleep(10);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
*/
|
||||
|
||||
try
|
||||
{
|
||||
return usbConnection.bulkTransfer(epIn, buffer, RecvBlockSize, timeout);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
return -99;
|
||||
}
|
||||
}
|
||||
|
||||
public static long UsbCtrlWrite(byte[] buffer, int len, int timeout)
|
||||
{
|
||||
try
|
||||
{
|
||||
return usbConnection.controlTransfer(0x21, 0x09, 0x0200, 0x0000, buffer, SendBlockSize, timeout);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
return -99;
|
||||
}
|
||||
}
|
||||
|
||||
public static long UsbCtrlRead(byte[] buffer, int timeout)
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(10);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return usbConnection.controlTransfer(0xA1, 0x01, 0x0100, 0x0000, buffer, RecvBlockSize, timeout);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
return -99;
|
||||
}
|
||||
}
|
||||
|
||||
public static void UsbClosePort()
|
||||
{
|
||||
try
|
||||
{
|
||||
usbConnection.releaseInterface(usbInterface);
|
||||
usbConnection.close();
|
||||
usbConnection = null;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
01SDK/神思标准化接口规范-设备接口层.doc
Normal file
BIN
01SDK/神思标准化接口规范-设备接口层.doc
Normal file
Binary file not shown.
201
01SDK/身份证接口文档提取.txt
Normal file
201
01SDK/身份证接口文档提取.txt
Normal file
@@ -0,0 +1,201 @@
|
||||
2.3.1 基本接口
|
||||
2.3.1.1 打开设备
|
||||
函数名称 打开设备
|
||||
函数声明 long OpenDevice(char *PortType, char *PortPara, char *ExtendPara);
|
||||
功能描述 与设备建立通讯连接,返回设备句柄。
|
||||
参数说明 序号 参数 输入/输出 类型 含义
|
||||
1 PortType IN 字符串 端口类型(详见参数补充说明)
|
||||
2 PortPara IN 字符串 端口参数(详见参数补充说明)
|
||||
3 ExtendPara IN 字符串 扩展参数(详见参数补充说明)
|
||||
返回值 大于0表示成功,数值为设备句柄;其他为失败。
|
||||
|
||||
参数补充说明:
|
||||
PortType PortPara ExtendPara
|
||||
串口 COMn 或 1~1000 波特率(9600、115200) 串口扩展盒参数(1B2541)
|
||||
USB USBn 或 1001~2000 VID_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表示失败。
|
||||
|
||||
3
CHANGELOG.md
Normal file
3
CHANGELOG.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.0.1
|
||||
|
||||
* TODO: Describe initial release.
|
||||
360
README.md
Normal file
360
README.md
Normal file
@@ -0,0 +1,360 @@
|
||||
# 身份证读卡器 Flutter 插件
|
||||
|
||||
一个用于读取中国二代身份证的 Flutter 插件,集成了01SDK身份证识别功能,支持USB和蓝牙连接方式。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- ✅ 支持二代身份证读取
|
||||
- ✅ 获取完整的身份证信息(姓名、性别、民族、出生日期、地址、身份证号、签发机关、有效期)
|
||||
- ✅ 支持读取身份证照片
|
||||
- ✅ 支持USB连接读卡器
|
||||
- ✅ 支持蓝牙连接读卡器
|
||||
- ✅ 完整的错误处理和状态管理
|
||||
- ✅ 简单易用的API接口
|
||||
|
||||
## 支持平台
|
||||
|
||||
- ✅ Android
|
||||
- ❌ iOS(暂不支持)
|
||||
|
||||
## 安装
|
||||
|
||||
在 `pubspec.yaml` 文件中添加依赖:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
idcard: ^0.0.1
|
||||
```
|
||||
|
||||
然后运行:
|
||||
|
||||
```bash
|
||||
flutter pub get
|
||||
```
|
||||
|
||||
## Android 配置
|
||||
|
||||
### 权限配置
|
||||
|
||||
在 `android/app/src/main/AndroidManifest.xml` 中添加必要权限:
|
||||
|
||||
```xml
|
||||
<!-- USB权限 -->
|
||||
<uses-permission android:name="android.permission.USB_PERMISSION" />
|
||||
<uses-feature android:name="android.hardware.usb.host" android:required="false" />
|
||||
|
||||
<!-- Android 12+ USB权限 -->
|
||||
<uses-permission android:name="android.permission.MANAGE_USB" android:maxSdkVersion="30" />
|
||||
|
||||
<!-- 蓝牙权限 -->
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
|
||||
<!-- Android 12+ 蓝牙权限 -->
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
```
|
||||
|
||||
### SDK版本配置
|
||||
|
||||
确保 `minSdkVersion` 至少为 21,`targetSdkVersion` 为 34:
|
||||
|
||||
```gradle
|
||||
android {
|
||||
compileSdk 34
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 34
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Android 12+ 兼容性
|
||||
|
||||
本插件已针对 Android 12+ 进行了优化,包括:
|
||||
|
||||
- **PendingIntent 兼容性**:自动处理 Android 12+ 的 `FLAG_IMMUTABLE` 要求
|
||||
- **USB 权限优化**:改进了权限请求流程,增加了超时处理
|
||||
- **错误处理增强**:提供更详细的错误信息和用户指导
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 基本用法
|
||||
|
||||
```dart
|
||||
import 'package:idcard/idcard.dart';
|
||||
|
||||
class IdCardReader {
|
||||
final _idcardPlugin = Idcard();
|
||||
|
||||
/// 连接设备并读取身份证
|
||||
Future<void> readIdCard() async {
|
||||
try {
|
||||
// 1. 获取USB权限
|
||||
int permissionResult = await _idcardPlugin.getUsbPermission();
|
||||
if (permissionResult != 0) {
|
||||
print('获取USB权限失败,错误码:$permissionResult');
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 打开设备
|
||||
int openResult = await _idcardPlugin.openDevice();
|
||||
if (openResult <= 0) {
|
||||
print('打开设备失败,错误码:$openResult');
|
||||
return;
|
||||
}
|
||||
print('设备打开成功,句柄:$openResult');
|
||||
|
||||
// 3. 读取身份证信息(完整流程)
|
||||
IdCardInfo cardInfo = await _idcardPlugin.readCardComplete();
|
||||
|
||||
print('姓名:${cardInfo.name}');
|
||||
print('身份证号:${cardInfo.idNumber}');
|
||||
print('性别:${cardInfo.gender}');
|
||||
// ... 其他信息
|
||||
|
||||
// 4. 关闭设备
|
||||
await _idcardPlugin.closeDevice();
|
||||
|
||||
} catch (e) {
|
||||
print('读卡失败:$e');
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 高级用法
|
||||
|
||||
#### 分步骤读取
|
||||
|
||||
```dart
|
||||
// 手动控制每个步骤
|
||||
try {
|
||||
// 寻卡
|
||||
int findResult = await _idcardPlugin.findCard();
|
||||
if (findResult <= 0) {
|
||||
throw Exception('寻卡失败,错误码:$findResult');
|
||||
}
|
||||
|
||||
// 选卡
|
||||
int selectResult = await _idcardPlugin.selectCard();
|
||||
if (selectResult <= 0) {
|
||||
throw Exception('选卡失败,错误码:$selectResult');
|
||||
}
|
||||
|
||||
// 读取详细信息
|
||||
IdCardInfo cardInfo = await _idcardPlugin.readCardInfo();
|
||||
print('读取成功:${cardInfo.toString()}');
|
||||
|
||||
} catch (e) {
|
||||
print('读卡过程出错:$e');
|
||||
}
|
||||
```
|
||||
|
||||
#### 蓝牙连接
|
||||
|
||||
```dart
|
||||
// 使用蓝牙连接
|
||||
int result = await _idcardPlugin.openDevice(
|
||||
portType: 'BLUETOOTH',
|
||||
portPara: 'BLUETOOTH_DEVICE_NAME', // 蓝牙设备名称
|
||||
);
|
||||
```
|
||||
|
||||
#### 读取原始数据
|
||||
|
||||
```dart
|
||||
// 读取原始二进制数据
|
||||
Map<String, dynamic> result = await _idcardPlugin.readCard(
|
||||
cardType: 1,
|
||||
infoEncoding: 0,
|
||||
timeOut: 30000,
|
||||
);
|
||||
|
||||
if (result['result'] > 0) {
|
||||
List<int> rawData = result['data'];
|
||||
// 处理原始数据
|
||||
}
|
||||
```
|
||||
|
||||
## 返回值说明
|
||||
|
||||
根据神思标准化接口规范,所有设备操作方法的返回值遵循以下规则:
|
||||
|
||||
- **大于0**:表示操作成功
|
||||
- 对于 `openDevice()`:返回值是设备句柄,用于后续操作
|
||||
- 对于其他方法:返回值表示操作成功的状态码
|
||||
- **小于等于0**:表示操作失败,返回值为错误码
|
||||
- 具体错误码含义请参考设备厂商提供的接口文档
|
||||
|
||||
### 重要提醒
|
||||
|
||||
在判断操作是否成功时,请使用 `> 0` 而不是 `== 0` 来判断成功状态:
|
||||
|
||||
```dart
|
||||
// ✅ 正确的判断方式
|
||||
int result = await _idcardPlugin.openDevice();
|
||||
if (result > 0) {
|
||||
print('设备打开成功,句柄:$result');
|
||||
} else {
|
||||
print('设备打开失败,错误码:$result');
|
||||
}
|
||||
|
||||
// ❌ 错误的判断方式
|
||||
if (result == 0) {
|
||||
// 这样判断是错误的
|
||||
}
|
||||
```
|
||||
|
||||
## API 参考
|
||||
|
||||
### Idcard 类
|
||||
|
||||
#### 方法
|
||||
|
||||
| 方法 | 描述 | 参数 | 返回值 |
|
||||
|------|------|------|--------|
|
||||
| `getPlatformVersion()` | 获取平台版本 | 无 | `Future<String?>` |
|
||||
| `getUsbPermission({int vid, int pid})` | 获取USB权限 | vid: 厂商ID, pid: 产品ID | `Future<int>` |
|
||||
| `openDevice({String portType, String portPara, String extendPara})` | 打开设备 | portType: 端口类型, portPara: 端口参数, extendPara: 扩展参数 | `Future<int>` - 大于0表示成功(设备句柄),小于等于0表示失败 |
|
||||
| `closeDevice()` | 关闭设备 | 无 | `Future<int>` - 大于0表示成功,小于等于0表示失败 |
|
||||
| `findCard()` | 寻卡 | 无 | `Future<int>` - 大于0表示成功,小于等于0表示失败 |
|
||||
| `selectCard()` | 选卡 | 无 | `Future<int>` - 大于0表示成功,小于等于0表示失败 |
|
||||
| `readCard({int cardType, int infoEncoding, int timeOut})` | 读取原始数据 | cardType: 卡片类型, infoEncoding: 编码, timeOut: 超时时间 | `Future<Map<String, dynamic>>` |
|
||||
| `readCardInfo()` | 读取详细信息 | 无 | `Future<IdCardInfo>` |
|
||||
| `readCardComplete({int timeOut})` | 完整读卡流程 | timeOut: 超时时间 | `Future<IdCardInfo>` |
|
||||
| `isDeviceConnected()` | 检查设备连接状态 | 无 | `Future<bool>` |
|
||||
|
||||
### IdCardInfo 类
|
||||
|
||||
身份证信息数据模型:
|
||||
|
||||
```dart
|
||||
class IdCardInfo {
|
||||
final String name; // 姓名
|
||||
final String gender; // 性别
|
||||
final String nation; // 民族
|
||||
final String birthDate; // 出生日期
|
||||
final String address; // 地址
|
||||
final String idNumber; // 身份证号
|
||||
final String signOrgan; // 签发机关
|
||||
final String validTerm; // 有效期
|
||||
final List<int>? photo; // 照片数据(JPEG格式)
|
||||
}
|
||||
```
|
||||
|
||||
## 错误码说明
|
||||
|
||||
| 错误码 | 说明 |
|
||||
|--------|------|
|
||||
| 0 | 成功 |
|
||||
| -1 | 设备未找到 |
|
||||
| -2 | 设备接口错误 |
|
||||
| -3 | 权限被拒绝 |
|
||||
| 其他 | 具体错误码请参考01SDK文档 |
|
||||
|
||||
## 示例应用
|
||||
|
||||
查看 `example` 目录中的完整示例应用,演示了如何使用本插件的所有功能。
|
||||
|
||||
运行示例:
|
||||
|
||||
```bash
|
||||
cd example
|
||||
flutter run
|
||||
```
|
||||
|
||||
## 故障排除
|
||||
|
||||
### Android 12+ USB权限问题
|
||||
|
||||
如果在Android 12或更高版本上遇到"获取USB权限失败"的问题,请尝试以下解决方案:
|
||||
|
||||
#### 1. 检查权限配置
|
||||
确保在 `AndroidManifest.xml` 中正确配置了所有必要权限:
|
||||
```xml
|
||||
<uses-permission android:name="android.permission.USB_PERMISSION" />
|
||||
<uses-permission android:name="android.permission.MANAGE_USB" android:maxSdkVersion="30" />
|
||||
<uses-feature android:name="android.hardware.usb.host" android:required="false" />
|
||||
```
|
||||
|
||||
#### 2. 手动授权USB权限
|
||||
1. 进入 **设置** > **应用管理** > **您的应用**
|
||||
2. 点击 **权限管理**
|
||||
3. 找到并开启 **USB** 相关权限
|
||||
4. 重新启动应用
|
||||
|
||||
#### 3. 开发者选项设置
|
||||
1. 进入 **设置** > **开发者选项**
|
||||
2. 开启 **USB调试**
|
||||
3. 开启 **USB安装** (如果有)
|
||||
4. 关闭 **监控ADB安装应用** (如果有)
|
||||
|
||||
#### 4. 设备连接顺序
|
||||
1. 先启动应用
|
||||
2. 再连接USB设备
|
||||
3. 在权限对话框中点击"允许"
|
||||
4. 调用 `getUsbPermission()` 方法
|
||||
|
||||
#### 5. 检查设备兼容性
|
||||
确保您的身份证读卡器设备:
|
||||
- 支持USB HID协议
|
||||
- 与Android系统兼容
|
||||
- VID/PID参数正确
|
||||
|
||||
### 常见错误码
|
||||
|
||||
| 错误码 | 含义 | 解决方案 |
|
||||
|--------|------|----------|
|
||||
| -1 | 设备未找到 | 检查设备连接和VID/PID |
|
||||
| -2 | 接口错误 | 检查设备驱动和兼容性 |
|
||||
| -3 | 权限被拒绝 | 手动授权USB权限 |
|
||||
| -99 | 未知错误 | 重启应用或设备 |
|
||||
|
||||
### 其他常见问题
|
||||
|
||||
#### Q: 设备连接失败怎么办?
|
||||
A: 请检查:
|
||||
1. 设备是否正确连接
|
||||
2. USB权限是否已授权
|
||||
3. 设备驱动是否正常
|
||||
4. VID/PID参数是否正确
|
||||
|
||||
#### Q: 读取身份证失败?
|
||||
A: 请确保:
|
||||
1. 身份证放置正确
|
||||
2. 身份证芯片完好
|
||||
3. 设备工作正常
|
||||
4. 按正确顺序调用API
|
||||
|
||||
#### Q: 在模拟器上无法使用?
|
||||
A: 身份证读卡功能需要物理USB设备,模拟器不支持。请在真机上测试。
|
||||
|
||||
### Q: 照片数据如何显示?
|
||||
A: 照片数据是JPEG格式的字节数组,可以使用 `Image.memory()` 显示:
|
||||
|
||||
```dart
|
||||
if (cardInfo.photo != null) {
|
||||
Image.memory(Uint8List.fromList(cardInfo.photo!))
|
||||
}
|
||||
```
|
||||
|
||||
## 许可证
|
||||
|
||||
本项目采用 MIT 许可证。详见 [LICENSE](LICENSE) 文件。
|
||||
|
||||
## 贡献
|
||||
|
||||
欢迎提交 Issue 和 Pull Request!
|
||||
|
||||
## 更新日志
|
||||
|
||||
### 0.0.1
|
||||
- 初始版本
|
||||
- 支持基本的身份证读取功能
|
||||
- 集成01SDK
|
||||
- 支持USB和蓝牙连接
|
||||
|
||||
4
analysis_options.yaml
Normal file
4
analysis_options.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
9
android/.gitignore
vendored
Normal file
9
android/.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/workspace.xml
|
||||
/.idea/libraries
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.cxx
|
||||
55
android/build.gradle
Normal file
55
android/build.gradle
Normal file
@@ -0,0 +1,55 @@
|
||||
group = "com.example.idcard"
|
||||
version = "1.0"
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle:7.3.0")
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: "com.android.library"
|
||||
|
||||
android {
|
||||
if (project.android.hasProperty("namespace")) {
|
||||
namespace = "com.example.idcard"
|
||||
}
|
||||
|
||||
compileSdk = 34
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
targetSdk = 34
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
testImplementation("org.mockito:mockito-core:5.0.0")
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests.all {
|
||||
testLogging {
|
||||
events "passed", "skipped", "failed", "standardOut", "standardError"
|
||||
outputs.upToDateWhen {false}
|
||||
showStandardStreams = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
android/settings.gradle
Normal file
1
android/settings.gradle
Normal file
@@ -0,0 +1 @@
|
||||
rootProject.name = 'idcard'
|
||||
32
android/src/main/AndroidManifest.xml
Normal file
32
android/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.idcard">
|
||||
|
||||
<!-- USB权限 -->
|
||||
<uses-permission android:name="android.permission.USB_PERMISSION" />
|
||||
<uses-feature android:name="android.hardware.usb.host" android:required="false" />
|
||||
|
||||
<!-- Android 12+ USB权限 -->
|
||||
<uses-permission android:name="android.permission.MANAGE_USB" android:maxSdkVersion="30" />
|
||||
|
||||
<application>
|
||||
<!-- USB设备过滤器 -->
|
||||
<activity android:name=".UsbPermissionActivity" android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
|
||||
android:resource="@xml/device_filter" />
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
<!-- 蓝牙权限 -->
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
|
||||
<!-- Android 12+ 蓝牙权限 -->
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
|
||||
</manifest>
|
||||
642
android/src/main/java/com/example/idcard/IdcardPlugin.java
Normal file
642
android/src/main/java/com/example/idcard/IdcardPlugin.java
Normal file
@@ -0,0 +1,642 @@
|
||||
package com.example.idcard;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin;
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
|
||||
import io.flutter.plugin.common.MethodChannel.Result;
|
||||
|
||||
import com.sdses.JniCommonInterface;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* IdcardPlugin - Flutter插件,用于身份证识别功能
|
||||
* 集成了01SDK的身份证读取功能
|
||||
*/
|
||||
public class IdcardPlugin implements FlutterPlugin, MethodCallHandler {
|
||||
private static final String TAG = "IdcardPlugin";
|
||||
|
||||
/// 与Flutter通信的方法通道
|
||||
private MethodChannel channel;
|
||||
private Context context;
|
||||
|
||||
@Override
|
||||
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
|
||||
channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "idcard");
|
||||
channel.setMethodCallHandler(this);
|
||||
context = flutterPluginBinding.getApplicationContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
|
||||
try {
|
||||
switch (call.method) {
|
||||
case "getPlatformVersion":
|
||||
result.success("Android " + android.os.Build.VERSION.RELEASE);
|
||||
break;
|
||||
case "openDevice":
|
||||
handleOpenDevice(call, result);
|
||||
break;
|
||||
case "setCurrentDevice":
|
||||
handleSetCurrentDevice(call, result);
|
||||
break;
|
||||
case "getCurrentDevice":
|
||||
handleGetCurrentDevice(result);
|
||||
break;
|
||||
case "getLibraryInfo":
|
||||
handleGetLibraryInfo(result);
|
||||
break;
|
||||
case "getTerminalModel":
|
||||
handleGetTerminalModel(result);
|
||||
break;
|
||||
case "terminalHeartBeat":
|
||||
handleTerminalHeartBeat(result);
|
||||
break;
|
||||
case "getLastRecvData":
|
||||
handleGetLastRecvData(result);
|
||||
break;
|
||||
case "getTerminalFirmVersion":
|
||||
handleGetTerminalFirmVersion(result);
|
||||
break;
|
||||
case "getTerminalSn":
|
||||
handleGetTerminalSn(result);
|
||||
break;
|
||||
case "closeDevice":
|
||||
handleCloseDevice(result);
|
||||
break;
|
||||
case "findCard":
|
||||
handleFindCard(result);
|
||||
break;
|
||||
case "selectCard":
|
||||
handleSelectCard(result);
|
||||
break;
|
||||
case "idReadCard":
|
||||
handleIdReadCard(call, result);
|
||||
break;
|
||||
case "readCard":
|
||||
handleReadCard(call, result);
|
||||
break;
|
||||
case "readCardInfo":
|
||||
handleReadCardInfo(result);
|
||||
break;
|
||||
case "readNewAddress":
|
||||
handleReadNewAddress(result);
|
||||
break;
|
||||
case "getSamStatus":
|
||||
handleGetSamStatus(result);
|
||||
break;
|
||||
case "getSamIdStr":
|
||||
handleGetSamIdStr(result);
|
||||
break;
|
||||
case "getUsbPermission":
|
||||
handleGetUsbPermission(call, result);
|
||||
break;
|
||||
default:
|
||||
result.notImplemented();
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error in method call: " + call.method, e);
|
||||
result.error("ERROR", "Method call failed: " + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取USB权限
|
||||
*/
|
||||
private void handleGetUsbPermission(MethodCall call, Result result) {
|
||||
Integer vid = call.argument("vid");
|
||||
Integer pid = call.argument("pid");
|
||||
|
||||
if (vid == null || pid == null) {
|
||||
result.error("INVALID_PARAMS", "VID and PID are required", null);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
long ret = JniCommonInterface.GetUsbPermission(context, vid, pid);
|
||||
|
||||
// 根据返回值提供更详细的错误信息
|
||||
switch ((int)ret) {
|
||||
case 0:
|
||||
result.success(ret);
|
||||
break;
|
||||
case -1:
|
||||
result.error("DEVICE_NOT_FOUND", "未找到指定的USB设备 (VID:" + vid + ", PID:" + pid + ")", null);
|
||||
break;
|
||||
case -2:
|
||||
result.error("INTERFACE_ERROR", "USB设备接口错误", null);
|
||||
break;
|
||||
case -3:
|
||||
result.error("PERMISSION_DENIED", "USB权限请求被拒绝或超时,请在系统设置中手动授权USB权限", null);
|
||||
break;
|
||||
case -99:
|
||||
result.error("UNKNOWN_ERROR", "获取USB权限时发生未知错误", null);
|
||||
break;
|
||||
default:
|
||||
result.error("ERROR", "获取USB权限失败,错误码: " + ret, null);
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
result.error("EXCEPTION", "获取USB权限时发生异常: " + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开设备
|
||||
*/
|
||||
private void handleOpenDevice(MethodCall call, Result result) {
|
||||
try {
|
||||
String portType = call.argument("portType");
|
||||
String portPara = call.argument("portPara");
|
||||
String extendPara = call.argument("extendPara");
|
||||
|
||||
if (portType == null) portType = "USB";
|
||||
if (portPara == null) portPara = "";
|
||||
if (extendPara == null) extendPara = "";
|
||||
|
||||
long deviceHandle = JniCommonInterface.OpenDevice(portType, portPara, extendPara);
|
||||
|
||||
// 根据接口文档,返回值大于0表示成功(设备句柄),小于等于0表示失败
|
||||
if (deviceHandle > 0) {
|
||||
result.success(deviceHandle);
|
||||
} else {
|
||||
result.error("OPEN_DEVICE_FAILED", "Failed to open device, error code: " + deviceHandle, null);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error opening device", e);
|
||||
result.error("ERROR", "Failed to open device: " + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭设备
|
||||
*/
|
||||
private void handleCloseDevice(Result result) {
|
||||
try {
|
||||
long closeResult = JniCommonInterface.CloseDevice();
|
||||
|
||||
// 根据接口文档,返回值0表示成功;非0表示失败
|
||||
if (closeResult == 0) {
|
||||
result.success(closeResult);
|
||||
} else {
|
||||
result.error("CLOSE_DEVICE_FAILED", "Failed to close device, error code: " + closeResult, null);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error closing device", e);
|
||||
result.error("ERROR", "Failed to close device: " + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 寻卡
|
||||
*/
|
||||
private void handleFindCard(Result result) {
|
||||
try {
|
||||
long findResult = JniCommonInterface.IdFindCard();
|
||||
|
||||
if (findResult == 0) {
|
||||
result.success(findResult);
|
||||
} else {
|
||||
result.error("FIND_CARD_FAILED", "Failed to find card, error code: " + findResult, null);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error finding card", e);
|
||||
result.error("ERROR", "Failed to find card: " + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 选卡
|
||||
*/
|
||||
private void handleSelectCard(Result result) {
|
||||
try {
|
||||
long selectResult = JniCommonInterface.IdSelectCard();
|
||||
|
||||
if (selectResult == 0) {
|
||||
result.success(selectResult);
|
||||
} else {
|
||||
result.error("SELECT_CARD_FAILED", "Failed to select card, error code: " + selectResult, null);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error selecting card", e);
|
||||
result.error("ERROR", "Failed to select card: " + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取身份证信息
|
||||
*/
|
||||
private void handleReadCard(MethodCall call, Result result) {
|
||||
Integer cardType = call.argument("cardType");
|
||||
Integer infoEncoding = call.argument("infoEncoding");
|
||||
Integer timeOut = call.argument("timeOut");
|
||||
|
||||
if (cardType == null) cardType = 1;
|
||||
if (infoEncoding == null) infoEncoding = 0;
|
||||
if (timeOut == null) timeOut = 30000;
|
||||
|
||||
byte[] idCardInfo = new byte[4096];
|
||||
long ret = JniCommonInterface.IdReadCard((byte)cardType.intValue(),
|
||||
(byte)infoEncoding.intValue(),
|
||||
idCardInfo,
|
||||
timeOut);
|
||||
|
||||
Map<String, Object> resultMap = new HashMap<>();
|
||||
resultMap.put("result", ret);
|
||||
// 根据接口文档,返回值0表示成功;非0表示失败
|
||||
if (ret == 0) {
|
||||
resultMap.put("data", idCardInfo);
|
||||
}
|
||||
result.success(resultMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取身份证详细信息
|
||||
*/
|
||||
private void handleReadCardInfo(Result result) {
|
||||
try {
|
||||
// 读取基本信息和照片
|
||||
byte[] chMsg = new byte[256];
|
||||
long[] chMsgLen = new long[1];
|
||||
byte[] phMsg = new byte[1024];
|
||||
long[] phMsgLen = new long[1];
|
||||
|
||||
long ret = JniCommonInterface.IdReadBaseMsg(chMsg, chMsgLen, phMsg, phMsgLen);
|
||||
|
||||
if (ret != 0) {
|
||||
result.error("READ_failed", "Failed to read card info, error code: " + ret, null);
|
||||
return;
|
||||
}
|
||||
|
||||
// 解析身份证信息
|
||||
Map<String, Object> cardInfo = new HashMap<>();
|
||||
|
||||
// 获取各个字段
|
||||
byte[] name = new byte[32];
|
||||
byte[] gender = new byte[4];
|
||||
byte[] nation = new byte[32];
|
||||
byte[] birthDate = new byte[16];
|
||||
byte[] address = new byte[128];
|
||||
byte[] idNumber = new byte[32];
|
||||
byte[] signOrgan = new byte[64];
|
||||
byte[] validTerm = new byte[32];
|
||||
|
||||
JniCommonInterface.IdCardGetName(name);
|
||||
JniCommonInterface.IdCardGetGender(gender);
|
||||
JniCommonInterface.IdCardGetNation(nation);
|
||||
JniCommonInterface.IdCardGetBirthDate(birthDate);
|
||||
JniCommonInterface.IdCardGetAddress(address);
|
||||
JniCommonInterface.IdCardGetIdNumber(idNumber);
|
||||
JniCommonInterface.IdCardGetSignOrgan(signOrgan);
|
||||
JniCommonInterface.IdCardGetValidTerm(validTerm);
|
||||
|
||||
// 获取照片
|
||||
byte[] photoBuffer = new byte[10240];
|
||||
long[] photoBufferLen = new long[1];
|
||||
JniCommonInterface.IdCardGetPhotoBuffer((byte)1, photoBuffer, photoBufferLen);
|
||||
|
||||
cardInfo.put("name", new String(name, "GBK").trim());
|
||||
cardInfo.put("gender", new String(gender, "GBK").trim());
|
||||
cardInfo.put("nation", new String(nation, "GBK").trim());
|
||||
cardInfo.put("birthDate", new String(birthDate, "GBK").trim());
|
||||
cardInfo.put("address", new String(address, "GBK").trim());
|
||||
cardInfo.put("idNumber", new String(idNumber, "GBK").trim());
|
||||
cardInfo.put("signOrgan", new String(signOrgan, "GBK").trim());
|
||||
cardInfo.put("validTerm", new String(validTerm, "GBK").trim());
|
||||
|
||||
if (photoBufferLen[0] > 0) {
|
||||
byte[] photo = new byte[(int)photoBufferLen[0]];
|
||||
System.arraycopy(photoBuffer, 0, photo, 0, (int)photoBufferLen[0]);
|
||||
cardInfo.put("photo", photo);
|
||||
}
|
||||
|
||||
result.success(cardInfo);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error reading card info", e);
|
||||
result.error("ERROR", "Failed to read card2 info: " + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前设备(多设备操作)
|
||||
*/
|
||||
private void handleSetCurrentDevice(MethodCall call, Result result) {
|
||||
try {
|
||||
Integer devHandle = call.argument("devHandle");
|
||||
if (devHandle == null) {
|
||||
result.error("INVALID_PARAMS", "Device handle is required", null);
|
||||
return;
|
||||
}
|
||||
|
||||
long ret = JniCommonInterface.SetCurrentDevice(devHandle);
|
||||
|
||||
// 根据接口文档,返回值0表示成功;非0表示失败
|
||||
if (ret == 0) {
|
||||
result.success(ret);
|
||||
} else {
|
||||
result.error("SET_CURRENT_DEVICE_FAILED", "Failed to set current device, error code: " + ret, null);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error setting current device", e);
|
||||
result.error("ERROR", "Failed to set current device: " + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前设备(多设备操作)
|
||||
*/
|
||||
private void handleGetCurrentDevice(Result result) {
|
||||
try {
|
||||
long currentDevice = JniCommonInterface.GetCurrentDevice();
|
||||
result.success(currentDevice);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error getting current device", e);
|
||||
result.error("ERROR", "Failed to get current device: " + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取接口库信息
|
||||
*/
|
||||
private void handleGetLibraryInfo(Result result) {
|
||||
try {
|
||||
byte[] version = new byte[64];
|
||||
byte[] description = new byte[256];
|
||||
|
||||
long ret = JniCommonInterface.GetLibraryInfo(version, description);
|
||||
|
||||
// 根据接口文档,返回值0表示成功;非0表示失败
|
||||
if (ret == 0) {
|
||||
Map<String, String> info = new HashMap<>();
|
||||
info.put("version", new String(version, "GBK").trim());
|
||||
info.put("description", new String(description, "GBK").trim());
|
||||
result.success(info);
|
||||
} else {
|
||||
result.error("GET_LIBRARY_INFO_FAILED", "Failed to get library info, error code: " + ret, null);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error getting library info", e);
|
||||
result.error("ERROR", "Failed to get library info: " + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备型号
|
||||
*/
|
||||
private void handleGetTerminalModel(Result result) {
|
||||
try {
|
||||
byte[] model = new byte[64];
|
||||
long ret = JniCommonInterface.GetTerminalModel(model);
|
||||
|
||||
// 根据接口文档,返回值0表示成功;非0表示失败
|
||||
if (ret == 0) {
|
||||
String modelStr = new String(model, "GBK").trim();
|
||||
result.success(modelStr);
|
||||
} else {
|
||||
result.error("GET_TERMINAL_MODEL_FAILED", "Failed to get terminal model, error code: " + ret, null);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error getting terminal model", e);
|
||||
result.error("ERROR", "Failed to get terminal model: " + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备轮询心跳
|
||||
*/
|
||||
private void handleTerminalHeartBeat(Result result) {
|
||||
try {
|
||||
long ret = JniCommonInterface.TerminalHeartBeat();
|
||||
|
||||
// 根据接口文档,返回值0表示成功;非0表示失败
|
||||
if (ret == 0) {
|
||||
result.success(ret);
|
||||
} else {
|
||||
result.error("TERMINAL_HEARTBEAT_FAILED", "Terminal heartbeat failed, error code: " + ret, null);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error in terminal heart beat", e);
|
||||
result.error("ERROR", "Failed to perform terminal heart beat: " + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取接收数据
|
||||
*/
|
||||
private void handleGetLastRecvData(Result result) {
|
||||
try {
|
||||
byte[] data = new byte[1024];
|
||||
long[] dataLen = new long[1];
|
||||
|
||||
JniCommonInterface.GetLastRecvData(data, dataLen);
|
||||
|
||||
if (dataLen[0] > 0) {
|
||||
byte[] actualData = new byte[(int)dataLen[0]];
|
||||
System.arraycopy(data, 0, actualData, 0, (int)dataLen[0]);
|
||||
result.success(actualData);
|
||||
} else {
|
||||
result.success(new byte[0]);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error getting last recv data", e);
|
||||
result.error("ERROR", "Failed to get last recv data: " + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备固件版本
|
||||
*/
|
||||
private void handleGetTerminalFirmVersion(Result result) {
|
||||
try {
|
||||
byte[] firmVersion = new byte[64];
|
||||
byte[] hardwareVersion = new byte[64];
|
||||
|
||||
long ret = JniCommonInterface.GetTerminalFirmVersion(firmVersion, hardwareVersion);
|
||||
|
||||
// 根据接口文档,返回值0表示成功;非0表示失败
|
||||
if (ret == 0) {
|
||||
Map<String, String> versionInfo = new HashMap<>();
|
||||
versionInfo.put("firmVersion", new String(firmVersion, "GBK").trim());
|
||||
versionInfo.put("hardwareVersion", new String(hardwareVersion, "GBK").trim());
|
||||
result.success(versionInfo);
|
||||
} else {
|
||||
result.error("GET_FIRM_VERSION_FAILED", "Failed to get terminal firm version, error code: " + ret, null);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error getting terminal firm version", e);
|
||||
result.error("ERROR", "Failed to get terminal firm version: " + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备序列号
|
||||
*/
|
||||
private void handleGetTerminalSn(Result result) {
|
||||
try {
|
||||
byte[] sn = new byte[64];
|
||||
long ret = JniCommonInterface.GetTerminalSn(sn);
|
||||
|
||||
// 根据接口文档,返回值0表示成功;非0表示失败
|
||||
if (ret == 0) {
|
||||
String snStr = new String(sn, "GBK").trim();
|
||||
result.success(snStr);
|
||||
} else {
|
||||
result.error("GET_TERMINAL_SN_FAILED", "Failed to get terminal SN, error code: " + ret, null);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error getting terminal SN", e);
|
||||
result.error("ERROR", "Failed to get terminal SN: " + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取身份证(新版接口)
|
||||
*/
|
||||
private void handleIdReadCard(MethodCall call, Result result) {
|
||||
try {
|
||||
Integer cardType = call.argument("cardType");
|
||||
Integer infoEncoding = call.argument("infoEncoding");
|
||||
Integer timeOut = call.argument("timeOut");
|
||||
|
||||
if (cardType == null) cardType = 0x00;
|
||||
if (infoEncoding == null) infoEncoding = 0x01;
|
||||
if (timeOut == null) timeOut = 30000;
|
||||
|
||||
byte[] idCardInfo = new byte[4096];
|
||||
long ret = JniCommonInterface.IdReadCard((byte)cardType.intValue(),
|
||||
(byte)infoEncoding.intValue(),
|
||||
idCardInfo,
|
||||
timeOut);
|
||||
|
||||
Map<String, Object> resultMap = new HashMap<>();
|
||||
resultMap.put("result", ret);
|
||||
|
||||
// 根据接口文档,返回值0表示成功;非0表示失败
|
||||
if (ret == 0) {
|
||||
// 将字节数组转换为字符串(根据编码方式)
|
||||
String encoding = "GBK";
|
||||
if (infoEncoding == 0x02) {
|
||||
encoding = "UTF-16LE";
|
||||
} else if (infoEncoding == 0x03) {
|
||||
encoding = "UTF-8";
|
||||
}
|
||||
|
||||
// 找到字节数组中第一个null字符的位置
|
||||
int length = 0;
|
||||
for (int i = 0; i < idCardInfo.length; i++) {
|
||||
if (idCardInfo[i] == 0) {
|
||||
length = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有找到null字符,使用整个数组长度
|
||||
if (length == 0) {
|
||||
length = idCardInfo.length;
|
||||
}
|
||||
|
||||
// 只转换有效的字节数据
|
||||
String dataStr = new String(idCardInfo, 0, length, encoding).trim();
|
||||
resultMap.put("data", dataStr);
|
||||
|
||||
Log.d(TAG, "IdReadCard success, data length: " + length + ", data: " + dataStr);
|
||||
} else {
|
||||
Log.e(TAG, "IdReadCard failed with error code: " + ret);
|
||||
}
|
||||
|
||||
result.success(resultMap);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error in idReadCard", e);
|
||||
result.error("ERROR", "Failed to read card: " + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取追加住址
|
||||
*/
|
||||
private void handleReadNewAddress(Result result) {
|
||||
try {
|
||||
byte[] address = new byte[256];
|
||||
long ret = JniCommonInterface.IdReadNewAddress(address);
|
||||
|
||||
// 根据接口文档,返回值0表示成功;非0表示失败
|
||||
if (ret == 0) {
|
||||
String addressStr = new String(address, "GBK").trim();
|
||||
result.success(addressStr);
|
||||
} else {
|
||||
result.error("READ_failed", "Failed to read new address, error code: " + ret, null);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error reading new address", e);
|
||||
result.error("ERROR", "Failed to read new address: " + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取SAM模块状态
|
||||
*/
|
||||
private void handleGetSamStatus(Result result) {
|
||||
try {
|
||||
long status = JniCommonInterface.GetSamStatus();
|
||||
// 根据接口文档,返回值0表示成功;非0表示失败
|
||||
if (status == 0) {
|
||||
result.success(0); // 成功时返回0
|
||||
} else {
|
||||
result.error("SAM_STATUS_ERROR", "SAM module status error, code: " + status, null);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error getting SAM status", e);
|
||||
result.error("ERROR", "Failed to get SAM status: " + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取SAM模块编号字符串
|
||||
*/
|
||||
private void handleGetSamIdStr(Result result) {
|
||||
try {
|
||||
byte[] samId = new byte[64];
|
||||
long ret = JniCommonInterface.GetSamIdStr(samId);
|
||||
|
||||
// 根据接口文档,返回值0表示成功;非0表示失败
|
||||
if (ret == 0) {
|
||||
String samIdStr = new String(samId, "GBK").trim();
|
||||
result.success(samIdStr);
|
||||
} else {
|
||||
result.error("GET_SAM_ID_FAILED", "Failed to get SAM ID string, error code: " + ret, null);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error getting SAM ID string", e);
|
||||
result.error("ERROR", "Failed to get SAM ID string: " + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
|
||||
channel.setMethodCallHandler(null);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
170
android/src/main/java/com/sdses/BlueTooth.java
Normal file
170
android/src/main/java/com/sdses/BlueTooth.java
Normal file
@@ -0,0 +1,170 @@
|
||||
package com.sdses;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothSocket;
|
||||
|
||||
public class BlueTooth
|
||||
{
|
||||
private static UUID SS_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
|
||||
private static BluetoothAdapter mAdapter = null;
|
||||
private static BluetoothDevice mDevice = null;
|
||||
private static BluetoothSocket mSocket = null;
|
||||
private static InputStream mInputStream = null;
|
||||
private static OutputStream mOutputStream = null;
|
||||
|
||||
public static long BthConnect(String BlueToothName)
|
||||
{
|
||||
mAdapter = BluetoothAdapter.getDefaultAdapter();
|
||||
if (mAdapter == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!mAdapter.isEnabled())
|
||||
{
|
||||
return -2;
|
||||
/*
|
||||
mAdapter.enable();
|
||||
if (!mAdapter.isEnabled())
|
||||
{
|
||||
return -2;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
//获得已配对的蓝牙设备列表
|
||||
Set<BluetoothDevice> pairedDevices = mAdapter.getBondedDevices();
|
||||
if(pairedDevices.size() <= 0)
|
||||
{
|
||||
return -3;
|
||||
}
|
||||
|
||||
ArrayList<BluetoothDevice> ALBluetoothDevice = new ArrayList<BluetoothDevice>();
|
||||
for (BluetoothDevice device : pairedDevices)
|
||||
{
|
||||
ALBluetoothDevice.add(device);
|
||||
}
|
||||
|
||||
//mAdapter.cancelDiscovery();
|
||||
|
||||
//查找设备
|
||||
for(int i = 0; i < ALBluetoothDevice.size(); i++)
|
||||
{
|
||||
BluetoothDevice cDevice = (BluetoothDevice)ALBluetoothDevice.get(i);
|
||||
String theBlueToothName = cDevice.getName();
|
||||
if(theBlueToothName.indexOf(BlueToothName) < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//开始连接设备
|
||||
mDevice = cDevice;
|
||||
try
|
||||
{
|
||||
mSocket = mDevice.createRfcommSocketToServiceRecord(SS_UUID);
|
||||
mSocket.connect();
|
||||
mInputStream = mSocket.getInputStream();
|
||||
mOutputStream = mSocket.getOutputStream();
|
||||
return 0;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return -4;
|
||||
}
|
||||
}
|
||||
|
||||
return -5; //没有配对设备
|
||||
}
|
||||
|
||||
public static long BthSendSocket(byte[] buffer, int len)
|
||||
{
|
||||
if(mOutputStream == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
mOutputStream.write(buffer, 0, len);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
return -2;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
public static long BthRecvSocket(byte[] buffer)
|
||||
{
|
||||
if(mInputStream == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// int nAvailable = mInputStream.available();
|
||||
// if(nAvailable <= 0)
|
||||
// {
|
||||
// return 0;
|
||||
// }
|
||||
|
||||
return mInputStream.read(buffer, 0, 4096);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
|
||||
public static void BthDisconnect()
|
||||
{
|
||||
if(mInputStream != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
mInputStream.close();
|
||||
mInputStream = null;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(mOutputStream != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
mOutputStream.close();
|
||||
mOutputStream = null;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(mSocket != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
mSocket.close();
|
||||
mSocket = null;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
126
android/src/main/java/com/sdses/JniCommonInterface.java
Normal file
126
android/src/main/java/com/sdses/JniCommonInterface.java
Normal file
@@ -0,0 +1,126 @@
|
||||
package com.sdses;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public class JniCommonInterface
|
||||
{
|
||||
static
|
||||
{
|
||||
System.loadLibrary("WltRS");
|
||||
System.loadLibrary("PortCommunication");
|
||||
System.loadLibrary("TerminalProtocol");
|
||||
System.loadLibrary("CommonInterface");
|
||||
}
|
||||
|
||||
public static long GetUsbPermission(Context ctx, int Vid, int Pid)
|
||||
{
|
||||
return UsbHidPort.GetUsbPermission(ctx, Vid, Pid);
|
||||
}
|
||||
|
||||
public static native long SetLogFileEx(String LogFileName);
|
||||
public static native long SetTerminalLibrary(String LibraryFileName);
|
||||
public static native long GetLibraryInfo(byte[] Version, byte[] Description);
|
||||
public static native long SetAutoPara(String PortType, String PortPara, String ExtendPara, String DllName, int UsingGA467);
|
||||
public static native long OpenDevice(String PortType, String PortPara, String ExtendPara);
|
||||
public static native long SetCurrentDevice(long DevHandle);
|
||||
public static native long GetCurrentDevice();
|
||||
public static native long CloseDevice();
|
||||
public static native long CommandTransmit(long Command, long CmdDataLength, byte[] CmdData, byte[] StatusWords, long[] RecvDataLength, byte[] RecvData, long TimeOut);
|
||||
public static native long GA467Transmit(long Command, long CmdDataLength, byte[] CmdData, byte[] StatusWords, long[] RecvDataLength, byte[] RecvData, long TimeOut);
|
||||
public static native long GetTerminalModel(byte[] TerminalModel);
|
||||
public static native long GetTerminalFirmVersion(byte[] FirmVersion, byte[]HardwareVersion);
|
||||
public static native long GetTerminalSn(byte[] TerminalSn);
|
||||
public static native long TerminalHeartBeat();
|
||||
public static native long LedOnOff(byte LedNo, byte OnOff);
|
||||
public static native long LedBlink(byte LedNo, long OnTimeMs, long OffTimeMs, byte BlinkCount, byte FinalState);
|
||||
public static native long GetLastRecvData(byte[] LastRecvData, long[] LastRecvDataLen);
|
||||
|
||||
public static native long IdFindCard();
|
||||
public static native long IdSelectCard();
|
||||
public static native long IdReadBaseMsg(byte[] pucCHMsg, long[] puiCHMsgLen, byte[] pucPHMsg, long[] puiPHMsgLen);
|
||||
public static native long IdReadBaseFpMsg(byte[] pucCHMsg, long[] puiCHMsgLen, byte[] pucPHMsg, long[] puiPHMsgLen, byte[] pucFPMsg, long[] puiFPMsgLen);
|
||||
public static native long IdReadNewAppMsg(byte[] pucAppMsg, long[] puiAppMsgLen);
|
||||
public static native long IdReadSn(byte[] SN, long[] SNLen);
|
||||
public static native long IdApdu(long SendApduLen, byte[] SendApdu, byte[] RecvApdu, long[] RecvApduLen);
|
||||
public static native long SdtFindCard(byte[] pucManaInfo);
|
||||
public static native long SdtSelectCard(byte[] pucManaMsg);
|
||||
public static native long SdtReadBaseMsg(byte[] pucCHMsg, long[] puiCHMsgLen, byte[] pucPHMsg, long[] puiPHMsgLen);
|
||||
public static native long SdtReadBaseFpMsg(byte[] pucCHMsg, long[] puiCHMsgLen, byte[] pucPHMsg, long[] puiPHMsgLen, byte[] pucFPMsg, long[] puiFPMsgLen);
|
||||
public static native long SdtReadNewAppMsg(byte[] pucAppMsg, long[] puiAppMsgLen);
|
||||
public static native long GetSamStatus();
|
||||
public static native long GetSamId(byte[] SamId, long[] SamIdLen);
|
||||
public static native long GetSamIdStr(byte[] SamIdStr);
|
||||
public static native long SamGetStatus();
|
||||
public static native long SamGetId(byte[] SamId, long[] SamIdLen);
|
||||
public static native long SamGetIdStr(byte[] SamIdStr);
|
||||
public static native long SdtSamGetStatus();
|
||||
public static native long SdtSamGetId(byte[] SamId, long[] SamIdLen);
|
||||
public static native long SdtSamGetIdStr(byte[] SamIdStr);
|
||||
|
||||
public static native long IdReadCard(byte CardType, byte InfoEncoding, byte[] IdCardInfo, long TimeOutMs);
|
||||
public static native long SdtReadCard(byte CardType, byte InfoEncoding, byte[] IdCardInfo, long TimeOutMs);
|
||||
public static native long IdReadNewAddress(byte[] NewAddress);
|
||||
public static native long SdtReadNewAddress(byte[] NewAddress);
|
||||
public static native long IdCardGetName(byte[] Name);
|
||||
public static native long IdCardGetNameEn(byte[] NameEn);
|
||||
public static native long IdCardGetGender(byte[] Gender);
|
||||
public static native long IdCardGetGenderId(byte[] GenderId);
|
||||
public static native long IdCardGetNation(byte[] Nation);
|
||||
public static native long IdCardGetNationId(byte[] NationId);
|
||||
public static native long IdCardGetBirthDate(byte[] BirthDate);
|
||||
public static native long IdCardGetAddress(byte[] Address);
|
||||
public static native long IdCardGetIdNumber(byte[] IdNumber);
|
||||
public static native long IdCardGetSignOrgan(byte[] SignOrgan);
|
||||
public static native long IdCardGetBeginTerm(byte[] BeginTerm);
|
||||
public static native long IdCardGetValidTerm(byte[] ValidTerm);
|
||||
public static native long IdCardGetFPBuffer(byte[] FPBuffer, long[] FPBufferLen);
|
||||
public static native long IdCardGetPhotoFile(String PhotoFile);
|
||||
public static native long IdCardGetPhotoBuffer(byte WltBmpJpg, byte[] PhotoBuffer, long[] PhotoBufferLen);
|
||||
|
||||
public static native long MIdSamGetCert(byte[] Cert, long[] CertLen);
|
||||
public static native long MIdSamEnable(long EnableDataLen, byte[] EnableData);
|
||||
public static native long MIdSamDisable(long DisableDataLen, byte[] DisableData);
|
||||
public static native long MIdSamGetEnableState(byte[] EnableState, long[] RemainNum, byte[] EnableTime, byte[] DisableTime, byte[] SecondCert, long[] SecondCertLen, byte[] EncryptCert, long[] EncryptCertLen, byte[] SignCert, long[] SignCertLen);
|
||||
public static native long MIdSamAuthRequest(byte[] RequestData56);
|
||||
public static native long MIdSamAuthConfirm(byte[] ConfirmData89);
|
||||
public static native long MIdReadChkData(byte[] Random16, byte[] RemainAuthNum, byte[] SamId22, byte[] BeginAndValidTerm32, byte[] ShortCode16, byte[] CheckData, long[] CheckDataLen, byte[] Sign64);
|
||||
public static native long MIdReadChkDataPF(byte[] Random16, byte[] RemainAuthNum, byte[] SamId22, byte[] BeginAndValidTerm32, byte[] ShortCode16, byte[] CheckData, long[] CheckDataLen, byte[] Hash32, byte[] Sign64, byte[] PH, long[] PHLen, byte[] FP, long[] FPLen);
|
||||
public static native long MIdCheckShortLongCode(byte[] Random16, byte ShortCodeCount, byte[] ShortCode, byte LongCodeCount, byte[] LongCode, byte[] RemainAuthNum, byte[] SamId22, byte[] BeginAndValidTerm32, byte[] MatchCode, byte[] MatchCodeLen, byte[] CheckData, long[] CheckDataLen, byte[] Sign64);
|
||||
public static native long MIdCheckShortLongCodePF(byte[] Random16, byte ShortCodeCount, byte[] ShortCode, byte LongCodeCount, byte[] LongCode, byte[] RemainAuthNum, byte[] SamId22,byte[] BeginAndValidTerm32, byte[] MatchCode, byte[] MatchCodeLen, byte[] CheckData, long[] CheckDataLen, byte[] Hash32, byte[] Sign64, byte[] PH, long[] PHLen, byte[] FP, long[] FPLen);
|
||||
public static native long MIdCheckProperty(byte[] Random16, byte PropertyCode, byte PropertyValLen, byte[] PropertyVal, byte[] RemainAuthNum, byte[] SamId22, byte[] CheckResult, byte[] CheckData, long[] CheckDataLen, byte[] Sign64);
|
||||
|
||||
public static native long MagRead(byte Tracks, byte[] TrackData1, byte[] TrackData2, byte[] TrackData3, byte TimeOutSec);
|
||||
public static native long MagWrite(byte Tracks, String TrackData1, String TrackData2, String TrackData3, byte TimeOutSec);
|
||||
|
||||
public static native long QrRead(byte[] QrData, byte TimeOutSec);
|
||||
public static native long QrCancel();
|
||||
public static native long QrAsynEnable(byte TimeOutSec);
|
||||
public static native long QrAsynRead(byte[] QrData);
|
||||
public static native long QrAsynDisable();
|
||||
|
||||
public static native long M1FindCard(byte[] UID, long[] UIDLen);
|
||||
public static native long M1Authentication(byte KeyType, byte SecAddr, byte[] Key, byte[] UID);
|
||||
public static native long M1ReadBlock(byte BlockAddr, byte[] BlockData, long[] BlockDataLen);
|
||||
public static native long M1WriteBlock(byte BlockAddr, long BlockDataLen, byte[] BlockData);
|
||||
public static native long M1Halt();
|
||||
|
||||
public static native long FpCapFeature(byte[] Feature, long[] FeatureLen);
|
||||
public static native long FpMatchFeature(long FeatureLen1, byte[] Feature1, long FeatureLen2, byte[] Feature2, long[] Score);
|
||||
|
||||
public static native long SsseReadCard(int iType, byte[] SSCardInfo, byte[] SSErrorInfo);
|
||||
public static native long SsseReadCard2(int iType, byte[] SSCardInfo, byte[] SSErrorInfo);
|
||||
public static native long SsseGetCardInfo(String Tag, byte[] SSCardInfo);
|
||||
|
||||
public static native long CpuPowerOn(byte Slot, byte[] ATRS, long[] ATRSLen);
|
||||
public static native long CpuApdu(byte Slot, long SendApduLen, byte[] SendApdu, byte[] RecvApdu, long[] RecvApduLen);
|
||||
public static native long CpuPowerOff(byte Slot);
|
||||
|
||||
public static native long IccGetCardInfo(int ICtype, String AIDList, String TagList, byte[] IcCardInfo);
|
||||
public static native long IccGetARQC(int ICtype, String trData, String AIDList, byte[] ARQC, byte[] trAppData);
|
||||
public static native long IccARPCExeScript(int ICtype, String trData, String ARPC, String trAppData, byte[] ScriptResult, byte[] TC);
|
||||
public static native long IccGetTrDetail(int ICtype, String AIDList, byte[] TrDetail);
|
||||
public static native long IccGetLoadDetail(int ICtype, String AIDList, byte[] LoadDetail);
|
||||
|
||||
public static native long HexToAsc(byte[] Hex, long HexLength, byte[] Asc);
|
||||
public static native long AscToHex(String Asc, long HexLength, byte[] Hex);
|
||||
}
|
||||
300
android/src/main/java/com/sdses/UsbHidPort.java
Normal file
300
android/src/main/java/com/sdses/UsbHidPort.java
Normal file
@@ -0,0 +1,300 @@
|
||||
package com.sdses;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.hardware.usb.UsbConstants;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.hardware.usb.UsbDeviceConnection;
|
||||
import android.hardware.usb.UsbEndpoint;
|
||||
import android.hardware.usb.UsbInterface;
|
||||
import android.hardware.usb.UsbManager;
|
||||
import android.os.Build;
|
||||
|
||||
public class UsbHidPort
|
||||
{
|
||||
private static final String ACTION_USB_PERMISSION = "com.sdses.JniCommonInterface.USB_PERMISSION";
|
||||
|
||||
private static final BroadcastReceiver mUsbReceiver = new BroadcastReceiver()
|
||||
{
|
||||
public void onReceive(Context context, Intent intent)
|
||||
{
|
||||
String action = intent.getAction();
|
||||
if (action.equals(ACTION_USB_PERMISSION))
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false))
|
||||
{
|
||||
if (device != null)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static UsbManager usbManager = null;
|
||||
public static UsbDevice usbDevice = null;
|
||||
public static UsbInterface usbInterface = null;
|
||||
public static UsbDeviceConnection usbConnection = null;
|
||||
public static UsbEndpoint epOut;
|
||||
public static UsbEndpoint epIn;
|
||||
public static int SendBlockSize = 0;
|
||||
public static int RecvBlockSize = 0;
|
||||
|
||||
public static synchronized long GetUsbPermission(Context ctx, int Vid, int Pid)
|
||||
{
|
||||
try
|
||||
{
|
||||
usbManager = (UsbManager)ctx.getSystemService(Context.USB_SERVICE);
|
||||
HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();
|
||||
Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
|
||||
|
||||
usbDevice = null;
|
||||
while (deviceIterator.hasNext())
|
||||
{
|
||||
UsbDevice device = deviceIterator.next();
|
||||
if ((device.getVendorId() == Vid) && (device.getProductId() == Pid))
|
||||
{
|
||||
usbDevice = device;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (usbDevice == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(usbDevice.getInterfaceCount() <= 0)
|
||||
{
|
||||
return -2;
|
||||
}
|
||||
|
||||
usbInterface = usbDevice.getInterface(0);
|
||||
if(usbInterface == null)
|
||||
{
|
||||
return -2;
|
||||
}
|
||||
|
||||
// 判断是否有权限
|
||||
if(!usbManager.hasPermission(usbDevice))
|
||||
{
|
||||
// Android 12+ 需要明确指定 PendingIntent 的 flag
|
||||
int flags = 0;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
flags = PendingIntent.FLAG_IMMUTABLE;
|
||||
}
|
||||
PendingIntent mPermissionIntent = PendingIntent.getBroadcast(ctx, 0, new Intent(ACTION_USB_PERMISSION), flags);
|
||||
IntentFilter permissionFilter = new IntentFilter(ACTION_USB_PERMISSION);
|
||||
ctx.registerReceiver(mUsbReceiver, permissionFilter);
|
||||
usbManager.requestPermission(usbDevice, mPermissionIntent);
|
||||
System.out.print("Requesting Usb Permission");
|
||||
|
||||
// 等待权限授权,最多等待10秒
|
||||
int waitCount = 0;
|
||||
while(!usbManager.hasPermission(usbDevice) && waitCount < 100)
|
||||
{
|
||||
try {
|
||||
Thread.sleep(100); // 等待100ms
|
||||
waitCount++;
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 清理广播接收器
|
||||
try {
|
||||
ctx.unregisterReceiver(mUsbReceiver);
|
||||
} catch (Exception e) {
|
||||
// 忽略取消注册时的异常
|
||||
}
|
||||
|
||||
// 检查是否获得权限
|
||||
if (!usbManager.hasPermission(usbDevice)) {
|
||||
return -3; // 权限请求失败或超时
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
return -99;
|
||||
}
|
||||
}
|
||||
|
||||
public static long UsbOpenPort(int Vid, int Pid)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (usbDevice == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
usbConnection = usbManager.openDevice(usbDevice);
|
||||
if(usbConnection == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(usbInterface == null)
|
||||
{
|
||||
return -2;
|
||||
}
|
||||
|
||||
if(!usbConnection.claimInterface(usbInterface, true))
|
||||
{
|
||||
usbConnection.close();
|
||||
usbConnection = null;
|
||||
return -2;
|
||||
}
|
||||
|
||||
for(int i = 0; i < usbInterface.getEndpointCount(); i++)
|
||||
{
|
||||
UsbEndpoint endPoint = usbInterface.getEndpoint(i);
|
||||
switch(endPoint.getType())
|
||||
{
|
||||
case UsbConstants.USB_ENDPOINT_XFER_BULK:
|
||||
case UsbConstants.USB_ENDPOINT_XFER_INT:
|
||||
{
|
||||
switch(endPoint.getDirection())
|
||||
{
|
||||
case UsbConstants.USB_DIR_OUT:
|
||||
{
|
||||
epOut = endPoint;
|
||||
break;
|
||||
}
|
||||
case UsbConstants.USB_DIR_IN:
|
||||
{
|
||||
epIn = endPoint;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UsbConstants.USB_ENDPOINT_XFER_CONTROL:
|
||||
{
|
||||
epOut = endPoint;
|
||||
epIn = endPoint;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SendBlockSize = epOut.getMaxPacketSize();
|
||||
RecvBlockSize = epIn.getMaxPacketSize();
|
||||
|
||||
return SendBlockSize;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
return -99;
|
||||
}
|
||||
}
|
||||
|
||||
public static long UsbWrite(byte[] buffer, int len, int timeout)
|
||||
{
|
||||
try
|
||||
{
|
||||
return usbConnection.bulkTransfer(epOut, buffer, SendBlockSize, timeout);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
return -99;
|
||||
}
|
||||
}
|
||||
|
||||
public static long UsbRead(byte[] buffer, int timeout)
|
||||
{
|
||||
/*
|
||||
try
|
||||
{
|
||||
Thread.sleep(10);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
*/
|
||||
|
||||
try
|
||||
{
|
||||
return usbConnection.bulkTransfer(epIn, buffer, RecvBlockSize, timeout);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
return -99;
|
||||
}
|
||||
}
|
||||
|
||||
public static long UsbCtrlWrite(byte[] buffer, int len, int timeout)
|
||||
{
|
||||
try
|
||||
{
|
||||
return usbConnection.controlTransfer(0x21, 0x09, 0x0200, 0x0000, buffer, SendBlockSize, timeout);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
return -99;
|
||||
}
|
||||
}
|
||||
|
||||
public static long UsbCtrlRead(byte[] buffer, int timeout)
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(10);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return usbConnection.controlTransfer(0xA1, 0x01, 0x0100, 0x0000, buffer, RecvBlockSize, timeout);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
return -99;
|
||||
}
|
||||
}
|
||||
|
||||
public static void UsbClosePort()
|
||||
{
|
||||
try
|
||||
{
|
||||
usbConnection.releaseInterface(usbInterface);
|
||||
usbConnection.close();
|
||||
usbConnection = null;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
android/src/main/jniLibs/arm64-v8a/libCommonInterface.so
Normal file
BIN
android/src/main/jniLibs/arm64-v8a/libCommonInterface.so
Normal file
Binary file not shown.
BIN
android/src/main/jniLibs/arm64-v8a/libPortCommunication.so
Normal file
BIN
android/src/main/jniLibs/arm64-v8a/libPortCommunication.so
Normal file
Binary file not shown.
BIN
android/src/main/jniLibs/arm64-v8a/libTerminalProtocol.so
Normal file
BIN
android/src/main/jniLibs/arm64-v8a/libTerminalProtocol.so
Normal file
Binary file not shown.
BIN
android/src/main/jniLibs/arm64-v8a/libWltRS.so
Normal file
BIN
android/src/main/jniLibs/arm64-v8a/libWltRS.so
Normal file
Binary file not shown.
BIN
android/src/main/jniLibs/armeabi/libCommonInterface.so
Normal file
BIN
android/src/main/jniLibs/armeabi/libCommonInterface.so
Normal file
Binary file not shown.
BIN
android/src/main/jniLibs/armeabi/libPortCommunication.so
Normal file
BIN
android/src/main/jniLibs/armeabi/libPortCommunication.so
Normal file
Binary file not shown.
BIN
android/src/main/jniLibs/armeabi/libTerminalProtocol.so
Normal file
BIN
android/src/main/jniLibs/armeabi/libTerminalProtocol.so
Normal file
Binary file not shown.
BIN
android/src/main/jniLibs/armeabi/libWltRS.so
Normal file
BIN
android/src/main/jniLibs/armeabi/libWltRS.so
Normal file
Binary file not shown.
14
android/src/main/res/xml/device_filter.xml
Normal file
14
android/src/main/res/xml/device_filter.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- 身份证读卡器设备过滤器 -->
|
||||
<!-- 可以根据实际设备的VID/PID进行配置 -->
|
||||
<usb-device vendor-id="1234" product-id="5678" />
|
||||
|
||||
<!-- 通用HID设备过滤器 -->
|
||||
<usb-device class="3" subclass="0" protocol="0" />
|
||||
|
||||
<!-- 如果知道具体的设备VID/PID,可以添加更多配置 -->
|
||||
|
||||
<usb-device vendor-id="1024" product-id="50010" />
|
||||
|
||||
</resources>
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.example.idcard;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* This demonstrates a simple unit test of the Java portion of this plugin's implementation.
|
||||
*
|
||||
* Once you have built the plugin's example app, you can run these tests from the command
|
||||
* line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or
|
||||
* you can run them directly from IDEs that support JUnit such as Android Studio.
|
||||
*/
|
||||
|
||||
public class IdcardPluginTest {
|
||||
@Test
|
||||
public void onMethodCall_getPlatformVersion_returnsExpectedValue() {
|
||||
IdcardPlugin plugin = new IdcardPlugin();
|
||||
|
||||
final MethodCall call = new MethodCall("getPlatformVersion", null);
|
||||
MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
|
||||
plugin.onMethodCall(call, mockResult);
|
||||
|
||||
verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE);
|
||||
}
|
||||
}
|
||||
43
example/.gitignore
vendored
Normal file
43
example/.gitignore
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
16
example/README.md
Normal file
16
example/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# idcard_example
|
||||
|
||||
Demonstrates how to use the idcard plugin.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a starting point for a Flutter application.
|
||||
|
||||
A few resources to get you started if this is your first Flutter project:
|
||||
|
||||
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
||||
|
||||
For help getting started with Flutter development, view the
|
||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
28
example/analysis_options.yaml
Normal file
28
example/analysis_options.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
# This file configures the analyzer, which statically analyzes Dart code to
|
||||
# check for errors, warnings, and lints.
|
||||
#
|
||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||
# invoked from the command line by running `flutter analyze`.
|
||||
|
||||
# The following line activates a set of recommended lints for Flutter apps,
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
# included above or to enable additional rules. A list of all available lints
|
||||
# and their documentation is published at https://dart.dev/lints.
|
||||
#
|
||||
# Instead of disabling a lint rule for the entire project in the
|
||||
# section below, it can also be suppressed for a single line of code
|
||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||
# producing the lint.
|
||||
rules:
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
13
example/android/.gitignore
vendored
Normal file
13
example/android/.gitignore
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
gradle-wrapper.jar
|
||||
/.gradle
|
||||
/captures/
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/local.properties
|
||||
GeneratedPluginRegistrant.java
|
||||
|
||||
# Remember to never publicly share your keystore.
|
||||
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
|
||||
key.properties
|
||||
**/*.keystore
|
||||
**/*.jks
|
||||
58
example/android/app/build.gradle
Normal file
58
example/android/app/build.gradle
Normal file
@@ -0,0 +1,58 @@
|
||||
plugins {
|
||||
id "com.android.application"
|
||||
id "kotlin-android"
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id "dev.flutter.flutter-gradle-plugin"
|
||||
}
|
||||
|
||||
def localProperties = new Properties()
|
||||
def localPropertiesFile = rootProject.file("local.properties")
|
||||
if (localPropertiesFile.exists()) {
|
||||
localPropertiesFile.withReader("UTF-8") { reader ->
|
||||
localProperties.load(reader)
|
||||
}
|
||||
}
|
||||
|
||||
def flutterVersionCode = localProperties.getProperty("flutter.versionCode")
|
||||
if (flutterVersionCode == null) {
|
||||
flutterVersionCode = "1"
|
||||
}
|
||||
|
||||
def flutterVersionName = localProperties.getProperty("flutter.versionName")
|
||||
if (flutterVersionName == null) {
|
||||
flutterVersionName = "1.0"
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.idcard_example"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "com.example.idcard_example"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||
minSdk = flutter.minSdkVersion
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutterVersionCode.toInteger()
|
||||
versionName = flutterVersionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig = signingConfigs.debug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
7
example/android/app/src/debug/AndroidManifest.xml
Normal file
7
example/android/app/src/debug/AndroidManifest.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
45
example/android/app/src/main/AndroidManifest.xml
Normal file
45
example/android/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,45 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application
|
||||
android:label="idcard_example"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility and
|
||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||
|
||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.example.idcard_example;
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity;
|
||||
|
||||
public class MainActivity extends FlutterActivity {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
BIN
example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 544 B |
BIN
example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 442 B |
BIN
example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 721 B |
BIN
example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
BIN
example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
18
example/android/app/src/main/res/values-night/styles.xml
Normal file
18
example/android/app/src/main/res/values-night/styles.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
18
example/android/app/src/main/res/values/styles.xml
Normal file
18
example/android/app/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
7
example/android/app/src/profile/AndroidManifest.xml
Normal file
7
example/android/app/src/profile/AndroidManifest.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
18
example/android/build.gradle
Normal file
18
example/android/build.gradle
Normal file
@@ -0,0 +1,18 @@
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.buildDir = "../build"
|
||||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(":app")
|
||||
}
|
||||
|
||||
tasks.register("clean", Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
3
example/android/gradle.properties
Normal file
3
example/android/gradle.properties
Normal file
@@ -0,0 +1,3 @@
|
||||
org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
5
example/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
example/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip
|
||||
25
example/android/settings.gradle
Normal file
25
example/android/settings.gradle
Normal file
@@ -0,0 +1,25 @@
|
||||
pluginManagement {
|
||||
def flutterSdkPath = {
|
||||
def properties = new Properties()
|
||||
file("local.properties").withInputStream { properties.load(it) }
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
return flutterSdkPath
|
||||
}()
|
||||
|
||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version "7.3.0" apply false
|
||||
id "org.jetbrains.kotlin.android" version "1.7.10" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
||||
25
example/integration_test/plugin_integration_test.dart
Normal file
25
example/integration_test/plugin_integration_test.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
// This is a basic Flutter integration test.
|
||||
//
|
||||
// Since integration tests run in a full Flutter application, they can interact
|
||||
// with the host side of a plugin implementation, unlike Dart unit tests.
|
||||
//
|
||||
// For more information about Flutter integration tests, please see
|
||||
// https://docs.flutter.dev/cookbook/testing/integration/introduction
|
||||
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'package:idcard/idcard.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
testWidgets('getPlatformVersion test', (WidgetTester tester) async {
|
||||
final Idcard plugin = Idcard();
|
||||
final String? version = await plugin.getPlatformVersion();
|
||||
// The version string depends on the host platform running the test, so
|
||||
// just assert that some non-empty string is returned.
|
||||
expect(version?.isNotEmpty, true);
|
||||
});
|
||||
}
|
||||
366
example/lib/main.dart
Normal file
366
example/lib/main.dart
Normal file
@@ -0,0 +1,366 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:idcard/idcard.dart';
|
||||
import 'package:idcard/idcard_platform_interface.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatefulWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
State<MyApp> createState() => _MyAppState();
|
||||
}
|
||||
|
||||
class _MyAppState extends State<MyApp> {
|
||||
String _platformVersion = 'Unknown';
|
||||
String _status = '未连接';
|
||||
IdCardInfo? _cardInfo;
|
||||
bool _isReading = false;
|
||||
bool _isLoading = false;
|
||||
bool _isConnected = false;
|
||||
String _statusMessage = '未连接';
|
||||
final _idcardPlugin = Idcard();
|
||||
|
||||
final String _portType = "USB";
|
||||
final String _portPara = "0400C35A";
|
||||
final String _portExtend = "MI";
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
initPlatformState();
|
||||
}
|
||||
|
||||
/// 初始化平台状态
|
||||
Future<void> initPlatformState() async {
|
||||
String platformVersion;
|
||||
try {
|
||||
platformVersion = await _idcardPlugin.getPlatformVersion() ?? 'Unknown platform version';
|
||||
} on PlatformException {
|
||||
platformVersion = 'Failed to get platform version.';
|
||||
}
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
setState(() {
|
||||
_platformVersion = platformVersion;
|
||||
});
|
||||
}
|
||||
|
||||
/// 连接设备
|
||||
Future<void> _connectDevice() async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_statusMessage = '正在连接设备...';
|
||||
_status = '正在连接设备...';
|
||||
});
|
||||
|
||||
try {
|
||||
// 首先获取USB权限
|
||||
setState(() {
|
||||
_statusMessage = '正在请求USB权限...';
|
||||
_status = '正在请求USB权限...';
|
||||
});
|
||||
|
||||
// int vid = int.parse(portPara.substring(0, 4), radix: 16);
|
||||
// int pid = int.parse(portPara.substring(4, 8), radix: 16);
|
||||
|
||||
final permissionResult = await _idcardPlugin.getUsbPermission(vid: int.parse(_portPara.substring(0, 4), radix: 16), pid: int.parse(_portPara.substring(4, 8), radix: 16));
|
||||
if (permissionResult != 0) {
|
||||
throw Exception('获取USB权限失败: $permissionResult');
|
||||
}
|
||||
|
||||
// 打开设备
|
||||
setState(() {
|
||||
_statusMessage = '正在打开设备...';
|
||||
_status = '正在打开设备...';
|
||||
});
|
||||
|
||||
// "USB", "0400C35A", "MI";
|
||||
final openResult = await _idcardPlugin.openDevice(portType: _portType, portPara: _portPara, extendPara: _portExtend);
|
||||
print("设备句柄:$openResult");
|
||||
if (openResult <= 0) {
|
||||
throw Exception('打开设备失败: $openResult');
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_isConnected = true;
|
||||
_statusMessage = '设备连接成功';
|
||||
_status = '设备连接成功';
|
||||
});
|
||||
} on PlatformException catch (e) {
|
||||
String errorMessage = '连接失败: ${e.message}';
|
||||
|
||||
// 针对不同错误提供用户友好的提示
|
||||
if (e.code == 'PERMISSION_DENIED') {
|
||||
errorMessage = 'USB权限被拒绝。\n\n解决方法:\n1. 请在弹出的权限对话框中点击"允许"\n2. 或者在系统设置中手动授权USB权限\n3. 确保设备已正确连接';
|
||||
} else if (e.code == 'DEVICE_NOT_FOUND') {
|
||||
errorMessage = '未找到身份证读卡器设备。\n\n请检查:\n1. 设备是否已连接\n2. 设备驱动是否正常\n3. VID/PID是否正确';
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_statusMessage = errorMessage;
|
||||
_status = errorMessage;
|
||||
_isConnected = false;
|
||||
});
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_statusMessage = '连接失败: $e';
|
||||
_status = '连接失败: $e';
|
||||
_isConnected = false;
|
||||
});
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// 断开设备连接
|
||||
Future<void> _disconnectDevice() async {
|
||||
try {
|
||||
await _idcardPlugin.closeDevice();
|
||||
setState(() {
|
||||
_status = '设备已断开';
|
||||
_statusMessage = '设备已断开';
|
||||
_isConnected = false;
|
||||
_cardInfo = null;
|
||||
});
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_status = '断开连接失败:$e';
|
||||
_statusMessage = '断开连接失败:$e';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// 读取身份证
|
||||
Future<void> _readCard() async {
|
||||
if (_isReading) return;
|
||||
|
||||
setState(() {
|
||||
_isReading = true;
|
||||
_status = '请将身份证放在读卡器上...';
|
||||
_cardInfo = null;
|
||||
});
|
||||
|
||||
try {
|
||||
// 使用完整的读卡流程
|
||||
IdCardInfo cardInfo = await _idcardPlugin.readCardComplete();
|
||||
print(cardInfo);
|
||||
|
||||
setState(() {
|
||||
_cardInfo = cardInfo;
|
||||
_status = '读卡成功';
|
||||
_isReading = false;
|
||||
});
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_status = '读卡失败:$e';
|
||||
_isReading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// 构建身份证信息显示卡片
|
||||
Widget _buildCardInfoWidget() {
|
||||
if (_cardInfo == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.all(16),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'身份证信息',
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildInfoRow('姓名', _cardInfo!.name),
|
||||
_buildInfoRow('性别', _cardInfo!.gender),
|
||||
_buildInfoRow('民族', _cardInfo!.nation),
|
||||
_buildInfoRow('出生日期', _cardInfo!.birthDate),
|
||||
_buildInfoRow('身份证号', _cardInfo!.idNumber),
|
||||
_buildInfoRow('地址', _cardInfo!.address),
|
||||
_buildInfoRow('签发机关', _cardInfo!.signOrgan),
|
||||
_buildInfoRow('有效期', _cardInfo!.validTerm),
|
||||
if (_cardInfo!.photo != null) ...[
|
||||
const SizedBox(height: 16),
|
||||
const Text('照片:'),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
width: 120,
|
||||
height: 160,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Image.memory(
|
||||
Uint8List.fromList(_cardInfo!.photo!),
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return const Center(
|
||||
child: Text('照片加载失败'),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建信息行
|
||||
Widget _buildInfoRow(String label, String value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 80,
|
||||
child: Text(
|
||||
'$label:',
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(value),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: '身份证读卡器示例',
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.blue,
|
||||
useMaterial3: true,
|
||||
),
|
||||
home: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('身份证读卡器示例'),
|
||||
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
// 平台信息
|
||||
Card(
|
||||
margin: const EdgeInsets.all(16),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'平台信息',
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text('运行平台:$_platformVersion'),
|
||||
const SizedBox(height: 8),
|
||||
Text('设备状态:$_status'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// 操作按钮
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: (_isLoading || _isConnected) ? null : _connectDevice,
|
||||
child: _isLoading
|
||||
? const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text('连接中...'),
|
||||
],
|
||||
)
|
||||
: const Text('连接设备'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: (_isLoading || !_isConnected) ? null : _disconnectDevice,
|
||||
child: const Text('断开连接'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// 读卡按钮
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: (_isReading || !_isConnected || _isLoading) ? null : _readCard,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.green,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
),
|
||||
child: _isReading
|
||||
? const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text('正在读卡...'),
|
||||
],
|
||||
)
|
||||
: const Text(
|
||||
'读取身份证',
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// 身份证信息显示
|
||||
_buildCardInfoWidget(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
283
example/pubspec.lock
Normal file
283
example/pubspec.lock
Normal file
@@ -0,0 +1,283 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.11.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cupertino_icons
|
||||
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.8"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_driver:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
fuchsia_remote_debug_protocol:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
idcard:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: ".."
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
integration_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.4"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.16+1"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.0"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.4"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
process:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: process
|
||||
sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.2"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.99"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
sync_http:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sync_http
|
||||
sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.0"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.2.1"
|
||||
webdriver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webdriver
|
||||
sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
sdks:
|
||||
dart: ">=3.4.4 <4.0.0"
|
||||
flutter: ">=3.18.0-18.0.pre.54"
|
||||
85
example/pubspec.yaml
Normal file
85
example/pubspec.yaml
Normal file
@@ -0,0 +1,85 @@
|
||||
name: idcard_example
|
||||
description: "Demonstrates how to use the idcard plugin."
|
||||
# The following line prevents the package from being accidentally published to
|
||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
|
||||
environment:
|
||||
sdk: '>=3.4.4 <4.0.0'
|
||||
|
||||
# Dependencies specify other packages that your package needs in order to work.
|
||||
# To automatically upgrade your package dependencies to the latest versions
|
||||
# consider running `flutter pub upgrade --major-versions`. Alternatively,
|
||||
# dependencies can be manually updated by changing the version numbers below to
|
||||
# the latest version available on pub.dev. To see which dependencies have newer
|
||||
# versions available, run `flutter pub outdated`.
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
idcard:
|
||||
# When depending on this package from a real application you should use:
|
||||
# idcard: ^x.y.z
|
||||
# See https://dart.dev/tools/pub/dependencies#version-constraints
|
||||
# The example app is bundled with the plugin so we use a path dependency on
|
||||
# the parent directory to use the current plugin's version.
|
||||
path: ../
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.6
|
||||
|
||||
dev_dependencies:
|
||||
integration_test:
|
||||
sdk: flutter
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
# The "flutter_lints" package below contains a set of recommended lints to
|
||||
# encourage good coding practices. The lint set provided by the package is
|
||||
# activated in the `analysis_options.yaml` file located at the root of your
|
||||
# package. See that file for information about deactivating specific lint
|
||||
# rules and activating additional ones.
|
||||
flutter_lints: ^3.0.0
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
# The following section is specific to Flutter packages.
|
||||
flutter:
|
||||
|
||||
# The following line ensures that the Material Icons font is
|
||||
# included with your application, so that you can use the icons in
|
||||
# the material Icons class.
|
||||
uses-material-design: true
|
||||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
||||
|
||||
# For details regarding adding assets from package dependencies, see
|
||||
# https://flutter.dev/assets-and-images/#from-packages
|
||||
|
||||
# To add custom fonts to your application, add a fonts section here,
|
||||
# in this "flutter" section. Each entry in this list should have a
|
||||
# "family" key with the font family name, and a "fonts" key with a
|
||||
# list giving the asset and other descriptors for the font. For
|
||||
# example:
|
||||
# fonts:
|
||||
# - family: Schyler
|
||||
# fonts:
|
||||
# - asset: fonts/Schyler-Regular.ttf
|
||||
# - asset: fonts/Schyler-Italic.ttf
|
||||
# style: italic
|
||||
# - family: Trajan Pro
|
||||
# fonts:
|
||||
# - asset: fonts/TrajanPro.ttf
|
||||
# - asset: fonts/TrajanPro_Bold.ttf
|
||||
# weight: 700
|
||||
#
|
||||
# For details regarding fonts from package dependencies,
|
||||
# see https://flutter.dev/custom-fonts/#from-packages
|
||||
27
example/test/widget_test.dart
Normal file
27
example/test/widget_test.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
// This is a basic Flutter widget test.
|
||||
//
|
||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||
// utility in the flutter_test package. For example, you can send tap and scroll
|
||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:idcard_example/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Verify Platform version', (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
await tester.pumpWidget(const MyApp());
|
||||
|
||||
// Verify that platform version is retrieved.
|
||||
expect(
|
||||
find.byWidgetPredicate(
|
||||
(Widget widget) => widget is Text &&
|
||||
widget.data!.startsWith('Running on:'),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
}
|
||||
214
lib/idcard.dart
Normal file
214
lib/idcard.dart
Normal file
@@ -0,0 +1,214 @@
|
||||
|
||||
import 'idcard_platform_interface.dart';
|
||||
|
||||
/// 身份证读卡器插件主类
|
||||
/// 基于神思标准化接口规范实现的Flutter插件
|
||||
class Idcard {
|
||||
/// 获取平台版本
|
||||
Future<String?> getPlatformVersion() {
|
||||
return IdcardPlatform.instance.getPlatformVersion();
|
||||
}
|
||||
|
||||
/// 获取USB权限
|
||||
/// [vid] - USB设备的厂商ID,默认为0x261A(神思USB读卡器厂商ID)
|
||||
/// [pid] - USB设备的产品ID,默认为0x0011
|
||||
Future<int> getUsbPermission({int vid = 0x261A, int pid = 0x0011}) {
|
||||
return IdcardPlatform.instance.getUsbPermission(vid, pid);
|
||||
}
|
||||
|
||||
/// 打开设备连接
|
||||
/// [portType] - 端口类型,支持"USB"、"COM"、"SKT"、"BTH"、"AUTO"
|
||||
/// [portPara] - 端口参数,根据端口类型不同而不同
|
||||
/// [extendPara] - 扩展参数
|
||||
/// 返回值:大于0表示成功(设备句柄),其他为失败
|
||||
Future<int> openDevice({String portType = 'USB', String portPara = '', String extendPara = ''}) {
|
||||
return IdcardPlatform.instance.openDevice(
|
||||
portType: portType,
|
||||
portPara: portPara,
|
||||
extendPara: extendPara,
|
||||
);
|
||||
}
|
||||
|
||||
/// 设置当前设备(多设备操作)
|
||||
/// [devHandle] - 设备句柄
|
||||
/// 返回值:0表示成功;非0表示失败
|
||||
Future<int> setCurrentDevice(int devHandle) {
|
||||
return IdcardPlatform.instance.setCurrentDevice(devHandle);
|
||||
}
|
||||
|
||||
/// 获取当前设备(多设备操作)
|
||||
/// 返回值:返回当前设备的句柄
|
||||
Future<int> getCurrentDevice() {
|
||||
return IdcardPlatform.instance.getCurrentDevice();
|
||||
}
|
||||
|
||||
/// 获取接口库信息
|
||||
/// 返回值:包含version和description的Map
|
||||
Future<Map<String, String>> getLibraryInfo() {
|
||||
return IdcardPlatform.instance.getLibraryInfo();
|
||||
}
|
||||
|
||||
/// 获取设备型号
|
||||
/// 返回值:设备型号字符串
|
||||
Future<String> getTerminalModel() {
|
||||
return IdcardPlatform.instance.getTerminalModel();
|
||||
}
|
||||
|
||||
/// 设备轮询心跳
|
||||
/// 用于检测与读卡器是否已建立连接并且通讯正常
|
||||
/// 返回值:0表示成功;非0表示失败
|
||||
Future<int> terminalHeartBeat() {
|
||||
return IdcardPlatform.instance.terminalHeartBeat();
|
||||
}
|
||||
|
||||
/// 获取接收数据
|
||||
/// 获取最后一次通讯收到的数据,一般用于获取读卡器协议层错误信息
|
||||
/// 返回值:最后一次通讯收到的数据
|
||||
Future<List<int>> getLastRecvData() {
|
||||
return IdcardPlatform.instance.getLastRecvData();
|
||||
}
|
||||
|
||||
/// 获取设备固件版本
|
||||
/// 返回值:包含firmVersion和hardwareVersion的Map
|
||||
Future<Map<String, String>> getTerminalFirmVersion() {
|
||||
return IdcardPlatform.instance.getTerminalFirmVersion();
|
||||
}
|
||||
|
||||
/// 获取设备序列号
|
||||
/// 返回值:设备序列号字符串
|
||||
Future<String> getTerminalSn() {
|
||||
return IdcardPlatform.instance.getTerminalSn();
|
||||
}
|
||||
|
||||
/// 关闭设备连接
|
||||
/// 返回值:0表示成功;非0表示失败
|
||||
Future<int> closeDevice() {
|
||||
return IdcardPlatform.instance.closeDevice();
|
||||
}
|
||||
|
||||
/// 寻找身份证卡片
|
||||
/// 返回值:大于0表示成功找到卡片,小于等于0表示错误码
|
||||
Future<int> findCard() {
|
||||
return IdcardPlatform.instance.findCard();
|
||||
}
|
||||
|
||||
/// 选择身份证卡片
|
||||
/// 返回值:大于0表示成功选择卡片,小于等于0表示错误码
|
||||
Future<int> selectCard() {
|
||||
return IdcardPlatform.instance.selectCard();
|
||||
}
|
||||
|
||||
/// 读取身份证(新版接口,支持多种证件类型)
|
||||
/// [cardType] - 读取卡类型:
|
||||
/// 0x00:读取二代证或外国人或港澳台
|
||||
/// 0x01:只读二代证
|
||||
/// 0x02:只读外国人
|
||||
/// 0x03:只读港澳台
|
||||
/// 0x10-0x13:对应上述类型但包含指纹信息
|
||||
/// [infoEncoding] - 返回信息的编码方式:
|
||||
/// 0x01:GB18030编码(GBK)
|
||||
/// 0x02:UTF16-LE编码
|
||||
/// 0x03:UTF-8编码
|
||||
/// [timeOut] - 读卡超时时间(毫秒),0表示不等待,>0表示等待放卡
|
||||
/// 返回值:包含result(错误码)和data(冒号分隔的身份证信息字符串)的Map
|
||||
Future<Map<String, dynamic>> idReadCard({int cardType = 0x00, int infoEncoding = 0x01, int timeOut = 30000}) {
|
||||
return IdcardPlatform.instance.idReadCard(
|
||||
cardType: cardType,
|
||||
infoEncoding: infoEncoding,
|
||||
timeOut: timeOut,
|
||||
);
|
||||
}
|
||||
|
||||
/// 读取身份证原始数据(兼容旧版接口)
|
||||
/// [cardType] - 卡片类型,1表示身份证
|
||||
/// [infoEncoding] - 信息编码,0表示默认编码
|
||||
/// [timeOut] - 超时时间(毫秒),默认30秒
|
||||
/// 返回值:包含result(错误码)和data(原始数据)的Map
|
||||
Future<Map<String, dynamic>> readCard({int cardType = 1, int infoEncoding = 0, int timeOut = 30000}) {
|
||||
return IdcardPlatform.instance.readCard(
|
||||
cardType: cardType,
|
||||
infoEncoding: infoEncoding,
|
||||
timeOut: timeOut,
|
||||
);
|
||||
}
|
||||
|
||||
/// 读取身份证详细信息
|
||||
/// 返回值:IdCardInfo对象,包含姓名、性别、民族等详细信息
|
||||
/// 抛出异常:如果读取失败会抛出Exception
|
||||
Future<IdCardInfo> readCardInfo() {
|
||||
return IdcardPlatform.instance.readCardInfo();
|
||||
}
|
||||
|
||||
/// 读取追加住址
|
||||
/// 返回值:追加住址信息字符串
|
||||
Future<String> readNewAddress() {
|
||||
return IdcardPlatform.instance.readNewAddress();
|
||||
}
|
||||
|
||||
/// 获取SAM模块状态
|
||||
/// 返回值:0表示成功;非0表示失败
|
||||
Future<int> getSamStatus() {
|
||||
return IdcardPlatform.instance.getSamStatus();
|
||||
}
|
||||
|
||||
/// 获取SAM模块编号字符串
|
||||
/// 返回值:SAM模块编号字符串
|
||||
Future<String> getSamIdStr() {
|
||||
return IdcardPlatform.instance.getSamIdStr();
|
||||
}
|
||||
|
||||
/// 完整的身份证读取流程
|
||||
/// 自动执行寻卡、选卡、读取信息的完整流程
|
||||
/// [timeOut] - 超时时间(毫秒),默认30秒
|
||||
/// [retryInterval] - 重试间隔时间(毫秒),默认500毫秒
|
||||
/// 返回值:IdCardInfo对象
|
||||
/// 抛出异常:如果超时或读取失败会抛出Exception
|
||||
Future<IdCardInfo> readCardComplete({int timeOut = 30000, int retryInterval = 500}) async {
|
||||
final startTime = DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
// 1. 寻卡
|
||||
int findResult = await findCard();
|
||||
print("寻卡结果:$findResult");
|
||||
|
||||
if (findResult == 0) {
|
||||
// 2. 选卡
|
||||
int selectResult = await selectCard();
|
||||
print("选卡结果:$selectResult");
|
||||
|
||||
if (selectResult == 0) {
|
||||
// 3. 读取信息
|
||||
return await readCardInfo();
|
||||
} else {
|
||||
print('选卡失败,错误码:$selectResult,继续重试...');
|
||||
}
|
||||
} else {
|
||||
print('寻卡失败,错误码:$findResult,继续重试...');
|
||||
}
|
||||
} catch (e) {
|
||||
print('读卡过程中发生异常:$e,继续重试...');
|
||||
}
|
||||
|
||||
// 检查是否超时
|
||||
final currentTime = DateTime.now().millisecondsSinceEpoch;
|
||||
if (currentTime - startTime >= timeOut) {
|
||||
throw Exception('读卡超时,超时时间:$timeOut毫秒');
|
||||
}
|
||||
|
||||
// 等待重试间隔
|
||||
await Future.delayed(Duration(milliseconds: retryInterval));
|
||||
}
|
||||
}
|
||||
|
||||
/// 检查设备是否已连接
|
||||
/// 通过尝试获取平台版本来检查设备连接状态
|
||||
Future<bool> isDeviceConnected() async {
|
||||
try {
|
||||
final version = await getPlatformVersion();
|
||||
return version != null;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
180
lib/idcard_method_channel.dart
Normal file
180
lib/idcard_method_channel.dart
Normal file
@@ -0,0 +1,180 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'idcard_platform_interface.dart';
|
||||
|
||||
/// 使用方法通道的IdcardPlatform实现
|
||||
class MethodChannelIdcard extends IdcardPlatform {
|
||||
/// 与原生平台交互的方法通道
|
||||
@visibleForTesting
|
||||
final methodChannel = const MethodChannel('idcard');
|
||||
|
||||
@override
|
||||
Future<String?> getPlatformVersion() async {
|
||||
final version = await methodChannel.invokeMethod<String>('getPlatformVersion');
|
||||
return version;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> getUsbPermission(int vid, int pid) async {
|
||||
final result = await methodChannel.invokeMethod<int>('getUsbPermission', {
|
||||
'vid': vid,
|
||||
'pid': pid,
|
||||
});
|
||||
return result ?? -1;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> openDevice({String portType = 'USB', String portPara = '', String extendPara = ''}) async {
|
||||
final result = await methodChannel.invokeMethod<int>('openDevice', {
|
||||
'portType': portType,
|
||||
'portPara': portPara,
|
||||
'extendPara': extendPara,
|
||||
});
|
||||
return result ?? -1;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> setCurrentDevice(int devHandle) async {
|
||||
final result = await methodChannel.invokeMethod<int>('setCurrentDevice', {
|
||||
'devHandle': devHandle,
|
||||
});
|
||||
return result ?? -1;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> getCurrentDevice() async {
|
||||
final result = await methodChannel.invokeMethod<int>('getCurrentDevice');
|
||||
return result ?? -1;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, String>> getLibraryInfo() async {
|
||||
final result = await methodChannel.invokeMethod('getLibraryInfo');
|
||||
if (result == null) return {'version': '', 'description': ''};
|
||||
// 安全地转换类型
|
||||
final Map<String, dynamic> data = Map<String, dynamic>.from(result as Map);
|
||||
return {
|
||||
'version': data['version']?.toString() ?? '',
|
||||
'description': data['description']?.toString() ?? '',
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> getTerminalModel() async {
|
||||
final result = await methodChannel.invokeMethod<String>('getTerminalModel');
|
||||
return result ?? '';
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> terminalHeartBeat() async {
|
||||
final result = await methodChannel.invokeMethod<int>('terminalHeartBeat');
|
||||
return result ?? -1;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<int>> getLastRecvData() async {
|
||||
final result = await methodChannel.invokeMethod<List<dynamic>>('getLastRecvData');
|
||||
return result?.cast<int>() ?? [];
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, String>> getTerminalFirmVersion() async {
|
||||
final result = await methodChannel.invokeMethod('getTerminalFirmVersion');
|
||||
if (result == null) return {'firmVersion': '', 'hardwareVersion': ''};
|
||||
// 安全地转换类型
|
||||
final Map<String, dynamic> data = Map<String, dynamic>.from(result as Map);
|
||||
return {
|
||||
'firmVersion': data['firmVersion']?.toString() ?? '',
|
||||
'hardwareVersion': data['hardwareVersion']?.toString() ?? '',
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> getTerminalSn() async {
|
||||
final result = await methodChannel.invokeMethod<String>('getTerminalSn');
|
||||
return result ?? '';
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> closeDevice() async {
|
||||
final result = await methodChannel.invokeMethod<int>('closeDevice');
|
||||
return result ?? -1;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> findCard() async {
|
||||
final result = await methodChannel.invokeMethod<int>('findCard');
|
||||
return result ?? -1;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> selectCard() async {
|
||||
final result = await methodChannel.invokeMethod<int>('selectCard');
|
||||
return result ?? -1;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>> idReadCard({int cardType = 0x00, int infoEncoding = 0x01, int timeOut = 30000}) async {
|
||||
final result = await methodChannel.invokeMethod('idReadCard', {
|
||||
'cardType': cardType,
|
||||
'infoEncoding': infoEncoding,
|
||||
'timeOut': timeOut,
|
||||
});
|
||||
if (result == null) {
|
||||
return {'result': -1};
|
||||
}
|
||||
// 安全地转换类型
|
||||
return Map<String, dynamic>.from(result as Map);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>> readCard({int cardType = 1, int infoEncoding = 0, int timeOut = 30000}) async {
|
||||
final result = await methodChannel.invokeMethod('readCard', {
|
||||
'cardType': cardType,
|
||||
'infoEncoding': infoEncoding,
|
||||
'timeOut': timeOut,
|
||||
});
|
||||
if (result == null) {
|
||||
return {'result': -1};
|
||||
}
|
||||
// 安全地转换类型
|
||||
return Map<String, dynamic>.from(result as Map);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<IdCardInfo> readCardInfo() async {
|
||||
// 使用idReadCard方法读取身份证信息
|
||||
final result = await idReadCard();
|
||||
|
||||
if (result['result'] != 0) {
|
||||
throw Exception('Failed to read card info, error code: ${result['result']}');
|
||||
}
|
||||
|
||||
final String? dataStr = result['data'];
|
||||
if (dataStr == null || dataStr.isEmpty) {
|
||||
throw Exception('Card data is empty');
|
||||
}
|
||||
|
||||
// 根据文档,数据是冒号分隔的字符串格式
|
||||
return IdCardInfo.fromColonString(dataStr);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> readNewAddress() async {
|
||||
final result = await methodChannel.invokeMethod<String>('readNewAddress');
|
||||
return result ?? '';
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> getSamStatus() async {
|
||||
final result = await methodChannel.invokeMethod<int>('getSamStatus');
|
||||
return result ?? -1;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> getSamIdStr() async {
|
||||
final result = await methodChannel.invokeMethod<String>('getSamIdStr');
|
||||
return result ?? '';
|
||||
}
|
||||
}
|
||||
276
lib/idcard_platform_interface.dart
Normal file
276
lib/idcard_platform_interface.dart
Normal file
@@ -0,0 +1,276 @@
|
||||
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
|
||||
import 'idcard_method_channel.dart';
|
||||
|
||||
/// 身份证信息数据模型
|
||||
class IdCardInfo {
|
||||
final String cardType; // 证件类型 (A-二代证, I/Y-外国人, J-港澳台)
|
||||
final String name; // 中文姓名
|
||||
final String nameEn; // 英文姓名
|
||||
final String gender; // 性别
|
||||
final String genderId; // 性别代码
|
||||
final String nation; // 民族/国籍
|
||||
final String nationId; // 民族代码/国籍代码/通行证号码
|
||||
final String birthDate; // 出生日期
|
||||
final String address; // 住址
|
||||
final String idNumber; // 身份证号码/永久居留证号码
|
||||
final String signOrgan; // 签发机关
|
||||
final String beginTerm; // 有效期起始日期
|
||||
final String validTerm; // 有效期截止日期
|
||||
final String version; // 证件版本号
|
||||
final String? photoBase64; // 头像JPG照片base64编码
|
||||
final String? fingerprintBase64; // 指纹特征值base64编码
|
||||
final List<int>? photo; // 照片数据(原始格式)
|
||||
|
||||
IdCardInfo({
|
||||
required this.cardType,
|
||||
required this.name,
|
||||
this.nameEn = '',
|
||||
required this.gender,
|
||||
this.genderId = '',
|
||||
required this.nation,
|
||||
this.nationId = '',
|
||||
required this.birthDate,
|
||||
required this.address,
|
||||
required this.idNumber,
|
||||
required this.signOrgan,
|
||||
this.beginTerm = '',
|
||||
required this.validTerm,
|
||||
this.version = '',
|
||||
this.photoBase64,
|
||||
this.fingerprintBase64,
|
||||
this.photo,
|
||||
});
|
||||
|
||||
/// 从Map创建IdCardInfo对象
|
||||
factory IdCardInfo.fromMap(Map<String, dynamic> map) {
|
||||
return IdCardInfo(
|
||||
cardType: map['cardType'] ?? '',
|
||||
name: map['name'] ?? '',
|
||||
nameEn: map['nameEn'] ?? '',
|
||||
gender: map['gender'] ?? '',
|
||||
genderId: map['genderId'] ?? '',
|
||||
nation: map['nation'] ?? '',
|
||||
nationId: map['nationId'] ?? '',
|
||||
birthDate: map['birthDate'] ?? '',
|
||||
address: map['address'] ?? '',
|
||||
idNumber: map['idNumber'] ?? '',
|
||||
signOrgan: map['signOrgan'] ?? '',
|
||||
beginTerm: map['beginTerm'] ?? '',
|
||||
validTerm: map['validTerm'] ?? '',
|
||||
version: map['version'] ?? '',
|
||||
photoBase64: map['photoBase64'],
|
||||
fingerprintBase64: map['fingerprintBase64'],
|
||||
photo: map['photo'] != null ? List<int>.from(map['photo']) : null,
|
||||
);
|
||||
}
|
||||
|
||||
/// 从冒号分隔的字符串创建IdCardInfo对象
|
||||
/// 根据接口文档格式:证件类型:中文姓名:英文姓名:性别:性别代码:民族:民族代码:出生日期:住址:身份证号码:签发机关:发卡日期:卡有效期:证件版本号:头像JPG照片base64编码:指纹特征值base64编码
|
||||
factory IdCardInfo.fromColonString(String colonString) {
|
||||
final parts = colonString.split(':');
|
||||
// 确保至少有16个部分
|
||||
while (parts.length < 16) {
|
||||
parts.add('');
|
||||
}
|
||||
|
||||
return IdCardInfo(
|
||||
cardType: parts[0],
|
||||
name: parts[1],
|
||||
nameEn: parts[2],
|
||||
gender: parts[3],
|
||||
genderId: parts[4],
|
||||
nation: parts[5],
|
||||
nationId: parts[6],
|
||||
birthDate: parts[7],
|
||||
address: parts[8],
|
||||
idNumber: parts[9],
|
||||
signOrgan: parts[10],
|
||||
beginTerm: parts[11],
|
||||
validTerm: parts[12],
|
||||
version: parts[13],
|
||||
photoBase64: parts[14].isNotEmpty ? parts[14] : null,
|
||||
fingerprintBase64: parts[15].isNotEmpty ? parts[15] : null,
|
||||
);
|
||||
}
|
||||
|
||||
/// 转换为Map
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'cardType': cardType,
|
||||
'name': name,
|
||||
'nameEn': nameEn,
|
||||
'gender': gender,
|
||||
'genderId': genderId,
|
||||
'nation': nation,
|
||||
'nationId': nationId,
|
||||
'birthDate': birthDate,
|
||||
'address': address,
|
||||
'idNumber': idNumber,
|
||||
'signOrgan': signOrgan,
|
||||
'beginTerm': beginTerm,
|
||||
'validTerm': validTerm,
|
||||
'version': version,
|
||||
'photoBase64': photoBase64,
|
||||
'fingerprintBase64': fingerprintBase64,
|
||||
'photo': photo,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'IdCardInfo{cardType: $cardType, name: $name, nameEn: $nameEn, gender: $gender, genderId: $genderId, nation: $nation, nationId: $nationId, birthDate: $birthDate, address: $address, idNumber: $idNumber, signOrgan: $signOrgan, beginTerm: $beginTerm, validTerm: $validTerm, version: $version}';
|
||||
}
|
||||
}
|
||||
|
||||
/// 身份证读卡器平台接口
|
||||
abstract class IdcardPlatform extends PlatformInterface {
|
||||
/// 构造函数
|
||||
IdcardPlatform() : super(token: _token);
|
||||
|
||||
static final Object _token = Object();
|
||||
|
||||
static IdcardPlatform _instance = MethodChannelIdcard();
|
||||
|
||||
/// 默认实例
|
||||
static IdcardPlatform get instance => _instance;
|
||||
|
||||
/// 设置平台特定的实现
|
||||
static set instance(IdcardPlatform instance) {
|
||||
PlatformInterface.verifyToken(instance, _token);
|
||||
_instance = instance;
|
||||
}
|
||||
|
||||
/// 获取平台版本
|
||||
Future<String?> getPlatformVersion() {
|
||||
throw UnimplementedError('platformVersion() has not been implemented.');
|
||||
}
|
||||
|
||||
/// 获取USB权限
|
||||
/// [vid] - USB设备的厂商ID
|
||||
/// [pid] - USB设备的产品ID
|
||||
Future<int> getUsbPermission(int vid, int pid) {
|
||||
throw UnimplementedError('getUsbPermission() has not been implemented.');
|
||||
}
|
||||
|
||||
/// 打开设备
|
||||
/// [portType] - 端口类型,如"USB", "COM", "SKT", "BTH", "AUTO"
|
||||
/// [portPara] - 端口参数
|
||||
/// [extendPara] - 扩展参数
|
||||
/// 返回值:大于0表示成功(设备句柄),其他为失败
|
||||
Future<int> openDevice({String portType = 'USB', String portPara = '', String extendPara = ''}) {
|
||||
throw UnimplementedError('openDevice() has not been implemented.');
|
||||
}
|
||||
|
||||
/// 设置当前设备(多设备操作)
|
||||
/// [devHandle] - 设备句柄
|
||||
/// 返回值:0表示成功;非0表示失败
|
||||
Future<int> setCurrentDevice(int devHandle) {
|
||||
throw UnimplementedError('setCurrentDevice() has not been implemented.');
|
||||
}
|
||||
|
||||
/// 获取当前设备(多设备操作)
|
||||
/// 返回值:返回当前设备的句柄
|
||||
Future<int> getCurrentDevice() {
|
||||
throw UnimplementedError('getCurrentDevice() has not been implemented.');
|
||||
}
|
||||
|
||||
/// 获取接口库信息
|
||||
/// 返回值:包含version和description的Map
|
||||
Future<Map<String, String>> getLibraryInfo() {
|
||||
throw UnimplementedError('getLibraryInfo() has not been implemented.');
|
||||
}
|
||||
|
||||
/// 获取设备型号
|
||||
/// 返回值:设备型号字符串
|
||||
Future<String> getTerminalModel() {
|
||||
throw UnimplementedError('getTerminalModel() has not been implemented.');
|
||||
}
|
||||
|
||||
/// 设备轮询心跳
|
||||
/// 返回值:0表示成功;非0表示失败
|
||||
Future<int> terminalHeartBeat() {
|
||||
throw UnimplementedError('terminalHeartBeat() has not been implemented.');
|
||||
}
|
||||
|
||||
/// 获取接收数据
|
||||
/// 返回值:最后一次通讯收到的数据
|
||||
Future<List<int>> getLastRecvData() {
|
||||
throw UnimplementedError('getLastRecvData() has not been implemented.');
|
||||
}
|
||||
|
||||
/// 获取设备固件版本
|
||||
/// 返回值:包含firmVersion和hardwareVersion的Map
|
||||
Future<Map<String, String>> getTerminalFirmVersion() {
|
||||
throw UnimplementedError('getTerminalFirmVersion() has not been implemented.');
|
||||
}
|
||||
|
||||
/// 获取设备序列号
|
||||
/// 返回值:设备序列号字符串
|
||||
Future<String> getTerminalSn() {
|
||||
throw UnimplementedError('getTerminalSn() has not been implemented.');
|
||||
}
|
||||
|
||||
/// 关闭设备
|
||||
/// 返回值:0表示成功;非0表示失败
|
||||
Future<int> closeDevice() {
|
||||
throw UnimplementedError('closeDevice() has not been implemented.');
|
||||
}
|
||||
|
||||
/// 寻卡
|
||||
Future<int> findCard() {
|
||||
throw UnimplementedError('findCard() has not been implemented.');
|
||||
}
|
||||
|
||||
/// 选卡
|
||||
Future<int> selectCard() {
|
||||
throw UnimplementedError('selectCard() has not been implemented.');
|
||||
}
|
||||
|
||||
/// 读取身份证(新版接口,支持多种证件类型)
|
||||
/// [cardType] - 读取卡类型:
|
||||
/// 0x00:读取二代证或外国人或港澳台
|
||||
/// 0x01:只读二代证
|
||||
/// 0x02:只读外国人
|
||||
/// 0x03:只读港澳台
|
||||
/// 0x10-0x13:对应上述类型但包含指纹信息
|
||||
/// [infoEncoding] - 返回信息的编码方式:
|
||||
/// 0x01:GB18030编码(GBK)
|
||||
/// 0x02:UTF16-LE编码
|
||||
/// 0x03:UTF-8编码
|
||||
/// [timeOut] - 读卡超时时间(毫秒),0表示不等待,>0表示等待放卡
|
||||
/// 返回值:0表示成功;非0表示失败,成功时返回冒号分隔的身份证信息字符串
|
||||
Future<Map<String, dynamic>> idReadCard({int cardType = 0x00, int infoEncoding = 0x01, int timeOut = 30000}) {
|
||||
throw UnimplementedError('idReadCard() has not been implemented.');
|
||||
}
|
||||
|
||||
/// 读取身份证(兼容旧版接口)
|
||||
/// [cardType] - 卡片类型
|
||||
/// [infoEncoding] - 信息编码
|
||||
/// [timeOut] - 超时时间(毫秒)
|
||||
Future<Map<String, dynamic>> readCard({int cardType = 1, int infoEncoding = 0, int timeOut = 30000}) {
|
||||
throw UnimplementedError('readCard() has not been implemented.');
|
||||
}
|
||||
|
||||
/// 读取身份证详细信息
|
||||
Future<IdCardInfo> readCardInfo() {
|
||||
throw UnimplementedError('readCardInfo() has not been implemented.');
|
||||
}
|
||||
|
||||
/// 读取追加住址
|
||||
/// 返回值:追加住址信息字符串
|
||||
Future<String> readNewAddress() {
|
||||
throw UnimplementedError('readNewAddress() has not been implemented.');
|
||||
}
|
||||
|
||||
/// 获取SAM模块状态
|
||||
/// 返回值:0表示成功;非0表示失败
|
||||
Future<int> getSamStatus() {
|
||||
throw UnimplementedError('getSamStatus() has not been implemented.');
|
||||
}
|
||||
|
||||
/// 获取SAM模块编号字符串
|
||||
/// 返回值:SAM模块编号字符串
|
||||
Future<String> getSamIdStr() {
|
||||
throw UnimplementedError('getSamIdStr() has not been implemented.');
|
||||
}
|
||||
}
|
||||
72
pubspec.yaml
Normal file
72
pubspec.yaml
Normal file
@@ -0,0 +1,72 @@
|
||||
name: idcard
|
||||
description: "一个用于读取中国二代身份证的 Flutter 插件,集成了01SDK身份证识别功能,支持USB和蓝牙连接方式。"
|
||||
version: 0.0.1
|
||||
homepage: https://github.com/your-username/idcard
|
||||
repository: https://github.com/your-username/idcard
|
||||
issue_tracker: https://github.com/your-username/idcard/issues
|
||||
|
||||
environment:
|
||||
sdk: '>=3.4.4 <4.0.0'
|
||||
flutter: '>=3.3.0'
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
plugin_platform_interface: ^2.0.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^3.0.0
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
# The following section is specific to Flutter packages.
|
||||
flutter:
|
||||
# This section identifies this Flutter project as a plugin project.
|
||||
# The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.)
|
||||
# which should be registered in the plugin registry. This is required for
|
||||
# using method channels.
|
||||
# The Android 'package' specifies package in which the registered class is.
|
||||
# This is required for using method channels on Android.
|
||||
# The 'ffiPlugin' specifies that native code should be built and bundled.
|
||||
# This is required for using `dart:ffi`.
|
||||
# All these are used by the tooling to maintain consistency when
|
||||
# adding or updating assets for this project.
|
||||
plugin:
|
||||
platforms:
|
||||
android:
|
||||
package: com.example.idcard
|
||||
pluginClass: IdcardPlugin
|
||||
|
||||
# To add assets to your plugin package, add an assets section, like this:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
#
|
||||
# For details regarding assets in packages, see
|
||||
# https://flutter.dev/assets-and-images/#from-packages
|
||||
#
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
||||
|
||||
# To add custom fonts to your plugin package, add a fonts section here,
|
||||
# in this "flutter" section. Each entry in this list should have a
|
||||
# "family" key with the font family name, and a "fonts" key with a
|
||||
# list giving the asset and other descriptors for the font. For
|
||||
# example:
|
||||
# fonts:
|
||||
# - family: Schyler
|
||||
# fonts:
|
||||
# - asset: fonts/Schyler-Regular.ttf
|
||||
# - asset: fonts/Schyler-Italic.ttf
|
||||
# style: italic
|
||||
# - family: Trajan Pro
|
||||
# fonts:
|
||||
# - asset: fonts/TrajanPro.ttf
|
||||
# - asset: fonts/TrajanPro_Bold.ttf
|
||||
# weight: 700
|
||||
#
|
||||
# For details regarding fonts in packages, see
|
||||
# https://flutter.dev/custom-fonts/#from-packages
|
||||
27
test/idcard_method_channel_test.dart
Normal file
27
test/idcard_method_channel_test.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:idcard/idcard_method_channel.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
MethodChannelIdcard platform = MethodChannelIdcard();
|
||||
const MethodChannel channel = MethodChannel('idcard');
|
||||
|
||||
setUp(() {
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
channel,
|
||||
(MethodCall methodCall) async {
|
||||
return '42';
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, null);
|
||||
});
|
||||
|
||||
test('getPlatformVersion', () async {
|
||||
expect(await platform.getPlatformVersion(), '42');
|
||||
});
|
||||
}
|
||||
29
test/idcard_test.dart
Normal file
29
test/idcard_test.dart
Normal file
@@ -0,0 +1,29 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:idcard/idcard.dart';
|
||||
import 'package:idcard/idcard_platform_interface.dart';
|
||||
import 'package:idcard/idcard_method_channel.dart';
|
||||
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
|
||||
|
||||
class MockIdcardPlatform
|
||||
with MockPlatformInterfaceMixin
|
||||
implements IdcardPlatform {
|
||||
|
||||
@override
|
||||
Future<String?> getPlatformVersion() => Future.value('42');
|
||||
}
|
||||
|
||||
void main() {
|
||||
final IdcardPlatform initialPlatform = IdcardPlatform.instance;
|
||||
|
||||
test('$MethodChannelIdcard is the default instance', () {
|
||||
expect(initialPlatform, isInstanceOf<MethodChannelIdcard>());
|
||||
});
|
||||
|
||||
test('getPlatformVersion', () async {
|
||||
Idcard idcardPlugin = Idcard();
|
||||
MockIdcardPlatform fakePlatform = MockIdcardPlatform();
|
||||
IdcardPlatform.instance = fakePlatform;
|
||||
|
||||
expect(await idcardPlugin.getPlatformVersion(), '42');
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user