Updated on 2026-08-14

This commit is contained in:
Tangem 2019-11-01 10:53:51 +03:00
parent 005f7295b5
commit e82a584e8b
15 changed files with 272 additions and 144 deletions

View file

@ -38,9 +38,9 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.material:material:1.1.0-beta01'
implementation 'com.skyfishjy.ripplebackground:library:1.0.1'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.core:core-ktx:1.1.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
implementation "androidx.lifecycle:lifecycle-runtime:2.1.0"
implementation "androidx.lifecycle:lifecycle-common-java8:2.1.0"

View file

@ -1,66 +1,117 @@
package com.tangem.tangem_sdk_new
import android.view.View
import androidx.fragment.app.FragmentActivity
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.tangem.CardManagerDelegate
import com.tangem.Log
import com.tangem.tasks.Task
import com.tangem.LoggerInterface
import com.tangem.common.CompletionResult
import com.tangem.tangem_sdk_new.nfc.NfcReader
import com.tangem.tangem_sdk_new.ui.NfcEnableDialog
import com.tangem.tangem_sdk_new.ui.TouchCardAnimation
import com.tangem.tasks.TaskError
import kotlinx.android.synthetic.main.layout_touch_card.*
import kotlinx.android.synthetic.main.nfc_bottom_sheet.*
class DefaultCardManagerDelegate(private val activity: FragmentActivity, private val reader: NfcReader) : CardManagerDelegate {
class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDelegate {
private var dialog: BottomSheetDialog? = null
lateinit var activity: FragmentActivity
private var readingDialog: BottomSheetDialog? = null
private var nfcEnableDialog: NfcEnableDialog? = null
private var task: Task<*>? = null
override fun showSecurityDelay(ms: Int) {
// activity.runOnUiThread{
// activity.tv_security_delay?.text = "Security delay. $ms left."
// }
init {
setLogger()
}
override fun requestPin(success: () -> String, error: (cardError: TaskError.CardError) -> TaskError.CardError) {
override fun onTaskStarted() {
// reader.isoDep = null
reader.readingCancelled = false
postUI { showReadingDialog(activity) }
if (!reader.nfcEnabled) showNFCEnableDialog()
}
private fun showReadingDialog(activity: FragmentActivity) {
val dialogView = activity.getLayoutInflater().inflate(R.layout.nfc_bottom_sheet, null)
readingDialog = BottomSheetDialog(activity)
readingDialog?.setContentView(dialogView)
readingDialog?.dismissWithAnimation = true
readingDialog?.create()
readingDialog?.setOnShowListener {
readingDialog?.rippleBackgroundNfc?.startRippleAnimation()
val nfcDeviceAntenna = TouchCardAnimation(
activity, readingDialog!!.ivHandCardHorizontal,
readingDialog!!.ivHandCardVertical, readingDialog!!.llHand, readingDialog!!.llNfc)
nfcDeviceAntenna.init()
}
readingDialog?.setOnCancelListener {
reader.readingCancelled = true
// reader.closeSession()
Log.i(this::class.simpleName!!, "readingCancelled is set to true")
}
readingDialog?.show()
}
private fun showNFCEnableDialog() {
nfcEnableDialog = NfcEnableDialog()
activity.supportFragmentManager.let { nfcEnableDialog?.show(it, NfcEnableDialog.TAG) }
}
override fun showSecurityDelay(ms: Int) {
postUI {
readingDialog?.lTouchCard?.visibility = View.GONE
readingDialog?.tvRemainingTime?.text = ms.div(100).toString()
readingDialog?.flSecurityDelay?.visibility = View.VISIBLE
readingDialog?.tvTaskTitle?.text = activity.getText(R.string.dialog_security_delay)
readingDialog?.tvTaskText?.text =
activity.getText(R.string.dialog_security_delay_description)
}
}
override fun onTaskCompleted() {
reader.closeSession()
postUI {
readingDialog?.lTouchCard?.visibility = View.GONE
readingDialog?.flSecurityDelay?.visibility = View.GONE
readingDialog?.flCompletion?.visibility = View.VISIBLE
readingDialog?.ivCompletion?.setImageDrawable(activity.getDrawable(R.drawable.ic_done_135dp))
}
postUI(300) { readingDialog?.dismiss() }
}
override fun onTaskError(error: TaskError?) {
reader.closeSession()
postUI {
readingDialog?.lTouchCard?.visibility = View.GONE
readingDialog?.flSecurityDelay?.visibility = View.GONE
readingDialog?.flCompletion?.visibility = View.VISIBLE
readingDialog?.ivCompletion?.setImageDrawable(activity.getDrawable(R.drawable.ic_error_outline_135dp))
readingDialog?.tvTaskTitle?.text = activity.getText(R.string.dialog_error)
readingDialog?.tvTaskText?.text = if (error != null) error::class.simpleName else ""
}
}
override fun requestPin(callback: (result: CompletionResult<String>) -> Unit) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun openNfcPopup() {
private fun setLogger() {
Log.setLogger(
object : LoggerInterface {
override fun i(logTag: String, message: String) {
android.util.Log.i(logTag, message)
}
activity.runOnUiThread {
val dialogView = activity.getLayoutInflater().inflate(R.layout.nfc_bottom_sheet, null)
dialog = BottomSheetDialog(activity)
dialog?.setContentView(dialogView)
dialog?.dismissWithAnimation
dialog?.create()
// .setTitle("NFC reader mode")
dialog?.setOnShowListener {
dialog!!.rippleBackgroundNfc?.startRippleAnimation()
// init NFC Antenna
val nfcDeviceAntenna = TouchCardAnimation(
activity, dialog!!.ivHandCardHorizontal,
dialog!!.ivHandCardVertical, dialog!!.llHand, dialog!!.llNfc)
nfcDeviceAntenna.init()
}
dialog?.setOnDismissListener {
reader.readingCancelled = false
task = null
Log.i(this::class.simpleName!!, "readingCancelled is set to true")
}
dialog?.show()
}
Log.i(this::class.simpleName!!, "Nfc PopUp is opened")
}
override fun closeNfcPopup() {
task = null
activity.runOnUiThread {
dialog?.dismiss()
}
Log.i(this::class.simpleName!!, "Nfc PopUp is closed")
override fun e(logTag: String, message: String) {
android.util.Log.e(logTag, message)
}
override fun v(logTag: String, message: String) {
android.util.Log.v(logTag, message)
}
}
)
}
}

View file

@ -1,28 +1,26 @@
package com.tangem.tangem_sdk_new
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent
import com.tangem.tangem_sdk_new.nfc.NfcManager
class NfcLifecycleObserver(private var nfcManager: NfcManager) : DefaultLifecycleObserver {
class NfcLifecycleObserver(private var nfcManager: NfcManager) : LifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onResume(owner: LifecycleOwner) {
nfcManager.onResume()
}
override fun onPause(owner: LifecycleOwner) {
super.onPause(owner)
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun onPause(owner: LifecycleOwner) {
nfcManager.onPause()
}
override fun onStop(owner: LifecycleOwner) {
super.onStop(owner)
nfcManager.onStop()
}
override fun onDestroy(owner: LifecycleOwner) {
super.onDestroy(owner)
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy(owner: LifecycleOwner) {
nfcManager.onDestroy()
}
}

View file

@ -3,9 +3,8 @@ package com.tangem.tangem_sdk_new
import android.os.Handler
import android.os.Looper
val uiHandler = Handler(Looper.getMainLooper())
fun postUI(msTime: Long = 0, func: () -> Unit) {
internal fun postUI(msTime: Long = 0, func: () -> Unit) {
if (msTime > 0) uiHandler.postDelayed({ func() }, msTime) else uiHandler.post(func)
}

View file

@ -1,4 +1,4 @@
package com.tangem.tangem_sdk_new
package com.tangem.tangem_sdk_new.nfc
import android.content.BroadcastReceiver
import android.content.Context
@ -10,20 +10,17 @@ import android.nfc.tech.IsoDep
import android.os.Build
import android.os.Bundle
import androidx.fragment.app.FragmentActivity
import com.tangem.Log
class NfcManager(private val reader: NfcReader) : NfcAdapter.ReaderCallback {
class NfcManager : NfcAdapter.ReaderCallback {
val reader = NfcReader()
private var activity: FragmentActivity? = null
private var nfcAdapter: NfcAdapter? = null
private var readerCallback: NfcAdapter.ReaderCallback? = null
// private var nfcEnableDialog: NfcEnableDialog? = null
fun setCurrentActivity(activity: FragmentActivity, readerCallback: NfcAdapter.ReaderCallback) {
fun setCurrentActivity(activity: FragmentActivity) {
this.activity = activity
nfcAdapter = NfcAdapter.getDefaultAdapter(activity)
this.readerCallback = readerCallback
}
private val mBroadcastReceiver = object : BroadcastReceiver() {
@ -37,7 +34,7 @@ class NfcManager(private val reader: NfcReader) : NfcAdapter.ReaderCallback {
}
override fun onTagDiscovered(tag: Tag?) {
// reader.isoDep = IsoDep.get(tag)
Log.i(this::class.simpleName!!, "Nfc tag is discovered")
if (reader.readingActive) reader.onTagDiscovered(tag) else ignoreTag(tag)
}
@ -46,10 +43,12 @@ class NfcManager(private val reader: NfcReader) : NfcAdapter.ReaderCallback {
val filter = IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED)
activity?.registerReceiver(mBroadcastReceiver, filter)
if (nfcAdapter == null || nfcAdapter?.isEnabled != true)
// showNFCEnableDialog()
else
if ((nfcAdapter == null || nfcAdapter?.isEnabled != true)) {
reader.nfcEnabled = false
} else {
reader.nfcEnabled = true
enableReaderMode()
}
}
fun onPause() {
@ -57,31 +56,22 @@ class NfcManager(private val reader: NfcReader) : NfcAdapter.ReaderCallback {
disableReaderMode()
}
fun onStop() {
// nfcEnableDialog?.dismiss()
}
fun onDestroy() {
activity = null
nfcAdapter = null
readerCallback = null
}
// private fun showNFCEnableDialog() {
// nfcEnableDialog = NfcEnableDialog()
// activity?.supportFragmentManager?.let { nfcEnableDialog?.show(it, NfcEnableDialog.TAG) }
// }
private fun enableReaderMode() {
val options = Bundle()
nfcAdapter?.enableReaderMode(activity, readerCallback, READER_FLAGS, options)
nfcAdapter?.enableReaderMode(activity, activity as NfcAdapter.ReaderCallback, READER_FLAGS, options)
}
private fun disableReaderMode() {
nfcAdapter?.disableReaderMode(activity)
}
fun ignoreTag(tag: Tag?) {
private fun ignoreTag(tag: Tag?) {
Log.i(this::class.simpleName!!, "Nfc tag is ignored")
if (Build.VERSION.SDK_INT >= 24) {
nfcAdapter?.ignore(tag, 1500, null, null)
}

View file

@ -1,4 +1,4 @@
package com.tangem.tangem_sdk_new
package com.tangem.tangem_sdk_new.nfc
import android.nfc.Tag
import android.nfc.tech.IsoDep
@ -11,19 +11,29 @@ import com.tangem.tasks.TaskError
class NfcReader : CardReader {
override var readingActive = false
var nfcEnabled = false
var isoDep: IsoDep? = null
set(value) {
// if tag is received, call connect first before transceiving data
if (field == null) {
field = value
connect()
}
// don't reassign when there's an active tag already
if (value == null) field = value
}
override var readingActive = false
var readingCancelled = false
set(value) {
if (value) readingActive = false
field = value
}
set(value) {
field = value
if (value) {
// Stops reading and sends failure callback to a task
// if reading is cancelled (when user closes nfc bottom sheet dialog).
readingActive = false
callback?.invoke(CompletionResult.Failure(TaskError.UserCancelledError()))
}
}
var data: ByteArray? = null
var callback: ((response: CompletionResult<ResponseApdu>) -> Unit)? = null
@ -33,15 +43,16 @@ class NfcReader : CardReader {
readingCancelled = false
}
override fun closeSession() {
isoDep = null
readingActive = false
}
override fun transceiveApdu(apdu: CommandApdu, callback: (response: CompletionResult<ResponseApdu>) -> Unit) {
data = apdu.apduData
this.callback = callback
if (isoDep != null) {
try {
transceiveData()
} catch (exception: Exception) {
isoDep = null
}
transceiveData()
}
}
@ -50,11 +61,18 @@ class NfcReader : CardReader {
callback?.invoke(CompletionResult.Failure(TaskError.UserCancelledError()))
return
}
val rawResponse = isoDep?.transceive(data)
if (data == null) return
val rawResponse: ByteArray?
try {
rawResponse = isoDep?.transceive(data)
} catch (exception: Exception) {
isoDep = null
return
}
if (rawResponse != null) {
Log.i(this::class.simpleName!!, "Nfc response is received")
data = null
readingActive = false
}
rawResponse?.let { callback?.invoke(CompletionResult.Success(ResponseApdu(it))) }
}
@ -69,7 +87,7 @@ class NfcReader : CardReader {
isoDep?.connect()
isoDep?.close()
isoDep?.connect()
isoDep?.timeout = 120000
isoDep?.timeout = 240000
Log.i(this::class.simpleName!!, "Nfc session is started")
}

View file

@ -0,0 +1,30 @@
package com.tangem.tangem_sdk_new.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.tangem_sdk_new.R
class NfcEnableDialog : DialogFragment() {
companion object {
val TAG: String = NfcEnableDialog::class.java.simpleName
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(requireContext())
builder.setCancelable(false)
.setIcon(R.drawable.ic_action_nfc_gray)
.setTitle(R.string.dialog_nfc_enable_title)
.setMessage(R.string.dialog_nfc_enable_text)
.setPositiveButton(R.string.general_ok
) { _, _ ->
activity?.startActivity(Intent(Settings.ACTION_NFC_SETTINGS))
}
return builder.create()
}
}

View file

@ -1,4 +1,4 @@
package com.tangem.tangem_sdk_new
package com.tangem.tangem_sdk_new.ui
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),

View file

@ -1,4 +1,4 @@
package com.tangem.tangem_sdk_new
package com.tangem.tangem_sdk_new.ui
import android.content.Context
import android.os.Build
@ -39,7 +39,7 @@ class TouchCardAnimation(private var context: Context,
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)
val lm = dp * (47 + x * 75)
lp.topMargin = (dp * (-100 + y * 250)).toInt()
lp2.topMargin = (dp * (-125 + y * 250)).toInt()
llNfc.layoutParams = lp2
@ -91,8 +91,8 @@ class TouchCardAnimation(private var context: Context,
// set card z position
when (z) {
TouchCardAnimation.CARD_ON_BACK -> llHand.elevation = 0.0f
TouchCardAnimation.CARD_ON_FRONT -> llHand.elevation = 30.0f
CARD_ON_BACK -> llHand.elevation = 0.0f
CARD_ON_FRONT -> llHand.elevation = 30.0f
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 536 B

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="135dp"
android:height="135dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/colorPrimary"
android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z" />
</vector>

View file

@ -0,0 +1,4 @@
<vector android:height="135dp" android:viewportHeight="24.0"
android:viewportWidth="24.0" android:width="135dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/holo_red_dark" android:pathData="M11,15h2v2h-2zM11,7h2v6h-2zM11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
</vector>

View file

@ -1,11 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="400dp"
android:layout_height="250dp"
android:gravity="center_vertical|center_horizontal"
>
android:gravity="center_vertical|center_horizontal">
<LinearLayout
android:layout_width="match_parent"
@ -69,38 +67,4 @@
</androidx.appcompat.widget.LinearLayoutCompat>
<RelativeLayout
android:id="@+id/rlProgressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:elevation="10dp"
android:visibility="gone"
tools:visibility="visible">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0.75"
android:background="@android:color/white" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:alpha="1"
android:text="@string/general_status_reading"
android:textColor="@color/colorPrimary"
android:textSize="12dp"
android:textStyle="bold" />
<ProgressBar
android:layout_width="@dimen/size_progress_bar_big"
android:layout_height="@dimen/size_progress_bar_big"
android:layout_centerInParent="true"
android:alpha="1"
android:elevation="1dp" />
</RelativeLayout>
</RelativeLayout>

View file

@ -1,18 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_behavior="@string/bottom_sheet_behavior">
<TextView
android:id="@+id/tvTaskTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="24dp"
android:text="Ready to Scan"
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
<include
android:id="@+id/lTouchCard"
layout="@layout/layout_touch_card"
android:layout_width="match_parent"
android:layout_height="250dp"
android:layout_height="200dp"
android:layout_gravity="center" />
<FrameLayout
android:id="@+id/flSecurityDelay"
android:layout_width="wrap_content"
android:layout_height="200dp"
android:layout_gravity="center"
android:visibility="gone">
<TextView
android:id="@+id/tvRemainingTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textSize="40dp" />
<ProgressBar
android:id="@+id/pbSecurityDelay"
android:layout_width="135dp"
android:layout_height="135dp"
android:layout_gravity="center" />
</FrameLayout>
<FrameLayout
android:id="@+id/flCompletion"
android:layout_width="wrap_content"
android:layout_height="200dp"
android:layout_gravity="center"
android:visibility="gone">
<ImageView
android:id="@+id/ivCompletion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_done_135dp"
android:layout_gravity="center"/>
</FrameLayout>
<TextView
android:id="@+id/tvTaskText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginBottom="16dp"
android:gravity="center"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:text="\nScan card with your phone as shown above\n"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
</LinearLayout>

View file

@ -1,4 +1,10 @@
<resources>
<string name="app_name">Tangem Sdk</string>
<string name="general_status_reading">READING…</string>
<string name="general_ok">OK</string>
<string name="dialog_nfc_enable_title">Enable NFC?</string>
<string name="dialog_nfc_enable_text">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>
<string name="dialog_security_delay">Security delay</string>
<string name="dialog_security_delay_description">This banknote has enforced security delay. Please hold the banknote firmly until the operation is completed…</string>
<string name="dialog_error">Error</string>
</resources>