From b870306c5d15f5a99ceee128d07a8eef805dc4a8 Mon Sep 17 00:00:00 2001 From: Dmitriy Karpushin Date: Wed, 22 Feb 2023 14:22:03 +0300 Subject: [PATCH] Support of multiple-code config --- client/android/AndroidManifest.xml | 3 +- .../src/org/amnezia/vpn/qt/CameraActivity.kt | 52 +++--- .../platforms/android/android_controller.cpp | 6 +- client/platforms/android/android_controller.h | 9 +- client/resources.qrc | 1 - client/ui/pages_logic/QrDecoderLogic.cpp | 70 +++++++- client/ui/pages_logic/QrDecoderLogic.h | 11 ++ client/ui/pages_logic/StartPageLogic.cpp | 8 + client/ui/pages_logic/StartPageLogic.h | 4 + client/ui/qml/Pages/PageQrDecoder.qml | 164 ------------------ client/ui/qml/Pages/PageStart.qml | 2 +- 11 files changed, 126 insertions(+), 204 deletions(-) delete mode 100644 client/ui/qml/Pages/PageQrDecoder.qml diff --git a/client/android/AndroidManifest.xml b/client/android/AndroidManifest.xml index af4e5c764..22c828fb0 100644 --- a/client/android/AndroidManifest.xml +++ b/client/android/AndroidManifest.xml @@ -34,6 +34,7 @@ android:extractNativeLibs="true" android:requestLegacyExternalStorage="true" android:allowNativeHeapPointerTagging="false" + android:theme="@style/Theme.AppCompat.NoActionBar" android:icon="@drawable/icon"> () + + private inner class BarCodeAnalyzer(): ImageAnalysis.Analyzer { private val options = BarcodeScannerOptions.Builder() .setBarcodeFormats(Barcode.FORMAT_QR_CODE) @@ -130,21 +135,24 @@ class CameraActivity : AppCompatActivity() { scanner.process(image) .addOnSuccessListener { barcodes -> if (barcodes.isNotEmpty()) { - callback.onSuccess(barcodes[0]?.displayValue ?: "empty?") + val barcode = barcodes[0] + if (barcode != null) { + val str = barcode?.displayValue ?: "" + if (str.isNotEmpty()) { + val isAdded = barcodesSet.add(str) + if (isAdded) { + passDataToDecoder(str) + } + } + } } imageProxy.close() } .addOnFailureListener { - callback.onFailure(it) imageProxy.close() } } } } - - interface OnBarcodeActivityResult { - fun onSuccess(result: String) - fun onFailure(result: Exception) - } } \ No newline at end of file diff --git a/client/platforms/android/android_controller.cpp b/client/platforms/android/android_controller.cpp index b25fce78f..a86ab7caf 100644 --- a/client/platforms/android/android_controller.cpp +++ b/client/platforms/android/android_controller.cpp @@ -15,7 +15,6 @@ #include "private/qandroidextras_p.h" #include "ui/pages_logic/StartPageLogic.h" -#include "androidvpnactivity.h" #include "androidutils.h" namespace { @@ -262,6 +261,11 @@ void AndroidController::setVpnConfig(const QJsonObject &newVpnConfig) m_vpnConfig = newVpnConfig; } +void AndroidController::startQrReaderActivity() +{ + AndroidVPNActivity::instance()->startQrCodeReader(); +} + void AndroidController::scheduleStatusCheckSlot() { QTimer::singleShot(1000, [this]() { diff --git a/client/platforms/android/android_controller.h b/client/platforms/android/android_controller.h index ddaa0d79f..59ecd9b3d 100644 --- a/client/platforms/android/android_controller.h +++ b/client/platforms/android/android_controller.h @@ -11,6 +11,7 @@ #include "ui/pages_logic/StartPageLogic.h" #include "protocols/vpnprotocol.h" +#include "androidvpnactivity.h" using namespace amnezia; @@ -42,6 +43,8 @@ public: const QJsonObject &vpnConfig() const; void setVpnConfig(const QJsonObject &newVpnConfig); + void startQrReaderActivity(); + signals: void connectionStateChanged(VpnProtocol::VpnConnectionState state); @@ -50,8 +53,7 @@ signals: // to true and the "connectionDate" should be set to the activation date if // known. // If "status" is set to false, the backend service is considered unavailable. - void initialized(bool status, bool connected, - const QDateTime& connectionDate); + void initialized(bool status, bool connected, const QDateTime& connectionDate); void statusUpdated(QString totalRx, QString totalTx, QString endpoint, QString deviceIPv4); void scheduleStatusCheckSignal(); @@ -59,9 +61,6 @@ signals: protected slots: void scheduleStatusCheckSlot(); -protected: - - private: bool m_init = false; diff --git a/client/resources.qrc b/client/resources.qrc index 4651f91eb..fd26f5ad1 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -81,7 +81,6 @@ ui/qml/Pages/PageSites.qml ui/qml/Pages/PageStart.qml ui/qml/Pages/PageVPN.qml - ui/qml/Pages/PageQrDecoder.qml ui/qml/Pages/PageAbout.qml ui/qml/Pages/PageQrDecoderIos.qml ui/qml/Pages/PageViewConfig.qml diff --git a/client/ui/pages_logic/QrDecoderLogic.cpp b/client/ui/pages_logic/QrDecoderLogic.cpp index 764a6e4b3..e1845c77b 100644 --- a/client/ui/pages_logic/QrDecoderLogic.cpp +++ b/client/ui/pages_logic/QrDecoderLogic.cpp @@ -3,15 +3,70 @@ #include "ui/uilogic.h" #include "ui/pages_logic/StartPageLogic.h" +#ifdef Q_OS_ANDROID +#include +#include +#include "../../platforms/android/androidutils.h" +#endif + using namespace amnezia; using namespace PageEnumNS; +namespace { + QrDecoderLogic* mInstance = nullptr; + constexpr auto CLASSNAME = "org.amnezia.vpn.qt.CameraActivity"; +} + QrDecoderLogic::QrDecoderLogic(UiLogic *logic, QObject *parent): PageLogicBase(logic, parent) { + mInstance = this; + #if (defined(Q_OS_ANDROID)) + AndroidUtils::runOnAndroidThreadAsync([]() { + JNINativeMethod methods[]{ + {"passDataToDecoder", "(Ljava/lang/String;)V", reinterpret_cast(onNewDataChunk)}, + }; + + QJniObject javaClass(CLASSNAME); + QJniEnvironment env; + jclass objectClass = env->GetObjectClass(javaClass.object()); + env->RegisterNatives(objectClass, methods, sizeof(methods) / sizeof(methods[0])); + env->DeleteLocalRef(objectClass); + }); + #endif } +void QrDecoderLogic::stopDecodingQr() +{ + #if (defined(Q_OS_ANDROID)) + QJniObject::callStaticMethod(CLASSNAME, "stopQrCodeReader", "()V"); + #endif + + emit stopDecode(); +} + +#ifdef Q_OS_ANDROID +void QrDecoderLogic::onNewDataChunk(JNIEnv *env, jobject thiz, jstring data) +{ + Q_UNUSED(thiz); + const char* buffer = env->GetStringUTFChars(data, nullptr); + if (!buffer) { + return; + } + + QString parcelBody(buffer); + env->ReleaseStringUTFChars(data, buffer); + + if (mInstance != nullptr) { + if (!mInstance->m_detectingEnabled) { + mInstance->onUpdatePage(); + } + mInstance->onDetectedQrCode(parcelBody); + } +} +#endif + void QrDecoderLogic::onUpdatePage() { m_chunks.clear(); @@ -24,7 +79,6 @@ void QrDecoderLogic::onUpdatePage() void QrDecoderLogic::onDetectedQrCode(const QString &code) { //qDebug() << code; - if (!detectingEnabled()) return; // check if chunk received @@ -32,12 +86,12 @@ void QrDecoderLogic::onDetectedQrCode(const QString &code) QDataStream s(&ba, QIODevice::ReadOnly); qint16 magic; s >> magic; - if (magic == amnezia::qrMagicCode) { quint8 chunksCount; s >> chunksCount; if (totalChunksCount() != chunksCount) { m_chunks.clear(); } + set_totalChunksCount(chunksCount); quint8 chunkId; s >> chunkId; @@ -46,6 +100,7 @@ void QrDecoderLogic::onDetectedQrCode(const QString &code) if (m_chunks.size() == totalChunksCount()) { QByteArray data; + for (int i = 0; i < totalChunksCount(); ++i) { data.append(m_chunks.value(i)); } @@ -53,21 +108,18 @@ void QrDecoderLogic::onDetectedQrCode(const QString &code) bool ok = uiLogic()->pageLogic()->importConnectionFromQr(data); if (ok) { set_detectingEnabled(false); - emit stopDecode(); - } - else { + stopDecodingQr(); + } else { m_chunks.clear(); set_totalChunksCount(0); set_receivedChunksCount(0); } } - } - else { + } else { bool ok = uiLogic()->pageLogic()->importConnectionFromQr(ba); if (ok) { set_detectingEnabled(false); - emit stopDecode(); + stopDecodingQr(); } } } - diff --git a/client/ui/pages_logic/QrDecoderLogic.h b/client/ui/pages_logic/QrDecoderLogic.h index 243da95be..2b24bf27e 100644 --- a/client/ui/pages_logic/QrDecoderLogic.h +++ b/client/ui/pages_logic/QrDecoderLogic.h @@ -3,6 +3,10 @@ #include "PageLogicBase.h" +#ifdef Q_OS_ANDROID +#include "jni.h" +#endif + class UiLogic; class QrDecoderLogic : public PageLogicBase @@ -16,10 +20,17 @@ public: Q_INVOKABLE void onUpdatePage() override; Q_INVOKABLE void onDetectedQrCode(const QString &code); +#ifdef Q_OS_ANDROID + static void onNewDataChunk(JNIEnv *env, jobject thiz, jstring data); +#endif + public: explicit QrDecoderLogic(UiLogic *uiLogic, QObject *parent = nullptr); ~QrDecoderLogic() = default; +private: + void stopDecodingQr(); + signals: void startDecode(); void stopDecode(); diff --git a/client/ui/pages_logic/StartPageLogic.cpp b/client/ui/pages_logic/StartPageLogic.cpp index 3c15199de..06febc057 100644 --- a/client/ui/pages_logic/StartPageLogic.cpp +++ b/client/ui/pages_logic/StartPageLogic.cpp @@ -13,6 +13,7 @@ #ifdef Q_OS_ANDROID #include #include "../../platforms/android/androidutils.h" +#include "../../platforms/android/android_controller.h" #endif namespace { @@ -184,6 +185,13 @@ void StartPageLogic::onPushButtonImportOpenFile() } } +#ifdef Q_OS_ANDROID +void StartPageLogic::startQrDecoder() +{ + AndroidController::instance()->startQrReaderActivity(); +} +#endif + bool StartPageLogic::importConnection(const QJsonObject &profile) { ServerCredentials credentials; diff --git a/client/ui/pages_logic/StartPageLogic.h b/client/ui/pages_logic/StartPageLogic.h index 183d0bd3d..b3dea0020 100644 --- a/client/ui/pages_logic/StartPageLogic.h +++ b/client/ui/pages_logic/StartPageLogic.h @@ -31,6 +31,10 @@ public: Q_INVOKABLE void onPushButtonImport(); Q_INVOKABLE void onPushButtonImportOpenFile(); +#ifdef Q_OS_ANDROID + Q_INVOKABLE void startQrDecoder(); +#endif + bool importConnection(const QJsonObject &profile); bool importConnectionFromCode(QString code); bool importConnectionFromQr(const QByteArray &data); diff --git a/client/ui/qml/Pages/PageQrDecoder.qml b/client/ui/qml/Pages/PageQrDecoder.qml deleted file mode 100644 index 9a4b26f5b..000000000 --- a/client/ui/qml/Pages/PageQrDecoder.qml +++ /dev/null @@ -1,164 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtMultimedia -import PageEnum 1.0 -import QZXing 3.2 - -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.QrDecoder - logic: QrDecoderLogic - - onDeactivated: { - console.debug("Stopping QR decoder") - loader.sourceComponent = undefined - } - - BackButton { - } - Caption { - id: caption - text: qsTr("Import configuration") - } - - Connections { - target: Qt.platform.os != "ios" ? QrDecoderLogic : null - function onStartDecode() { - console.debug("Starting QR decoder") - loader.sourceComponent = component - } - function onStopDecode() { - console.debug("Stopping QR decoder") - loader.sourceComponent = undefined - } - } - - Loader { - id: loader - - anchors.top: caption.bottom - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - } - - Component { - id: component - - Item { - anchors.fill: parent - - Camera - { - id:camera - focus { - focusMode: CameraFocus.FocusContinuous - focusPointMode: CameraFocus.FocusPointAuto - } - } - - VideoOutput - { - id: videoOutput - source: camera - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - autoOrientation: true - fillMode: VideoOutput.PreserveAspectFit -// filters: [ zxingFilter ] - - - Rectangle { - color: "black" - opacity: 0.5 - width: videoOutput.contentRect.width * 0.15 - height: videoOutput.contentRect.height - x: (videoOutput.width - videoOutput.contentRect.width)/2 - anchors.verticalCenter: videoOutput.verticalCenter - } - - Rectangle { - color: "black" - opacity: 0.5 - width: videoOutput.contentRect.width * 0.15 - height: videoOutput.contentRect.height - x: videoOutput.width/2 + videoOutput.contentRect.width/2 - videoOutput.contentRect.width * 0.15 - anchors.verticalCenter: videoOutput.verticalCenter - } - - Rectangle { - color: "black" - opacity: 0.5 - width: videoOutput.contentRect.width * 0.7 - height: videoOutput.contentRect.height * 0.15 - x: (videoOutput.width - videoOutput.contentRect.width)/2 + videoOutput.contentRect.width * 0.15 - y: (videoOutput.height - videoOutput.contentRect.height)/2 - } - - Rectangle { - color: "black" - opacity: 0.5 - width: videoOutput.contentRect.width * 0.7 - height: videoOutput.contentRect.height * 0.15 - x: (videoOutput.width - videoOutput.contentRect.width)/2 + videoOutput.contentRect.width * 0.15 - y: videoOutput.height/2 + videoOutput.contentRect.height/2 - videoOutput.contentRect.height * 0.15 - } - - LabelType { - width: parent.width - text: qsTr("Decoded QR chunks " + QrDecoderLogic.receivedChunksCount + "/" + QrDecoderLogic.totalChunksCount) - horizontalAlignment: Text.AlignLeft - visible: QrDecoderLogic.totalChunksCount > 0 - anchors.horizontalCenter: videoOutput.horizontalCenter - y: videoOutput.height/2 + videoOutput.contentRect.height/2 - } - } - - QZXingFilter - { - id: zxingFilter - orientation: videoOutput.orientation - captureRect: { - // setup bindings - videoOutput.contentRect; - videoOutput.sourceRect; - return videoOutput.mapRectToSource(videoOutput.mapNormalizedRectToItem(Qt.rect( - 0.15, 0.15, 0.7, 0.7 //0, 0, 1.0, 1.0 - ))); - } - - decoder { - enabledDecoders: QZXing.DecoderFormat_QR_CODE - - onTagFound: { - QrDecoderLogic.onDetectedQrCode(tag) - } - - tryHarder: true - } - - property int framesDecoded: 0 - property real timePerFrameDecode: 0 - - onDecodingFinished: - { - timePerFrameDecode = (decodeTime + framesDecoded * timePerFrameDecode) / (framesDecoded + 1); - framesDecoded++; - if(succeeded) - console.log("frame finished: " + succeeded, decodeTime, timePerFrameDecode, framesDecoded); - } - } - - - } - - } - - -} diff --git a/client/ui/qml/Pages/PageStart.qml b/client/ui/qml/Pages/PageStart.qml index d5e67e488..eacb607df 100644 --- a/client/ui/qml/Pages/PageStart.qml +++ b/client/ui/qml/Pages/PageStart.qml @@ -154,7 +154,7 @@ PageBase { if (Qt.platform.os === "ios") { UiLogic.goToPage(PageEnum.QrDecoderIos) } else { - UiLogic.goToPage(PageEnum.QrDecoder) + StartPageLogic.startQrDecoder() } } enabled: StartPageLogic.pushButtonConnectEnabled