diff --git a/resources/ausweisapp.qrc b/resources/ausweisapp.qrc index a845aba..400320a 100644 --- a/resources/ausweisapp.qrc +++ b/resources/ausweisapp.qrc @@ -74,9 +74,11 @@ images/icon_nfc.svg images/icon_remote.svg images/icon_bluetooth.svg + images/icon_omapi.svg images/phone_nfc.svg images/phone_remote.svg images/phone_bluetooth.svg + images/phone_omapi.svg images/location_flag_de.svg images/location_flag_en.svg images/siteWithLogo.png diff --git a/resources/images/icon_omapi.svg b/resources/images/icon_omapi.svg new file mode 100644 index 0000000..6769f10 --- /dev/null +++ b/resources/images/icon_omapi.svg @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/resources/images/phone_omapi.svg b/resources/images/phone_omapi.svg new file mode 100644 index 0000000..47abc05 --- /dev/null +++ b/resources/images/phone_omapi.svg @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/resources/packaging/android/AndroidManifest.xml.apk.in b/resources/packaging/android/AndroidManifest.xml.apk.in index 9e19b92..cebe1a6 100644 --- a/resources/packaging/android/AndroidManifest.xml.apk.in +++ b/resources/packaging/android/AndroidManifest.xml.apk.in @@ -157,7 +157,17 @@ + + + + + + + + @@ -173,6 +183,9 @@ + + + diff --git a/resources/packaging/android/build.gradle.append b/resources/packaging/android/build.gradle.append index 19efe91..1892d10 100644 --- a/resources/packaging/android/build.gradle.append +++ b/resources/packaging/android/build.gradle.append @@ -6,10 +6,14 @@ task sourcesJar(type: Jar) { dependencies { compile "com.android.support:support-v4:21.0.3" + compile 'net.vx4:lib.omw.ivid:0.0.1-SNAPSHOT' + provided 'net.vx4:lib.omw.omapi:0.0.1-SNAPSHOT' } allprojects { repositories { + mavenLocal() + maven { url "https://maven.google.com" } diff --git a/resources/qml/Governikus/ChangePinView/ChangePinController.qml b/resources/qml/Governikus/ChangePinView/ChangePinController.qml index 1b0cd8d..c5f13a3 100644 --- a/resources/qml/Governikus/ChangePinView/ChangePinController.qml +++ b/resources/qml/Governikus/ChangePinView/ChangePinController.qml @@ -82,7 +82,11 @@ Item { } break case "StateConnectCard": - setPinWorkflowStateAndContinue(ChangePinController.WorkflowStates.Card) + if (d.readerPlugInType === ReaderPlugIn.OMAPI) { + controller.workflowState = IdentifyController.WorkflowStates.Card + } else { + setIdentifyWorkflowStateAndContinue(IdentifyController.WorkflowStates.Card) + } break case "StatePreparePace": fireReplace(pinProgressView) diff --git a/resources/qml/Governikus/IdentifyView/+mobile/IdentifyController.qml b/resources/qml/Governikus/IdentifyView/+mobile/IdentifyController.qml index 15c875f..f917f94 100644 --- a/resources/qml/Governikus/IdentifyView/+mobile/IdentifyController.qml +++ b/resources/qml/Governikus/IdentifyView/+mobile/IdentifyController.qml @@ -111,7 +111,11 @@ Item { } break case "StateConnectCard": - setIdentifyWorkflowStateAndContinue(IdentifyController.WorkflowStates.Card) + if (d.readerPlugInType === ReaderPlugIn.OMAPI) { + controller.workflowState = IdentifyController.WorkflowStates.Card + } else { + setIdentifyWorkflowStateAndContinue(IdentifyController.WorkflowStates.Card) + } break case "StatePreparePace": fireReplace(identifyProgressView) diff --git a/resources/qml/Governikus/TechnologyInfo/+android/TechnologySwitch.qml b/resources/qml/Governikus/TechnologyInfo/+android/TechnologySwitch.qml index a62f492..d44caa8 100644 --- a/resources/qml/Governikus/TechnologyInfo/+android/TechnologySwitch.qml +++ b/resources/qml/Governikus/TechnologyInfo/+android/TechnologySwitch.qml @@ -38,5 +38,12 @@ Rectangle { imageSource: "qrc:///images/icon_bluetooth.svg" text: qsTr("Bluetooth") + settingsModel.translationTrigger } + + TechnologySwitchButton { + buttonActive: selectedTechnology !== ReaderPlugIn.OMAPI + onClicked: baseItem.requestPluginType(ReaderPlugIn.OMAPI) + imageSource: "qrc:///images/icon_omapi.svg" + text: qsTr("meID") + settingsModel.translationTrigger + } } } diff --git a/resources/qml/Governikus/Workflow/+mobile/GeneralWorkflow.qml b/resources/qml/Governikus/Workflow/+mobile/GeneralWorkflow.qml index 5998e93..668d409 100644 --- a/resources/qml/Governikus/Workflow/+mobile/GeneralWorkflow.qml +++ b/resources/qml/Governikus/Workflow/+mobile/GeneralWorkflow.qml @@ -51,4 +51,12 @@ SectionPage onContinueWorkflow: workflowModel.continueWorkflow() onRequestPluginType: workflowModel.readerPlugInType = pReaderPlugInType; } + + OmapiWorkflow + { + anchors.fill: parent + waitingFor: baseItem.waitingFor + visible: d.readerPlugInType === ReaderPlugIn.OMAPI + onRequestPluginType: workflowModel.readerPlugInType = pReaderPlugInType; + } } diff --git a/resources/qml/Governikus/Workflow/+mobile/OmapiWorkflow.qml b/resources/qml/Governikus/Workflow/+mobile/OmapiWorkflow.qml new file mode 100644 index 0000000..88322fd --- /dev/null +++ b/resources/qml/Governikus/Workflow/+mobile/OmapiWorkflow.qml @@ -0,0 +1,55 @@ +import QtQuick 2.10 +import QtQuick.Layouts 1.1 + +import Governikus.Global 1.0 +import Governikus.TechnologyInfo 1.0 +import Governikus.Type.ApplicationModel 1.0 +import Governikus.Type.ReaderPlugIn 1.0 +import Governikus.Type.NumberModel 1.0 + + +Item { + id: baseItem + signal requestPluginType(int pReaderPlugInType) + property int waitingFor: 0 + clip: true + + ProgressIndicator { + id: progressIndicator + anchors.left: parent.left + anchors.top: parent.top + anchors.right: parent.right + height: parent.height / 2 + imageIconSource: "qrc:///images/icon_omapi.svg" + imagePhoneSource: "qrc:///images/phone_omapi.svg" + state: baseItem.waitingFor === Workflow.WaitingFor.Reader ? "off" : "one" + } + + TechnologyInfo { + id: technologyInfo + + anchors.left: parent.left + anchors.leftMargin: Utils.dp(5) + anchors.right: parent.right + anchors.rightMargin: anchors.leftMargin + anchors.top: progressIndicator.bottom + anchors.bottom: technologySwitch.top + clip: true + + enableButtonVisible: baseItem.waitingFor !== Workflow.WaitingFor.Reader + enableButtonText: qsTr("Continue") + settingsModel.translationTrigger + enableText: (enableButtonVisible ? qsTr("Please confirm the usage of your mobile eID.") : "") + settingsModel.translationTrigger + onEnableClicked: workflowModel.continueWorkflow() + titleText: qsTr("No mobile eID found") + settingsModel.translationTrigger + subTitleText: qsTr("Please ensure that a SIM card is inserted and a suitable mobile eID is stored there.") + settingsModel.translationTrigger + } + + TechnologySwitch { + id: technologySwitch + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + selectedTechnology: ReaderPlugIn.OMAPI + onRequestPluginType: parent.requestPluginType(pReaderPlugInType) + } +} diff --git a/resources/qml/Governikus/Workflow/qmldir b/resources/qml/Governikus/Workflow/qmldir index 07e5325..157ea94 100644 --- a/resources/qml/Governikus/Workflow/qmldir +++ b/resources/qml/Governikus/Workflow/qmldir @@ -5,6 +5,7 @@ internal BusyImageIndicator BusyImageIndicator.qml internal CardReader CardReader.qml internal NfcProgressIndicator NfcProgressIndicator.qml internal NfcWorkflow NfcWorkflow.qml +internal OmapiWorkflow OmapiWorkflow.qml internal ProgressCircle ProgressCircle.qml internal ProgressIndicator ProgressIndicator.qml internal RemoteWorkflow RemoteWorkflow.qml diff --git a/resources/translations/ausweisapp2_de.ts b/resources/translations/ausweisapp2_de.ts index 0b9574d..6b7d903 100644 --- a/resources/translations/ausweisapp2_de.ts +++ b/resources/translations/ausweisapp2_de.ts @@ -1306,6 +1306,25 @@ Bitte platzieren Sie Ihr Gerät<br/>über Ihrem Personalausweis. + + OmapiWorkflow + + Continue + Fortsetzen + + + Please confirm the usage of your mobile eID. + Bitte bestätigen Sie die Verwendung ihrer mobilen eID. + + + No mobile eID found + Keine mobile eID gefunden + + + Please ensure that a SIM card is inserted and a suitable mobile eID is stored there. + Bitte stellen Sie sicher, dass eine SIM-Karte eingelegt ist und eine passende mobile eID hinterlegt ist. + + PinSettingsWidget @@ -2126,6 +2145,10 @@ Bitte beachten Sie, dass Sie mit Ihrer PUK lediglich Ihren Online-Ausweis entspe Use Bluetooth card reader instead<br/>of remote card reader Bluetooth Kartenlesegerät anstelle <br/>vom WLAN Kartenlesegerät verwenden + + meID + meID + TitleBar diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fcb7696..04333c5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -206,6 +206,7 @@ IF(ANDROID) TARGET_LINK_LIBRARIES(AusweisApp AusweisAppCardNfc) IF(NOT ANDROID_BUILD_AAR) TARGET_LINK_LIBRARIES(AusweisApp AusweisAppActivationIntent) + TARGET_LINK_LIBRARIES(AusweisApp AusweisAppCardOmapi) ENDIF() ENDIF() diff --git a/src/card/CMakeLists.txt b/src/card/CMakeLists.txt index 1ba6fb9..f4f5983 100644 --- a/src/card/CMakeLists.txt +++ b/src/card/CMakeLists.txt @@ -23,4 +23,8 @@ IF(TARGET Qt5::Bluetooth) ADD_SUBDIRECTORY(bluetooth) ENDIF() +IF(ANDROID) + ADD_SUBDIRECTORY(omapi) +ENDIF() + ADD_SUBDIRECTORY(drivers) diff --git a/src/card/base/ReaderManagerPlugInInfo.h b/src/card/base/ReaderManagerPlugInInfo.h index 199c3d7..cd02d15 100644 --- a/src/card/base/ReaderManagerPlugInInfo.h +++ b/src/card/base/ReaderManagerPlugInInfo.h @@ -15,7 +15,7 @@ namespace governikus { -defineEnumType(ReaderManagerPlugInType, UNKNOWN, PCSC, BLUETOOTH, NFC, REMOTE) +defineEnumType(ReaderManagerPlugInType, UNKNOWN, PCSC, BLUETOOTH, NFC, REMOTE, OMAPI) class ReaderManagerPlugInInfo diff --git a/src/card/omapi/CMakeLists.txt b/src/card/omapi/CMakeLists.txt new file mode 100644 index 0000000..b1979e7 --- /dev/null +++ b/src/card/omapi/CMakeLists.txt @@ -0,0 +1,15 @@ +##################################################################### +# The ReaderManagerPlugin for OMAPI. +# +# This plugin is a wrapper for the Virtual eID on a SIM card. +# Supported: Android. +##################################################################### + +ADD_PLATFORM_LIBRARY(AusweisAppCardOmapi) + +TARGET_LINK_LIBRARIES(AusweisAppCardOmapi Qt5::Core AusweisAppGlobal AusweisAppCard) +TARGET_COMPILE_DEFINITIONS(AusweisAppCardOmapi PRIVATE QT_STATICPLUGIN) + +IF(ANDROID) + TARGET_LINK_LIBRARIES(AusweisAppCardOmapi Qt5::AndroidExtras) +ENDIF() diff --git a/src/card/omapi/Omapi.java b/src/card/omapi/Omapi.java new file mode 100644 index 0000000..fa577a0 --- /dev/null +++ b/src/card/omapi/Omapi.java @@ -0,0 +1,51 @@ +/*! + * \copyright Copyright (c) 2019 Governikus GmbH & Co. KG, Germany + */ + +package com.governikus.ausweisapp2.omapi; + +import java.io.Closeable; +import java.util.List; + +import com.governikus.ausweisapp2.omapi.OmapiError; +import com.governikus.ausweisapp2.omapi.OmapiReader; + +/** + * + */ +public interface Omapi extends Closeable // AutoClosable requires min. API level 19 +{ + /** + * Initialize omapi layer. + * This must be called if isAvailable() = false + * + * @return Null on success, otherwise an error. + */ + OmapiError init(); + + /** + * Check if omapi layer is available. + * + * @return True if omapi layer exists, otherwise false. + */ + boolean isAvailable(); + + /** + * Check if omapi layer is enabled. + * + * @return True if omapi layer is enabled, otherwise false. + */ + boolean isEnabled(); + + /** + * Get all enabled reader. + * + * @return List of reader, otherwise empty list. + */ + List getReader(); + + /** + * Closes and destroys the currently allocated implementation instance. + */ + void close(); +} diff --git a/src/card/omapi/OmapiCard.cpp b/src/card/omapi/OmapiCard.cpp new file mode 100644 index 0000000..a6aa1bc --- /dev/null +++ b/src/card/omapi/OmapiCard.cpp @@ -0,0 +1,194 @@ +/*! + * \copyright Copyright (c) 2019 Governikus GmbH & Co. KG, Germany + */ + +#include "OmapiCard.h" + +#include "DestroyPaceChannel.h" +#include "EstablishPaceChannel.h" + +#include + +using namespace governikus; + + +Q_DECLARE_LOGGING_CATEGORY(card_omapi) + + +OmapiCard::OmapiCard() + : Card() + , mIsValid(true) + , mConnected(false) +{ +} + + +bool OmapiCard::isValid() const +{ + return mIsValid; +} + + +CardReturnCode OmapiCard::connect() +{ + mConnected = true; + return CardReturnCode::OK; +} + + +CardReturnCode OmapiCard::disconnect() +{ + mConnected = false; + return CardReturnCode::OK; +} + + +bool OmapiCard::isConnected() +{ + return mConnected; +} + + +#ifdef Q_OS_ANDROID +QByteArray OmapiCard::sendData(const QByteArray& pData) +{ + QByteArray ret; + QAndroidJniEnvironment env; + jbyteArray cmd = convert(pData); + + const QAndroidJniObject javaActivity(QtAndroid::androidActivity()); + if (!javaActivity.isValid()) + { + qCritical() << "Cannot determine android activity"; + return ret; + } + + const auto response = QAndroidJniObject::callStaticObjectMethod("com/governikus/ausweisapp2/omapi/impl/OmapiJNI", + "transmit", + "([B)[B", + cmd); + + if (!response.isValid()) + { + return ret; + } + + ret = convert(static_cast(response.object())); + if (env->ExceptionCheck()) + { + qCritical() << "Cannot call Omapi.transmit()"; + env->ExceptionDescribe(); + env->ExceptionClear(); + } + env->DeleteLocalRef(cmd); + + return ret; +} + + +QByteArray OmapiCard::convert(const jbyteArray& pData) +{ + QAndroidJniEnvironment env; + + jsize size = env->GetArrayLength(pData); + QVector buffer(size); + env->GetByteArrayRegion(pData, 0, size, buffer.data()); + + return QByteArray(reinterpret_cast(buffer.data()), buffer.size()); +} + + +jbyteArray OmapiCard::convert(const QByteArray& pData) +{ + QAndroidJniEnvironment env; + const int size = pData.size(); + const char* buffer = pData.constData(); + + jbyteArray target = env->NewByteArray(size); + jbyte* bytes = env->GetByteArrayElements(target, 0); + for (int i = 0; i < size; ++i) + { + bytes[i] = static_cast(buffer[i]); + } + env->SetByteArrayRegion(target, 0, size, bytes); + + return target; +} + + +CardReturnCode OmapiCard::establishPaceChannel(PacePasswordId pPasswordId, + const QByteArray& pChat, + const QByteArray& pCertificateDescription, + EstablishPaceChannelOutput& pChannelOutput, quint8 pTimeoutSeconds) +{ + Q_UNUSED(pTimeoutSeconds); + + EstablishPaceChannel builder; + builder.setPasswordId(pPasswordId); + builder.setChat(pChat); + builder.setCertificateDescription(pCertificateDescription); + + QAndroidJniEnvironment env; + jbyteArray paceData = convert(builder.createCommandData()); + + const QAndroidJniObject javaActivity(QtAndroid::androidActivity()); + if (!javaActivity.isValid()) + { + qCritical() << "Cannot determine android activity"; + return CardReturnCode::COMMAND_FAILED; + } + + const auto response = QAndroidJniObject::callStaticObjectMethod("com/governikus/ausweisapp2/omapi/impl/OmapiJNI", + "paceControl", + "([B)[B", + paceData); + + if (!response.isValid()) + { + return CardReturnCode::COMMAND_FAILED; + } + + if (env->ExceptionCheck()) + { + qCritical() << "Cannot call Omapi.paceControl()"; + env->ExceptionDescribe(); + env->ExceptionClear(); + } + + env->DeleteLocalRef(paceData); + paceData = static_cast(response.object()); + if (env->IsSameObject(paceData, NULL)) + { + qCritical() << "Control to establish PACE channel failed"; + return CardReturnCode::COMMAND_FAILED; + } + + pChannelOutput.parse(convert(paceData), pPasswordId); + return pChannelOutput.getPaceReturnCode(); +} + + +CardReturnCode OmapiCard::destroyPaceChannel() +{ + // TODO: to be completed, for now PACE channel is destroyed implicitly on establish() + return CardReturnCode::OK; +} + + +#endif + +CardReturnCode OmapiCard::transmit(const CommandApdu& pCmd, ResponseApdu& pRes) +{ +#ifdef Q_OS_ANDROID + const auto& data = pCmd.getBuffer(); + qCDebug(card_omapi) << "transmit to omapi:" << data; + const auto& response = sendData(data); + qCDebug(card_omapi) << "received from omapi:" << response; + pRes.setBuffer(response); + +#else + Q_UNUSED(pCmd) + Q_UNUSED(pRes) +#endif + return CardReturnCode::OK; +} diff --git a/src/card/omapi/OmapiCard.h b/src/card/omapi/OmapiCard.h new file mode 100644 index 0000000..1ff3a32 --- /dev/null +++ b/src/card/omapi/OmapiCard.h @@ -0,0 +1,51 @@ +/*! + * \brief Implementation of \ref Card for Omapi. + * + * \copyright Copyright (c) 2019 Governikus GmbH & Co. KG, Germany + */ + +#pragma once + +#include "Card.h" + +#ifdef Q_OS_ANDROID +#include +#include +#include +#endif + +namespace governikus +{ +class OmapiCard + : public Card +{ + Q_OBJECT + + private: + bool mIsValid; + bool mConnected; + +#ifdef Q_OS_ANDROID + QByteArray convert(const jbyteArray& pData); + jbyteArray convert(const QByteArray& pData); + QByteArray sendData(const QByteArray& pData); +#endif + + public: + OmapiCard(); + virtual ~OmapiCard() override = default; + + bool isValid() const; + + virtual CardReturnCode connect() override; + virtual CardReturnCode disconnect() override; + virtual bool isConnected() override; + + virtual CardReturnCode transmit(const CommandApdu& pCmd, ResponseApdu& pRes) override; + + virtual CardReturnCode establishPaceChannel(PacePasswordId pPasswordId, const QByteArray& pChat, const QByteArray& pCertificateDescription, EstablishPaceChannelOutput& pChannelOutput, quint8 pTimeoutSeconds) override; + + virtual CardReturnCode destroyPaceChannel() override; +}; + +} // namespace governikus diff --git a/src/card/omapi/OmapiCard.java b/src/card/omapi/OmapiCard.java new file mode 100644 index 0000000..c0b6b27 --- /dev/null +++ b/src/card/omapi/OmapiCard.java @@ -0,0 +1,18 @@ +/*! + * \copyright Copyright (c) 2019 Governikus GmbH & Co. KG, Germany + */ + +package com.governikus.ausweisapp2.omapi; + +/** + * + */ +public interface OmapiCard +{ + /** + * Send an APDU to the card. + * + * @return Response apdu, null on error. + */ + byte[] transmit(final byte[] apdu); +} diff --git a/src/card/omapi/OmapiError.java b/src/card/omapi/OmapiError.java new file mode 100644 index 0000000..26373c8 --- /dev/null +++ b/src/card/omapi/OmapiError.java @@ -0,0 +1,49 @@ +/*! + * \copyright Copyright (c) 2019 Governikus GmbH & Co. KG, Germany + */ + +package com.governikus.ausweisapp2.omapi; + +/** + * + */ +public class OmapiError +{ + private final String error, desc; + + /** + * Create error instance. + * + * @param error + * @param desc + */ + public OmapiError(final String error, final String desc) + { + this.error = error; + this.desc = desc; + } + + + /** + * Get name of error. + * + * @return + */ + public String getName() + { + return error; + } + + + /** + * Get a human readable description of the error. + * + * @return + */ + public String getDescription() + { + return desc; + } + + +} diff --git a/src/card/omapi/OmapiReader.cpp b/src/card/omapi/OmapiReader.cpp new file mode 100644 index 0000000..a9bad50 --- /dev/null +++ b/src/card/omapi/OmapiReader.cpp @@ -0,0 +1,55 @@ +/*! + * \copyright Copyright (c) 2019 Governikus GmbH & Co. KG, Germany + */ + +#include "CardConnectionWorker.h" +#include "OmapiReader.h" + +#include + + +using namespace governikus; + + +Q_DECLARE_LOGGING_CATEGORY(card_omapi) + + +OmapiReader::OmapiReader() + : ConnectableReader(ReaderManagerPlugInType::OMAPI, QStringLiteral("omapi")) + , mConnected(false) +{ + mReaderInfo.setBasicReader(false); + mReaderInfo.setConnected(true); + QMetaObject::invokeMethod(this, &Reader::update, Qt::QueuedConnection); +} + + +void OmapiReader::connectReader() +{ + mConnected = true; + + qCDebug(card_omapi) << "create card"; + mOmapiCard.reset(new OmapiCard); + QSharedPointer cardConnection = createCardConnectionWorker(); + CardInfoFactory::create(cardConnection, mReaderInfo); + Q_EMIT fireCardInserted(getName()); +} + + +void OmapiReader::disconnectReader() +{ + mConnected = false; + mOmapiCard.reset(new OmapiCard); +} + + +Card* OmapiReader::getCard() const +{ + return mOmapiCard.data(); +} + + +Reader::CardEvent OmapiReader::updateCard() +{ + return Reader::CardEvent::NONE; +} diff --git a/src/card/omapi/OmapiReader.h b/src/card/omapi/OmapiReader.h new file mode 100644 index 0000000..9ebe001 --- /dev/null +++ b/src/card/omapi/OmapiReader.h @@ -0,0 +1,37 @@ +/*! + * \brief Implementation of \ref Reader for Omapi. + * + * \copyright Copyright (c) 2015-2019 Governikus GmbH & Co. KG, Germany + */ + +#pragma once + +#include "OmapiCard.h" +#include "Reader.h" + +#include + +namespace governikus +{ + +class OmapiReader + : public ConnectableReader +{ + Q_OBJECT + + private: + bool mConnected; + QScopedPointer mOmapiCard; + + public: + OmapiReader(); + virtual ~OmapiReader() override = default; + + virtual void connectReader() override; + virtual void disconnectReader() override; + + virtual Card* getCard() const override; + virtual CardEvent updateCard() override; +}; + +} // namespace governikus diff --git a/src/card/omapi/OmapiReader.java b/src/card/omapi/OmapiReader.java new file mode 100644 index 0000000..5eab254 --- /dev/null +++ b/src/card/omapi/OmapiReader.java @@ -0,0 +1,21 @@ +/*! + * \copyright Copyright (c) 2019 Governikus GmbH & Co. KG, Germany + */ + +package com.governikus.ausweisapp2.omapi; + +/** + * + */ +public interface OmapiReader +{ + /** + * Get name of reader. + */ + String getName(); + + /** + * Get inserted card, null if not inserted. + */ + OmapiCard getCard(); +} diff --git a/src/card/omapi/OmapiReaderManagerPlugIn.cpp b/src/card/omapi/OmapiReaderManagerPlugIn.cpp new file mode 100644 index 0000000..6764a6e --- /dev/null +++ b/src/card/omapi/OmapiReaderManagerPlugIn.cpp @@ -0,0 +1,108 @@ +/*! + * \copyright Copyright (c) 2019 Governikus GmbH & Co. KG, Germany + */ + +#include "OmapiReaderManagerPlugIn.h" + +#include + +#ifdef Q_OS_ANDROID +#include +#include +#include +#endif + +using namespace governikus; + + +Q_DECLARE_LOGGING_CATEGORY(card_omapi) + +OmapiReaderManagerPlugIn::OmapiReaderManagerPlugIn() + : ReaderManagerPlugIn(ReaderManagerPlugInType::OMAPI, true, true) + , mEnabled(false) + , mReader(new OmapiReader) +{ + connect(mReader.data(), &OmapiReader::fireCardInserted, this, &OmapiReaderManagerPlugIn::fireCardInserted); + connect(mReader.data(), &OmapiReader::fireCardRemoved, this, &OmapiReaderManagerPlugIn::fireCardRemoved); + connect(mReader.data(), &OmapiReader::fireCardRetryCounterChanged, this, &OmapiReaderManagerPlugIn::fireCardRetryCounterChanged); + connect(mReader.data(), &OmapiReader::fireReaderPropertiesUpdated, this, &OmapiReaderManagerPlugIn::fireReaderPropertiesUpdated); + qCDebug(card_omapi) << "Add reader" << mReader->getName(); +} + + +OmapiReaderManagerPlugIn::~OmapiReaderManagerPlugIn() +{ +} + + +QList OmapiReaderManagerPlugIn::getReaders() const +{ + qCDebug(card_omapi) << "get my readers"; + if (mEnabled) + { + return QList({mReader.data()}); + } + + return QList(); +} + + +void OmapiReaderManagerPlugIn::startScan(bool) +{ + qCDebug(card_omapi) << "scan me"; + mEnabled = true; + mReader->connectReader(); + Q_EMIT fireReaderAdded(mReader->getName()); +} + + +void OmapiReaderManagerPlugIn::stopScan() +{ + mEnabled = false; + mReader->disconnectReader(); + Q_EMIT fireReaderRemoved(mReader->getName()); +} + + +void OmapiReaderManagerPlugIn::init() +{ + ReaderManagerPlugIn::init(); +#ifdef Q_OS_ANDROID + qCDebug(card_omapi) << "omapi init"; + + QAndroidJniEnvironment env; + const QAndroidJniObject context(QtAndroid::androidContext()); + if (!context.isValid()) + { + qCritical() << "Cannot determine android context."; + return; + } + + QAndroidJniObject::callStaticMethod("com/governikus/ausweisapp2/omapi/impl/OmapiJNI", + "init", + "(Landroid/content/Context;)Z", + context.object()); + + if (env->ExceptionCheck()) + { + qCritical() << "Cannot call OmapiJNI.init()"; + env->ExceptionDescribe(); + env->ExceptionClear(); + } +#endif +} + + +void OmapiReaderManagerPlugIn::shutdown() +{ +#ifdef Q_OS_ANDROID + QAndroidJniEnvironment env; + QAndroidJniObject::callStaticMethod("com/governikus/ausweisapp2/omapi/impl/OmapiJNI", "close"); + if (env->ExceptionCheck()) + { + qCritical() << "Cannot call OmapiJNI.close()"; + env->ExceptionDescribe(); + env->ExceptionClear(); + } +#endif +} diff --git a/src/card/omapi/OmapiReaderManagerPlugIn.h b/src/card/omapi/OmapiReaderManagerPlugIn.h new file mode 100644 index 0000000..1edf3ea --- /dev/null +++ b/src/card/omapi/OmapiReaderManagerPlugIn.h @@ -0,0 +1,41 @@ +/*! + * \brief Implementation of \ref ReaderManagerPlugIn for Omapi on Android. + * + * \copyright Copyright (c) 2015-2019 Governikus GmbH & Co. KG, Germany + */ + +#pragma once + +#include "OmapiReader.h" +#include "ReaderManagerPlugIn.h" + +#include + +namespace governikus +{ + +class OmapiReaderManagerPlugIn + : public ReaderManagerPlugIn +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "governikus.ReaderManagerPlugIn" FILE "metadata.json") + Q_INTERFACES(governikus::ReaderManagerPlugIn) + + private: + bool mEnabled; + QScopedPointer mReader; + + public: + OmapiReaderManagerPlugIn(); + virtual ~OmapiReaderManagerPlugIn() override; + + virtual QList getReaders() const override; + + virtual void startScan(bool pAutoConnect) override; + virtual void stopScan() override; + + virtual void init() override; + virtual void shutdown() override; +}; + +} // namespace governikus diff --git a/src/card/omapi/impl/OmapiImpl.java b/src/card/omapi/impl/OmapiImpl.java new file mode 100644 index 0000000..15c38a2 --- /dev/null +++ b/src/card/omapi/impl/OmapiImpl.java @@ -0,0 +1,202 @@ +/*! + * \copyright Copyright (c) 2019 Governikus GmbH & Co. KG, Germany + */ + +package com.governikus.ausweisapp2.omapi.impl; + +import android.content.Context; +import com.governikus.ausweisapp2.omapi.Omapi; +import com.governikus.ausweisapp2.omapi.OmapiError; +import com.governikus.ausweisapp2.omapi.OmapiReader; +import net.vx4.lib.omapi.Hex; +import net.vx4.lib.omapi.OMAPITP; +import org.simalliance.openmobileapi.Channel; +import org.simalliance.openmobileapi.Reader; +import org.simalliance.openmobileapi.Session; +import org.simalliance.openmobileapi.SEService; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * This class is the main entry point into the wrapper and handels basic logic as selecting the applet instance on the + * secure element. + * + * @author kahlo, 2018 + */ +public class OmapiImpl implements Omapi, SEService.CallBack +{ + private final Context mContext; + private final SEService seService; + private final List readerList = new ArrayList<>(); + + OmapiImpl(final Context context) + { + System.out.println("OmapiImpl.OmapiImpl"); + System.out.println("context = [" + context + "]"); + + this.mContext = context; + this.seService = new SEService(this.mContext, OmapiImpl.this); + System.out.println("SEService0: " + OmapiImpl.this.seService + " connected? " + OmapiImpl.this.seService.isConnected()); + } + + + private void log(final String msg) + { + System.out.println("OmapiImpl.log: " + msg); + } + + + private String toHex(final byte[] in) + { + return Hex.toString(in); + } + + + @Override + public void serviceConnected(final SEService service) + { + System.out.println("OmapiImpl.serviceConnected"); + System.out.println("service = [" + service + "]"); + + if (this.seService == service) + { + System.out.println("SEService1: " + this.seService + " connected? " + (this.seService == null ? "null" : this.seService.isConnected())); + System.out.println("SEService2: " + service + " connected? " + (service == null ? "null" : service.isConnected())); + } + else + { + System.out.println("SHOULD NOT HAPPEN: ignoring callback from foreign service = [" + service + "]"); + } + } + + + @Override + public boolean isAvailable() + { + System.out.println("OmapiImpl.isAvailable"); + + for (int i = 0; i < 20 && this.seService != null && !this.seService.isConnected(); i++) + { + System.out.println("waiting for connect"); + try + { + Thread.sleep(100); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + } + + System.out.println("SEService*: " + this.seService + " connected? " + (this.seService == null ? "null" : this.seService.isConnected())); + return this.seService != null && this.seService.isConnected(); + } + + + @Override + public OmapiError init() + { + if (!isAvailable()) + { + return new OmapiError("No Secure Element API", "OpenMobile API not available."); + } + + System.out.println("OmapiImpl.init"); + final Reader[] readers = this.seService.getReaders(); + log("readers: " + (readers != null ? readers.length : "NONE")); + log("readers: " + (readers != null ? Arrays.asList(readers) : "NONE")); + + if (readers != null && readers.length > 0) + { + for (final Reader reader : readers) + { + try + { + log("Reader: " + reader.getName() + " - SE present? " + reader.isSecureElementPresent()); + if (reader.isSecureElementPresent()) + { + final Session session = readers[0].openSession(); + + log("ATR: " + toHex(session.getATR())); + log("Create logical channel to D2760000930101 within the session..."); + + final Channel channel = session.openLogicalChannel(Hex.fromString("D2760000930101")); + if (channel != null) + { + log("Channel: " + channel + " / " + Hex.x(channel.getSelectResponse())); + // TODO: check channel and status + // 6F0E8407D276000093010185030300FF9000 + + log("Send CIPHER ENABLE command"); + final byte[] respApdu = channel.transmit(Hex.fromString("80CE000000")); + log("Response: " + Hex.toString(respApdu)); + // TODO: check type depending on supported algs and name of instance if multi-SE + // 7C0C8004FFFFFFFF8104544553549000 + + final OMAPITP tp = new OMAPITP(channel); + new OmapiSecretFetcherImpl.Builder(this.mContext).set(tp); + + readerList.add(new OmapiReaderImpl(tp)); + } + } + } + catch (final Exception e) + { + // don't fail if single reader doesn't work, print log + //return new OmapiErrorImpl("Exception while connecting to readers", e.getMessage()); + e.printStackTrace(); + } + } + } + else + { + return new OmapiError("No readers", "No readers available"); + } + + // no error + return null; + } + + + @Override + public boolean isEnabled() + { + System.out.println("OmapiImpl.isEnabled"); + return readerList.size() > 0; + } + + + @Override + public List getReader() + { + System.out.println("OmapiImpl.getReader"); + System.out.println("readerList = [" + readerList + "]"); + + return readerList; + } + + + @Override + public void close() + { + for (final OmapiReader r : this.readerList) + { + if (r != null) + { + final Channel c = ((OmapiReaderImpl) r).getChannel(); + if (!c.isClosed()) + { + c.getSession().closeChannels(); + c.getSession().close(); + } + } + } + + this.readerList.clear(); + // seService.shutdown(); + } + + +} diff --git a/src/card/omapi/impl/OmapiJNI.java b/src/card/omapi/impl/OmapiJNI.java new file mode 100644 index 0000000..aed3333 --- /dev/null +++ b/src/card/omapi/impl/OmapiJNI.java @@ -0,0 +1,224 @@ +/*! + * \copyright Copyright (c) 2019 Governikus GmbH & Co. KG, Germany + */ + +package com.governikus.ausweisapp2.omapi.impl; + +import android.content.Context; +import android.util.Log; +import com.governikus.ausweisapp2.omapi.Omapi; +import com.governikus.ausweisapp2.omapi.OmapiCard; +import com.governikus.ausweisapp2.omapi.OmapiReader; +import net.vx4.lib.omapi.ArrayTool; +import net.vx4.lib.omapi.Hex; + +/** + * Factory for OMAPI Wrapper and simple JNI entry for the Qt code. + */ +public final class OmapiJNI +{ + private static final String LOG_TAG = "AusweisApp2.OMAPI"; + private static final OmapiJNI INSTANCE = new OmapiJNI(); + + private Omapi mOmapi; + private OmapiCard mCard; + + private OmapiJNI() + { + } + + + private static final OmapiJNI get() + { + return INSTANCE; + } + + + /** + * Initialize this factory for later usage. + * + * @param context Android {@link Context} of application (service or activity) + * @return + */ + public static boolean init(final Context context) + { + return get()._init0(context); + } + + + /** + * Transmit data to first available and functional SE. + * + * @param apdu + * @return + */ + public static byte[] transmit(final byte[] apdu) + { + return get()._transmit0(apdu); + } + + + /** + * Execute CCID PACE with control message + * + * @param paceData + * @return + */ + public static byte[] paceControl(final byte[] paceData) + { + return get()._paceControl0(paceData); + } + + + /** + * Closes and destroys the currently allocated implementation instance. + */ + public static void close() + { + get()._close0(); + } + + + /** + * Retrieve internal service provider object. + * + * @return instance of {@link Omapi} + */ + public static final Omapi getInstance() + { + return INSTANCE.mOmapi; + } + + + /* + // instance implementation + */ + + /** + * @param context + * @return + */ + private final boolean _init0(final Context context) + { + Log.d(LOG_TAG, "init omapi jni"); + System.out.println("OmapiJNI.init"); + System.out.println("context = [" + context + "]"); + + if (this.mOmapi == null) + { + this.mOmapi = new OmapiImpl(context); + if (this.mOmapi.isAvailable()) + { + this.mOmapi.init(); + } + } + + for (final OmapiReader reader : this.mOmapi.getReader()) + { + if ((this.mCard = reader.getCard()) != null) + { + return true; + } + } + + return false; + } + + + /** + * @param apdu + * @return + */ + private final byte[] _transmit0(final byte[] apdu) + { + Log.d(LOG_TAG, "transmit apdu with omapi jni"); + System.out.println("OmapiJNI.transmit"); + System.out.println("apdu = [" + Hex.x(apdu) + "]"); + + if (this.mOmapi != null) + { + if (this.mCard == null) + { + System.out.println("OmapiJNI.transmit: check for readers."); + for (final OmapiReader reader : this.mOmapi.getReader()) + { + if ((this.mCard = reader.getCard()) != null) + { + break; + } + } + } + + if (this.mCard != null) + { + return this.mCard.transmit(apdu); + } + } + + return null; + } + + + /** + * @param paceData + * @return + */ + private final byte[] _paceControl0(final byte[] paceData) + { + Log.d(LOG_TAG, "paceControl with omapi jni"); + System.out.println("OmapiJNI.paceControl"); + System.out.println("paceData = [" + paceData != null ? Hex.x(paceData) : "null" + "]"); + + if (this.mOmapi != null) + { + if (this.mCard == null) + { + System.out.println("OmapiJNI.paceControl: check for readers."); + for (final OmapiReader reader : this.mOmapi.getReader()) + { + if ((this.mCard = reader.getCard()) != null) + { + break; + } + } + } + + if (this.mCard != null) + { + if (paceData == null) // use as short hand for get caps or destroy? + { // TODO: to be completed + } + + // keep in mind: extended length format and little to big endian conversion + final byte[] cmd = new byte[] { + (byte) 0xFF, (byte) 0x9A, (byte) 0x04, paceData[0], (byte) 0x00, paceData[2], paceData[1] + }; + final byte[] res = this.mCard.transmit(ArrayTool.concat(cmd, ArrayTool.sub(paceData, 3, paceData.length - 3))); + + return ArrayTool.sub(res, 0, res.length - 2); + } + } + + return null; + } + + + /** + * + */ + private final void _close0() + { + Log.d(LOG_TAG, "transmit apdu with omapi jni"); + try + { + this.mOmapi.close(); + } + finally + { + this.mOmapi = null; + this.mCard = null; + } + } + + +} diff --git a/src/card/omapi/impl/OmapiReaderImpl.java b/src/card/omapi/impl/OmapiReaderImpl.java new file mode 100644 index 0000000..b27cddd --- /dev/null +++ b/src/card/omapi/impl/OmapiReaderImpl.java @@ -0,0 +1,79 @@ +/*! + * \copyright Copyright (c) 2019 Governikus GmbH & Co. KG, Germany + */ + +package com.governikus.ausweisapp2.omapi.impl; + +import android.util.Log; +import com.governikus.ausweisapp2.omapi.OmapiCard; +import com.governikus.ausweisapp2.omapi.OmapiReader; +import net.vx4.lib.omapi.Hex; +import net.vx4.lib.omapi.OMAPITP; +import net.vx4.lib.omapi.TransportProvider; +import org.simalliance.openmobileapi.Channel; + +/** + * This implementation basically wraps away specifics of the underlying transport providers. + * + * @author kahlo + */ +public class OmapiReaderImpl implements OmapiReader +{ + private static final String LOG_TAG = "AusweisApp2.OMAPI"; + + private final OMAPITP tp; + + public OmapiReaderImpl(final OMAPITP tp) + { + this.tp = tp; + System.out.println("OmapiReaderImpl.OmapiReaderImpl"); + System.out.println("tp = [" + tp + "]"); + } + + + Channel getChannel() + { + return (Channel) ((TransportProvider) tp.getParent()).getParent(); + } + + + @Override + public String getName() + { + System.out.println("OmapiReaderImpl.getName"); + return getChannel().getSession().getReader().getName(); + } + + + @Override + public OmapiCard getCard() + { + System.out.println("OmapiReaderImpl.getCard"); + + return new OmapiCard() { + @Override + public byte[] transmit(final byte[] apdu) + { + Log.d(LOG_TAG, "transmit omapi"); + System.out.println("OmapiCardImpl.transmit"); + System.out.println("apdu = [" + Hex.toString(apdu) + "]"); + + if (tp != null) + { + byte[] rpdu = tp.process(apdu); + System.out.println("rpdu = [" + Hex.toString(rpdu) + "]"); + return rpdu; + } + else + { + System.out.println("No TransportProvider."); + } + + return null; + } + + }; + } + + +} diff --git a/src/card/omapi/impl/OmapiSecretFetcherImpl.java b/src/card/omapi/impl/OmapiSecretFetcherImpl.java new file mode 100644 index 0000000..8d77a79 --- /dev/null +++ b/src/card/omapi/impl/OmapiSecretFetcherImpl.java @@ -0,0 +1,245 @@ +/*! + * \copyright Copyright (c) 2019 Governikus GmbH & Co. KG, Germany + * + * Copyright 2017-2019 adesso AG + * + * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by the + * European Commission - subsequent versions of the EUPL (the "Licence"); You may + * not use this work except in compliance with the Licence. + * + * You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the Licence for the + * specific language governing permissions and limitations under the Licence. + */ + +package com.governikus.ausweisapp2.omapi.impl; + +import android.app.Activity; +import android.app.KeyguardManager; +import android.content.*; +import android.hardware.fingerprint.FingerprintManager; +import android.os.Build; +import android.os.Bundle; +import android.widget.Toast; +import net.vx4.lib.omapi.Hex; +import net.vx4.lib.omapi.OMAPITP; + +/** + * This class is a pretty "raw" stub to request a screen unlock to provide a fixed "secret" unlock key for the UICC + * applet. As the UICC is fixed in the mobile phone there is no real encryption need. But in general we want to enable + * a protocol similar to PACE (Password Authenticated Connection Establishment) for user authentication. As there are + * ideas to enable multiple identities on one or several (remember dual SIM phones) UICCs we stick to a fixed secret + * here until a reliable management of "CHANGE SECRET", recovery codes and hardware key storage using KeyGuard is + * defined and supported by the GUI. Also there are some compatibility quirks below we didn't want to throw away as + * long as test mobile phones still rely on them. + * + * @author kahlo + */ +public final class OmapiSecretFetcherImpl extends Activity +{ + private static final String LOG_TAG = "AusweisApp2.OMAPI"; + + + /** + * Used to build and start the enclosing activity requiring device unlock. + */ + static final class Builder + { + private final Context context; + + /** + * + * @param context + */ + Builder(final Context context) + { + this.context = context; + } + + + /** + * Set the callback handler, which is created inline below. + * + * @param tp + */ + final void set(final OMAPITP tp) + { + System.out.println("Builder.set"); + System.out.println("tp = [" + tp + "]"); + + if (tp != null && tp.getClass() == OMAPITP.class && tp.getClass().getProtectionDomain() == this.getClass().getProtectionDomain()) + { + tp.setCallbackHandler(new OMAPITP.CallbackHandler(){ + @Override + public byte[] getSecret(){ + System.out.println("Builder.getSecret"); + + final Class< ? >[] context = new SecurityManager() { + @Override + public Class< ? >[] getClassContext(){ + return super.getClassContext(); + } + } + .getClassContext(); + + System.out.println("context = " + context); // is null on Android + //System.out.println(context[2]); + //System.out.println(context[3]); + + SecurityException se = new SecurityException(); + if (!tp.getClass().getName().equals(se.getStackTrace()[2].getClassName())) + { + throw se; + } + final Context ctx = Builder.this.context; + + // TODO: check for call-source + + final SharedPreferences pref = ctx.getSharedPreferences("IVID", MODE_PRIVATE); + final String tmwRawData = pref.getString("rawData", null); + + // if(Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + // Toast.makeText(ctx, "Android API Version >= 23 required.", Toast.LENGTH_LONG); + // return null; + // }; + + final Object fpm = ctx.getSystemService(Context.FINGERPRINT_SERVICE); + if (fpm != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && ((FingerprintManager) fpm).isHardwareDetected()) + { + // fingerprint auth + // ((FingerprintManager) fpm).authenticate(); + final FingerprintManager fingerprintManager = (FingerprintManager) fpm; + if (!fingerprintManager.hasEnrolledFingerprints()) + { + // Toast.makeText(ctx, "No fingerprints enrolled.", Toast.LENGTH_LONG); + } + else + { + + } + } + else + { + // fallback to KeyGuard + + } + + final KeyguardManager kgm = (KeyguardManager) ctx.getSystemService(Context.KEYGUARD_SERVICE); + Class< ? extends Activity> clazz = OmapiSecretFetcherImpl.class; + + final Object[] res = new Object[1]; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || kgm.isDeviceSecure()) + { + ctx.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent){ + System.out.println("OmapiImpl.onReceive"); + System.out.println("context = [" + context + "], intent = [" + intent + "]"); + synchronized (res){ + res[0] = ((Object[]) intent.getExtras().get("result"))[0]; + res.notifyAll(); + } + } + }, new IntentFilter(clazz.getName())); + + ctx.startActivity(new Intent(ctx, clazz).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK).putExtra("result", res)); + + synchronized (res){ + try + { + res.wait(); + } + catch (InterruptedException e) + { + } + } + + if (res[0] == null) + { + System.out.println("RESULT: null - NEGATIVE"); + return null; + } + else + { + System.out.println("RESULT: " + res[0] + " - POSITIVE"); + + // TODO: use real secret + final byte[] ulk = Hex.x("E813C3318BBA3958D5ABD2E1D0B3163A"); + + return ulk; + } + } + + return null; + } + }); + } + } + + + } + + /** + * + * @param savedInstanceState + */ + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + System.out.println("OmapiActivity.onCreate"); + System.out.println("savedInstanceState = [" + savedInstanceState + "]"); + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) + { + setResult(RESULT_CANCELED, null); + // this.sendBroadcast(new Intent(this.getClass().getName()).putExtra("result", new Object[1])); + + this.sendBroadcast(new Intent(this.getClass().getName()).putExtra("result", new Object[] {""})); + + finish(); + } + else + { + final KeyguardManager kgm = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); + final Intent i = kgm.createConfirmDeviceCredentialIntent("meID", "meID requires valid user authorization."); + this.startActivityForResult(i, 1); + } + } + + + /** + * + * @param requestCode + * @param resultCode + * @param data + */ + @Override + protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) + { + System.out.println("OmapiActivity.onActivityResult"); + System.out.println("requestCode = [" + requestCode + "], resultCode = [" + resultCode + "], data = [" + data + "]"); + + switch (requestCode) + { + case 1: + setResult(resultCode, data); + if (resultCode == RESULT_OK) + { + this.sendBroadcast(new Intent(this.getClass().getName()).putExtra("result", new Object[] {""})); + } + finish(); + break; + } + + this.sendBroadcast(new Intent(this.getClass().getName()).putExtra("result", new Object[1])); + finish(); + } + + +} diff --git a/src/card/omapi/metadata.json b/src/card/omapi/metadata.json new file mode 100644 index 0000000..8e18fcf --- /dev/null +++ b/src/card/omapi/metadata.json @@ -0,0 +1,4 @@ +{ + "name" : "OmapiReaderManagerPlugIn", + "dependencies" : [] +} diff --git a/src/core/states/StateConnectCard.cpp b/src/core/states/StateConnectCard.cpp index e9098cd..e0c9540 100644 --- a/src/core/states/StateConnectCard.cpp +++ b/src/core/states/StateConnectCard.cpp @@ -23,8 +23,6 @@ void StateConnectCard::run() const auto readerManager = Env::getSingleton(); mConnections += connect(readerManager, &ReaderManager::fireCardInserted, this, &StateConnectCard::onCardInserted); mConnections += connect(readerManager, &ReaderManager::fireReaderRemoved, this, &StateConnectCard::onReaderRemoved); - mConnections += connect(getContext().data(), &WorkflowContext::fireAbortCardSelection, this, &StateConnectCard::onAbort); - mConnections += connect(getContext().data(), &WorkflowContext::fireReaderPlugInTypesChanged, this, &StateConnectCard::fireRetry); onCardInserted(); } @@ -82,3 +80,20 @@ void StateConnectCard::onAbort() } Q_EMIT fireRetry(); } + + +void StateConnectCard::onEntry(QEvent* pEvent) +{ + const WorkflowContext* const context = getContext().data(); + Q_ASSERT(context); + + mConnections += connect(context, &WorkflowContext::fireAbortCardSelection, this, &StateConnectCard::onAbort); + + /* + * Note: the plugin types to be used in this state must be already set in the workflow context before this state is entered. + * Changing the plugin types in the context, e.g. from {NFC} to {BLUETOOTH}, causes the state to be left with a fireRetry signal. + */ + mConnections += connect(context, &WorkflowContext::fireReaderPlugInTypesChanged, this, &StateConnectCard::fireRetry); + + AbstractGenericState::onEntry(pEvent); +} diff --git a/src/core/states/StateConnectCard.h b/src/core/states/StateConnectCard.h index 940275c..2f98788 100644 --- a/src/core/states/StateConnectCard.h +++ b/src/core/states/StateConnectCard.h @@ -28,6 +28,9 @@ class StateConnectCard void onReaderRemoved(const QString& pReaderName); void onAbort(); + public: + void onEntry(QEvent* pEvent) override; + Q_SIGNALS: void fireRetry(); void fireReaderRemoved(); diff --git a/src/core/states/StateSelectReader.cpp b/src/core/states/StateSelectReader.cpp index 0d054c3..5c7b0d1 100644 --- a/src/core/states/StateSelectReader.cpp +++ b/src/core/states/StateSelectReader.cpp @@ -24,7 +24,8 @@ StateSelectReader::StateSelectReader(const QSharedPointer& pCon bool StateSelectReader::requiresCard(ReaderManagerPlugInType pPlugInType) { return pPlugInType == ReaderManagerPlugInType::PCSC || - pPlugInType == ReaderManagerPlugInType::REMOTE; + pPlugInType == ReaderManagerPlugInType::REMOTE || + pPlugInType == ReaderManagerPlugInType::OMAPI; } diff --git a/src/global/LogCategories.cpp b/src/global/LogCategories.cpp index 373512b..48d0f63 100644 --- a/src/global/LogCategories.cpp +++ b/src/global/LogCategories.cpp @@ -12,6 +12,7 @@ Q_LOGGING_CATEGORY(bluetooth, "bluetooth") Q_LOGGING_CATEGORY(card_pcsc, "card_pcsc") Q_LOGGING_CATEGORY(card_nfc, "card_nfc") Q_LOGGING_CATEGORY(card_remote, "card_remote") +Q_LOGGING_CATEGORY(card_omapi, "card_omapi") Q_LOGGING_CATEGORY(remote_device, "remote_device") Q_LOGGING_CATEGORY(card_drivers, "card_drivers") Q_LOGGING_CATEGORY(statemachine, "statemachine") diff --git a/src/main.cpp b/src/main.cpp index 837c30a..99d739b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -52,6 +52,7 @@ Q_IMPORT_PLUGIN(NfcReaderManagerPlugIn) #ifndef ANDROID_BUILD_AAR Q_IMPORT_PLUGIN(IntentActivationHandler) +Q_IMPORT_PLUGIN(OmapiReaderManagerPlugIn) #endif #endif