mirror of
https://github.com/status-im/keycard-connect.git
synced 2025-02-27 10:00:38 +00:00
refactor
This commit is contained in:
parent
ae7c937d9e
commit
4cf6fba24f
@ -1,24 +0,0 @@
|
||||
package im.status.keycard.connect
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("im.status.keycard.connect", appContext.packageName)
|
||||
}
|
||||
}
|
@ -16,7 +16,8 @@
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity android:name=".ui.InitActivity"></activity>
|
||||
<activity android:name=".ui.ChangePINActivity"></activity>
|
||||
<activity android:name=".ui.InitActivity" />
|
||||
<activity android:name=".ui.PairingActivity" />
|
||||
<activity android:name=".ui.PINActivity" />
|
||||
<activity
|
||||
|
39
app/src/main/java/im/status/keycard/connect/Registry.kt
Normal file
39
app/src/main/java/im/status/keycard/connect/Registry.kt
Normal file
@ -0,0 +1,39 @@
|
||||
package im.status.keycard.connect
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.nfc.NfcAdapter
|
||||
import im.status.keycard.android.NFCCardManager
|
||||
import im.status.keycard.connect.card.*
|
||||
import im.status.keycard.connect.data.PINCache
|
||||
import im.status.keycard.connect.data.PairingManager
|
||||
|
||||
object Registry {
|
||||
lateinit var pinCache: PINCache
|
||||
private set
|
||||
|
||||
lateinit var pairingManager: PairingManager
|
||||
private set
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
lateinit var scriptExecutor: CardScriptExecutor
|
||||
private set
|
||||
|
||||
lateinit var cardManager: NFCCardManager
|
||||
private set
|
||||
|
||||
lateinit var nfcAdapter: NfcAdapter
|
||||
private set
|
||||
|
||||
fun init(activity: Activity) {
|
||||
pairingManager = PairingManager(activity)
|
||||
pinCache = PINCache()
|
||||
|
||||
nfcAdapter = NfcAdapter.getDefaultAdapter(activity)
|
||||
scriptExecutor = CardScriptExecutor(activity)
|
||||
|
||||
cardManager = NFCCardManager()
|
||||
cardManager.setCardListener(scriptExecutor)
|
||||
cardManager.start()
|
||||
}
|
||||
}
|
@ -2,11 +2,11 @@ package im.status.keycard.connect.card
|
||||
|
||||
import android.content.Intent
|
||||
|
||||
enum class CommandResult {
|
||||
OK, CANCEL, RETRY, UX_ONGOING
|
||||
}
|
||||
|
||||
interface CardCommand {
|
||||
fun run(context: CardScriptExecutor.Context): CommandResult
|
||||
enum class Result {
|
||||
OK, CANCEL, RETRY, UX_ONGOING
|
||||
}
|
||||
|
||||
fun run(context: CardScriptExecutor.ScriptContext): Result
|
||||
fun onDataReceived(data: Intent?) {}
|
||||
}
|
@ -5,26 +5,21 @@ import android.content.Intent
|
||||
import im.status.keycard.applet.KeycardCommandSet
|
||||
import im.status.keycard.io.CardChannel
|
||||
import im.status.keycard.io.CardListener
|
||||
import java.util.*
|
||||
|
||||
class CardScriptExecutor(activity: Activity) : CardListener {
|
||||
class Context(val mainActivity: Activity) {
|
||||
var cardChannel: CardChannel? = null
|
||||
var cmdSet: KeycardCommandSet? = null
|
||||
}
|
||||
class CardScriptExecutor(private val activity: Activity) : CardListener {
|
||||
class ScriptContext(val activity: Activity, val cmdSet: KeycardCommandSet)
|
||||
|
||||
enum class State {
|
||||
READY, UX_ONGOING, RUNNING
|
||||
}
|
||||
|
||||
private var state = State.READY
|
||||
private var executionContext = Context(activity)
|
||||
private var script: List<CardCommand>? = null
|
||||
private var defaultScript: List<CardCommand>? = null
|
||||
private var waitingCmd: CardCommand? = null
|
||||
|
||||
override fun onConnected(channel: CardChannel) {
|
||||
executionContext.cardChannel = channel
|
||||
executionContext.cmdSet = KeycardCommandSet(executionContext.cardChannel)
|
||||
val executionContext = ScriptContext(activity, KeycardCommandSet(channel))
|
||||
|
||||
if (state == State.READY) {
|
||||
state = State.RUNNING
|
||||
@ -32,22 +27,20 @@ class CardScriptExecutor(activity: Activity) : CardListener {
|
||||
return
|
||||
}
|
||||
|
||||
//TODO: replace with default script
|
||||
val runningScript = script ?: LinkedList()
|
||||
val runningScript = script ?: defaultScript ?: return
|
||||
|
||||
for (cmd in runningScript) {
|
||||
when (cmd.run(executionContext)) {
|
||||
CommandResult.OK -> {}
|
||||
CommandResult.CANCEL -> { state = State.READY; return }
|
||||
CommandResult.UX_ONGOING -> { waitingCmd = cmd; return }
|
||||
CommandResult.RETRY -> { return }
|
||||
CardCommand.Result.OK -> {}
|
||||
CardCommand.Result.CANCEL -> { state = State.READY; return }
|
||||
CardCommand.Result.UX_ONGOING -> { waitingCmd = cmd; return }
|
||||
CardCommand.Result.RETRY -> { return }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDisconnected() {
|
||||
executionContext.cardChannel = null
|
||||
executionContext.cmdSet = null
|
||||
|
||||
}
|
||||
|
||||
fun onUserInteractionReturned(resultCode: Int, data: Intent?) {
|
||||
|
@ -2,6 +2,7 @@ package im.status.keycard.connect.card
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import im.status.keycard.connect.Registry
|
||||
import im.status.keycard.connect.data.*
|
||||
import im.status.keycard.connect.ui.InitActivity
|
||||
import im.status.keycard.io.APDUException
|
||||
@ -12,37 +13,35 @@ class InitCommand : CardCommand {
|
||||
private var initPUK: String? = null
|
||||
private var initPairing: String? = null
|
||||
|
||||
private fun promptInit(mainActivity: Activity): CommandResult {
|
||||
val intent = Intent(mainActivity, InitActivity::class.java)
|
||||
mainActivity.startActivityForResult(intent, REQ_INTERACTIVE_SCRIPT)
|
||||
return CommandResult.UX_ONGOING
|
||||
private fun promptInit(activity: Activity): CardCommand.Result {
|
||||
val intent = Intent(activity, InitActivity::class.java)
|
||||
activity.startActivityForResult(intent, REQ_INTERACTIVE_SCRIPT)
|
||||
return CardCommand.Result.UX_ONGOING
|
||||
}
|
||||
|
||||
override fun run(context: CardScriptExecutor.Context): CommandResult {
|
||||
val cmdSet = context.cmdSet ?: return CommandResult.CANCEL
|
||||
|
||||
if (cmdSet.applicationInfo.isInitializedCard) {
|
||||
return CommandResult.OK
|
||||
override fun run(context: CardScriptExecutor.ScriptContext): CardCommand.Result {
|
||||
if (context.cmdSet.applicationInfo.isInitializedCard) {
|
||||
return CardCommand.Result.OK
|
||||
}
|
||||
|
||||
if (initPIN != null && initPUK != null && initPairing != null) {
|
||||
try {
|
||||
cmdSet.init(initPIN, initPUK, initPairing).checkOK()
|
||||
cmdSet.select().checkOK()
|
||||
cmdSet.autoPair(initPairing)
|
||||
PairingManager.putPairing(cmdSet.applicationInfo.instanceUID, cmdSet.pairing)
|
||||
return CommandResult.OK
|
||||
context.cmdSet.init(initPIN, initPUK, initPairing).checkOK()
|
||||
context.cmdSet.select().checkOK()
|
||||
context.cmdSet.autoPair(initPairing)
|
||||
Registry.pairingManager.putPairing(context.cmdSet.applicationInfo.instanceUID, context.cmdSet.pairing)
|
||||
return CardCommand.Result.OK
|
||||
} catch (e: IOException) {
|
||||
return CommandResult.RETRY
|
||||
return CardCommand.Result.RETRY
|
||||
} catch (e: APDUException) {
|
||||
return CommandResult.CANCEL
|
||||
return CardCommand.Result.CANCEL
|
||||
} finally {
|
||||
initPIN = null
|
||||
initPUK = null
|
||||
initPairing = null
|
||||
}
|
||||
} else {
|
||||
return promptInit(context.mainActivity)
|
||||
return promptInit(context.activity)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,8 +3,8 @@ package im.status.keycard.connect.card
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import im.status.keycard.applet.KeycardCommandSet
|
||||
import im.status.keycard.connect.Registry
|
||||
import im.status.keycard.connect.data.PAIRING_ACTIVITY_PASSWORD
|
||||
import im.status.keycard.connect.data.PairingManager
|
||||
import im.status.keycard.connect.data.REQ_INTERACTIVE_SCRIPT
|
||||
import im.status.keycard.connect.ui.PairingActivity
|
||||
import java.io.IOException
|
||||
@ -12,25 +12,25 @@ import java.io.IOException
|
||||
class OpenSecureChannelCommand : CardCommand {
|
||||
private var pairingPassword: String? = null
|
||||
|
||||
private fun openSecureChannel(cmdSet: KeycardCommandSet): CommandResult {
|
||||
private fun openSecureChannel(cmdSet: KeycardCommandSet): CardCommand.Result {
|
||||
try {
|
||||
cmdSet.autoOpenSecureChannel()
|
||||
} catch (e: IOException) {
|
||||
//TODO: must distinguish real IOException from card exception (to fix in SDK)
|
||||
return CommandResult.CANCEL
|
||||
return CardCommand.Result.CANCEL
|
||||
}
|
||||
|
||||
return CommandResult.OK
|
||||
return CardCommand.Result.OK
|
||||
}
|
||||
|
||||
private fun pair(mainActivity: Activity, cmdSet: KeycardCommandSet): CommandResult {
|
||||
private fun pair(activity: Activity, cmdSet: KeycardCommandSet): CardCommand.Result {
|
||||
if (pairingPassword != null) {
|
||||
try {
|
||||
//TODO: must distinguish real IOException from card exception (to fix in SDK)
|
||||
cmdSet.autoPair(pairingPassword)
|
||||
PairingManager.putPairing(cmdSet.applicationInfo.instanceUID, cmdSet.pairing)
|
||||
Registry.pairingManager.putPairing(cmdSet.applicationInfo.instanceUID, cmdSet.pairing)
|
||||
cmdSet.autoOpenSecureChannel()
|
||||
return CommandResult.OK
|
||||
return CardCommand.Result.OK
|
||||
} catch(e: IOException) {
|
||||
e.printStackTrace()
|
||||
} finally {
|
||||
@ -38,31 +38,29 @@ class OpenSecureChannelCommand : CardCommand {
|
||||
}
|
||||
}
|
||||
|
||||
return promptPairingPassword(mainActivity)
|
||||
return promptPairingPassword(activity)
|
||||
}
|
||||
|
||||
private fun promptPairingPassword(mainActivity: Activity): CommandResult {
|
||||
private fun promptPairingPassword(mainActivity: Activity): CardCommand.Result {
|
||||
val intent = Intent(mainActivity, PairingActivity::class.java)
|
||||
mainActivity.startActivityForResult(intent, REQ_INTERACTIVE_SCRIPT)
|
||||
|
||||
return CommandResult.UX_ONGOING
|
||||
return CardCommand.Result.UX_ONGOING
|
||||
}
|
||||
|
||||
override fun run(context: CardScriptExecutor.Context): CommandResult {
|
||||
val cmdSet = context.cmdSet ?: return CommandResult.CANCEL
|
||||
|
||||
val pairing = PairingManager.getPairing(cmdSet.applicationInfo.instanceUID)
|
||||
override fun run(context: CardScriptExecutor.ScriptContext): CardCommand.Result {
|
||||
val pairing = Registry.pairingManager.getPairing(context.cmdSet.applicationInfo.instanceUID)
|
||||
|
||||
if (pairing != null) {
|
||||
cmdSet.pairing = pairing
|
||||
if (openSecureChannel(cmdSet) == CommandResult.CANCEL) {
|
||||
PairingManager.removePairing(cmdSet.applicationInfo.instanceUID)
|
||||
context.cmdSet.pairing = pairing
|
||||
if (openSecureChannel(context.cmdSet) == CardCommand.Result.CANCEL) {
|
||||
Registry.pairingManager.removePairing(context.cmdSet.applicationInfo.instanceUID)
|
||||
} else {
|
||||
return CommandResult.OK
|
||||
return CardCommand.Result.OK
|
||||
}
|
||||
}
|
||||
|
||||
return pair(context.mainActivity, cmdSet)
|
||||
return pair(context.activity, context.cmdSet)
|
||||
}
|
||||
|
||||
override fun onDataReceived(data: Intent?) {
|
||||
|
@ -4,16 +4,16 @@ import java.io.IOException
|
||||
import java.lang.Exception
|
||||
|
||||
class SelectCommand : CardCommand {
|
||||
override fun run(context: CardScriptExecutor.Context): CommandResult {
|
||||
override fun run(context: CardScriptExecutor.ScriptContext): CardCommand.Result {
|
||||
//TODO: handle not-installed-applet/not-a-keycard
|
||||
try {
|
||||
context.cmdSet!!.select().checkOK()
|
||||
context.cmdSet.select().checkOK()
|
||||
} catch(e: IOException) {
|
||||
return CommandResult.RETRY
|
||||
return CardCommand.Result.RETRY
|
||||
} catch (e: Exception) {
|
||||
return CommandResult.CANCEL
|
||||
return CardCommand.Result.CANCEL
|
||||
}
|
||||
|
||||
return CommandResult.OK
|
||||
return CardCommand.Result.OK
|
||||
}
|
||||
}
|
@ -2,8 +2,8 @@ package im.status.keycard.connect.card
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import im.status.keycard.connect.Registry
|
||||
import im.status.keycard.connect.ui.PINActivity
|
||||
import im.status.keycard.connect.data.PINCache
|
||||
import im.status.keycard.connect.data.PIN_ACTIVITY_ATTEMPTS
|
||||
import im.status.keycard.connect.data.PIN_ACTIVITY_CARD_UID
|
||||
import im.status.keycard.connect.data.REQ_INTERACTIVE_SCRIPT
|
||||
@ -14,39 +14,37 @@ import java.io.IOException
|
||||
class VerifyPINCommand : CardCommand {
|
||||
private var retries = -1
|
||||
|
||||
private fun promptPIN(mainActivity: Activity, instanceUID: ByteArray): CommandResult {
|
||||
val intent = Intent(mainActivity, PINActivity::class.java).apply {
|
||||
private fun promptPIN(activity: Activity, instanceUID: ByteArray): CardCommand.Result {
|
||||
val intent = Intent(activity, PINActivity::class.java).apply {
|
||||
putExtra(PIN_ACTIVITY_ATTEMPTS, retries)
|
||||
putExtra(PIN_ACTIVITY_CARD_UID, instanceUID)
|
||||
}
|
||||
|
||||
mainActivity.startActivityForResult(intent, REQ_INTERACTIVE_SCRIPT)
|
||||
activity.startActivityForResult(intent, REQ_INTERACTIVE_SCRIPT)
|
||||
|
||||
return CommandResult.UX_ONGOING
|
||||
return CardCommand.Result.UX_ONGOING
|
||||
}
|
||||
|
||||
override fun run(context: CardScriptExecutor.Context): CommandResult {
|
||||
override fun run(context: CardScriptExecutor.ScriptContext): CardCommand.Result {
|
||||
//TODO: handle retries == 0 with UNBLOCK PIN
|
||||
|
||||
val cmdSet = context.cmdSet ?: return CommandResult.CANCEL
|
||||
|
||||
val pin = PINCache.getPIN(cmdSet.applicationInfo.instanceUID)
|
||||
val pin = Registry.pinCache.getPIN(context.cmdSet.applicationInfo.instanceUID)
|
||||
|
||||
if (pin != null) {
|
||||
try {
|
||||
cmdSet.verifyPIN(pin).checkAuthOK()
|
||||
context.cmdSet.verifyPIN(pin).checkAuthOK()
|
||||
retries = -1
|
||||
return CommandResult.OK
|
||||
return CardCommand.Result.OK
|
||||
} catch (e: WrongPINException) {
|
||||
PINCache.removePIN(cmdSet.applicationInfo.instanceUID)
|
||||
Registry.pinCache.removePIN(context.cmdSet.applicationInfo.instanceUID)
|
||||
retries = e.retryAttempts
|
||||
} catch(e: IOException) {
|
||||
return CommandResult.RETRY
|
||||
return CardCommand.Result.RETRY
|
||||
} catch(e: APDUException) {
|
||||
return CommandResult.CANCEL
|
||||
return CardCommand.Result.CANCEL
|
||||
}
|
||||
}
|
||||
|
||||
return promptPIN(context.mainActivity, cmdSet.applicationInfo.instanceUID)
|
||||
return promptPIN(context.activity, context.cmdSet.applicationInfo.instanceUID)
|
||||
}
|
||||
}
|
@ -10,3 +10,5 @@ const val INIT_ACTIVITY_PUK = "initPUK"
|
||||
const val INIT_ACTIVITY_PAIRING = "initPairing"
|
||||
|
||||
const val REQ_INTERACTIVE_SCRIPT = 0x01
|
||||
|
||||
const val CACHE_VALIDITY = 15 * 60 * 1000
|
@ -5,19 +5,17 @@ import java.util.concurrent.ScheduledExecutorService
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
object PINCache {
|
||||
const val CACHE_VALIDITY = 15 * 60 * 1000;
|
||||
|
||||
class PINCache {
|
||||
//TODO: don't use Strings, the memory should be cleared before release. For this the entire
|
||||
// chain from the EditText to the SDK should be controlled and never generate a String object.
|
||||
// This will require extensions to the SDK.
|
||||
private val pins: MutableMap<ByteArrayKey, String> = HashMap()
|
||||
private val timestamps: MutableMap<Long, ByteArrayKey> = HashMap()
|
||||
|
||||
private val scheduler: ScheduledExecutorService = Executors.newScheduledThreadPool(1);
|
||||
private val scheduler: ScheduledExecutorService = Executors.newScheduledThreadPool(1)
|
||||
|
||||
init {
|
||||
scheduler.scheduleAtFixedRate(this::cleanCache, 1, 1, TimeUnit.MINUTES);
|
||||
scheduler.scheduleAtFixedRate(this::cleanCache, 1, 1, TimeUnit.MINUTES)
|
||||
}
|
||||
|
||||
private fun cleanCache() {
|
||||
|
@ -9,14 +9,14 @@ import im.status.keycard.applet.Pairing
|
||||
import androidx.security.crypto.EncryptedSharedPreferences
|
||||
import androidx.security.crypto.MasterKeys
|
||||
|
||||
object PairingManager {
|
||||
class PairingManager(context: Context) {
|
||||
private lateinit var sharedPreferences: SharedPreferences
|
||||
|
||||
private fun id(instanceUID: ByteArray) : String {
|
||||
return Base64.encodeToString(instanceUID, NO_PADDING or NO_WRAP)
|
||||
}
|
||||
|
||||
fun init(context: Context) {
|
||||
init {
|
||||
val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
|
||||
sharedPreferences = EncryptedSharedPreferences.create("pairings", masterKeyAlias, context, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM)
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
package im.status.keycard.connect.ui
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import im.status.keycard.connect.R
|
||||
|
||||
class ChangePINActivity : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
//TODO: pin validation and confirmation
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_change_pin)
|
||||
}
|
||||
|
||||
fun ok(@Suppress("UNUSED_PARAMETER") view: View) {
|
||||
|
||||
finish()
|
||||
}
|
||||
|
||||
fun cancel(@Suppress("UNUSED_PARAMETER") view: View) {
|
||||
finish()
|
||||
}
|
||||
}
|
@ -6,45 +6,34 @@ import androidx.appcompat.app.AppCompatActivity
|
||||
import im.status.keycard.android.NFCCardManager
|
||||
import android.content.Intent
|
||||
import im.status.keycard.connect.R
|
||||
import im.status.keycard.connect.Registry
|
||||
import im.status.keycard.connect.card.*
|
||||
import im.status.keycard.connect.data.PairingManager
|
||||
import im.status.keycard.connect.data.REQ_INTERACTIVE_SCRIPT
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
private lateinit var cardManager: NFCCardManager
|
||||
private lateinit var nfcAdapter: NfcAdapter
|
||||
private lateinit var executor: CardScriptExecutor
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
PairingManager.init(this)
|
||||
|
||||
nfcAdapter = NfcAdapter.getDefaultAdapter(this)
|
||||
executor = CardScriptExecutor(this)
|
||||
executor.setScript(listOf(SelectCommand(), InitCommand(), OpenSecureChannelCommand(), VerifyPINCommand()))
|
||||
|
||||
cardManager = NFCCardManager()
|
||||
cardManager.setCardListener(executor)
|
||||
cardManager.start()
|
||||
Registry.init(this)
|
||||
Registry.scriptExecutor.setScript(listOf(SelectCommand(), InitCommand(), OpenSecureChannelCommand(), VerifyPINCommand()))
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
nfcAdapter.enableReaderMode(this, this.cardManager,NfcAdapter.FLAG_READER_NFC_A or NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK, null)
|
||||
Registry.nfcAdapter.enableReaderMode(this, Registry.cardManager,NfcAdapter.FLAG_READER_NFC_A or NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK, null)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
nfcAdapter.disableReaderMode(this)
|
||||
Registry.nfcAdapter.disableReaderMode(this)
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
|
||||
if (requestCode == REQ_INTERACTIVE_SCRIPT) {
|
||||
executor.onUserInteractionReturned(resultCode, data)
|
||||
Registry.scriptExecutor.onUserInteractionReturned(resultCode, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import android.view.View
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import im.status.keycard.connect.R
|
||||
import im.status.keycard.connect.Registry
|
||||
import im.status.keycard.connect.data.PINCache
|
||||
import im.status.keycard.connect.data.PIN_ACTIVITY_ATTEMPTS
|
||||
import im.status.keycard.connect.data.PIN_ACTIVITY_CARD_UID
|
||||
@ -34,7 +35,7 @@ class PINActivity : AppCompatActivity() {
|
||||
fun ok(@Suppress("UNUSED_PARAMETER") view: View) {
|
||||
val pinText = findViewById<EditText>(R.id.pinText)
|
||||
|
||||
PINCache.putPIN(cardUID, pinText.text.toString())
|
||||
Registry.pinCache.putPIN(cardUID, pinText.text.toString())
|
||||
setResult(Activity.RESULT_OK)
|
||||
finish()
|
||||
}
|
||||
|
53
app/src/main/res/layout/activity_change_pin.xml
Normal file
53
app/src/main/res/layout/activity_change_pin.xml
Normal file
@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout 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="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ui.ChangePINActivity">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/newPinPrompt"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="52dp"
|
||||
android:text="@string/change_pin_prompt"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Display2"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.497"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/okButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="88dp"
|
||||
android:onClick="ok"
|
||||
android:text="@android:string/ok"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/cancelButton" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/cancelButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="68dp"
|
||||
android:layout_marginTop="76dp"
|
||||
android:onClick="cancel"
|
||||
android:text="@android:string/cancel"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/newPINText" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/newPINText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="88dp"
|
||||
android:ems="10"
|
||||
android:inputType="numberPassword"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/newPinPrompt"
|
||||
tools:text="123456" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -2,4 +2,5 @@
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
<dimen name="fab_margin">16dp</dimen>
|
||||
</resources>
|
||||
|
@ -7,4 +7,6 @@
|
||||
<string name="pin_label">PIN</string>
|
||||
<string name="puk_label">PUK</string>
|
||||
<string name="pairing_label">Pairing password</string>
|
||||
<string name="title_activity_change_pin">ChangePINActivity</string>
|
||||
<string name="change_pin_prompt">New PIN</string>
|
||||
</resources>
|
||||
|
@ -8,4 +8,13 @@
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
|
||||
|
||||
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
|
||||
|
||||
</resources>
|
||||
|
@ -1,17 +0,0 @@
|
||||
package im.status.keycard.connect
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user