2016-01-15 14:57:40 +01:00
|
|
|
/****************************************************************************
|
2012-04-18 20:30:57 +03:00
|
|
|
**
|
2016-01-15 14:57:40 +01:00
|
|
|
** Copyright (C) 2016 BogDan Vatra <bog_dan_ro@yahoo.com>
|
|
|
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
|
|
|
** Contact: https://www.qt.io/licensing/
|
2012-04-18 20:30:57 +03:00
|
|
|
**
|
2012-10-02 09:12:39 +02:00
|
|
|
** This file is part of Qt Creator.
|
2012-04-18 20:30:57 +03:00
|
|
|
**
|
2012-10-02 09:12:39 +02:00
|
|
|
** 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
|
2016-01-15 14:57:40 +01:00
|
|
|
** 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.
|
2012-04-18 20:30:57 +03:00
|
|
|
**
|
2016-01-15 14:57:40 +01:00
|
|
|
** 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.
|
2012-04-18 20:30:57 +03:00
|
|
|
**
|
2012-10-02 09:12:39 +02:00
|
|
|
****************************************************************************/
|
2012-04-18 20:30:57 +03:00
|
|
|
|
|
|
|
|
#include "androidrunner.h"
|
|
|
|
|
|
2013-09-17 18:24:57 +02:00
|
|
|
#include "androiddeployqtstep.h"
|
2012-04-18 20:30:57 +03:00
|
|
|
#include "androidconfigurations.h"
|
|
|
|
|
#include "androidglobal.h"
|
|
|
|
|
#include "androidrunconfiguration.h"
|
2012-04-24 15:49:09 +02:00
|
|
|
#include "androidmanager.h"
|
2017-04-03 11:11:17 +02:00
|
|
|
#include "androidavdmanager.h"
|
2012-04-24 15:49:09 +02:00
|
|
|
|
2013-03-27 13:03:15 +01:00
|
|
|
#include <debugger/debuggerrunconfigurationaspect.h>
|
2016-06-08 16:57:32 +02:00
|
|
|
#include <projectexplorer/projectexplorer.h>
|
|
|
|
|
#include <projectexplorer/projectexplorerconstants.h>
|
|
|
|
|
#include <projectexplorer/projectexplorersettings.h>
|
2012-04-24 15:49:09 +02:00
|
|
|
#include <projectexplorer/target.h>
|
2014-08-07 10:10:32 +03:00
|
|
|
#include <qtsupport/qtkitinformation.h>
|
2013-02-27 15:12:36 +01:00
|
|
|
#include <utils/qtcassert.h>
|
2016-02-05 15:06:15 +01:00
|
|
|
#include <utils/runextensions.h>
|
2016-04-29 16:52:58 +02:00
|
|
|
#include <utils/synchronousprocess.h>
|
2017-01-19 16:44:22 +01:00
|
|
|
#include <utils/temporaryfile.h>
|
2012-04-18 20:30:57 +03:00
|
|
|
|
2016-11-24 16:33:42 +01:00
|
|
|
#include <chrono>
|
|
|
|
|
#include <memory>
|
2014-11-18 15:14:40 +01:00
|
|
|
#include <QApplication>
|
2014-08-12 09:06:16 +02:00
|
|
|
#include <QDir>
|
2017-03-09 23:02:32 +01:00
|
|
|
#include <QRegExp>
|
2012-04-18 20:30:57 +03:00
|
|
|
#include <QTime>
|
2013-04-18 10:01:05 +02:00
|
|
|
#include <QTcpServer>
|
2014-11-18 15:14:40 +01:00
|
|
|
#include <QTcpSocket>
|
2016-10-10 15:38:50 +03:00
|
|
|
#include <QRegularExpression>
|
2012-04-18 20:30:57 +03:00
|
|
|
|
2016-11-24 16:33:42 +01:00
|
|
|
using namespace std;
|
|
|
|
|
using namespace std::placeholders;
|
2016-06-08 16:57:32 +02:00
|
|
|
using namespace ProjectExplorer;
|
|
|
|
|
|
2014-09-18 18:29:35 +02:00
|
|
|
/*
|
|
|
|
|
This uses explicit handshakes between the application and the
|
2014-11-18 15:14:40 +01:00
|
|
|
gdbserver start and the host side by using the gdbserver socket.
|
|
|
|
|
|
|
|
|
|
For the handshake there are two mechanisms. Only the first method works
|
|
|
|
|
on Android 5.x devices and is chosen as default option. The second
|
|
|
|
|
method can be enabled by setting the QTC_ANDROID_USE_FILE_HANDSHAKE
|
|
|
|
|
environment variable before starting Qt Creator.
|
|
|
|
|
|
|
|
|
|
1.) This method uses a TCP server on the Android device which starts
|
|
|
|
|
listening for incoming connections. The socket is forwarded by adb
|
|
|
|
|
and creator connects to it. This is the only method that works
|
|
|
|
|
on Android 5.x devices.
|
|
|
|
|
|
|
|
|
|
2.) This method uses two files ("ping" file in the application dir,
|
|
|
|
|
"pong" file in /data/local/tmp/qt).
|
2014-09-18 18:29:35 +02:00
|
|
|
|
|
|
|
|
The sequence is as follows:
|
|
|
|
|
|
|
|
|
|
host: adb forward debugsocket :5039
|
|
|
|
|
|
|
|
|
|
host: adb shell rm pong file
|
|
|
|
|
host: adb shell am start
|
|
|
|
|
host: loop until ping file appears
|
|
|
|
|
|
|
|
|
|
app start up: launch gdbserver --multi +debug-socket
|
|
|
|
|
app start up: loop until debug socket appear
|
|
|
|
|
|
|
|
|
|
gdbserver: normal start up including opening debug-socket,
|
|
|
|
|
not yet attached to any process
|
|
|
|
|
|
2014-11-18 15:14:40 +01:00
|
|
|
app start up: 1.) set up ping connection or 2.) touch ping file
|
|
|
|
|
app start up: 1.) accept() or 2.) loop until pong file appears
|
2014-09-18 18:29:35 +02:00
|
|
|
|
|
|
|
|
host: start gdb
|
|
|
|
|
host: gdb: set up binary, breakpoints, path etc
|
|
|
|
|
host: gdb: target extended-remote :5039
|
|
|
|
|
|
|
|
|
|
gdbserver: accepts connection from gdb
|
|
|
|
|
|
|
|
|
|
host: gdb: attach <application-pid>
|
|
|
|
|
|
|
|
|
|
gdbserver: attaches to the application
|
|
|
|
|
and stops it
|
|
|
|
|
|
|
|
|
|
app start up: stopped now (it is still waiting for
|
|
|
|
|
the pong anyway)
|
|
|
|
|
|
|
|
|
|
host: gdb: continue
|
|
|
|
|
|
|
|
|
|
gdbserver: resumes application
|
|
|
|
|
|
|
|
|
|
app start up: resumed (still waiting for the pong)
|
|
|
|
|
|
2014-11-18 15:14:40 +01:00
|
|
|
host: 1) write "ok" to ping pong connection or 2.) write pong file
|
2014-09-18 18:29:35 +02:00
|
|
|
|
|
|
|
|
app start up: java code continues now, the process
|
|
|
|
|
is already fully under control
|
|
|
|
|
of gdbserver. Breakpoints are set etc,
|
|
|
|
|
we are before main.
|
|
|
|
|
app start up: native code launches
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
2012-04-18 20:30:57 +03:00
|
|
|
namespace Android {
|
|
|
|
|
namespace Internal {
|
|
|
|
|
|
2014-11-18 15:14:40 +01:00
|
|
|
const int MIN_SOCKET_HANDSHAKE_PORT = 20001;
|
|
|
|
|
const int MAX_SOCKET_HANDSHAKE_PORT = 20999;
|
2016-10-10 15:38:50 +03:00
|
|
|
static const QString pidScript = QStringLiteral("input keyevent KEYCODE_WAKEUP; "
|
|
|
|
|
"while true; do sleep 1; echo \"=\"; "
|
|
|
|
|
"for p in /proc/[0-9]*; "
|
|
|
|
|
"do cat <$p/cmdline && echo :${p##*/}; done; done");
|
2017-02-08 11:41:42 +01:00
|
|
|
|
|
|
|
|
static const QString regExpLogcat = QStringLiteral("[0-9\\-]*" // date
|
|
|
|
|
"\\s+"
|
|
|
|
|
"[0-9\\-:.]*"// time
|
|
|
|
|
"\\s*"
|
|
|
|
|
"(\\d*)" // pid 1. capture
|
|
|
|
|
"\\s+"
|
|
|
|
|
"\\d*" // unknown
|
|
|
|
|
"\\s+"
|
|
|
|
|
"(\\w)" // message type 2. capture
|
|
|
|
|
"\\s+"
|
|
|
|
|
"(.*): " // source 3. capture
|
|
|
|
|
"(.*)" // message 4. capture
|
|
|
|
|
"[\\n\\r]*"
|
|
|
|
|
);
|
2016-11-24 16:33:42 +01:00
|
|
|
static int APP_START_TIMEOUT = 45000;
|
|
|
|
|
|
2016-10-10 15:38:50 +03:00
|
|
|
enum class PidStatus {
|
|
|
|
|
Found,
|
|
|
|
|
Lost
|
|
|
|
|
};
|
2016-11-24 16:33:42 +01:00
|
|
|
|
2016-10-10 15:38:50 +03:00
|
|
|
struct PidInfo
|
2016-11-24 16:33:42 +01:00
|
|
|
{
|
2016-10-10 15:38:50 +03:00
|
|
|
PidInfo(qint64 pid = -1, PidStatus status = PidStatus::Lost, QString name = {})
|
|
|
|
|
: pid(pid)
|
|
|
|
|
, status(status)
|
|
|
|
|
, name(name)
|
|
|
|
|
{}
|
|
|
|
|
qint64 pid;
|
|
|
|
|
PidStatus status;
|
|
|
|
|
QString name;
|
|
|
|
|
};
|
2016-11-24 16:33:42 +01:00
|
|
|
|
|
|
|
|
static void deleter(QProcess *p)
|
|
|
|
|
{
|
2016-10-10 15:38:50 +03:00
|
|
|
p->disconnect();
|
2016-11-24 16:33:42 +01:00
|
|
|
p->kill();
|
|
|
|
|
p->waitForFinished();
|
|
|
|
|
// Might get deleted from its own signal handler.
|
|
|
|
|
p->deleteLater();
|
|
|
|
|
}
|
2014-11-18 15:14:40 +01:00
|
|
|
|
2016-08-05 15:40:33 +02:00
|
|
|
class AndroidRunnerWorker : public QObject
|
|
|
|
|
{
|
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
|
|
enum DebugHandShakeType {
|
|
|
|
|
PingPongFiles,
|
|
|
|
|
SocketHandShake
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
AndroidRunnerWorker(AndroidRunConfiguration *runConfig, Core::Id runMode,
|
|
|
|
|
const QString &packageName, const QStringList &selector);
|
2016-11-24 16:33:42 +01:00
|
|
|
~AndroidRunnerWorker();
|
2016-08-05 15:40:33 +02:00
|
|
|
|
|
|
|
|
void asyncStart(const QString &intentName, const QVector<QStringList> &adbCommands);
|
|
|
|
|
void asyncStop(const QVector<QStringList> &adbCommands);
|
|
|
|
|
|
|
|
|
|
void setAdbParameters(const QString &packageName, const QStringList &selector);
|
|
|
|
|
void handleRemoteDebuggerRunning();
|
|
|
|
|
|
|
|
|
|
signals:
|
|
|
|
|
void remoteServerRunning(const QByteArray &serverChannel, int pid);
|
|
|
|
|
void remoteProcessStarted(Utils::Port gdbServerPort, Utils::Port qmlPort);
|
|
|
|
|
void remoteProcessFinished(const QString &errString = QString());
|
|
|
|
|
|
|
|
|
|
void remoteOutput(const QString &output);
|
|
|
|
|
void remoteErrorOutput(const QString &output);
|
2016-10-10 15:38:50 +03:00
|
|
|
void pidFound(qint64, const QString &name);
|
|
|
|
|
void pidLost(qint64);
|
2016-08-05 15:40:33 +02:00
|
|
|
|
|
|
|
|
private:
|
2016-10-10 15:38:50 +03:00
|
|
|
void findProcessPids();
|
|
|
|
|
void onProcessIdChanged(PidInfo pidInfo);
|
2016-08-05 15:40:33 +02:00
|
|
|
void logcatReadStandardError();
|
|
|
|
|
void logcatReadStandardOutput();
|
|
|
|
|
void adbKill(qint64 pid);
|
|
|
|
|
QStringList selector() const { return m_selector; }
|
|
|
|
|
void forceStop();
|
|
|
|
|
void logcatProcess(const QByteArray &text, QByteArray &buffer, bool onlyError);
|
|
|
|
|
bool adbShellAmNeedsQuotes();
|
|
|
|
|
bool runAdb(const QStringList &args, QString *exitMessage = nullptr, int timeoutS = 10);
|
2016-10-10 15:38:50 +03:00
|
|
|
int deviceSdkVersion();
|
2016-08-05 15:40:33 +02:00
|
|
|
|
|
|
|
|
// Create the processes and timer in the worker thread, for correct thread affinity
|
2016-11-24 16:33:42 +01:00
|
|
|
std::unique_ptr<QProcess, decltype(&deleter)> m_adbLogcatProcess;
|
2016-10-10 15:38:50 +03:00
|
|
|
std::unique_ptr<QProcess, decltype(&deleter)> m_pidsFinderProcess;
|
2016-08-05 15:40:33 +02:00
|
|
|
QScopedPointer<QTcpSocket> m_socket;
|
|
|
|
|
|
|
|
|
|
QByteArray m_stdoutBuffer;
|
|
|
|
|
QByteArray m_stderrBuffer;
|
|
|
|
|
|
2016-10-10 15:38:50 +03:00
|
|
|
QSet<qint64> m_processPids;
|
2016-08-05 15:40:33 +02:00
|
|
|
bool m_useCppDebugger = false;
|
|
|
|
|
QmlDebug::QmlDebugServicesPreset m_qmlDebugServices;
|
|
|
|
|
Utils::Port m_localGdbServerPort; // Local end of forwarded debug socket.
|
|
|
|
|
Utils::Port m_qmlPort;
|
|
|
|
|
QString m_pingFile;
|
|
|
|
|
QString m_pongFile;
|
|
|
|
|
QString m_gdbserverPath;
|
|
|
|
|
QString m_gdbserverSocket;
|
|
|
|
|
QString m_adb;
|
|
|
|
|
QStringList m_selector;
|
|
|
|
|
DebugHandShakeType m_handShakeMethod = SocketHandShake;
|
|
|
|
|
bool m_customPort = false;
|
|
|
|
|
|
|
|
|
|
QString m_packageName;
|
|
|
|
|
int m_socketHandShakePort = MIN_SOCKET_HANDSHAKE_PORT;
|
2016-10-10 15:38:50 +03:00
|
|
|
QByteArray m_pidsBuffer;
|
|
|
|
|
QScopedPointer<QTimer> m_timeoutTimer;
|
2016-08-05 15:40:33 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
AndroidRunnerWorker::AndroidRunnerWorker(AndroidRunConfiguration *runConfig, Core::Id runMode,
|
|
|
|
|
const QString &packageName, const QStringList &selector)
|
2016-11-24 16:33:42 +01:00
|
|
|
: m_adbLogcatProcess(nullptr, deleter)
|
2016-10-10 15:38:50 +03:00
|
|
|
, m_pidsFinderProcess(nullptr, deleter)
|
2016-11-24 16:33:42 +01:00
|
|
|
, m_selector(selector)
|
|
|
|
|
, m_packageName(packageName)
|
2012-04-18 20:30:57 +03:00
|
|
|
{
|
2013-03-27 13:03:15 +01:00
|
|
|
Debugger::DebuggerRunConfigurationAspect *aspect
|
|
|
|
|
= runConfig->extraAspect<Debugger::DebuggerRunConfigurationAspect>();
|
2016-08-05 15:40:33 +02:00
|
|
|
const bool debuggingMode =
|
|
|
|
|
(runMode == ProjectExplorer::Constants::DEBUG_RUN_MODE
|
|
|
|
|
|| runMode == ProjectExplorer::Constants::DEBUG_RUN_MODE_WITH_BREAK_ON_MAIN);
|
2013-03-26 17:03:57 +01:00
|
|
|
m_useCppDebugger = debuggingMode && aspect->useCppDebugger();
|
2015-08-10 17:43:58 +02:00
|
|
|
if (debuggingMode && aspect->useQmlDebugger())
|
|
|
|
|
m_qmlDebugServices = QmlDebug::QmlDebuggerServices;
|
|
|
|
|
else if (runMode == ProjectExplorer::Constants::QML_PROFILER_RUN_MODE)
|
|
|
|
|
m_qmlDebugServices = QmlDebug::QmlProfilerServices;
|
|
|
|
|
else
|
|
|
|
|
m_qmlDebugServices = QmlDebug::NoQmlDebugServices;
|
2013-02-27 15:12:36 +01:00
|
|
|
QString channel = runConfig->remoteChannel();
|
|
|
|
|
QTC_CHECK(channel.startsWith(QLatin1Char(':')));
|
2016-04-19 16:43:30 +02:00
|
|
|
m_localGdbServerPort = Utils::Port(channel.mid(1).toUShort());
|
|
|
|
|
QTC_CHECK(m_localGdbServerPort.isValid());
|
2015-08-10 17:43:58 +02:00
|
|
|
if (m_qmlDebugServices != QmlDebug::NoQmlDebugServices) {
|
2013-04-18 10:01:05 +02:00
|
|
|
QTcpServer server;
|
|
|
|
|
QTC_ASSERT(server.listen(QHostAddress::LocalHost)
|
|
|
|
|
|| server.listen(QHostAddress::LocalHostIPv6),
|
|
|
|
|
qDebug() << tr("No free ports available on host for QML debugging."));
|
2016-04-19 16:43:30 +02:00
|
|
|
m_qmlPort = Utils::Port(server.serverPort());
|
2014-09-15 18:01:48 +02:00
|
|
|
} else {
|
2016-04-19 16:43:30 +02:00
|
|
|
m_qmlPort = Utils::Port();
|
2013-04-18 10:01:05 +02:00
|
|
|
}
|
2013-12-16 20:19:07 +01:00
|
|
|
m_adb = AndroidConfigurations::currentConfig().adbToolPath().toString();
|
2013-02-27 15:12:36 +01:00
|
|
|
|
2016-08-17 11:48:46 +02:00
|
|
|
QString packageDir = "/data/data/" + m_packageName;
|
|
|
|
|
m_pingFile = packageDir + "/debug-ping";
|
|
|
|
|
m_pongFile = "/data/local/tmp/qt/debug-pong-" + m_packageName;
|
|
|
|
|
m_gdbserverSocket = packageDir + "/debug-socket";
|
2016-08-05 15:40:33 +02:00
|
|
|
const QtSupport::BaseQtVersion *version = QtSupport::QtKitInformation::qtVersion(
|
|
|
|
|
runConfig->target()->kit());
|
2014-08-07 10:10:32 +03:00
|
|
|
if (version && version->qtVersion() >= QtSupport::QtVersionNumber(5, 4, 0))
|
2016-08-17 11:48:46 +02:00
|
|
|
m_gdbserverPath = packageDir + "/lib/libgdbserver.so";
|
2014-08-14 16:07:33 +03:00
|
|
|
else
|
2016-08-17 11:48:46 +02:00
|
|
|
m_gdbserverPath = packageDir + "/lib/gdbserver";
|
2014-08-14 16:07:33 +03:00
|
|
|
|
2014-11-18 15:14:40 +01:00
|
|
|
if (version && version->qtVersion() >= QtSupport::QtVersionNumber(5, 4, 0)) {
|
|
|
|
|
if (qEnvironmentVariableIsSet("QTC_ANDROID_USE_FILE_HANDSHAKE"))
|
|
|
|
|
m_handShakeMethod = PingPongFiles;
|
|
|
|
|
} else {
|
|
|
|
|
m_handShakeMethod = PingPongFiles;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (qEnvironmentVariableIsSet("QTC_ANDROID_SOCKET_HANDSHAKE_PORT")) {
|
|
|
|
|
QByteArray envData = qgetenv("QTC_ANDROID_SOCKET_HANDSHAKE_PORT");
|
|
|
|
|
if (!envData.isEmpty()) {
|
|
|
|
|
bool ok = false;
|
|
|
|
|
int port = 0;
|
|
|
|
|
port = envData.toInt(&ok);
|
|
|
|
|
if (ok && port > 0 && port < 65535) {
|
2016-08-05 15:40:33 +02:00
|
|
|
m_socketHandShakePort = port;
|
2014-11-18 15:14:40 +01:00
|
|
|
m_customPort = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-08-05 15:40:33 +02:00
|
|
|
}
|
|
|
|
|
|
2016-11-24 16:33:42 +01:00
|
|
|
AndroidRunnerWorker::~AndroidRunnerWorker()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-05 15:40:33 +02:00
|
|
|
void AndroidRunnerWorker::forceStop()
|
2013-02-27 15:12:36 +01:00
|
|
|
{
|
2016-08-17 11:48:46 +02:00
|
|
|
runAdb(selector() << "shell" << "am" << "force-stop" << m_packageName, nullptr, 30);
|
2012-04-18 20:30:57 +03:00
|
|
|
|
2016-10-10 15:38:50 +03:00
|
|
|
for (auto it = m_processPids.constBegin(); it != m_processPids.constEnd(); ++it) {
|
|
|
|
|
emit pidLost(*it);
|
|
|
|
|
adbKill(*it);
|
2012-04-18 20:30:57 +03:00
|
|
|
}
|
2016-10-10 15:38:50 +03:00
|
|
|
m_processPids.clear();
|
|
|
|
|
m_pidsBuffer.clear();
|
2012-04-18 20:30:57 +03:00
|
|
|
}
|
|
|
|
|
|
2016-08-05 15:40:33 +02:00
|
|
|
void AndroidRunnerWorker::asyncStart(const QString &intentName,
|
|
|
|
|
const QVector<QStringList> &adbCommands)
|
2012-04-18 20:30:57 +03:00
|
|
|
{
|
2016-11-24 16:33:42 +01:00
|
|
|
forceStop();
|
2016-11-07 15:13:02 +02:00
|
|
|
|
2017-02-08 11:41:42 +01:00
|
|
|
// Start the logcat process before app starts.
|
|
|
|
|
std::unique_ptr<QProcess, decltype(&deleter)> logcatProcess(new QProcess, deleter);
|
|
|
|
|
connect(logcatProcess.get(), &QProcess::readyReadStandardOutput,
|
|
|
|
|
this, &AndroidRunnerWorker::logcatReadStandardOutput);
|
|
|
|
|
connect(logcatProcess.get(), &QProcess::readyReadStandardError,
|
|
|
|
|
this, &AndroidRunnerWorker::logcatReadStandardError);
|
2016-10-10 15:38:50 +03:00
|
|
|
|
2017-02-08 11:41:42 +01:00
|
|
|
// Its assumed that the device or avd returned by selector() is online.
|
2016-10-10 15:38:50 +03:00
|
|
|
QStringList logcatArgs = selector() << "logcat" << "-v" << "time";
|
|
|
|
|
if (deviceSdkVersion() > 20)
|
|
|
|
|
logcatArgs << "-T" << "0";
|
|
|
|
|
logcatProcess->start(m_adb, logcatArgs);
|
2016-06-08 16:57:32 +02:00
|
|
|
|
2016-04-29 16:52:58 +02:00
|
|
|
QString errorMessage;
|
2013-02-27 15:12:36 +01:00
|
|
|
|
2016-04-29 16:52:58 +02:00
|
|
|
if (m_useCppDebugger)
|
2016-08-17 11:48:46 +02:00
|
|
|
runAdb(selector() << "shell" << "rm" << m_pongFile); // Remove pong file.
|
2013-02-27 15:12:36 +01:00
|
|
|
|
2016-08-05 15:40:33 +02:00
|
|
|
foreach (const QStringList &entry, adbCommands)
|
2016-04-29 16:52:58 +02:00
|
|
|
runAdb(selector() << entry);
|
2016-01-29 14:05:00 +02:00
|
|
|
|
2013-02-27 15:12:36 +01:00
|
|
|
QStringList args = selector();
|
2016-08-17 11:48:46 +02:00
|
|
|
args << "shell" << "am" << "start" << "-n" << intentName;
|
2013-02-27 15:12:36 +01:00
|
|
|
|
|
|
|
|
if (m_useCppDebugger) {
|
2016-08-17 11:48:46 +02:00
|
|
|
if (!runAdb(selector() << "forward"
|
2016-04-29 16:52:58 +02:00
|
|
|
<< QString::fromLatin1("tcp:%1").arg(m_localGdbServerPort.number())
|
2016-08-17 11:48:46 +02:00
|
|
|
<< "localfilesystem:" + m_gdbserverSocket, &errorMessage)) {
|
2016-04-29 16:52:58 +02:00
|
|
|
emit remoteProcessFinished(tr("Failed to forward C++ debugging ports. Reason: %1.").arg(errorMessage));
|
2012-04-18 20:30:57 +03:00
|
|
|
return;
|
|
|
|
|
}
|
2013-02-27 15:12:36 +01:00
|
|
|
|
2016-08-17 11:48:46 +02:00
|
|
|
const QString pingPongSocket(m_packageName + ".ping_pong_socket");
|
|
|
|
|
args << "-e" << "debug_ping" << "true";
|
2014-11-18 15:14:40 +01:00
|
|
|
if (m_handShakeMethod == SocketHandShake) {
|
2016-08-17 11:48:46 +02:00
|
|
|
args << "-e" << "ping_socket" << pingPongSocket;
|
2014-11-18 15:14:40 +01:00
|
|
|
} else if (m_handShakeMethod == PingPongFiles) {
|
2016-08-17 11:48:46 +02:00
|
|
|
args << "-e" << "ping_file" << m_pingFile;
|
|
|
|
|
args << "-e" << "pong_file" << m_pongFile;
|
2014-11-18 15:14:40 +01:00
|
|
|
}
|
2015-09-23 09:25:50 +02:00
|
|
|
|
|
|
|
|
QString gdbserverCommand = QString::fromLatin1(adbShellAmNeedsQuotes() ? "\"%1 --multi +%2\"" : "%1 --multi +%2")
|
|
|
|
|
.arg(m_gdbserverPath).arg(m_gdbserverSocket);
|
2016-08-17 11:48:46 +02:00
|
|
|
args << "-e" << "gdbserver_command" << gdbserverCommand;
|
|
|
|
|
args << "-e" << "gdbserver_socket" << m_gdbserverSocket;
|
2014-11-18 15:14:40 +01:00
|
|
|
|
|
|
|
|
if (m_handShakeMethod == SocketHandShake) {
|
2016-08-05 15:40:33 +02:00
|
|
|
const QString port = QString::fromLatin1("tcp:%1").arg(m_socketHandShakePort);
|
2016-08-17 11:48:46 +02:00
|
|
|
if (!runAdb(selector() << "forward" << port << ("localabstract:" + pingPongSocket),
|
2016-08-05 15:40:33 +02:00
|
|
|
&errorMessage)) {
|
|
|
|
|
emit remoteProcessFinished(tr("Failed to forward ping pong ports. Reason: %1.")
|
|
|
|
|
.arg(errorMessage));
|
2014-11-18 15:14:40 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-04-18 20:30:57 +03:00
|
|
|
}
|
2013-05-03 12:41:58 +02:00
|
|
|
|
2015-08-10 17:43:58 +02:00
|
|
|
if (m_qmlDebugServices != QmlDebug::NoQmlDebugServices) {
|
2013-02-27 15:12:36 +01:00
|
|
|
// currently forward to same port on device and host
|
2016-04-19 16:43:30 +02:00
|
|
|
const QString port = QString::fromLatin1("tcp:%1").arg(m_qmlPort.number());
|
2016-08-17 11:48:46 +02:00
|
|
|
if (!runAdb(selector() << "forward" << port << port, &errorMessage)) {
|
2016-08-05 15:40:33 +02:00
|
|
|
emit remoteProcessFinished(tr("Failed to forward QML debugging ports. Reason: %1.")
|
|
|
|
|
.arg(errorMessage));
|
2012-06-24 19:32:45 -07:00
|
|
|
return;
|
|
|
|
|
}
|
2015-08-10 17:43:58 +02:00
|
|
|
|
2016-08-17 11:48:46 +02:00
|
|
|
args << "-e" << "qml_debug" << "true"
|
|
|
|
|
<< "-e" << "qmljsdebugger"
|
2015-08-10 17:43:58 +02:00
|
|
|
<< QString::fromLatin1("port:%1,block,services:%2")
|
2016-04-19 16:43:30 +02:00
|
|
|
.arg(m_qmlPort.number()).arg(QmlDebug::qmlDebugServices(m_qmlDebugServices));
|
2012-06-24 19:32:45 -07:00
|
|
|
}
|
2012-04-18 20:30:57 +03:00
|
|
|
|
2016-04-29 16:52:58 +02:00
|
|
|
if (!runAdb(args, &errorMessage)) {
|
2016-08-05 15:40:33 +02:00
|
|
|
emit remoteProcessFinished(tr("Failed to start the activity. Reason: %1.")
|
|
|
|
|
.arg(errorMessage));
|
2012-04-18 20:30:57 +03:00
|
|
|
return;
|
|
|
|
|
}
|
2013-02-27 15:12:36 +01:00
|
|
|
|
2013-04-18 10:01:05 +02:00
|
|
|
if (m_useCppDebugger) {
|
2014-11-18 15:14:40 +01:00
|
|
|
if (m_handShakeMethod == SocketHandShake) {
|
|
|
|
|
//Handling socket
|
|
|
|
|
bool wasSuccess = false;
|
|
|
|
|
const int maxAttempts = 20; //20 seconds
|
2016-08-05 15:40:33 +02:00
|
|
|
m_socket.reset(new QTcpSocket());
|
2014-11-18 15:14:40 +01:00
|
|
|
for (int i = 0; i < maxAttempts; i++) {
|
|
|
|
|
|
|
|
|
|
QThread::sleep(1); // give Android time to start process
|
2016-08-05 15:40:33 +02:00
|
|
|
m_socket->connectToHost(QHostAddress(QStringLiteral("127.0.0.1")),
|
|
|
|
|
m_socketHandShakePort);
|
2014-11-18 15:14:40 +01:00
|
|
|
if (!m_socket->waitForConnected())
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (!m_socket->waitForReadyRead()) {
|
|
|
|
|
m_socket->close();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QByteArray pid = m_socket->readLine();
|
|
|
|
|
if (pid.isEmpty()) {
|
|
|
|
|
m_socket->close();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wasSuccess = true;
|
2013-02-27 15:12:36 +01:00
|
|
|
|
|
|
|
|
break;
|
2014-11-18 15:14:40 +01:00
|
|
|
}
|
2013-02-27 15:12:36 +01:00
|
|
|
|
2014-11-18 15:14:40 +01:00
|
|
|
if (!m_customPort) {
|
|
|
|
|
// increment running port to avoid clash when using multiple
|
|
|
|
|
// debug sessions at the same time
|
2016-08-05 15:40:33 +02:00
|
|
|
m_socketHandShakePort++;
|
2014-11-18 15:14:40 +01:00
|
|
|
// wrap ports around to avoid overflow
|
2016-08-05 15:40:33 +02:00
|
|
|
if (m_socketHandShakePort == MAX_SOCKET_HANDSHAKE_PORT)
|
|
|
|
|
m_socketHandShakePort = MIN_SOCKET_HANDSHAKE_PORT;
|
2014-11-18 15:14:40 +01:00
|
|
|
}
|
2017-02-08 11:41:42 +01:00
|
|
|
|
|
|
|
|
if (!wasSuccess) {
|
|
|
|
|
emit remoteProcessFinished(tr("Failed to contact debugging port."));
|
|
|
|
|
return;
|
|
|
|
|
}
|
2014-11-18 15:14:40 +01:00
|
|
|
} else {
|
|
|
|
|
// Handling ping.
|
|
|
|
|
for (int i = 0; ; ++i) {
|
2017-01-19 16:44:22 +01:00
|
|
|
Utils::TemporaryFile tmp("pingpong");
|
2014-11-18 15:14:40 +01:00
|
|
|
tmp.open();
|
|
|
|
|
tmp.close();
|
|
|
|
|
|
2016-08-17 11:48:46 +02:00
|
|
|
runAdb(selector() << "pull" << m_pingFile << tmp.fileName());
|
2014-11-18 15:14:40 +01:00
|
|
|
|
|
|
|
|
QFile res(tmp.fileName());
|
|
|
|
|
const bool doBreak = res.size();
|
|
|
|
|
res.remove();
|
|
|
|
|
if (doBreak)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
if (i == 20) {
|
2016-08-05 15:40:33 +02:00
|
|
|
emit remoteProcessFinished(tr("Unable to start \"%1\".").arg(m_packageName));
|
2014-11-18 15:14:40 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
qDebug() << "WAITING FOR " << tmp.fileName();
|
|
|
|
|
QThread::msleep(500);
|
2013-02-27 15:12:36 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-04-18 20:30:57 +03:00
|
|
|
}
|
2017-02-08 11:41:42 +01:00
|
|
|
|
|
|
|
|
QTC_ASSERT(!m_adbLogcatProcess, /**/);
|
|
|
|
|
m_adbLogcatProcess = std::move(logcatProcess);
|
2016-10-10 15:38:50 +03:00
|
|
|
|
|
|
|
|
m_timeoutTimer.reset(new QTimer);
|
|
|
|
|
m_timeoutTimer->setSingleShot(true);
|
|
|
|
|
connect(m_timeoutTimer.data(), &QTimer::timeout,
|
|
|
|
|
this,[this] { onProcessIdChanged(PidInfo{}); });
|
|
|
|
|
m_timeoutTimer->start(APP_START_TIMEOUT);
|
|
|
|
|
|
|
|
|
|
m_pidsFinderProcess.reset(new QProcess);
|
|
|
|
|
m_pidsFinderProcess->setProcessChannelMode(QProcess::MergedChannels);
|
|
|
|
|
connect(m_pidsFinderProcess.get(), &QProcess::readyRead, this, &AndroidRunnerWorker::findProcessPids);
|
|
|
|
|
connect(m_pidsFinderProcess.get(),
|
|
|
|
|
static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
|
|
|
|
|
this, [this] { onProcessIdChanged(PidInfo{}); });
|
|
|
|
|
m_pidsFinderProcess->start(m_adb, selector() << "shell" << pidScript);
|
2012-04-18 20:30:57 +03:00
|
|
|
}
|
|
|
|
|
|
2016-08-05 15:40:33 +02:00
|
|
|
bool AndroidRunnerWorker::adbShellAmNeedsQuotes()
|
2015-09-23 09:25:50 +02:00
|
|
|
{
|
|
|
|
|
// Between Android SDK Tools version 24.3.1 and 24.3.4 the quoting
|
|
|
|
|
// needs for the 'adb shell am start ...' parameters changed.
|
|
|
|
|
// Run a test to find out on what side of the fence we live.
|
|
|
|
|
// The command will fail with a complaint about the "--dummy"
|
|
|
|
|
// option on newer SDKs, and with "No intent supplied" on older ones.
|
|
|
|
|
// In case the test itself fails assume a new SDK.
|
2016-04-29 16:52:58 +02:00
|
|
|
Utils::SynchronousProcess adb;
|
|
|
|
|
adb.setTimeoutS(10);
|
|
|
|
|
Utils::SynchronousProcessResponse response
|
2016-08-17 11:48:46 +02:00
|
|
|
= adb.run(m_adb, selector() << "shell" << "am" << "start"
|
|
|
|
|
<< "-e" << "dummy" << "dummy --dummy");
|
2016-04-29 16:52:58 +02:00
|
|
|
if (response.result == Utils::SynchronousProcessResponse::StartFailed
|
|
|
|
|
|| response.result != Utils::SynchronousProcessResponse::Finished)
|
2015-09-23 09:25:50 +02:00
|
|
|
return true;
|
|
|
|
|
|
2016-04-29 16:52:58 +02:00
|
|
|
const QString output = response.allOutput();
|
2016-06-10 11:39:43 +03:00
|
|
|
const bool oldSdk = output.contains("Error: No intent supplied");
|
|
|
|
|
return !oldSdk;
|
2016-04-29 16:52:58 +02:00
|
|
|
}
|
2015-09-23 09:25:50 +02:00
|
|
|
|
2016-08-05 15:40:33 +02:00
|
|
|
bool AndroidRunnerWorker::runAdb(const QStringList &args, QString *exitMessage, int timeoutS)
|
2016-04-29 16:52:58 +02:00
|
|
|
{
|
|
|
|
|
Utils::SynchronousProcess adb;
|
|
|
|
|
adb.setTimeoutS(timeoutS);
|
|
|
|
|
Utils::SynchronousProcessResponse response
|
|
|
|
|
= adb.run(m_adb, args);
|
2016-08-05 15:40:33 +02:00
|
|
|
if (exitMessage)
|
|
|
|
|
*exitMessage = response.exitMessage(m_adb, timeoutS);
|
2016-04-29 16:52:58 +02:00
|
|
|
return response.result == Utils::SynchronousProcessResponse::Finished;
|
2015-09-23 09:25:50 +02:00
|
|
|
}
|
|
|
|
|
|
2016-10-10 15:38:50 +03:00
|
|
|
int AndroidRunnerWorker::deviceSdkVersion()
|
|
|
|
|
{
|
|
|
|
|
Utils::SynchronousProcess adb;
|
|
|
|
|
adb.setTimeoutS(10);
|
|
|
|
|
Utils::SynchronousProcessResponse response
|
|
|
|
|
= adb.run(m_adb, selector() << "shell" << "getprop" << "ro.build.version.sdk");
|
|
|
|
|
if (response.result == Utils::SynchronousProcessResponse::StartFailed
|
|
|
|
|
|| response.result != Utils::SynchronousProcessResponse::Finished)
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
return response.allOutput().trimmed().toInt();
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-05 15:40:33 +02:00
|
|
|
void AndroidRunnerWorker::handleRemoteDebuggerRunning()
|
2012-04-18 20:30:57 +03:00
|
|
|
{
|
2013-04-18 10:01:05 +02:00
|
|
|
if (m_useCppDebugger) {
|
2014-11-18 15:14:40 +01:00
|
|
|
if (m_handShakeMethod == SocketHandShake) {
|
|
|
|
|
m_socket->write("OK");
|
|
|
|
|
m_socket->waitForBytesWritten();
|
|
|
|
|
m_socket->close();
|
|
|
|
|
} else {
|
2017-01-19 16:44:22 +01:00
|
|
|
Utils::TemporaryFile tmp("pingpong");
|
2014-11-18 15:14:40 +01:00
|
|
|
tmp.open();
|
2013-02-27 15:12:36 +01:00
|
|
|
|
2016-08-17 11:48:46 +02:00
|
|
|
runAdb(selector() << "push" << tmp.fileName() << m_pongFile);
|
2014-11-18 15:14:40 +01:00
|
|
|
}
|
2016-10-10 15:38:50 +03:00
|
|
|
QTC_CHECK(!m_processPids.isEmpty());
|
2013-04-18 10:01:05 +02:00
|
|
|
}
|
|
|
|
|
emit remoteProcessStarted(m_localGdbServerPort, m_qmlPort);
|
2012-04-18 20:30:57 +03:00
|
|
|
}
|
|
|
|
|
|
2016-10-10 15:38:50 +03:00
|
|
|
void AndroidRunnerWorker::findProcessPids()
|
2012-04-18 20:30:57 +03:00
|
|
|
{
|
2016-10-10 15:38:50 +03:00
|
|
|
static QMap<qint64, QByteArray> extractedPids;
|
|
|
|
|
static auto oldPids = m_processPids;
|
2016-11-24 16:33:42 +01:00
|
|
|
|
2016-10-10 15:38:50 +03:00
|
|
|
m_pidsBuffer += m_pidsFinderProcess->readAll();
|
|
|
|
|
while (!m_pidsBuffer.isEmpty()) {
|
|
|
|
|
const int to = m_pidsBuffer.indexOf('\n');
|
|
|
|
|
if (to < 0)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
if (to == 0) {
|
|
|
|
|
m_pidsBuffer = m_pidsBuffer.mid(1);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// = is used to delimit ps outputs
|
|
|
|
|
// is needed to know when an existins PID is killed
|
|
|
|
|
if (m_pidsBuffer[0] != '=') {
|
|
|
|
|
QByteArray tuple = m_pidsBuffer.left(to + 1).simplified();
|
|
|
|
|
QList<QByteArray> parts = tuple.split(':');
|
|
|
|
|
QByteArray commandName = parts.takeFirst();
|
|
|
|
|
if (QString::fromLocal8Bit(commandName) == m_packageName) {
|
|
|
|
|
auto pid = parts.last().toLongLong();
|
|
|
|
|
if (!m_processPids.contains(pid)) {
|
|
|
|
|
extractedPids[pid] = commandName + (parts.length() == 2
|
|
|
|
|
? ":" + parts.first() : QByteArray{});
|
|
|
|
|
} else {
|
|
|
|
|
oldPids.remove(pid);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Add new PIDs
|
|
|
|
|
for (auto it = extractedPids.constBegin(); it != extractedPids.constEnd(); ++it) {
|
|
|
|
|
onProcessIdChanged(PidInfo(it.key(), PidStatus::Found,
|
|
|
|
|
QString::fromLocal8Bit(it.value())));
|
|
|
|
|
}
|
|
|
|
|
extractedPids.clear();
|
|
|
|
|
|
|
|
|
|
// Remove the dead ones
|
|
|
|
|
for (auto it = oldPids.constBegin(); it != oldPids.constEnd(); ++it)
|
|
|
|
|
onProcessIdChanged(PidInfo(*it, PidStatus::Lost));
|
|
|
|
|
|
|
|
|
|
// Save the current non dead PIDs
|
|
|
|
|
oldPids = m_processPids;
|
|
|
|
|
if (m_processPids.isEmpty()) {
|
|
|
|
|
extractedPids.clear();
|
|
|
|
|
m_pidsBuffer.clear();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
m_pidsBuffer = m_pidsBuffer.mid(to + 1);
|
2012-12-18 14:25:07 +02:00
|
|
|
}
|
2016-10-10 15:38:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidRunnerWorker::asyncStop(const QVector<QStringList> &adbCommands)
|
|
|
|
|
{
|
|
|
|
|
m_timeoutTimer.reset();
|
|
|
|
|
m_pidsFinderProcess.reset();
|
|
|
|
|
if (!m_processPids.isEmpty())
|
|
|
|
|
forceStop();
|
|
|
|
|
|
2016-08-05 15:40:33 +02:00
|
|
|
foreach (const QStringList &entry, adbCommands)
|
2016-04-29 16:52:58 +02:00
|
|
|
runAdb(selector() << entry);
|
2016-10-10 15:38:50 +03:00
|
|
|
|
|
|
|
|
m_adbLogcatProcess.reset();
|
|
|
|
|
emit remoteProcessFinished(QLatin1String("\n\n") +
|
|
|
|
|
tr("\"%1\" terminated.").arg(m_packageName));
|
2016-08-05 15:40:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidRunnerWorker::setAdbParameters(const QString &packageName, const QStringList &selector)
|
|
|
|
|
{
|
|
|
|
|
m_packageName = packageName;
|
|
|
|
|
m_selector = selector;
|
2012-04-18 20:30:57 +03:00
|
|
|
}
|
|
|
|
|
|
2016-08-05 15:40:33 +02:00
|
|
|
void AndroidRunnerWorker::logcatProcess(const QByteArray &text, QByteArray &buffer, bool onlyError)
|
2012-04-18 20:30:57 +03:00
|
|
|
{
|
2014-03-13 17:33:47 +01:00
|
|
|
QList<QByteArray> lines = text.split('\n');
|
2014-02-13 12:55:19 +01:00
|
|
|
// lines always contains at least one item
|
2014-03-13 17:33:47 +01:00
|
|
|
lines[0].prepend(buffer);
|
2014-02-13 12:55:19 +01:00
|
|
|
if (!lines.last().endsWith('\n')) {
|
|
|
|
|
// incomplete line
|
2014-03-13 17:33:47 +01:00
|
|
|
buffer = lines.last();
|
2014-02-13 12:55:19 +01:00
|
|
|
lines.removeLast();
|
|
|
|
|
} else {
|
2014-03-13 17:33:47 +01:00
|
|
|
buffer.clear();
|
2014-02-13 12:55:19 +01:00
|
|
|
}
|
|
|
|
|
|
2015-06-12 15:36:04 +02:00
|
|
|
foreach (const QByteArray &msg, lines) {
|
2016-10-10 15:38:50 +03:00
|
|
|
const QString line = QString::fromUtf8(msg.trimmed());
|
|
|
|
|
if (onlyError)
|
|
|
|
|
emit remoteErrorOutput(line);
|
|
|
|
|
else
|
|
|
|
|
emit remoteOutput(line);
|
2012-04-18 20:30:57 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-10 15:38:50 +03:00
|
|
|
void AndroidRunnerWorker::onProcessIdChanged(PidInfo pidInfo)
|
2016-11-24 16:33:42 +01:00
|
|
|
{
|
|
|
|
|
// Don't write to m_psProc from a different thread
|
|
|
|
|
QTC_ASSERT(QThread::currentThread() == thread(), return);
|
2016-10-10 15:38:50 +03:00
|
|
|
|
|
|
|
|
auto isFirst = m_processPids.isEmpty();
|
|
|
|
|
if (pidInfo.status == PidStatus::Lost) {
|
|
|
|
|
m_processPids.remove(pidInfo.pid);
|
|
|
|
|
emit pidLost(pidInfo.pid);
|
|
|
|
|
} else {
|
|
|
|
|
m_processPids.insert(pidInfo.pid);
|
|
|
|
|
emit pidFound(pidInfo.pid, pidInfo.name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_processPids.isEmpty() || pidInfo.pid == -1) {
|
2017-02-08 11:41:42 +01:00
|
|
|
emit remoteProcessFinished(QLatin1String("\n\n") + tr("\"%1\" died.")
|
|
|
|
|
.arg(m_packageName));
|
|
|
|
|
// App died/killed. Reset log and monitor processes.
|
2016-10-10 15:38:50 +03:00
|
|
|
forceStop();
|
2017-02-08 11:41:42 +01:00
|
|
|
m_adbLogcatProcess.reset();
|
2016-10-10 15:38:50 +03:00
|
|
|
m_timeoutTimer.reset();
|
|
|
|
|
} else if (isFirst) {
|
|
|
|
|
m_timeoutTimer.reset();
|
2016-11-24 16:33:42 +01:00
|
|
|
if (m_useCppDebugger) {
|
|
|
|
|
// This will be funneled to the engine to actually start and attach
|
|
|
|
|
// gdb. Afterwards this ends up in handleRemoteDebuggerRunning() below.
|
|
|
|
|
QByteArray serverChannel = ':' + QByteArray::number(m_localGdbServerPort.number());
|
2016-10-10 15:38:50 +03:00
|
|
|
emit remoteServerRunning(serverChannel, pidInfo.pid);
|
2016-11-24 16:33:42 +01:00
|
|
|
} else if (m_qmlDebugServices == QmlDebug::QmlDebuggerServices) {
|
|
|
|
|
// This will be funneled to the engine to actually start and attach
|
|
|
|
|
// gdb. Afterwards this ends up in handleRemoteDebuggerRunning() below.
|
|
|
|
|
QByteArray serverChannel = QByteArray::number(m_qmlPort.number());
|
2016-10-10 15:38:50 +03:00
|
|
|
emit remoteServerRunning(serverChannel, pidInfo.pid);
|
2016-11-24 16:33:42 +01:00
|
|
|
} else if (m_qmlDebugServices == QmlDebug::QmlProfilerServices) {
|
|
|
|
|
emit remoteProcessStarted(Utils::Port(), m_qmlPort);
|
|
|
|
|
} else {
|
|
|
|
|
// Start without debugging.
|
|
|
|
|
emit remoteProcessStarted(Utils::Port(), Utils::Port());
|
|
|
|
|
}
|
|
|
|
|
logcatReadStandardOutput();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-05 15:40:33 +02:00
|
|
|
void AndroidRunnerWorker::logcatReadStandardError()
|
2014-03-13 17:33:47 +01:00
|
|
|
{
|
2016-10-10 15:38:50 +03:00
|
|
|
if (!m_processPids.isEmpty() && m_adbLogcatProcess)
|
2016-08-05 15:40:33 +02:00
|
|
|
logcatProcess(m_adbLogcatProcess->readAllStandardError(), m_stderrBuffer, true);
|
2014-03-13 17:33:47 +01:00
|
|
|
}
|
|
|
|
|
|
2016-08-05 15:40:33 +02:00
|
|
|
void AndroidRunnerWorker::logcatReadStandardOutput()
|
2014-03-13 17:33:47 +01:00
|
|
|
{
|
2016-10-10 15:38:50 +03:00
|
|
|
if (!m_processPids.isEmpty() && m_adbLogcatProcess)
|
2016-08-05 15:40:33 +02:00
|
|
|
logcatProcess(m_adbLogcatProcess->readAllStandardOutput(), m_stdoutBuffer, false);
|
2014-03-13 17:33:47 +01:00
|
|
|
}
|
|
|
|
|
|
2016-08-05 15:40:33 +02:00
|
|
|
void AndroidRunnerWorker::adbKill(qint64 pid)
|
2012-04-18 20:30:57 +03:00
|
|
|
{
|
2016-08-17 11:48:46 +02:00
|
|
|
runAdb(selector() << "shell" << "kill" << "-9" << QString::number(pid));
|
|
|
|
|
runAdb(selector() << "shell" << "run-as" << m_packageName
|
|
|
|
|
<< "kill" << "-9" << QString::number(pid));
|
2012-04-18 20:30:57 +03:00
|
|
|
}
|
|
|
|
|
|
2016-08-05 15:40:33 +02:00
|
|
|
AndroidRunner::AndroidRunner(QObject *parent, AndroidRunConfiguration *runConfig, Core::Id runMode)
|
|
|
|
|
: QObject(parent), m_runConfig(runConfig)
|
|
|
|
|
{
|
|
|
|
|
static const int metaTypes[] = {
|
|
|
|
|
qRegisterMetaType<QVector<QStringList> >("QVector<QStringList>"),
|
|
|
|
|
qRegisterMetaType<Utils::Port>("Utils::Port")
|
|
|
|
|
};
|
|
|
|
|
Q_UNUSED(metaTypes);
|
|
|
|
|
|
|
|
|
|
m_checkAVDTimer.setInterval(2000);
|
|
|
|
|
connect(&m_checkAVDTimer, &QTimer::timeout, this, &AndroidRunner::checkAVD);
|
|
|
|
|
|
|
|
|
|
Target *target = runConfig->target();
|
|
|
|
|
m_androidRunnable.intentName = AndroidManager::intentName(target);
|
|
|
|
|
m_androidRunnable.packageName = m_androidRunnable.intentName.left(
|
|
|
|
|
m_androidRunnable.intentName.indexOf(QLatin1Char('/')));
|
|
|
|
|
m_androidRunnable.deviceSerialNumber = AndroidManager::deviceSerialNumber(target);
|
|
|
|
|
|
|
|
|
|
m_worker.reset(new AndroidRunnerWorker(
|
|
|
|
|
runConfig, runMode, m_androidRunnable.packageName,
|
|
|
|
|
AndroidDeviceInfo::adbSelector(m_androidRunnable.deviceSerialNumber)));
|
|
|
|
|
m_worker->moveToThread(&m_thread);
|
|
|
|
|
|
|
|
|
|
connect(this, &AndroidRunner::asyncStart, m_worker.data(), &AndroidRunnerWorker::asyncStart);
|
|
|
|
|
connect(this, &AndroidRunner::asyncStop, m_worker.data(), &AndroidRunnerWorker::asyncStop);
|
|
|
|
|
connect(this, &AndroidRunner::adbParametersChanged,
|
|
|
|
|
m_worker.data(), &AndroidRunnerWorker::setAdbParameters);
|
|
|
|
|
connect(this, &AndroidRunner::remoteDebuggerRunning, m_worker.data(),
|
|
|
|
|
&AndroidRunnerWorker::handleRemoteDebuggerRunning);
|
|
|
|
|
|
|
|
|
|
connect(m_worker.data(), &AndroidRunnerWorker::remoteServerRunning,
|
|
|
|
|
this, &AndroidRunner::remoteServerRunning);
|
|
|
|
|
connect(m_worker.data(), &AndroidRunnerWorker::remoteProcessStarted,
|
|
|
|
|
this, &AndroidRunner::remoteProcessStarted);
|
|
|
|
|
connect(m_worker.data(), &AndroidRunnerWorker::remoteProcessFinished,
|
|
|
|
|
this, &AndroidRunner::remoteProcessFinished);
|
|
|
|
|
connect(m_worker.data(), &AndroidRunnerWorker::remoteOutput,
|
|
|
|
|
this, &AndroidRunner::remoteOutput);
|
|
|
|
|
connect(m_worker.data(), &AndroidRunnerWorker::remoteErrorOutput,
|
|
|
|
|
this, &AndroidRunner::remoteErrorOutput);
|
2016-10-10 15:38:50 +03:00
|
|
|
connect(m_worker.data(), &AndroidRunnerWorker::pidFound,
|
|
|
|
|
this, &AndroidRunner::pidFound);
|
|
|
|
|
connect(m_worker.data(), &AndroidRunnerWorker::pidLost,
|
|
|
|
|
this, &AndroidRunner::pidLost);
|
2016-08-05 15:40:33 +02:00
|
|
|
|
|
|
|
|
m_thread.start();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AndroidRunner::~AndroidRunner()
|
|
|
|
|
{
|
|
|
|
|
m_thread.quit();
|
|
|
|
|
m_thread.wait();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidRunner::start()
|
|
|
|
|
{
|
|
|
|
|
if (!ProjectExplorerPlugin::projectExplorerSettings().deployBeforeRun) {
|
|
|
|
|
// User choose to run the app without deployment. Start the AVD if not running.
|
|
|
|
|
launchAVD();
|
|
|
|
|
if (!m_launchedAVDName.isEmpty()) {
|
|
|
|
|
m_checkAVDTimer.start();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
emit asyncStart(m_androidRunnable.intentName, m_androidRunnable.beforeStartADBCommands);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidRunner::stop()
|
|
|
|
|
{
|
|
|
|
|
if (m_checkAVDTimer.isActive()) {
|
|
|
|
|
m_checkAVDTimer.stop();
|
|
|
|
|
emit remoteProcessFinished(QLatin1String("\n\n") + tr("\"%1\" terminated.")
|
|
|
|
|
.arg(m_androidRunnable.packageName));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
emit asyncStop(m_androidRunnable.afterFinishADBCommands);
|
|
|
|
|
}
|
|
|
|
|
|
2012-04-18 20:30:57 +03:00
|
|
|
QString AndroidRunner::displayName() const
|
|
|
|
|
{
|
2016-01-29 14:05:00 +02:00
|
|
|
return m_androidRunnable.packageName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidRunner::setRunnable(const AndroidRunnable &runnable)
|
|
|
|
|
{
|
2016-08-05 15:40:33 +02:00
|
|
|
if (runnable != m_androidRunnable) {
|
|
|
|
|
m_androidRunnable = runnable;
|
|
|
|
|
emit adbParametersChanged(runnable.packageName,
|
|
|
|
|
AndroidDeviceInfo::adbSelector(runnable.deviceSerialNumber));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidRunner::launchAVD()
|
|
|
|
|
{
|
|
|
|
|
if (!m_runConfig->target() && !m_runConfig->target()->project())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
int deviceAPILevel = AndroidManager::minimumSDK(m_runConfig->target());
|
|
|
|
|
QString targetArch = AndroidManager::targetArch(m_runConfig->target());
|
|
|
|
|
|
|
|
|
|
// Get AVD info.
|
|
|
|
|
AndroidDeviceInfo info = AndroidConfigurations::showDeviceDialog(
|
|
|
|
|
m_runConfig->target()->project(), deviceAPILevel, targetArch,
|
|
|
|
|
AndroidConfigurations::None);
|
|
|
|
|
AndroidManager::setDeviceSerialNumber(m_runConfig->target(), info.serialNumber);
|
|
|
|
|
m_androidRunnable.deviceSerialNumber = info.serialNumber;
|
|
|
|
|
emit adbParametersChanged(m_androidRunnable.packageName,
|
|
|
|
|
AndroidDeviceInfo::adbSelector(info.serialNumber));
|
|
|
|
|
if (info.isValid()) {
|
2017-04-03 11:11:17 +02:00
|
|
|
AndroidAvdManager avdManager;
|
|
|
|
|
if (avdManager.findAvd(info.avdname).isEmpty()) {
|
|
|
|
|
bool launched = avdManager.startAvdAsync(info.avdname);
|
2016-08-05 15:40:33 +02:00
|
|
|
m_launchedAVDName = launched ? info.avdname:"";
|
|
|
|
|
} else {
|
|
|
|
|
m_launchedAVDName.clear();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidRunner::checkAVD()
|
|
|
|
|
{
|
|
|
|
|
const AndroidConfig &config = AndroidConfigurations::currentConfig();
|
2017-04-03 11:11:17 +02:00
|
|
|
AndroidAvdManager avdManager(config);
|
|
|
|
|
QString serialNumber = avdManager.findAvd(m_launchedAVDName);
|
2016-08-05 15:40:33 +02:00
|
|
|
if (!serialNumber.isEmpty())
|
|
|
|
|
return; // try again on next timer hit
|
|
|
|
|
|
2017-04-03 11:11:17 +02:00
|
|
|
if (avdManager.isAvdBooted(serialNumber)) {
|
2016-08-05 15:40:33 +02:00
|
|
|
m_checkAVDTimer.stop();
|
|
|
|
|
AndroidManager::setDeviceSerialNumber(m_runConfig->target(), serialNumber);
|
|
|
|
|
emit asyncStart(m_androidRunnable.intentName, m_androidRunnable.beforeStartADBCommands);
|
|
|
|
|
} else if (!config.isConnected(serialNumber)) {
|
|
|
|
|
// device was disconnected
|
|
|
|
|
m_checkAVDTimer.stop();
|
|
|
|
|
}
|
2012-04-18 20:30:57 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace Internal
|
2013-10-16 11:02:37 +02:00
|
|
|
} // namespace Android
|
2016-08-05 15:40:33 +02:00
|
|
|
|
|
|
|
|
#include "androidrunner.moc"
|