Android: Let user specify shell commands

Let user specify list of shell commands to run before app starts
and after app quits.

Change-Id: I9794fb96180530ca6c28ce6581fda51a25be28d4
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Vikas Pachdha
2017-06-08 14:57:09 +02:00
parent cc8bff67b3
commit a6a13bfb76
13 changed files with 569 additions and 71 deletions

View File

@@ -0,0 +1,198 @@
/****************************************************************************
**
** Copyright (C) 2017 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 "adbcommandswidget.h"
#include "ui_adbcommandswidget.h"
#include "utils/utilsicons.h"
#include <QGroupBox>
#include <QItemSelectionModel>
#include <QShortcut>
#include <QStringListModel>
namespace Android {
namespace Internal {
using namespace std::placeholders;
const char defaultCommand[] = "echo \"shell command\"";
static void swapData(QStringListModel *model, const QModelIndex &srcIndex,
const QModelIndex &destIndex)
{
if (model) {
QVariant data = model->data(destIndex, Qt::EditRole); // QTBUG-55078
model->setData(destIndex, model->data(srcIndex, Qt::EditRole));
model->setData(srcIndex, data);
}
}
class AdbCommandsWidgetPrivate
{
public:
AdbCommandsWidgetPrivate(const AdbCommandsWidget &parent, QWidget *parentWidget);
~AdbCommandsWidgetPrivate();
private:
void addString(const QString &str);
void onAddButton();
void onMoveUpButton();
void onMoveDownButton();
void onRemove();
void onCurrentIndexChanged(const QModelIndex &newIndex, const QModelIndex &prevIndex);
const AdbCommandsWidget &m_parent;
QGroupBox *m_rootWidget = nullptr;
Ui::AdbCommandsWidget *m_ui = nullptr;
QStringListModel *m_stringModel = nullptr;
friend class AdbCommandsWidget;
};
AdbCommandsWidget::AdbCommandsWidget(QWidget *parent) :
QObject(parent),
d(new AdbCommandsWidgetPrivate(*this, parent))
{
}
AdbCommandsWidget::~AdbCommandsWidget()
{
}
QStringList AdbCommandsWidget::commandsList() const
{
return d->m_stringModel->stringList();
}
QWidget *AdbCommandsWidget::widget() const
{
return d->m_rootWidget;
}
void AdbCommandsWidget::setTitleText(const QString &title)
{
d->m_rootWidget->setTitle(title);
}
void AdbCommandsWidget::setCommandList(const QStringList &commands)
{
d->m_stringModel->setStringList(commands);
}
AdbCommandsWidgetPrivate::AdbCommandsWidgetPrivate(const AdbCommandsWidget &parent,
QWidget *parentWidget):
m_parent(parent),
m_rootWidget(new QGroupBox(parentWidget)),
m_ui(new Ui::AdbCommandsWidget),
m_stringModel(new QStringListModel)
{
m_ui->setupUi(m_rootWidget);
m_ui->addButton->setIcon(Utils::Icons::PLUS.icon());
m_ui->removeButton->setIcon(Utils::Icons::MINUS.icon());
m_ui->moveUpButton->setIcon(Utils::Icons::ARROW_UP.icon());
m_ui->moveDownButton->setIcon(Utils::Icons::ARROW_DOWN.icon());
auto deleteShortcut = new QShortcut(QKeySequence(QKeySequence::Delete), m_ui->commandsView);
deleteShortcut->setContext(Qt::WidgetShortcut);
QObject::connect(deleteShortcut, &QShortcut::activated,
std::bind(&AdbCommandsWidgetPrivate::onRemove, this));
QObject::connect(m_ui->addButton, &QToolButton::clicked,
std::bind(&AdbCommandsWidgetPrivate::onAddButton, this));
QObject::connect(m_ui->removeButton, &QToolButton::clicked,
std::bind(&AdbCommandsWidgetPrivate::onRemove, this));
QObject::connect(m_ui->moveUpButton, &QToolButton::clicked,
std::bind(&AdbCommandsWidgetPrivate::onMoveUpButton, this));
QObject::connect(m_ui->moveDownButton, &QToolButton::clicked,
std::bind(&AdbCommandsWidgetPrivate::onMoveDownButton, this));
m_ui->commandsView->setModel(m_stringModel);
QObject::connect(m_stringModel, &QStringListModel::dataChanged,
&m_parent, &AdbCommandsWidget::commandsChanged);
QObject::connect(m_stringModel, &QStringListModel::rowsRemoved,
&m_parent, &AdbCommandsWidget::commandsChanged);
QObject::connect(m_ui->commandsView->selectionModel(), &QItemSelectionModel::currentChanged,
std::bind(&AdbCommandsWidgetPrivate::onCurrentIndexChanged, this, _1, _2));
}
AdbCommandsWidgetPrivate::~AdbCommandsWidgetPrivate()
{
delete m_ui;
delete m_stringModel;
}
void AdbCommandsWidgetPrivate::addString(const QString &str)
{
if (!str.isEmpty()) {
m_stringModel->insertRows(m_stringModel->rowCount(), 1);
const QModelIndex lastItemIndex = m_stringModel->index(m_stringModel->rowCount() - 1);
m_stringModel->setData(lastItemIndex, str);
}
}
void AdbCommandsWidgetPrivate::onAddButton()
{
addString(defaultCommand);
const QModelIndex index = m_stringModel->index(m_stringModel->rowCount() - 1);
m_ui->commandsView->setCurrentIndex(index);
m_ui->commandsView->edit(index);
}
void AdbCommandsWidgetPrivate::onMoveUpButton()
{
QModelIndex index = m_ui->commandsView->currentIndex();
if (index.row() > 0) {
const QModelIndex newIndex = m_stringModel->index(index.row() - 1, 0);
swapData(m_stringModel, index, newIndex);
m_ui->commandsView->setCurrentIndex(newIndex);
}
}
void AdbCommandsWidgetPrivate::onMoveDownButton()
{
QModelIndex index = m_ui->commandsView->currentIndex();
if (index.row() < m_stringModel->rowCount() - 1) {
const QModelIndex newIndex = m_stringModel->index(index.row() + 1, 0);
swapData(m_stringModel, index, newIndex);
m_ui->commandsView->setCurrentIndex(newIndex);
}
}
void AdbCommandsWidgetPrivate::onRemove()
{
const QModelIndex &index = m_ui->commandsView->currentIndex();
if (index.isValid())
m_stringModel->removeRow(index.row());
}
void AdbCommandsWidgetPrivate::onCurrentIndexChanged(const QModelIndex &newIndex, const QModelIndex &prevIndex)
{
Q_UNUSED(prevIndex)
m_ui->moveUpButton->setEnabled(newIndex.row() != 0);
m_ui->moveDownButton->setEnabled(newIndex.row() < m_stringModel->rowCount() - 1);
m_ui->removeButton->setEnabled(newIndex.isValid());
}
} // Internal
} // Android

View File

@@ -0,0 +1,64 @@
/****************************************************************************
**
** Copyright (C) 2017 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 <QObject>
#include <QStringList>
#include <memory>
QT_BEGIN_NAMESPACE
class QWidget;
QT_END_NAMESPACE
namespace Android {
namespace Internal {
class AdbCommandsWidgetPrivate;
class AdbCommandsWidget : public QObject
{
Q_OBJECT
public:
explicit AdbCommandsWidget(QWidget *parent);
~AdbCommandsWidget();
QStringList commandsList() const;
void setCommandList(const QStringList &commands);
QWidget *widget() const;
void setTitleText(const QString &title);
signals:
void commandsChanged();
private:
std::unique_ptr<AdbCommandsWidgetPrivate> d;
};
} // Internal
} // Android

View File

@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AdbCommandsWidget</class>
<widget class="QGroupBox" name="AdbCommandsWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>682</width>
<height>391</height>
</rect>
</property>
<property name="windowTitle">
<string>Widget</string>
</property>
<property name="title">
<string/>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0" rowspan="6">
<widget class="QListView" name="commandsView">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="defaultDropAction">
<enum>Qt::MoveAction</enum>
</property>
<property name="movement">
<enum>QListView::Snap</enum>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QToolButton" name="moveUpButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string/>
</property>
<property name="arrowType">
<enum>Qt::NoArrow</enum>
</property>
</widget>
</item>
<item row="5" column="1">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="1">
<widget class="QToolButton" name="addButton">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QToolButton" name="moveDownButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string/>
</property>
<property name="arrowType">
<enum>Qt::NoArrow</enum>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QToolButton" name="removeButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
<layoutdefault spacing="6" margin="11"/>
<tabstops>
<tabstop>commandsView</tabstop>
<tabstop>addButton</tabstop>
<tabstop>removeButton</tabstop>
<tabstop>moveUpButton</tabstop>
<tabstop>moveDownButton</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@@ -50,7 +50,8 @@ HEADERS += \
androidtoolmanager.h \ androidtoolmanager.h \
androidsdkmanager.h \ androidsdkmanager.h \
androidavdmanager.h \ androidavdmanager.h \
androidrunconfigurationwidget.h androidrunconfigurationwidget.h \
adbcommandswidget.h
SOURCES += \ SOURCES += \
androidconfigurations.cpp \ androidconfigurations.cpp \
@@ -94,7 +95,8 @@ SOURCES += \
androidtoolmanager.cpp \ androidtoolmanager.cpp \
androidsdkmanager.cpp \ androidsdkmanager.cpp \
androidavdmanager.cpp \ androidavdmanager.cpp \
androidrunconfigurationwidget.cpp androidrunconfigurationwidget.cpp \
adbcommandswidget.cpp
FORMS += \ FORMS += \
androidsettingswidget.ui \ androidsettingswidget.ui \
@@ -103,7 +105,8 @@ FORMS += \
androiddevicedialog.ui \ androiddevicedialog.ui \
androiddeployqtwidget.ui \ androiddeployqtwidget.ui \
androidbuildapkwidget.ui \ androidbuildapkwidget.ui \
androidrunconfigurationwidget.ui androidrunconfigurationwidget.ui \
adbcommandswidget.ui
RESOURCES = android.qrc RESOURCES = android.qrc

View File

@@ -18,8 +18,11 @@ Project {
files: [ files: [
"android_global.h", "android_global.h",
"addnewavddialog.ui",
"android.qrc", "android.qrc",
"adbcommandswidget.cpp",
"adbcommandswidget.h",
"adbcommandswidget.ui",
"addnewavddialog.ui",
"androidanalyzesupport.cpp", "androidanalyzesupport.cpp",
"androidanalyzesupport.h", "androidanalyzesupport.h",
"androidavdmanager.cpp", "androidavdmanager.cpp",

View File

@@ -40,7 +40,10 @@ using namespace ProjectExplorer;
namespace Android { namespace Android {
using namespace Internal; using namespace Internal;
const char amStartArgsKey[] = "Android.AmStartArgsKey"; const char amStartArgsKey[] = "Android.AmStartArgsKey";
const char preStartShellCmdsKey[] = "Android.PreStartShellCmdListKey";
const char postFinishShellCmdsKey[] = "Android.PostFinishShellCmdListKey";
AndroidRunConfiguration::AndroidRunConfiguration(Target *parent, Core::Id id) AndroidRunConfiguration::AndroidRunConfiguration(Target *parent, Core::Id id)
: RunConfiguration(parent, id) : RunConfiguration(parent, id)
@@ -52,6 +55,16 @@ AndroidRunConfiguration::AndroidRunConfiguration(Target *parent, AndroidRunConfi
{ {
} }
void AndroidRunConfiguration::setPreStartShellCommands(const QStringList &cmdList)
{
m_preStartShellCommands = cmdList;
}
void AndroidRunConfiguration::setPostFinishShellCommands(const QStringList &cmdList)
{
m_postFinishShellCommands = cmdList;
}
void AndroidRunConfiguration::setAmStartExtraArgs(const QStringList &args) void AndroidRunConfiguration::setAmStartExtraArgs(const QStringList &args)
{ {
m_amStartExtraArgs = args; m_amStartExtraArgs = args;
@@ -61,8 +74,14 @@ QWidget *AndroidRunConfiguration::createConfigurationWidget()
{ {
auto configWidget = new AndroidRunConfigurationWidget(); auto configWidget = new AndroidRunConfigurationWidget();
configWidget->setAmStartArgs(m_amStartExtraArgs); configWidget->setAmStartArgs(m_amStartExtraArgs);
configWidget->setPreStartShellCommands(m_preStartShellCommands);
configWidget->setPostFinishShellCommands(m_postFinishShellCommands);
connect(configWidget, &AndroidRunConfigurationWidget::amStartArgsChanged, connect(configWidget, &AndroidRunConfigurationWidget::amStartArgsChanged,
this, &AndroidRunConfiguration::setAmStartExtraArgs); this, &AndroidRunConfiguration::setAmStartExtraArgs);
connect(configWidget, &AndroidRunConfigurationWidget::preStartCmdsChanged,
this, &AndroidRunConfiguration::setPreStartShellCommands);
connect(configWidget, &AndroidRunConfigurationWidget::postFinishCmdsChanged,
this, &AndroidRunConfiguration::setPostFinishShellCommands);
return configWidget; return configWidget;
} }
@@ -73,6 +92,8 @@ Utils::OutputFormatter *AndroidRunConfiguration::createOutputFormatter() const
bool AndroidRunConfiguration::fromMap(const QVariantMap &map) bool AndroidRunConfiguration::fromMap(const QVariantMap &map)
{ {
m_preStartShellCommands = map.value(preStartShellCmdsKey).toStringList();
m_postFinishShellCommands = map.value(postFinishShellCmdsKey).toStringList();
m_amStartExtraArgs = map.value(amStartArgsKey).toStringList(); m_amStartExtraArgs = map.value(amStartArgsKey).toStringList();
return RunConfiguration::fromMap(map); return RunConfiguration::fromMap(map);
} }
@@ -80,6 +101,8 @@ bool AndroidRunConfiguration::fromMap(const QVariantMap &map)
QVariantMap AndroidRunConfiguration::toMap() const QVariantMap AndroidRunConfiguration::toMap() const
{ {
QVariantMap res = RunConfiguration::toMap(); QVariantMap res = RunConfiguration::toMap();
res[preStartShellCmdsKey] = m_preStartShellCommands;
res[postFinishShellCmdsKey] = m_postFinishShellCommands;
res[amStartArgsKey] = m_amStartExtraArgs; res[amStartArgsKey] = m_amStartExtraArgs;
return res; return res;
} }
@@ -88,4 +111,15 @@ const QStringList &AndroidRunConfiguration::amStartExtraArgs() const
{ {
return m_amStartExtraArgs; return m_amStartExtraArgs;
} }
const QStringList &AndroidRunConfiguration::preStartShellCommands() const
{
return m_preStartShellCommands;
}
const QStringList &AndroidRunConfiguration::postFinishShellCommands() const
{
return m_postFinishShellCommands;
}
} // namespace Android } // namespace Android

View File

@@ -48,15 +48,21 @@ public:
QVariantMap toMap() const override; QVariantMap toMap() const override;
const QStringList &amStartExtraArgs() const; const QStringList &amStartExtraArgs() const;
const QStringList &preStartShellCommands() const;
const QStringList &postFinishShellCommands() const;
protected: protected:
AndroidRunConfiguration(ProjectExplorer::Target *parent, AndroidRunConfiguration *source); AndroidRunConfiguration(ProjectExplorer::Target *parent, AndroidRunConfiguration *source);
private: private:
void setPreStartShellCommands(const QStringList &cmdList);
void setPostFinishShellCommands(const QStringList &cmdList);
void setAmStartExtraArgs(const QStringList &args); void setAmStartExtraArgs(const QStringList &args);
private: private:
QStringList m_amStartExtraArgs; QStringList m_amStartExtraArgs;
QStringList m_preStartShellCommands;
QStringList m_postFinishShellCommands;
}; };
} // namespace Android } // namespace Android

View File

@@ -23,6 +23,7 @@
** **
****************************************************************************/ ****************************************************************************/
#include "androidrunconfigurationwidget.h" #include "androidrunconfigurationwidget.h"
#include "adbcommandswidget.h"
#include "ui_androidrunconfigurationwidget.h" #include "ui_androidrunconfigurationwidget.h"
#include "utils/utilsicons.h" #include "utils/utilsicons.h"
@@ -39,6 +40,26 @@ AndroidRunConfigurationWidget::AndroidRunConfigurationWidget(QWidget *parent):
m_ui->setupUi(detailsWidget); m_ui->setupUi(detailsWidget);
m_ui->m_warningIconLabel->setPixmap(Utils::Icons::WARNING.pixmap()); m_ui->m_warningIconLabel->setPixmap(Utils::Icons::WARNING.pixmap());
m_preStartCmdsWidget = new AdbCommandsWidget(detailsWidget);
connect(m_preStartCmdsWidget, &AdbCommandsWidget::commandsChanged, [this]() {
emit preStartCmdsChanged(m_preStartCmdsWidget->commandsList());
});
m_preStartCmdsWidget->setTitleText(tr("Shell commands to run on Android device before"
" application launch."));
m_postEndCmdsWidget = new AdbCommandsWidget(detailsWidget);
connect(m_postEndCmdsWidget, &AdbCommandsWidget::commandsChanged, [this]() {
emit postFinishCmdsChanged(m_postEndCmdsWidget->commandsList());
});
m_postEndCmdsWidget->setTitleText(tr("Shell commands to run on Android device after application"
" quits."));
auto mainLayout = static_cast<QGridLayout*>(detailsWidget->layout());
mainLayout->addWidget(m_preStartCmdsWidget->widget(), mainLayout->rowCount(),
0, mainLayout->columnCount() - 1, 0);
mainLayout->addWidget(m_postEndCmdsWidget->widget(), mainLayout->rowCount(),
0, mainLayout->columnCount() - 1, 0);
setWidget(detailsWidget); setWidget(detailsWidget);
setSummaryText(tr("Android run settings")); setSummaryText(tr("Android run settings"));
@@ -54,10 +75,19 @@ AndroidRunConfigurationWidget::~AndroidRunConfigurationWidget()
void AndroidRunConfigurationWidget::setAmStartArgs(const QStringList &args) void AndroidRunConfigurationWidget::setAmStartArgs(const QStringList &args)
{ {
if (m_ui->m_amStartArgsEdit && !args.isEmpty())
m_ui->m_amStartArgsEdit->setText(Utils::QtcProcess::joinArgs(args, Utils::OsTypeLinux)); m_ui->m_amStartArgsEdit->setText(Utils::QtcProcess::joinArgs(args, Utils::OsTypeLinux));
} }
void AndroidRunConfigurationWidget::setPreStartShellCommands(const QStringList &cmdList)
{
m_preStartCmdsWidget->setCommandList(cmdList);
}
void AndroidRunConfigurationWidget::setPostFinishShellCommands(const QStringList &cmdList)
{
m_postEndCmdsWidget->setCommandList(cmdList);
}
} // namespace Internal } // namespace Internal
} // namespace Android } // namespace Android

View File

@@ -29,10 +29,14 @@
#include "projectexplorer/runconfiguration.h" #include "projectexplorer/runconfiguration.h"
#include "utils/detailswidget.h" #include "utils/detailswidget.h"
#include <QStringListModel>
#include <memory>
namespace Android { namespace Android {
namespace Internal { namespace Internal {
class AdbCommandsWidget;
namespace Ui { namespace Ui {
class AndroidRunConfigurationWidget; class AndroidRunConfigurationWidget;
} }
@@ -45,12 +49,22 @@ public:
~AndroidRunConfigurationWidget(); ~AndroidRunConfigurationWidget();
void setAmStartArgs(const QStringList &args); void setAmStartArgs(const QStringList &args);
void setPreStartShellCommands(const QStringList &cmdList);
void setPostFinishShellCommands(const QStringList &cmdList);
signals: signals:
void amStartArgsChanged(QStringList args); void amStartArgsChanged(QStringList args);
void preStartCmdsChanged(const QStringList &cmdList);
void postFinishCmdsChanged(const QStringList &cmdList);
private:
void addPreStartCommand(const QString &command);
void addPostFinishCommand(const QString &command);
private: private:
std::unique_ptr<Ui::AndroidRunConfigurationWidget> m_ui; std::unique_ptr<Ui::AndroidRunConfigurationWidget> m_ui;
AdbCommandsWidget *m_preStartCmdsWidget = nullptr;
AdbCommandsWidget *m_postEndCmdsWidget = nullptr;
}; };
} // namespace Internal } // namespace Internal

View File

@@ -6,25 +6,31 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>625</width> <width>731</width>
<height>73</height> <height>119</height>
</rect> </rect>
</property> </property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string>Form</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="0" column="2"> <property name="spacing">
<widget class="QLineEdit" name="m_amStartArgsEdit"/> <number>-1</number>
</item> </property>
<item row="1" column="2"> <item row="1" column="0" colspan="2">
<widget class="QLabel" name="label_3"> <widget class="QLabel" name="label">
<property name="text"> <property name="text">
<string>Conflicting &quot;am start&quot; options might result in the app startup failure.</string> <string>Activity manager start options:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="2" column="1">
<widget class="QLabel" name="m_warningIconLabel"> <widget class="QLabel" name="m_warningIconLabel">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred"> <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
@@ -37,7 +43,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="2" column="0">
<spacer name="horizontalSpacer"> <spacer name="horizontalSpacer">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
@@ -53,13 +59,32 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item row="0" column="0" colspan="2"> <item row="3" column="0" colspan="3">
<widget class="QLabel" name="label"> <spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>12</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="2">
<widget class="QLabel" name="label_3">
<property name="text"> <property name="text">
<string>Activity manager start options:</string> <string>Conflicting "am start" options might result in the app startup failure.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="2">
<widget class="QLineEdit" name="m_amStartArgsEdit"/>
</item>
</layout> </layout>
</widget> </widget>
<resources/> <resources/>

View File

@@ -37,8 +37,8 @@ struct ANDROID_EXPORT AndroidRunnable
QString intentName; QString intentName;
QStringList amStartExtraArgs; QStringList amStartExtraArgs;
Utils::Environment environment; Utils::Environment environment;
QVector<QStringList> beforeStartADBCommands; QStringList beforeStartAdbCommands;
QVector<QStringList> afterFinishADBCommands; QStringList afterFinishAdbCommands;
QString deviceSerialNumber; QString deviceSerialNumber;
QString displayName() const { return packageName; } QString displayName() const { return packageName; }
@@ -51,8 +51,8 @@ inline bool operator==(const AndroidRunnable &r1, const AndroidRunnable &r2)
&& r1.intentName == r2.intentName && r1.intentName == r2.intentName
&& r1.amStartExtraArgs == r2.amStartExtraArgs && r1.amStartExtraArgs == r2.amStartExtraArgs
&& r1.environment == r2.environment && r1.environment == r2.environment
&& r1.beforeStartADBCommands == r2.beforeStartADBCommands && r1.beforeStartAdbCommands == r2.beforeStartAdbCommands
&& r1.afterFinishADBCommands == r2.afterFinishADBCommands && r1.afterFinishAdbCommands == r2.afterFinishAdbCommands
&& r1.deviceSerialNumber == r2.deviceSerialNumber; && r1.deviceSerialNumber == r2.deviceSerialNumber;
} }

View File

@@ -214,14 +214,13 @@ class AndroidRunnerWorker : public QObject
}; };
public: public:
AndroidRunnerWorker(RunControl *runControl, const QString &packageName, AndroidRunnerWorker(RunControl *runControl, const AndroidRunnable &runnable);
const QStringList &selector);
~AndroidRunnerWorker(); ~AndroidRunnerWorker();
void asyncStart(const AndroidRunnable &runnable); void asyncStart();
void asyncStop(const AndroidRunnable &runnable); void asyncStop();
void setAdbParameters(const QString &packageName, const QStringList &selector); void setAndroidRunnable(const AndroidRunnable &runnable);
void handleRemoteDebuggerRunning(); void handleRemoteDebuggerRunning();
Utils::Port localGdbServerPort() const { return m_localGdbServerPort; } Utils::Port localGdbServerPort() const { return m_localGdbServerPort; }
@@ -238,7 +237,7 @@ private:
void logcatReadStandardError(); void logcatReadStandardError();
void logcatReadStandardOutput(); void logcatReadStandardOutput();
void adbKill(qint64 pid); void adbKill(qint64 pid);
QStringList selector() const { return m_selector; } QStringList selector() const;
void forceStop(); void forceStop();
void findPs(); void findPs();
void logcatProcess(const QByteArray &text, QByteArray &buffer, bool onlyError); void logcatProcess(const QByteArray &text, QByteArray &buffer, bool onlyError);
@@ -264,22 +263,19 @@ private:
QString m_gdbserverPath; QString m_gdbserverPath;
QString m_gdbserverSocket; QString m_gdbserverSocket;
QString m_adb; QString m_adb;
QStringList m_selector;
QRegExp m_logCatRegExp; QRegExp m_logCatRegExp;
DebugHandShakeType m_handShakeMethod = SocketHandShake; DebugHandShakeType m_handShakeMethod = SocketHandShake;
bool m_customPort = false; bool m_customPort = false;
QString m_packageName; AndroidRunnable m_androidRunnable;
int m_socketHandShakePort = MIN_SOCKET_HANDSHAKE_PORT; int m_socketHandShakePort = MIN_SOCKET_HANDSHAKE_PORT;
}; };
AndroidRunnerWorker::AndroidRunnerWorker(RunControl *runControl, const QString &packageName, AndroidRunnerWorker::AndroidRunnerWorker(RunControl *runControl, const AndroidRunnable &runnable)
const QStringList &selector)
: m_adbLogcatProcess(nullptr, deleter) : m_adbLogcatProcess(nullptr, deleter)
, m_psIsAlive(nullptr, deleter) , m_psIsAlive(nullptr, deleter)
, m_selector(selector)
, m_logCatRegExp(regExpLogcat) , m_logCatRegExp(regExpLogcat)
, m_packageName(packageName) , m_androidRunnable(runnable)
{ {
auto runConfig = runControl->runConfiguration(); auto runConfig = runControl->runConfiguration();
auto aspect = runConfig->extraAspect<Debugger::DebuggerRunConfigurationAspect>(); auto aspect = runConfig->extraAspect<Debugger::DebuggerRunConfigurationAspect>();
@@ -307,9 +303,9 @@ AndroidRunnerWorker::AndroidRunnerWorker(RunControl *runControl, const QString &
} }
m_adb = AndroidConfigurations::currentConfig().adbToolPath().toString(); m_adb = AndroidConfigurations::currentConfig().adbToolPath().toString();
QString packageDir = "/data/data/" + m_packageName; QString packageDir = "/data/data/" + m_androidRunnable.packageName;
m_pingFile = packageDir + "/debug-ping"; m_pingFile = packageDir + "/debug-ping";
m_pongFile = "/data/local/tmp/qt/debug-pong-" + m_packageName; m_pongFile = "/data/local/tmp/qt/debug-pong-" + m_androidRunnable.packageName;
m_gdbserverSocket = packageDir + "/debug-socket"; m_gdbserverSocket = packageDir + "/debug-socket";
const QtSupport::BaseQtVersion *version = QtSupport::QtKitInformation::qtVersion( const QtSupport::BaseQtVersion *version = QtSupport::QtKitInformation::qtVersion(
runConfig->target()->kit()); runConfig->target()->kit());
@@ -347,20 +343,20 @@ AndroidRunnerWorker::~AndroidRunnerWorker()
void AndroidRunnerWorker::forceStop() void AndroidRunnerWorker::forceStop()
{ {
runAdb({"shell", "am", "force-stop", m_packageName}, nullptr, 30); runAdb({"shell", "am", "force-stop", m_androidRunnable.packageName}, nullptr, 30);
// try killing it via kill -9 // try killing it via kill -9
const QByteArray out = Utils::SynchronousProcess() const QByteArray out = Utils::SynchronousProcess()
.runBlocking(m_adb, selector() << QStringLiteral("shell") << pidScript) .runBlocking(m_adb, selector() << QStringLiteral("shell") << pidScript)
.allRawOutput(); .allRawOutput();
qint64 pid = extractPID(out.simplified(), m_packageName); qint64 pid = extractPID(out.simplified(), m_androidRunnable.packageName);
if (pid != -1) { if (pid != -1) {
adbKill(pid); adbKill(pid);
} }
} }
void AndroidRunnerWorker::asyncStart(const AndroidRunnable &runnable) void AndroidRunnerWorker::asyncStart()
{ {
forceStop(); forceStop();
@@ -378,12 +374,12 @@ void AndroidRunnerWorker::asyncStart(const AndroidRunnable &runnable)
if (m_useCppDebugger) if (m_useCppDebugger)
runAdb({"shell", "rm", m_pongFile}); // Remove pong file. runAdb({"shell", "rm", m_pongFile}); // Remove pong file.
for (const QStringList &entry: runnable.beforeStartADBCommands) for (const QString &entry: m_androidRunnable.beforeStartAdbCommands)
runAdb(entry); runAdb(entry.split(' ', QString::SkipEmptyParts));
QStringList args({"shell", "am", "start"}); QStringList args({"shell", "am", "start"});
args << runnable.amStartExtraArgs; args << m_androidRunnable.amStartExtraArgs;
args << "-n" << runnable.intentName; args << "-n" << m_androidRunnable.intentName;
if (m_useCppDebugger) { if (m_useCppDebugger) {
if (!runAdb({"forward", "--remove", "tcp:" + m_localGdbServerPort.toString()})){ if (!runAdb({"forward", "--remove", "tcp:" + m_localGdbServerPort.toString()})){
@@ -395,7 +391,7 @@ void AndroidRunnerWorker::asyncStart(const AndroidRunnable &runnable)
return; return;
} }
const QString pingPongSocket(m_packageName + ".ping_pong_socket"); const QString pingPongSocket(m_androidRunnable.packageName + ".ping_pong_socket");
args << "-e" << "debug_ping" << "true"; args << "-e" << "debug_ping" << "true";
if (m_handShakeMethod == SocketHandShake) { if (m_handShakeMethod == SocketHandShake) {
args << "-e" << "ping_socket" << pingPongSocket; args << "-e" << "ping_socket" << pingPongSocket;
@@ -499,7 +495,8 @@ void AndroidRunnerWorker::asyncStart(const AndroidRunnable &runnable)
break; break;
if (i == 20) { if (i == 20) {
emit remoteProcessFinished(tr("Unable to start \"%1\".").arg(m_packageName)); emit remoteProcessFinished(tr("Unable to start \"%1\".")
.arg(m_androidRunnable.packageName));
return; return;
} }
qDebug() << "WAITING FOR " << tmp.fileName(); qDebug() << "WAITING FOR " << tmp.fileName();
@@ -512,7 +509,7 @@ void AndroidRunnerWorker::asyncStart(const AndroidRunnable &runnable)
QTC_ASSERT(!m_adbLogcatProcess, /**/); QTC_ASSERT(!m_adbLogcatProcess, /**/);
m_adbLogcatProcess = std::move(logcatProcess); m_adbLogcatProcess = std::move(logcatProcess);
m_pidFinder = Utils::onResultReady(Utils::runAsync(&findProcessPID, m_adb, selector(), m_pidFinder = Utils::onResultReady(Utils::runAsync(&findProcessPID, m_adb, selector(),
m_packageName), m_androidRunnable.packageName),
bind(&AndroidRunnerWorker::onProcessIdChanged, this, _1)); bind(&AndroidRunnerWorker::onProcessIdChanged, this, _1));
} }
@@ -543,7 +540,7 @@ bool AndroidRunnerWorker::runAdb(const QStringList &args, QString *exitMessage,
{ {
Utils::SynchronousProcess adb; Utils::SynchronousProcess adb;
adb.setTimeoutS(timeoutS); adb.setTimeoutS(timeoutS);
Utils::SynchronousProcessResponse response = adb.run(m_adb, m_selector + args); Utils::SynchronousProcessResponse response = adb.run(m_adb, selector() + args);
if (exitMessage) if (exitMessage)
*exitMessage = response.exitMessage(m_adb, timeoutS); *exitMessage = response.exitMessage(m_adb, timeoutS);
return response.result == Utils::SynchronousProcessResponse::Finished; return response.result == Utils::SynchronousProcessResponse::Finished;
@@ -567,7 +564,7 @@ void AndroidRunnerWorker::handleRemoteDebuggerRunning()
// emit remoteProcessStarted(m_localGdbServerPort, m_qmlPort); // emit remoteProcessStarted(m_localGdbServerPort, m_qmlPort);
} }
void AndroidRunnerWorker::asyncStop(const AndroidRunnable &runnable) void AndroidRunnerWorker::asyncStop()
{ {
if (!m_pidFinder.isFinished()) if (!m_pidFinder.isFinished())
m_pidFinder.cancel(); m_pidFinder.cancel();
@@ -575,14 +572,11 @@ void AndroidRunnerWorker::asyncStop(const AndroidRunnable &runnable)
if (m_processPID != -1) { if (m_processPID != -1) {
forceStop(); forceStop();
} }
for (const QStringList &entry: runnable.afterFinishADBCommands)
runAdb(entry);
} }
void AndroidRunnerWorker::setAdbParameters(const QString &packageName, const QStringList &selector) void AndroidRunnerWorker::setAndroidRunnable(const AndroidRunnable &runnable)
{ {
m_packageName = packageName; m_androidRunnable = runnable;
m_selector = selector;
} }
void AndroidRunnerWorker::logcatProcess(const QByteArray &text, QByteArray &buffer, bool onlyError) void AndroidRunnerWorker::logcatProcess(const QByteArray &text, QByteArray &buffer, bool onlyError)
@@ -635,10 +629,14 @@ void AndroidRunnerWorker::onProcessIdChanged(qint64 pid)
m_processPID = pid; m_processPID = pid;
if (pid == -1) { if (pid == -1) {
emit remoteProcessFinished(QLatin1String("\n\n") + tr("\"%1\" died.") emit remoteProcessFinished(QLatin1String("\n\n") + tr("\"%1\" died.")
.arg(m_packageName)); .arg(m_androidRunnable.packageName));
// App died/killed. Reset log and monitor processes. // App died/killed. Reset log and monitor processes.
m_adbLogcatProcess.reset(); m_adbLogcatProcess.reset();
m_psIsAlive.reset(); m_psIsAlive.reset();
// Run adb commands after application quit.
for (const QString &entry: m_androidRunnable.afterFinishAdbCommands)
runAdb(entry.split(' ', QString::SkipEmptyParts));
} else { } else {
// In debugging cases this will be funneled to the engine to actually start // In debugging cases this will be funneled to the engine to actually start
// and attach gdb. Afterwards this ends up in handleRemoteDebuggerRunning() below. // and attach gdb. Afterwards this ends up in handleRemoteDebuggerRunning() below.
@@ -671,7 +669,12 @@ void AndroidRunnerWorker::logcatReadStandardOutput()
void AndroidRunnerWorker::adbKill(qint64 pid) void AndroidRunnerWorker::adbKill(qint64 pid)
{ {
runAdb({"shell", "kill", "-9", QString::number(pid)}); runAdb({"shell", "kill", "-9", QString::number(pid)});
runAdb({"shell", "run-as", m_packageName, "kill", "-9", QString::number(pid)}); runAdb({"shell", "run-as", m_androidRunnable.packageName, "kill", "-9", QString::number(pid)});
}
QStringList AndroidRunnerWorker::selector() const
{
return AndroidDeviceInfo::adbSelector(m_androidRunnable.deviceSerialNumber);
} }
AndroidRunner::AndroidRunner(RunControl *runControl) AndroidRunner::AndroidRunner(RunControl *runControl)
@@ -694,15 +697,19 @@ AndroidRunner::AndroidRunner(RunControl *runControl)
auto androidRunConfig = qobject_cast<AndroidRunConfiguration *>(runControl->runConfiguration()); auto androidRunConfig = qobject_cast<AndroidRunConfiguration *>(runControl->runConfiguration());
m_androidRunnable.amStartExtraArgs = androidRunConfig->amStartExtraArgs(); m_androidRunnable.amStartExtraArgs = androidRunConfig->amStartExtraArgs();
for (QString shellCmd: androidRunConfig->preStartShellCommands())
m_androidRunnable.beforeStartAdbCommands.append(QString("shell %1").arg(shellCmd));
m_worker.reset(new AndroidRunnerWorker(runControl, m_androidRunnable.packageName, for (QString shellCmd: androidRunConfig->postFinishShellCommands())
AndroidDeviceInfo::adbSelector(m_androidRunnable.deviceSerialNumber))); m_androidRunnable.afterFinishAdbCommands.append(QString("shell %1").arg(shellCmd));
m_worker.reset(new AndroidRunnerWorker(runControl, m_androidRunnable));
m_worker->moveToThread(&m_thread); m_worker->moveToThread(&m_thread);
connect(this, &AndroidRunner::asyncStart, m_worker.data(), &AndroidRunnerWorker::asyncStart); connect(this, &AndroidRunner::asyncStart, m_worker.data(), &AndroidRunnerWorker::asyncStart);
connect(this, &AndroidRunner::asyncStop, m_worker.data(), &AndroidRunnerWorker::asyncStop); connect(this, &AndroidRunner::asyncStop, m_worker.data(), &AndroidRunnerWorker::asyncStop);
connect(this, &AndroidRunner::adbParametersChanged, connect(this, &AndroidRunner::androidRunnableChanged,
m_worker.data(), &AndroidRunnerWorker::setAdbParameters); m_worker.data(), &AndroidRunnerWorker::setAndroidRunnable);
connect(this, &AndroidRunner::remoteDebuggerRunning, connect(this, &AndroidRunner::remoteDebuggerRunning,
m_worker.data(), &AndroidRunnerWorker::handleRemoteDebuggerRunning); m_worker.data(), &AndroidRunnerWorker::handleRemoteDebuggerRunning);
@@ -738,7 +745,7 @@ void AndroidRunner::start()
} }
} }
emit asyncStart(m_androidRunnable); emit asyncStart();
} }
void AndroidRunner::stop() void AndroidRunner::stop()
@@ -750,7 +757,7 @@ void AndroidRunner::stop()
return; return;
} }
emit asyncStop(m_androidRunnable); emit asyncStop();
} }
void AndroidRunner::qmlServerPortReady(Port port) void AndroidRunner::qmlServerPortReady(Port port)
@@ -797,8 +804,7 @@ void AndroidRunner::setRunnable(const AndroidRunnable &runnable)
{ {
if (runnable != m_androidRunnable) { if (runnable != m_androidRunnable) {
m_androidRunnable = runnable; m_androidRunnable = runnable;
emit adbParametersChanged(runnable.packageName, emit androidRunnableChanged(m_androidRunnable);
AndroidDeviceInfo::adbSelector(runnable.deviceSerialNumber));
} }
} }
@@ -816,8 +822,7 @@ void AndroidRunner::launchAVD()
AndroidConfigurations::None); AndroidConfigurations::None);
AndroidManager::setDeviceSerialNumber(m_target, info.serialNumber); AndroidManager::setDeviceSerialNumber(m_target, info.serialNumber);
m_androidRunnable.deviceSerialNumber = info.serialNumber; m_androidRunnable.deviceSerialNumber = info.serialNumber;
emit adbParametersChanged(m_androidRunnable.packageName, emit androidRunnableChanged(m_androidRunnable);
AndroidDeviceInfo::adbSelector(info.serialNumber));
if (info.isValid()) { if (info.isValid()) {
AndroidAvdManager avdManager; AndroidAvdManager avdManager;
if (avdManager.findAvd(info.avdname).isEmpty()) { if (avdManager.findAvd(info.avdname).isEmpty()) {
@@ -840,7 +845,7 @@ void AndroidRunner::checkAVD()
if (avdManager.isAvdBooted(serialNumber)) { if (avdManager.isAvdBooted(serialNumber)) {
m_checkAVDTimer.stop(); m_checkAVDTimer.stop();
AndroidManager::setDeviceSerialNumber(m_target, serialNumber); AndroidManager::setDeviceSerialNumber(m_target, serialNumber);
emit asyncStart(m_androidRunnable); emit asyncStart();
} else if (!config.isConnected(serialNumber)) { } else if (!config.isConnected(serialNumber)) {
// device was disconnected // device was disconnected
m_checkAVDTimer.stop(); m_checkAVDTimer.stop();

View File

@@ -64,12 +64,11 @@ public:
void stop() override; void stop() override;
signals: signals:
void asyncStart(const AndroidRunnable &runnable); void asyncStart();
void asyncStop(const AndroidRunnable &runnable); void asyncStop();
void remoteDebuggerRunning(); void remoteDebuggerRunning();
void qmlServerReady(const QUrl &serverUrl); void qmlServerReady(const QUrl &serverUrl);
void androidRunnableChanged(const AndroidRunnable &runnable);
void adbParametersChanged(const QString &packageName, const QStringList &selector);
void avdDetected(); void avdDetected();
private: private: