Adapt key connection instructions to available transports
This commit is contained in:
@@ -50,18 +50,22 @@ class CredentialBottomSheet : BottomSheetDialogFragment() {
|
||||
private lateinit var iconStatus: ImageView
|
||||
private lateinit var iconBackground: View
|
||||
private lateinit var accountList: RecyclerView
|
||||
private lateinit var nfcHintContainer: View
|
||||
private lateinit var btnNfcSettings: MaterialButton
|
||||
|
||||
private var pulseAnimator: ObjectAnimator? = null
|
||||
|
||||
private var pendingStatus: String? = null
|
||||
private var pendingInstruction: String? = null
|
||||
private var pendingShowPinInput: Boolean = false
|
||||
private var pendingShowNfcHint: Boolean = false
|
||||
private var pendingState: State = State.WAITING
|
||||
|
||||
var onCancelClick: (() -> Unit)? = null
|
||||
var onPinEntered: ((String) -> Unit)? = null
|
||||
var onAccountSelected: ((Int) -> Unit)? = null
|
||||
var onBiometricSelected: (() -> Unit)? = null
|
||||
var onNfcSettingsClick: (() -> Unit)? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -93,6 +97,8 @@ class CredentialBottomSheet : BottomSheetDialogFragment() {
|
||||
iconStatus = view.findViewById(R.id.iconStatus)
|
||||
iconBackground = view.findViewById(R.id.iconBackground)
|
||||
accountList = view.findViewById(R.id.accountList)
|
||||
nfcHintContainer = view.findViewById(R.id.nfcHintContainer)
|
||||
btnNfcSettings = view.findViewById(R.id.btnNfcSettings)
|
||||
|
||||
accountList.layoutManager = LinearLayoutManager(context)
|
||||
|
||||
@@ -103,6 +109,8 @@ class CredentialBottomSheet : BottomSheetDialogFragment() {
|
||||
pendingStatus?.let { statusText.text = it }
|
||||
pendingInstruction?.let { instructionText.text = it }
|
||||
|
||||
nfcHintContainer.visibility = if (pendingShowNfcHint) View.VISIBLE else View.GONE
|
||||
|
||||
if (pendingShowPinInput) {
|
||||
pinInputField.visibility = View.VISIBLE
|
||||
btnContinue.visibility = View.VISIBLE
|
||||
@@ -123,6 +131,10 @@ class CredentialBottomSheet : BottomSheetDialogFragment() {
|
||||
onBiometricSelected?.invoke()
|
||||
}
|
||||
|
||||
btnNfcSettings.setOnClickListener {
|
||||
onNfcSettingsClick?.invoke()
|
||||
}
|
||||
|
||||
pinInputField.setOnDoneAction {
|
||||
submitPin()
|
||||
}
|
||||
@@ -230,6 +242,15 @@ class CredentialBottomSheet : BottomSheetDialogFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
/** Shows the "NFC is off" row with a shortcut to system NFC settings. */
|
||||
fun showNfcHint(show: Boolean) {
|
||||
if (::nfcHintContainer.isInitialized) {
|
||||
nfcHintContainer.visibility = if (show) View.VISIBLE else View.GONE
|
||||
} else {
|
||||
pendingShowNfcHint = show
|
||||
}
|
||||
}
|
||||
|
||||
fun showProgress(show: Boolean) {
|
||||
if (::progressBar.isInitialized) {
|
||||
progressBar.visibility = if (show) View.VISIBLE else View.GONE
|
||||
|
||||
@@ -13,6 +13,7 @@ import android.nfc.Tag
|
||||
import android.nfc.tech.IsoDep
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
@@ -49,6 +50,7 @@ class CredentialProviderActivity : AppCompatActivity() {
|
||||
private data class ClientData(val json: String?, val hash: ByteArray)
|
||||
|
||||
private var nfcAdapter: NfcAdapter? = null
|
||||
private var connectPromptVisible = false
|
||||
private lateinit var usbManager: UsbManager
|
||||
|
||||
private var bottomSheet: CredentialBottomSheet? = null
|
||||
@@ -116,7 +118,7 @@ class CredentialProviderActivity : AppCompatActivity() {
|
||||
if (granted && device != null) {
|
||||
connectToUsbDevice(device)
|
||||
} else {
|
||||
setInstruction(getString(R.string.instruction_usb_permission_denied))
|
||||
setInstruction(usbPermissionDeniedInstruction())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,6 +147,17 @@ class CredentialProviderActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
// Fires when NFC is toggled anywhere, including the quick settings shade.
|
||||
private val nfcStateReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action != NfcAdapter.ACTION_ADAPTER_STATE_CHANGED) return
|
||||
when (intent.getIntExtra(NfcAdapter.EXTRA_ADAPTER_STATE, NfcAdapter.STATE_OFF)) {
|
||||
NfcAdapter.STATE_ON, NfcAdapter.STATE_OFF ->
|
||||
if (connectPromptVisible) showConnectPrompt()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
@@ -171,7 +184,7 @@ class CredentialProviderActivity : AppCompatActivity() {
|
||||
val publicKeyRequest = createRequest!!.callingRequest as? CreatePublicKeyCredentialRequest
|
||||
requestJson = publicKeyRequest?.requestJson
|
||||
providedClientDataHash = publicKeyRequest?.clientDataHash
|
||||
showBottomSheet(getString(R.string.create_passkey), getString(R.string.instruction_connect_key))
|
||||
showBottomSheet(getString(R.string.create_passkey))
|
||||
}
|
||||
getRequest != null -> {
|
||||
isCreateRequest = false
|
||||
@@ -180,7 +193,7 @@ class CredentialProviderActivity : AppCompatActivity() {
|
||||
val publicKeyOption = options.firstOrNull { it is GetPublicKeyCredentialOption } as? GetPublicKeyCredentialOption
|
||||
requestJson = publicKeyOption?.requestJson
|
||||
providedClientDataHash = publicKeyOption?.clientDataHash
|
||||
showBottomSheet(getString(R.string.sign_in), getString(R.string.instruction_connect_key))
|
||||
showBottomSheet(getString(R.string.sign_in))
|
||||
}
|
||||
else -> {
|
||||
Log.e(TAG, "No valid request found in intent")
|
||||
@@ -199,14 +212,35 @@ class CredentialProviderActivity : AppCompatActivity() {
|
||||
checkPinRequirement()
|
||||
}
|
||||
|
||||
private fun showBottomSheet(status: String, instruction: String) {
|
||||
bottomSheet = CredentialBottomSheet.newInstance(status, instruction).apply {
|
||||
private fun showBottomSheet(status: String) {
|
||||
bottomSheet = CredentialBottomSheet.newInstance(status, connectKeyInstruction()).apply {
|
||||
onCancelClick = { cancelOperation() }
|
||||
onPinEntered = { pin -> handlePinEntered(pin) }
|
||||
onBiometricSelected = { handleBiometricSelected() }
|
||||
onNfcSettingsClick = { openNfcSettings() }
|
||||
}
|
||||
bottomSheet?.show(supportFragmentManager, CredentialBottomSheet.TAG)
|
||||
bottomSheet?.setState(CredentialBottomSheet.State.WAITING)
|
||||
connectPromptVisible = true
|
||||
bottomSheet?.showNfcHint(shouldOfferNfcSettings())
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks the user to present a key, naming only the transports this device can
|
||||
* currently use. Re-callable: the instruction depends on live NFC state.
|
||||
*/
|
||||
private fun showConnectPrompt() {
|
||||
connectPromptVisible = true
|
||||
bottomSheet?.setInstruction(connectKeyInstruction())
|
||||
bottomSheet?.showNfcHint(shouldOfferNfcSettings())
|
||||
}
|
||||
|
||||
private fun openNfcSettings() {
|
||||
try {
|
||||
startActivity(Intent(Settings.ACTION_NFC_SETTINGS))
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "No NFC settings activity", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handlePinEntered(pin: String) {
|
||||
@@ -219,7 +253,7 @@ class CredentialProviderActivity : AppCompatActivity() {
|
||||
authenticateAndExecute(pin, json)
|
||||
} else {
|
||||
pendingPin = pin
|
||||
setInstruction(getString(R.string.instruction_connect_key))
|
||||
showConnectPrompt()
|
||||
setState(CredentialBottomSheet.State.WAITING)
|
||||
bottomSheet?.showPinInput(false)
|
||||
}
|
||||
@@ -238,7 +272,7 @@ class CredentialProviderActivity : AppCompatActivity() {
|
||||
}
|
||||
} else {
|
||||
// Key not connected yet — show waiting state
|
||||
setInstruction(getString(R.string.instruction_connect_key))
|
||||
showConnectPrompt()
|
||||
setState(CredentialBottomSheet.State.WAITING)
|
||||
}
|
||||
}
|
||||
@@ -248,7 +282,9 @@ class CredentialProviderActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun setInstruction(text: String) {
|
||||
connectPromptVisible = false
|
||||
bottomSheet?.setInstruction(text)
|
||||
bottomSheet?.showNfcHint(false)
|
||||
}
|
||||
|
||||
private fun showProgress(show: Boolean) {
|
||||
@@ -310,6 +346,16 @@ class CredentialProviderActivity : AppCompatActivity() {
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
// NFC may have been toggled while we were backgrounded.
|
||||
if (connectPromptVisible) showConnectPrompt()
|
||||
|
||||
// Also catch toggles that happen while we're in the foreground (e.g. the quick
|
||||
// settings shade, which does not pause us).
|
||||
registerReceiver(
|
||||
nfcStateReceiver,
|
||||
IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED)
|
||||
)
|
||||
|
||||
nfcAdapter?.let { adapter ->
|
||||
val intent = Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
||||
|
||||
@@ -355,6 +401,11 @@ class CredentialProviderActivity : AppCompatActivity() {
|
||||
} catch (e: Exception) {
|
||||
// Ignore
|
||||
}
|
||||
try {
|
||||
unregisterReceiver(nfcStateReceiver)
|
||||
} catch (e: Exception) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
@@ -369,6 +420,11 @@ class CredentialProviderActivity : AppCompatActivity() {
|
||||
} catch (e: Exception) {
|
||||
// Ignore
|
||||
}
|
||||
try {
|
||||
unregisterReceiver(nfcStateReceiver)
|
||||
} catch (e: Exception) {
|
||||
// Ignore
|
||||
}
|
||||
scope.cancel()
|
||||
ctapSession?.close()
|
||||
}
|
||||
|
||||
@@ -51,6 +51,8 @@ class MainActivity : AppCompatActivity() {
|
||||
private lateinit var providerStatusContainer: LinearLayout
|
||||
private lateinit var providerStatusText: TextView
|
||||
private lateinit var btnEnableProvider: Button
|
||||
private lateinit var nfcHintContainer: LinearLayout
|
||||
private lateinit var btnNfcSettings: MaterialButton
|
||||
|
||||
private var currentTransport: FidoTransport? = null
|
||||
private var pinProtocol: PinProtocol? = null
|
||||
@@ -119,6 +121,16 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
// Fires when NFC is toggled anywhere, including the quick settings shade.
|
||||
private val nfcStateReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action != NfcAdapter.ACTION_ADAPTER_STATE_CHANGED) return
|
||||
when (intent.getIntExtra(NfcAdapter.EXTRA_ADAPTER_STATE, NfcAdapter.STATE_OFF)) {
|
||||
NfcAdapter.STATE_ON, NfcAdapter.STATE_OFF -> updateConnectionStatus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
enableEdgeToEdge()
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -147,6 +159,8 @@ class MainActivity : AppCompatActivity() {
|
||||
providerStatusContainer = findViewById(R.id.providerStatusContainer)
|
||||
providerStatusText = findViewById(R.id.providerStatusText)
|
||||
btnEnableProvider = findViewById(R.id.btnEnableProvider)
|
||||
nfcHintContainer = findViewById(R.id.nfcHintContainer)
|
||||
btnNfcSettings = findViewById(R.id.btnNfcSettings)
|
||||
|
||||
nfcAdapter = NfcAdapter.getDefaultAdapter(this)
|
||||
usbManager = getSystemService(Context.USB_SERVICE) as UsbManager
|
||||
@@ -164,6 +178,7 @@ class MainActivity : AppCompatActivity() {
|
||||
btnListCredentials.setOnClickListener { listCredentials() }
|
||||
btnChangePin.setOnClickListener { showChangePinDialog() }
|
||||
btnEnableProvider.setOnClickListener { openProviderSettings() }
|
||||
btnNfcSettings.setOnClickListener { openNfcSettings() }
|
||||
|
||||
updateConnectionStatus()
|
||||
}
|
||||
@@ -180,6 +195,11 @@ class MainActivity : AppCompatActivity() {
|
||||
} catch (e: Exception) {
|
||||
// Ignore
|
||||
}
|
||||
try {
|
||||
unregisterReceiver(nfcStateReceiver)
|
||||
} catch (e: Exception) {
|
||||
// Ignore
|
||||
}
|
||||
scope.cancel()
|
||||
}
|
||||
|
||||
@@ -189,6 +209,16 @@ class MainActivity : AppCompatActivity() {
|
||||
// Check credential provider status
|
||||
checkProviderStatus()
|
||||
|
||||
// Refresh the waiting prompt: NFC may have been toggled while backgrounded
|
||||
updateConnectionStatus()
|
||||
|
||||
// Catch NFC toggles that happen while we're in the foreground (e.g. from the
|
||||
// quick settings shade, which does not pause us).
|
||||
registerReceiver(
|
||||
nfcStateReceiver,
|
||||
IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED)
|
||||
)
|
||||
|
||||
// Enable NFC foreground dispatch
|
||||
nfcAdapter?.let { adapter ->
|
||||
val intent = Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
||||
@@ -234,6 +264,11 @@ class MainActivity : AppCompatActivity() {
|
||||
} catch (e: Exception) {
|
||||
// Ignore
|
||||
}
|
||||
try {
|
||||
unregisterReceiver(nfcStateReceiver)
|
||||
} catch (e: Exception) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
@@ -422,7 +457,19 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
// Update status text if not connected and not waiting for reconnect
|
||||
if (!connected && !awaitingNfcReconnect) {
|
||||
statusText.text = getString(R.string.waiting_for_key)
|
||||
statusText.text = connectKeyInstruction()
|
||||
}
|
||||
|
||||
// Offer to turn NFC on, but only while there's nothing connected anyway
|
||||
nfcHintContainer.visibility =
|
||||
if (!connected && shouldOfferNfcSettings()) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
private fun openNfcSettings() {
|
||||
try {
|
||||
startActivity(Intent(Settings.ACTION_NFC_SETTINGS))
|
||||
} catch (e: Exception) {
|
||||
// No NFC settings activity available
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package pl.lebihan.authnkey
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.nfc.NfcAdapter
|
||||
|
||||
/** Whether the device has NFC hardware at all. */
|
||||
fun Context.hasNfc(): Boolean = NfcAdapter.getDefaultAdapter(this) != null
|
||||
|
||||
/** Whether NFC is present and switched on. Read at point of use; the user can toggle it anytime. */
|
||||
fun Context.isNfcEnabled(): Boolean = NfcAdapter.getDefaultAdapter(this)?.isEnabled == true
|
||||
|
||||
/** Whether the device can act as a USB host (required to talk to a plugged-in key). */
|
||||
fun Context.hasUsbHost(): Boolean =
|
||||
packageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)
|
||||
|
||||
/** Whether to offer the "NFC is off" hint: hardware exists but is disabled. */
|
||||
fun Context.shouldOfferNfcSettings(): Boolean = hasNfc() && !isNfcEnabled()
|
||||
|
||||
/**
|
||||
* The instruction to show while waiting for a key, covering only the transports
|
||||
* this device can actually use right now.
|
||||
*/
|
||||
fun Context.connectKeyInstruction(): String {
|
||||
val nfc = isNfcEnabled()
|
||||
val usb = hasUsbHost()
|
||||
return getString(
|
||||
when {
|
||||
nfc && usb -> R.string.instruction_connect_key
|
||||
nfc -> R.string.instruction_connect_key_nfc_only
|
||||
usb -> R.string.instruction_connect_key_usb_only
|
||||
else -> R.string.instruction_no_transport
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/** USB permission denial message, only suggesting NFC when NFC is usable. */
|
||||
fun Context.usbPermissionDeniedInstruction(): String = getString(
|
||||
if (isNfcEnabled()) R.string.instruction_usb_permission_denied
|
||||
else R.string.instruction_usb_permission_denied_no_nfc
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="12dp" />
|
||||
<solid android:color="@color/hint_background" />
|
||||
</shape>
|
||||
@@ -37,6 +37,35 @@
|
||||
style="@style/Widget.Material3.Button.OutlinedButton" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- NFC off hint -->
|
||||
<LinearLayout
|
||||
android:id="@+id/nfcHintContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="12dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:background="@drawable/bg_hint_row"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/hint_nfc_off"
|
||||
android:textSize="14sp"
|
||||
android:textColor="@color/hint_text" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnNfcSettings"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/action_turn_on_nfc"
|
||||
app:strokeColor="@color/provider_button_outline"
|
||||
style="@style/Widget.Material3.Button.OutlinedButton" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/statusText"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -62,6 +62,35 @@
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:layout_marginBottom="24dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/nfcHintContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="12dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="@drawable/bg_hint_row"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/hint_nfc_off"
|
||||
android:textSize="14sp"
|
||||
android:textColor="@color/hint_text" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnNfcSettings"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/action_turn_on_nfc"
|
||||
app:strokeColor="@color/provider_button_outline"
|
||||
style="@style/Widget.Material3.Button.OutlinedButton" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/accountList"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -13,6 +13,10 @@
|
||||
<color name="provider_not_supported_background">#B71C1C</color>
|
||||
<color name="provider_not_supported_text">#FFCDD2</color>
|
||||
|
||||
<!-- Inline hint row (dark theme) -->
|
||||
<color name="hint_background">#3A2E22</color>
|
||||
<color name="hint_text">#FFCC80</color>
|
||||
|
||||
<!-- Tag lost warning state -->
|
||||
<color name="warning_container">#7C2D12</color>
|
||||
|
||||
|
||||
@@ -13,6 +13,10 @@
|
||||
<color name="provider_not_supported_background">#FFEBEE</color>
|
||||
<color name="provider_not_supported_text">#C62828</color>
|
||||
|
||||
<!-- Inline hint row (light theme) -->
|
||||
<color name="hint_background">#FFF3E0</color>
|
||||
<color name="hint_text">#E65100</color>
|
||||
|
||||
<!-- Tag lost warning state -->
|
||||
<color name="warning_container">#FED7AA</color>
|
||||
|
||||
|
||||
@@ -19,11 +19,15 @@
|
||||
|
||||
<!-- Instructions -->
|
||||
<string name="instruction_connect_key">Hold your security key against the back of your phone, or plug it in via USB</string>
|
||||
<string name="instruction_connect_key_nfc_only">Hold your security key against the back of your phone</string>
|
||||
<string name="instruction_connect_key_usb_only">Plug your security key in via USB</string>
|
||||
<string name="instruction_no_transport">No usable connection method</string>
|
||||
<string name="instruction_touch_key">Touch your security key to confirm…</string>
|
||||
<string name="instruction_signing_in">Signing in…</string>
|
||||
<string name="instruction_creating">Creating passkey…</string>
|
||||
<string name="instruction_enter_pin">Enter your security key PIN</string>
|
||||
<string name="instruction_usb_permission_denied">USB permission denied. Try again or use NFC.</string>
|
||||
<string name="instruction_usb_permission_denied_no_nfc">USB permission denied. Try again.</string>
|
||||
<string name="instruction_key_connected">Security key connected</string>
|
||||
<string name="instruction_connecting_usb">Connecting to USB device…</string>
|
||||
<string name="instruction_authenticating">Authenticating…</string>
|
||||
@@ -33,6 +37,10 @@
|
||||
<string name="instruction_tag_lost">Lost contact with the security key\n\nReposition and hold until completion</string>
|
||||
<string name="instruction_waiting_biometric">Touch the fingerprint sensor on your security key…</string>
|
||||
|
||||
<!-- NFC hint -->
|
||||
<string name="hint_nfc_off">NFC is off</string>
|
||||
<string name="action_turn_on_nfc">Turn on</string>
|
||||
|
||||
<!-- PIN Dialog -->
|
||||
<string name="pin_dialog_title">Security Key PIN</string>
|
||||
<string name="pin_dialog_message">Enter your security key PIN</string>
|
||||
|
||||
Reference in New Issue
Block a user