added qr-code decoder for android

- added color change for status and navigation bar for android
This commit is contained in:
vladimir.kuznetsov 2023-07-25 16:56:10 +09:00
parent b9a13d3a32
commit 0411792ca5
13 changed files with 255 additions and 58 deletions

View file

@ -202,31 +202,6 @@ QList<QString> ExportController::getQrCodes()
}
void ExportController::saveFile()
{
QString fileExtension = ".vpn";
QString docDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
QUrl fileName;
fileName = QFileDialog::getSaveFileUrl(nullptr, tr("Save AmneziaVPN config"),
QUrl::fromLocalFile(docDir + "/" + "amnezia_config"), "*" + fileExtension);
if (fileName.isEmpty())
return;
if (!fileName.toString().endsWith(fileExtension)) {
fileName = QUrl(fileName.toString() + fileExtension);
}
if (fileName.isEmpty())
return;
QFile save(fileName.toLocalFile());
save.open(QIODevice::WriteOnly);
save.write(m_config.toUtf8());
save.close();
QFileInfo fi(fileName.toLocalFile());
QDesktopServices::openUrl(fi.absoluteDir().absolutePath());
}
void ExportController::shareFile()
{
#if defined Q_OS_IOS
ext.replace("*", "");
@ -253,6 +228,28 @@ void ExportController::shareFile()
AndroidController::instance()->shareConfig(m_config, "amnezia_config");
return;
#endif
QString fileExtension = ".vpn";
QString docDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
QUrl fileName;
fileName = QFileDialog::getSaveFileUrl(nullptr, tr("Save AmneziaVPN config"),
QUrl::fromLocalFile(docDir + "/" + "amnezia_config"), "*" + fileExtension);
if (fileName.isEmpty())
return;
if (!fileName.toString().endsWith(fileExtension)) {
fileName = QUrl(fileName.toString() + fileExtension);
}
if (fileName.isEmpty())
return;
QFile save(fileName.toLocalFile());
save.open(QIODevice::WriteOnly);
save.write(m_config.toUtf8());
save.close();
QFileInfo fi(fileName.toLocalFile());
QDesktopServices::openUrl(fi.absoluteDir().absolutePath());
}
QList<QString> ExportController::generateQrCodeImageSeries(const QByteArray &data)

View file

@ -36,7 +36,6 @@ public slots:
QList<QString> getQrCodes();
void saveFile();
void shareFile();
signals:
void generateConfig(int type);

View file

@ -41,6 +41,11 @@ namespace
}
return ConfigTypes::Amnezia;
}
#ifdef Q_OS_ANDROID
ImportController *mInstance = nullptr;
constexpr auto AndroidCameraActivity = "org.amnezia.vpn.qt.CameraActivity";
#endif
} // namespace
ImportController::ImportController(const QSharedPointer<ServersModel> &serversModel,
@ -49,6 +54,7 @@ ImportController::ImportController(const QSharedPointer<ServersModel> &serversMo
: QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings)
{
#ifdef Q_OS_ANDROID
mInstance = this;
// Set security screen for Android app
AndroidUtils::runOnAndroidThreadSync([]() {
QJniObject activity = AndroidUtils::getActivity();
@ -58,6 +64,18 @@ ImportController::ImportController(const QSharedPointer<ServersModel> &serversMo
window.callMethod<void>("addFlags", "(I)V", FLAG_SECURE);
}
});
AndroidUtils::runOnAndroidThreadAsync([]() {
JNINativeMethod methods[] {
{ "passDataToDecoder", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onNewQrCodeDataChunk) },
};
QJniObject javaClass(AndroidCameraActivity);
QJniEnvironment env;
jclass objectClass = env->GetObjectClass(javaClass.object<jobject>());
env->RegisterNatives(objectClass, methods, sizeof(methods) / sizeof(methods[0]));
env->DeleteLocalRef(objectClass);
});
#endif
}
@ -93,11 +111,21 @@ void ImportController::extractConfigFromCode(QString code)
m_configFileName = "";
}
void ImportController::extractConfigFromQr()
bool ImportController::extractConfigFromQr(const QByteArray &data)
{
#ifdef Q_OS_ANDROID
AndroidController::instance()->startQrReaderActivity();
#endif
QJsonObject dataObj = QJsonDocument::fromJson(data).object();
if (!dataObj.isEmpty()) {
m_config = dataObj;
return true;
}
QByteArray ba_uncompressed = qUncompress(data);
if (!ba_uncompressed.isEmpty()) {
m_config = QJsonDocument::fromJson(ba_uncompressed).object();
return true;
}
return false;
}
QString ImportController::getConfig()
@ -149,21 +177,6 @@ QJsonObject ImportController::extractAmneziaConfig(QString &data)
return QJsonDocument::fromJson(ba).object();
}
// bool ImportController::importConnectionFromQr(const QByteArray &data)
//{
// QJsonObject dataObj = QJsonDocument::fromJson(data).object();
// if (!dataObj.isEmpty()) {
// return importConnection(dataObj);
// }
// QByteArray ba_uncompressed = qUncompress(data);
// if (!ba_uncompressed.isEmpty()) {
// return importConnection(QJsonDocument::fromJson(ba_uncompressed).object());
// }
// return false;
//}
QJsonObject ImportController::extractOpenVpnConfig(const QString &data)
{
QJsonObject openVpnConfig;
@ -251,3 +264,90 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data)
return config;
}
#ifdef Q_OS_ANDROID
void ImportController::startDecodingQr()
{
AndroidController::instance()->startQrReaderActivity();
}
void ImportController::stopDecodingQr()
{
QJniObject::callStaticMethod<void>(AndroidCameraActivity, "stopQrCodeReader", "()V");
emit qrDecodingFinished();
}
void ImportController::onNewQrCodeDataChunk(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_isQrCodeProcessed) {
mInstance->m_qrCodeChunks.clear();
mInstance->m_isQrCodeProcessed = true;
mInstance->m_totalQrCodeChunksCount = 0;
mInstance->m_receivedQrCodeChunksCount = 0;
}
mInstance->parseQrCodeChunk(parcelBody);
}
}
void ImportController::parseQrCodeChunk(const QString &code)
{
// qDebug() << code;
if (!m_isQrCodeProcessed)
return;
// check if chunk received
QByteArray ba = QByteArray::fromBase64(code.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
QDataStream s(&ba, QIODevice::ReadOnly);
qint16 magic;
s >> magic;
if (magic == amnezia::qrMagicCode) {
quint8 chunksCount;
s >> chunksCount;
if (m_totalQrCodeChunksCount != chunksCount) {
m_qrCodeChunks.clear();
}
m_totalQrCodeChunksCount = chunksCount;
quint8 chunkId;
s >> chunkId;
s >> m_qrCodeChunks[chunkId];
m_receivedQrCodeChunksCount = m_qrCodeChunks.size();
if (m_qrCodeChunks.size() == m_totalQrCodeChunksCount) {
QByteArray data;
for (int i = 0; i < m_totalQrCodeChunksCount; ++i) {
data.append(m_qrCodeChunks.value(i));
}
bool ok = extractConfigFromQr(data);
if (ok) {
m_isQrCodeProcessed = false;
stopDecodingQr();
} else {
m_qrCodeChunks.clear();
m_totalQrCodeChunksCount = 0;
m_receivedQrCodeChunksCount = 0;
}
}
} else {
bool ok = extractConfigFromQr(ba);
if (ok) {
m_isQrCodeProcessed = false;
stopDecodingQr();
}
}
}
#endif

View file

@ -7,6 +7,9 @@
#include "core/defs.h"
#include "ui/models/containers_model.h"
#include "ui/models/servers_model.h"
#ifdef Q_OS_ANDROID
#include "jni.h"
#endif
class ImportController : public QObject
{
@ -21,25 +24,44 @@ public slots:
void extractConfigFromFile();
void extractConfigFromData(QString &data);
void extractConfigFromCode(QString code);
void extractConfigFromQr();
bool extractConfigFromQr(const QByteArray &data);
QString getConfig();
QString getConfigFileName();
#if defined Q_OS_ANDROID
void startDecodingQr();
#endif
signals:
void importFinished();
void importErrorOccurred(QString errorMessage);
void qrDecodingFinished();
private:
QJsonObject extractAmneziaConfig(QString &data);
QJsonObject extractOpenVpnConfig(const QString &data);
QJsonObject extractWireGuardConfig(const QString &data);
#if defined Q_OS_ANDROID
void stopDecodingQr();
static void onNewQrCodeDataChunk(JNIEnv *env, jobject thiz, jstring data);
void parseQrCodeChunk(const QString &code);
#endif
QSharedPointer<ServersModel> m_serversModel;
QSharedPointer<ContainersModel> m_containersModel;
std::shared_ptr<Settings> m_settings;
QJsonObject m_config;
QString m_configFileName;
#if defined Q_OS_ANDROID
QMap<int, QByteArray> m_qrCodeChunks;
bool m_isQrCodeProcessed;
int m_totalQrCodeChunksCount;
int m_receivedQrCodeChunksCount;
#endif
};
#endif // IMPORTCONTROLLER_H

View file

@ -1,10 +1,28 @@
#include "pageController.h"
#include <QApplication>
#ifdef Q_OS_ANDROID
#include "../../platforms/android/androidutils.h"
#include <QJniObject>
#endif
PageController::PageController(const QSharedPointer<ServersModel> &serversModel, QObject *parent)
: QObject(parent), m_serversModel(serversModel)
{
#ifdef Q_OS_ANDROID
// Change color of navigation and status bar's
auto initialPageNavigationBarColor = getInitialPageNavigationBarColor();
AndroidUtils::runOnAndroidThreadSync([&initialPageNavigationBarColor]() {
QJniObject activity = AndroidUtils::getActivity();
QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;");
if (window.isValid()) {
window.callMethod<void>("addFlags", "(I)V", 0x80000000);
window.callMethod<void>("clearFlags", "(I)V", 0x04000000);
window.callMethod<void>("setStatusBarColor", "(I)V", 0xFF0E0E11);
window.callMethod<void>("setNavigationBarColor", "(I)V", initialPageNavigationBarColor);
}
});
#endif
}
QString PageController::getInitialPage()
@ -47,3 +65,26 @@ void PageController::keyPressEvent(Qt::Key key)
default: return;
}
}
unsigned int PageController::getInitialPageNavigationBarColor()
{
if (m_serversModel->getServersCount()) {
return 0xFF1C1D21;
} else {
return 0xFF0E0E11;
}
}
void PageController::updateNavigationBarColor(const int color)
{
#ifdef Q_OS_ANDROID
// Change color of navigation bar
AndroidUtils::runOnAndroidThreadSync([&color]() {
QJniObject activity = AndroidUtils::getActivity();
QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;");
if (window.isValid()) {
window.callMethod<void>("setNavigationBarColor", "(I)V", color);
}
});
#endif
}

View file

@ -71,6 +71,9 @@ public slots:
void closeWindow();
void keyPressEvent(Qt::Key key);
unsigned int getInitialPageNavigationBarColor();
void updateNavigationBarColor(const int color);
signals:
void goToPageHome();
void goToPageSettings();

View file

@ -54,10 +54,10 @@ DrawerType {
Layout.fillWidth: true
Layout.topMargin: 16
text: Qt.platform.os === "android" ? qsTr("Share") : qsTr("Save connection code")
text: qsTr("Share")
onClicked: {
Qt.platform.os === "android" ? ExportController.shareFile() : ExportController.saveFile()
ExportController.saveFile()
}
}

View file

@ -12,6 +12,7 @@ Drawer {
velocity: 4
}
}
exit: Transition {
SmoothedAnimation {
velocity: 4
@ -31,4 +32,17 @@ Drawer {
Overlay.modal: Rectangle {
color: Qt.rgba(14/255, 14/255, 17/255, 0.8)
}
onAboutToShow: {
if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) {
PageController.updateNavigationBarColor(0xFF1C1D21)
}
}
onClosed: {
var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor()
if (initialPageNavigationBarColor !== 0xFF1C1D21) {
PageController.updateNavigationBarColor(initialPageNavigationBarColor)
}
}
}

View file

@ -13,6 +13,14 @@ import "../Config"
PageType {
id: root
Connections {
target: ImportController
function onQrDecodingFinished() {
goToPage(PageEnum.PageSetupWizardViewConfig)
}
}
FlickableType {
id: fl
anchors.top: parent.top
@ -77,7 +85,7 @@ It's okay if a friend passed the code.")
leftImageSource: "qrc:/images/controls/qr-code.svg"
clickedFunction: function() {
ImportController.extractConfigFromQr()
ImportController.startDecodingQr()
// goToPage(PageEnum.PageSetupWizardQrReader)
}
}

View file

@ -47,8 +47,7 @@ PageType {
} else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) {
goToPage(PageEnum.PageSettingsServersList, false)
} else {
var pagePath = PageController.getPagePath(PageEnum.PageStart)
stackView.replace(pagePath, { "objectName" : pagePath })
PageController.replaceStartPage()
}
if (isInstalledContainerFound) {

View file

@ -30,8 +30,7 @@ PageType {
} else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) {
goToPage(PageEnum.PageSettingsServersList, false)
} else {
var pagePath = PageController.getPagePath(PageEnum.PageStart)
stackView.replace(pagePath, { "objectName" : pagePath })
PageController.replaceStartPage()
}
}
}

View file

@ -1,6 +1,7 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Shapes
import PageEnum 1.0
@ -81,14 +82,27 @@ PageType {
anchors.bottom: parent.bottom
topPadding: 8
bottomPadding: 8//34
bottomPadding: 8
leftPadding: shareTabButton.visible ? 96 : 128
rightPadding: shareTabButton.visible ? 96 : 128
background: Rectangle {
border.width: 1
border.color: "#2C2D30"
color: "#1C1D21"
background: Shape {
width: parent.width
height: parent.height
ShapePath {
startX: 0
startY: 0
PathLine { x: width; y: 0 }
PathLine { x: width; y: height - 1 }
PathLine { x: 0; y: height - 1 }
PathLine { x: 0; y: 0 }
strokeWidth: 1
strokeColor: "#2C2D30"
fillColor: "#1C1D21"
}
}
TabImageButtonType {

View file

@ -50,6 +50,7 @@ Window {
while (rootStackView.depth > 1) {
rootStackView.pop()
}
PageController.updateNavigationBarColor(PageController.getInitialPageNavigationBarColor())
rootStackView.replace(pagePath, { "objectName" : pagePath })
}