55#include < QDebug>
66#include < QThread>
77#include < QCoreApplication>
8+ #ifdef Q_OS_ANDROID
9+ #include < QGuiApplication>
10+ #include < QTimer>
11+ #endif
812
913namespace Keycard {
1014
11- // Android NFC timeout extension is now handled via platform/android_nfc_utils.h
12- // and called from CommandSet before long operations like GlobalPlatform factory reset
13-
1415KeycardChannelUnifiedQtNfc::KeycardChannelUnifiedQtNfc (QObject* parent)
1516 : KeycardChannelBackend(parent)
1617 , m_manager(nullptr )
@@ -22,6 +23,15 @@ KeycardChannelUnifiedQtNfc::KeycardChannelUnifiedQtNfc(QObject* parent)
2223 this , &KeycardChannelUnifiedQtNfc::onTargetDetected, Qt::DirectConnection);
2324 connect (m_manager, &QNearFieldManager::targetLost,
2425 this , &KeycardChannelUnifiedQtNfc::onTargetLost, Qt::DirectConnection);
26+ connect (this , &KeycardChannelUnifiedQtNfc::channelStateChanged, this , [this ](ChannelOperationalState state) {
27+ if (state == ChannelOperationalState::WaitingForKeycard) {
28+ m_manager->setUserInformation (" Waiting for keycard. Please hold the keycard near the device." );
29+ } else if (state == ChannelOperationalState::Reading) {
30+ m_manager->setUserInformation (" Reading keycard. Please hold the keycard near the device." );
31+ } else if (state == ChannelOperationalState::Error) {
32+ m_manager->setUserInformation (" Error reading keycard. Please try again." );
33+ }
34+ });
2535}
2636
2737KeycardChannelUnifiedQtNfc::~KeycardChannelUnifiedQtNfc ()
@@ -34,6 +44,48 @@ void KeycardChannelUnifiedQtNfc::startDetection()
3444{
3545 qDebug () << " KeycardChannelUnifiedQtNfc::startDetection()" ;
3646
47+ #ifdef Q_OS_ANDROID
48+ // Android-only: emitted when the OS NFC adapter is toggled on/off (or transitioning).
49+ QObject::connect (m_manager, &QNearFieldManager::adapterStateChanged,
50+ this , &KeycardChannelUnifiedQtNfc::onAdapterStateChanged, Qt::UniqueConnection);
51+ // Qt 6.9 Android NFC uses foreground dispatch and may fail if started before the Activity
52+ // is fully active/resumed. If we call too early, Qt may consider discovery enabled while
53+ // the platform never actually starts delivering tag intents until a background→foreground cycle.
54+ //
55+ // Mitigation: defer startTargetDetection() until the Qt app is ApplicationActive.
56+ auto *app = QCoreApplication::instance ();
57+ auto *guiApp = qobject_cast<QGuiApplication *>(app);
58+ qDebug () << " KeycardChannelUnifiedQtNfc:Verifying app state=" << static_cast <int >(guiApp->applicationState ());
59+
60+ if (guiApp && guiApp->applicationState () != Qt::ApplicationActive) {
61+ qDebug () << " KeycardChannelUnifiedQtNfc: App not active yet, deferring NFC start. state="
62+ << static_cast <int >(guiApp->applicationState ());
63+
64+ if (!m_waitingForAppActive) {
65+ m_waitingForAppActive = true ;
66+ m_appStateConn = connect (guiApp, &QGuiApplication::applicationStateChanged,
67+ this , [this ](Qt::ApplicationState state) {
68+ if (state != Qt::ApplicationActive) {
69+ return ;
70+ }
71+
72+ // One-shot: once active, drop the guard and retry immediately on the event loop.
73+ m_waitingForAppActive = false ;
74+ QObject::disconnect (m_appStateConn);
75+ m_appStateConn = {};
76+
77+ QTimer::singleShot (0 , this , [this ]() {
78+ this ->startDetection ();
79+ });
80+ });
81+ }
82+
83+ // Keep UX consistent: we are logically waiting for a keycard, just not starting NFC yet.
84+ emitChannelState (ChannelOperationalState::WaitingForKeycard);
85+ return ;
86+ }
87+ #endif
88+
3789 if (!m_manager->isSupported (QNearFieldTarget::TagTypeSpecificAccess)) {
3890 emitChannelState (ChannelOperationalState::NotSupported);
3991 emit readerAvailabilityChanged (false );
@@ -49,7 +101,7 @@ void KeycardChannelUnifiedQtNfc::startDetection()
49101 m_detectionActive = false ;
50102 return ;
51103 }
52- // Desktop: Start continuous detection immediately
104+
53105 m_manager->startTargetDetection (QNearFieldTarget::TagTypeSpecificAccess);
54106 m_detectionActive = true ;
55107 emit readerAvailabilityChanged (true );
@@ -58,6 +110,11 @@ void KeycardChannelUnifiedQtNfc::startDetection()
58110
59111void KeycardChannelUnifiedQtNfc::stopDetection ()
60112{
113+ #ifdef Q_OS_ANDROID
114+ QObject::disconnect (m_manager, &QNearFieldManager::adapterStateChanged,
115+ this , &KeycardChannelUnifiedQtNfc::onAdapterStateChanged);
116+ #endif
117+ // iOS NFC session management can be sensitive to threading; serialize with transmit.
61118#ifdef Q_OS_IOS
62119 QMutexLocker locker (&m_transmitMutex);
63120 qDebug () << " KeycardChannelUnifiedQtNfc::stopDetection()" ;
@@ -160,6 +217,44 @@ void KeycardChannelUnifiedQtNfc::onTargetLost(QNearFieldTarget* target)
160217 emit cardRemoved ();
161218}
162219
220+ #ifdef Q_OS_ANDROID
221+ void KeycardChannelUnifiedQtNfc::onAdapterStateChanged (QNearFieldManager::AdapterState state)
222+ {
223+ qDebug () << " KeycardChannelUnifiedQtNfc::onAdapterStateChanged() state=" << static_cast <int >(state);
224+
225+ switch (state) {
226+ case QNearFieldManager::AdapterState::Offline:
227+ case QNearFieldManager::AdapterState::TurningOff: {
228+ m_manager->stopTargetDetection ();
229+ m_detectionActive = false ;
230+ disconnect ();
231+ // NFC is going away: stop scanning and drop any current target.
232+ // Do NOT emit Idle in-between (avoid flicker); go straight to NotAvailable.
233+ emit readerAvailabilityChanged (false );
234+ emitChannelState (ChannelOperationalState::NotAvailable);
235+ break ;
236+ }
237+
238+ case QNearFieldManager::AdapterState::Online: {
239+ emit readerAvailabilityChanged (true );
240+
241+ // If higher-level logic expects us to be scanning, (re)start now.
242+ // Use event-loop deferral to avoid re-entrancy if this signal fires during Qt NFC internals.
243+ QTimer::singleShot (0 , this , [this ]() { this ->setState (ChannelState::WaitingForCard); });
244+ break ;
245+ }
246+
247+ case QNearFieldManager::AdapterState::TurningOn: {
248+ // Transitional state: avoid emitting errors; keep UX in "waiting" if applicable.
249+ if (m_state == ChannelState::WaitingForCard) {
250+ emitChannelState (ChannelOperationalState::WaitingForKeycard);
251+ }
252+ break ;
253+ }
254+ }
255+ }
256+ #endif
257+
163258QString KeycardChannelUnifiedQtNfc::describe (QNearFieldTarget::Error error)
164259{
165260 switch (error) {
0 commit comments