summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVille Voutilainen <ville.voutilainen@qt.io>2020-05-07 09:07:39 +0300
committerVille Voutilainen <ville.voutilainen@qt.io>2020-05-25 11:18:09 +0000
commit52188918c0d4c90c38be99eb2c4586dd3a11b67f (patch)
tree3bdab8da448f5fa9c1d770d39b7c614844100285
parent14666c801aeaa4a7b12ed43e33f50e71f4345269 (diff)
downloadqt-creator-52188918c0d4c90c38be99eb2c4586dd3a11b67f.tar.gz
Android: add service editor to manifest editor
Task-number: QTCREATORBUG-23937 Change-Id: Iec0435721504df744ec985bd3e5cefcc0700e852 Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io>
-rw-r--r--src/plugins/android/CMakeLists.txt1
-rw-r--r--src/plugins/android/android.pro3
-rw-r--r--src/plugins/android/android.qbs3
-rw-r--r--src/plugins/android/androidmanifesteditorwidget.cpp238
-rw-r--r--src/plugins/android/androidmanifesteditorwidget.h10
-rw-r--r--src/plugins/android/androidservicewidget.cpp410
-rw-r--r--src/plugins/android/androidservicewidget.h89
-rw-r--r--src/plugins/android/androidservicewidget_p.h59
8 files changed, 805 insertions, 8 deletions
diff --git a/src/plugins/android/CMakeLists.txt b/src/plugins/android/CMakeLists.txt
index e672527ea6..c19da7c15a 100644
--- a/src/plugins/android/CMakeLists.txt
+++ b/src/plugins/android/CMakeLists.txt
@@ -40,6 +40,7 @@ add_qtc_plugin(Android
androidsdkmanagerwidget.cpp androidsdkmanagerwidget.h androidsdkmanagerwidget.ui
androidsdkmodel.cpp androidsdkmodel.h
androidsdkpackage.cpp androidsdkpackage.h
+ androidservicewidget.cpp androidservicewidget.h androidservicewidget_p.h
androidsettingswidget.cpp androidsettingswidget.h androidsettingswidget.ui
androidsignaloperation.cpp androidsignaloperation.h
androidtoolchain.cpp androidtoolchain.h
diff --git a/src/plugins/android/android.pro b/src/plugins/android/android.pro
index dd62a79bcd..529e4d090c 100644
--- a/src/plugins/android/android.pro
+++ b/src/plugins/android/android.pro
@@ -13,6 +13,8 @@ HEADERS += \
androidmanifesteditoriconwidget.h \
androidrunconfiguration.h \
androidruncontrol.h \
+ androidservicewidget.h \
+ androidservicewidget_p.h \
androidsettingswidget.h \
androidtoolchain.h \
androiderrormessage.h \
@@ -60,6 +62,7 @@ SOURCES += \
androidmanifesteditoriconwidget.cpp \
androidrunconfiguration.cpp \
androidruncontrol.cpp \
+ androidservicewidget.cpp \
androidsettingswidget.cpp \
androidtoolchain.cpp \
androiderrormessage.cpp \
diff --git a/src/plugins/android/android.qbs b/src/plugins/android/android.qbs
index 0710d851fe..0edfce86a5 100644
--- a/src/plugins/android/android.qbs
+++ b/src/plugins/android/android.qbs
@@ -93,6 +93,9 @@ Project {
"androidsdkmodel.h",
"androidsdkpackage.cpp",
"androidsdkpackage.h",
+ "androidservicewidget.cpp",
+ "androidservicewidget.h",
+ "androidservicewidget_p.h",
"androidsettingswidget.cpp",
"androidsettingswidget.h",
"androidsettingswidget.ui",
diff --git a/src/plugins/android/androidmanifesteditorwidget.cpp b/src/plugins/android/androidmanifesteditorwidget.cpp
index 7e1dbf540e..3a7296b508 100644
--- a/src/plugins/android/androidmanifesteditorwidget.cpp
+++ b/src/plugins/android/androidmanifesteditorwidget.cpp
@@ -30,6 +30,7 @@
#include "androidconstants.h"
#include "androidmanifestdocument.h"
#include "androidmanager.h"
+#include "androidservicewidget.h"
#include <coreplugin/icore.h>
#include <coreplugin/infobar.h>
@@ -68,6 +69,7 @@
#include <QLabel>
#include <QLineEdit>
#include <QListView>
+#include <QMessageBox>
#include <QPushButton>
#include <QScrollArea>
#include <QSpinBox>
@@ -253,6 +255,9 @@ void AndroidManifestEditorWidget::initializePage()
formLayout->addRow(QString(), m_iconButtons);
+ m_services = new AndroidServiceWidget(this);
+ formLayout->addRow(tr("Android services:"), m_services);
+
applicationGroupBox->setLayout(formLayout);
connect(m_appNameLineEdit, &QLineEdit::textEdited,
@@ -264,6 +269,12 @@ void AndroidManifestEditorWidget::initializePage()
connect(m_styleExtractMethod,
QOverload<int>::of(&QComboBox::currentIndexChanged),
this, setDirtyFunc);
+ connect(m_services, &AndroidServiceWidget::servicesModified,
+ this, setDirtyFunc);
+ connect(m_services, &AndroidServiceWidget::servicesModified,
+ this, &AndroidManifestEditorWidget::clearInvalidServiceInfo);
+ connect(m_services, &AndroidServiceWidget::servicesInvalid,
+ this, &AndroidManifestEditorWidget::setInvalidServiceInfo);
}
@@ -539,6 +550,14 @@ AndroidManifestEditorWidget::EditorPage AndroidManifestEditorWidget::activePage(
return AndroidManifestEditorWidget::EditorPage(currentIndex());
}
+bool servicesValid(const QList<AndroidServiceData> &services)
+{
+ for (auto &&x : services)
+ if (!x.isValid())
+ return false;
+ return true;
+}
+
bool AndroidManifestEditorWidget::setActivePage(EditorPage page)
{
EditorPage prevPage = activePage();
@@ -547,6 +566,11 @@ bool AndroidManifestEditorWidget::setActivePage(EditorPage page)
return true;
if (page == Source) {
+ if (!servicesValid(m_services->services())) {
+ QMessageBox::critical(nullptr, tr("Service Definition Invalid"),
+ tr("Cannot switch to source when there are invalid services."));
+ return false;
+ }
syncToEditor();
} else {
if (!syncToWidgets())
@@ -567,8 +591,14 @@ bool AndroidManifestEditorWidget::setActivePage(EditorPage page)
void AndroidManifestEditorWidget::preSave()
{
- if (activePage() != Source)
+ if (activePage() != Source) {
+ if (!servicesValid(m_services->services())) {
+ QMessageBox::critical(nullptr, tr("Service Definition Invalid"),
+ tr("Cannot save when there are invalid services."));
+ return;
+ }
syncToEditor();
+ }
// no need to emit changed() since this is called as part of saving
updateInfoBar();
@@ -707,7 +737,26 @@ void AndroidManifestEditorWidget::hideInfoBar()
{
Core::InfoBar *infoBar = m_textEditorWidget->textDocument()->infoBar();
infoBar->removeInfo(infoBarId);
- m_timerParseCheck.stop();
+ m_timerParseCheck.stop();
+}
+
+static const char kServicesInvalid[] = "AndroidServiceDefinitionInvalid";
+
+void AndroidManifestEditorWidget::setInvalidServiceInfo()
+{
+ Core::Id id(kServicesInvalid);
+ if (m_textEditorWidget->textDocument()->infoBar()->containsInfo(id))
+ return;
+ Core::InfoBarEntry info(id,
+ tr("Services invalid. "
+ "Manifest cannot be saved. Correct the service definitions before saving."));
+ m_textEditorWidget->textDocument()->infoBar()->addInfo(info);
+
+}
+
+void AndroidManifestEditorWidget::clearInvalidServiceInfo()
+{
+ m_textEditorWidget->textDocument()->infoBar()->removeInfo(Core::Id(kServicesInvalid));
}
void setApiLevel(QComboBox *box, const QDomElement &element, const QString &attribute)
@@ -810,6 +859,33 @@ void AndroidManifestEditorWidget::syncToWidgets(const QDomDocument &doc)
m_permissionsModel->setPermissions(permissions);
updateAddRemovePermissionButtons();
+ QList<AndroidServiceData> services;
+ QDomElement serviceElem = applicationElement.firstChildElement(QLatin1String("service"));
+ while (!serviceElem.isNull()) {
+ AndroidServiceData service;
+ service.setClassName(serviceElem.attribute(QLatin1String("android:name")));
+ QString process = serviceElem.attribute(QLatin1String("android:process"));
+ service.setRunInExternalProcess(!process.isEmpty());
+ service.setExternalProcessName(process);
+ QDomElement serviceMetadataElem = serviceElem.firstChildElement(QLatin1String("meta-data"));
+ while (!serviceMetadataElem.isNull()) {
+ QString metadataName = serviceMetadataElem.attribute(QLatin1String("android:name"));
+ if (metadataName == QLatin1String("android.app.lib_name")) {
+ QString metadataValue = serviceMetadataElem.attribute(QLatin1String("android:value"));
+ service.setRunInExternalLibrary(metadataValue != QLatin1String("-- %%INSERT_APP_LIB_NAME%% --"));
+ service.setExternalLibraryName(metadataValue);
+ }
+ else if (metadataName == QLatin1String("android.app.arguments")) {
+ QString metadataValue = serviceMetadataElem.attribute(QLatin1String("android:value"));
+ service.setServiceArguments(metadataValue);
+ }
+ serviceMetadataElem = serviceMetadataElem.nextSiblingElement(QLatin1String("meta-data"));
+ }
+ services << service;
+ serviceElem = serviceElem.nextSiblingElement(QLatin1String("service"));
+ }
+ m_services->setServices(services);
+
m_iconButtons->loadIcons();
m_stayClean = false;
@@ -988,13 +1064,19 @@ void AndroidManifestEditorWidget::parseApplication(QXmlStreamReader &reader, QXm
while (!reader.atEnd()) {
if (reader.isEndElement()) {
+ parseNewServices(writer);
writer.writeCurrentToken(reader);
+ m_services->servicesSaved();
return;
} else if (reader.isStartElement()) {
if (reader.name() == QLatin1String("activity"))
parseActivity(reader, writer);
+ else if (reader.name() == QLatin1String("service"))
+ parseService(reader, writer);
else
parseUnknownElement(reader, writer);
+ } else if (reader.isWhitespace()) {
+ /* no copying of whitespace */
} else {
writer.writeCurrentToken(reader);
}
@@ -1003,6 +1085,144 @@ void AndroidManifestEditorWidget::parseApplication(QXmlStreamReader &reader, QXm
}
}
+static int findService(const QString &name, const QList<AndroidServiceData> &data)
+{
+ for (int i = 0; i < data.size(); ++i) {
+ if (data[i].className() == name)
+ return i;
+ }
+ return -1;
+}
+
+static void writeServiceMetadataElement(const char *name,
+ const char *attributeName,
+ const char *value,
+ QXmlStreamWriter &writer)
+{
+ writer.writeStartElement(QLatin1String("meta-data"));
+ writer.writeAttribute(QLatin1String("android:name"), QLatin1String(name));
+ writer.writeAttribute(QLatin1String(attributeName), QLatin1String(value));
+ writer.writeEndElement();
+
+}
+
+static void writeServiceMetadataElement(const char *name,
+ const char *attributeName,
+ const QString &value,
+ QXmlStreamWriter &writer)
+{
+ writer.writeStartElement(QLatin1String("meta-data"));
+ writer.writeAttribute(QLatin1String("android:name"), QLatin1String(name));
+ writer.writeAttribute(QLatin1String(attributeName), value);
+ writer.writeEndElement();
+
+}
+
+static void addServiceArgumentsAndLibName(const AndroidServiceData &service, QXmlStreamWriter &writer)
+{
+ if (!service.isRunInExternalLibrary() && !service.serviceArguments().isEmpty())
+ writeServiceMetadataElement("android.app.arguments", "android:value", service.serviceArguments(), writer);
+ if (service.isRunInExternalLibrary() && !service.externalLibraryName().isEmpty())
+ writeServiceMetadataElement("android.app.lib_name", "android:value", service.externalLibraryName(), writer);
+ else
+ writeServiceMetadataElement("android.app.lib_name", "android:value", "-- %%INSERT_APP_LIB_NAME%% --", writer);
+}
+
+static void addServiceMetadata(QXmlStreamWriter &writer)
+{
+ writeServiceMetadataElement("android.app.qt_sources_resource_id", "android:resource", "@array/qt_sources", writer);
+ writeServiceMetadataElement("android.app.repository", "android:value", "default", writer);
+ writeServiceMetadataElement("android.app.qt_libs_resource_id", "android:resource", "@array/qt_libs", writer);
+ writeServiceMetadataElement("android.app.bundled_libs_resource_id", "android:resource", "@array/bundled_libs", writer);
+ writeServiceMetadataElement("android.app.bundle_local_qt_libs", "android:value", "-- %%BUNDLE_LOCAL_QT_LIBS%% --", writer);
+ writeServiceMetadataElement("android.app.use_local_qt_libs", "android:value", "-- %%USE_LOCAL_QT_LIBS%% --", writer);
+ writeServiceMetadataElement("android.app.libs_prefix", "android:value", "/data/local/tmp/qt/", writer);
+ writeServiceMetadataElement("android.app.load_local_libs_resource_id", "android:resource", "@array/load_local_libs", writer);
+ writeServiceMetadataElement("android.app.load_local_jars", "android:value", "-- %%INSERT_LOCAL_JARS%% --", writer);
+ writeServiceMetadataElement("android.app.static_init_classes", "android:value", "-- %%INSERT_INIT_CLASSES%% --", writer);
+}
+
+void AndroidManifestEditorWidget::parseService(QXmlStreamReader &reader, QXmlStreamWriter &writer)
+{
+ Q_ASSERT(reader.isStartElement());
+ const auto &services = m_services->services();
+ QString serviceName = reader.attributes().value(QLatin1String("android:name")).toString();
+ int serviceIndex = findService(serviceName, services);
+ const AndroidServiceData* serviceFound = (serviceIndex >= 0) ? &services[serviceIndex] : nullptr;
+ if (serviceFound && serviceFound->isValid()) {
+ writer.writeStartElement(reader.name().toString());
+ writer.writeAttribute(QLatin1String("android:name"), serviceFound->className());
+ if (serviceFound->isRunInExternalProcess())
+ writer.writeAttribute(QLatin1String("android:process"), serviceFound->externalProcessName());
+ }
+
+ reader.readNext();
+
+ bool bundleTagFound = false;
+
+ while (!reader.atEnd()) {
+ if (reader.isEndElement()) {
+ if (serviceFound && serviceFound->isValid()) {
+ addServiceArgumentsAndLibName(*serviceFound, writer);
+ if (serviceFound->isRunInExternalProcess() && !bundleTagFound)
+ addServiceMetadata(writer);
+ writer.writeCurrentToken(reader);
+ }
+ return;
+ } else if (reader.isStartElement()) {
+ if (serviceFound && !serviceFound->isValid())
+ parseUnknownElement(reader, writer, true);
+ else if (reader.name() == QLatin1String("meta-data")) {
+ QString metaTagName = reader.attributes().value(QLatin1String("android:name")).toString();
+ if (serviceFound) {
+ if (metaTagName == QLatin1String("android.app.bundle_local_qt_libs"))
+ bundleTagFound = true;
+ if (metaTagName == QLatin1String("android.app.arguments"))
+ parseUnknownElement(reader, writer, true);
+ else if (metaTagName == QLatin1String("android.app.lib_name"))
+ parseUnknownElement(reader, writer, true);
+ else if (serviceFound->isRunInExternalProcess()
+ || metaTagName == QLatin1String("android.app.background_running"))
+ parseUnknownElement(reader, writer);
+ else
+ parseUnknownElement(reader, writer, true);
+ } else
+ parseUnknownElement(reader, writer, true);
+ } else
+ parseUnknownElement(reader, writer, true);
+ } else if (reader.isWhitespace()) {
+ /* no copying of whitespace */
+ } else {
+ if (serviceFound)
+ writer.writeCurrentToken(reader);
+ }
+ reader.readNext();
+ }
+}
+
+void AndroidManifestEditorWidget::parseNewServices(QXmlStreamWriter &writer)
+{
+ const auto &services = m_services->services();
+ for (const auto &x : services) {
+ if (x.isNewService() && x.isValid()) {
+ writer.writeStartElement(QLatin1String("service"));
+ writer.writeAttribute(QLatin1String("android:name"), x.className());
+ if (x.isRunInExternalProcess()) {
+ writer.writeAttribute(QLatin1String("android:process"),
+ x.externalProcessName());
+ }
+ addServiceArgumentsAndLibName(x, writer);
+ if (x.isRunInExternalProcess())
+ addServiceMetadata(writer);
+ writer.writeStartElement(QLatin1String("meta-data"));
+ writer.writeAttribute(QLatin1String("android:name"), QLatin1String("android.app.background_running"));
+ writer.writeAttribute(QLatin1String("android:value"), QLatin1String("true"));
+ writer.writeEndElement();
+ writer.writeEndElement();
+ }
+ }
+}
+
void AndroidManifestEditorWidget::parseActivity(QXmlStreamReader &reader, QXmlStreamWriter &writer)
{
Q_ASSERT(reader.isStartElement());
@@ -1180,20 +1400,24 @@ QString AndroidManifestEditorWidget::parseComment(QXmlStreamReader &reader, QXml
return commentText;
}
-void AndroidManifestEditorWidget::parseUnknownElement(QXmlStreamReader &reader, QXmlStreamWriter &writer)
+void AndroidManifestEditorWidget::parseUnknownElement(QXmlStreamReader &reader, QXmlStreamWriter &writer,
+ bool ignore)
{
Q_ASSERT(reader.isStartElement());
- writer.writeCurrentToken(reader);
+ if (!ignore)
+ writer.writeCurrentToken(reader);
reader.readNext();
while (!reader.atEnd()) {
if (reader.isEndElement()) {
- writer.writeCurrentToken(reader);
+ if (!ignore)
+ writer.writeCurrentToken(reader);
return;
} else if (reader.isStartElement()) {
- parseUnknownElement(reader, writer);
+ parseUnknownElement(reader, writer, ignore);
} else {
- writer.writeCurrentToken(reader);
+ if (!ignore)
+ writer.writeCurrentToken(reader);
}
reader.readNext();
}
diff --git a/src/plugins/android/androidmanifesteditorwidget.h b/src/plugins/android/androidmanifesteditorwidget.h
index 2384264257..6d49cf7f42 100644
--- a/src/plugins/android/androidmanifesteditorwidget.h
+++ b/src/plugins/android/androidmanifesteditorwidget.h
@@ -54,6 +54,7 @@ namespace Internal {
class AndroidManifestEditor;
class AndroidManifestEditorIconContainerWidget;
class AndroidManifestEditorWidget;
+class AndroidServiceWidget;
class PermissionsModel: public QAbstractListModel
{
@@ -136,10 +137,15 @@ private:
void updateInfoBar(const QString &errorMessage, int line, int column);
void hideInfoBar();
+ void setInvalidServiceInfo();
+ void clearInvalidServiceInfo();
+
void updateTargetComboBox();
void parseManifest(QXmlStreamReader &reader, QXmlStreamWriter &writer);
void parseApplication(QXmlStreamReader &reader, QXmlStreamWriter &writer);
+ void parseService(QXmlStreamReader &reader, QXmlStreamWriter &writer);
+ void parseNewServices(QXmlStreamWriter &writer);
void parseActivity(QXmlStreamReader &reader, QXmlStreamWriter &writer);
bool parseMetaData(QXmlStreamReader &reader, QXmlStreamWriter &writer);
void parseUsesSdk(QXmlStreamReader &reader, QXmlStreamWriter &writer);
@@ -147,7 +153,7 @@ private:
QXmlStreamWriter &writer,
const QSet<QString> &permissions);
QString parseComment(QXmlStreamReader &reader, QXmlStreamWriter &writer);
- void parseUnknownElement(QXmlStreamReader &reader, QXmlStreamWriter &writer);
+ void parseUnknownElement(QXmlStreamReader &reader, QXmlStreamWriter &writer, bool ignore=false);
bool m_dirty; // indicates that we need to call syncToEditor()
bool m_stayClean;
@@ -178,6 +184,8 @@ private:
QPushButton *m_removePermissionButton;
QComboBox *m_permissionsComboBox;
+ // Services
+ AndroidServiceWidget *m_services;
QTimer m_timerParseCheck;
TextEditor::TextEditorWidget *m_textEditorWidget;
AndroidManifestEditor *m_editor;
diff --git a/src/plugins/android/androidservicewidget.cpp b/src/plugins/android/androidservicewidget.cpp
new file mode 100644
index 0000000000..465d2e2600
--- /dev/null
+++ b/src/plugins/android/androidservicewidget.cpp
@@ -0,0 +1,410 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt 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 General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** 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-3.0.html.
+**
+****************************************************************************/
+
+#include "androidservicewidget.h"
+#include "androidservicewidget_p.h"
+
+#include <utils/utilsicons.h>
+
+#include <QAbstractTableModel>
+#include <QGridLayout>
+#include <QHBoxLayout>
+#include <QHeaderView>
+#include <QPushButton>
+#include <QTableView>
+
+namespace Android {
+namespace Internal {
+
+bool AndroidServiceData::isValid() const
+{
+ return !m_className.isEmpty()
+ && (!m_isRunInExternalProcess || !m_externalProcessName.isEmpty())
+ && (!m_isRunInExternalLibrary || !m_externalLibName.isEmpty())
+ && (m_isRunInExternalLibrary || !m_serviceArguments.isEmpty());
+}
+
+void AndroidServiceData::setClassName(const QString &className)
+{
+ m_className = className;
+}
+
+QString AndroidServiceData::className() const
+{
+ return m_className;
+}
+
+void AndroidServiceData::setRunInExternalProcess(bool isRunInExternalProcess)
+{
+ m_isRunInExternalProcess = isRunInExternalProcess;
+ if (!m_isRunInExternalProcess) {
+ m_isRunInExternalLibrary = false;
+ m_externalProcessName.clear();
+ m_externalLibName.clear();
+ }
+}
+
+bool AndroidServiceData::isRunInExternalProcess() const
+{
+ return m_isRunInExternalProcess;
+}
+
+void AndroidServiceData::setExternalProcessName(const QString &externalProcessName)
+{
+ if (m_isRunInExternalProcess)
+ m_externalProcessName = externalProcessName;
+}
+
+QString AndroidServiceData::externalProcessName() const
+{
+ return m_externalProcessName;
+}
+
+void AndroidServiceData::setRunInExternalLibrary(bool isRunInExternalLibrary)
+{
+ if (m_isRunInExternalProcess)
+ m_isRunInExternalLibrary = isRunInExternalLibrary;
+ if (!m_isRunInExternalLibrary)
+ m_externalLibName.clear();
+ else
+ m_serviceArguments.clear();
+}
+
+bool AndroidServiceData::isRunInExternalLibrary() const
+{
+ return m_isRunInExternalLibrary;
+}
+
+void AndroidServiceData::setExternalLibraryName(const QString &externalLibraryName)
+{
+ if (m_isRunInExternalLibrary)
+ m_externalLibName = externalLibraryName;
+}
+
+QString AndroidServiceData::externalLibraryName() const
+{
+ return m_externalLibName;
+}
+
+void AndroidServiceData::setServiceArguments(const QString &serviceArguments)
+{
+ if (!m_isRunInExternalLibrary)
+ m_serviceArguments = serviceArguments;
+}
+
+QString AndroidServiceData::serviceArguments() const
+{
+ return m_serviceArguments;
+}
+
+void AndroidServiceData::setNewService(bool isNewService)
+{
+ m_isNewService = isNewService;
+}
+
+bool AndroidServiceData::isNewService() const
+{
+ return m_isNewService;
+}
+
+void AndroidServiceWidget::AndroidServiceModel::setServices(const QList<AndroidServiceData> &androidServices)
+{
+ beginResetModel();
+ m_services = androidServices;
+ endResetModel();
+}
+
+const QList<AndroidServiceData> &AndroidServiceWidget::AndroidServiceModel::services()
+{
+ return m_services;
+}
+
+void AndroidServiceWidget::AndroidServiceModel::addService()
+{
+ int rowIndex = m_services.size();
+ beginInsertRows(QModelIndex(), rowIndex, rowIndex);
+ AndroidServiceData service;
+ service.setNewService(true);
+ m_services.push_back(service);
+ endInsertRows();
+ invalidDataChanged();
+}
+
+void AndroidServiceWidget::AndroidServiceModel::removeService(int row)
+{
+ beginRemoveRows(QModelIndex(), row, row);
+ m_services.removeAt(row);
+ endRemoveRows();
+}
+
+void AndroidServiceWidget::AndroidServiceModel::servicesSaved()
+{
+ for (auto && x : m_services)
+ x.setNewService(false);
+}
+
+int AndroidServiceWidget::AndroidServiceModel::rowCount(const QModelIndex &/*parent*/) const
+{
+ return m_services.count();
+}
+
+int AndroidServiceWidget::AndroidServiceModel::columnCount(const QModelIndex &/*parent*/) const
+{
+ return 6;
+}
+
+Qt::ItemFlags AndroidServiceWidget::AndroidServiceModel::flags(const QModelIndex &index) const
+{
+ if (index.column() == 0)
+ return Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable;
+ else if (index.column() == 1)
+ return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsSelectable;
+ else if (index.column() == 2 && index.row() <= m_services.count()) {
+ if (m_services[index.row()].isRunInExternalProcess())
+ return Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable;
+ return Qt::ItemIsSelectable;
+ } else if (index.column() == 3 && index.row() <= m_services.count()) {
+ if (m_services[index.row()].isRunInExternalProcess())
+ return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsSelectable;
+ return Qt::ItemIsUserCheckable | Qt::ItemIsSelectable;
+ } else if (index.column() == 4 && index.row() <= m_services.count()) {
+ if (m_services[index.row()].isRunInExternalLibrary())
+ return Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable;
+ return Qt::ItemIsSelectable;
+ } else if (index.column() == 5 && index.row() <= m_services.count()) {
+ if (!m_services[index.row()].isRunInExternalLibrary())
+ return Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable;
+ return Qt::ItemIsSelectable;
+ }
+ return Qt::ItemIsSelectable;
+}
+
+QVariant AndroidServiceWidget::AndroidServiceModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (role == Qt::ToolTipRole && orientation == Qt::Horizontal) {
+ if (section == 0)
+ return tr("The name of the class implementing the service");
+ else if (section == 1)
+ return tr("Checked if the service is run in an external process");
+ else if (section == 2)
+ return tr("The name of the external process.\n"
+ "Prefix with : if the process is private, use a lowercase name if the process is global.");
+ else if (section == 3)
+ return tr("Checked if the service is in a separate dynamic library");
+ else if (section == 4)
+ return tr("The name of the separate dynamic library");
+ else if (section == 5)
+ return tr("The arguments for telling the app to run the service instead of the main activity");
+ } else if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
+ if (section == 0)
+ return tr("Service class name");
+ else if (section == 1)
+ return tr("Run in external process");
+ else if (section == 2)
+ return tr("Process name");
+ else if (section == 3)
+ return tr("Run in external library");
+ else if (section == 4)
+ return tr("Library name");
+ else if (section == 5)
+ return tr("Service arguments");
+ }
+ return {};
+}
+
+QVariant AndroidServiceWidget::AndroidServiceModel::data(const QModelIndex &index, int role) const
+{
+ if (!(index.row() >= 0 && index.row() < m_services.count()))
+ return {};
+ if (role == Qt::CheckStateRole) {
+ if (index.column() == 3)
+ return m_services[index.row()].isRunInExternalLibrary() ? Qt::Checked : Qt::Unchecked;
+ else if (index.column() == 1 && index.row() <= m_services.count())
+ return m_services[index.row()].isRunInExternalProcess() ? Qt::Checked : Qt::Unchecked;
+ return QVariant();
+ } else if (role == Qt::DisplayRole) {
+ if (index.column() == 0)
+ return m_services[index.row()].className();
+ else if (index.column() == 1)
+ return tr("Run in external process");
+ else if (index.column() == 2)
+ return m_services[index.row()].externalProcessName();
+ else if (index.column() == 3)
+ return tr("Run in external library");
+ else if (index.column() == 4)
+ return m_services[index.row()].externalLibraryName();
+ else if (index.column() == 5)
+ return m_services[index.row()].serviceArguments();
+ } else if (role == Qt::ToolTipRole) {
+ if (index.column() == 0 && m_services[index.row()].className().isEmpty())
+ return tr("The class name must be set");
+ else if (index.column() == 2 && m_services[index.row()].isRunInExternalProcess())
+ return tr("The process name must be set for a service run in an external process");
+ else if (index.column() == 4 && m_services[index.row()].isRunInExternalLibrary())
+ return tr("The library name must be set for a service run in an external library");
+ else if (index.column() == 5 && !m_services[index.row()].isRunInExternalLibrary())
+ return tr("The service arguments must be set for a service not run in an external library");
+ } else if (role == Qt::EditRole) {
+ if (index.column() == 0)
+ return m_services[index.row()].className();
+ else if (index.column() == 2)
+ return m_services[index.row()].externalProcessName();
+ else if (index.column() == 4)
+ return m_services[index.row()].externalLibraryName();
+ else if (index.column() == 5)
+ return m_services[index.row()].serviceArguments();
+ } else if (role == Qt::DecorationRole) {
+ if (index.column() == 0) {
+ if (m_services[index.row()].className().isEmpty())
+ return Utils::Icons::WARNING.icon();
+ } else if (index.column() == 2) {
+ if (m_services[index.row()].isRunInExternalProcess()
+ && m_services[index.row()].externalProcessName().isEmpty())
+ return Utils::Icons::WARNING.icon();
+ } else if (index.column() == 4) {
+ if (m_services[index.row()].isRunInExternalLibrary()
+ && m_services[index.row()].externalLibraryName().isEmpty())
+ return Utils::Icons::WARNING.icon();
+ } else if (index.column() == 5) {
+ if (!m_services[index.row()].isRunInExternalLibrary()
+ && m_services[index.row()].serviceArguments().isEmpty())
+ return Utils::Icons::WARNING.icon();
+ }
+ }
+ return {};
+}
+
+bool AndroidServiceWidget::AndroidServiceModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ if (!(index.row() >= 0 && index.row() < m_services.count()))
+ return {};
+ if (role == Qt::CheckStateRole) {
+ if (index.column() == 1)
+ m_services[index.row()].setRunInExternalProcess((value == Qt::Checked) ? true : false);
+ else if (index.column() == 3)
+ m_services[index.row()].setRunInExternalLibrary((value == Qt::Checked) ? true : false);
+ dataChanged(createIndex(index.row(), 0), createIndex(index.row(), 5));
+ if (m_services[index.row()].isValid())
+ validDataChanged();
+ else
+ invalidDataChanged();
+ } else if (role == Qt::EditRole) {
+ if (index.column() == 0) {
+ QString className = value.toString();
+ if (!className.isEmpty() && className[0] != QChar('.'))
+ className.push_front(QChar('.'));
+ m_services[index.row()].setClassName(className);
+ m_services[index.row()].setNewService(true);
+ } else if (index.column() == 2) {
+ m_services[index.row()].setExternalProcessName(value.toString());
+ } else if (index.column() == 4) {
+ m_services[index.row()].setExternalLibraryName(value.toString());
+ } else if (index.column() == 5) {
+ m_services[index.row()].setServiceArguments(value.toString());
+ }
+ dataChanged(index, index);
+ if (m_services[index.row()].isValid())
+ validDataChanged();
+ else
+ invalidDataChanged();
+ }
+ return true;
+}
+
+AndroidServiceWidget::AndroidServiceWidget(QWidget *parent) : QWidget(parent),
+ m_model(new AndroidServiceModel), m_tableView(new QTableView(this))
+{
+ m_tableView->setModel(m_model.data());
+ m_tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
+ QSizePolicy sizePolicy;
+ sizePolicy.setHorizontalPolicy(QSizePolicy::Expanding);
+ m_tableView->setSizePolicy(sizePolicy);
+ m_tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
+ auto layout = new QHBoxLayout(this);
+ layout->addWidget(m_tableView, 1);
+ auto buttonLayout = new QGridLayout();
+ auto addButton = new QPushButton(this);
+ addButton->setText(tr("Add"));
+ buttonLayout->addWidget(addButton, 0, 0);
+ m_removeButton = new QPushButton(this);
+ m_removeButton->setText(tr("Remove"));
+ m_removeButton->setEnabled(false);
+ buttonLayout->addWidget(m_removeButton, 1, 0);
+ layout->addLayout(buttonLayout);
+ layout->setAlignment(buttonLayout, Qt::AlignTop);
+ connect(addButton, &QAbstractButton::clicked,
+ this, &AndroidServiceWidget::addService);
+ connect(m_removeButton, &QAbstractButton::clicked,
+ this, &AndroidServiceWidget::removeService);
+ connect(m_tableView->selectionModel(), &QItemSelectionModel::selectionChanged,
+ [this](const QItemSelection &selected, const QItemSelection &/*deselected*/) {
+ if (!selected.isEmpty())
+ m_removeButton->setEnabled(true);
+ });
+ connect(m_model.data(), &AndroidServiceWidget::AndroidServiceModel::validDataChanged,
+ [this] {servicesModified();});
+ connect(m_model.data(), &AndroidServiceWidget::AndroidServiceModel::invalidDataChanged,
+ [this] {servicesInvalid();});
+}
+
+AndroidServiceWidget::~AndroidServiceWidget()
+{
+
+}
+
+void AndroidServiceWidget::setServices(const QList<AndroidServiceData> &androidServices)
+{
+ m_removeButton->setEnabled(false);
+ m_model->setServices(androidServices);
+}
+
+const QList<AndroidServiceData> &AndroidServiceWidget::services()
+{
+ return m_model->services();
+}
+
+void AndroidServiceWidget::servicesSaved()
+{
+ m_model->servicesSaved();
+}
+
+void AndroidServiceWidget::addService()
+{
+ m_model->addService();
+}
+
+void AndroidServiceWidget::removeService()
+{
+ auto selections = m_tableView->selectionModel()->selectedRows();
+ for (const auto &x : selections) {
+ m_model->removeService(x.row());
+ m_removeButton->setEnabled(false);
+ servicesModified();
+ break;
+ }
+}
+
+} // namespace Internal
+} // namespace Android
diff --git a/src/plugins/android/androidservicewidget.h b/src/plugins/android/androidservicewidget.h
new file mode 100644
index 0000000000..cc30003b65
--- /dev/null
+++ b/src/plugins/android/androidservicewidget.h
@@ -0,0 +1,89 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt 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 General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** 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-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QList>
+#include <QString>
+#include <QWidget>
+
+class QPushButton;
+class QTableView;
+
+namespace Android {
+namespace Internal {
+
+struct AndroidServiceData
+{
+public:
+ bool isValid() const;
+ void setClassName(const QString &className);
+ QString className() const;
+ void setRunInExternalProcess(bool isRunInExternalProcess);
+ bool isRunInExternalProcess() const;
+ void setExternalProcessName(const QString &externalProcessName);
+ QString externalProcessName() const;
+ void setRunInExternalLibrary(bool isRunInExternalLibrary);
+ bool isRunInExternalLibrary() const ;
+ void setExternalLibraryName(const QString &externalLibraryName);
+ QString externalLibraryName() const;
+ void setServiceArguments(const QString &serviceArguments);
+ QString serviceArguments() const;
+ void setNewService(bool isNewService);
+ bool isNewService() const;
+private:
+ QString m_className;
+ bool m_isRunInExternalProcess = false;
+ QString m_externalProcessName;
+ bool m_isRunInExternalLibrary = false;
+ QString m_externalLibName;
+ QString m_serviceArguments;
+ bool m_isNewService = false;
+};
+
+class AndroidServiceWidget : public QWidget
+{
+ Q_OBJECT
+public:
+ explicit AndroidServiceWidget(QWidget *parent = nullptr);
+ ~AndroidServiceWidget();
+ void setServices(const QList<AndroidServiceData> &androidServices);
+ const QList<AndroidServiceData> &services();
+ void servicesSaved();
+private:
+ void addService();
+ void removeService();
+signals:
+ void servicesModified();
+ void servicesInvalid();
+private:
+ class AndroidServiceModel;
+ QScopedPointer<AndroidServiceModel> m_model;
+ QTableView *m_tableView;
+ QPushButton *m_removeButton;
+};
+
+} // namespace Internal
+} // namespace Android
diff --git a/src/plugins/android/androidservicewidget_p.h b/src/plugins/android/androidservicewidget_p.h
new file mode 100644
index 0000000000..89b03639a1
--- /dev/null
+++ b/src/plugins/android/androidservicewidget_p.h
@@ -0,0 +1,59 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt 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 General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** 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-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "androidservicewidget.h"
+
+#include <QAbstractTableModel>
+
+namespace Android {
+namespace Internal {
+
+class AndroidServiceWidget::AndroidServiceModel : public QAbstractTableModel
+{
+ Q_OBJECT
+public:
+ void setServices(const QList<AndroidServiceData> &androidServices);
+ const QList<AndroidServiceData> &services();
+ void addService();
+ void removeService(int row);
+ void servicesSaved();
+signals:
+ void validDataChanged();
+ void invalidDataChanged();
+private:
+ int rowCount(const QModelIndex &/*parent*/ = QModelIndex()) const override;
+ int columnCount(const QModelIndex &/*parent*/ = QModelIndex()) const override;
+ Qt::ItemFlags flags(const QModelIndex &index) const override;
+ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
+private:
+ QList<AndroidServiceData> m_services;
+};
+
+} // namespace Internal
+} // namespace Android