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 class Method {
public static final String VERIFY = URL_TANGEM + "verify"; 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 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 * HTTP
* Card verify * Card verify
*/ */
private CardVerifyListener cardVerifyListener; // private CardVerifyListener cardVerifyListener;
//
public interface CardVerifyListener { // public interface CardVerifyListener {
void onCardVerify(CardVerifyResponse cardVerifyResponse); // void onCardVerify(CardVerifyResponse cardVerifyResponse);
} // }
//
public void setCardVerify(CardVerifyListener listener) { // public void setCardVerify(CardVerifyListener listener) {
cardVerifyListener = listener; // cardVerifyListener = listener;
} // }
//
public void cardVerify(TangemCard card) { // public void cardVerify(TangemCard card) {
Retrofit retrofit = new Retrofit.Builder() // Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Server.ApiTangem.URL_TANGEM) // .baseUrl(Server.ApiTangem.URL_TANGEM)
.addConverterFactory(GsonConverterFactory.create()) // .addConverterFactory(GsonConverterFactory.create())
.build(); // .build();
//
TangemApi tangemApi = retrofit.create(TangemApi.class); // TangemApi tangemApi = retrofit.create(TangemApi.class);
//
CardVerify[] requests = new CardVerify[1]; // CardVerify[] requests = new CardVerify[1];
requests[0] = new CardVerify(Util.bytesToHex(card.getCID()), Util.bytesToHex(card.getCardPublicKey())); // requests[0] = new CardVerify(Util.bytesToHex(card.getCID()), Util.bytesToHex(card.getCardPublicKey()));
//
CardVerifyBody cardVerifyBody = new CardVerifyBody(requests); // CardVerifyBody cardVerifyBody = new CardVerifyBody(requests);
//
Call<CardVerifyResponse> call = tangemApi.getCardVerify(cardVerifyBody); // Call<CardVerifyResponse> call = tangemApi.getCardVerify(cardVerifyBody);
call.enqueue(new Callback<CardVerifyResponse>() { // call.enqueue(new Callback<CardVerifyResponse>() {
@Override // @Override
public void onResponse(@NonNull Call<CardVerifyResponse> call, @NonNull Response<CardVerifyResponse> response) { // public void onResponse(@NonNull Call<CardVerifyResponse> call, @NonNull Response<CardVerifyResponse> response) {
if (response.code() == 200) { // if (response.code() == 200) {
CardVerifyResponse cardVerifyResponse = response.body(); // CardVerifyResponse cardVerifyResponse = response.body();
cardVerifyListener.onCardVerify(cardVerifyResponse); // cardVerifyListener.onCardVerify(cardVerifyResponse);
Log.i(TAG, "cardVerify onResponse " + response.code()); // Log.i(TAG, "cardVerify onResponse " + response.code());
} else // } else
Log.e(TAG, "cardVerify onResponse " + response.code()); // Log.e(TAG, "cardVerify onResponse " + response.code());
} // }
//
@Override // @Override
public void onFailure(@NonNull Call<CardVerifyResponse> call, @NonNull Throwable t) { // public void onFailure(@NonNull Call<CardVerifyResponse> call, @NonNull Throwable t) {
Log.e(TAG, "cardVerify onFailure " + t.getMessage()); // Log.e(TAG, "cardVerify onFailure " + t.getMessage());
} // }
}); // });
} // }
/** /**
@ -259,8 +259,8 @@ public class ServerApiHelper {
TangemApi tangemApi = retrofit.create(TangemApi.class); TangemApi tangemApi = retrofit.create(TangemApi.class);
List<CardVerifyAndGetArtwork.Request.RequestItem> requests = new ArrayList<>(); List<CardVerifyAndGetArtwork.Request.Item> requests = new ArrayList<>();
requests.add(new CardVerifyAndGetArtwork.Request.RequestItem(Util.bytesToHex(card.getCID()), Util.bytesToHex(card.getCardPublicKey()),null)); requests.add(new CardVerifyAndGetArtwork.Request.Item(Util.bytesToHex(card.getCID()), Util.bytesToHex(card.getCardPublicKey())));
CardVerifyAndGetArtwork.Request requestBody = new CardVerifyAndGetArtwork.Request(requests); 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 * HTTP

View file

@ -1,7 +1,7 @@
package com.tangem.data.network; package com.tangem.data.network;
public class ServerURL { 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_COINMARKETCAP = "https://api.coinmarketcap.com/";
public static final String API_INFURA = "https://mainnet.infura.io/"; public static final String API_INFURA = "https://mainnet.infura.io/";
public static final String API_ESTIMATEFEE = " https://estimatefee.com/"; 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.CardVerifyBody;
import com.tangem.data.network.model.CardVerifyResponse; import com.tangem.data.network.model.CardVerifyResponse;
import okhttp3.ResponseBody;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.http.Body; import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.Headers; import retrofit2.http.Headers;
import retrofit2.http.POST; import retrofit2.http.POST;
import retrofit2.http.Query;
public interface TangemApi { public interface TangemApi {
@Headers("Content-Type: application/json") @Headers("Content-Type: application/json")
@ -18,4 +21,8 @@ public interface TangemApi {
@POST(Server.ApiTangem.Method.VERIFY_AND_GET_ARTWORK) @POST(Server.ApiTangem.Method.VERIFY_AND_GET_ARTWORK)
Call<CardVerifyAndGetArtwork.Response> getCardVerifyAndGetArtwork(@Body CardVerifyAndGetArtwork.Request requestBody); 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 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 class CardVerifyAndGetArtwork {
{ data class Request(
var requests: List<Item>? = null
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
) { ) {
data class ResultItem( data class Item(
@SerializedName("error")
var error: String = "",
@SerializedName("CID")
var CID: String = "", var CID: String = "",
var publicKey: String = ""
@SerializedName("passed")
var passed: Boolean = false,
@SerializedName("artwork")
var artwork: String = "",
@SerializedName("batch")
var batch: String = "",
@SerializedName("update_date")
var update_date: 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 android.widget.Toast
import com.google.zxing.WriterException import com.google.zxing.WriterException
import com.tangem.data.network.ServerApiHelper 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.model.InfuraResponse
import com.tangem.data.network.request.ElectrumRequest import com.tangem.data.network.request.ElectrumRequest
import com.tangem.data.network.request.InfuraRequest import com.tangem.data.network.request.InfuraRequest
@ -79,6 +80,8 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific
private var timerRepeatRefresh: Timer? = null private var timerRepeatRefresh: Timer? = null
private val artworksStorage: ArtworksStorage = ArtworksStorage(context!!)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
nfcManager = NfcManager(activity, this) nfcManager = NfcManager(activity, this)
@ -105,7 +108,8 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific
if (card!!.blockchain == Blockchain.Token) if (card!!.blockchain == Blockchain.Token)
tvBalance.setSingleLine(false) tvBalance.setSingleLine(false)
ivTangemCard.setImageResource(card!!.cardImageResource) //ivTangemCard.setImageResource(card!!.cardImageResource)
ivTangemCard.setImageBitmap(artworksStorage.getBatchBitmap(card!!.batch))
val engine = CoinEngineFactory.create(card!!.blockchain) val engine = CoinEngineFactory.create(card!!.blockchain)
val visibleFlag = engine?.inOutPutVisible() ?: true val visibleFlag = engine?.inOutPutVisible() ?: true
@ -215,13 +219,25 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific
} }
// request card verify listener // 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 card!!.isOnlineVerified = it.results!![0].passed
srlLoadedWallet!!.isRefreshing = false 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) // Log.i(TAG, "setCardVerify " + it.results!![0].passed)
} }
// request rate info listener // request rate info listener
serverApiHelper!!.setRateInfoData { serverApiHelper!!.setRateInfoData {
val rate = it.priceUsd.toFloat() val rate = it.priceUsd.toFloat()
@ -343,7 +359,7 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific
private fun requestCardVerify() { private fun requestCardVerify() {
if ((card!!.isOnlineVerified == null || !card!!.isOnlineVerified)) if ((card!!.isOnlineVerified == null || !card!!.isOnlineVerified))
serverApiHelper!!.cardVerify(card) serverApiHelper!!.cardVerifyAndGetArtwork(card) //serverApiHelper!!.cardVerify(card)
} }
private fun requestRateInfo(cryptoId: String) { private fun requestRateInfo(cryptoId: String) {