Updated on 2026-08-14

This commit is contained in:
Tangem 2018-09-16 13:53:14 +03:00
parent 1c7abe647d
commit 5f3ff0f09f
7 changed files with 356 additions and 89 deletions

View file

@ -12,6 +12,7 @@ public class Server {
public static class Method {
public static final String VERIFY = URL_TANGEM + "verify";
public static final String VERIFY_AND_GET_ARTWORK = URL_TANGEM + "verify-and-get-artwork";
public static final String ARTWORK = URL_TANGEM + "artwork";
}
}

View file

@ -194,47 +194,47 @@ public class ServerApiHelper {
* HTTP
* Card verify
*/
private CardVerifyListener cardVerifyListener;
public interface CardVerifyListener {
void onCardVerify(CardVerifyResponse cardVerifyResponse);
}
public void setCardVerify(CardVerifyListener listener) {
cardVerifyListener = listener;
}
public void cardVerify(TangemCard card) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Server.ApiTangem.URL_TANGEM)
.addConverterFactory(GsonConverterFactory.create())
.build();
TangemApi tangemApi = retrofit.create(TangemApi.class);
CardVerify[] requests = new CardVerify[1];
requests[0] = new CardVerify(Util.bytesToHex(card.getCID()), Util.bytesToHex(card.getCardPublicKey()));
CardVerifyBody cardVerifyBody = new CardVerifyBody(requests);
Call<CardVerifyResponse> call = tangemApi.getCardVerify(cardVerifyBody);
call.enqueue(new Callback<CardVerifyResponse>() {
@Override
public void onResponse(@NonNull Call<CardVerifyResponse> call, @NonNull Response<CardVerifyResponse> response) {
if (response.code() == 200) {
CardVerifyResponse cardVerifyResponse = response.body();
cardVerifyListener.onCardVerify(cardVerifyResponse);
Log.i(TAG, "cardVerify onResponse " + response.code());
} else
Log.e(TAG, "cardVerify onResponse " + response.code());
}
@Override
public void onFailure(@NonNull Call<CardVerifyResponse> call, @NonNull Throwable t) {
Log.e(TAG, "cardVerify onFailure " + t.getMessage());
}
});
}
// private CardVerifyListener cardVerifyListener;
//
// public interface CardVerifyListener {
// void onCardVerify(CardVerifyResponse cardVerifyResponse);
// }
//
// public void setCardVerify(CardVerifyListener listener) {
// cardVerifyListener = listener;
// }
//
// public void cardVerify(TangemCard card) {
// Retrofit retrofit = new Retrofit.Builder()
// .baseUrl(Server.ApiTangem.URL_TANGEM)
// .addConverterFactory(GsonConverterFactory.create())
// .build();
//
// TangemApi tangemApi = retrofit.create(TangemApi.class);
//
// CardVerify[] requests = new CardVerify[1];
// requests[0] = new CardVerify(Util.bytesToHex(card.getCID()), Util.bytesToHex(card.getCardPublicKey()));
//
// CardVerifyBody cardVerifyBody = new CardVerifyBody(requests);
//
// Call<CardVerifyResponse> call = tangemApi.getCardVerify(cardVerifyBody);
// call.enqueue(new Callback<CardVerifyResponse>() {
// @Override
// public void onResponse(@NonNull Call<CardVerifyResponse> call, @NonNull Response<CardVerifyResponse> response) {
// if (response.code() == 200) {
// CardVerifyResponse cardVerifyResponse = response.body();
// cardVerifyListener.onCardVerify(cardVerifyResponse);
// Log.i(TAG, "cardVerify onResponse " + response.code());
// } else
// Log.e(TAG, "cardVerify onResponse " + response.code());
// }
//
// @Override
// public void onFailure(@NonNull Call<CardVerifyResponse> call, @NonNull Throwable t) {
// Log.e(TAG, "cardVerify onFailure " + t.getMessage());
// }
// });
// }
/**
@ -259,8 +259,8 @@ public class ServerApiHelper {
TangemApi tangemApi = retrofit.create(TangemApi.class);
List<CardVerifyAndGetArtwork.Request.RequestItem> requests = new ArrayList<>();
requests.add(new CardVerifyAndGetArtwork.Request.RequestItem(Util.bytesToHex(card.getCID()), Util.bytesToHex(card.getCardPublicKey()),null));
List<CardVerifyAndGetArtwork.Request.Item> requests = new ArrayList<>();
requests.add(new CardVerifyAndGetArtwork.Request.Item(Util.bytesToHex(card.getCID()), Util.bytesToHex(card.getCardPublicKey())));
CardVerifyAndGetArtwork.Request requestBody = new CardVerifyAndGetArtwork.Request(requests);
@ -283,6 +283,62 @@ public class ServerApiHelper {
});
}
/**
* HTTP
* Last version request from GitHub
*/
private ArtworkListener artworkListener;
public interface ArtworkListener {
void onArtwork(InputStream inputStream);
}
public void setArtworkListener(ArtworkListener listener) {
artworkListener = listener;
}
public void requestArtwork() {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient httpClient = new OkHttpClient.Builder().
addInterceptor(logging).
// addInterceptor(new AuthorizationInterceptor()).
build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Server.ApiUpdateVersion.URL_UPDATE_VERSION)
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient)
.build();
UpdateVersionApi updateVersionApi = retrofit.create(UpdateVersionApi.class);
Call<ResponseBody> call = updateVersionApi.getLastVersion();
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(@NonNull Call<ResponseBody> call, @NonNull Response<ResponseBody> response) {
Log.i(TAG, "lastVersion onResponse " + response.code());
if (response.code() == 200) {
String stringResponse;
try {
stringResponse = response.body().string();
lastVersionListener.onLastVersion(stringResponse);
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void onFailure(@NonNull Call<ResponseBody> call, @NonNull Throwable t) {
Log.e(TAG, "lastVersion onFailure " + t.getMessage());
}
});
}
/**
* HTTP

View file

@ -1,7 +1,7 @@
package com.tangem.data.network;
public class ServerURL {
public static final String API_TANGEM = "https://verify.tangem.com/";
public static final String API_TANGEM = "https://tangem-services.appspot.com"; //TODO: "https://verify.tangem.com/";
public static final String API_COINMARKETCAP = "https://api.coinmarketcap.com/";
public static final String API_INFURA = "https://mainnet.infura.io/";
public static final String API_ESTIMATEFEE = " https://estimatefee.com/";

View file

@ -4,10 +4,13 @@ import com.tangem.data.network.model.CardVerifyAndGetArtwork;
import com.tangem.data.network.model.CardVerifyBody;
import com.tangem.data.network.model.CardVerifyResponse;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.Headers;
import retrofit2.http.POST;
import retrofit2.http.Query;
public interface TangemApi {
@Headers("Content-Type: application/json")
@ -18,4 +21,8 @@ public interface TangemApi {
@POST(Server.ApiTangem.Method.VERIFY_AND_GET_ARTWORK)
Call<CardVerifyAndGetArtwork.Response> getCardVerifyAndGetArtwork(@Body CardVerifyAndGetArtwork.Request requestBody);
@GET(Server.ApiTangem.Method.ARTWORK)
Call<ResponseBody> getArtwork(@Query("artworkId") String artworkId, @Query("CID") String CID, @Query("publicKey") String publicKey);
}

View file

@ -1,52 +1,46 @@
package com.tangem.data.network.model
import com.google.gson.annotations.SerializedName
import android.os.Build
import java.text.SimpleDateFormat
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import java.util.*
class CardVerifyAndGetArtwork
{
data class Request(
@SerializedName("requests")
var requests: List<RequestItem>? = null
) {
data class RequestItem(
@SerializedName("CID")
var CID: String = "",
@SerializedName("publicKey")
var publicKey: String = "",
@SerializedName("artwork")
var artwork: String = ""
)
}
data class Response(
@SerializedName("results")
var results: List<ResultItem>? = null
class CardVerifyAndGetArtwork {
data class Request(
var requests: List<Item>? = null
) {
data class ResultItem(
@SerializedName("error")
var error: String = "",
@SerializedName("CID")
data class Item(
var CID: String = "",
@SerializedName("passed")
var passed: Boolean = false,
@SerializedName("artwork")
var artwork: String = "",
@SerializedName("batch")
var batch: String = "",
@SerializedName("update_date")
var update_date: String = ""
var publicKey: String = ""
)
}
data class Response(
var results: List<Item>? = null
) {
data class Item(
var error: String = "",
var CID: String = "",
var passed: Boolean = false,
var batch: String = "",
var artworkId: String = "",
var artworkHash: String = "",
var update_date: String = ""
) {
fun getUpdateDate(): Instant? {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
return LocalDateTime.parse(update_date, DateTimeFormatter.ISO_LOCAL_DATE_TIME).toInstant(ZoneOffset.UTC)
}else{
return SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US).parse(update_date).toInstant()
}
}
}
}
}

View file

@ -0,0 +1,193 @@
package com.tangem.domain.wallet
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Bitmap
import java.io.File
import kotlin.collections.HashMap
import android.graphics.BitmapFactory
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.tangem.util.Util
import com.tangem.wallet.R
import java.io.InputStream
import java.lang.Exception
import java.time.Instant
data class ArtworksStorage(
val context: Context
) {
private val cache: FileCache = FileCache(context)
private lateinit var artworks: HashMap<String, ArtworkInfo>
private lateinit var batches: HashMap<String, BatchInfo>
private var artworksFile: File = File(context.filesDir, "artworks.json")
private var batchesFile: File = File(context.filesDir, "batches.json")
init {
if (artworksFile.exists()) {
artworksFile.bufferedReader().use { artworks = Gson().fromJson(it, object : TypeToken<Map<String, ArtworkInfo>>() {}.type) }
} else {
artworks = HashMap()
putResourceArtworkToCatalog("card_default", false)
putResourceArtworkToCatalog("card_btc_001", false)
putResourceArtworkToCatalog("card_btc_005", true)
}
if (batchesFile.exists()) {
batchesFile.bufferedReader().use { batches = Gson().fromJson(it, object : TypeToken<Map<String, BatchInfo>>() {}.type) }
} else {
batches = HashMap()
putBatchToCatalog("0004", "card_btc_001", false)
putBatchToCatalog("0006", "card_btc_001", false)
putBatchToCatalog("0010", "card_btc_001", false)
putBatchToCatalog("0005", "card_btc_005", false)
putBatchToCatalog("0007", "card_btc_005", false)
putBatchToCatalog("0011", "card_btc_005", true)
// "0012": card_seed;
// "0013": card_btc_hk_s;
// "0014": card_btc_0014
// "0015": card_btc_000;
// "0016": card_eth000;
// "0017": card_qlear200;
// "0019": card_cle100;
// "001A": card_btc_001a;
// "001B": card_btc_001b;
// "001C": card_btc_001c;
// "001D": card_eth_001d;
}
}
fun checkNeedUpdateArtwork(batch: String, artworkId: String, artworkHash: String, updateDate: Instant?): Boolean {
val batchInfo = batches[batch]
if (batchInfo == null) {
putBatchToCatalog(batch, artworkId)
return true
}
if (batchInfo.artworkId != artworkId) {
putBatchToCatalog(batch, artworkId)
}
val artwork = artworks[artworkId] ?: return true
if (artwork.hash == artworkHash) return false
if (updateDate == null) return false
return artwork.updateDate == null || artwork.updateDate < updateDate
}
fun updateArtwork(artworkId: String, inputStream: InputStream, updateDate: Instant) {
val data = inputStream.readBytes()
cache.saveBitmap(artworkId, data)
putArtworkToCatalog(artworkId, data, updateDate, true)
}
private fun putBatchToCatalog(batch: String, artworkId: String, forceSave: Boolean = true) {
batches[batch] = BatchInfo(artworkId)
if (forceSave) {
val sBatches = Gson().toJson(batches)
batchesFile.bufferedWriter().use { it.write(sBatches) }
}
}
@SuppressLint("ResourceType")
private fun putResourceArtworkToCatalog(artworkId: String, forceSave: Boolean) {
context.resources.openRawResource(R.drawable.card_default).use { putArtworkToCatalog(artworkId, it.readBytes(), null, forceSave) }
}
private fun putArtworkToCatalog(artworkId: String, data: ByteArray, instant: Instant?, forceSave: Boolean = true) {
val artworkInfo = ArtworkInfo(
false, Util.bytesToHex(Util.calculateSHA256(data)), instant
)
artworks[artworkId] = artworkInfo
if (forceSave) {
val sArtworks = Gson().toJson(artworks)
artworksFile.bufferedWriter().use { it.write(sArtworks) }
}
}
private fun getArtworkBitmap(artworkId: String): Bitmap? {
val info = artworks[artworkId] ?: return null
return if (info.isResource) {
val resID = context.resources.getIdentifier(artworkId, "drawable", context.packageName)
if (resID == 0) return null
BitmapFactory.decodeResource(context.resources, resID)
} else {
cache.getBitmap(artworkId)
}
}
private fun getDefaultArtworkBitmap(): Bitmap {
val info = artworks[defaultArtworkId]
var bitmap: Bitmap? = null
if (info != null && !info.isResource) {
bitmap = cache.getBitmap(defaultArtworkId)
}
if (bitmap == null) return BitmapFactory.decodeResource(context.resources, R.drawable.card_default)
return bitmap
}
fun getBatchBitmap(batch: String): Bitmap {
val batchInfo = batches[batch] ?: return getDefaultArtworkBitmap()
return getArtworkBitmap(batchInfo.artworkId) ?: return getDefaultArtworkBitmap()
}
data class ArtworkInfo(
val isResource: Boolean,
val hash: String,
val updateDate: Instant?
)
data class BatchInfo(
val artworkId: String
)
inner class FileCache(context: Context) {
private var cacheDir: File? = null
init {
cacheDir = File(context.filesDir, "artworks")
//Find the dir to save cached images
// if (android.os.Environment.getExternalStorageState() == android.os.Environment.MEDIA_MOUNTED)
// cacheDir = File(android.os.Environment.getExternalStorageDirectory(), "artworks_cache")
// else
// cacheDir = context.getCacheDir()
if (!cacheDir!!.exists())
cacheDir!!.mkdirs()
}
private fun getFile(artworkId: String): File {
return File(cacheDir, "$artworkId.png")
}
fun getBitmap(artworkId: String): Bitmap? {
return try {
BitmapFactory.decodeFile(getFile(artworkId).absolutePath)
} catch (E: Exception) {
E.printStackTrace()
null
}
}
fun saveBitmap(artworkId: String, data: ByteArray) {
val file = getFile(artworkId)
file.writeBytes(data)
}
fun clear() {
val files = cacheDir!!.listFiles() ?: return
for (f in files)
f.delete()
}
}
companion object {
const val defaultArtworkId = ""
}
}

View file

@ -23,6 +23,7 @@ import android.view.ViewGroup
import android.widget.Toast
import com.google.zxing.WriterException
import com.tangem.data.network.ServerApiHelper
import com.tangem.data.network.model.CardVerifyAndGetArtwork
import com.tangem.data.network.model.InfuraResponse
import com.tangem.data.network.request.ElectrumRequest
import com.tangem.data.network.request.InfuraRequest
@ -79,6 +80,8 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific
private var timerRepeatRefresh: Timer? = null
private val artworksStorage: ArtworksStorage = ArtworksStorage(context!!)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
nfcManager = NfcManager(activity, this)
@ -105,7 +108,8 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific
if (card!!.blockchain == Blockchain.Token)
tvBalance.setSingleLine(false)
ivTangemCard.setImageResource(card!!.cardImageResource)
//ivTangemCard.setImageResource(card!!.cardImageResource)
ivTangemCard.setImageBitmap(artworksStorage.getBatchBitmap(card!!.batch))
val engine = CoinEngineFactory.create(card!!.blockchain)
val visibleFlag = engine?.inOutPutVisible() ?: true
@ -215,13 +219,25 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific
}
// request card verify listener
serverApiHelper!!.setCardVerify {
// serverApiHelper!!.setCardVerify {
// card!!.isOnlineVerified = it.results!![0].passed
// srlLoadedWallet!!.isRefreshing = false
//
//// Log.i(TAG, "setCardVerify " + it.results!![0].passed)
// }
serverApiHelper!!.setCardVerifyAndGetArtworkListener {
card!!.isOnlineVerified = it.results!![0].passed
srlLoadedWallet!!.isRefreshing = false
val result=it.results!![0]
if( artworksStorage.checkNeedUpdateArtwork(result.batch, result.artworkId, result.artworkHash, result.getUpdateDate() ) )
{
}
// Log.i(TAG, "setCardVerify " + it.results!![0].passed)
}
// request rate info listener
serverApiHelper!!.setRateInfoData {
val rate = it.priceUsd.toFloat()
@ -343,7 +359,7 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific
private fun requestCardVerify() {
if ((card!!.isOnlineVerified == null || !card!!.isOnlineVerified))
serverApiHelper!!.cardVerify(card)
serverApiHelper!!.cardVerifyAndGetArtwork(card) //serverApiHelper!!.cardVerify(card)
}
private fun requestRateInfo(cryptoId: String) {