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"
|
|
|
|
|
|
2013-03-27 13:03:15 +01:00
|
|
|
#include <debugger/debuggerrunconfigurationaspect.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>
|
2012-04-18 20:30:57 +03:00
|
|
|
|
2014-11-18 15:14:40 +01:00
|
|
|
#include <QApplication>
|
2014-08-12 09:06:16 +02:00
|
|
|
#include <QDir>
|
2012-04-18 20:30:57 +03:00
|
|
|
#include <QTime>
|
2013-02-27 15:12:36 +01:00
|
|
|
#include <QTemporaryFile>
|
2013-04-18 10:01:05 +02:00
|
|
|
#include <QTcpServer>
|
2014-11-18 15:14:40 +01:00
|
|
|
#include <QTcpSocket>
|
2012-04-18 20:30:57 +03:00
|
|
|
|
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 {
|
|
|
|
|
|
2013-02-27 15:12:36 +01:00
|
|
|
typedef QLatin1String _;
|
2014-11-18 15:14:40 +01:00
|
|
|
const int MIN_SOCKET_HANDSHAKE_PORT = 20001;
|
|
|
|
|
const int MAX_SOCKET_HANDSHAKE_PORT = 20999;
|
|
|
|
|
|
|
|
|
|
static int socketHandShakePort = MIN_SOCKET_HANDSHAKE_PORT;
|
2013-02-27 15:12:36 +01:00
|
|
|
|
2013-05-03 12:41:58 +02:00
|
|
|
AndroidRunner::AndroidRunner(QObject *parent,
|
|
|
|
|
AndroidRunConfiguration *runConfig,
|
2015-06-29 10:36:29 +03:00
|
|
|
Core::Id runMode)
|
2014-11-18 15:14:40 +01:00
|
|
|
: QThread(parent), m_handShakeMethod(SocketHandShake), m_socket(0),
|
|
|
|
|
m_customPort(false)
|
2012-04-18 20:30:57 +03:00
|
|
|
{
|
2014-02-12 12:24:56 +01:00
|
|
|
m_tries = 0;
|
2013-03-27 13:03:15 +01:00
|
|
|
Debugger::DebuggerRunConfigurationAspect *aspect
|
|
|
|
|
= runConfig->extraAspect<Debugger::DebuggerRunConfigurationAspect>();
|
2015-06-29 10:36:29 +03: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
|
|
|
}
|
2012-04-24 15:49:09 +02:00
|
|
|
ProjectExplorer::Target *target = runConfig->target();
|
2016-01-29 14:05:00 +02:00
|
|
|
m_androidRunnable.intentName = AndroidManager::intentName(target);
|
|
|
|
|
m_androidRunnable.packageName = m_androidRunnable.intentName.left(m_androidRunnable.intentName.indexOf(QLatin1Char('/')));
|
2013-09-17 18:24:57 +02:00
|
|
|
|
2016-01-29 14:05:00 +02:00
|
|
|
m_androidRunnable.deviceSerialNumber = AndroidManager::deviceSerialNumber(target);
|
2012-04-18 20:30:57 +03:00
|
|
|
m_processPID = -1;
|
2013-12-16 20:19:07 +01:00
|
|
|
m_adb = AndroidConfigurations::currentConfig().adbToolPath().toString();
|
2016-01-29 14:05:00 +02:00
|
|
|
m_selector = AndroidDeviceInfo::adbSelector(m_androidRunnable.deviceSerialNumber);
|
2013-02-27 15:12:36 +01:00
|
|
|
|
2016-01-29 14:05:00 +02:00
|
|
|
QString packageDir = _("/data/data/") + m_androidRunnable.packageName;
|
2013-02-27 15:12:36 +01:00
|
|
|
m_pingFile = packageDir + _("/debug-ping");
|
2016-01-29 14:05:00 +02:00
|
|
|
m_pongFile = _("/data/local/tmp/qt/debug-pong-") + m_androidRunnable.packageName;
|
2013-02-27 15:12:36 +01:00
|
|
|
m_gdbserverSocket = packageDir + _("/debug-socket");
|
2014-08-07 10:10:32 +03:00
|
|
|
const QtSupport::BaseQtVersion *version = QtSupport::QtKitInformation::qtVersion(target->kit());
|
|
|
|
|
if (version && version->qtVersion() >= QtSupport::QtVersionNumber(5, 4, 0))
|
2014-08-14 16:07:33 +03:00
|
|
|
m_gdbserverPath = packageDir + _("/lib/libgdbserver.so");
|
|
|
|
|
else
|
|
|
|
|
m_gdbserverPath = packageDir + _("/lib/gdbserver");
|
|
|
|
|
|
2014-08-07 10:10:32 +03:00
|
|
|
|
2013-02-27 15:12:36 +01:00
|
|
|
// Detect busybox, as we need to pass -w to ps to get wide output.
|
|
|
|
|
QProcess psProc;
|
|
|
|
|
psProc.start(m_adb, selector() << _("shell") << _("readlink") << _("$(which ps)"));
|
|
|
|
|
psProc.waitForFinished();
|
|
|
|
|
QByteArray which = psProc.readAll();
|
|
|
|
|
m_isBusyBox = which.startsWith("busybox");
|
|
|
|
|
|
2013-06-14 15:49:38 +02:00
|
|
|
m_checkPIDTimer.setInterval(1000);
|
|
|
|
|
|
2012-04-18 20:30:57 +03:00
|
|
|
connect(&m_adbLogcatProcess, SIGNAL(readyReadStandardOutput()), SLOT(logcatReadStandardOutput()));
|
2013-02-27 15:12:36 +01:00
|
|
|
connect(&m_adbLogcatProcess, SIGNAL(readyReadStandardError()), SLOT(logcatReadStandardError()));
|
|
|
|
|
connect(&m_checkPIDTimer, SIGNAL(timeout()), SLOT(checkPID()));
|
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) {
|
|
|
|
|
socketHandShakePort = port;
|
|
|
|
|
m_customPort = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-06-12 15:36:04 +02:00
|
|
|
|
|
|
|
|
m_logCatRegExp = QRegExp(QLatin1String("[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]*"
|
|
|
|
|
));
|
2012-04-18 20:30:57 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AndroidRunner::~AndroidRunner()
|
|
|
|
|
{
|
2013-02-27 15:12:36 +01:00
|
|
|
//stop();
|
2014-11-18 15:14:40 +01:00
|
|
|
delete m_socket;
|
2012-04-18 20:30:57 +03:00
|
|
|
}
|
|
|
|
|
|
2013-02-27 15:12:36 +01:00
|
|
|
static int extractPidFromChunk(const QByteArray &chunk, int from)
|
|
|
|
|
{
|
|
|
|
|
int pos1 = chunk.indexOf(' ', from);
|
|
|
|
|
if (pos1 == -1)
|
|
|
|
|
return -1;
|
|
|
|
|
while (chunk[pos1] == ' ')
|
|
|
|
|
++pos1;
|
|
|
|
|
int pos3 = chunk.indexOf(' ', pos1);
|
|
|
|
|
int pid = chunk.mid(pos1, pos3 - pos1).toInt();
|
|
|
|
|
return pid;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int extractPid(const QString &exeName, const QByteArray &psOutput)
|
|
|
|
|
{
|
|
|
|
|
const QByteArray needle = exeName.toUtf8() + '\r';
|
|
|
|
|
const int to = psOutput.indexOf(needle);
|
|
|
|
|
if (to == -1)
|
|
|
|
|
return -1;
|
|
|
|
|
const int from = psOutput.lastIndexOf('\n', to);
|
|
|
|
|
if (from == -1)
|
|
|
|
|
return -1;
|
|
|
|
|
return extractPidFromChunk(psOutput, from);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QByteArray AndroidRunner::runPs()
|
2012-04-18 20:30:57 +03:00
|
|
|
{
|
2016-04-29 16:18:21 +03:00
|
|
|
if (QThread::currentThread() != thread()) {
|
|
|
|
|
QByteArray ret;
|
|
|
|
|
QMetaObject::invokeMethod(this, "runPs", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QByteArray, ret));
|
|
|
|
|
return ret;
|
|
|
|
|
} else {
|
|
|
|
|
QByteArray psLine("ps");
|
|
|
|
|
if (m_isBusyBox)
|
|
|
|
|
psLine += " -w";
|
|
|
|
|
psLine += '\n';
|
|
|
|
|
m_psProc.write(psLine);
|
|
|
|
|
m_psProc.waitForBytesWritten(psLine.size());
|
|
|
|
|
return m_psProc.readAllStandardOutput();
|
|
|
|
|
}
|
2013-02-27 15:12:36 +01:00
|
|
|
}
|
2012-12-10 23:49:46 +00:00
|
|
|
|
2013-02-27 15:12:36 +01:00
|
|
|
void AndroidRunner::checkPID()
|
|
|
|
|
{
|
|
|
|
|
QByteArray psOut = runPs();
|
2016-01-29 14:05:00 +02:00
|
|
|
m_processPID = extractPid(m_androidRunnable.packageName, psOut);
|
2014-02-12 12:24:56 +01:00
|
|
|
|
2013-06-14 15:49:38 +02:00
|
|
|
if (m_processPID == -1) {
|
2014-02-12 12:24:56 +01:00
|
|
|
if (m_wasStarted) {
|
|
|
|
|
m_wasStarted = false;
|
|
|
|
|
m_checkPIDTimer.stop();
|
2016-01-29 14:05:00 +02:00
|
|
|
emit remoteProcessFinished(QLatin1String("\n\n") + tr("\"%1\" died.").arg(m_androidRunnable.packageName));
|
2014-02-12 12:24:56 +01:00
|
|
|
} else {
|
|
|
|
|
if (++m_tries > 3)
|
2016-01-29 14:05:00 +02:00
|
|
|
emit remoteProcessFinished(QLatin1String("\n\n") + tr("Unable to start \"%1\".").arg(m_androidRunnable.packageName));
|
2014-02-12 12:24:56 +01:00
|
|
|
}
|
|
|
|
|
} else if (!m_wasStarted){
|
|
|
|
|
if (m_useCppDebugger) {
|
|
|
|
|
// This will be funneled to the engine to actually start and attach
|
|
|
|
|
// gdb. Afterwards this ends up in handleRemoteDebuggerRunning() below.
|
2016-04-19 16:43:30 +02:00
|
|
|
QByteArray serverChannel = ':' + QByteArray::number(m_localGdbServerPort.number());
|
2014-02-12 12:24:56 +01:00
|
|
|
emit remoteServerRunning(serverChannel, m_processPID);
|
2015-08-10 17:43:58 +02:00
|
|
|
} else if (m_qmlDebugServices == QmlDebug::QmlDebuggerServices) {
|
2014-02-12 12:24:56 +01:00
|
|
|
// This will be funneled to the engine to actually start and attach
|
|
|
|
|
// gdb. Afterwards this ends up in handleRemoteDebuggerRunning() below.
|
2016-04-19 16:43:30 +02:00
|
|
|
QByteArray serverChannel = QByteArray::number(m_qmlPort.number());
|
2014-02-12 12:24:56 +01:00
|
|
|
emit remoteServerRunning(serverChannel, m_processPID);
|
2015-08-10 17:43:58 +02:00
|
|
|
} else if (m_qmlDebugServices == QmlDebug::QmlProfilerServices) {
|
2016-04-19 16:43:30 +02:00
|
|
|
emit remoteProcessStarted(Utils::Port(), m_qmlPort);
|
2014-02-12 12:24:56 +01:00
|
|
|
} else {
|
|
|
|
|
// Start without debugging.
|
2016-04-19 16:43:30 +02:00
|
|
|
emit remoteProcessStarted(Utils::Port(), Utils::Port());
|
2014-02-12 12:24:56 +01:00
|
|
|
}
|
|
|
|
|
m_wasStarted = true;
|
2014-03-13 16:21:36 +01:00
|
|
|
logcatReadStandardOutput();
|
2013-06-14 15:49:38 +02:00
|
|
|
}
|
2013-02-27 15:12:36 +01:00
|
|
|
}
|
2012-04-18 20:30:57 +03:00
|
|
|
|
2013-02-27 15:12:36 +01:00
|
|
|
void AndroidRunner::forceStop()
|
|
|
|
|
{
|
|
|
|
|
QProcess proc;
|
2013-10-30 15:39:05 +01:00
|
|
|
proc.start(m_adb, selector() << _("shell") << _("am") << _("force-stop")
|
2016-01-29 14:05:00 +02:00
|
|
|
<< m_androidRunnable.packageName);
|
2013-02-27 15:12:36 +01:00
|
|
|
proc.waitForFinished();
|
2012-04-18 20:30:57 +03:00
|
|
|
|
2013-10-30 15:39:05 +01:00
|
|
|
// try killing it via kill -9
|
2013-02-27 15:12:36 +01:00
|
|
|
const QByteArray out = runPs();
|
|
|
|
|
int from = 0;
|
|
|
|
|
while (1) {
|
|
|
|
|
const int to = out.indexOf('\n', from);
|
|
|
|
|
if (to == -1)
|
|
|
|
|
break;
|
|
|
|
|
QString line = QString::fromUtf8(out.data() + from, to - from - 1);
|
2016-01-29 14:05:00 +02:00
|
|
|
if (line.endsWith(m_androidRunnable.packageName) || line.endsWith(m_gdbserverPath)) {
|
2013-02-27 15:12:36 +01:00
|
|
|
int pid = extractPidFromChunk(out, from);
|
|
|
|
|
adbKill(pid);
|
2012-04-18 20:30:57 +03:00
|
|
|
}
|
2013-02-27 15:12:36 +01:00
|
|
|
from = to + 1;
|
2012-04-18 20:30:57 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidRunner::start()
|
|
|
|
|
{
|
2013-02-27 15:12:36 +01:00
|
|
|
m_adbLogcatProcess.start(m_adb, selector() << _("logcat"));
|
2016-03-07 18:49:09 +02:00
|
|
|
m_psProc.start(m_adb, selector() << _("shell"));
|
2016-02-05 15:06:15 +01:00
|
|
|
Utils::runAsync(&AndroidRunner::asyncStart, this);
|
2012-04-18 20:30:57 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidRunner::asyncStart()
|
|
|
|
|
{
|
|
|
|
|
QMutexLocker locker(&m_mutex);
|
2013-02-27 15:12:36 +01:00
|
|
|
forceStop();
|
|
|
|
|
|
2012-06-24 19:32:45 -07:00
|
|
|
if (m_useCppDebugger) {
|
2013-02-27 15:12:36 +01:00
|
|
|
// Remove pong file.
|
|
|
|
|
QProcess adb;
|
|
|
|
|
adb.start(m_adb, selector() << _("shell") << _("rm") << m_pongFile);
|
|
|
|
|
adb.waitForFinished();
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-29 14:05:00 +02:00
|
|
|
foreach (const QStringList &entry, m_androidRunnable.beforeStartADBCommands) {
|
|
|
|
|
QProcess adb;
|
|
|
|
|
adb.start(m_adb, selector() << entry);
|
|
|
|
|
adb.waitForFinished();
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-27 15:12:36 +01:00
|
|
|
QStringList args = selector();
|
2016-01-29 14:05:00 +02:00
|
|
|
args << _("shell") << _("am") << _("start") << _("-n") << m_androidRunnable.intentName;
|
2013-02-27 15:12:36 +01:00
|
|
|
|
|
|
|
|
if (m_useCppDebugger) {
|
|
|
|
|
QProcess adb;
|
|
|
|
|
adb.start(m_adb, selector() << _("forward")
|
2016-04-19 16:43:30 +02:00
|
|
|
<< QString::fromLatin1("tcp:%1").arg(m_localGdbServerPort.number())
|
2013-02-27 15:12:36 +01:00
|
|
|
<< _("localfilesystem:") + m_gdbserverSocket);
|
|
|
|
|
if (!adb.waitForStarted()) {
|
|
|
|
|
emit remoteProcessFinished(tr("Failed to forward C++ debugging ports. Reason: %1.").arg(adb.errorString()));
|
2012-04-18 20:30:57 +03:00
|
|
|
return;
|
|
|
|
|
}
|
2014-06-23 14:13:37 +02:00
|
|
|
if (!adb.waitForFinished(10000)) {
|
2012-09-28 16:13:16 +02:00
|
|
|
emit remoteProcessFinished(tr("Failed to forward C++ debugging ports."));
|
2012-04-18 20:30:57 +03:00
|
|
|
return;
|
|
|
|
|
}
|
2013-02-27 15:12:36 +01:00
|
|
|
|
2016-01-29 14:05:00 +02:00
|
|
|
const QString pingPongSocket(m_androidRunnable.packageName + _(".ping_pong_socket"));
|
2013-02-27 15:12:36 +01:00
|
|
|
args << _("-e") << _("debug_ping") << _("true");
|
2014-11-18 15:14:40 +01:00
|
|
|
if (m_handShakeMethod == SocketHandShake) {
|
|
|
|
|
args << _("-e") << _("ping_socket") << pingPongSocket;
|
|
|
|
|
} else if (m_handShakeMethod == PingPongFiles) {
|
|
|
|
|
args << _("-e") << _("ping_file") << m_pingFile;
|
|
|
|
|
args << _("-e") << _("pong_file") << m_pongFile;
|
|
|
|
|
}
|
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);
|
|
|
|
|
args << _("-e") << _("gdbserver_command") << gdbserverCommand;
|
2013-02-27 15:12:36 +01:00
|
|
|
args << _("-e") << _("gdbserver_socket") << m_gdbserverSocket;
|
2014-11-18 15:14:40 +01:00
|
|
|
|
|
|
|
|
if (m_handShakeMethod == SocketHandShake) {
|
|
|
|
|
QProcess adb;
|
|
|
|
|
const QString port = QString::fromLatin1("tcp:%1").arg(socketHandShakePort);
|
|
|
|
|
adb.start(m_adb, selector() << _("forward") << port << _("localabstract:") + pingPongSocket);
|
|
|
|
|
if (!adb.waitForStarted()) {
|
|
|
|
|
emit remoteProcessFinished(tr("Failed to forward ping pong ports. Reason: %1.").arg(adb.errorString()));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!adb.waitForFinished()) {
|
|
|
|
|
emit remoteProcessFinished(tr("Failed to forward ping pong ports."));
|
|
|
|
|
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());
|
2013-02-27 15:12:36 +01:00
|
|
|
QProcess adb;
|
|
|
|
|
adb.start(m_adb, selector() << _("forward") << port << port);
|
|
|
|
|
if (!adb.waitForStarted()) {
|
|
|
|
|
emit remoteProcessFinished(tr("Failed to forward QML debugging ports. Reason: %1.").arg(adb.errorString()));
|
2012-06-24 19:32:45 -07:00
|
|
|
return;
|
|
|
|
|
}
|
2013-02-27 15:12:36 +01:00
|
|
|
if (!adb.waitForFinished()) {
|
2012-09-28 16:13:16 +02:00
|
|
|
emit remoteProcessFinished(tr("Failed to forward QML debugging ports."));
|
2012-06-24 19:32:45 -07:00
|
|
|
return;
|
|
|
|
|
}
|
2015-08-10 17:43:58 +02:00
|
|
|
|
|
|
|
|
args << _("-e") << _("qml_debug") << _("true")
|
|
|
|
|
<< _("-e") << _("qmljsdebugger")
|
|
|
|
|
<< 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
|
|
|
|
2013-02-27 15:12:36 +01:00
|
|
|
QProcess adb;
|
|
|
|
|
adb.start(m_adb, args);
|
|
|
|
|
if (!adb.waitForStarted()) {
|
|
|
|
|
emit remoteProcessFinished(tr("Failed to start the activity. Reason: %1.").arg(adb.errorString()));
|
2012-04-18 20:30:57 +03:00
|
|
|
return;
|
|
|
|
|
}
|
2014-06-23 14:13:37 +02:00
|
|
|
if (!adb.waitForFinished(10000)) {
|
2013-02-27 15:12:36 +01:00
|
|
|
adb.terminate();
|
2016-01-29 14:05:00 +02:00
|
|
|
emit remoteProcessFinished(tr("Unable to start \"%1\".").arg(m_androidRunnable.packageName));
|
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
|
|
|
|
|
if (m_socket)
|
|
|
|
|
delete m_socket;
|
|
|
|
|
m_socket = new QTcpSocket();
|
|
|
|
|
for (int i = 0; i < maxAttempts; i++) {
|
|
|
|
|
|
|
|
|
|
QThread::sleep(1); // give Android time to start process
|
|
|
|
|
m_socket->connectToHost(QHostAddress(QStringLiteral("127.0.0.1")), socketHandShakePort);
|
|
|
|
|
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;
|
|
|
|
|
m_socket->moveToThread(QApplication::instance()->thread());
|
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 (!wasSuccess)
|
|
|
|
|
emit remoteProcessFinished(tr("Failed to contact debugging port."));
|
|
|
|
|
|
|
|
|
|
if (!m_customPort) {
|
|
|
|
|
// increment running port to avoid clash when using multiple
|
|
|
|
|
// debug sessions at the same time
|
|
|
|
|
socketHandShakePort++;
|
|
|
|
|
// wrap ports around to avoid overflow
|
|
|
|
|
if (socketHandShakePort == MAX_SOCKET_HANDSHAKE_PORT)
|
|
|
|
|
socketHandShakePort = MIN_SOCKET_HANDSHAKE_PORT;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Handling ping.
|
|
|
|
|
for (int i = 0; ; ++i) {
|
|
|
|
|
QTemporaryFile tmp(QDir::tempPath() + _("/pingpong"));
|
|
|
|
|
tmp.open();
|
|
|
|
|
tmp.close();
|
|
|
|
|
|
|
|
|
|
QProcess process;
|
|
|
|
|
process.start(m_adb, selector() << _("pull") << m_pingFile << tmp.fileName());
|
|
|
|
|
process.waitForFinished();
|
|
|
|
|
|
|
|
|
|
QFile res(tmp.fileName());
|
|
|
|
|
const bool doBreak = res.size();
|
|
|
|
|
res.remove();
|
|
|
|
|
if (doBreak)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
if (i == 20) {
|
2016-01-29 14:05:00 +02:00
|
|
|
emit remoteProcessFinished(tr("Unable to start \"%1\".").arg(m_androidRunnable.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
|
|
|
}
|
2013-02-27 15:12:36 +01:00
|
|
|
|
2014-02-12 12:24:56 +01:00
|
|
|
m_tries = 0;
|
|
|
|
|
m_wasStarted = false;
|
2013-06-14 15:49:38 +02:00
|
|
|
QMetaObject::invokeMethod(&m_checkPIDTimer, "start");
|
2012-04-18 20:30:57 +03:00
|
|
|
}
|
|
|
|
|
|
2015-09-23 09:25:50 +02:00
|
|
|
bool AndroidRunner::adbShellAmNeedsQuotes()
|
|
|
|
|
{
|
|
|
|
|
// 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.
|
|
|
|
|
QProcess adb;
|
|
|
|
|
adb.start(m_adb, selector() << _("shell") << _("am") << _("start")
|
|
|
|
|
<< _("-e") << _("dummy") <<_("dummy --dummy"));
|
|
|
|
|
if (!adb.waitForStarted())
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
if (!adb.waitForFinished(10000))
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
QByteArray output = adb.readAllStandardError() + adb.readAllStandardOutput();
|
|
|
|
|
bool oldSdk = output.contains("Error: No intent supplied");
|
|
|
|
|
return !oldSdk;
|
|
|
|
|
}
|
|
|
|
|
|
2013-04-18 10:01:05 +02:00
|
|
|
void AndroidRunner::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 {
|
|
|
|
|
QTemporaryFile tmp(QDir::tempPath() + _("/pingpong"));
|
|
|
|
|
tmp.open();
|
2013-02-27 15:12:36 +01:00
|
|
|
|
2014-11-18 15:14:40 +01:00
|
|
|
QProcess process;
|
|
|
|
|
process.start(m_adb, selector() << _("push") << tmp.fileName() << m_pongFile);
|
|
|
|
|
process.waitForFinished();
|
|
|
|
|
}
|
2013-04-18 10:01:05 +02:00
|
|
|
QTC_CHECK(m_processPID != -1);
|
|
|
|
|
}
|
|
|
|
|
emit remoteProcessStarted(m_localGdbServerPort, m_qmlPort);
|
2012-04-18 20:30:57 +03:00
|
|
|
}
|
|
|
|
|
|
2012-12-18 14:25:07 +02:00
|
|
|
void AndroidRunner::stop()
|
2012-04-18 20:30:57 +03:00
|
|
|
{
|
|
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
|
m_checkPIDTimer.stop();
|
2014-02-12 12:24:56 +01:00
|
|
|
m_tries = 0;
|
2013-02-27 15:12:36 +01:00
|
|
|
if (m_processPID != -1) {
|
2013-10-30 15:39:05 +01:00
|
|
|
forceStop();
|
2016-01-29 14:05:00 +02:00
|
|
|
emit remoteProcessFinished(QLatin1String("\n\n") + tr("\"%1\" terminated.").arg(m_androidRunnable.packageName));
|
2012-12-18 14:25:07 +02:00
|
|
|
}
|
2013-02-27 15:12:36 +01:00
|
|
|
//QObject::disconnect(&m_adbLogcatProcess, 0, this, 0);
|
2012-12-18 14:25:07 +02:00
|
|
|
m_adbLogcatProcess.kill();
|
2013-02-27 15:12:36 +01:00
|
|
|
m_adbLogcatProcess.waitForFinished();
|
2016-03-07 18:49:09 +02:00
|
|
|
m_psProc.kill();
|
|
|
|
|
m_psProc.waitForFinished();
|
2016-01-29 14:05:00 +02:00
|
|
|
foreach (const QStringList &entry, m_androidRunnable.afterFinishADBCommands) {
|
|
|
|
|
QProcess adb;
|
|
|
|
|
adb.start(m_adb, selector() << entry);
|
|
|
|
|
adb.waitForFinished();
|
|
|
|
|
}
|
2012-04-18 20:30:57 +03:00
|
|
|
}
|
|
|
|
|
|
2014-03-13 17:33:47 +01:00
|
|
|
void AndroidRunner::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
|
|
|
QString pidString = QString::number(m_processPID);
|
|
|
|
|
foreach (const QByteArray &msg, lines) {
|
2015-06-26 13:06:08 +02:00
|
|
|
const QString line = QString::fromUtf8(msg).trimmed() + QLatin1Char('\n');
|
2015-06-12 15:36:04 +02:00
|
|
|
if (!line.contains(pidString))
|
2012-04-18 20:30:57 +03:00
|
|
|
continue;
|
2015-06-12 15:36:04 +02:00
|
|
|
if (m_logCatRegExp.exactMatch(line)) {
|
|
|
|
|
// Android M
|
|
|
|
|
if (m_logCatRegExp.cap(1) == pidString) {
|
|
|
|
|
const QString &messagetype = m_logCatRegExp.cap(2);
|
|
|
|
|
QString output = line.mid(m_logCatRegExp.pos(2));
|
|
|
|
|
|
|
|
|
|
if (onlyError
|
|
|
|
|
|| messagetype == QLatin1String("F")
|
|
|
|
|
|| messagetype == QLatin1String("E")
|
2015-07-15 16:36:11 +02:00
|
|
|
|| messagetype == QLatin1String("W"))
|
2015-06-12 15:36:04 +02:00
|
|
|
emit remoteErrorOutput(output);
|
|
|
|
|
else
|
|
|
|
|
emit remoteOutput(output);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (onlyError || line.startsWith(_("F/"))
|
|
|
|
|
|| line.startsWith(_("E/"))
|
|
|
|
|
|| line.startsWith(_("W/")))
|
|
|
|
|
emit remoteErrorOutput(line);
|
|
|
|
|
else
|
|
|
|
|
emit remoteOutput(line);
|
|
|
|
|
}
|
2012-04-18 20:30:57 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-13 17:33:47 +01:00
|
|
|
void AndroidRunner::logcatReadStandardError()
|
|
|
|
|
{
|
|
|
|
|
if (m_processPID != -1)
|
|
|
|
|
logcatProcess(m_adbLogcatProcess.readAllStandardError(), m_stderrBuffer, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidRunner::logcatReadStandardOutput()
|
|
|
|
|
{
|
|
|
|
|
if (m_processPID != -1)
|
|
|
|
|
logcatProcess(m_adbLogcatProcess.readAllStandardOutput(), m_stdoutBuffer, false);
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-27 15:12:36 +01:00
|
|
|
void AndroidRunner::adbKill(qint64 pid)
|
2012-04-18 20:30:57 +03:00
|
|
|
{
|
2013-02-27 15:12:36 +01:00
|
|
|
{
|
|
|
|
|
QProcess process;
|
|
|
|
|
process.start(m_adb, selector() << _("shell")
|
|
|
|
|
<< _("kill") << QLatin1String("-9") << QString::number(pid));
|
|
|
|
|
process.waitForFinished();
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
QProcess process;
|
|
|
|
|
process.start(m_adb, selector() << _("shell")
|
2016-01-29 14:05:00 +02:00
|
|
|
<< _("run-as") << m_androidRunnable.packageName
|
2013-02-27 15:12:36 +01:00
|
|
|
<< _("kill") << QLatin1String("-9") << QString::number(pid));
|
|
|
|
|
process.waitForFinished();
|
|
|
|
|
}
|
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)
|
|
|
|
|
{
|
|
|
|
|
m_androidRunnable = runnable;
|
|
|
|
|
m_selector = AndroidDeviceInfo::adbSelector(m_androidRunnable.deviceSerialNumber);
|
2012-04-18 20:30:57 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace Internal
|
2013-10-16 11:02:37 +02:00
|
|
|
} // namespace Android
|