diff options
author | Robert Griebl <robert.griebl@pelagicore.com> | 2018-10-08 18:22:12 +0200 |
---|---|---|
committer | Dominik Holland <dominik.holland@pelagicore.com> | 2018-10-12 09:44:35 +0000 |
commit | a54b61a7b17c0dc94441dba10243f364a57bcbb6 (patch) | |
tree | 6d6e649faa4542456e54266f9aea7cf5dc28be8d /src/intent-server-lib | |
parent | 8ced28c4c8d96be151fbb646d9bf08265f29249b (diff) | |
download | qtapplicationmanager-a54b61a7b17c0dc94441dba10243f364a57bcbb6.tar.gz |
Add support for Intents for both single- and multi-process mode
This commit adds support for Intents, aka. a loosely coupled IPC between
arbitrary applications in the AM system.
(please read https://wiki.qt.io/QtAppMan-Intents for same background
information).
The core implementation on both server and client side in this patch is not
dependent on the AM itself (apart from the common-lib for convenience sake,
but this dependency could easily be removed). There are 2 interfaces
that are implemented in the manager-lib and launcher-lib that connect the
Intent core to the actual AM and AM's qml runtime launcher.
Missing features:
- updating the list of intents on app installation and removal
- support for background services in the AM itself
- support for "file-handles" in the request and reply part
- documentation
- an example that is better focused on intents themselves
Change-Id: Ia7cab2bb569fb2cdb8e5ab7e8167e477cff3068c
Reviewed-by: Dominik Holland <dominik.holland@pelagicore.com>
Diffstat (limited to 'src/intent-server-lib')
-rw-r--r-- | src/intent-server-lib/intent-server-lib.pro | 25 | ||||
-rw-r--r-- | src/intent-server-lib/intent.cpp | 133 | ||||
-rw-r--r-- | src/intent-server-lib/intent.h | 86 | ||||
-rw-r--r-- | src/intent-server-lib/intentserver.cpp | 417 | ||||
-rw-r--r-- | src/intent-server-lib/intentserver.h | 138 | ||||
-rw-r--r-- | src/intent-server-lib/intentserverrequest.cpp | 121 | ||||
-rw-r--r-- | src/intent-server-lib/intentserverrequest.h | 104 | ||||
-rw-r--r-- | src/intent-server-lib/intentserversysteminterface.cpp | 68 | ||||
-rw-r--r-- | src/intent-server-lib/intentserversysteminterface.h | 89 |
9 files changed, 1181 insertions, 0 deletions
diff --git a/src/intent-server-lib/intent-server-lib.pro b/src/intent-server-lib/intent-server-lib.pro new file mode 100644 index 00000000..5dadab01 --- /dev/null +++ b/src/intent-server-lib/intent-server-lib.pro @@ -0,0 +1,25 @@ +TEMPLATE = lib +TARGET = QtAppManIntentServer +MODULE = appman_intent_server + +load(am-config) + +QT = core network qml +QT_FOR_PRIVATE *= \ + appman_common-private \ + +CONFIG *= static internal_module + +HEADERS += \ + intent.h \ + intentserver.h \ + intentserverrequest.h \ + intentserversysteminterface.h + +SOURCES += \ + intent.cpp \ + intentserver.cpp \ + intentserverrequest.cpp \ + intentserversysteminterface.cpp + +load(qt_module) diff --git a/src/intent-server-lib/intent.cpp b/src/intent-server-lib/intent.cpp new file mode 100644 index 00000000..fedb998e --- /dev/null +++ b/src/intent-server-lib/intent.cpp @@ -0,0 +1,133 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Pelagicore AG +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Pelagicore Application Manager. +** +** $QT_BEGIN_LICENSE:LGPL-QTAS$ +** Commercial License Usage +** Licensees holding valid commercial Qt Automotive Suite licenses may use +** this file in accordance with the commercial license agreement provided +** with the Software or, alternatively, in accordance with the terms +** contained in a written agreement between you and The Qt Company. For +** licensing terms and conditions see https://www.qt.io/terms-conditions. +** For further information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +** SPDX-License-Identifier: LGPL-3.0 +** +****************************************************************************/ + +#include "intent.h" + +#include <QRegularExpression> +#include <QVariant> + +QT_BEGIN_NAMESPACE_AM + +QString Intent::id() const +{ + return m_id; +} + +Intent::Visibility Intent::visibility() const +{ + return m_visibility; +} + +QStringList Intent::requiredCapabilities() const +{ + return m_requiredCapabilities; +} + +QVariantMap Intent::parameterMatch() const +{ + return m_parameterMatch; +} + +QString Intent::applicationId() const +{ + return m_applicationId; +} + +QString Intent::backgroundServiceId() const +{ + return m_backgroundServiceId; +} + +bool Intent::checkParameterMatch(const QVariantMap ¶meters) const +{ + QMapIterator<QString, QVariant> rit(m_parameterMatch); + while (rit.hasNext()) { + rit.next(); + const QString ¶mName = rit.key(); + auto pit = parameters.find(paramName); + if (pit == parameters.cend()) + return false; + + const QVariant requiredValue = rit.value(); + const QVariant actualValue = pit.value(); + + switch (requiredValue.type()) { + case QVariant::String: { + QRegularExpression regexp(requiredValue.toString()); + auto match = regexp.match(actualValue.toString()); + if (!match.hasMatch()) + return false; + break; + } + case QVariant::List: { + bool foundMatch = false; + const QVariantList rvlist = requiredValue.toList(); + for (const QVariant &rv2 : rvlist) { + if (actualValue.canConvert(rv2.type()) && actualValue == rv2) { + foundMatch = true; + break; + } + } + if (!foundMatch) + return false; + break; + } + default: { + if (requiredValue != actualValue) + return false; + break; + } + } + } + return true; +} + +Intent::Intent(const QString &id, const QString &applicationId, const QString &backgroundHandlerId, + const QStringList &capabilities, Intent::Visibility visibility, const QVariantMap ¶meterMatch) + : m_id(id) + , m_visibility(visibility) + , m_requiredCapabilities(capabilities) + , m_parameterMatch(parameterMatch) + , m_applicationId(applicationId) + , m_backgroundServiceId(backgroundHandlerId) +{ } + +QT_END_NAMESPACE_AM diff --git a/src/intent-server-lib/intent.h b/src/intent-server-lib/intent.h new file mode 100644 index 00000000..24250615 --- /dev/null +++ b/src/intent-server-lib/intent.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Pelagicore AG +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Pelagicore Application Manager. +** +** $QT_BEGIN_LICENSE:LGPL-QTAS$ +** Commercial License Usage +** Licensees holding valid commercial Qt Automotive Suite licenses may use +** this file in accordance with the commercial license agreement provided +** with the Software or, alternatively, in accordance with the terms +** contained in a written agreement between you and The Qt Company. For +** licensing terms and conditions see https://www.qt.io/terms-conditions. +** For further information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +** SPDX-License-Identifier: LGPL-3.0 +** +****************************************************************************/ + +#pragma once + +#include <QString> +#include <QStringList> +#include <QVariantMap> +#include <QtAppManCommon/global.h> + +QT_BEGIN_NAMESPACE_AM + +class Intent +{ +public: + enum Visibility { + Public, + Hidden, + Private + }; + + QString id() const; + Visibility visibility() const; + QStringList requiredCapabilities() const; + QVariantMap parameterMatch() const; + + QString applicationId() const; + QString backgroundServiceId() const; + + bool checkParameterMatch(const QVariantMap ¶meters) const; + +private: + Intent(const QString &id, const QString &applicationId, const QString &backgroundHandlerId, + const QStringList &capabilities, Intent::Visibility visibility, + const QVariantMap ¶meterMatch = QVariantMap()); + + QString m_id; + Visibility m_visibility = Private; + QStringList m_requiredCapabilities; + QVariantMap m_parameterMatch; + + QString m_applicationId; + QString m_backgroundServiceId; + + friend class IntentServer; +}; + +QT_END_NAMESPACE_AM diff --git a/src/intent-server-lib/intentserver.cpp b/src/intent-server-lib/intentserver.cpp new file mode 100644 index 00000000..285e0770 --- /dev/null +++ b/src/intent-server-lib/intentserver.cpp @@ -0,0 +1,417 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Pelagicore AG +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Pelagicore Application Manager. +** +** $QT_BEGIN_LICENSE:LGPL-QTAS$ +** Commercial License Usage +** Licensees holding valid commercial Qt Automotive Suite licenses may use +** this file in accordance with the commercial license agreement provided +** with the Software or, alternatively, in accordance with the terms +** contained in a written agreement between you and The Qt Company. For +** licensing terms and conditions see https://www.qt.io/terms-conditions. +** For further information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +** SPDX-License-Identifier: LGPL-3.0 +** +****************************************************************************/ + +#include "intentserver.h" +#include "intentserversysteminterface.h" +#include "intentserverrequest.h" +#include <QtAppManCommon/logging.h> + +#include <algorithm> + +#include <QRegularExpression> +#include <QUuid> +#include <QTimer> +#include <QDebug> + +#include <QQmlEngine> +#include <QQmlInfo> + + +QT_BEGIN_NAMESPACE_AM + +IntentServer *IntentServer::s_instance = nullptr; + +IntentServer *IntentServer::createInstance(IntentServerSystemInterface *systemInterface) +{ + if (Q_UNLIKELY(s_instance)) + qFatal("IntentServer::createInstance() was called a second time."); + if (Q_UNLIKELY(!systemInterface)) + qFatal("IntentServer::createInstance() was called without a systemInterface."); + + QScopedPointer<IntentServer> is(new IntentServer(systemInterface)); + systemInterface->initialize(is.data()); + + qmlRegisterSingletonType<IntentServer>("QtApplicationManager", 1, 0, "IntentManager", + &IntentServer::instanceForQml); + return s_instance = is.take(); +} + +IntentServer *IntentServer::instance() +{ + if (!s_instance) + qFatal("IntentServer::instance() was called before createInstance()."); + return s_instance; +} + +QObject *IntentServer::instanceForQml(QQmlEngine *, QJSEngine *) +{ + QQmlEngine::setObjectOwnership(instance(), QQmlEngine::CppOwnership); + return instance(); +} + + +IntentServer::IntentServer(IntentServerSystemInterface *systemInterface, QObject *parent) + : QObject(parent) + , m_systemInterface(systemInterface) +{ + m_systemInterface->setParent(this); +} + +IntentServer::~IntentServer() +{ + s_instance = nullptr; +} + +bool IntentServer::addApplication(const QString &applicationId) +{ + if (m_knownApplications.contains(applicationId)) + return false; + m_knownApplications << applicationId; + return true; +} + +bool IntentServer::addApplicationBackgroundHandler(const QString &applicationId, const QString &backgroundServiceId) +{ + if (!m_knownApplications.contains(applicationId)) + return false; + const QStringList services = m_knownBackgroundServices.value(applicationId); + if (services.contains(backgroundServiceId)) + return false; + m_knownBackgroundServices[applicationId].append(backgroundServiceId); + return true; +} + +const Intent *IntentServer::addIntent(const QString &id, const QString &applicationId, const QStringList &capabilities, Intent::Visibility visibility, const QVariantMap ¶meterMatch) +{ + return addIntent(id, applicationId, QString(), capabilities, visibility, parameterMatch); +} + +const Intent *IntentServer::addIntent(const QString &id, const QString &applicationId, const QString &backgroundHandlerId, const QStringList &capabilities, Intent::Visibility visibility, const QVariantMap ¶meterMatch) +{ + if (id.isEmpty() + || !m_knownApplications.contains(applicationId) + || find(id, applicationId) + || (!backgroundHandlerId.isEmpty() + && !m_knownBackgroundServices[applicationId].contains(backgroundHandlerId))) { + return nullptr; + } + + auto intent = new Intent(id, applicationId, backgroundHandlerId, capabilities, visibility, + parameterMatch); + m_intents << intent; + emit intentAdded(intent); + return intent; +} + +void IntentServer::removeIntent(const Intent *intent) +{ + if (intent) { + int pos = m_intents.indexOf(intent); + + if (pos >= 0) { + m_intents.removeAt(pos); + emit intentRemoved(intent); + } + } +} + +QVector<const Intent *> IntentServer::all() const +{ + return m_intents; +} + +QVector<const Intent *> IntentServer::filterByIntentId(const QString &intentId, const QVariantMap ¶meters) const +{ + QVector<const Intent *> result; + std::copy_if(m_intents.cbegin(), m_intents.cend(), std::back_inserter(result), + [intentId, parameters](const Intent *intent) -> bool { + return (intent->id() == intentId) && intent->checkParameterMatch(parameters); + + }); + return result; +} + +QVector<const Intent *> IntentServer::filterByApplicationId(const QString &applicationId, const QVariantMap ¶meters) const +{ + QVector<const Intent *> result; + std::copy_if(m_intents.cbegin(), m_intents.cend(), std::back_inserter(result), + [applicationId, parameters](const Intent *intent) -> bool { + return (intent->applicationId() == applicationId) && intent->checkParameterMatch(parameters); + + }); + return result; +} + +const Intent *IntentServer::find(const QString &intentId, const QString &applicationId, const QVariantMap ¶meters) const +{ + auto it = std::find_if(m_intents.cbegin(), m_intents.cend(), + [intentId, applicationId, parameters](const Intent *intent) -> bool { + return (intent->applicationId() == applicationId) && (intent->id() == intentId) + && intent->checkParameterMatch(parameters); + }); + return (it != m_intents.cend()) ? *it : nullptr; +} + + +void IntentServer::triggerRequestQueue() +{ + QTimer::singleShot(0, this, &IntentServer::processRequestQueue); +} + +void IntentServer::enqueueRequest(IntentServerRequest *irs) +{ + qCDebug(LogIntents) << "Enqueueing Intent request:" << irs << irs->id() << irs->state() << m_requestQueue.size(); + m_requestQueue.enqueue(irs); + triggerRequestQueue(); +} + +void IntentServer::processRequestQueue() +{ + if (m_requestQueue.isEmpty()) + return; + + IntentServerRequest *irs = m_requestQueue.takeFirst(); + + qCDebug(LogIntents) << "Processing intent request" << irs << irs->id() << "in state" << irs->state() << m_requestQueue.size(); + + if (irs->state() == IntentServerRequest::State::ReceivedRequest) { // step 1) disambiguate + if (!irs->m_actualIntent) { + // not disambiguated yet + + if (!isSignalConnected(QMetaMethod::fromSignal(&IntentServer::disambiguationRequest))) { + // If the System-UI does not react to the signal, then just use the first match. + irs->m_actualIntent = irs->m_intents.first(); + } else { + m_disambiguationQueue.enqueue(irs); + irs->setState(IntentServerRequest::State::WaitingForDisambiguation); + emit disambiguationRequest(irs->id(), irs->m_intents.first()->id(), irs->m_intents, irs->m_parameters); + } + } + if (irs->intent()) { + qCDebug(LogIntents) << "No disambiguation necessary/required for intent" << irs->intent()->id(); + irs->setState(IntentServerRequest::State::Disambiguated); + } + } + + if (irs->state() == IntentServerRequest::State::Disambiguated) { // step 2) start app + auto handlerIPC = m_systemInterface->findClientIpc(irs->intent()->applicationId()); + if (!handlerIPC) { + qCDebug(LogIntents) << "Intent target app" << irs->intent()->applicationId() << "is not running"; + m_startingAppQueue.enqueue(irs); + irs->setState(IntentServerRequest::State::WaitingForApplicationStart); + m_systemInterface->startApplication(irs->intent()->applicationId()); + } else { + qCDebug(LogIntents) << "Intent target app" << irs->intent()->applicationId() << "is already running"; + irs->setState(IntentServerRequest::State::StartedApplication); + } + } + + if (irs->state() == IntentServerRequest::State::StartedApplication) { // step 3) send request out + auto clientIPC = m_systemInterface->findClientIpc(irs->intent()->applicationId()); + if (!clientIPC) { + qCWarning(LogIntents) << "Could not find an IPC connection for application" + << irs->intent()->applicationId() << "to forward the intent request" + << irs->id(); + irs->requestFailed(qSL("No IPC channel to reach target application.")); + } else { + qCDebug(LogIntents) << "Sending intent request to application" << irs->intent()->applicationId(); + m_sentToAppQueue.enqueue(irs); + m_systemInterface->requestToApplication(clientIPC, irs); + irs->setState(IntentServerRequest::State::WaitingForReplyFromApplication); + } + } + + if (irs->state() == IntentServerRequest::State::ReceivedReplyFromApplication) { // step 5) send reply to requesting app + auto clientIPC = m_systemInterface->findClientIpc(irs->m_requestingAppId); + if (!clientIPC) { + qCWarning(LogIntents) << "Could not find an IPC connection for application" + << irs->m_requestingAppId << "to forward the Intent reply" + << irs->id(); + } else { + qCDebug(LogIntents) << "Forwarding intent reply" << irs->id() << "to requesting application" + << irs->m_requestingAppId; + m_systemInterface->replyFromSystem(clientIPC, irs); + } + QTimer::singleShot(0, this, [irs]() { delete irs; }); // aka deleteLater for non-QObject + irs = nullptr; + } + + triggerRequestQueue(); +} + +void IntentServer::acknowledgeDisambiguationRequest(const QUuid &requestId, const Intent *intent) +{ + IntentServerRequest *irs = nullptr; + for (int i = 0; i < m_disambiguationQueue.size(); ++i) { + if (m_disambiguationQueue.at(i)->id() == requestId) { + irs = m_disambiguationQueue.takeAt(i); + break; + } + } + + if (!irs) { + qmlWarning(this) << "Got a disambiguation acknowledge for intent" << requestId + << "but no disambiguation was expected for this intent"; + } else { + if (irs->m_intents.contains(intent)) { + irs->m_actualIntent = intent; + irs->setState(IntentServerRequest::State::Disambiguated); + } else { + qCWarning(LogIntents) << "IntentServer::acknowledgeDisambiguationRequest for intent" + << requestId << "tried to disambiguate to the intent" + << (intent ? intent->id() : qSL("<null>")) + << "which was not in the list of available disambiguations"; + + irs->requestFailed(qSL("Failed to disambiguate")); + } + enqueueRequest(irs); + } +} + +void IntentServer::applicationWasStarted(const QString &applicationId) +{ + // check if any intent request is waiting for this app to start + bool foundOne = false; + for (int i = 0; i < m_startingAppQueue.size(); ++i) { + auto irs = m_startingAppQueue.at(i); + if (irs->intent()->applicationId() == applicationId) { + qCDebug(LogIntents) << "Intent request" << irs->intent()->id() + << "can now be forwarded to application" << applicationId; + + irs->setState(IntentServerRequest::State::StartedApplication); + m_requestQueue << m_startingAppQueue.takeAt(i); + foundOne = true; + } + } + if (foundOne) + triggerRequestQueue(); +} + +void IntentServer::replyFromApplication(const QString &replyingApplicationId, const QString &requestId, bool error, const QVariantMap &result) +{ + IntentServerRequest *irs = nullptr; + for (int i = 0; i < m_sentToAppQueue.size(); ++i) { + if (m_sentToAppQueue.at(i)->id() == requestId) { + irs = m_sentToAppQueue.takeAt(i); + break; + } + } + + if (!irs) { + qCWarning(LogIntents) << "Got a reply for intent" << requestId << "from application" + << replyingApplicationId << "but no reply was expected for this intent"; + } else { + if (irs->intent()->applicationId() != replyingApplicationId) { + qCWarning(LogIntents) << "Got a reply for intent" << irs->id() << "from application" + << replyingApplicationId << "but expected a reply from" + << irs->intent()->applicationId() << "instead"; + irs->requestFailed(qSL("Request reply received from wrong application")); + } else { + QString errorMessage; + if (error) { + errorMessage = result.value(qSL("errorMessage")).toString(); + qCDebug(LogIntents) << "Got an error reply for intent" << irs->id() << "from application" + << replyingApplicationId << ":" << errorMessage; + irs->requestFailed(errorMessage); + } else { + qCDebug(LogIntents) << "Got a reply for intent" << irs->id() << "from application" + << replyingApplicationId << ":" << result; + irs->requestSucceeded(result); + } + } + enqueueRequest(irs); + } +} + +IntentServerRequest *IntentServer::requestToSystem(const QString &requestingApplicationId, const QString &intentId, const QString &applicationId, const QVariantMap ¶meters) +{ + qCDebug(LogIntents) << "Incoming intent request" << intentId << "from application" + << requestingApplicationId << "to application" << applicationId; + + QVector<const Intent *> intents; + if (applicationId.isEmpty()) + intents = filterByIntentId(intentId, parameters); + else if (const Intent *intent = find(intentId, applicationId, parameters)) + intents << intent; + + if (intents.isEmpty()) { + qCWarning(LogIntents) << "Unknown intent" << intentId << "was requested from application" + << requestingApplicationId; + return nullptr; + } + + // filter on visibility and capabilities + //TODO: move this part to a separate filter() function? + for (auto it = intents.begin(); it != intents.end(); ) { + const Intent *intent = *it; + bool keep = true; + + if ((intent->visibility() == Intent::Private) + && (intent->applicationId() != requestingApplicationId)) { + qCDebug(LogIntents) << "Not considering" << intent->id() << "/" << intent->applicationId() + << "due to private visibility"; + keep = false; + } + else if (!intent->requiredCapabilities().isEmpty() + && !m_systemInterface->checkApplicationCapabilities(requestingApplicationId, + intent->requiredCapabilities())) { + qCDebug(LogIntents) << "Not considering" << intent->id() << "/" << intent->applicationId() + << "due to missing capabilities"; + keep = false; + } + if (!keep) + intents.erase(it); + else + ++it; + } + + if (intents.isEmpty()) { + qCWarning(LogIntents) << "Inaccessible intent" << intentId << "was requested from application" + << requestingApplicationId; + return nullptr; + } + + auto irs = new IntentServerRequest(true, requestingApplicationId, intents, parameters); + enqueueRequest(irs); + return irs; +} + +QT_END_NAMESPACE_AM diff --git a/src/intent-server-lib/intentserver.h b/src/intent-server-lib/intentserver.h new file mode 100644 index 00000000..2333be08 --- /dev/null +++ b/src/intent-server-lib/intentserver.h @@ -0,0 +1,138 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Pelagicore AG +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Pelagicore Application Manager. +** +** $QT_BEGIN_LICENSE:LGPL-QTAS$ +** Commercial License Usage +** Licensees holding valid commercial Qt Automotive Suite licenses may use +** this file in accordance with the commercial license agreement provided +** with the Software or, alternatively, in accordance with the terms +** contained in a written agreement between you and The Qt Company. For +** licensing terms and conditions see https://www.qt.io/terms-conditions. +** For further information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +** SPDX-License-Identifier: LGPL-3.0 +** +****************************************************************************/ + +#pragma once + +#include <QObject> +#include <QVariantMap> +#include <QVector> +#include <QUuid> +#include <QQueue> +#include <QtAppManCommon/global.h> +#include <QtAppManIntentServer/intent.h> + +QT_FORWARD_DECLARE_CLASS(QIODevice) +QT_FORWARD_DECLARE_CLASS(QQmlEngine) +QT_FORWARD_DECLARE_CLASS(QJSEngine) + + +QT_BEGIN_NAMESPACE_AM + +class AbstractRuntime; +class IntentServerRequest; +class IntentServerSystemInterface; + +class IntentServer : public QObject +{ + Q_OBJECT + +public: + ~IntentServer() override; + static IntentServer *createInstance(IntentServerSystemInterface *systemInterface); + static IntentServer *instance(); + static QObject *instanceForQml(QQmlEngine *qmlEngine, QJSEngine *); + + Q_INVOKABLE QVector<const Intent *> all() const; + Q_INVOKABLE QVector<const Intent *> filterByIntentId(const QString &intentId, + const QVariantMap ¶meters = QVariantMap{}) const; + Q_INVOKABLE QVector<const Intent *> filterByApplicationId(const QString &applicationId, + const QVariantMap ¶meters = QVariantMap{}) const; + Q_INVOKABLE const Intent *find(const QString &intentId, const QString &applicationId, + const QVariantMap ¶meters = QVariantMap{}) const; + + + bool addApplication(const QString &applicationId); + bool addApplicationBackgroundHandler(const QString &applicationId, const QString &backgroundServiceId); + + const Intent *addIntent(const QString &id, const QString &applicationId, + const QStringList &capabilities, Intent::Visibility visibility, + const QVariantMap ¶meterMatch = QVariantMap()); + + const Intent *addIntent(const QString &id, const QString &applicationId, + const QString &backgroundHandlerId, + const QStringList &capabilities, Intent::Visibility visibility, + const QVariantMap ¶meterMatch = QVariantMap()); + + void removeIntent(const Intent *intent); + +signals: + void intentAdded(const Intent *intent); + void intentRemoved(const Intent *intent); + + void disambiguationRequest(const QUuid &requestId, const QString &intentId, + const QVector<const Intent *> &intents, const QVariantMap ¶meters); + + +public slots: + void acknowledgeDisambiguationRequest(const QUuid &requestId, const Intent *intent); + +private: + void applicationWasStarted(const QString &applicationId); + void replyFromApplication(const QString &replyingApplicationId, const QString &requestId, + bool error, const QVariantMap &result); + IntentServerRequest *requestToSystem(const QString &requestingApplicationId, const QString &intentId, + const QString &applicationId, const QVariantMap ¶meters); + + void triggerRequestQueue(); + void enqueueRequest(IntentServerRequest *irs); + void processRequestQueue(); + +private: + IntentServer(IntentServerSystemInterface *systemInterface, QObject *parent = nullptr); + Q_DISABLE_COPY(IntentServer) + static IntentServer *s_instance; + + QStringList m_knownApplications; + QMap<QString, QStringList> m_knownBackgroundServices; + + QQueue<IntentServerRequest *> m_requestQueue; + + QQueue<IntentServerRequest *> m_disambiguationQueue; + QQueue<IntentServerRequest *> m_startingAppQueue; + QQueue<IntentServerRequest *> m_sentToAppQueue; + + QVector<const Intent *> m_intents; + + IntentServerSystemInterface *m_systemInterface; + friend class IntentServerSystemInterface; +}; + +QT_END_NAMESPACE_AM diff --git a/src/intent-server-lib/intentserverrequest.cpp b/src/intent-server-lib/intentserverrequest.cpp new file mode 100644 index 00000000..df71a93f --- /dev/null +++ b/src/intent-server-lib/intentserverrequest.cpp @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Pelagicore AG +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Pelagicore Application Manager. +** +** $QT_BEGIN_LICENSE:LGPL-QTAS$ +** Commercial License Usage +** Licensees holding valid commercial Qt Automotive Suite licenses may use +** this file in accordance with the commercial license agreement provided +** with the Software or, alternatively, in accordance with the terms +** contained in a written agreement between you and The Qt Company. For +** licensing terms and conditions see https://www.qt.io/terms-conditions. +** For further information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +** SPDX-License-Identifier: LGPL-3.0 +** +****************************************************************************/ + +#include <QDebug> +#include "intentserverrequest.h" + +QT_BEGIN_NAMESPACE_AM + +IntentServerRequest::IntentServerRequest(bool external, const QString &requestingAppId, + const QVector<const Intent *> &intents, + const QVariantMap ¶meters) + : m_id(QUuid::createUuid()) + , m_state(State::ReceivedRequest) + , m_external(external) + , m_requestingAppId(requestingAppId) + , m_intents(intents) + , m_parameters(parameters) + , m_actualIntent(intents.size() == 1 ? intents.first() : nullptr) +{ } + +IntentServerRequest::State IntentServerRequest::state() const +{ + return m_state; +} + +QUuid IntentServerRequest::id() const +{ + return m_id; +} + +const Intent *IntentServerRequest::intent() const +{ + return m_actualIntent; +} + +QVariantMap IntentServerRequest::parameters() const +{ + return m_parameters; +} + +bool IntentServerRequest::isWaiting() const +{ + switch (state()) { + case State::WaitingForDisambiguation: + case State::WaitingForApplicationStart: + case State::WaitingForReplyFromApplication: + return true; + default: + return false; + } +} + +bool IntentServerRequest::hasSucceeded() const +{ + return m_succeeded; +} + +QVariantMap IntentServerRequest::result() const +{ + return m_result; +} + +void IntentServerRequest::requestFailed(const QString &errorMessage) +{ + m_succeeded = false; + m_result.clear(); + m_result[qSL("errorMessage")] = errorMessage; + m_state = State::ReceivedReplyFromApplication; +} + +void IntentServerRequest::requestSucceeded(const QVariantMap &result) +{ + m_succeeded = true; + m_result = result; + m_state = State::ReceivedReplyFromApplication; +} + +void IntentServerRequest::setState(IntentServerRequest::State newState) +{ + m_state = newState; +} + +QT_END_NAMESPACE_AM diff --git a/src/intent-server-lib/intentserverrequest.h b/src/intent-server-lib/intentserverrequest.h new file mode 100644 index 00000000..d523adde --- /dev/null +++ b/src/intent-server-lib/intentserverrequest.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Pelagicore AG +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Pelagicore Application Manager. +** +** $QT_BEGIN_LICENSE:LGPL-QTAS$ +** Commercial License Usage +** Licensees holding valid commercial Qt Automotive Suite licenses may use +** this file in accordance with the commercial license agreement provided +** with the Software or, alternatively, in accordance with the terms +** contained in a written agreement between you and The Qt Company. For +** licensing terms and conditions see https://www.qt.io/terms-conditions. +** For further information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +** SPDX-License-Identifier: LGPL-3.0 +** +****************************************************************************/ + +#pragma once + +#include <QObject> +#include <QString> +#include <QVariantMap> +#include <QUuid> +#include <QVector> +#include <QtAppManCommon/global.h> + +QT_BEGIN_NAMESPACE_AM + +class IntentServer; +class Intent; + +class IntentServerRequest +{ + Q_GADGET + +public: + IntentServerRequest(bool external, const QString &requestingAppId, const QVector<const Intent *> &intents, + const QVariantMap ¶meters); + + enum class State { + ReceivedRequest, + WaitingForDisambiguation, + Disambiguated, + WaitingForApplicationStart, + StartedApplication, + WaitingForReplyFromApplication, + ReceivedReplyFromApplication, + }; + + Q_ENUM(State) + + State state() const; + QUuid id() const; + const QtAM::Intent *intent() const; + QVariantMap parameters() const; + bool isWaiting() const; + bool hasSucceeded() const; + QVariantMap result() const; + + void requestFailed(const QString &errorMessage); + void requestSucceeded(const QVariantMap &result); + +private: + void setState(State newState); + +private: + QUuid m_id; + State m_state; + bool m_external; + bool m_succeeded = false; + QString m_requestingAppId; + QVector<const Intent *> m_intents; + QVariantMap m_parameters; + QVariantMap m_result; + const Intent *m_actualIntent; + + friend class IntentServer; +}; + +QT_END_NAMESPACE_AM diff --git a/src/intent-server-lib/intentserversysteminterface.cpp b/src/intent-server-lib/intentserversysteminterface.cpp new file mode 100644 index 00000000..bc8310ac --- /dev/null +++ b/src/intent-server-lib/intentserversysteminterface.cpp @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Pelagicore AG +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Pelagicore Application Manager. +** +** $QT_BEGIN_LICENSE:LGPL-QTAS$ +** Commercial License Usage +** Licensees holding valid commercial Qt Automotive Suite licenses may use +** this file in accordance with the commercial license agreement provided +** with the Software or, alternatively, in accordance with the terms +** contained in a written agreement between you and The Qt Company. For +** licensing terms and conditions see https://www.qt.io/terms-conditions. +** For further information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +** SPDX-License-Identifier: LGPL-3.0 +** +****************************************************************************/ + +#include "intentserversysteminterface.h" +#include "intentserver.h" + +QT_BEGIN_NAMESPACE_AM + +void IntentServerSystemInterface::initialize(IntentServer *intentServer) +{ + m_is = intentServer; + + connect(this, &IntentServerSystemInterface::replyFromApplication, + m_is, &IntentServer::replyFromApplication); + connect(this, &IntentServerSystemInterface::applicationWasStarted, + m_is, &IntentServer::applicationWasStarted); +} + +IntentServer *IntentServerSystemInterface::intentServer() const +{ + return m_is; +} + +IntentServerRequest *IntentServerSystemInterface::requestToSystem(const QString &requestingApplicationId, const QString &intentId, const QString &applicationId, const QVariantMap ¶meters) +{ + // not possible to do via signal, due to return value + return m_is->requestToSystem(requestingApplicationId, intentId, applicationId, parameters); +} + +QT_END_NAMESPACE_AM diff --git a/src/intent-server-lib/intentserversysteminterface.h b/src/intent-server-lib/intentserversysteminterface.h new file mode 100644 index 00000000..f75cb7e2 --- /dev/null +++ b/src/intent-server-lib/intentserversysteminterface.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Pelagicore AG +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Pelagicore Application Manager. +** +** $QT_BEGIN_LICENSE:LGPL-QTAS$ +** Commercial License Usage +** Licensees holding valid commercial Qt Automotive Suite licenses may use +** this file in accordance with the commercial license agreement provided +** with the Software or, alternatively, in accordance with the terms +** contained in a written agreement between you and The Qt Company. For +** licensing terms and conditions see https://www.qt.io/terms-conditions. +** For further information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +** SPDX-License-Identifier: LGPL-3.0 +** +****************************************************************************/ + +#pragma once + +#include <QObject> +#include <QVariantMap> +#include <QString> +#include <QStringList> +#include <QtAppManCommon/global.h> + +QT_BEGIN_NAMESPACE_AM + +class IntentServer; +class IntentServerRequest; + +class IntentServerSystemInterface : public QObject +{ + Q_OBJECT + +public: + virtual ~IntentServerSystemInterface() = default; + + virtual void initialize(IntentServer *intentServer); + IntentServer *intentServer() const; + + class IpcConnection; + virtual IpcConnection *findClientIpc(const QString &appId) = 0; + + virtual void startApplication(const QString &appId) = 0; + + + virtual bool checkApplicationCapabilities(const QString &applicationId, + const QStringList &requiredCapabilities) = 0; + + IntentServerRequest *requestToSystem(const QString &requestingApplicationId, const QString &intentId, + const QString &applicationId, const QVariantMap ¶meters); + virtual void replyFromSystem(IpcConnection *clientIPC, IntentServerRequest *irs) = 0; + + virtual void requestToApplication(IpcConnection *clientIPC, IntentServerRequest *irs) = 0; + +signals: + void applicationWasStarted(const QString &appId); + void replyFromApplication(const QString &replyingApplicationId, const QString &requestId, + bool error, const QVariantMap &result); + +private: + IntentServer *m_is = nullptr; +}; + +QT_END_NAMESPACE_AM |