From ea6266ce6bb2cb7a04e2c8f002c2bbecbe06641f Mon Sep 17 00:00:00 2001 From: Mykhailo Nester Date: Wed, 25 Aug 2021 14:04:13 +0300 Subject: [PATCH] NFC card emulation (#100) * - NFC card emulation mode for sharing DCC on View cert page. Host apdu service with NFC Forum type 4 implementation for NDEF message exchange; - NFC reader mode on main certificates page; * - add base binding fragment; - clean up logic; * - claim certificate when received via NFC; * - detect and handle DCC sent event in service; - display toast when dcc sent; * - code refactor; * - remove NFC reader initialization from cert fragment; - remove UI component; - parse message in activity and navigate to claim cert fragment; - send qr code text only via nfc; * - navigate to claim cert view on app start; * - merge main branch; - remove unused images; * - revert changes to claim viewModel; * - update button style; --- .idea/codeStyles/Project.xml | 16 - .idea/misc.xml | 13 + app/src/main/AndroidManifest.xml | 25 +- .../dgca/wallet/app/android/MainActivity.kt | 65 +++- .../app/android/base/BindingFragment.kt | 58 ++++ .../certificate/CertificatesFragment.kt | 30 +- .../claim/ClaimCertificateFragment.kt | 24 +- .../claim/ClaimCertificateViewModel.kt | 6 +- .../certificate/ViewCertificateFragment.kt | 93 +++++- .../certificate/ViewCertificateViewModel.kt | 4 + .../wallet/app/android/nfc/DCCApduService.kt | 290 ++++++++++++++++++ .../dgca/wallet/app/android/nfc/NdefParser.kt | 83 +++++ .../app/android/nfc/ParsedNdefRecord.kt | 27 ++ .../dgca/wallet/app/android/nfc/TextRecord.kt | 30 ++ .../java/dgca/wallet/app/android/nfc/Util.kt | 59 ++++ app/src/main/res/drawable-hdpi/icon_check.png | Bin 1234 -> 0 bytes app/src/main/res/drawable-hdpi/icon_error.png | Bin 801 -> 0 bytes .../res/drawable-hdpi/icon_large_error.png | Bin 2387 -> 0 bytes app/src/main/res/drawable-ldpi/icon_check.png | Bin 648 -> 0 bytes app/src/main/res/drawable-ldpi/icon_error.png | Bin 493 -> 0 bytes .../res/drawable-ldpi/icon_large_error.png | Bin 836 -> 0 bytes app/src/main/res/drawable-mdpi/icon_check.png | Bin 762 -> 0 bytes app/src/main/res/drawable-mdpi/icon_error.png | Bin 589 -> 0 bytes .../res/drawable-mdpi/icon_large_error.png | Bin 1049 -> 0 bytes .../main/res/drawable-xhdpi/icon_check.png | Bin 1652 -> 0 bytes .../main/res/drawable-xhdpi/icon_error.png | Bin 1048 -> 0 bytes .../res/drawable-xhdpi/icon_large_error.png | Bin 3455 -> 0 bytes .../main/res/drawable-xxhdpi/icon_check.png | Bin 2446 -> 0 bytes .../main/res/drawable-xxhdpi/icon_error.png | Bin 2245 -> 0 bytes .../res/drawable-xxhdpi/icon_large_error.png | Bin 5610 -> 0 bytes .../main/res/drawable-xxxhdpi/icon_check.png | Bin 3440 -> 0 bytes .../main/res/drawable-xxxhdpi/icon_error.png | Bin 3104 -> 0 bytes .../res/drawable-xxxhdpi/icon_large_error.png | Bin 7526 -> 0 bytes .../res/layout/dialog_fragment_add_new.xml | 7 + .../res/layout/fragment_certificate_view.xml | 50 ++- app/src/main/res/navigation/nav_graph.xml | 8 + app/src/main/res/values/strings.xml | 12 +- app/src/main/res/values/styles.xml | 3 +- app/src/main/res/xml/nfc_application_ids.xml | 32 ++ 39 files changed, 852 insertions(+), 83 deletions(-) create mode 100644 app/src/main/java/dgca/wallet/app/android/base/BindingFragment.kt create mode 100644 app/src/main/java/dgca/wallet/app/android/nfc/DCCApduService.kt create mode 100644 app/src/main/java/dgca/wallet/app/android/nfc/NdefParser.kt create mode 100644 app/src/main/java/dgca/wallet/app/android/nfc/ParsedNdefRecord.kt create mode 100644 app/src/main/java/dgca/wallet/app/android/nfc/TextRecord.kt create mode 100644 app/src/main/java/dgca/wallet/app/android/nfc/Util.kt delete mode 100644 app/src/main/res/drawable-hdpi/icon_check.png delete mode 100644 app/src/main/res/drawable-hdpi/icon_error.png delete mode 100644 app/src/main/res/drawable-hdpi/icon_large_error.png delete mode 100644 app/src/main/res/drawable-ldpi/icon_check.png delete mode 100644 app/src/main/res/drawable-ldpi/icon_error.png delete mode 100644 app/src/main/res/drawable-ldpi/icon_large_error.png delete mode 100644 app/src/main/res/drawable-mdpi/icon_check.png delete mode 100644 app/src/main/res/drawable-mdpi/icon_error.png delete mode 100644 app/src/main/res/drawable-mdpi/icon_large_error.png delete mode 100644 app/src/main/res/drawable-xhdpi/icon_check.png delete mode 100644 app/src/main/res/drawable-xhdpi/icon_error.png delete mode 100644 app/src/main/res/drawable-xhdpi/icon_large_error.png delete mode 100644 app/src/main/res/drawable-xxhdpi/icon_check.png delete mode 100644 app/src/main/res/drawable-xxhdpi/icon_error.png delete mode 100644 app/src/main/res/drawable-xxhdpi/icon_large_error.png delete mode 100644 app/src/main/res/drawable-xxxhdpi/icon_check.png delete mode 100644 app/src/main/res/drawable-xxxhdpi/icon_error.png delete mode 100644 app/src/main/res/drawable-xxxhdpi/icon_large_error.png create mode 100644 app/src/main/res/xml/nfc_application_ids.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 992af513..3b523f57 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,22 +1,6 @@ - - diff --git a/.idea/misc.xml b/.idea/misc.xml index d5d35ec4..c2ba1a9b 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,18 @@ + + + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 998e049e..0b4c187c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,6 +5,7 @@ + - + + + + + + + + + + + + + + + + if (destination.id == R.id.certificatesFragment) { + checkNdefMessage(intent) + } + } + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + + checkNdefMessage(intent) } override fun onCreateOptionsMenu(menu: Menu?): Boolean { @@ -62,8 +85,6 @@ class MainActivity : AppCompatActivity() { } override fun onOptionsItemSelected(item: MenuItem): Boolean { - val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment - val navController = navHostFragment.navController return when (item.itemId) { R.id.settings -> { navController.navigate(R.id.settingsFragment) @@ -74,6 +95,10 @@ class MainActivity : AppCompatActivity() { } } + override fun onSupportNavigateUp(): Boolean { + return navController.navigateUp() || super.onSupportNavigateUp() + } + fun clearBackground() { window.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.white)) } @@ -81,4 +106,38 @@ class MainActivity : AppCompatActivity() { fun disableBackButton() { binding.toolbar.navigationIcon = null } + + private fun checkNdefMessage(intent: Intent) { + if (NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action) { + intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)?.also { rawMessages -> + val messages: List = rawMessages.map { it as NdefMessage } + parseNdefMessages(messages) + intent.removeExtra(NfcAdapter.EXTRA_NDEF_MESSAGES) + } + } + } + + private fun parseNdefMessages(messages: List) { + if (messages.isEmpty()) { + return + } + + val builder = StringBuilder() + val records = NdefParser.parse(messages[0]) + val size = records.size + + for (i in 0 until size) { + val record = records[i] + val str = record.str() + builder.append(str) + } + + val qrCodeText = builder.toString() + if (qrCodeText.isNotEmpty()) { + val action = CertificatesFragmentDirections.actionCertificatesFragmentToClaimCertificateFragment(qrCodeText) + navController.navigate(action) + } else { + Timber.d("Received empty NDEFMessage") + } + } } \ No newline at end of file diff --git a/app/src/main/java/dgca/wallet/app/android/base/BindingFragment.kt b/app/src/main/java/dgca/wallet/app/android/base/BindingFragment.kt new file mode 100644 index 00000000..eabd136c --- /dev/null +++ b/app/src/main/java/dgca/wallet/app/android/base/BindingFragment.kt @@ -0,0 +1,58 @@ +/* + * ---license-start + * eu-digital-green-certificates / dgca-verifier-app-android + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + * + * Created by mykhailo.nester on 18/08/2021, 17:21 + */ + +package dgca.wallet.app.android.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.viewbinding.ViewBinding + +abstract class BindingFragment : Fragment() { + + private var _binding: T? = null + val binding get() = _binding!! + + abstract fun onCreateBinding(inflater: LayoutInflater, container: ViewGroup?): T + + open fun onDestroyBinding(binding: T) { + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val innerBinding = onCreateBinding(inflater, container) + _binding = innerBinding + return innerBinding.root + } + + override fun onDestroyView() { + val innerBinding = _binding + if (innerBinding != null) { + onDestroyBinding(innerBinding) + } + + _binding = null + + super.onDestroyView() + } +} \ No newline at end of file diff --git a/app/src/main/java/dgca/wallet/app/android/certificate/CertificatesFragment.kt b/app/src/main/java/dgca/wallet/app/android/certificate/CertificatesFragment.kt index 57193e88..3142b44a 100644 --- a/app/src/main/java/dgca/wallet/app/android/certificate/CertificatesFragment.kt +++ b/app/src/main/java/dgca/wallet/app/android/certificate/CertificatesFragment.kt @@ -28,46 +28,41 @@ import android.view.View import android.view.ViewGroup import androidx.activity.addCallback import androidx.core.view.isVisible -import androidx.fragment.app.Fragment import androidx.fragment.app.setFragmentResultListener import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint import dgca.wallet.app.android.MainActivity +import dgca.wallet.app.android.base.BindingFragment import dgca.wallet.app.android.databinding.FragmentCertificatesBinding import java.io.File @AndroidEntryPoint -class CertificatesFragment : Fragment(), CertificateCardsAdapter.CertificateCardClickListener, +class CertificatesFragment : BindingFragment(), + CertificateCardsAdapter.CertificateCardClickListener, CertificateCardsAdapter.FileCardClickListener { private val viewModel by viewModels() - private var _binding: FragmentCertificatesBinding? = null - private val binding get() = _binding!! override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) (activity as MainActivity).clearBackground() } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - (activity as MainActivity).disableBackButton() - _binding = FragmentCertificatesBinding.inflate(inflater, container, false) - return binding.root - } + override fun onCreateBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentCertificatesBinding = + FragmentCertificatesBinding.inflate(inflater, container, false) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + + (activity as MainActivity).disableBackButton() requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { requireActivity().finish() } binding.scanCode.setOnClickListener { showAddNewDialog() } - viewModel.certificates.observe(viewLifecycleOwner, { - setCertificateCards(it) - }) - viewModel.inProgress.observe(viewLifecycleOwner, { - binding.progressView.isVisible = it - }) + viewModel.certificates.observe(viewLifecycleOwner, { setCertificateCards(it) }) + viewModel.inProgress.observe(viewLifecycleOwner, { binding.progressView.isVisible = it }) + viewModel.fetchCertificates() setFragmentResultListener(AddNewBottomDialogFragment.REQUEST_KEY) { key, bundle -> @@ -104,11 +99,6 @@ class CertificatesFragment : Fragment(), CertificateCardsAdapter.CertificateCard } } - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - private fun showCodeReader() { val action = CertificatesFragmentDirections.actionCertificatesFragmentToCodeReaderFragment() findNavController().navigate(action) diff --git a/app/src/main/java/dgca/wallet/app/android/certificate/claim/ClaimCertificateFragment.kt b/app/src/main/java/dgca/wallet/app/android/certificate/claim/ClaimCertificateFragment.kt index e15a22b1..12e89e9b 100644 --- a/app/src/main/java/dgca/wallet/app/android/certificate/claim/ClaimCertificateFragment.kt +++ b/app/src/main/java/dgca/wallet/app/android/certificate/claim/ClaimCertificateFragment.kt @@ -28,24 +28,23 @@ import android.view.View import android.view.ViewGroup import android.widget.Toast import androidx.core.view.isVisible -import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint import dgca.wallet.app.android.R +import dgca.wallet.app.android.base.BindingFragment import dgca.wallet.app.android.data.CertificateModel import dgca.wallet.app.android.data.getCertificateListData import dgca.wallet.app.android.databinding.FragmentCertificateClaimBinding @AndroidEntryPoint -class ClaimCertificateFragment : Fragment() { +class ClaimCertificateFragment : BindingFragment() { private val args by navArgs() private val viewModel by viewModels() - private var _binding: FragmentCertificateClaimBinding? = null - private val binding get() = _binding!! + private lateinit var adapter: CertListAdapter override fun onCreate(savedInstanceState: Bundle?) { @@ -53,10 +52,8 @@ class ClaimCertificateFragment : Fragment() { adapter = CertListAdapter(layoutInflater) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - _binding = FragmentCertificateClaimBinding.inflate(inflater, container, false) - return binding.root - } + override fun onCreateBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentCertificateClaimBinding = + FragmentCertificateClaimBinding.inflate(inflater, container, false) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -88,19 +85,14 @@ class ClaimCertificateFragment : Fragment() { viewModel.init(args.qrCodeText) } - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - private fun CertificateModel.getType() = when { - this.vaccinations?.isNotEmpty() == true -> getString( + vaccinations?.isNotEmpty() == true -> getString( R.string.vaccination, this.vaccinations.first().doseNumber.toString(), this.vaccinations.first().totalSeriesOfDoses.toString() ) - this.recoveryStatements?.isNotEmpty() == true -> getString(R.string.recovery) - this.tests?.isNotEmpty() == true -> getString(R.string.test) + recoveryStatements?.isNotEmpty() == true -> getString(R.string.recovery) + tests?.isNotEmpty() == true -> getString(R.string.test) else -> "" } diff --git a/app/src/main/java/dgca/wallet/app/android/certificate/claim/ClaimCertificateViewModel.kt b/app/src/main/java/dgca/wallet/app/android/certificate/claim/ClaimCertificateViewModel.kt index eab62f64..fcb543ca 100644 --- a/app/src/main/java/dgca/wallet/app/android/certificate/claim/ClaimCertificateViewModel.kt +++ b/app/src/main/java/dgca/wallet/app/android/certificate/claim/ClaimCertificateViewModel.kt @@ -146,7 +146,11 @@ class ClaimCertificateViewModel @Inject constructor( ) val config = configRepository.local().getConfig() - claimResult = walletRepository.claimCertificate(config.getClaimUrl(BuildConfig.VERSION_NAME), prefixValidationService.encode(qrCode), request) + claimResult = walletRepository.claimCertificate( + config.getClaimUrl(BuildConfig.VERSION_NAME), + prefixValidationService.encode(qrCode), + request + ) } _inProgress.value = false claimResult?.success?.let { _event.value = Event(ClaimCertEvent.OnCertClaimed(true)) } diff --git a/app/src/main/java/dgca/wallet/app/android/certificate/view/certificate/ViewCertificateFragment.kt b/app/src/main/java/dgca/wallet/app/android/certificate/view/certificate/ViewCertificateFragment.kt index 7f4ec948..b55f28b1 100644 --- a/app/src/main/java/dgca/wallet/app/android/certificate/view/certificate/ViewCertificateFragment.kt +++ b/app/src/main/java/dgca/wallet/app/android/certificate/view/certificate/ViewCertificateFragment.kt @@ -22,34 +22,43 @@ package dgca.wallet.app.android.certificate.view.certificate +import android.content.BroadcastReceiver +import android.content.Context import android.content.Intent +import android.content.IntentFilter +import android.content.pm.PackageManager +import android.nfc.NfcAdapter import android.os.Bundle import android.util.DisplayMetrics import android.view.* import android.widget.Toast import androidx.core.view.isVisible -import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint import dgca.wallet.app.android.R +import dgca.wallet.app.android.base.BindingFragment import dgca.wallet.app.android.certificate.claim.CertListAdapter import dgca.wallet.app.android.certificate.claim.bindText import dgca.wallet.app.android.data.CertificateModel import dgca.wallet.app.android.data.getCertificateListData import dgca.wallet.app.android.databinding.FragmentCertificateViewBinding +import dgca.wallet.app.android.nfc.DCCApduService +import dgca.wallet.app.android.nfc.DCCApduService.Companion.NFC_NDEF_KEY +import dgca.wallet.app.android.nfc.showTurnOnNfcDialog +import timber.log.Timber import javax.inject.Inject - @AndroidEntryPoint -class ViewCertificateFragment : Fragment() { +class ViewCertificateFragment : BindingFragment() { + private val args by navArgs() private val viewModel by viewModels() - private var _binding: FragmentCertificateViewBinding? = null - private val binding get() = _binding!! + private lateinit var adapter: CertListAdapter + private var nfcAdapter: NfcAdapter? = null @Inject lateinit var shareImageIntentProvider: ShareImageIntentProvider @@ -60,13 +69,14 @@ class ViewCertificateFragment : Fragment() { adapter = CertListAdapter(layoutInflater) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - _binding = FragmentCertificateViewBinding.inflate(inflater, container, false) - return binding.root - } + override fun onCreateBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentCertificateViewBinding = + FragmentCertificateViewBinding.inflate(inflater, container, false) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + + nfcAdapter = NfcAdapter.getDefaultAdapter(requireContext()) + val displayMetrics = DisplayMetrics() requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics) val minEdge = displayMetrics.widthPixels * 0.9 @@ -115,6 +125,19 @@ class ViewCertificateFragment : Fragment() { viewModel.sharePdfFile.observe(viewLifecycleOwner) { event -> event.getContentIfNotHandled()?.let { launchSharing(it) } } + + binding.nfcAction.setOnCheckedChangeListener { _, isChecked -> + if (isChecked) { + initNFCFunction() + } else { + stopNfcService() + } + } + } + + override fun onPause() { + super.onPause() + stopNfcService() } override fun onPrepareOptionsMenu(menu: Menu) { @@ -169,4 +192,56 @@ class ViewCertificateFragment : Fragment() { is ViewCertificateViewModel.ViewCertEvent.OnCertDeleted -> findNavController().popBackStack() } } + + private fun initNFCFunction() { + if (!requireActivity().packageManager.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) { + binding.nfcStatus.text = getString(R.string.no_nfc) + return + } + + if (nfcAdapter?.isEnabled == true) { + initNfcService() + } else { + showTurnOnNfcDialog() + } + } + + private fun initNfcService() { + val certificate = viewModel.certificate.value?.certificatesCard + val intent = Intent(requireContext(), DCCApduService::class.java) + intent.putExtra(NFC_NDEF_KEY, certificate?.qrCodeText) + requireContext().startService(intent) + + val filter = IntentFilter(NFC_BROADCAST) + requireContext().registerReceiver(nfcReceiver, filter) + } + + private fun stopNfcService() { + if (nfcAdapter?.isEnabled == true) { + requireContext().stopService(Intent(requireContext(), DCCApduService::class.java)) + } + binding.nfcAction.isChecked = false + try { + requireContext().unregisterReceiver(nfcReceiver) + } catch (ex: Exception) { + Timber.d("nfcReceiver not registered.") + } + } + + private val nfcReceiver = object : BroadcastReceiver() { + + override fun onReceive(context: Context, intent: Intent) { + if (intent.hasExtra(NFC_EXTRA_DCC_SENT)) { + if (intent.getBooleanExtra(NFC_EXTRA_DCC_SENT, false)) { + viewModel.onCertificateShared() + Toast.makeText(requireContext(), "DCC was sent successfully", Toast.LENGTH_SHORT).show() + } + } + } + } + + companion object { + const val NFC_BROADCAST = "dgca.wallet.app.android.certificate.view.nfc_broadcast" + const val NFC_EXTRA_DCC_SENT = "nfc_dcc_sent" + } } \ No newline at end of file diff --git a/app/src/main/java/dgca/wallet/app/android/certificate/view/certificate/ViewCertificateViewModel.kt b/app/src/main/java/dgca/wallet/app/android/certificate/view/certificate/ViewCertificateViewModel.kt index 8f0b5316..c1525cec 100644 --- a/app/src/main/java/dgca/wallet/app/android/certificate/view/certificate/ViewCertificateViewModel.kt +++ b/app/src/main/java/dgca/wallet/app/android/certificate/view/certificate/ViewCertificateViewModel.kt @@ -156,6 +156,10 @@ class ViewCertificateViewModel @Inject constructor( } } + fun onCertificateShared() { +// TODO: store state to DB if needed. Or show dialog with delete option. + } + sealed class ViewCertEvent { data class OnCertDeleted(val isDeleted: Boolean) : ViewCertEvent() } diff --git a/app/src/main/java/dgca/wallet/app/android/nfc/DCCApduService.kt b/app/src/main/java/dgca/wallet/app/android/nfc/DCCApduService.kt new file mode 100644 index 00000000..8a3e160a --- /dev/null +++ b/app/src/main/java/dgca/wallet/app/android/nfc/DCCApduService.kt @@ -0,0 +1,290 @@ +/* + * ---license-start + * eu-digital-green-certificates / dgca-verifier-app-android + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + * + * Created by mykhailo.nester on 17/08/2021, 19:30 + */ + +package dgca.wallet.app.android.nfc + +import android.app.Service +import android.content.Intent +import android.nfc.NdefMessage +import android.nfc.NdefRecord +import android.nfc.cardemulation.HostApduService +import android.os.Bundle +import dgca.wallet.app.android.certificate.view.certificate.ViewCertificateFragment +import timber.log.Timber +import java.io.UnsupportedEncodingException +import java.math.BigInteger +import java.util.* + +@Suppress("PrivatePropertyName") +class DCCApduService : HostApduService() { + + private val APDU_SELECT = byteArrayOf( + 0x00.toByte(), // CLA - Class - Class of instruction + 0xA4.toByte(), // INS - Instruction - Instruction code + 0x04.toByte(), // P1 - Parameter 1 - Instruction parameter 1 + 0x00.toByte(), // P2 - Parameter 2 - Instruction parameter 2 + 0x07.toByte(), // Lc field - Number of bytes present in the data field of the command + 0xD2.toByte(), + 0x76.toByte(), + 0x00.toByte(), + 0x00.toByte(), + 0x85.toByte(), + 0x01.toByte(), + 0x01.toByte(), // NDEF Tag Application name + 0x00.toByte() // Le field - Maximum number of bytes expected in the data field of the response to the command + ) + + private val CAPABILITY_CONTAINER_OK = byteArrayOf( + 0x00.toByte(), // CLA - Class - Class of instruction + 0xa4.toByte(), // INS - Instruction - Instruction code + 0x00.toByte(), // P1 - Parameter 1 - Instruction parameter 1 + 0x0c.toByte(), // P2 - Parameter 2 - Instruction parameter 2 + 0x02.toByte(), // Lc field - Number of bytes present in the data field of the command + 0xe1.toByte(), 0x03.toByte() // file identifier of the CC file + ) + + private val READ_CAPABILITY_CONTAINER = byteArrayOf( + 0x00.toByte(), // CLA - Class - Class of instruction + 0xb0.toByte(), // INS - Instruction - Instruction code + 0x00.toByte(), // P1 - Parameter 1 - Instruction parameter 1 + 0x00.toByte(), // P2 - Parameter 2 - Instruction parameter 2 + 0x0f.toByte() // Lc field - Number of bytes present in the data field of the command + ) + + // In the scenario that we have done a CC read, the same byte[] match + // for ReadBinary would trigger and we don't want that in succession + private var READ_CAPABILITY_CONTAINER_CHECK = false + + private val READ_CAPABILITY_CONTAINER_RESPONSE = byteArrayOf( + 0x00.toByte(), 0x11.toByte(), // CCLEN length of the CC file + 0x20.toByte(), // Mapping Version 2.0 + 0xFF.toByte(), 0xFF.toByte(), // MLe maximum + 0xFF.toByte(), 0xFF.toByte(), // MLc maximum + 0x04.toByte(), // T field of the NDEF File Control TLV + 0x06.toByte(), // L field of the NDEF File Control TLV + 0xE1.toByte(), 0x04.toByte(), // File Identifier of NDEF file + 0xFF.toByte(), 0xFE.toByte(), // Maximum NDEF file size of 65534 bytes + 0x00.toByte(), // Read access without any security + 0xFF.toByte(), // Write access without any security + 0x90.toByte(), 0x00.toByte() // A_OKAY + ) + + private val NDEF_SELECT_OK = byteArrayOf( + 0x00.toByte(), // CLA - Class - Class of instruction + 0xa4.toByte(), // Instruction byte (INS) for Select command + 0x00.toByte(), // Parameter byte (P1), select by identifier + 0x0c.toByte(), // Parameter byte (P1), select by identifier + 0x02.toByte(), // Lc field - Number of bytes present in the data field of the command + 0xE1.toByte(), 0x04.toByte() // file identifier of the NDEF file retrieved from the CC file + ) + + private val NDEF_READ_BINARY = byteArrayOf( + 0x00.toByte(), // Class byte (CLA) + 0xb0.toByte() // Instruction byte (INS) for ReadBinary command + ) + + private val NDEF_READ_BINARY_NLEN = byteArrayOf( + 0x00.toByte(), // Class byte (CLA) + 0xb0.toByte(), // Instruction byte (INS) for ReadBinary command + 0x00.toByte(), 0x00.toByte(), // Parameter byte (P1, P2), offset inside the CC file + 0x02.toByte() // Le field + ) + + private val A_OKAY = byteArrayOf( + 0x90.toByte(), // SW1 Status byte 1 - Command processing status + 0x00.toByte() // SW2 Status byte 2 - Command processing qualifier + ) + + private val A_ERROR = byteArrayOf( + 0x6A.toByte(), // SW1 Status byte 1 - Command processing status + 0x82.toByte() // SW2 Status byte 2 - Command processing qualifier + ) + + private val ndefId = byteArrayOf(0xE1.toByte(), 0x04.toByte()) + + private var ndefUri = NdefMessage(createTextRecord(Locale.getDefault().language, "", ndefId)) + private var ndefUriBytes = ndefUri.toByteArray() + private var ndefUriLength = fillByteArrayToFixedDimension( + BigInteger.valueOf(ndefUriBytes.size.toLong()).toByteArray(), 2 + ) + + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + if (intent.hasExtra(NFC_NDEF_KEY)) { + ndefUri = + NdefMessage(createTextRecord(Locale.getDefault().language, intent.getStringExtra(NFC_NDEF_KEY) ?: "", ndefId)) + ndefUriBytes = ndefUri.toByteArray() + ndefUriLength = fillByteArrayToFixedDimension( + BigInteger.valueOf(ndefUriBytes.size.toLong()).toByteArray(), 2 + ) + } + + Timber.i("onStartCommand() | NDEF$ndefUri") + + return Service.START_STICKY + } + + override fun onDeactivated(reason: Int) { + Timber.d("Deactivated: $reason") + } + + override fun processCommandApdu(commandApdu: ByteArray, extras: Bundle?): ByteArray { + // The following flow is based on Appendix E "Example of Mapping Version 2.0 Command Flow" + // in the NFC Forum specification + Timber.i("processCommandApdu() | incoming commandApdu: ${commandApdu.toHex()}") + + // First command: NDEF Tag Application select (Section 5.5.2 in NFC Forum spec) + if (APDU_SELECT.contentEquals(commandApdu)) { + Timber.i("APDU_SELECT triggered. Our Response: ${A_OKAY.toHex()}") + return A_OKAY + } + + // Second command: Capability Container select (Section 5.5.3 in NFC Forum spec) + if (CAPABILITY_CONTAINER_OK.contentEquals(commandApdu)) { + Timber.i("CAPABILITY_CONTAINER_OK triggered. Our Response: ${A_OKAY.toHex()}") + return A_OKAY + } + + // Third command: ReadBinary data from CC file (Section 5.5.4 in NFC Forum spec) + if (READ_CAPABILITY_CONTAINER.contentEquals(commandApdu) && !READ_CAPABILITY_CONTAINER_CHECK) { + Timber.i("READ_CAPABILITY_CONTAINER triggered. Our Response: ${READ_CAPABILITY_CONTAINER_RESPONSE.toHex()}") + READ_CAPABILITY_CONTAINER_CHECK = true + return READ_CAPABILITY_CONTAINER_RESPONSE + } + + // Fourth command: NDEF Select command (Section 5.5.5 in NFC Forum spec) + if (NDEF_SELECT_OK.contentEquals(commandApdu)) { + Timber.i("NDEF_SELECT_OK triggered. Our Response: ${A_OKAY.toHex()}") + return A_OKAY + } + + if (NDEF_READ_BINARY_NLEN.contentEquals(commandApdu)) { + // Build our response + val response = ByteArray(ndefUriLength.size + A_OKAY.size) + System.arraycopy(ndefUriLength, 0, response, 0, ndefUriLength.size) + System.arraycopy(A_OKAY, 0, response, ndefUriLength.size, A_OKAY.size) + + Timber.i("NDEF_READ_BINARY_NLEN triggered. Our Response: ${response.toHex()}") + + READ_CAPABILITY_CONTAINER_CHECK = false + return response + } + + if (commandApdu.sliceArray(0..1).contentEquals(NDEF_READ_BINARY)) { + val offset = commandApdu.sliceArray(2..3).toHex().toInt(16) + val length = commandApdu.sliceArray(4..4).toHex().toInt(16) + + val fullResponse = ByteArray(ndefUriLength.size + ndefUriBytes.size) + System.arraycopy(ndefUriLength, 0, fullResponse, 0, ndefUriLength.size) + System.arraycopy( + ndefUriBytes, + 0, + fullResponse, + ndefUriLength.size, + ndefUriBytes.size + ) + + Timber.i("NDEF_READ_BINARY triggered. Full data: ${fullResponse.toHex()}") + Timber.i("READ_BINARY - OFFSET: $offset - LEN: $length") + + val slicedResponse = fullResponse.sliceArray(offset until fullResponse.size) + + // Build our response + val realLength = if (slicedResponse.size <= length) { + slicedResponse.size + } else { + length + } + val response = ByteArray(realLength + A_OKAY.size) + + System.arraycopy(slicedResponse, 0, response, 0, realLength) + System.arraycopy(A_OKAY, 0, response, realLength, A_OKAY.size) + + val responseHex = response.toHex() + val okHex = A_OKAY.toHex() + if (responseHex.contains(okHex)) { + val recordWithOkMessage = ndefUri.records[0].payload.toHex() + okHex + if (recordWithOkMessage.contains(responseHex)) { + onDccShared() + Timber.i("NDEF_READ_BINARY triggered. Full DCC sent") + } + } + Timber.i("NDEF_READ_BINARY triggered. Our Response: $responseHex") + + READ_CAPABILITY_CONTAINER_CHECK = false + return response + } + + // We're doing something outside our scope + Timber.i("processCommandApdu() | out of scope") + return A_ERROR + } + + private fun onDccShared() { + val intent = Intent(ViewCertificateFragment.NFC_BROADCAST).apply { + putExtra(ViewCertificateFragment.NFC_EXTRA_DCC_SENT, true) + } + sendBroadcast(intent) + } + + private fun createTextRecord(language: String, text: String, id: ByteArray): NdefRecord { + val languageBytes: ByteArray + val textBytes: ByteArray + try { + languageBytes = language.toByteArray(Charsets.US_ASCII) + textBytes = text.toByteArray(Charsets.UTF_8) + } catch (e: UnsupportedEncodingException) { + throw AssertionError(e) + } + + val recordPayload = ByteArray(1 + (languageBytes.size and 0x03F) + textBytes.size) + + recordPayload[0] = (languageBytes.size and 0x03F).toByte() + System.arraycopy(languageBytes, 0, recordPayload, 1, languageBytes.size and 0x03F) + System.arraycopy( + textBytes, + 0, + recordPayload, + 1 + (languageBytes.size and 0x03F), + textBytes.size + ) + + return NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, id, recordPayload) + } + + private fun fillByteArrayToFixedDimension(array: ByteArray, fixedSize: Int): ByteArray { + if (array.size == fixedSize) { + return array + } + + val start = byteArrayOf(0x00.toByte()) + val filledArray = ByteArray(start.size + array.size) + System.arraycopy(start, 0, filledArray, 0, start.size) + System.arraycopy(array, 0, filledArray, start.size, array.size) + return fillByteArrayToFixedDimension(filledArray, fixedSize) + } + + companion object { + const val NFC_NDEF_KEY = "ndefMessage" + const val NFC_TAG_DCC = "DCC:" + const val NFC_TAG_TAN = "TAN:" + } +} \ No newline at end of file diff --git a/app/src/main/java/dgca/wallet/app/android/nfc/NdefParser.kt b/app/src/main/java/dgca/wallet/app/android/nfc/NdefParser.kt new file mode 100644 index 00000000..a269558b --- /dev/null +++ b/app/src/main/java/dgca/wallet/app/android/nfc/NdefParser.kt @@ -0,0 +1,83 @@ +/* + * ---license-start + * eu-digital-green-certificates / dgca-verifier-app-android + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + * + * Created by mykhailo.nester on 17/08/2021, 18:52 + */ + +package dgca.wallet.app.android.nfc + +import android.nfc.NdefMessage +import android.nfc.NdefRecord +import timber.log.Timber +import java.io.UnsupportedEncodingException +import java.util.* +import kotlin.experimental.and + +object NdefParser { + + fun parse(message: NdefMessage): List = getRecords(message.records) + + private fun getRecords(records: Array): List = + records.map { + it.parse() ?: object : ParsedNdefRecord { + override fun str(): String { + return String(it.payload) + } + } + } +} + +fun NdefRecord.parse(): ParsedNdefRecord? { + return if (tnf == NdefRecord.TNF_WELL_KNOWN && Arrays.equals(type, NdefRecord.RTD_TEXT)) { + try { + val recordPayload = payload + + /* + * payload[0] contains the "Status Byte Encodings" field, per the + * NFC Forum "Text Record Type Definition" section 3.2.1. + * + * bit7 is the Text Encoding Field. + * + * if (Bit_7 == 0): The text is encoded in UTF-8 if (Bit_7 == 1): + * The text is encoded in UTF16 + * + * Bit_6 is reserved for future use and must be set to zero. + * + * Bits 5 to 0 are the length of the IANA language code. + */ + val textEncoding = if (recordPayload[0] and 128.toByte() == 0.toByte()) { + Charsets.UTF_8 + } else { + Charsets.UTF_16 + } + + val languageCodeLength: Int = (recordPayload[0] and 63.toByte()).toInt() + val text = String( + recordPayload, languageCodeLength + 1, + recordPayload.size - languageCodeLength - 1, textEncoding + ) + return TextRecord(text) + } catch (e: UnsupportedEncodingException) { + Timber.w("We got a malformed tag.") + return null + } + } else { + null + } +} \ No newline at end of file diff --git a/app/src/main/java/dgca/wallet/app/android/nfc/ParsedNdefRecord.kt b/app/src/main/java/dgca/wallet/app/android/nfc/ParsedNdefRecord.kt new file mode 100644 index 00000000..ecb4f5bd --- /dev/null +++ b/app/src/main/java/dgca/wallet/app/android/nfc/ParsedNdefRecord.kt @@ -0,0 +1,27 @@ +/* + * ---license-start + * eu-digital-green-certificates / dgca-verifier-app-android + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + * + * Created by mykhailo.nester on 17/08/2021, 18:52 + */ + +package dgca.wallet.app.android.nfc + +interface ParsedNdefRecord { + fun str(): String +} \ No newline at end of file diff --git a/app/src/main/java/dgca/wallet/app/android/nfc/TextRecord.kt b/app/src/main/java/dgca/wallet/app/android/nfc/TextRecord.kt new file mode 100644 index 00000000..f5d3a9b8 --- /dev/null +++ b/app/src/main/java/dgca/wallet/app/android/nfc/TextRecord.kt @@ -0,0 +1,30 @@ +/* + * ---license-start + * eu-digital-green-certificates / dgca-verifier-app-android + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + * + * Created by mykhailo.nester on 17/08/2021, 18:54 + */ + +package dgca.wallet.app.android.nfc + +class TextRecord( + val text: String +) : ParsedNdefRecord { + + override fun str(): String = text +} \ No newline at end of file diff --git a/app/src/main/java/dgca/wallet/app/android/nfc/Util.kt b/app/src/main/java/dgca/wallet/app/android/nfc/Util.kt new file mode 100644 index 00000000..59d36377 --- /dev/null +++ b/app/src/main/java/dgca/wallet/app/android/nfc/Util.kt @@ -0,0 +1,59 @@ +/* + * ---license-start + * eu-digital-green-certificates / dgca-verifier-app-android + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + * + * Created by mykhailo.nester on 17/08/2021, 18:13 + */ + +package dgca.wallet.app.android.nfc + +import android.content.Intent +import android.provider.Settings +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.Fragment +import dgca.wallet.app.android.R + +private val HEX_CHARS = "0123456789ABCDEF".toCharArray() + +fun ByteArray.toHex(): String { + val result = StringBuffer() + + forEach { + val octet = it.toInt() + val firstIndex = (octet and 0xF0).ushr(4) + val secondIndex = octet and 0x0F + result.append(HEX_CHARS[firstIndex]) + result.append(HEX_CHARS[secondIndex]) + } + + return result.toString() +} + + +fun Fragment.showTurnOnNfcDialog() { + val nfcDialog = AlertDialog.Builder(requireContext()) + .setTitle(getString(R.string.nfc_turn_on_title)) + .setMessage(getString(R.string.nfc_turn_on_message)) + .setPositiveButton(getString(R.string.nfc_turn_on_positive)) { dialog, _ -> + startActivity(Intent(Settings.ACTION_NFC_SETTINGS)) + dialog.dismiss() + } + .setNegativeButton(getString(R.string.nfc_turn_on_negative)) { dialog, _ -> dialog.dismiss() } + .create() + nfcDialog.show() +} \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/icon_check.png b/app/src/main/res/drawable-hdpi/icon_check.png deleted file mode 100644 index 6189a45fb020af6b01419062a0461e399b771808..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1234 zcmV;@1TFiCP)p00004b3#c}2nYxW zdMn3VfvP_E-aJPc1$UU*bW zdGTaQC`pk^Aq^RFzoa}6Dat%}5G5qH@cIA0)*j#GT=v;}o$otmR-Mk7Z}whm{nlD% z?|t?z4CqHvEEdNA=QOU(g+k#?99ki+L0%|`%UMETz@;H$t5?&M)|3Ap-C)C;+*J7jTyPgoyI~E&*V>3dt=zz(;}Pehj#Q z{fUP*R6ut*eJ7W_rK__;5IQD=qNM!V7p09GtG0@h)V1N4dVtm3_UytuqgYI?%WWk$a_> zi5gX~GYmgo!_};4rzYb@cWmYb{17_4!!;8D=v8#APnCdQ#SgO;^&f+-ZSa{3n;MCP zheKP@<7ip0w|JhT9e|X;xrF6XHV{0r>;?P?8dV2~0bm92?k%u0R7zbl;{@LVAOA)< z7?^oDv5`DRshF> z!~#1*(23vI+TiQq(;pj$Dl#wNr_gbbO=q-uEftU`00MpieHU5Uv1xV@jaJ)&S7aUz zt-ufG6M!3s7=UIN{b1u^ir{PD12-(JrIdL%#0*@*HQ2gmWy6>Fvn4J7utWW{qnYt5 z(6>A(cq#J&&JyH1=pzikEI9EoNQr=-!~a-eT0-!Zu=&eHA>o;cWiFiq`f!h&hXpkO zfE~V4ZAx~irWe=V$I>*Nhuq%{$M} zNA8VHRSLip=;0Lh6&Gg@PM=yr@TCC%;j6H3s*|lEZU;~HRVw`-ihH;yn z9M1n-2XH-5FR557q-8V~o`z8YEx2)S&36fG!$I(QVasG21-5Oh;3WWDO7a`P_2b9J zr^xRE8wE+)Si?&IR^iT%lemqJBtDq7R$qoLio^OOf|mfa<9-}h;=U1%C)yLx3V;&; z#(?c`;INVtPP;)Gww>Um0`}m(&(n!s^mpC|^tA0)x~PByaA23C69o6(0d}-q>gJqz zV-x{k`SUeMA`J)Ef@c6Z0CFI-5!FKv8-rnOyfZKFKc_xUft(7XB$eYX*8r%%O8~g| z;UXw(UoF*xPL}|v!Ak(R{N*y3%i(e%I0rXQYnc&McnLuDhA0m+c&L#naQdnaF99$c wqD`yDj>CV)TB9JM&~4|l_GE|$>+E&u=k07*qoM6N<$g8oh{?f?J) diff --git a/app/src/main/res/drawable-hdpi/icon_error.png b/app/src/main/res/drawable-hdpi/icon_error.png deleted file mode 100644 index fee6212e0a8583e2f3cb2cdeaf7a674c68817310..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 801 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 diff --git a/app/src/main/res/drawable-ldpi/icon_large_error.png b/app/src/main/res/drawable-ldpi/icon_large_error.png deleted file mode 100644 index ee18511ed88f0b0f5d76281bec470a97b99fecb2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 836 zcmV-K1H1f*P)feMmT03F1sfJX(G4on4* z3Q&$A2P++LCy&XjpT)EFZ0*m??D{ZzFE0lq&+606j#iR2x~&OV^;j2BkX}Y8h=L4H z5}F~JA(|mkjZ{lUOGZmZOD0M{OGZm3Dq*zCaF@)ys`p=wxyIkE@L!9*3GfVg>zUBWCV{WY7U;9;nZ^vyjr4DY}@yoam7`I;C2LOH` z%7hY^R5H(1J;gzN**E|plx-N@81?`Tj@srW6@?1&h#;q1gEOP}Fx)sSz!{}vNg%(g zdS19ez zjI1CjD7i7RgSddWAiTl_p?ktMxZxA9zaxN}x;F8}>Hl4PQe6!g+FIX_Nn8-dBCPJ8 z4I~<)8)ascEFj4k1yIC6$QYSXL_tC^+ysgPIrP?khVl^u3C37H>B4tA=aNs}yx7Yf zL_lIOy4D(eM=O{q>>#Na(YN_RD6AkKcF%!C@34ANZ+mo1dqO}(VtaoIppPiWVU zQ;^;jQ4j?gE>g`9%@ECys79tGqa~vyqa_n1pe3Uv6O}O9Wwd056XrkWi56pIsp&8P O0000I diff --git a/app/src/main/res/drawable-mdpi/icon_check.png b/app/src/main/res/drawable-mdpi/icon_check.png deleted file mode 100644 index 788542744bfa572fc061fd193dc317eb8855d9be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 762 zcmVv$B3wu-cP(;JsTqh1H))h%AR2`(%#DKE ziqak0l+MhB>!jZdXgAsy54|%PC%K1mbOw>P$2uD zn@27hDEG1LE*mJ2JtSO81?hP|$^r^x7l0p;OXf-fDL%P_D&z{es?tJ6f)c!*n~TUZ zwuXO(1Sd?1(@DqIQXxY@6>^=M4ahcf7g*7zrjW9=TsAl!VEcf4#o-RN6GQJn`gT&$ zdlp#YwBz#)sWw7BCeN2JpJUJn$RMm#b|)?|aaytYl_liy(8oafY+G8{NLYpZiQSdB z2MXMm;f}1NZ|5b148k^3*u)zEz~gN|3-lHRLH!od$Q%vM?-L zOdMxPkfERo+lH3?(DKg`1zR#?B&fo6puZ!QQ;!RKL`QF$Iq3#E^O(nO@DK-?_?B~| zpFao$_PON>!!0MRr6!Xjwlhn0A6x spjz2U^hh}%Qw_bd8_2m-4l=p7|NdR_68oeo2mk;807*qoM6N<$f+L<$o&W#< diff --git a/app/src/main/res/drawable-mdpi/icon_error.png b/app/src/main/res/drawable-mdpi/icon_error.png deleted file mode 100644 index 6438b00fc646fd35d1ab1349d7ae6683a6aff315..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 589 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 SJ(ZLI0000;gY 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 diff --git a/app/src/main/res/drawable-xxxhdpi/icon_check.png b/app/src/main/res/drawable-xxxhdpi/icon_check.png deleted file mode 100644 index 87aa553522c2011fe62229057fff5ab8542b92f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3440 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 diff --git a/app/src/main/res/layout/dialog_fragment_add_new.xml b/app/src/main/res/layout/dialog_fragment_add_new.xml index f6582f8e..3f22df05 100644 --- a/app/src/main/res/layout/dialog_fragment_add_new.xml +++ b/app/src/main/res/layout/dialog_fragment_add_new.xml @@ -28,6 +28,9 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + +

Wallet App + Host Card Emulator Service + DCC AID Retry Biometric login @@ -59,7 +61,6 @@ Passed Failed Open - Negative Rule Passed for %1$s (see settings) Open for %1$s (see settings) @@ -89,4 +90,13 @@ Pick Image Error importing file Your File + + There is no NFC module on this device + NFC is turned off. + You need turn on NFC module for scanning. Wish turn on it now? + Turn on + Dismiss + NFC Service enabled + NFC Service disabled + NFC status ok diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 414d9c67..1555dbc9 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -52,9 +52,10 @@ \ No newline at end of file diff --git a/app/src/main/res/xml/nfc_application_ids.xml b/app/src/main/res/xml/nfc_application_ids.xml new file mode 100644 index 00000000..0336e2d2 --- /dev/null +++ b/app/src/main/res/xml/nfc_application_ids.xml @@ -0,0 +1,32 @@ + + + + + + + + \ No newline at end of file