Updated on 2026-08-14

This commit is contained in:
Tangem 2019-06-13 17:23:45 +03:00
commit bd8d84a85a
680 changed files with 51751 additions and 6799 deletions

View file

@ -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());
}
}

View 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>

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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
}
}
}

View file

@ -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()
}
}

View file

@ -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),
}

View file

@ -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)
}
}

View file

@ -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
}
}

View file

@ -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()
}
}

View file

@ -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)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 536 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View 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>

View file

@ -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);
}
}