Updated on 2026-08-14
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.card_android;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
@Test
|
||||
public void useAppContext() {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getTargetContext();
|
||||
|
||||
assertEquals("com.tangem.card_android.test", appContext.getPackageName());
|
||||
}
|
||||
}
|
||||
8
card-android/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.tangem.card_android" >
|
||||
<uses-feature
|
||||
android:name="android.hardware.nfc"
|
||||
android:required="true" />
|
||||
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package com.tangem.card_android.android.data;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.tangem.card_common.data.external.FirmwaresDigestsProvider;
|
||||
import com.tangem.card_common.util.Util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class Firmwares implements FirmwaresDigestsProvider {
|
||||
private static JsonArray jaFirmwares = null;
|
||||
|
||||
public Firmwares(Context context) {
|
||||
try (InputStream is = context.getAssets().open("fw_hashes.json")) {
|
||||
try (InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) {
|
||||
JsonParser parser = new JsonParser();
|
||||
jaFirmwares = parser.parse(reader).getAsJsonArray();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public FirmwaresDigestsProvider.VerifyCodeRecord selectRandomVerifyCodeBlock(String firmwareVersion) {
|
||||
|
||||
try {
|
||||
for (int i = 0; i < jaFirmwares.size(); i++) {
|
||||
JsonObject jsVersion = jaFirmwares.get(i).getAsJsonObject();
|
||||
if (jsVersion.get("fw").getAsString().equals(firmwareVersion)) {
|
||||
FirmwaresDigestsProvider.VerifyCodeRecord result = new FirmwaresDigestsProvider.VerifyCodeRecord();
|
||||
result.hashAlg = "sha-256";
|
||||
JsonArray jsHashes = jsVersion.get(result.hashAlg).getAsJsonArray();
|
||||
result.challenge = Util.hexToBytes(jsVersion.get("challenge").getAsString());
|
||||
int caseIndex = (Util.byteArrayToInt(Util.generateRandomBytes(4)) & 0xFFFFFF) % jsHashes.size();
|
||||
JsonObject jsRecord = jsHashes.get(caseIndex).getAsJsonObject();
|
||||
result.blockIndex = jsRecord.get("block").getAsInt();
|
||||
result.blockCount = jsRecord.get("count").getAsInt();
|
||||
result.digest = Util.hexToBytes(jsRecord.get("digest").getAsString());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
package com.tangem.card_android.android.data;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Base64;
|
||||
|
||||
import com.tangem.card_common.data.external.PINsProvider;
|
||||
import com.tangem.card_common.reader.CardProtocol;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
/**
|
||||
* Created by dvol on 12.09.2017.
|
||||
* Global PIN Storage
|
||||
*/
|
||||
|
||||
public class PINStorage implements PINsProvider {
|
||||
private static String mSavedPIN, mUserPIN, mLastUsedPIN, mEncryptedPIN, mPIN2;
|
||||
private static SharedPreferences sharedPreferences = null;
|
||||
|
||||
public static void init(Context context) {
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
mSavedPIN = sharedPreferences.getString("SavedPIN", null);
|
||||
mUserPIN = null;
|
||||
mLastUsedPIN = null;
|
||||
mEncryptedPIN = null;
|
||||
mPIN2 = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getPINs() {
|
||||
ArrayList<String> result = new ArrayList<>();
|
||||
if (mLastUsedPIN != null) result.add(mLastUsedPIN);
|
||||
if (mEncryptedPIN != null && !result.contains(mEncryptedPIN)) result.add(mEncryptedPIN);
|
||||
if (mUserPIN != null && !result.contains(mUserPIN)) result.add(mUserPIN);
|
||||
if (mSavedPIN != null && !result.contains(mSavedPIN)) result.add(mSavedPIN);
|
||||
if (!result.contains(CardProtocol.DefaultPIN)) result.add(CardProtocol.DefaultPIN);
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setLastUsedPIN(String PIN) {
|
||||
mLastUsedPIN = PIN;
|
||||
}
|
||||
|
||||
public static void setUserPIN(String PIN) {
|
||||
mUserPIN = PIN;
|
||||
}
|
||||
|
||||
public static void setPIN2(String PIN) {
|
||||
mPIN2 = PIN;
|
||||
}
|
||||
|
||||
public static void savePIN(String PIN) {
|
||||
mSavedPIN = PIN;
|
||||
if (mSavedPIN != null && !mSavedPIN.isEmpty()) {
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString("SavedPIN", mSavedPIN);
|
||||
editor.apply();
|
||||
} else {
|
||||
deletePIN();
|
||||
}
|
||||
}
|
||||
|
||||
public static void deletePIN() {
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
if (mSavedPIN != null && mSavedPIN.equals(mLastUsedPIN)) {
|
||||
mLastUsedPIN = null;
|
||||
}
|
||||
mSavedPIN = null;
|
||||
editor.remove("SavedPIN");
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
public static void saveEncryptedPIN(Cipher cipher, String PIN) {
|
||||
try {
|
||||
byte[] iv = cipher.getIV();
|
||||
byte[] bytes = cipher.doFinal(PIN.getBytes());
|
||||
String encryptedPIN = Base64.encodeToString(bytes, Base64.NO_WRAP);
|
||||
String sIV = Base64.encodeToString(iv, Base64.NO_WRAP);
|
||||
|
||||
// Log.d("PINStorage", String.format("saveEncryptedPIN: %s, encrypted: %s, iv: %s",PIN,encryptedPIN,sIV));
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString("EncryptedPIN", encryptedPIN);
|
||||
editor.putString("EncryptedIV", sIV);
|
||||
editor.apply();
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] loadEncryptedIV() {
|
||||
String sIV = sharedPreferences.getString("EncryptedIV", "");
|
||||
// Log.d("PINStorage", String.format("loadEncryptedIV: %s",sIV));
|
||||
|
||||
return Base64.decode(sIV, Base64.NO_WRAP);
|
||||
}
|
||||
|
||||
public static String loadEncryptedPIN(Cipher cipher) {
|
||||
String encryptedPIN = sharedPreferences.getString("EncryptedPIN", null);
|
||||
|
||||
try {
|
||||
byte[] bytes = Base64.decode(encryptedPIN, Base64.NO_WRAP);
|
||||
mEncryptedPIN = new String(cipher.doFinal(bytes));
|
||||
// Log.d("PINStorage", String.format("loadEncryptedPIN: %s (encrypted: %s)",mEncryptedPIN,encryptedPIN));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
mEncryptedPIN = null;
|
||||
}
|
||||
return mEncryptedPIN;
|
||||
}
|
||||
|
||||
public static boolean haveEncryptedPIN() {
|
||||
return sharedPreferences.getString("EncryptedPIN", null) != null;
|
||||
}
|
||||
|
||||
public static void deleteEncryptedPIN() {
|
||||
if (mEncryptedPIN != null && mEncryptedPIN.equals(mLastUsedPIN)) {
|
||||
mLastUsedPIN = null;
|
||||
}
|
||||
mEncryptedPIN = null;
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.remove("EncryptedPIN");
|
||||
editor.remove("EncryptedIV");
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
public static void saveEncryptedPIN2(Cipher cipher, String PIN) {
|
||||
try {
|
||||
byte[] iv = cipher.getIV();
|
||||
byte[] bytes = cipher.doFinal(PIN.getBytes());
|
||||
String encryptedPIN = Base64.encodeToString(bytes, Base64.NO_WRAP);
|
||||
String sIV = Base64.encodeToString(iv, Base64.NO_WRAP);
|
||||
|
||||
// Log.d("PINStorage", String.format("saveEncryptedPIN: %s, encrypted: %s, iv: %s",PIN,encryptedPIN,sIV));
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString("EncryptedPIN2", encryptedPIN);
|
||||
editor.putString("EncryptedIV2", sIV);
|
||||
editor.apply();
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] loadEncryptedIV2() {
|
||||
String sIV = sharedPreferences.getString("EncryptedIV2", "");
|
||||
// Log.d("PINStorage", String.format("loadEncryptedIV: %s",sIV));
|
||||
return Base64.decode(sIV, Base64.NO_WRAP);
|
||||
}
|
||||
|
||||
public static String loadEncryptedPIN2(Cipher cipher) {
|
||||
String encryptedPIN = sharedPreferences.getString("EncryptedPIN2", null);
|
||||
try {
|
||||
byte[] bytes = Base64.decode(encryptedPIN, Base64.NO_WRAP);
|
||||
mPIN2 = new String(cipher.doFinal(bytes));
|
||||
// Log.d("PINStorage", String.format("loadEncryptedPIN: %s (encrypted: %s)",mEncryptedPIN,encryptedPIN));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
mPIN2 = null;
|
||||
}
|
||||
return mPIN2;
|
||||
}
|
||||
|
||||
public static boolean haveEncryptedPIN2() {
|
||||
return sharedPreferences.getString("EncryptedPIN2", null) != null;
|
||||
}
|
||||
|
||||
public static void deleteEncryptedPIN2() {
|
||||
mPIN2 = null;
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.remove("EncryptedPIN2");
|
||||
editor.remove("EncryptedIV2");
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
public String getPIN2() {
|
||||
return mPIN2;
|
||||
}
|
||||
|
||||
public static String getDefaultPIN() {
|
||||
return CardProtocol.DefaultPIN;
|
||||
}
|
||||
|
||||
public static String getDefaultPIN2() {
|
||||
return CardProtocol.DefaultPIN2;
|
||||
}
|
||||
|
||||
public static boolean needInit() {
|
||||
return sharedPreferences == null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
package com.tangem.card_android.android.nfc
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.view.View
|
||||
import android.view.animation.Animation
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import android.view.animation.Transformation
|
||||
import android.widget.ImageView
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.appcompat.widget.LinearLayoutCompat
|
||||
|
||||
class NfcDeviceAntennaLocation(private var context: Context,
|
||||
private var ivHandCardHorizontal: ImageView,
|
||||
private var ivHandCardVertical: ImageView,
|
||||
private var llHand: LinearLayoutCompat,
|
||||
private var llNfc: LinearLayoutCompat) {
|
||||
|
||||
companion object {
|
||||
const val CARD_ON_BACK = 0
|
||||
const val CARD_ON_FRONT = 1
|
||||
const val CARD_ORIENTATION_HORIZONTAL = 0
|
||||
const val CARD_ORIENTATION_VERTICAL = 1
|
||||
}
|
||||
|
||||
var orientation: Int = 0
|
||||
var fullName: String = ""
|
||||
var x: Float = 0.toFloat()
|
||||
var y: Float = 0.toFloat()
|
||||
var z: Int = 0
|
||||
|
||||
fun init() {
|
||||
getAntennaLocation()
|
||||
setCardOrientation()
|
||||
animate()
|
||||
}
|
||||
|
||||
fun animate() {
|
||||
val lp = llHand.layoutParams as RelativeLayout.LayoutParams
|
||||
val lp2 = llNfc.layoutParams as RelativeLayout.LayoutParams
|
||||
val dp = context.resources.displayMetrics.density
|
||||
val lm = dp * (69 + x * 75)
|
||||
lp.topMargin = (dp * (-100 + y * 250)).toInt()
|
||||
lp2.topMargin = (dp * (-125 + y * 250)).toInt()
|
||||
llNfc.layoutParams = lp2
|
||||
|
||||
val a = object : Animation() {
|
||||
override fun applyTransformation(interpolatedTime: Float, t: Transformation) {
|
||||
lp.leftMargin = (lm * interpolatedTime).toInt()
|
||||
llHand.layoutParams = lp
|
||||
}
|
||||
}
|
||||
a.duration = 2000
|
||||
a.interpolator = DecelerateInterpolator()
|
||||
llHand.startAnimation(a)
|
||||
}
|
||||
|
||||
private fun getAntennaLocation() {
|
||||
val codename = Build.DEVICE
|
||||
|
||||
// default values
|
||||
this.orientation = 0
|
||||
this.fullName = ""
|
||||
this.x = 0.5f
|
||||
this.y = 0.35f
|
||||
this.z = 0
|
||||
|
||||
for (nfcLocation in NfcLocation.values()) {
|
||||
if (codename.startsWith(nfcLocation.codename)) {
|
||||
this.fullName = nfcLocation.fullName
|
||||
this.orientation = nfcLocation.orientation
|
||||
this.x = nfcLocation.x / 100f
|
||||
this.y = nfcLocation.y / 100f
|
||||
this.z = nfcLocation.z
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setCardOrientation() {
|
||||
when (orientation) {
|
||||
CARD_ORIENTATION_HORIZONTAL -> {
|
||||
ivHandCardHorizontal.visibility = View.VISIBLE
|
||||
ivHandCardVertical.visibility = View.GONE
|
||||
}
|
||||
|
||||
CARD_ORIENTATION_VERTICAL -> {
|
||||
ivHandCardVertical.visibility = View.VISIBLE
|
||||
ivHandCardHorizontal.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
// set card z position
|
||||
when (z) {
|
||||
NfcDeviceAntennaLocation.CARD_ON_BACK -> llHand.elevation = 0.0f
|
||||
NfcDeviceAntennaLocation.CARD_ON_FRONT -> llHand.elevation = 30.0f
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.card_android.android.nfc
|
||||
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleObserver
|
||||
import androidx.lifecycle.OnLifecycleEvent
|
||||
import com.tangem.card_android.android.reader.NfcManager
|
||||
|
||||
/**
|
||||
* Lifecycle observer for all activities and fragments with nfc
|
||||
*
|
||||
* @see NfcManager
|
||||
*/
|
||||
class NfcLifecycleObserver(private var nfcManager: NfcManager) : LifecycleObserver {
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
||||
fun onResume() {
|
||||
nfcManager.onResume()
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
|
||||
fun onPause() {
|
||||
nfcManager.onPause()
|
||||
nfcManager.onStop()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,245 @@
|
|||
package com.tangem.card_android.android.nfc
|
||||
|
||||
enum class NfcLocation(val codename: String, val fullName: String, val orientation: Int, val x: Int, val y: Int, val z: Int) {
|
||||
model1("sailfish", "Google Pixel", 0, 65, 25, 0),
|
||||
model2("walleye", "Google Pixel 2", 0, 40, 15, 0),
|
||||
model3("taimen", "Google Pixel 2 XL", 0, 40, 15, 0),
|
||||
model4("marlin", "Google Pixel XL", 0, 65, 25, 0),
|
||||
model5("blueline", "Google Pixel 3 ", 0, 40, 30, 0),
|
||||
model6("crosshatch", "Google Pixel 3 XL", 0, 15, 20, 0),
|
||||
model7("htc_pme", "HTC 10 ", 0, 50, 20, 0),
|
||||
model8("htc_himau", "HTC One M9", 0, 50, 20, 0),
|
||||
model9("htc_himaw", "HTC One M9", 0, 50, 20, 0),
|
||||
model10("htc_oce", "HTC U Ultra", 0, 50, 20, 0),
|
||||
model11("htc_ocn", "HTC U11", 0, 50, 25, 0),
|
||||
model12("htc_ocm", "HTC U11+", 0, 50, 60, 0),
|
||||
model13("htc_ocluhljapan", "HTC U11 Life", 1, 50, 25, 0),
|
||||
model14("htc_haydugl", "HTC U11 EYEs", 0, 50, 65, 0),
|
||||
model15("htc_haydtwl", "HTC U11 EYEs", 0, 50, 65, 0),
|
||||
model16("htc_ime", "HTC U12+", 0, 50, 20, 0),
|
||||
model17("htc_bre2dugl", "HTC Desire 12s", 0, 50, 10, 0),
|
||||
model18("htc_imldugl", "HTC U12 Life", 0, 60, 20, 0),
|
||||
model19("htc_exodugl", "HTC EXODUS 1", 0, 50, 20, 0),
|
||||
model20("HWFRD", "Huawei Honor 8", 0, 50, 0, 0),
|
||||
model21("HWDUK", "Huawei Honor 8 Pro", 0, 50, 0, 0),
|
||||
model22("HWSTF", "Huawei Honor 9", 0, 50, 0, 0),
|
||||
model23("HWCOL", "Huawei Honor 10", 0, 50, 0, 0),
|
||||
model24("HWRVL", "Huawei Honor Note 10", 0, 50, 0, 0),
|
||||
model25("HWBKL", "Huawei Honor View 10", 0, 50, 0, 0),
|
||||
model26("HWPCT", "Huawei HONOR V20", 0, 60, 20, 0),
|
||||
model27("HWTNY", "Huawei Honor Magic 2", 0, 50, 0, 0),
|
||||
model28("HWBLN-H", "Huawei Mate 9 Lite / Honor 6X", 0, 40, 0, 0),
|
||||
model29("HWALP", "Huawei Mate 10", 0, 50, 0, 0),
|
||||
model30("HWBLA", "Huawei Mate 10 Pro", 0, 50, 0, 0),
|
||||
model31("HWHMA", "Huawei Mate 20", 0, 50, 20, 0),
|
||||
model32("HWEVR", "Huawei Mate 20 X", 0, 50, 20, 0),
|
||||
model33("HWLYA", "Huawei Mate 20 Pro", 0, 50, 20, 0),
|
||||
model34("angler", "Huawei Nexus 6P", 0, 40, 15, 0),
|
||||
model35("hwALE-", "Huawei P8 Lite ", 1, 75, 65, 0),
|
||||
model36("HWPRA-H", "Huawei P8 Lite 2017", 0, 50, 25, 0),
|
||||
model37("HWEVA", "Huawei P9", 0, 50, 0, 0),
|
||||
model38("HWVIE", "Huawei P9 Plus", 0, 50, 0, 0),
|
||||
model39("HWVNS-?", "Huawei P9 Lite", 0, 60, 45, 0),
|
||||
model40("HWVTR", "Huawei P10", 0, 50, 0, 0),
|
||||
model41("HWWAS-H", "Huawei P10 lite", 0, 50, 0, 0),
|
||||
model42("HWVKY", "Huawei P10 Plus", 0, 50, 0, 0),
|
||||
model43("HWEML", "Huawei P20", 0, 50, 50, 0),
|
||||
model44("HWANE", "Huawei P20 Lite", 0, 50, 20, 0),
|
||||
model45("HWCLT", "Huawei P20 Pro", 0, 50, 50, 0),
|
||||
model46("HW-01K", "Huawei P20 Pro", 0, 50, 50, 0),
|
||||
model47("HWELE", "Huawei P30", 0, 50, 50, 0),
|
||||
model48("HWVOG", "Huawei P30 Pro", 0, 50, 50, 0),
|
||||
model49("HW-02L", "Huawei P30 Pro", 0, 50, 50, 0),
|
||||
model50("HWFIG-H", "Huawei P Smart", 0, 45, 0, 0),
|
||||
model51("HWPOT-H", "Huawei P smart 2019", 0, 50, 20, 0),
|
||||
model52("h1", "LG G5", 0, 30, 20, 0),
|
||||
model53("lucye", "LG G6", 0, 50, 50, 0),
|
||||
model54("judyln", "LG G7", 0, 50, 50, 0),
|
||||
model55("hammerhead", "LG Nexus 5", 0, 50, 50, 0),
|
||||
model56("bullhead", "LG Nexus 5X", 0, 45, 15, 0),
|
||||
model57("mh", "LG Q6", 1, 25, 50, 0),
|
||||
model58("ph2n", "LG Stylo 2+", 0, 75, 20, 0),
|
||||
model59("sf340n", "LG Stylo 3+", 0, 75, 20, 0),
|
||||
model60("ph1n", "LG Stylus 2", 0, 75, 20, 0),
|
||||
model61("msf3", "LG Stylus 3", 0, 75, 20, 0),
|
||||
model62("elsa", "LG V20", 0, 50, 15, 0),
|
||||
model63("joan", "LG V30", 0, 50, 50, 0),
|
||||
model64("L-01K", "LG V30+", 0, 50, 50, 0),
|
||||
model65("judyp", "LG V35 ThinQ", 0, 50, 50, 0),
|
||||
model66("judyln", "LG G7 ThinQ", 0, 50, 50, 0),
|
||||
model67("L-02K", "LG JOJO", 0, 50, 60, 0),
|
||||
model68("mcv5a", "LG Q7", 0, 50, 60, 0),
|
||||
model69("cv7a", "LG Stylo 4", 0, 50, 60, 0),
|
||||
model70("nora_8917_n", "Motorola Moto E5", 0, 50, 40, 0),
|
||||
model71("potter_n", "Motorola Moto G5 Plus", 0, 50, 5, 0),
|
||||
model72("montana_n", "Motorola Moto G5s", 0, 50, 5, 0),
|
||||
model73("sanders_n", "Motorola Moto G5s Plus", 0, 50, 5, 0),
|
||||
model74("ali_n", "Motorola Moto G6", 0, 50, 35, 0),
|
||||
model75("aljeter_n", "Motorola Moto G6 Play", 0, 50, 35, 0),
|
||||
model76("evert_n", "Motorola Moto G6 Plus", 0, 40, 40, 0),
|
||||
model77("river_n", "Motorola moto g(7)", 0, 50, 50, 0),
|
||||
model78("lake_n", "Motorola moto g(7) plus", 0, 50, 50, 0),
|
||||
model79("payton", "Motorola Moto X4", 0, 50, 40, 0),
|
||||
model80("nash", "Motorola Moto Z2 Force", 0, 50, 40, 0),
|
||||
model81("albus", "Motorola Moto Z2 Play", 0, 50, 5, 0),
|
||||
model82("beckham", "Motorola Moto Z3 Play", 0, 50, 0, 0),
|
||||
model83("shamu", "Motorola Nexus 6", 0, 50, 60, 0),
|
||||
model84("NE1", "Nokia 3", 0, 40, 30, 0),
|
||||
model85("ND1", "Nokia 5", 0, 40, 30, 0),
|
||||
model86("PLE", "Nokia 6", 0, 50, 20, 0),
|
||||
model87("PL2_sprout", "Nokia 6.1", 0, 50, 0, 0),
|
||||
model88("C1N", "Nokia 7", 0, 50, 25, 0),
|
||||
model89("B2N", "Nokia 7 Plus", 0, 75, 10, 0),
|
||||
model90("NB1", "Nokia 8", 0, 50, 30, 0),
|
||||
model91("A1N_sprout", "Nokia 8 Sirocco", 0, 90, 0, 0),
|
||||
model92("A1N", "Nokia 8 Sirocco", 0, 90, 0, 0),
|
||||
model93("OnePlus3", "OnePlus 3", 0, 50, 15, 0),
|
||||
model94("OnePlus3T", "OnePlus 3t", 0, 50, 15, 0),
|
||||
model95("OnePlus5", "OnePlus 5", 0, 80, 5, 0),
|
||||
model96("OnePlus5T", "OnePlus 5t", 0, 80, 5, 0),
|
||||
model97("OnePlus6", "OnePlus 6", 0, 50, 20, 0),
|
||||
model98("A0001", "OnePlus One", 0, 50, 50, 0),
|
||||
model99("OnePlus6T", "OnePlus6T", 0, 50, 20, 0),
|
||||
model100("OnePlus6TSingle", "OnePlus6T", 0, 50, 20, 0),
|
||||
model101("OnePlus7Pro", "OnePlus 7 Pro", 0, 30, 25, 0),
|
||||
model102("OnePlus7ProTMO", "OnePlus 7 Pro", 0, 30, 25, 0),
|
||||
model103("a5x", "Samsung Galaxy A5 (2016)", 1, 50, 45, 0),
|
||||
model104("a5y17lte", "Samsung Galaxy A5 (2017)", 1, 65, 45, 0),
|
||||
model105("a7y17lte", "Samsung Galaxy A7 (2017)", 1, 60, 40, 0),
|
||||
model106("a7y18lte", "Samsung Galaxy A7 (2018)", 0, 40, 20, 0),
|
||||
model107("a7y18lteks", "Samsung Galaxy A7 (2018)", 0, 40, 20, 0),
|
||||
model108("jackpotlte", "Samsung Galaxy A8 (2018)", 1, 50, 50, 0),
|
||||
model109("a8", "Samsung Galaxy A8 / A8 (2016)", 0, 50, 15, 0),
|
||||
model110("jackpot2lte", "Samsung Galaxy A8+", 1, 50, 45, 0),
|
||||
model111("a8sqlte", "Samsung Galaxy A8s", 0, 50, 25, 0),
|
||||
model112("a8sqltechn", "Samsung Galaxy A8s", 0, 50, 25, 0),
|
||||
model113("a9xltechn", "Samsung Galaxy A9 (2016)", 1, 50, 45, 0),
|
||||
model114("a9y18qltekx", "Samsung Galaxy A9 (2018)", 0, 40, 25, 0),
|
||||
model115("a9y18qlte", "Samsung Galaxy A9 (2018)", 0, 40, 25, 0),
|
||||
model116("c5ltechn", "Samsung Galaxy C5", 0, 45, 15, 0),
|
||||
model117("c5pltechn", "Samsung Galaxy C5", 0, 45, 15, 0),
|
||||
model118("c5proltechn", "Samsung Galaxy C5 Pro", 1, 45, 15, 0),
|
||||
model119("c7ltechn", "Samsung Galaxy C7", 0, 50, 15, 0),
|
||||
model120("c7prolte", "Samsung Galaxy C7 Pro", 1, 50, 15, 0),
|
||||
model121("c9lte", "Samsung Galaxy C9 Pro", 0, 45, 15, 0),
|
||||
model122("j5x", "Samsung Galaxy J5 (2016)", 1, 40, 50, 0),
|
||||
model123("j5y17lte", "Samsung Galaxy J5 (2017) / J5 Pro", 1, 60, 50, 0),
|
||||
model124("j6lte", "Samsung Galaxy J6", 1, 60, 50, 0),
|
||||
model125("j7y17lte", "Samsung Galaxy J7 Pro", 1, 40, 50, 0),
|
||||
model126("noblelte", "Samsung Galaxy Note5", 1, 50, 60, 0),
|
||||
model127("SCV37", "Samsung Galaxy Note8", 1, 50, 45, 0),
|
||||
model128("SC-01K", "Samsung Galaxy Note8", 1, 50, 45, 0),
|
||||
model129("great", "Samsung Galaxy Note8", 1, 50, 45, 0),
|
||||
model130("crownqle", "Samsung Galaxy Note9", 1, 50, 50, 0),
|
||||
model131("crownlte", "Samsung Galaxy Note9", 1, 50, 50, 0),
|
||||
model132("SC-01L", "Samsung Galaxy Note9", 1, 50, 50, 0),
|
||||
model133("SCV40", "Samsung Galaxy Note9", 1, 50, 50, 0),
|
||||
model134("SC-05G", "Samsung Galaxy S6", 1, 30, 60, 0),
|
||||
model135("zeroflte", "Samsung Galaxy S6", 1, 30, 60, 0),
|
||||
model136("SCV31", "Samsung Galaxy S6 edge", 1, 30, 60, 0),
|
||||
model137("404SC", "Samsung Galaxy S6 edge", 1, 30, 60, 0),
|
||||
model138("SC-04G", "Samsung Galaxy S6 edge", 1, 30, 60, 0),
|
||||
model139("zerolte", "Samsung Galaxy S6 edge", 1, 30, 60, 0),
|
||||
model140("zenlte", "Samsung Galaxy S6 edge+", 1, 50, 55, 0),
|
||||
model141("heroqlte", "Samsung Galaxy S7", 1, 50, 45, 0),
|
||||
model142("herolte", "Samsung Galaxy S7", 1, 50, 45, 0),
|
||||
model143("poseidonlteatt", "Samsung Galaxy S7 Active", 1, 45, 55, 0),
|
||||
model144("SCV33", "Samsung Galaxy S7 Edge", 1, 50, 45, 0),
|
||||
model145("SC-02H", "Samsung Galaxy S7 Edge", 1, 50, 45, 0),
|
||||
model146("hero2", "Samsung Galaxy S7 Edge", 1, 50, 45, 0),
|
||||
model147("SCV36", "Samsung Galaxy S8", 0, 50, 55, 0),
|
||||
model148("SC-02J", "Samsung Galaxy S8", 0, 50, 55, 0),
|
||||
model149("dreamlte", "Samsung Galaxy S8", 0, 50, 55, 0),
|
||||
model150("dreamqlte", "Samsung Galaxy S8", 0, 50, 55, 0),
|
||||
model151("cruiserlte", "Samsung Galaxy S8 Active", 0, 50, 50, 0),
|
||||
model152("SCV35", "Samsung Galaxy S8+", 0, 50, 45, 0),
|
||||
model153("SC-03J", "Samsung Galaxy S8+", 0, 50, 45, 0),
|
||||
model154("dream2", "Samsung Galaxy S8+", 0, 50, 45, 0),
|
||||
model155("SCV38", "Samsung Galaxy S9", 0, 50, 50, 0),
|
||||
model156("SC-02K", "Samsung Galaxy S9", 0, 50, 50, 0),
|
||||
model157("starlte", "Samsung Galaxy S9", 0, 50, 50, 0),
|
||||
model158("starqlte", "Samsung Galaxy S9", 0, 50, 50, 0),
|
||||
model159("SCV39", "Samsung Galaxy S9+", 0, 50, 60, 0),
|
||||
model160("SC-03K", "Samsung Galaxy S9+", 0, 50, 60, 0),
|
||||
model161("star2", "Samsung Galaxy S9+", 0, 50, 60, 0),
|
||||
model162("beyond1q", "Samsung Galaxy S10", 0, 50, 50, 0),
|
||||
model163("beyond1", "Samsung Galaxy S10", 0, 50, 50, 0),
|
||||
model164("beyond2q", "Samsung Galaxy S10+", 0, 50, 50, 0),
|
||||
model165("beyond2", "Samsung Galaxy S10+", 0, 50, 50, 0),
|
||||
model166("beyond0q", "Samsung Galaxy S10e", 0, 50, 50, 0),
|
||||
model167("beyond0", "Samsung Galaxy S10e", 0, 50, 50, 0),
|
||||
model168("F331", "Sony Xperia E5", 0, 50, 25, 0),
|
||||
model169("G33", "Sony Xperia L1", 0, 50, 30, 0),
|
||||
model170("H33", "Sony Xperia L2", 0, 50, 20, 0),
|
||||
model171("H43", "Sony Xperia L2", 0, 50, 20, 0),
|
||||
model172("F512", "Sony Xperia X", 0, 10, 5, 1),
|
||||
model173("suzu", "Sony Xperia X", 0, 10, 5, 1),
|
||||
model174("F5321", "Sony Xperia X Compact", 0, 50, 55, 0),
|
||||
model175("SO-02J", "Sony Xperia X Compact", 0, 50, 55, 0),
|
||||
model176("F813", "Sony Xperia X Performance", 0, 10, 5, 1),
|
||||
model177("SOV33", "Sony Xperia X Performance", 0, 10, 5, 1),
|
||||
model178("SO-04H", "Sony Xperia X Performance", 0, 10, 5, 1),
|
||||
model179("502SO", "Sony Xperia X Performance", 0, 10, 5, 1),
|
||||
model180("F311", "Sony Xperia XA", 0, 50, 20, 0),
|
||||
model181("F321", "Sony Xperia XA Ultra", 0, 50, 25, 0),
|
||||
model182("G31", "Sony Xperia XA1", 0, 50, 20, 0),
|
||||
model183("G34", "Sony Xperia XA1 Plus", 0, 50, 25, 0),
|
||||
model184("G32", "Sony Xperia XA1 Ultra", 0, 50, 20, 0),
|
||||
model185("H31", "Sony Xperia XA2", 0, 50, 15, 0),
|
||||
model186("H41", "Sony Xperia XA2", 0, 50, 15, 0),
|
||||
model187("H4493", "Sony Xperia XA2 Plus", 0, 50, 20, 0),
|
||||
model188("H4413", "Sony Xperia XA2 Plus", 0, 50, 20, 0),
|
||||
model189("H3413", "Sony Xperia XA2 Plus", 0, 50, 20, 0),
|
||||
model190("H32", "Sony Xperia XA2 Ultra", 0, 50, 20, 0),
|
||||
model191("H42", "Sony Xperia XA2 Ultra", 0, 50, 20, 0),
|
||||
model192("F833", "Sony Xperia XZ", 0, 20, 5, 1),
|
||||
model193("SO-01J", "Sony Xperia XZ", 0, 20, 5, 1),
|
||||
model194("601SO", "Sony Xperia XZ", 0, 20, 5, 1),
|
||||
model195("SOV34", "Sony Xperia XZ", 0, 20, 5, 1),
|
||||
model196("G81", "Sony Xperia XZ Premium", 0, 50, 55, 0),
|
||||
model197("SO-04J", "Sony Xperia XZ Premium", 0, 50, 55, 0),
|
||||
model198("G834", "Sony Xperia XZ1", 0, 45, 5, 0),
|
||||
model199("701SO", "Sony Xperia XZ1", 0, 45, 5, 0),
|
||||
model200("SOV36", "Sony Xperia XZ1", 0, 45, 5, 0),
|
||||
model201("SO-01K", "Sony Xperia XZ1", 0, 45, 5, 0),
|
||||
model202("G8441", "Sony Xperia XZ1 Compact", 0, 50, 30, 0),
|
||||
model203("SO-02K", "Sony Xperia XZ1 Compact", 0, 50, 30, 0),
|
||||
model204("H82", "Sony Xperia XZ2", 0, 20, 40, 0),
|
||||
model205("SOV37", "Sony Xperia XZ2", 0, 20, 40, 0),
|
||||
model206("SO-03K", "Sony Xperia XZ2", 0, 20, 40, 0),
|
||||
model207("702SO", "Sony Xperia XZ2", 0, 20, 40, 0),
|
||||
model208("H8166", "Sony Xperia XZ2 Premium", 0, 60, 20, 0),
|
||||
model209("SO-04K", "Sony Xperia XZ2 Premium", 0, 60, 20, 0),
|
||||
model210("SOV38", "Sony Xperia XZ2 Premium", 0, 60, 20, 0),
|
||||
model211("H8116", "Sony Xperia XZ2 Premium", 0, 60, 20, 0),
|
||||
model212("H8324", "Sony Xperia XZ2 Compact", 0, 50, 25, 0),
|
||||
model213("SO-05K", "Sony Xperia XZ2 Compact", 0, 50, 25, 0),
|
||||
model214("H8314", "Sony Xperia XZ2 Compact", 0, 50, 25, 0),
|
||||
model215("G823", "Sony Xperia XZs", 0, 20, 5, 1),
|
||||
model216("602SO", "Sony Xperia XZs", 0, 20, 5, 1),
|
||||
model217("SOV35", "Sony Xperia XZs", 0, 20, 5, 1),
|
||||
model218("SO-03J", "Sony Xperia XZs", 0, 20, 5, 1),
|
||||
model219("801SO", "Sony Xperia XZ3", 0, 60, 20, 0),
|
||||
model220("H9493", "Sony Xperia XZ3", 0, 60, 20, 0),
|
||||
model221("H9436", "Sony Xperia XZ3", 0, 60, 20, 0),
|
||||
model222("SOV39", "Sony Xperia XZ3", 0, 60, 20, 0),
|
||||
model223("SO-01L", "Sony Xperia XZ3", 0, 60, 20, 0),
|
||||
model224("H8416", "Sony Xperia XZ3", 0, 60, 20, 0),
|
||||
model225("gemini", "Xiaomi Mi 5", 0, 40, 20, 0),
|
||||
model226("capricorn", "Xiaomi Mi 5S", 0, 50, 10, 0),
|
||||
model227("natrium", "Xiaomi Mi 5S Plus", 0, 45, 15, 0),
|
||||
model228("sagit", "Xiaomi Mi 6", 0, 50, 20, 0),
|
||||
model229("dipper", "Xiaomi Mi 8", 0, 45, 20, 0),
|
||||
model230("ursa", "Xiaomi MI 8 Explorer Edition", 0, 50, 40, 0),
|
||||
model231("cepheus", "Xiaomi MI 9", 0, 40, 20, 0),
|
||||
model232("grus", "Xiaomi MI 9 SE", 0, 40, 20, 0),
|
||||
model233("lithium", "Xiaomi Mi MIX", 0, 20, 20, 0),
|
||||
model234("chiron", "Xiaomi Mi MIX 2", 0, 45, 20, 0),
|
||||
model235("polaris", "Xiaomi Mi MIX 2S", 0, 45, 20, 0),
|
||||
model236("scorpio", "Xiaomi Mi Note 2", 0, 50, 20, 0),
|
||||
model237("jason", "Xiaomi Mi Note 3", 0, 50, 20, 0),
|
||||
model238("perseus", "Xiaomi MIX 3", 0, 60, 20, 0),
|
||||
model239("bbb100", "BlackBerry KEYone", 0, 60, 20, 0),
|
||||
model240("bbf100", "BlackBerry KEY2", 0, 50, 30, 0),
|
||||
model241("CatS61", "Cat S61", 0, 50, 50, 0),
|
||||
}
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
package com.tangem.card_android.android.reader
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.TargetApi
|
||||
import android.app.Activity
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.PackageManager
|
||||
import android.nfc.NfcAdapter
|
||||
import android.nfc.Tag
|
||||
import android.nfc.tech.IsoDep
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.tangem.card_android.android.ui.NfcEnableDialog
|
||||
import com.tangem.card_common.util.Log
|
||||
import java.io.IOException
|
||||
|
||||
class NfcManager(private val activity: FragmentActivity, private val readerCallback: NfcAdapter.ReaderCallback) {
|
||||
companion object {
|
||||
val TAG: String = NfcManager::class.java.simpleName
|
||||
|
||||
// reader mode flags: listen for type A (not B), skipping ndef check
|
||||
private const val READER_FLAGS = NfcAdapter.FLAG_READER_NFC_A or NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK or NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS
|
||||
private const val DELAY_PRESENCE = 1500
|
||||
|
||||
private const val REQUEST_NFC_PERMISSIONS = 1
|
||||
private val PERMISSIONS_NFC = arrayOf(Manifest.permission.NFC)
|
||||
|
||||
// checks if the app has NFC permission if the app does not has permission then the user will be prompted to grant permissions
|
||||
fun verifyPermissions(activity: Activity) {
|
||||
// check if we have write permission
|
||||
val permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.NFC)
|
||||
|
||||
if (permission != PackageManager.PERMISSION_GRANTED) {
|
||||
// we don't have permission so prompt the user
|
||||
ActivityCompat.requestPermissions(activity, PERMISSIONS_NFC, REQUEST_NFC_PERMISSIONS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val nfcAdapter: NfcAdapter? = NfcAdapter.getDefaultAdapter(activity)
|
||||
private var nfcEnableDialog: NfcEnableDialog? = null
|
||||
private val broadcomWorkaround = false
|
||||
internal var errorCount = 0
|
||||
private val mBroadcastReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val action = intent.action ?: return
|
||||
if (action == NfcAdapter.ACTION_ADAPTER_STATE_CHANGED) {
|
||||
val state = intent.getIntExtra(NfcAdapter.EXTRA_ADAPTER_STATE, NfcAdapter.STATE_ON)
|
||||
if (state == NfcAdapter.STATE_ON || state == NfcAdapter.STATE_TURNING_ON) {
|
||||
Log.i(TAG, "state: $state , dialog: $nfcEnableDialog")
|
||||
nfcEnableDialog?.dismiss()
|
||||
|
||||
if (state == NfcAdapter.STATE_ON)
|
||||
enableReaderMode()
|
||||
|
||||
} else {
|
||||
if (nfcEnableDialog == null || !nfcEnableDialog!!.isVisible)
|
||||
showNFCEnableDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onResume() {
|
||||
// register broadcast receiver
|
||||
val filter = IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED)
|
||||
activity.registerReceiver(mBroadcastReceiver, filter)
|
||||
|
||||
if (nfcAdapter == null || !nfcAdapter.isEnabled)
|
||||
showNFCEnableDialog()
|
||||
else
|
||||
enableReaderMode()
|
||||
}
|
||||
|
||||
fun onPause() {
|
||||
activity.unregisterReceiver(mBroadcastReceiver)
|
||||
disableReaderMode()
|
||||
}
|
||||
|
||||
fun onStop() {
|
||||
nfcEnableDialog?.dismiss()
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun ignoreTag(tag: Tag) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
nfcAdapter?.ignore(tag, 1500, null, null);
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
val isoDep = IsoDep.get(tag)
|
||||
isoDep?.close()
|
||||
}
|
||||
|
||||
fun notifyReadResult(success: Boolean) {
|
||||
// if (success) {
|
||||
// errorCount = 0;
|
||||
// } else {
|
||||
// errorCount++;
|
||||
// }
|
||||
// if (errorCount >= 3) {
|
||||
// disableReaderMode();
|
||||
// nfcAdapter = null;
|
||||
//// Toast.makeText(activity,"NFC restarted!",Toast.LENGTH_SHORT).show();
|
||||
// activity.runOnUiThread(() -> {
|
||||
// nfcAdapter = NfcAdapter.getDefaultAdapter(activity);
|
||||
// enableReaderMode();
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
||||
private fun showNFCEnableDialog() {
|
||||
nfcEnableDialog = NfcEnableDialog()
|
||||
nfcEnableDialog?.show(activity.supportFragmentManager, NfcEnableDialog.TAG)
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||
private fun enableReaderMode() {
|
||||
val options = Bundle()
|
||||
if (broadcomWorkaround) {
|
||||
/* This is a work around for some Broadcom chipsets that does
|
||||
* the presence check by sending commands that interrupt the
|
||||
* processing of the ongoing command.
|
||||
*/
|
||||
options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, DELAY_PRESENCE)
|
||||
}
|
||||
nfcAdapter!!.enableReaderMode(activity, readerCallback, READER_FLAGS, options)
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||
private fun disableReaderMode() {
|
||||
nfcAdapter?.disableReaderMode(activity)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.card_android.android.reader
|
||||
|
||||
import android.nfc.tech.IsoDep
|
||||
|
||||
data class NfcReader(
|
||||
val nfcManager: NfcManager,
|
||||
val isoDep: IsoDep
|
||||
) : com.tangem.card_common.reader.NfcReader {
|
||||
override fun getId(): ByteArray {
|
||||
return isoDep.tag.id
|
||||
}
|
||||
|
||||
override fun setTimeout(timeout: Int) {
|
||||
isoDep.timeout = timeout
|
||||
}
|
||||
|
||||
override fun getTimeout(): Int {
|
||||
return isoDep.timeout
|
||||
}
|
||||
|
||||
override fun transceive(data: ByteArray?): ByteArray {
|
||||
return isoDep.transceive(data)
|
||||
}
|
||||
|
||||
override fun ignoreTag() {
|
||||
nfcManager.ignoreTag(isoDep.tag)
|
||||
}
|
||||
|
||||
override fun notifyReadResult(success: Boolean) {
|
||||
nfcManager.notifyReadResult(success)
|
||||
}
|
||||
|
||||
override fun connect() {
|
||||
val timeout = isoDep.timeout
|
||||
isoDep.connect()
|
||||
isoDep.close()
|
||||
isoDep.connect()
|
||||
isoDep.timeout = timeout
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.card_android.android.ui
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
|
||||
import com.tangem.card_android.R
|
||||
|
||||
class NfcEnableDialog : DialogFragment() {
|
||||
companion object {
|
||||
val TAG: String = NfcEnableDialog::class.java.simpleName
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val builder = context?.let { AlertDialog.Builder(it) }
|
||||
builder?.setCancelable(false)
|
||||
?.setIcon(R.drawable.ic_action_nfc_gray)
|
||||
?.setTitle(R.string.nfc_disabled)
|
||||
?.setMessage(R.string.enable_nfc)
|
||||
?.setPositiveButton(R.string.dialog_ok
|
||||
) { _, _ ->
|
||||
activity?.startActivity(Intent(Settings.ACTION_NFC_SETTINGS))
|
||||
}
|
||||
?.setNegativeButton(R.string.dialog_quit
|
||||
) { dialog, _ ->
|
||||
dialog.cancel()
|
||||
activity?.finish()
|
||||
}
|
||||
return builder!!.create()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
package com.tangem.card_android.data
|
||||
|
||||
import android.os.Bundle
|
||||
import com.tangem.card_common.data.Manufacturer
|
||||
import com.tangem.card_common.data.TangemCard
|
||||
import com.tangem.card_common.util.Log
|
||||
import java.util.*
|
||||
|
||||
const val EXTRA_TANGEM_CARD = "Card"
|
||||
const val EXTRA_TANGEM_CARD_UID = "UID"
|
||||
|
||||
fun TangemCard.loadFromBundle(B: Bundle) {
|
||||
uid = B.getString("UID")
|
||||
cid = B.getByteArray("CID")
|
||||
pin = B.getString("PIN")
|
||||
PIN2 = TangemCard.PIN2_Mode.valueOf(B.getString("PIN2")!!)
|
||||
status = TangemCard.Status.valueOf(B.getString("Status")!!)
|
||||
blockchainID = B.getString("Blockchain")
|
||||
tokensDecimal = B.getInt("TokensDecimal", 18)
|
||||
tokenSymbol = B.getString("TokenSymbol", "")
|
||||
contractAddress = B.getString("ContractAddress", "")
|
||||
// if (B.containsKey("BlockchainName"))
|
||||
// blockchainName = B.getString("BlockchainName", "");
|
||||
if (B.containsKey("dtPersonalization")) {
|
||||
personalizationDateTime = Date(B.getLong("dtPersonalization"))
|
||||
}
|
||||
remainingSignatures = B.getInt("RemainingSignatures")
|
||||
maxSignatures = B.getInt("MaxSignatures")
|
||||
health = B.getInt("health")
|
||||
if (B.containsKey("settingsMask")) settingsMask = B.getInt("settingsMask")
|
||||
pauseBeforePIN2 = B.getInt("pauseBeforePIN2")
|
||||
if (B.containsKey("signingMethod"))
|
||||
setSigningMethod(B.getInt("signingMethod"))
|
||||
if (B.containsKey("Manufacturer"))
|
||||
setManufacturer(Manufacturer.valueOf(B.getString("Manufacturer")!!), B.getBoolean("ManufacturerConfirmed", false))
|
||||
if (B.containsKey("EncryptionMode"))
|
||||
encryptionMode = TangemCard.EncryptionMode.valueOf(B.getString("EncryptionMode")!!)
|
||||
else
|
||||
encryptionMode = null
|
||||
|
||||
if (B.containsKey("SignedHashes")) signedHashes = B.getInt("SignedHashes")
|
||||
|
||||
if (B.containsKey("Issuer")) setIssuer(B.getString("Issuer"), B.getByteArray("IssuerPublicDataKey"))
|
||||
|
||||
if (B.containsKey("FirmwareVersion")) firmwareVersion = B.getString("FirmwareVersion")
|
||||
if (B.containsKey("Batch")) batch = B.getString("Batch")
|
||||
|
||||
isCardPublicKeyValid = B.getBoolean("CardPublicKeyValid")
|
||||
if (B.containsKey("CardPublicKey")) cardPublicKey = B.getByteArray("CardPublicKey")
|
||||
|
||||
if (B.containsKey("OfflineBalance")) offlineBalance = B.getByteArray("OfflineBalance")
|
||||
else clearOfflineBalance()
|
||||
|
||||
if (B.containsKey("Denomination") && B.containsKey("DenominationText")) {
|
||||
setDenomination(B.getByteArray("Denomination"), B.getString("DenominationText"))
|
||||
} else if (B.containsKey("Denomination")) {
|
||||
setDenomination(B.getByteArray("Denomination"), "N/A")
|
||||
} else clearDenomination()
|
||||
|
||||
if (B.containsKey("IssuerData") && B.containsKey("IssuerDataSignature"))
|
||||
setIssuerData(B.getByteArray("IssuerData"), B.getByteArray("IssuerDataSignature"))
|
||||
else setIssuerData(null, null)
|
||||
|
||||
if (B.containsKey("NeedWriteIssuerData"))
|
||||
needWriteIssuerData = B.getBoolean("NeedWriteIssuerData")
|
||||
|
||||
isWalletPublicKeyValid = B.getBoolean("WalletPublicKeyValid")
|
||||
if (B.containsKey("PublicKey")) {
|
||||
walletPublicKey = B.getByteArray("PublicKey")
|
||||
}
|
||||
if (B.containsKey("PublicKeyRar")) {
|
||||
walletPublicKeyRar = B.getByteArray("PublicKeyRar")
|
||||
}
|
||||
|
||||
if (B.containsKey("codeConfirmed"))
|
||||
isCodeConfirmed = B.getBoolean("codeConfirmed")
|
||||
|
||||
if (B.containsKey("onlineVerified"))
|
||||
isOnlineVerified = B.getBoolean("onlineVerified")
|
||||
|
||||
if (B.containsKey("onlineValidated"))
|
||||
isOnlineValidated = B.getBoolean("onlineValidated")
|
||||
|
||||
}
|
||||
|
||||
val TangemCard.asBundle: Bundle
|
||||
get() {
|
||||
val bundle = Bundle()
|
||||
saveToBundle(bundle)
|
||||
return bundle
|
||||
}
|
||||
|
||||
fun TangemCard.saveToBundle(B: Bundle) {
|
||||
try {
|
||||
B.putString("UID", uid)
|
||||
B.putByteArray("CID", cid)
|
||||
B.putString("PIN", pin)
|
||||
B.putString("PIN2", PIN2.name)
|
||||
B.putString("Status", status.name)
|
||||
B.putString("Blockchain", blockchainID)
|
||||
B.putInt("TokensDecimal", tokensDecimal)
|
||||
B.putString("TokenSymbol", tokenSymbol)
|
||||
B.putString("ContractAddress", contractAddress)
|
||||
if (personalizationDateTime != null) B.putLong("dtPersonalization", personalizationDateTime.time)
|
||||
|
||||
B.putInt("RemainingSignatures", remainingSignatures)
|
||||
B.putInt("MaxSignatures", maxSignatures)
|
||||
B.putInt("Health", health)
|
||||
if (settingsMask != null) B.putInt("settingsMask", settingsMask)
|
||||
B.putInt("pauseBeforePIN2", pauseBeforePIN2)
|
||||
if( allowedSigningMethod!=null ){
|
||||
var iSigningMethod=0x80
|
||||
for(sM in allowedSigningMethod)
|
||||
{
|
||||
iSigningMethod=iSigningMethod.or(0x01.shl(sM.ID))
|
||||
}
|
||||
B.putInt("signingMethod", iSigningMethod)
|
||||
}
|
||||
if (manufacturer != null) B.putString("Manufacturer", manufacturer.name)
|
||||
if (encryptionMode != null) B.putString("EncryptionMode", encryptionMode.name)
|
||||
if (issuer != null) B.putString("Issuer", issuer.getID())
|
||||
if (issuerPublicDataKey != null) B.putByteArray("IssuerPublicDataKey", issuerPublicDataKey)
|
||||
if (firmwareVersion != null) B.putString("FirmwareVersion", firmwareVersion)
|
||||
if (batch != null) B.putString("Batch", batch)
|
||||
B.putBoolean("ManufacturerConfirmed", isManufacturerConfirmed)
|
||||
B.putBoolean("CardPublicKeyValid", isCardPublicKeyValid)
|
||||
B.putByteArray("CardPublicKey", cardPublicKey)
|
||||
|
||||
B.putInt("SignedHashes", signedHashes)
|
||||
B.putBoolean("WalletPublicKeyValid", isWalletPublicKeyValid)
|
||||
if (walletPublicKey != null)
|
||||
B.putByteArray("PublicKey", walletPublicKey)
|
||||
if (walletPublicKeyRar != null)
|
||||
B.putByteArray("PublicKeyRar", walletPublicKeyRar)
|
||||
|
||||
if (offlineBalance != null) B.putByteArray("OfflineBalance", offlineBalance)
|
||||
|
||||
if (denomination != null) B.putByteArray("Denomination", denomination)
|
||||
if (denominationText != null) B.putString("DenominationText", denominationText)
|
||||
|
||||
if (issuerData != null && issuerDataSignature != null) {
|
||||
B.putByteArray("IssuerData", issuerData)
|
||||
B.putByteArray("IssuerDataSignature", issuerDataSignature)
|
||||
B.putBoolean("NeedWriteIssuerData", needWriteIssuerData)
|
||||
}
|
||||
|
||||
if (isCodeConfirmed != null)
|
||||
B.putBoolean("codeConfirmed", isCodeConfirmed)
|
||||
|
||||
if (isOnlineVerified != null)
|
||||
B.putBoolean("onlineVerified", isOnlineVerified)
|
||||
|
||||
if (isOnlineValidated != null)
|
||||
B.putBoolean("onlineValidated", isOnlineValidated)
|
||||
} catch (e: Exception) {
|
||||
Log.e("Can't save to bundle ", e.message)
|
||||
}
|
||||
|
||||
}
|
||||
BIN
card-android/src/main/res/drawable/card_default.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
card-android/src/main/res/drawable/hand_card_horizontal.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
card-android/src/main/res/drawable/hand_card_vertical.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
card-android/src/main/res/drawable/ic_action_nfc_gray.png
Normal file
|
After Width: | Height: | Size: 536 B |
BIN
card-android/src/main/res/drawable/ic_logo_bat_token.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
card-android/src/main/res/drawable/ic_logo_bitcoin.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
card-android/src/main/res/drawable/ic_logo_bitcoin_cash.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
card-android/src/main/res/drawable/ic_logo_bitcoin_testnet.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
card-android/src/main/res/drawable/ic_logo_ethereum.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
card-android/src/main/res/drawable/ic_logo_ethereum_testnet.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
card-android/src/main/res/drawable/ic_logo_seed.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
card-android/src/main/res/drawable/ic_logo_stellar.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
card-android/src/main/res/drawable/ic_logo_unknown.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
card-android/src/main/res/drawable/tangem2.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
9
card-android/src/main/res/values/strings.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<resources>
|
||||
|
||||
<string name="app_name">tangemcard</string>
|
||||
<string name="dialog_ok">OK</string>
|
||||
<string name="dialog_quit">Quit</string>
|
||||
<string name="nfc_disabled">Enable NFC?</string>
|
||||
<string name="enable_nfc">This app is useless when NFC is disabled. Reader mode depends on it. Hit OK to go to Settings, where you can enable NFC.</string>
|
||||
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.card_android;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
@Test
|
||||
public void addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2);
|
||||
}
|
||||
}
|
||||