2012-04-18 20:30:57 +03:00
|
|
|
/**************************************************************************
|
|
|
|
|
**
|
|
|
|
|
** Copyright (c) 2012 BogDan Vatra <bog_dan_ro@yahoo.com>
|
2012-10-02 09:12:39 +02:00
|
|
|
** Contact: http://www.qt-project.org/legal
|
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
|
|
|
|
|
** a written agreement between you and Digia. For licensing terms and
|
|
|
|
|
** conditions see http://qt.digia.com/licensing. For further information
|
|
|
|
|
** use the contact form at http://qt.digia.com/contact-us.
|
2012-04-18 20:30:57 +03:00
|
|
|
**
|
|
|
|
|
** GNU Lesser General Public License Usage
|
2012-10-02 09:12:39 +02:00
|
|
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
|
|
|
|
** General Public License version 2.1 as published by the Free Software
|
|
|
|
|
** Foundation and appearing in the file LICENSE.LGPL included in the
|
|
|
|
|
** packaging of this file. Please review the following information to
|
|
|
|
|
** ensure the GNU Lesser General Public License version 2.1 requirements
|
|
|
|
|
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
|
|
|
|
**
|
|
|
|
|
** In addition, as a special exception, Digia gives you certain additional
|
|
|
|
|
** rights. These rights are described in the Digia Qt LGPL Exception
|
2012-04-18 20:30:57 +03:00
|
|
|
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
|
|
|
|
**
|
2012-10-02 09:12:39 +02:00
|
|
|
****************************************************************************/
|
2012-04-18 20:30:57 +03:00
|
|
|
|
|
|
|
|
#include "androidrunner.h"
|
|
|
|
|
|
|
|
|
|
#include "androiddeploystep.h"
|
|
|
|
|
#include "androidconfigurations.h"
|
|
|
|
|
#include "androidglobal.h"
|
|
|
|
|
#include "androidrunconfiguration.h"
|
2012-04-24 15:49:09 +02:00
|
|
|
#include "androidmanager.h"
|
|
|
|
|
|
|
|
|
|
#include <projectexplorer/target.h>
|
2012-04-18 20:30:57 +03:00
|
|
|
|
|
|
|
|
#include <QTime>
|
|
|
|
|
#include <QtConcurrentRun>
|
|
|
|
|
|
|
|
|
|
namespace Android {
|
|
|
|
|
namespace Internal {
|
|
|
|
|
|
2012-06-24 19:32:45 -07:00
|
|
|
AndroidRunner::AndroidRunner(QObject *parent, AndroidRunConfiguration *runConfig, bool debuggingMode)
|
2012-04-18 20:30:57 +03:00
|
|
|
: QThread(parent)
|
|
|
|
|
{
|
2012-06-24 19:32:45 -07:00
|
|
|
m_useCppDebugger = debuggingMode && runConfig->debuggerAspect()->useCppDebugger();
|
|
|
|
|
m_useQmlDebugger = debuggingMode && runConfig->debuggerAspect()->useQmlDebugger();
|
|
|
|
|
m_remoteGdbChannel = runConfig->remoteChannel();
|
|
|
|
|
m_qmlPort = runConfig->debuggerAspect()->qmlDebugServerPort();
|
2012-04-24 15:49:09 +02:00
|
|
|
ProjectExplorer::Target *target = runConfig->target();
|
|
|
|
|
AndroidDeployStep *ds = runConfig->deployStep();
|
2012-04-18 20:30:57 +03:00
|
|
|
if ((m_useLocalQtLibs = ds->useLocalQtLibs())) {
|
2012-04-24 15:49:09 +02:00
|
|
|
m_localLibs = AndroidManager::loadLocalLibs(target, ds->deviceAPILevel());
|
|
|
|
|
m_localJars = AndroidManager::loadLocalJars(target, ds->deviceAPILevel());
|
2013-01-06 16:23:08 +02:00
|
|
|
m_localJarsInitClasses = AndroidManager::loadLocalJarsInitClasses(target, ds->deviceAPILevel());
|
2012-04-18 20:30:57 +03:00
|
|
|
}
|
2012-04-24 15:49:09 +02:00
|
|
|
m_intentName = AndroidManager::intentName(target);
|
2012-04-18 20:30:57 +03:00
|
|
|
m_packageName = m_intentName.left(m_intentName.indexOf(QLatin1Char('/')));
|
|
|
|
|
m_deviceSerialNumber = ds->deviceSerialNumber();
|
|
|
|
|
m_processPID = -1;
|
|
|
|
|
m_gdbserverPID = -1;
|
|
|
|
|
connect(&m_checkPIDTimer, SIGNAL(timeout()), SLOT(checkPID()));
|
|
|
|
|
connect(&m_adbLogcatProcess, SIGNAL(readyReadStandardOutput()), SLOT(logcatReadStandardOutput()));
|
|
|
|
|
connect(&m_adbLogcatProcess, SIGNAL(readyReadStandardError()) , SLOT(logcatReadStandardError()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AndroidRunner::~AndroidRunner()
|
|
|
|
|
{
|
2012-12-07 19:53:32 +02:00
|
|
|
stop(false);
|
2012-04-18 20:30:57 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidRunner::checkPID()
|
|
|
|
|
{
|
|
|
|
|
QProcess psProc;
|
2012-12-10 23:49:46 +00:00
|
|
|
QLatin1String psCmd = QLatin1String("ps");
|
|
|
|
|
QLatin1String psPidRx = QLatin1String("\\d+\\s+(\\d+)");
|
|
|
|
|
|
|
|
|
|
// Detect busybox, as we need to pass -w to it to get wide output.
|
2012-04-24 15:49:09 +02:00
|
|
|
psProc.start(AndroidConfigurations::instance().adbToolPath().toString(),
|
2012-04-18 20:30:57 +03:00
|
|
|
QStringList() << QLatin1String("-s") << m_deviceSerialNumber
|
2012-12-10 23:49:46 +00:00
|
|
|
<< QLatin1String("shell") << QLatin1String("readlink") << QLatin1String("$(which ps)"));
|
2012-04-18 20:30:57 +03:00
|
|
|
if (!psProc.waitForFinished(-1)) {
|
2012-12-10 23:43:21 +00:00
|
|
|
psProc.kill();
|
2012-04-18 20:30:57 +03:00
|
|
|
return;
|
|
|
|
|
}
|
2012-12-10 23:49:46 +00:00
|
|
|
QByteArray which = psProc.readAll();
|
|
|
|
|
if (which.startsWith("busybox")) {
|
|
|
|
|
psCmd = QLatin1String("ps -w");
|
|
|
|
|
psPidRx = QLatin1String("(\\d+)");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
psProc.start(AndroidConfigurations::instance().adbToolPath().toString(),
|
|
|
|
|
QStringList() << QLatin1String("-s") << m_deviceSerialNumber
|
|
|
|
|
<< QLatin1String("shell") << psCmd);
|
|
|
|
|
if (!psProc.waitForFinished(-1)) {
|
|
|
|
|
psProc.kill();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
QRegExp rx(psPidRx);
|
2012-04-18 20:30:57 +03:00
|
|
|
qint64 pid = -1;
|
|
|
|
|
QList<QByteArray> procs = psProc.readAll().split('\n');
|
|
|
|
|
foreach (const QByteArray &proc, procs) {
|
2012-09-21 13:54:38 +02:00
|
|
|
if (proc.trimmed().endsWith(m_packageName.toLatin1())) {
|
2012-12-10 23:49:46 +00:00
|
|
|
if (rx.indexIn(QLatin1String(proc)) > -1) {
|
2012-04-18 20:30:57 +03:00
|
|
|
pid = rx.cap(1).toLongLong();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (-1 != m_processPID && pid == -1) {
|
|
|
|
|
m_processPID = -1;
|
2012-09-28 16:13:16 +02:00
|
|
|
emit remoteProcessFinished(tr("\n\n'%1' died.").arg(m_packageName));
|
2012-04-18 20:30:57 +03:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
m_processPID = pid;
|
|
|
|
|
|
2012-06-24 19:32:45 -07:00
|
|
|
if (!m_useCppDebugger)
|
|
|
|
|
return;
|
2012-04-18 20:30:57 +03:00
|
|
|
m_gdbserverPID = -1;
|
|
|
|
|
foreach (const QByteArray &proc, procs) {
|
|
|
|
|
if (proc.trimmed().endsWith("gdbserver")) {
|
2012-12-10 23:49:46 +00:00
|
|
|
if (rx.indexIn(QLatin1String(proc)) > -1) {
|
2012-04-18 20:30:57 +03:00
|
|
|
m_gdbserverPID = rx.cap(1).toLongLong();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidRunner::killPID()
|
|
|
|
|
{
|
|
|
|
|
checkPID(); //updates m_processPID and m_gdbserverPID
|
|
|
|
|
for (int tries = 0; tries < 10 && (m_processPID != -1 || m_gdbserverPID != -1); ++tries) {
|
|
|
|
|
if (m_processPID != -1) {
|
|
|
|
|
adbKill(m_processPID, m_deviceSerialNumber, 2000);
|
|
|
|
|
adbKill(m_processPID, m_deviceSerialNumber, 2000, m_packageName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_gdbserverPID != -1) {
|
|
|
|
|
adbKill(m_gdbserverPID, m_deviceSerialNumber, 2000);
|
|
|
|
|
adbKill(m_gdbserverPID, m_deviceSerialNumber, 2000, m_packageName);
|
|
|
|
|
}
|
|
|
|
|
checkPID();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidRunner::start()
|
|
|
|
|
{
|
|
|
|
|
QtConcurrent::run(this,&AndroidRunner::asyncStart);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidRunner::asyncStart()
|
|
|
|
|
{
|
|
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
|
m_processPID = -1;
|
|
|
|
|
killPID(); // kill any process with this name
|
|
|
|
|
QString extraParams;
|
|
|
|
|
QProcess adbStarProc;
|
2012-06-24 19:32:45 -07:00
|
|
|
if (m_useCppDebugger) {
|
2012-04-18 20:30:57 +03:00
|
|
|
QStringList arguments;
|
|
|
|
|
arguments << QLatin1String("-s") << m_deviceSerialNumber
|
2012-06-24 19:32:45 -07:00
|
|
|
<< QLatin1String("forward") << QString::fromLatin1("tcp%1").arg(m_remoteGdbChannel)
|
2012-04-18 20:30:57 +03:00
|
|
|
<< QString::fromLatin1("localfilesystem:/data/data/%1/debug-socket").arg(m_packageName);
|
2012-04-24 15:49:09 +02:00
|
|
|
adbStarProc.start(AndroidConfigurations::instance().adbToolPath().toString(), arguments);
|
2012-04-18 20:30:57 +03:00
|
|
|
if (!adbStarProc.waitForStarted()) {
|
2012-09-28 16:13:16 +02:00
|
|
|
emit remoteProcessFinished(tr("Failed to forward C++ debugging ports. Reason: %1.").arg(adbStarProc.errorString()));
|
2012-04-18 20:30:57 +03:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!adbStarProc.waitForFinished(-1)) {
|
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;
|
|
|
|
|
}
|
|
|
|
|
extraParams = QLatin1String("-e native_debug true -e gdbserver_socket +debug-socket");
|
|
|
|
|
}
|
2012-06-24 19:32:45 -07:00
|
|
|
if (m_useQmlDebugger) {
|
|
|
|
|
QStringList arguments;
|
|
|
|
|
QString port = QString::fromLatin1("tcp:%1").arg(m_qmlPort);
|
|
|
|
|
arguments << QLatin1String("-s") << m_deviceSerialNumber
|
|
|
|
|
<< QLatin1String("forward") << port << port; // currently forward to same port on device and host
|
|
|
|
|
adbStarProc.start(AndroidConfigurations::instance().adbToolPath().toString(), arguments);
|
|
|
|
|
if (!adbStarProc.waitForStarted()) {
|
2012-09-28 16:13:16 +02:00
|
|
|
emit remoteProcessFinished(tr("Failed to forward QML debugging ports. Reason: %1.").arg(adbStarProc.errorString()));
|
2012-06-24 19:32:45 -07:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!adbStarProc.waitForFinished(-1)) {
|
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;
|
|
|
|
|
}
|
|
|
|
|
extraParams+=QString::fromLatin1(" -e qml_debug true -e qmljsdebugger port:%1")
|
|
|
|
|
.arg(m_qmlPort);
|
|
|
|
|
}
|
2012-04-18 20:30:57 +03:00
|
|
|
|
|
|
|
|
if (m_useLocalQtLibs) {
|
|
|
|
|
extraParams += QLatin1String(" -e use_local_qt_libs true");
|
2013-01-06 16:22:23 +02:00
|
|
|
extraParams += QLatin1String(" -e libs_prefix /data/local/tmp/qt/");
|
2012-04-18 20:30:57 +03:00
|
|
|
extraParams += QLatin1String(" -e load_local_libs ") + m_localLibs;
|
|
|
|
|
extraParams += QLatin1String(" -e load_local_jars ") + m_localJars;
|
2013-01-06 16:23:08 +02:00
|
|
|
if (!m_localJarsInitClasses.isEmpty())
|
|
|
|
|
extraParams += QLatin1String(" -e static_init_classes ") + m_localJarsInitClasses;
|
2012-04-18 20:30:57 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extraParams = extraParams.trimmed();
|
|
|
|
|
QStringList arguments;
|
|
|
|
|
arguments << QLatin1String("-s") << m_deviceSerialNumber
|
|
|
|
|
<< QLatin1String("shell") << QLatin1String("am")
|
|
|
|
|
<< QLatin1String("start") << QLatin1String("-n") << m_intentName;
|
|
|
|
|
|
|
|
|
|
if (extraParams.length())
|
|
|
|
|
arguments << extraParams.split(QLatin1Char(' '));
|
|
|
|
|
|
2012-04-24 15:49:09 +02:00
|
|
|
adbStarProc.start(AndroidConfigurations::instance().adbToolPath().toString(), arguments);
|
2012-04-18 20:30:57 +03:00
|
|
|
if (!adbStarProc.waitForStarted()) {
|
2012-09-28 16:13:16 +02:00
|
|
|
emit remoteProcessFinished(tr("Failed to start the activity. Reason: %1.").arg(adbStarProc.errorString()));
|
2012-04-18 20:30:57 +03:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!adbStarProc.waitForFinished(-1)) {
|
|
|
|
|
adbStarProc.terminate();
|
2012-09-28 16:13:16 +02:00
|
|
|
emit remoteProcessFinished(tr("Unable to start '%1'.").arg(m_packageName));
|
2012-04-18 20:30:57 +03:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
QTime startTime = QTime::currentTime();
|
|
|
|
|
while (m_processPID == -1 && startTime.secsTo(QTime::currentTime()) < 10) { // wait up to 10 seconds for application to start
|
|
|
|
|
checkPID();
|
|
|
|
|
}
|
|
|
|
|
if (m_processPID == -1) {
|
2012-09-28 16:13:16 +02:00
|
|
|
emit remoteProcessFinished(tr("Cannot find %1 process.").arg(m_packageName));
|
2012-04-18 20:30:57 +03:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2012-06-24 19:32:45 -07:00
|
|
|
if (m_useCppDebugger) {
|
2012-04-18 20:30:57 +03:00
|
|
|
startTime = QTime::currentTime();
|
|
|
|
|
while (m_gdbserverPID == -1 && startTime.secsTo(QTime::currentTime()) < 25) { // wait up to 25 seconds to connect
|
|
|
|
|
checkPID();
|
|
|
|
|
}
|
|
|
|
|
msleep(200); // give gdbserver more time to start
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QMetaObject::invokeMethod(this, "startLogcat", Qt::QueuedConnection);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidRunner::startLogcat()
|
|
|
|
|
{
|
|
|
|
|
m_checkPIDTimer.start(1000); // check if the application is alive every 1 seconds
|
2012-04-24 15:49:09 +02:00
|
|
|
m_adbLogcatProcess.start(AndroidConfigurations::instance().adbToolPath().toString(),
|
2012-04-18 20:30:57 +03:00
|
|
|
QStringList() << QLatin1String("-s") << m_deviceSerialNumber
|
|
|
|
|
<< QLatin1String("logcat"));
|
|
|
|
|
emit remoteProcessStarted(5039);
|
|
|
|
|
}
|
|
|
|
|
|
2012-12-07 19:53:32 +02:00
|
|
|
void AndroidRunner::stop(bool async)
|
2012-04-18 20:30:57 +03:00
|
|
|
{
|
|
|
|
|
QMutexLocker locker(&m_mutex);
|
2012-12-10 23:43:21 +00:00
|
|
|
m_adbLogcatProcess.kill();
|
2012-04-18 20:30:57 +03:00
|
|
|
m_adbLogcatProcess.waitForFinished(-1);
|
|
|
|
|
m_checkPIDTimer.stop();
|
|
|
|
|
if (m_processPID == -1)
|
|
|
|
|
return; // don't emit another signal
|
2012-12-07 19:53:32 +02:00
|
|
|
if (async)
|
|
|
|
|
QtConcurrent::run(this, &AndroidRunner::asyncStop);
|
|
|
|
|
else
|
|
|
|
|
asyncStop();
|
2012-04-18 20:30:57 +03:00
|
|
|
}
|
2013-01-16 16:42:56 +01:00
|
|
|
|
2012-04-18 20:30:57 +03:00
|
|
|
void AndroidRunner::asyncStop()
|
|
|
|
|
{
|
|
|
|
|
killPID();
|
2012-09-28 16:13:16 +02:00
|
|
|
emit remoteProcessFinished(tr("\n\n'%1' killed.").arg(m_packageName));
|
2012-04-18 20:30:57 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidRunner::logcatReadStandardError()
|
|
|
|
|
{
|
|
|
|
|
emit remoteErrorOutput(m_adbLogcatProcess.readAllStandardError());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidRunner::logcatReadStandardOutput()
|
|
|
|
|
{
|
|
|
|
|
m_logcat += m_adbLogcatProcess.readAllStandardOutput();
|
|
|
|
|
bool keepLastLine = m_logcat.endsWith('\n');
|
|
|
|
|
QByteArray line;
|
2012-09-21 13:54:38 +02:00
|
|
|
QByteArray pid(QString::fromLatin1("%1):").arg(m_processPID).toLatin1());
|
2012-04-18 20:30:57 +03:00
|
|
|
foreach (line, m_logcat.split('\n')) {
|
|
|
|
|
if (!line.contains(pid))
|
|
|
|
|
continue;
|
2012-06-24 19:32:45 -07:00
|
|
|
if (line.endsWith('\r'))
|
|
|
|
|
line.chop(1);
|
|
|
|
|
line.append('\n');
|
2012-04-18 20:30:57 +03:00
|
|
|
if (line.startsWith("E/"))
|
|
|
|
|
emit remoteErrorOutput(line);
|
|
|
|
|
else
|
|
|
|
|
emit remoteOutput(line);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
if (keepLastLine)
|
|
|
|
|
m_logcat = line;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidRunner::adbKill(qint64 pid, const QString &device, int timeout, const QString &runAsPackageName)
|
|
|
|
|
{
|
|
|
|
|
QProcess process;
|
|
|
|
|
QStringList arguments;
|
|
|
|
|
|
|
|
|
|
arguments << QLatin1String("-s") << device;
|
|
|
|
|
arguments << QLatin1String("shell");
|
|
|
|
|
if (runAsPackageName.size())
|
|
|
|
|
arguments << QLatin1String("run-as") << runAsPackageName;
|
|
|
|
|
arguments << QLatin1String("kill") << QLatin1String("-9");
|
|
|
|
|
arguments << QString::number(pid);
|
|
|
|
|
|
2012-04-24 15:49:09 +02:00
|
|
|
process.start(AndroidConfigurations::instance().adbToolPath().toString(), arguments);
|
2012-04-18 20:30:57 +03:00
|
|
|
if (!process.waitForFinished(timeout))
|
2012-12-10 23:43:21 +00:00
|
|
|
process.kill();
|
2012-04-18 20:30:57 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString AndroidRunner::displayName() const
|
|
|
|
|
{
|
|
|
|
|
return m_packageName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace Internal
|
|
|
|
|
} // namespace Qt4ProjectManager
|