@@ -20,19 +20,26 @@ import android.content.BroadcastReceiver
20
20
import android.content.Context
21
21
import android.content.Intent
22
22
import android.content.IntentFilter
23
+ import android.content.pm.PackageManager
24
+ import android.nfc.NfcAdapter
23
25
import android.os.Bundle
26
+ import android.widget.Toast
24
27
import androidx.activity.compose.setContent
25
28
import androidx.activity.enableEdgeToEdge
26
29
import androidx.activity.viewModels
27
30
import androidx.appcompat.app.AppCompatActivity
28
31
import androidx.compose.runtime.collectAsState
29
32
import androidx.core.net.toUri
30
33
import androidx.lifecycle.lifecycleScope
31
- import androidx.navigation.*
34
+ import androidx.navigation.NavHostController
32
35
import androidx.navigation.compose.rememberNavController
33
36
import fr.acinq.lightning.utils.Connection
37
+ import fr.acinq.phoenix.android.components.nfc.NfcState
38
+ import fr.acinq.phoenix.android.components.nfc.NfcStateRepository
39
+ import fr.acinq.phoenix.android.services.HceService
34
40
import fr.acinq.phoenix.android.services.NodeService
35
41
import fr.acinq.phoenix.android.utils.PhoenixAndroidTheme
42
+ import fr.acinq.phoenix.android.utils.nfc.NfcReaderCallback
36
43
import fr.acinq.phoenix.managers.AppConnectionsDaemon
37
44
import kotlinx.coroutines.launch
38
45
import org.slf4j.Logger
@@ -55,6 +62,8 @@ class MainActivity : AppCompatActivity() {
55
62
}
56
63
}
57
64
65
+ private var nfcAdapter: NfcAdapter ? = null
66
+
58
67
override fun onCreate (savedInstanceState : Bundle ? ) {
59
68
enableEdgeToEdge()
60
69
super .onCreate(savedInstanceState)
@@ -63,6 +72,9 @@ class MainActivity : AppCompatActivity() {
63
72
}
64
73
65
74
intent?.fixUri()
75
+ onNewIntent(intent)
76
+
77
+ nfcAdapter = NfcAdapter .getDefaultAdapter(this )
66
78
67
79
// lock screen when screen is off
68
80
val intentFilter = IntentFilter (Intent .ACTION_SCREEN_ON )
@@ -91,17 +103,24 @@ class MainActivity : AppCompatActivity() {
91
103
92
104
override fun onNewIntent (intent : Intent ? ) {
93
105
super .onNewIntent(intent)
94
- // force the intent flag to single top, in order to avoid [handleDeepLink] finish the current activity.
95
- // this would otherwise clear the app view model, i.e. loose the state which virtually reboots the app
96
- // TODO: look into detaching the app state from the activity
97
- log.info(" receive new_intent with data=${intent?.data} " )
98
- intent?.flags = Intent .FLAG_ACTIVITY_SINGLE_TOP
106
+ log.info(" receive new_intent with action=${intent?.action} data=${intent?.data} " )
99
107
100
- intent?.fixUri()
101
- try {
102
- this .navController?.handleDeepLink(intent)
103
- } catch (e: Exception ) {
104
- log.warn(" could not handle deeplink: {}" , e.localizedMessage)
108
+ when (intent?.action) {
109
+ NfcAdapter .ACTION_NDEF_DISCOVERED , NfcAdapter .ACTION_TAG_DISCOVERED -> {
110
+ // ignored
111
+ }
112
+ else -> {
113
+ // force the intent flag to single top, in order to avoid [handleDeepLink] finish the current activity.
114
+ // this would otherwise clear the app view model, i.e. loose the state which virtually reboots the app
115
+ // TODO: look into detaching the app state from the activity
116
+ intent?.flags = Intent .FLAG_ACTIVITY_SINGLE_TOP
117
+ intent?.fixUri()
118
+ try {
119
+ this .navController?.handleDeepLink(intent)
120
+ } catch (e: Exception ) {
121
+ log.warn(" could not handle deeplink: {}" , e.localizedMessage)
122
+ }
123
+ }
105
124
}
106
125
}
107
126
@@ -117,6 +136,71 @@ class MainActivity : AppCompatActivity() {
117
136
tryReconnect()
118
137
}
119
138
139
+ override fun onPause () {
140
+ super .onPause()
141
+ stopNfcReader()
142
+ }
143
+
144
+ fun isNfcReaderAvailable () : Boolean {
145
+ return nfcAdapter?.isEnabled == true
146
+ }
147
+
148
+ fun startNfcReader () {
149
+ if (NfcStateRepository .isEmulating()) {
150
+ Toast .makeText(applicationContext, applicationContext.getString(R .string.nfc_err_busy), Toast .LENGTH_SHORT ).show()
151
+ return
152
+ }
153
+ NfcStateRepository .updateState(NfcState .ShowReader )
154
+ nfcAdapter?.enableReaderMode(this @MainActivity, NfcReaderCallback (onFoundData = {
155
+ runOnUiThread {
156
+ log.info(" nfc reader found valid ndef data, redirecting to send-screen with input=$it " )
157
+ this .navController?.navigate(" ${Screen .Send .route} ?input=$it " )
158
+ }
159
+ }), NfcAdapter .FLAG_READER_NFC_A or NfcAdapter .FLAG_READER_SKIP_NDEF_CHECK , Bundle ())
160
+ }
161
+
162
+ fun stopNfcReader () {
163
+ if (NfcStateRepository .isReading()) {
164
+ NfcStateRepository .updateState(NfcState .Inactive )
165
+ }
166
+ nfcAdapter?.disableReaderMode(this @MainActivity)
167
+ }
168
+
169
+ fun isHceSupported () : Boolean {
170
+ val adapter = nfcAdapter
171
+ return adapter != null && adapter.isEnabled && packageManager.hasSystemFeature(PackageManager .FEATURE_NFC_HOST_CARD_EMULATION )
172
+ }
173
+
174
+ fun startHceService (paymentRequest : String ) {
175
+ if (nfcAdapter == null ) {
176
+ Toast .makeText(this , applicationContext.getString(R .string.nfc_err_not_available), Toast .LENGTH_SHORT ).show()
177
+ return
178
+ }
179
+
180
+ if (nfcAdapter?.isEnabled == false ) {
181
+ Toast .makeText(this , applicationContext.getString(R .string.nfc_err_disabled), Toast .LENGTH_SHORT ).show()
182
+ return
183
+ }
184
+
185
+ if (! packageManager.hasSystemFeature(PackageManager .FEATURE_NFC_HOST_CARD_EMULATION )) {
186
+ Toast .makeText(this , applicationContext.getString(R .string.nfc_err_hce_not_supported), Toast .LENGTH_SHORT ).show()
187
+ return
188
+ }
189
+
190
+ if (NfcStateRepository .isReading()) {
191
+ Toast .makeText(applicationContext, applicationContext.getString(R .string.nfc_err_busy), Toast .LENGTH_SHORT ).show()
192
+ return
193
+ }
194
+
195
+ NfcStateRepository .updateState(NfcState .EmulatingTag (paymentRequest))
196
+ val intent = Intent (this @MainActivity, HceService ::class .java)
197
+ startService(intent)
198
+ }
199
+
200
+ fun stopHceService () {
201
+ stopService(Intent (this @MainActivity, HceService ::class .java))
202
+ }
203
+
120
204
private fun tryReconnect () {
121
205
lifecycleScope.launch {
122
206
(application as ? PhoenixApplication )?.business?.value?.let { business ->
0 commit comments