mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-23 02:00:20 +07:00
fixed scaner QR Android
This commit is contained in:
@@ -2,9 +2,11 @@
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDataStream>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QIODevice>
|
||||
#include <QMetaObject>
|
||||
#include <QPointer>
|
||||
#include <QRegularExpression>
|
||||
#include <QTimer>
|
||||
#include <QUuid>
|
||||
@@ -96,6 +98,33 @@ bool tryDecodeLegacyChunkedPairingQrPayload(const QString &t, QString *outUuid)
|
||||
*outUuid = u.toString(QUuid::WithoutBraces);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a pairing session UUID from raw QR text without touching QObject / signals.
|
||||
* Safe from CameraX / JNI threads while AmneziaActivity is stopped (Qt event loop may not run).
|
||||
*/
|
||||
QString extractPairingSessionUuidFromScanText(const QString &raw)
|
||||
{
|
||||
const QString t = raw.trimmed();
|
||||
if (t.startsWith(QStringLiteral("vpn://"), Qt::CaseInsensitive)) {
|
||||
return {};
|
||||
}
|
||||
static const QRegularExpression reV4(QStringLiteral(
|
||||
"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}"));
|
||||
const QRegularExpressionMatch m = reV4.match(t);
|
||||
if (m.hasMatch()) {
|
||||
return m.captured(0);
|
||||
}
|
||||
QString fromLegacy;
|
||||
if (tryDecodeLegacyChunkedPairingQrPayload(t, &fromLegacy)) {
|
||||
return fromLegacy;
|
||||
}
|
||||
const QUuid parsed = QUuid::fromString(t);
|
||||
if (!parsed.isNull()) {
|
||||
return parsed.toString(QUuid::WithoutBraces);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
#if defined(Q_OS_ANDROID)
|
||||
@@ -113,6 +142,15 @@ bool PairingUiController::iosNativePairingQrOverlayBuild() const
|
||||
#endif
|
||||
}
|
||||
|
||||
bool PairingUiController::androidNativePairingQrOverlayBuild() const
|
||||
{
|
||||
#if defined(Q_OS_ANDROID)
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
PairingUiController::PairingUiController(PairingController *pairingController, ServersController *serversController,
|
||||
SubscriptionController *subscriptionController,
|
||||
SecureAppSettingsRepository *appSettingsRepository, QObject *parent)
|
||||
@@ -173,7 +211,7 @@ void PairingUiController::openPairingQrScanner()
|
||||
{
|
||||
#if defined(Q_OS_ANDROID)
|
||||
qInfo() << "[PairingUi] openPairingQrScanner (Android native activity)";
|
||||
AndroidController::instance()->startQrReaderActivity();
|
||||
AndroidController::instance()->startPairingQrReaderActivity();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -319,34 +357,18 @@ bool PairingUiController::applyScannedTextAsPairingUuid(const QString &raw)
|
||||
{
|
||||
const QString t = raw.trimmed();
|
||||
qInfo() << "[PairingUi] scan raw len=" << t.size();
|
||||
if (t.startsWith(QStringLiteral("vpn://"), Qt::CaseInsensitive)) {
|
||||
qInfo() << "[PairingUi] scan rejected: looks like vpn:// bundle, not session UUID";
|
||||
const QString uuid = extractPairingSessionUuidFromScanText(raw);
|
||||
if (uuid.isEmpty()) {
|
||||
if (t.startsWith(QStringLiteral("vpn://"), Qt::CaseInsensitive)) {
|
||||
qInfo() << "[PairingUi] scan rejected: looks like vpn:// bundle, not session UUID";
|
||||
} else {
|
||||
qInfo() << "[PairingUi] scan rejected: no session UUID recognized in payload";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static const QRegularExpression reV4(QStringLiteral(
|
||||
"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}"));
|
||||
const QRegularExpressionMatch m = reV4.match(t);
|
||||
if (m.hasMatch()) {
|
||||
const QString uuid = m.captured(0);
|
||||
qInfo() << "[PairingUi] scan accepted uuid=" << uuid.left(13) << "...";
|
||||
emit pairingUuidFromScan(uuid);
|
||||
return true;
|
||||
}
|
||||
QString fromLegacy;
|
||||
if (tryDecodeLegacyChunkedPairingQrPayload(t, &fromLegacy)) {
|
||||
qInfo() << "[PairingUi] scan accepted legacy chunked QR uuid=" << fromLegacy.left(13) << "...";
|
||||
emit pairingUuidFromScan(fromLegacy);
|
||||
return true;
|
||||
}
|
||||
const QUuid parsed = QUuid::fromString(t);
|
||||
if (!parsed.isNull()) {
|
||||
const QString canon = parsed.toString(QUuid::WithoutBraces);
|
||||
qInfo() << "[PairingUi] scan accepted QUuid::fromString uuid=" << canon.left(13) << "...";
|
||||
emit pairingUuidFromScan(canon);
|
||||
return true;
|
||||
}
|
||||
qInfo() << "[PairingUi] scan rejected: no session UUID recognized in payload";
|
||||
return false;
|
||||
qInfo() << "[PairingUi] scan accepted uuid=" << uuid.left(13) << "...";
|
||||
emit pairingUuidFromScan(uuid);
|
||||
return true;
|
||||
}
|
||||
|
||||
#if defined(Q_OS_ANDROID)
|
||||
@@ -356,25 +378,65 @@ bool PairingUiController::tryConsumeAndroidQrScan(const QString &code)
|
||||
qWarning() << "[PairingUi] tryConsumeAndroidQrScan: no controller (g_pairingUiForAndroidQr null)";
|
||||
return false;
|
||||
}
|
||||
PairingUiController *const ctl = g_pairingUiForAndroidQr;
|
||||
bool consumed = false;
|
||||
const QString codeCopy = code;
|
||||
QObject *const app = QCoreApplication::instance();
|
||||
if (!app) {
|
||||
// Parse on this thread: while CameraActivity is foreground, AmneziaActivity is stopped and the Qt
|
||||
// event loop may not process BlockingQueuedConnection until the user returns — UI would lag behind.
|
||||
if (extractPairingSessionUuidFromScanText(codeCopy).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
// CameraActivity / ML Kit may invoke JNI from a non-Qt thread. Signals and QML must run on the Qt GUI thread.
|
||||
QMetaObject::invokeMethod(
|
||||
app,
|
||||
[ctl, codeCopy, &consumed]() {
|
||||
consumed = ctl->applyScannedTextAsPairingUuid(codeCopy);
|
||||
},
|
||||
Qt::BlockingQueuedConnection);
|
||||
qInfo() << "[PairingUi] tryConsumeAndroidQrScan consumed=" << consumed << "rawLen=" << codeCopy.size();
|
||||
return consumed;
|
||||
PairingUiController *const ctl = g_pairingUiForAndroidQr;
|
||||
QPointer<PairingUiController> ctlPtr(ctl);
|
||||
QTimer::singleShot(0, ctl, [ctlPtr, codeCopy]() {
|
||||
if (!ctlPtr) {
|
||||
return;
|
||||
}
|
||||
ctlPtr->applyScannedTextAsPairingUuid(codeCopy);
|
||||
});
|
||||
qInfo() << "[PairingUi] tryConsumeAndroidQrScan: scheduled apply on Qt thread, rawLen=" << codeCopy.size();
|
||||
return true;
|
||||
}
|
||||
|
||||
void PairingUiController::notifyAndroidPairingQrCameraClosed()
|
||||
{
|
||||
if (g_pairingUiForAndroidQr) {
|
||||
g_pairingUiForAndroidQr->suppressAndroidNativePairingReaderStarts(2000);
|
||||
}
|
||||
}
|
||||
|
||||
void PairingUiController::notifyAndroidPairingQrCameraUserDismissed()
|
||||
{
|
||||
if (!g_pairingUiForAndroidQr) {
|
||||
return;
|
||||
}
|
||||
PairingUiController *const ctl = g_pairingUiForAndroidQr;
|
||||
QPointer<PairingUiController> ptr(ctl);
|
||||
QTimer::singleShot(0, ctl, [ptr]() {
|
||||
if (!ptr) {
|
||||
return;
|
||||
}
|
||||
emit ptr->pairingAndroidNativeQrScannerUserDismissed();
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
void PairingUiController::suppressAndroidNativePairingReaderStarts(int ms)
|
||||
{
|
||||
if (ms <= 0) {
|
||||
return;
|
||||
}
|
||||
#if defined(Q_OS_ANDROID)
|
||||
const qint64 now = QDateTime::currentMSecsSinceEpoch();
|
||||
const qint64 until = now + ms;
|
||||
if (until <= m_androidPairingReaderCooldownUntilEpochMs) {
|
||||
return;
|
||||
}
|
||||
m_androidPairingReaderCooldownUntilEpochMs = until;
|
||||
emit androidPairingReaderCooldownUntilEpochMsChanged();
|
||||
#else
|
||||
Q_UNUSED(ms);
|
||||
#endif
|
||||
}
|
||||
|
||||
QVariantList PairingUiController::tvQrCodes() const
|
||||
{
|
||||
QVariantList list;
|
||||
|
||||
@@ -40,6 +40,14 @@ class PairingUiController : public QObject
|
||||
embeddedPairingQrCameraActiveChanged)
|
||||
/** True only on iOS builds: use native UIWindow QR overlay (not Qt.platform.os, which can differ). */
|
||||
Q_PROPERTY(bool iosNativePairingQrOverlayBuild READ iosNativePairingQrOverlayBuild CONSTANT)
|
||||
/** True only on Android builds: full-screen CameraActivity pairing scanner; QML hides duplicate scan chrome. */
|
||||
Q_PROPERTY(bool androidNativePairingQrOverlayBuild READ androidNativePairingQrOverlayBuild CONSTANT)
|
||||
/**
|
||||
* Epoch ms until which QML should not call openPairingQrScanner again (after native CameraActivity closes).
|
||||
* Android pairing flow only; always 0 on other platforms.
|
||||
*/
|
||||
Q_PROPERTY(qint64 androidPairingReaderCooldownUntilEpochMs READ androidPairingReaderCooldownUntilEpochMs NOTIFY
|
||||
androidPairingReaderCooldownUntilEpochMsChanged)
|
||||
|
||||
public:
|
||||
PairingUiController(PairingController *pairingController, ServersController *serversController,
|
||||
@@ -62,6 +70,7 @@ public:
|
||||
int tvPairingUiPhase() const { return m_tvPairingUiPhase; }
|
||||
bool embeddedPairingQrCameraActive() const { return m_embeddedPairingQrCameraActive; }
|
||||
bool iosNativePairingQrOverlayBuild() const;
|
||||
bool androidNativePairingQrOverlayBuild() const;
|
||||
Q_INVOKABLE void setEmbeddedPairingQrCameraActive(bool active);
|
||||
/** iOS: native dim strip height uses safe bottom + extraPt (see PageSettingsApiQrPairingSend scanDimBleedBottom). No-op elsewhere. */
|
||||
Q_INVOKABLE void syncIosEmbeddedPairingQrNativeBottomExtra(int extraPt);
|
||||
@@ -72,6 +81,10 @@ public:
|
||||
*/
|
||||
Q_INVOKABLE void refreshIosEmbeddedPairingQrChrome();
|
||||
|
||||
qint64 androidPairingReaderCooldownUntilEpochMs() const { return m_androidPairingReaderCooldownUntilEpochMs; }
|
||||
/** Lengthens androidPairingReaderCooldownUntilEpochMs to at least now + ms (Android pairing; no-op elsewhere). */
|
||||
Q_INVOKABLE void suppressAndroidNativePairingReaderStarts(int ms);
|
||||
|
||||
/**
|
||||
* iOS: UIKit UIWindow QR scanner (see iosPairingQrOverlayWindow). Pass translated title/subtitle for native chrome.
|
||||
* No-op on other platforms.
|
||||
@@ -83,6 +96,10 @@ public:
|
||||
|
||||
#if defined(Q_OS_ANDROID)
|
||||
static bool tryConsumeAndroidQrScan(const QString &code);
|
||||
/** JNI from CameraActivity onDestroy: avoid reopening native reader while camera HAL is still releasing. */
|
||||
static void notifyAndroidPairingQrCameraClosed();
|
||||
/** JNI before CameraActivity finish when user pressed back — Qt should reopen native scan (QML shell has no preview). */
|
||||
static void notifyAndroidPairingQrCameraUserDismissed();
|
||||
#endif
|
||||
|
||||
public slots:
|
||||
@@ -132,10 +149,13 @@ signals:
|
||||
/** After requestPairingCameraAccess(): true if OS granted camera access. */
|
||||
void pairingCameraAccessFinished(bool granted);
|
||||
void embeddedPairingQrCameraActiveChanged();
|
||||
void androidPairingReaderCooldownUntilEpochMsChanged();
|
||||
/** iOS native overlay scanner: payload was not a pairing session UUID (toast in QML). */
|
||||
void pairingSendQrScanRejectedInvalidPayload();
|
||||
/** Native overlay back chevron tapped — dismiss scanner and close page from QML. */
|
||||
void pairingIosNativeQrOverlayBackRequested();
|
||||
/** Android CameraActivity: user pressed back — QML should exit pairing send (e.g. closePage), not reopen camera. */
|
||||
void pairingAndroidNativeQrScannerUserDismissed();
|
||||
|
||||
private:
|
||||
void setTvBusy(bool busy);
|
||||
@@ -170,6 +190,7 @@ private:
|
||||
quint64 m_phoneSessionGeneration { 0 };
|
||||
|
||||
bool m_embeddedPairingQrCameraActive = false;
|
||||
qint64 m_androidPairingReaderCooldownUntilEpochMs = 0;
|
||||
};
|
||||
|
||||
#endif // PAIRINGUICONTROLLER_H
|
||||
|
||||
@@ -25,9 +25,17 @@ PageType {
|
||||
/** iOS-only: full-screen UIKit UIWindow scanner (PairingUiController.iosNativePairingQrOverlayBuild — not Qt.platform.os). */
|
||||
readonly property bool useIosNativePairingQrOverlay: PairingUiController.iosNativePairingQrOverlayBuild
|
||||
|
||||
/** Android: full-screen CameraActivity — Qt cannot reliably composite CameraX under QML on some OEMs (e.g. Samsung). */
|
||||
readonly property bool useAndroidNativePairingQrScanner: GC.isMobile() && Qt.platform.os === "android"
|
||||
/** Android: iOS-like flow — titles and camera preview only in CameraActivity; QML hides duplicate scan chrome. */
|
||||
readonly property bool useAndroidNativePairingQrOverlay: PairingUiController.androidNativePairingQrOverlayBuild
|
||||
&& GC.isMobile()
|
||||
&& Qt.platform.os === "android"
|
||||
|
||||
/** Let dimming draw into window chrome (status bar + tab bar) when camera underlay is active. */
|
||||
readonly property bool extendScanDimToScreenEdges: GC.isMobile() && pairingWizardStep === 0
|
||||
&& PairingUiController.embeddedPairingQrCameraActive
|
||||
&& !root.useAndroidNativePairingQrOverlay
|
||||
clip: !extendScanDimToScreenEdges
|
||||
|
||||
/** QQuickWindow (not Item); do not type as Item — breaks binding on Qt 6. */
|
||||
@@ -140,6 +148,8 @@ PageType {
|
||||
property bool awaitingCameraPermissionForScan: false
|
||||
property bool waitingSettingsReturnForScan: false
|
||||
property bool torchOn: false
|
||||
/** Suppress double startActivity when StackView fires both Component.onCompleted and onVisibleChanged. */
|
||||
property int _androidPairingReaderLastStartMs: 0
|
||||
|
||||
Timer {
|
||||
id: pairingCameraKickTimer
|
||||
@@ -171,9 +181,18 @@ PageType {
|
||||
if (!root.visible) {
|
||||
return
|
||||
}
|
||||
/** Confirm step (or transition to it): never reopen native / embedded scanner from stray taps or visibility. */
|
||||
if (root.pairingWizardStep !== 0) {
|
||||
return
|
||||
}
|
||||
if (addDeviceConfirmNavigationScheduled) {
|
||||
return
|
||||
}
|
||||
console.warn("[PairingQrSend] startMobileScanner Qt.platform.os=", Qt.platform.os,
|
||||
"iosNativePairingQrOverlayBuild=", PairingUiController.iosNativePairingQrOverlayBuild,
|
||||
"useIosNativePairingQrOverlay=", root.useIosNativePairingQrOverlay)
|
||||
"useIosNativePairingQrOverlay=", root.useIosNativePairingQrOverlay,
|
||||
"androidNativePairingQrOverlayBuild=", PairingUiController.androidNativePairingQrOverlayBuild,
|
||||
"useAndroidNativePairingQrOverlay=", root.useAndroidNativePairingQrOverlay)
|
||||
if (!PairingUiController.isPairingCameraAccessGranted()) {
|
||||
awaitingCameraPermissionForScan = true
|
||||
PairingUiController.requestPairingCameraAccess()
|
||||
@@ -186,6 +205,23 @@ PageType {
|
||||
/** Do not run pairingCameraKickTimer here: restartCapture during first startRunning races the session (torch needs 2–3 taps). */
|
||||
return
|
||||
}
|
||||
if (root.useAndroidNativePairingQrScanner) {
|
||||
const coolUntil = PairingUiController.androidPairingReaderCooldownUntilEpochMs
|
||||
if (Date.now() < coolUntil) {
|
||||
console.warn("[PairingQrSend] startMobileScanner: skip (native camera cooldown), ms left=",
|
||||
(coolUntil - Date.now()))
|
||||
return
|
||||
}
|
||||
const now = Date.now()
|
||||
if (now - _androidPairingReaderLastStartMs < 700) {
|
||||
console.warn("[PairingQrSend] startMobileScanner: skip duplicate Android CameraActivity within",
|
||||
(now - _androidPairingReaderLastStartMs), "ms")
|
||||
return
|
||||
}
|
||||
_androidPairingReaderLastStartMs = now
|
||||
PairingUiController.openPairingQrScanner()
|
||||
return
|
||||
}
|
||||
PairingUiController.embeddedPairingQrCameraActive = true
|
||||
if (root.useIosStyleNativeQrReader) {
|
||||
// Session must start here, not only after pairingCameraKickTimer (220ms), otherwise
|
||||
@@ -249,13 +285,15 @@ PageType {
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
addDeviceConfirmNavigationScheduled = false
|
||||
/** Only reset confirm flag on scan step; clearing it on confirm breaks guards if visible flickers. */
|
||||
if (pairingWizardStep === 0) {
|
||||
addDeviceConfirmNavigationScheduled = false
|
||||
Qt.callLater(startMobileScanner)
|
||||
}
|
||||
} else {
|
||||
pairingCameraKickTimer.stop()
|
||||
stopMobileScanner()
|
||||
_androidPairingReaderLastStartMs = 0
|
||||
pairingWizardStep = 0
|
||||
waitingSettingsReturnForScan = false
|
||||
if (!keepPhonePairingInBackgroundOnClose && !PairingUiController.phonePairingBusy) {
|
||||
@@ -268,6 +306,10 @@ PageType {
|
||||
if (pairingWizardStep !== 0) {
|
||||
stopMobileScanner()
|
||||
} else if (root.visible) {
|
||||
/**
|
||||
* Android native: use Qt.callLater like iOS — a multi-second Timer delay left the QML scan chrome
|
||||
* visible with an empty (black) viewport until CameraActivity opened.
|
||||
*/
|
||||
Qt.callLater(startMobileScanner)
|
||||
}
|
||||
}
|
||||
@@ -348,10 +390,31 @@ PageType {
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
/** Brief Qt backdrop + back while CameraActivity is starting (native holds title/instructions like iOS overlay). */
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: pairingWizardStep === 0 && root.useAndroidNativePairingQrOverlay
|
||||
color: AmneziaStyle.color.midnightBlack
|
||||
z: 1
|
||||
}
|
||||
BackButtonType {
|
||||
visible: pairingWizardStep === 0 && root.useAndroidNativePairingQrOverlay
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: PageController.safeAreaTopMargin
|
||||
anchors.left: parent.left
|
||||
width: parent.width
|
||||
z: 2
|
||||
backButtonFunction: function() {
|
||||
PageController.closePage()
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: scanStep
|
||||
anchors.fill: parent
|
||||
visible: pairingWizardStep === 0
|
||||
visible: pairingWizardStep === 0 && !root.useAndroidNativePairingQrOverlay
|
||||
/** Extra guard: invisible alone can race one frame on some stacks; deny input off scan step. */
|
||||
enabled: pairingWizardStep === 0 && !root.useAndroidNativePairingQrOverlay
|
||||
|
||||
readonly property real sqSz: Math.floor(Math.min(width, height) * 0.72)
|
||||
readonly property real sqX: (width - sqSz) / 2
|
||||
@@ -653,16 +716,16 @@ PageType {
|
||||
anchors.leftMargin: 0
|
||||
anchors.rightMargin: 0
|
||||
visible: pairingWizardStep === 1
|
||||
z: 10
|
||||
spacing: 16
|
||||
|
||||
BackButtonType {
|
||||
Layout.topMargin: 20 + PageController.safeAreaTopMargin
|
||||
Layout.leftMargin: 0
|
||||
backButtonFunction: function() {
|
||||
PairingUiController.cancelAllPairingActivity()
|
||||
pairingWizardStep = 0
|
||||
addDeviceConfirmNavigationScheduled = false
|
||||
Qt.callLater(startMobileScanner)
|
||||
pairingWizardStep = 0
|
||||
PairingUiController.cancelAllPairingActivity()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -714,10 +777,9 @@ PageType {
|
||||
text: qsTr("Cancel")
|
||||
|
||||
clickedFunc: function() {
|
||||
PairingUiController.cancelAllPairingActivity()
|
||||
pairingWizardStep = 0
|
||||
addDeviceConfirmNavigationScheduled = false
|
||||
Qt.callLater(startMobileScanner)
|
||||
pairingWizardStep = 0
|
||||
PairingUiController.cancelAllPairingActivity()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -736,7 +798,9 @@ PageType {
|
||||
}
|
||||
awaitingCameraPermissionForScan = false
|
||||
if (granted) {
|
||||
startMobileScanner()
|
||||
if (root.pairingWizardStep === 0) {
|
||||
startMobileScanner()
|
||||
}
|
||||
} else {
|
||||
waitingSettingsReturnForScan = true
|
||||
showScanCameraDeniedDrawer()
|
||||
@@ -750,9 +814,8 @@ PageType {
|
||||
addDeviceConfirmNavigationScheduled = true
|
||||
stopMobileScanner()
|
||||
PairingUiController.pendingPhonePairingUuid = uuid
|
||||
Qt.callLater(function() {
|
||||
pairingWizardStep = 1
|
||||
})
|
||||
/** Immediate step switch so scan chrome is not hit-testable for another frame (avoids reopening CameraActivity). */
|
||||
pairingWizardStep = 1
|
||||
}
|
||||
|
||||
function onPairingSendQrScanRejectedInvalidPayload() {
|
||||
@@ -771,5 +834,16 @@ PageType {
|
||||
stopMobileScanner()
|
||||
PageController.closePage()
|
||||
}
|
||||
|
||||
/** Native CameraActivity back: leave pairing flow (same as iOS overlay back). Do NOT reopen scanner. */
|
||||
function onPairingAndroidNativeQrScannerUserDismissed() {
|
||||
if (!root.useAndroidNativePairingQrOverlay) {
|
||||
return
|
||||
}
|
||||
stopMobileScanner()
|
||||
PairingUiController.cancelAllPairingActivity()
|
||||
addDeviceConfirmNavigationScheduled = false
|
||||
PageController.closePage()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user