AusweisApp2/src/ui/qml/ApplicationModel.cpp

551 lines
13 KiB
C++

/*!
* \copyright Copyright (c) 2016-2019 Governikus GmbH & Co. KG, Germany
*/
#include "ApplicationModel.h"
#include "context/AuthContext.h"
#include "context/ChangePinContext.h"
#include "context/SelfAuthContext.h"
#include "AppSettings.h"
#include "BuildHelper.h"
#include "DpiCalculator.h"
#include "Env.h"
#include "HelpAction.h"
#include "ReaderInfo.h"
#include "ReaderManager.h"
#include "RemoteClient.h"
#include "SingletonHelper.h"
#if !defined(Q_OS_WINRT) && !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#include "PdfExporter.h"
#endif
#include <QRegularExpression>
#if (defined(Q_OS_LINUX) && !defined(QT_NO_DEBUG)) || defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#include <QBluetoothLocalDevice>
#endif
#ifdef Q_OS_ANDROID
#include <QAndroidJniEnvironment>
#include <QAndroidJniObject>
#include <QOperatingSystemVersion>
#include <QtAndroid>
#endif
using namespace governikus;
Q_DECLARE_LOGGING_CATEGORY(qml)
Q_DECLARE_LOGGING_CATEGORY(feedback)
defineSingleton(ApplicationModel)
void ApplicationModel::onStatusChanged(const ReaderManagerPlugInInfo& pInfo)
{
if (pInfo.getPlugInType() == ReaderManagerPlugInType::BLUETOOTH)
{
Q_EMIT fireBluetoothEnabledChanged();
Q_EMIT fireBluetoothRespondingChanged();
}
else if (pInfo.getPlugInType() == ReaderManagerPlugInType::NFC)
{
Q_EMIT fireNfcEnabledChanged();
Q_EMIT fireNfcRunningChanged();
}
}
ApplicationModel::ApplicationModel()
: mContext()
, mDpiScale(DpiCalculator::getDpiScale())
, mScaleFactor(mDpiScale)
, mWifiInfo()
, mWifiEnabled(false)
, mBluetoothResponding(true)
, mFeedback()
, mFeedbackTimer()
, mFeedbackDisplayLength(3500)
#ifdef Q_OS_IOS
, mPrivate(new Private)
#endif
{
const auto readerManager = Env::getSingleton<ReaderManager>();
connect(readerManager, &ReaderManager::fireReaderAdded, this, &ApplicationModel::fireBluetoothReaderChanged);
connect(readerManager, &ReaderManager::fireReaderRemoved, this, &ApplicationModel::fireBluetoothReaderChanged);
connect(readerManager, &ReaderManager::fireReaderPropertiesUpdated, this, &ApplicationModel::fireReaderPropertiesUpdated);
connect(readerManager, &ReaderManager::fireStatusChanged, this, &ApplicationModel::onStatusChanged);
connect(readerManager, &ReaderManager::fireStatusChanged, this, &ApplicationModel::fireBluetoothReaderChanged);
connect(readerManager, &ReaderManager::fireReaderAdded, this, &ApplicationModel::fireSelectedReaderChanged);
connect(readerManager, &ReaderManager::fireReaderRemoved, this, &ApplicationModel::fireSelectedReaderChanged);
connect(&mWifiInfo, &WifiInfo::fireWifiEnabledChanged, this, &ApplicationModel::onWifiEnabledChanged);
onWifiEnabledChanged();
auto* const remoteClient = Env::getSingleton<RemoteClient>();
connect(remoteClient, &RemoteClient::fireCertificateRemoved, this, &ApplicationModel::fireCertificateRemoved);
mFeedbackTimer.setSingleShot(true);
connect(&mFeedbackTimer, &QTimer::timeout, this, &ApplicationModel::onShowNextFeedback, Qt::QueuedConnection);
}
void ApplicationModel::resetContext(const QSharedPointer<WorkflowContext>& pContext)
{
if (mContext)
{
disconnect(mContext.data(), &WorkflowContext::fireReaderPlugInTypesChanged, this, &ApplicationModel::fireSelectedReaderChanged);
}
if ((mContext = pContext))
{
connect(mContext.data(), &WorkflowContext::fireReaderPlugInTypesChanged, this, &ApplicationModel::fireSelectedReaderChanged);
connect(mContext.data(), &WorkflowContext::fireReaderNameChanged, this, &ApplicationModel::fireSelectedReaderChanged);
connect(mContext.data(), &WorkflowContext::fireReaderNameChanged, this, &ApplicationModel::fireReaderPropertiesUpdated);
connect(mContext.data(), &WorkflowContext::fireReaderInfoChanged, this, &ApplicationModel::fireReaderPropertiesUpdated);
}
Q_EMIT fireCurrentWorkflowChanged();
}
QString ApplicationModel::getPackageName() const
{
#ifdef Q_OS_ANDROID
return BuildHelper::getPackageName();
#else
return QString();
#endif
}
ReaderManagerPlugInInfo ApplicationModel::getFirstPlugInInfo(ReaderManagerPlugInType pType) const
{
const auto pluginInfos = Env::getSingleton<ReaderManager>()->getPlugInInfos();
for (const auto& info : pluginInfos)
{
if (info.getPlugInType() == pType)
{
return info;
}
}
return ReaderManagerPlugInInfo();
}
bool ApplicationModel::isNfcAvailable() const
{
#if !defined(QT_NO_DEBUG) && !defined(Q_OS_IOS) && !defined(Q_OS_ANDROID) && !defined(Q_OS_WINRT)
return getFirstPlugInInfo(ReaderManagerPlugInType::PCSC).isAvailable();
#else
return getFirstPlugInInfo(ReaderManagerPlugInType::NFC).isAvailable();
#endif
}
bool ApplicationModel::isNfcEnabled() const
{
#if !defined(QT_NO_DEBUG) && !defined(Q_OS_IOS) && !defined(Q_OS_ANDROID) && !defined(Q_OS_WINRT)
return getFirstPlugInInfo(ReaderManagerPlugInType::PCSC).isEnabled();
#else
return getFirstPlugInInfo(ReaderManagerPlugInType::NFC).isEnabled();
#endif
}
bool ApplicationModel::isNfcRunning() const
{
return Env::getSingleton<ReaderManager>()->isScanRunning(ReaderManagerPlugInType::NFC);
}
void ApplicationModel::setNfcRunning(bool pRunning)
{
const auto& readerManager = Env::getSingleton<ReaderManager>();
if (pRunning)
{
readerManager->startScan(ReaderManagerPlugInType::NFC);
return;
}
readerManager->stopScan(ReaderManagerPlugInType::NFC);
}
bool ApplicationModel::isExtendedLengthApdusUnsupported() const
{
if (mContext)
{
if (mContext->currentReaderHasEidCardButInsufficientApduLength())
{
return true;
}
if (!mContext->getReaderName().isEmpty())
{
ReaderInfo readerInfo = Env::getSingleton<ReaderManager>()->getReaderInfo(mContext->getReaderName());
return !readerInfo.sufficientApduLength();
}
}
return false;
}
void ApplicationModel::stopNfcScanWithError(const QString& pError) const
{
const auto readerManager = Env::getSingleton<ReaderManager>();
readerManager->stopScan(ReaderManagerPlugInType::NFC, pError);
}
bool ApplicationModel::isBluetoothAvailable() const
{
return getFirstPlugInInfo(ReaderManagerPlugInType::BLUETOOTH).isAvailable();
}
bool ApplicationModel::isBluetoothResponding() const
{
return getFirstPlugInInfo(ReaderManagerPlugInType::BLUETOOTH).isResponding();
}
bool ApplicationModel::isBluetoothEnabled() const
{
return getFirstPlugInInfo(ReaderManagerPlugInType::BLUETOOTH).isEnabled();
}
void ApplicationModel::setBluetoothEnabled(bool pEnabled)
{
#if (defined(Q_OS_LINUX) && !defined(QT_NO_DEBUG)) || defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
if (pEnabled)
{
QBluetoothLocalDevice localDevice;
localDevice.powerOn();
qDebug() << "Bluetooth enabled";
}
else
{
qWarning() << "Cannot disable Bluetooth: not supported";
}
#else
qWarning() << (pEnabled ? "Enabling" : "Disabling") << "Bluetooth not supported on this platform";
#endif
}
bool ApplicationModel::locationPermissionRequired() const
{
#ifdef Q_OS_ANDROID
const auto& result = QtAndroid::checkPermission(QStringLiteral("android.permission.ACCESS_COARSE_LOCATION"));
return (result != QtAndroid::PermissionResult::Granted) && Env::getSingleton<ReaderManager>()->getReaderInfos(ReaderManagerPlugInType::BLUETOOTH).isEmpty();
#else
return false;
#endif
}
bool ApplicationModel::isWifiEnabled() const
{
return mWifiEnabled;
}
qreal ApplicationModel::getDpiScale() const
{
return mDpiScale;
}
qreal ApplicationModel::getScaleFactor() const
{
return mScaleFactor;
}
void ApplicationModel::setScaleFactor(qreal pScaleFactor)
{
pScaleFactor *= mDpiScale;
if (qAbs(pScaleFactor - mScaleFactor) > 0)
{
mScaleFactor = pScaleFactor;
Q_EMIT fireScaleFactorChanged();
}
}
QString ApplicationModel::getCurrentWorkflow() const
{
if (mContext.objectCast<ChangePinContext>())
{
return QStringLiteral("changepin");
}
if (mContext.objectCast<SelfAuthContext>())
{
return QStringLiteral("selfauthentication");
}
if (mContext.objectCast<AuthContext>())
{
return QStringLiteral("authentication");
}
return QString();
}
bool ApplicationModel::foundSelectedReader() const
{
if (!mContext)
{
return false;
}
return !Env::getSingleton<ReaderManager>()->getReaderInfos(ReaderFilter(mContext->getReaderPlugInTypes())).isEmpty();
}
bool ApplicationModel::isReaderTypeAvailable(ReaderManagerPlugInType pPlugInType) const
{
if (!mContext)
{
return false;
}
if (!mContext->getReaderName().isEmpty())
{
return Env::getSingleton<ReaderManager>()->getReaderInfo(mContext->getReaderName()).getPlugInType() == pPlugInType;
}
return !Env::getSingleton<ReaderManager>()->getReaderInfos(ReaderFilter({pPlugInType})).isEmpty();
}
void ApplicationModel::showSettings(const ApplicationModel::Settings& pAction)
{
#ifdef Q_OS_ANDROID
const auto& androidQ = QOperatingSystemVersion(QOperatingSystemVersion::Android, 10);
switch (pAction)
{
case Settings::SETTING_NETWORK:
if (QOperatingSystemVersion::current() >= androidQ)
{
showSettings(QStringLiteral("android.settings.panel.action.INTERNET_CONNECTIVITY"));
}
else
{
showSettings(QStringLiteral("android.settings.WIRELESS_SETTINGS"));
}
break;
case Settings::SETTING_NFC:
if (QOperatingSystemVersion::current() >= androidQ)
{
showSettings(QStringLiteral("android.settings.panel.action.NFC"));
}
else
{
showSettings(QStringLiteral("android.settings.NFC_SETTINGS"));
}
break;
case Settings::SETTING_BLUETOOTH:
showSettings(QStringLiteral("android.settings.BLUETOOTH_SETTINGS"));
break;
}
#else
qCWarning(qml) << "NOT IMPLEMENTED:" << pAction;
#endif
}
void ApplicationModel::showSettings(const QString& pAction)
{
#ifdef Q_OS_ANDROID
QAndroidJniEnvironment env;
const QAndroidJniObject& jAction = QAndroidJniObject::fromString(pAction);
QAndroidJniObject intent("android/content/Intent", "(Ljava/lang/String;)V", jAction.object<jstring>());
const jint flag = QAndroidJniObject::getStaticField<jint>("android/content/Intent", "FLAG_ACTIVITY_NEW_TASK");
intent.callObjectMethod("setFlags", "(I)V", flag);
if (intent.isValid())
{
qCCritical(qml) << "Call action:" << pAction;
QtAndroid::startActivity(intent, 0);
}
if (env->ExceptionCheck())
{
qCCritical(qml) << "Cannot call an action as activity:" << pAction;
env->ExceptionDescribe();
env->ExceptionClear();
}
#else
qCWarning(qml) << "NOT IMPLEMENTED:" << pAction;
#endif
}
#ifndef Q_OS_IOS
bool ApplicationModel::isScreenReaderRunning() const
{
#ifdef Q_OS_ANDROID
const jboolean result = QtAndroid::androidActivity().callMethod<jboolean>("isScreenReaderRunning", "()Z");
return result != JNI_FALSE;
#else
qCWarning(qml) << "NOT IMPLEMENTED";
return false;
#endif
}
#endif
QString ApplicationModel::getFeedback() const
{
return mFeedback.isEmpty() ? QString() : mFeedback.first();
}
void ApplicationModel::onShowNextFeedback()
{
mFeedback.removeFirst();
if (!mFeedback.isEmpty() && !isScreenReaderRunning())
{
mFeedbackTimer.start(mFeedbackDisplayLength);
}
Q_EMIT fireFeedbackChanged();
}
void ApplicationModel::showFeedback(const QString& pMessage, bool pReplaceExisting)
{
qCInfo(feedback).noquote() << pMessage;
#if defined(Q_OS_ANDROID)
Q_UNUSED(pReplaceExisting)
// Wait for toast activation synchronously so that the app can not be deactivated
// in the meantime and all used Java objects are still alive when accessed.
QtAndroid::runOnAndroidThreadSync([pMessage](){
QAndroidJniEnvironment env;
const QAndroidJniObject& jMessage = QAndroidJniObject::fromString(pMessage);
const QAndroidJniObject& toast = QAndroidJniObject::callStaticObjectMethod(
"android/widget/Toast",
"makeText",
"(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;",
QtAndroid::androidActivity().object(),
jMessage.object(),
jint(1));
toast.callMethod<void>("show");
if (env->ExceptionCheck())
{
qCCritical(qml) << "Suppressing an unexpected exception.";
env->ExceptionDescribe();
env->ExceptionClear();
// The toast was probably not displayed (e.g. DeadObjectException). We halt on error
// since it is used to display information to the user as required by the TR.
Q_ASSERT(false);
}
});
#else
if (pReplaceExisting)
{
mFeedbackTimer.stop();
mFeedback.clear();
}
const bool initial = mFeedback.isEmpty();
mFeedback << pMessage;
if (initial)
{
if (!isScreenReaderRunning())
{
mFeedbackTimer.start(mFeedbackDisplayLength);
}
Q_EMIT fireFeedbackChanged();
}
#endif
}
#ifndef Q_OS_IOS
void ApplicationModel::keepScreenOn(bool pActive)
{
#if defined(Q_OS_ANDROID)
QtAndroid::runOnAndroidThread([pActive](){
QtAndroid::androidActivity().callMethod<void>("keepScreenOn", "(Z)V", pActive);
QAndroidJniEnvironment env;
if (env->ExceptionCheck())
{
qCCritical(qml) << "Exception calling java native function.";
env->ExceptionDescribe();
env->ExceptionClear();
}
});
#else
qCWarning(qml) << "NOT IMPLEMENTED:" << pActive;
#endif
}
#endif
void ApplicationModel::openOnlineHelp(const QString& pHelpSectionName)
{
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
qCWarning(qml) << "NOT IMPLEMENTED:" << pHelpSectionName;
#else
HelpAction::openContextHelp(pHelpSectionName);
#endif
}
void ApplicationModel::onWifiEnabledChanged()
{
mWifiEnabled = mWifiInfo.isWifiEnabled();
Q_EMIT fireWifiEnabledChanged();
}
void ApplicationModel::enableWifi()
{
#ifdef Q_OS_IOS
showFeedback(tr("Please enable Wi-Fi in your system settings."));
#else
mWifiInfo.enableWifi();
#endif
}
ApplicationModel& ApplicationModel::getInstance()
{
return *Instance;
}
QString ApplicationModel::stripHtmlTags(QString pString) const
{
return pString.remove(QRegularExpression(QStringLiteral("<[^>]*>")));
}