()
+ for (i in hex.indices step 2) {
+ result.add(hex.substring(i, i + 2))
+ }
+ return result
+ }
+
+ private fun buildApdu(): String {
+ val commandByteArray = splitHex(command).map { it.toLong(16) }.toLongArray()
+ return GPCAPDUGenerator.buildGPCAPDU(
+ APDUParam(commandByteArray, data),
+ useSafeChannel || (useSafeChannelWhenOpen && hasOpenSafeChannel)
+ )
+ }
+
+ private fun parseResponse(response: String): String {
+ return if (useSafeChannel || (useSafeChannelWhenOpen && hasOpenSafeChannel)) {
+ nativeGPCParseSafeAPDUResponse(response)
+ } else {
+ nativeGPCParseAPDUResponse(response)
+ }
+ }
+
+ fun send(connection: Connection): SendResponse {
+ beforeConnecting(connection)
+ // buildApdu && parseResponse Use in pairs
+ val apdu = buildApdu()
+ val result = Connection.send(connection.isoDep, apdu)
+ val resultObject = CardResponse.objectFromData(parseResponse(result.rawResponse))
+ result.result = resultObject?.response ?: ""
+ return result
+ }
+}
+
+class CommandGenerator {
+ companion object {
+ private fun readConfig(): JsonObject {
+ val context = Utils.getApp()
+ val config = context.assets.open("config/command.json")
+ config.use {
+ val json = it.bufferedReader().use { it.readText() }
+ return JsonParser.parseString(json).asJsonObject
+ }
+ }
+
+ val instanceConfig by lazy(LazyThreadSafetyMode.NONE) {
+ readConfig()
+ }
+ }
+
+ fun generalCommand(
+ cardType: AppleCardType,
+ type: CommandType,
+ hasOpenSafeChannel: Boolean,
+ data: String? = null
+ ): Command {
+
+ val config = when (cardType) {
+ AppleCardType.V1 -> instanceConfig.getAsJsonObject(type.code).getAsJsonObject("v1")
+ AppleCardType.V2 -> instanceConfig.getAsJsonObject(type.code).getAsJsonObject("v2")
+ }
+
+ val area = if (config.has("area")) {
+ CommandArea.parse(config["area"].asString)
+ } else {
+ CommandArea.None
+ }
+ val ignoreSafeChannel =
+ config.has("ignoreSafeChannel") && config["ignoreSafeChannel"].asBoolean
+ val useSafeChannel = config.has("useSafeChannel") && config["useSafeChannel"].asBoolean
+ val useSafeChannelWhenOpen =
+ config.has("useSafeChannelWhenOpen") && config["useSafeChannelWhenOpen"].asBoolean
+ val needData = config.has("needData") && config["needData"].asBoolean
+ val command = if (config.has("command")) {
+ config["command"].asString
+ } else {
+ throw Exception("command not found")
+ }
+
+ if (needData && data == null) throw Exception("data is null")
+
+ return Command(
+ cardType = cardType,
+ area = area,
+ command = command,
+ ignoreSafeChannel = ignoreSafeChannel,
+ useSafeChannel = useSafeChannel,
+ useSafeChannelWhenOpen = useSafeChannelWhenOpen,
+ hasOpenSafeChannel = hasOpenSafeChannel,
+ data = data ?: ""
+ )
+ }
+
+}
diff --git a/native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/onekeyLite/nfc/Connection.kt b/native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/onekeyLite/nfc/Connection.kt
new file mode 100644
index 00000000..409125b1
--- /dev/null
+++ b/native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/onekeyLite/nfc/Connection.kt
@@ -0,0 +1,439 @@
+package so.onekey.app.wallet.lite.onekeyLite.nfc
+
+import android.nfc.TagLostException
+import android.nfc.tech.IsoDep
+import org.haobtc.onekey.card.gpchannel.GPChannelNatives
+import org.haobtc.onekey.card.gpchannel.GPChannelNatives.nativeGPCFinalize
+import so.onekey.app.wallet.lite.keys.KeysNativeProvider
+import so.onekey.app.wallet.lite.nfc.NFCExceptions
+import so.onekey.app.wallet.lite.onekeyLite.NfcConstant
+import so.onekey.app.wallet.lite.onekeyLite.entitys.*
+import so.onekey.app.wallet.lite.onekeyLite.nfc.GPCAPDUGenerator.buildGPCAPDU
+import so.onekey.app.wallet.lite.onekeyLite.nfc.GPCAPDUGenerator.combCommand
+import so.onekey.app.wallet.lite.utils.HexUtils
+import so.onekey.app.wallet.lite.utils.HexUtils.byteArr2HexStr
+import so.onekey.app.wallet.lite.utils.HexUtils.hexString2Bytes
+import so.onekey.app.wallet.lite.utils.LogUtil.printLog
+import so.onekey.app.wallet.lite.utils.Utils
+import java.io.IOException
+
+class Connection(val isoDep: IsoDep, private val mCommandGenerator: CommandGenerator) {
+ companion object {
+ private const val TAG = "Connection"
+
+ @JvmStatic
+ private fun connect(isoDep: IsoDep?) {
+ val isConnected: Boolean = try {
+ isoDep?.isConnected ?: false
+ } catch (e: Exception) {
+ false
+ }
+ if (isConnected == false) {
+ isoDep?.connect()
+ isoDep?.timeout = 15000
+ }
+ }
+
+ @JvmStatic
+ fun send(isoDep: IsoDep, request: String?): SendResponse {
+ try {
+ connect(isoDep)
+ val response = isoDep.transceive(hexString2Bytes(request)) ?: byteArrayOf(
+ 0xFF.toByte(), 0xFF.toByte()
+ )
+ val resp: ByteArray = if (response.size > 2) {
+ response.copyOfRange(0, response.size - 2)
+ } else byteArrayOf()
+ val sw1 = response[response.size - 2]
+ val sw2 = response[response.size - 1]
+
+ return SendResponse(byteArr2HexStr(response), sw1, sw2, byteArr2HexStr(resp))
+ } catch (e: TagLostException) {
+ // throw NFCExceptions.InterruptException()
+ return SendResponse("0xFFFF", 0xFF.toByte(), 0xFF.toByte())
+ } catch (e: IOException) {
+ // e.printStackTrace()
+ return SendResponse("0xFFFF", 0xFF.toByte(), 0xFF.toByte())
+ }
+ }
+ }
+
+ private var mCardType: AppleCardType = AppleCardType.V2
+ private var mCardSerialNumber: String = NfcConstant.NOT_MATCH_DEVICE
+ private var hasOpenSafeChannel: Boolean = false
+ private var hasSetupPin: Boolean = false
+ private var hasBackup: Boolean = false
+ private var mCommandArea = CommandArea.None
+
+ init {
+ readCardInfo()
+ }
+
+ fun getCardType(): AppleCardType {
+ return mCardType
+ }
+
+ fun getCurrentCommandArea(): CommandArea {
+ return mCommandArea
+ }
+
+ fun selectPrimarySafety(): Boolean {
+ printLog(TAG, "---> selectPrimarySafety begin")
+ val command = mCommandGenerator.generalCommand(
+ mCardType, CommandType.SELECT_PRIMARY_SAFETY, hasOpenSafeChannel,
+ )
+ val res = command.send(this)
+ printLog(
+ TAG, "<--- selectPrimarySafety end: ${res.getCode()} ${res.data} area:${mCommandArea}"
+ )
+
+ if (res.isSuccess() && !res.isEmptyData()) {
+ mCommandArea = CommandArea.PrimarySafety
+ return true
+ }
+ return false
+ }
+
+ fun selectBackupApplet(cardType: AppleCardType?): Boolean {
+ printLog(TAG, "---> selectApplet begin")
+ val command = mCommandGenerator.generalCommand(
+ cardType ?: mCardType,
+ CommandType.SELECT_BACKUP_APPLET,
+ hasOpenSafeChannel,
+ cardType?.aid ?: mCardType.aid
+ )
+ val res = command.send(this)
+
+ printLog(TAG, "<--- selectApplet end: ${res.getCode()} ${res.data} area:${mCommandArea}")
+
+ if (res.isSuccess()) {
+ mCommandArea = CommandArea.BackupApplet
+ return true
+ }
+ return false
+ }
+
+ private fun getDeviceCertificate(): ParsedCertInfo? {
+ printLog(TAG, "---> getDeviceCertificate begin")
+ // #1.NFC:GET CERT.SD.ECKA 获取智能卡证书
+ val apdu = buildGPCAPDU(APDUParam(0x80, 0xCA, 0xBF, 0x21, "A60483021518"))
+ val rawCert = send(isoDep, apdu)
+ printLog(
+ TAG,
+ "<--- getDeviceCertificate end: ${rawCert.getCode()} ${rawCert.data} area:${mCommandArea}"
+ )
+
+ val cert = GPChannelNatives.nativeGPCTLVDecode(rawCert.data)
+ val certificate = CardResponse.objectFromData(cert).response
+
+ val certInfo = GPChannelNatives.nativeGPCParseCertificate(certificate)
+ return ParsedCertInfo.objectFromData(certInfo)
+ }
+
+ fun openSafeChannel(): Boolean {
+ printLog(TAG, "---> openSafeChannel begin")
+
+ if (hasOpenSafeChannel) {
+ printLog(TAG, "<--- has open safe channel")
+ return true
+ }
+
+ printLog(TAG, "0. ---> getDeviceCertificate begin")
+ val certInfo = getDeviceCertificate() ?: return false
+ printLog(TAG, "0. <--- getDeviceCertificate end: ${certInfo.subjectID}")
+
+ printLog(TAG, "1. ---> nativeGPCInitialize begin")
+ val param = SecureChanelParam.objectFromData(
+ KeysNativeProvider().getLiteSecureChannelInitParams(Utils.getApp())
+ )
+ param.cardGroupID = certInfo.subjectID
+ printLog(TAG, "nativeGPCInitialize read param done")
+ val initializeStatus = GPChannelNatives.nativeGPCInitialize(param.toString())
+ if (initializeStatus != 0) {
+ return false
+ }
+ printLog(TAG, "1. <--- nativeGPCInitialize end")
+
+ printLog(TAG, "2. ---> Perform security operation begin")
+ val command = mCommandGenerator.generalCommand(
+ mCardType, CommandType.VERIFY_CERTIFICATE, hasOpenSafeChannel, param.crt
+ )
+ val securityRes = command.send(this)
+ printLog(
+ TAG,
+ "2. <--- Perform security operation end: ${securityRes.getCode()} ${securityRes.data}"
+ )
+ if (!securityRes.isSuccess()) {
+ return false
+ }
+
+ printLog(TAG, "3. ---> mutual authenticate begin")
+ val authData = GPChannelNatives.nativeGPCBuildMutualAuthData()
+ printLog(TAG, "mutual authenticate data")
+ val authCommand = mCommandGenerator.generalCommand(
+ mCardType, CommandType.VERIFY_AUTH_DATA, hasOpenSafeChannel, authData
+ )
+ val authRes = authCommand.send(this)
+ printLog(TAG, "3. <--- mutual authenticate end")
+ if (authRes.isEmptyData() || !authRes.isSuccess()) {
+ return false
+ }
+
+ printLog(TAG, "4. ---> open secure channel begin")
+ val openStatus = GPChannelNatives.nativeGPCOpenSecureChannel(authRes.result)
+ if (openStatus != 0) {
+ return false
+ }
+ printLog(TAG, "4. <--- open secure channel end")
+
+ printLog(TAG, "<--- openSafeChannel end: Open Secure Channel OK")
+
+ hasOpenSafeChannel = true
+ return true
+ }
+
+ fun resetSecureChannel(): Boolean {
+ printLog(TAG, "---> resetSecureChannel begin")
+ nativeGPCFinalize()
+ hasOpenSafeChannel = false
+ printLog(TAG, "<--- resetSecureChannel end")
+ return true
+ }
+
+ private fun loadBackupStatus(): Boolean {
+ printLog(TAG, "---> getBackupStatus begin")
+ val command = mCommandGenerator.generalCommand(
+ mCardType, CommandType.GET_BACKUP_STATUS, hasOpenSafeChannel
+ )
+ val res = command.send(this)
+ printLog(TAG, "<--- getBackupStatus end: ${res.getCode()} ${res.data} area:${mCommandArea}")
+
+ if (!res.isSuccess()) {
+ return false
+ }
+
+ return (res.data == "02").also { hasBackup = it }
+ }
+
+ private fun loadPinStatus(): Boolean {
+ printLog(TAG, "---> getPinStatus begin")
+ val command = mCommandGenerator.generalCommand(
+ mCardType, CommandType.GET_PIN_STATUS, hasOpenSafeChannel, "DFFF028105"
+ )
+ val res = command.send(this)
+ printLog(TAG, "<--- getPinStatus end: ${res.getCode()} ${res.data} area:${mCommandArea}")
+
+ if (!res.isSuccess()) {
+ return false
+ }
+
+ val notSetPin = res.data == "02"
+ hasSetupPin = !notSetPin
+ return notSetPin
+ }
+
+ private fun loadSerialNumber(cardType: AppleCardType? = null): String {
+ printLog(TAG, "---> getSerialNumber begin")
+ val command = mCommandGenerator.generalCommand(
+ cardType ?: mCardType, CommandType.GET_SERIAL_NUMBER, hasOpenSafeChannel, "DFFF028101"
+ )
+ val res = command.send(this)
+
+ printLog(TAG, "<--- getSerialNumber end: ${res.getCode()} ${res.data} area:${mCommandArea}")
+ if (!res.isSuccess()) {
+ return NfcConstant.NOT_MATCH_DEVICE
+ }
+
+ return String(hexString2Bytes(res.data)).also {
+ mCardSerialNumber = it
+ }
+ }
+
+ private fun getRetryCount(): Int {
+ printLog(TAG, "---> getRetryNum begin")
+ val command = mCommandGenerator.generalCommand(
+ mCardType, CommandType.GET_PIN_RETRY_COUNT, hasOpenSafeChannel, "DFFF028102"
+ )
+ val res = command.send(this)
+
+ printLog(TAG, "<--- getRetryNum end: ${res.getCode()} ${res.data} area:${mCommandArea}")
+
+ if (res.isEmptyData() || !res.isSuccess()) {
+ return NfcConstant.GET_RETRY_NUM_INTERRUPT_STATUS
+ }
+ return res.data?.toInt(16) ?: NfcConstant.GET_RETRY_NUM_INTERRUPT_STATUS
+ }
+
+ private fun readCardInfo() {
+ printLog(TAG, "---> initCard begin")
+ try {
+ val cardTypes = arrayOf(AppleCardType.V1, AppleCardType.V2)
+ for (cardType in cardTypes) {
+ printLog(TAG, "---->> selectApplet: $cardType")
+ val serialNumber = loadSerialNumber(cardType)
+ if (serialNumber.startsWith(cardType.prefixSN)) {
+ mCardType = cardType
+ break
+ }
+ }
+ } catch (e: NFCExceptions) {
+ printLog(TAG, " init_channel NFCExceptions-->$e")
+ }
+
+ loadPinStatus()
+ if (mCardType == AppleCardType.V2) {
+ loadBackupStatus()
+ } else {
+ hasBackup = hasSetupPin
+ }
+ printLog(TAG, "<--- initCard end")
+ }
+
+ fun getCardInfo(): CardState {
+ val retryCount = getRetryCount()
+ return CardState(hasBackup, !hasSetupPin, mCardSerialNumber, retryCount)
+ }
+
+ fun getSerialNumber() = mCardSerialNumber
+
+ fun setupNewPin(pin: String): Boolean {
+ printLog(TAG, "---> setupNewPin begin")
+ val pinHex = HexUtils.stringToHexString(pin)
+ val command = mCommandGenerator.generalCommand(
+ mCardType, CommandType.SETUP_NEW_PIN, hasOpenSafeChannel, "DFFE0B8204080006$pinHex"
+ )
+ val res = command.send(this)
+ printLog(TAG, "<--- setupNewPin end: ${res.getCode()} ${res.data} area:${mCommandArea}")
+
+ if (!res.isSuccess()) {
+ return false
+ }
+
+ return true
+ }
+
+ fun changePin(oldPin: String, newPin: String?): Int {
+ printLog(TAG, "---> changePin begin")
+ val oldPinHex = HexUtils.stringToHexString(oldPin)
+ val newPinHex = HexUtils.stringToHexString(newPin)
+ val changePinCommand = combCommand(
+ hexString2Bytes("8204"), hexString2Bytes(oldPinHex), hexString2Bytes(newPinHex)
+ )
+ val execCommand = combCommand(hexString2Bytes("DFFE"), changePinCommand)
+
+ val command = mCommandGenerator.generalCommand(
+ mCardType, CommandType.CHANGE_PIN, hasOpenSafeChannel, byteArr2HexStr(execCommand)
+ )
+ val res = command.send(this)
+
+ printLog(TAG, "<--- changePin end: ${res.getCode()} ${res.data} area:${mCommandArea}")
+
+ return if (res.isConnectFailure()) {
+ NfcConstant.INTERRUPT_STATUS
+ } else if (res.isSuccess()) {
+ printLog(TAG, "--- verify success")
+ NfcConstant.CHANGE_PIN_SUCCESS
+ } else if (res.getCode() == "9B01") {
+ // V1 Lite Pin error
+ printLog(TAG, "--- verify error")
+ retryNumCommandAndReset()
+ } else if (res.getCode() == "6983") {
+ // V2 Lite Locked
+ printLog(TAG, "--- verify Too many errors, Locked")
+ resetCard()
+ } else {
+ if (res.getCode().startsWith("63C")) {
+ return res.sw2.toInt() and 0x0F
+ }
+ retryNumCommandAndReset()
+ }
+ }
+
+ fun startVerifyPin(pin: String): Int {
+ printLog(TAG, "---> startVerifyPin begin")
+ val pinHex = HexUtils.stringToHexString(pin)
+ val command = mCommandGenerator.generalCommand(
+ mCardType, CommandType.VERIFY_PIN, hasOpenSafeChannel, "06$pinHex"
+ )
+ val res = command.send(this)
+ printLog(TAG, "<--- startVerifyPin end: ${res.getCode()} ${res.data} area:${mCommandArea}")
+
+ return if (res.isConnectFailure()) {
+ NfcConstant.INTERRUPT_STATUS
+ } else if (res.isSuccess()) {
+ printLog(TAG, "--- verify success")
+ NfcConstant.VERIFY_SUCCESS
+ } else if (res.getCode() == "9B01") {
+ // V1 Lite Pin error
+ printLog(TAG, "--- verify error")
+ retryNumCommandAndReset()
+ } else if (res.getCode() == "6983") {
+ // V2 Lite Locked
+ printLog(TAG, "--- verify Too many errors, Locked")
+ resetCard()
+ } else {
+ if (res.getCode().startsWith("63C")) {
+ return res.sw2.toInt() and 0x0F
+ }
+ retryNumCommandAndReset()
+ }
+ }
+
+ fun backupData(mnemonic: String): Boolean {
+ printLog(TAG, "---> backupData begin")
+ val command = mCommandGenerator.generalCommand(
+ mCardType, CommandType.BACKUP_DATA, hasOpenSafeChannel, mnemonic
+ )
+ val res = command.send(this)
+ printLog(TAG, "<--- backupData end: ${res.getCode()} ${res.data} area:${mCommandArea}")
+
+ if (!res.isSuccess()) {
+ return false
+ }
+
+ return true
+ }
+
+ fun exportData(): String? {
+ printLog(TAG, "---> exportData begin")
+ val command = mCommandGenerator.generalCommand(
+ mCardType, CommandType.EXPORT_DATA, hasOpenSafeChannel
+ )
+ val res = command.send(this)
+ printLog(
+ TAG,
+ "<--- exportData end: ${res.getCode()} emptyData:${res.isEmptyData()} area:${mCommandArea}"
+ )
+
+ if (res.isEmptyData() || !res.isSuccess()) {
+ return null
+ }
+
+ return res.result
+ }
+
+ private fun retryNumCommandAndReset(): Int {
+ val leftRetryNum = getRetryCount()
+ return if (leftRetryNum == 0) {
+ resetCard()
+ } else {
+ leftRetryNum
+ }
+ }
+
+ fun resetCard(): Int {
+ printLog(TAG, "---> resetCard begin")
+ val command = mCommandGenerator.generalCommand(
+ mCardType, CommandType.RESET_CARD, hasOpenSafeChannel, "DFFE028205"
+ )
+ val res = command.send(this)
+
+ printLog(TAG, "<--- resetCard end: ${res.getCode()} ${res.data} area:${mCommandArea}")
+
+ return if (res.isConnectFailure()) {
+ NfcConstant.RESET_INTERRUPT_STATUS
+ } else {
+ NfcConstant.RESET_PIN_SUCCESS
+ }
+ }
+}
diff --git a/native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/onekeyLite/nfc/GPCAPDUGenerator.kt b/native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/onekeyLite/nfc/GPCAPDUGenerator.kt
new file mode 100644
index 00000000..0e4de83d
--- /dev/null
+++ b/native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/onekeyLite/nfc/GPCAPDUGenerator.kt
@@ -0,0 +1,56 @@
+package so.onekey.app.wallet.lite.onekeyLite.nfc
+
+import com.google.gson.Gson
+import org.haobtc.onekey.card.gpchannel.GPChannelNatives
+import so.onekey.app.wallet.lite.onekeyLite.entitys.APDUParam
+import so.onekey.app.wallet.lite.utils.LogUtil.printLog
+
+object GPCAPDUGenerator {
+ private const val TAG = "GPCAPDUGenerator"
+
+ @JvmStatic
+ fun buildGPCAPDU(param: APDUParam, safeChannel: Boolean = false): String {
+ printLog(TAG, " --->> BuildGPCAPDU begin")
+ return if (safeChannel) {
+ GPChannelNatives.nativeGPCBuildSafeAPDU(
+ param.cla,
+ param.ins,
+ param.p1,
+ param.p2,
+ param.data
+ )
+ } else {
+ GPChannelNatives.nativeGPCBuildAPDU(
+ param.cla,
+ param.ins,
+ param.p1,
+ param.p2,
+ param.data
+ )
+ }.also {
+ printLog(TAG, " <<--- BuildGPCAPDU done: safeChannel:$safeChannel")
+ }
+ }
+
+ @JvmStatic
+ fun combCommand(command: ByteArray? = null, vararg params: ByteArray?): ByteArray {
+ var combParam = byteArrayOf()
+ params.forEach { param ->
+ param?.let {
+ combParam = combParam.plus(it.size.toByte())
+ combParam = combParam.plus(it)
+ }
+ }
+
+ var combCommand = byteArrayOf()
+ command?.let {
+ combCommand = combCommand.plus(it)
+ if (params.size != 1) {
+ // params 只有一个的时候就不再次拼接长度了
+ combCommand = combCommand.plus(combParam.size.toByte())
+ }
+ }
+ combCommand = combCommand.plus(combParam)
+ return combCommand
+ }
+}
\ No newline at end of file
diff --git a/native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/EventUtils.kt b/native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/EventUtils.kt
new file mode 100644
index 00000000..9fa97d9f
--- /dev/null
+++ b/native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/EventUtils.kt
@@ -0,0 +1,14 @@
+package so.onekey.app.wallet.lite.utils
+
+import com.facebook.react.bridge.ReactContext
+import com.facebook.react.modules.core.DeviceEventManagerModule
+
+fun sendEvent(
+ reactContext: ReactContext,
+ eventName: String,
+ params: String? = null
+) {
+ reactContext
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
+ .emit(eventName, params)
+}
\ No newline at end of file
diff --git a/native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/GpsUtil.kt b/native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/GpsUtil.kt
new file mode 100644
index 00000000..a79671a3
--- /dev/null
+++ b/native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/GpsUtil.kt
@@ -0,0 +1,38 @@
+package so.onekey.app.wallet.lite.utils
+
+import android.content.Context
+import android.content.Intent
+import android.provider.Settings
+
+
+object GpsUtil {
+ /**
+ * 判断GPS是否开启,GPS或者AGPS开启一个就认为是开启的
+ * @param context
+ * @return true 表示开启
+ */
+ fun isOpen(context: Context?): Boolean {
+ if (context == null) {
+ return true
+ }
+ var locationMode = 0
+ val locationProviders: String
+
+ locationMode = try {
+ Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.LOCATION_MODE)
+ } catch (e: Settings.SettingNotFoundException) {
+ e.printStackTrace()
+ return false
+ }
+ return locationMode != Settings.Secure.LOCATION_MODE_OFF
+ }
+
+ /**
+ * 跳转设置 打开GPS
+ * @param context
+ */
+ fun openGPS(context: Context) {
+ val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
+ context.startActivity(intent)
+ }
+}
\ No newline at end of file
diff --git a/native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/HexUtils.java b/native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/HexUtils.java
new file mode 100644
index 00000000..d10fddfe
--- /dev/null
+++ b/native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/HexUtils.java
@@ -0,0 +1,93 @@
+package so.onekey.app.wallet.lite.utils;
+
+import java.math.BigInteger;
+import java.util.Collections;
+
+/**
+ * @author liyan
+ */
+public class HexUtils {
+
+ public static byte[] hexString2Bytes(String hex) {
+ if (hex == null) return new byte[0];
+ return String2Bytes(hex, 16);
+ }
+
+ private static byte[] String2Bytes(String str, int digit) {
+ byte[] bArray = new BigInteger("10" + str, digit).toByteArray();
+
+ byte[] ret = new byte[bArray.length - 1];
+ for (int i = 0; i < ret.length; i++) {
+ ret[i] = bArray[i + 1];
+ }
+
+ return ret;
+ }
+
+
+ public static String stringToHexString(String s) {
+ if (s == null) return "";
+ String str = "";
+ for (int i = 0; i < s.length(); i++) {
+ int ch = s.charAt(i);
+ String s4 = Integer.toHexString(ch);
+ str = str + s4;
+ }
+ return str;
+ }
+
+ /**
+ * 将字节数组转换成十六进制的字符串
+ *
+ * @param bt 字节数组
+ * @param start 起始下标
+ * @param end 终止下标
+ */
+ public static String byteArr2HexStr(byte[] bt, int start, int end) {
+ return byteArr2HexStr(bt, start, end, "");
+ }
+
+ /**
+ * 将字节数组转换成十六进制的字符串
+ *
+ * @param bt 字节数组
+ */
+ public static String byteArr2HexStr(byte[] bt) {
+ if (bt == null) return "";
+ if (bt.length == 0) return "";
+ return byteArr2HexStr(bt, 0, bt.length, "");
+ }
+
+ /**
+ * 将字节数组转换成十六进制的字符串
+ *
+ * @param bt 字节数组
+ * @param start 起始下标
+ * @param end 终止下标
+ * @param sep 每个字节之间的分割字符串
+ */
+ public static String byteArr2HexStr(byte[] bt, int start, int end, String sep) {
+ if (bt == null || bt.length < end || start < 0 || start >= end) {
+ throw new RuntimeException("param format error");
+ }
+
+ StringBuffer sb = new StringBuffer();
+ for (int i = start; i < end; i++) {
+ sb.append(byte2HexStr(bt[i])).append(sep);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * 将byte转换成对应的十六进制字符串(如:byte值0x3D转换成字符串"3D")
+ *
+ * @return 返回字符串长度一定为2
+ */
+ public static String byte2HexStr(byte b) {
+ int i = (b & 0xF0) >> 4;
+ int j = (b & 0x0F);
+ char c = (char) (i > 9 ? 'A' + i % 10 : '0' + i);
+ char d = (char) (j > 9 ? 'A' + j % 10 : '0' + j);
+ return "" + c + d;
+ }
+}
\ No newline at end of file
diff --git a/native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/LogUtil.kt b/native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/LogUtil.kt
new file mode 100644
index 00000000..a5f5e9a7
--- /dev/null
+++ b/native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/LogUtil.kt
@@ -0,0 +1,11 @@
+package so.onekey.app.wallet.lite.utils
+
+import so.onekey.app.wallet.lite.onekeyLite.NfcConstant
+import so.onekey.app.wallet.lite.LoggerManager
+
+object LogUtil {
+ @JvmStatic
+ fun printLog(tag: String, msg: String) {
+ if (NfcConstant.DEBUG) LoggerManager.getInstance()?.logInfo("$tag: $msg")
+ }
+}
diff --git a/native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/MiUtil.kt b/native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/MiUtil.kt
new file mode 100644
index 00000000..d3851551
--- /dev/null
+++ b/native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/MiUtil.kt
@@ -0,0 +1,93 @@
+package so.onekey.app.wallet.lite.utils
+
+import android.app.AppOpsManager
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.os.Build
+import android.provider.Settings
+import android.util.Log
+import androidx.annotation.IntDef
+import androidx.annotation.RequiresApi
+import so.onekey.app.wallet.lite.utils.MiUtil.PermissionResult.Companion.PERMISSION_ASK
+import so.onekey.app.wallet.lite.utils.MiUtil.PermissionResult.Companion.PERMISSION_DENIED
+import so.onekey.app.wallet.lite.utils.MiUtil.PermissionResult.Companion.PERMISSION_GRANTED
+import so.onekey.app.wallet.lite.utils.MiUtil.PermissionResult.Companion.PERMISSION_UNKNOWN
+
+object MiUtil {
+ val TAG = MiUtil::class.simpleName
+
+ @IntDef(value = [PERMISSION_GRANTED, PERMISSION_DENIED, PERMISSION_ASK, PERMISSION_UNKNOWN])
+ @kotlin.annotation.Retention(
+ AnnotationRetention.SOURCE
+ )
+ annotation class PermissionResult {
+ companion object {
+ /**
+ * Permission check result: this is returned by [.check]
+ * if the permission has been granted to the given package.
+ */
+ const val PERMISSION_GRANTED = 0
+
+ /**
+ * Permission check result: this is returned by [.check]
+ * if the permission has not been granted to the given package.
+ */
+ const val PERMISSION_DENIED = -1
+
+ const val PERMISSION_ASK = 1
+
+ const val PERMISSION_UNKNOWN = 2
+ }
+ }
+
+ /**
+ * 检测NFC权限是否有授权.
+ */
+ @PermissionResult
+ @RequiresApi(Build.VERSION_CODES.KITKAT)
+ fun checkNfcPermission(context: Context): Int {
+ try {
+ val mAppOps = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
+ val pkg = context.applicationContext.packageName
+ val uid = context.applicationInfo.uid
+ val appOpsClass = Class.forName(AppOpsManager::class.java.name)
+ val checkOpNoThrowMethod = appOpsClass.getDeclaredMethod(
+ "checkOpNoThrow", Integer.TYPE, Integer.TYPE,
+ String::class.java
+ )
+ //the ops of NFC is 10016,check /data/system/appops/xxx.xml
+ val invoke = checkOpNoThrowMethod.invoke(mAppOps, 10016, uid, pkg)
+ if (invoke == null) {
+ Log.d(TAG,
+ "MIUI check permission checkOpNoThrowMethod(AppOpsManager) invoke result is null"
+ )
+ return PERMISSION_UNKNOWN
+ }
+ val result = invoke.toString()
+ Log.d(TAG,
+ "MIUI check permission checkOpNoThrowMethod(AppOpsManager) invoke result = $result"
+ )
+ when (result) {
+ "0" -> return PERMISSION_GRANTED
+ "1" -> return PERMISSION_DENIED
+ "5" -> return PERMISSION_ASK
+ }
+ } catch (e: Exception) {
+ Log.d(TAG, "check nfc permission fail ${e.message}", e)
+ }
+ return PERMISSION_UNKNOWN
+ }
+
+ fun intentToAppSetting(context: Context): Boolean {
+ val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
+ intent.data = Uri.fromParts("package", context.packageName, null)
+ return try {
+ context.startActivity(intent)
+ true
+ } catch (e: Exception) {
+ Log.d(TAG, "open app setting fail ${e.message}", e)
+ false
+ }
+ }
+}
\ No newline at end of file
diff --git a/native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/NfcPermissionUtils.kt b/native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/NfcPermissionUtils.kt
new file mode 100755
index 00000000..928261af
--- /dev/null
+++ b/native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/NfcPermissionUtils.kt
@@ -0,0 +1,24 @@
+package so.onekey.app.wallet.lite.utils
+
+import android.app.Activity
+import android.os.Build
+
+object NfcPermissionUtils {
+
+ inline fun checkPermission(activity: Activity, doNext: () -> Unit): Int {
+ return if ("xiaomi".equals(Build.MANUFACTURER, true)) {
+ return checkMiuiPermission(activity, doNext)
+ } else {
+ doNext.invoke()
+ 0
+ }
+ }
+
+ inline fun checkMiuiPermission(activity: Activity, doNext: () -> Unit): Int {
+ when (MiUtil.checkNfcPermission(activity)) {
+ MiUtil.PermissionResult.PERMISSION_GRANTED -> doNext.invoke()
+ else -> {}
+ }
+ return MiUtil.checkNfcPermission(activity)
+ }
+}
\ No newline at end of file
diff --git a/native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/Utils.java b/native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/Utils.java
new file mode 100755
index 00000000..d57ed46c
--- /dev/null
+++ b/native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/Utils.java
@@ -0,0 +1,437 @@
+package so.onekey.app.wallet.lite.utils;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.Application;
+import android.app.Application.ActivityLifecycleCallbacks;
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.facebook.react.bridge.ReactApplicationContext;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ *
+ * author:
+ * ___ ___ ___ ___
+ * _____ / /\ /__/\ /__/| / /\
+ * / /::\ / /::\ \ \:\ | |:| / /:/
+ * / /:/\:\ ___ ___ / /:/\:\ \ \:\ | |:| /__/::\
+ * / /:/~/::\ /__/\ / /\ / /:/~/::\ _____\__\:\ __| |:| \__\/\:\
+ * /__/:/ /:/\:| \ \:\ / /:/ /__/:/ /:/\:\ /__/::::::::\ /__/\_|:|____ \ \:\
+ * \ \:\/:/~/:/ \ \:\ /:/ \ \:\/:/__\/ \ \:\~~\~~\/ \ \:\/:::::/ \__\:\
+ * \ \::/ /:/ \ \:\/:/ \ \::/ \ \:\ ~~~ \ \::/~~~~ / /:/
+ * \ \:\/:/ \ \::/ \ \:\ \ \:\ \ \:\ /__/:/
+ * \ \::/ \__\/ \ \:\ \ \:\ \ \:\ \__\/
+ * \__\/ \__\/ \__\/ \__\/
+ * blog : http://blankj.com
+ * time : 16/12/08
+ * desc : utils about initialization
+ *
+ */
+public final class Utils {
+
+ @SuppressLint("StaticFieldLeak")
+ private static Application sApplication;
+
+ static final ActivityLifecycleImpl ACTIVITY_LIFECYCLE = new ActivityLifecycleImpl();
+
+ private Utils() {
+ throw new UnsupportedOperationException("u can't instantiate me...");
+ }
+
+ /**
+ * Init utils.
+ *
+ * Init it in the class of Application.
+ *
+ * @param context context
+ */
+ public static void init(@NonNull final ReactApplicationContext context) {
+ Utils.getActivityLifecycle().setTopActivity(context.getCurrentActivity());
+ init((Application) context.getApplicationContext());
+ }
+
+ /**
+ * Init utils.
+ *
+ *
Init it in the class of Application.
+ *
+ * @param app application
+ */
+ public static void init(@NonNull final Application app) {
+ if (sApplication == null) {
+ Utils.sApplication = app;
+ Utils.sApplication.registerActivityLifecycleCallbacks(ACTIVITY_LIFECYCLE);
+ }
+ }
+
+ /**
+ * Return the context of Application object.
+ *
+ * @return the context of Application object
+ */
+ public static Application getApp() {
+ if (sApplication != null) {
+ return sApplication;
+ }
+ try {
+ @SuppressLint("PrivateApi")
+ Class> activityThread = Class.forName("android.app.ActivityThread");
+ Object at = activityThread.getMethod("currentActivityThread").invoke(null);
+ Object app = activityThread.getMethod("getApplication").invoke(at);
+ if (app == null) {
+ throw new NullPointerException("u should init first");
+ }
+ init((Application) app);
+ return sApplication;
+ } catch (NoSuchMethodException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ e.printStackTrace();
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ }
+ throw new NullPointerException("u should init first");
+ }
+
+ public static ActivityLifecycleImpl getActivityLifecycle() {
+ return ACTIVITY_LIFECYCLE;
+ }
+
+ public static LinkedList getActivityList() {
+ return ACTIVITY_LIFECYCLE.mActivityList;
+ }
+
+ @NotNull
+ public static Context getTopActivityOrApp() {
+ if (isAppForeground()) {
+ Activity topActivity = ACTIVITY_LIFECYCLE.getTopActivity();
+ return topActivity == null ? Utils.getApp() : topActivity;
+ } else {
+ return Utils.getApp();
+ }
+ }
+
+ @Nullable
+ public static Activity getTopActivity() {
+ if (!isAppForeground()) {
+ return null;
+ }
+ return ACTIVITY_LIFECYCLE.getTopActivity();
+ }
+
+ /**
+ * Finish all of activities.
+ */
+ public static void finishAllActivities() {
+ finishAllActivities(false);
+ }
+
+ /**
+ * Finish all of activities.
+ *
+ * @param isLoadAnim True to use animation for the outgoing activity, false otherwise.
+ */
+ public static void finishAllActivities(final boolean isLoadAnim) {
+ List activityList = getActivityList();
+ for (Activity act : activityList) {
+ // sActivityList remove the index activity at onActivityDestroyed
+ act.finish();
+ if (!isLoadAnim) {
+ act.overridePendingTransition(0, 0);
+ }
+ }
+ }
+
+ /**
+ * Finish all of activities except the newest activity.
+ */
+ public static void finishAllActivitiesExceptNewest() {
+ finishAllActivitiesExceptNewest(false);
+ }
+
+ /**
+ * Finish all of activities except the newest activity.
+ *
+ * @param isLoadAnim True to use animation for the outgoing activity, false otherwise.
+ */
+ public static void finishAllActivitiesExceptNewest(final boolean isLoadAnim) {
+ List activities = getActivityList();
+ for (int i = 1; i < activities.size(); i++) {
+ finishActivity(activities.get(i), isLoadAnim);
+ }
+ }
+
+ public static void finishActivity(@NonNull final Activity activity) {
+ finishActivity(activity, false);
+ }
+
+ public static void finishActivity(@NonNull final Activity activity, final boolean isLoadAnim) {
+ activity.finish();
+ if (!isLoadAnim) {
+ activity.overridePendingTransition(0, 0);
+ }
+ }
+
+ /**
+ * Finish the activity.
+ *
+ * @param clz The activity class.
+ */
+ public static void finishActivity(@NonNull final Class extends Activity> clz) {
+ finishActivity(clz, false);
+ }
+
+ /**
+ * Finish the activity.
+ *
+ * @param clz The activity class.
+ * @param isLoadAnim True to use animation for the outgoing activity, false otherwise.
+ */
+ public static void finishActivity(
+ @NonNull final Class extends Activity> clz, final boolean isLoadAnim) {
+ List activities = getActivityList();
+ for (Activity activity : activities) {
+ if (activity.getClass().equals(clz)) {
+ activity.finish();
+ if (!isLoadAnim) {
+ activity.overridePendingTransition(0, 0);
+ }
+ }
+ }
+ }
+
+ /**
+ * Finish the activities whose type not equals the activity class.
+ *
+ * @param clz The activity class.
+ */
+ public static void finishOtherActivities(@NonNull final Class extends Activity> clz) {
+ finishOtherActivities(clz, false);
+ }
+
+ /**
+ * Finish the activities whose type not equals the activity class.
+ *
+ * @param clz The activity class.
+ * @param isLoadAnim True to use animation for the outgoing activity, false otherwise.
+ */
+ public static void finishOtherActivities(
+ @NonNull final Class extends Activity> clz, final boolean isLoadAnim) {
+ List activities = getActivityList();
+ for (Activity act : activities) {
+ if (!act.getClass().equals(clz)) {
+ finishActivity(act, isLoadAnim);
+ }
+ }
+ }
+
+ public static boolean isAppForeground() {
+ ActivityManager am =
+ (ActivityManager) Utils.getApp().getSystemService(Context.ACTIVITY_SERVICE);
+ if (am == null) {
+ return false;
+ }
+ List info = am.getRunningAppProcesses();
+ if (info == null || info.size() == 0) {
+ return false;
+ }
+ for (ActivityManager.RunningAppProcessInfo aInfo : info) {
+ if (aInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
+ return aInfo.processName.equals(Utils.getApp().getPackageName());
+ }
+ }
+ return false;
+ }
+
+ static class ActivityLifecycleImpl implements ActivityLifecycleCallbacks {
+
+ final LinkedList mActivityList = new LinkedList<>();
+ final HashMap