summaryrefslogtreecommitdiff
path: root/src/plugins/android/androidrunner.cpp
diff options
context:
space:
mode:
authorEike Ziller <eike.ziller@qt.io>2017-04-19 09:56:14 +0200
committerEike Ziller <eike.ziller@qt.io>2017-04-19 09:56:14 +0200
commit88897f3a870de7b756356907801f09a90902e490 (patch)
treea947c7fdb92d9050e9ade763dcb87db192b4585e /src/plugins/android/androidrunner.cpp
parent8b6eb5aabb48fb83c3cd92be9a77401ca26a810b (diff)
parent01b2ed7904132f845819e78c84477ac9a66bd1e3 (diff)
downloadqt-creator-88897f3a870de7b756356907801f09a90902e490.tar.gz
Merge remote-tracking branch 'origin/4.3'
Conflicts: src/plugins/genericprojectmanager/genericproject.cpp src/plugins/genericprojectmanager/genericproject.h src/plugins/genericprojectmanager/genericprojectnodes.cpp src/plugins/genericprojectmanager/genericprojectnodes.h Change-Id: Ie0c870f68c8d200a75489b75860987655b2f6175
Diffstat (limited to 'src/plugins/android/androidrunner.cpp')
-rw-r--r--src/plugins/android/androidrunner.cpp291
1 files changed, 167 insertions, 124 deletions
diff --git a/src/plugins/android/androidrunner.cpp b/src/plugins/android/androidrunner.cpp
index 778d348b68..9208e69953 100644
--- a/src/plugins/android/androidrunner.cpp
+++ b/src/plugins/android/androidrunner.cpp
@@ -31,6 +31,7 @@
#include "androidglobal.h"
#include "androidrunconfiguration.h"
#include "androidmanager.h"
+#include "androidavdmanager.h"
#include <debugger/debuggerrunconfigurationaspect.h>
#include <projectexplorer/projectexplorer.h>
@@ -51,6 +52,7 @@
#include <QTime>
#include <QTcpServer>
#include <QTcpSocket>
+#include <QRegularExpression>
using namespace std;
using namespace std::placeholders;
@@ -125,10 +127,10 @@ namespace Internal {
const int MIN_SOCKET_HANDSHAKE_PORT = 20001;
const int MAX_SOCKET_HANDSHAKE_PORT = 20999;
-static const QString pidScript = QStringLiteral("for p in /proc/[0-9]*; "
- "do cat <$p/cmdline && echo :${p##*/}; done");
-static const QString pidPollingScript = QStringLiteral("while true; do sleep 1; "
- "cat /proc/%1/cmdline > /dev/null; done");
+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");
static const QString regExpLogcat = QStringLiteral("[0-9\\-]*" // date
"\\s+"
@@ -146,55 +148,26 @@ static const QString regExpLogcat = QStringLiteral("[0-9\\-]*" // date
);
static int APP_START_TIMEOUT = 45000;
-static bool isTimedOut(const chrono::high_resolution_clock::time_point &start,
- int msecs = APP_START_TIMEOUT)
-{
- bool timedOut = false;
- auto end = chrono::high_resolution_clock::now();
- if (chrono::duration_cast<chrono::milliseconds>(end-start).count() > msecs)
- timedOut = true;
- return timedOut;
-}
-
-static qint64 extractPID(const QByteArray &output, const QString &packageName)
-{
- qint64 pid = -1;
- foreach (auto tuple, output.split('\n')) {
- tuple = tuple.simplified();
- if (!tuple.isEmpty()) {
- auto parts = tuple.split(':');
- QString commandName = QString::fromLocal8Bit(parts.first());
- if (parts.length() == 2 && commandName == packageName) {
- pid = parts.last().toLongLong();
- break;
- }
- }
- }
- return pid;
-}
+enum class PidStatus {
+ Found,
+ Lost
+};
-void findProcessPID(QFutureInterface<qint64> &fi, const QString &adbPath,
- QStringList selector, const QString &packageName)
+struct PidInfo
{
- if (packageName.isEmpty())
- return;
-
- qint64 processPID = -1;
- chrono::high_resolution_clock::time_point start = chrono::high_resolution_clock::now();
- do {
- QThread::msleep(200);
- const QByteArray out = Utils::SynchronousProcess()
- .runBlocking(adbPath, selector << QStringLiteral("shell") << pidScript)
- .allRawOutput();
- processPID = extractPID(out, packageName);
- } while (processPID == -1 && !isTimedOut(start) && !fi.isCanceled());
-
- if (!fi.isCanceled())
- fi.reportResult(processPID);
-}
+ PidInfo(qint64 pid = -1, PidStatus status = PidStatus::Lost, QString name = {})
+ : pid(pid)
+ , status(status)
+ , name(name)
+ {}
+ qint64 pid;
+ PidStatus status;
+ QString name;
+};
static void deleter(QProcess *p)
{
+ p->disconnect();
p->kill();
p->waitForFinished();
// Might get deleted from its own signal handler.
@@ -228,29 +201,31 @@ signals:
void remoteOutput(const QString &output);
void remoteErrorOutput(const QString &output);
+ void pidFound(qint64, const QString &name);
+ void pidLost(qint64);
private:
- void onProcessIdChanged(qint64 pid);
+ void findProcessPids();
+ void onProcessIdChanged(PidInfo pidInfo);
void logcatReadStandardError();
void logcatReadStandardOutput();
void adbKill(qint64 pid);
QStringList selector() const { return m_selector; }
void forceStop();
- void findPs();
void logcatProcess(const QByteArray &text, QByteArray &buffer, bool onlyError);
bool adbShellAmNeedsQuotes();
bool runAdb(const QStringList &args, QString *exitMessage = nullptr, int timeoutS = 10);
+ int deviceSdkVersion();
// Create the processes and timer in the worker thread, for correct thread affinity
std::unique_ptr<QProcess, decltype(&deleter)> m_adbLogcatProcess;
- std::unique_ptr<QProcess, decltype(&deleter)> m_psIsAlive;
+ std::unique_ptr<QProcess, decltype(&deleter)> m_pidsFinderProcess;
QScopedPointer<QTcpSocket> m_socket;
QByteArray m_stdoutBuffer;
QByteArray m_stderrBuffer;
- QFuture<qint64> m_pidFinder;
- qint64 m_processPID = -1;
+ QSet<qint64> m_processPids;
bool m_useCppDebugger = false;
QmlDebug::QmlDebugServicesPreset m_qmlDebugServices;
Utils::Port m_localGdbServerPort; // Local end of forwarded debug socket.
@@ -261,20 +236,20 @@ private:
QString m_gdbserverSocket;
QString m_adb;
QStringList m_selector;
- QRegExp m_logCatRegExp;
DebugHandShakeType m_handShakeMethod = SocketHandShake;
bool m_customPort = false;
QString m_packageName;
int m_socketHandShakePort = MIN_SOCKET_HANDSHAKE_PORT;
+ QByteArray m_pidsBuffer;
+ QScopedPointer<QTimer> m_timeoutTimer;
};
AndroidRunnerWorker::AndroidRunnerWorker(AndroidRunConfiguration *runConfig, Core::Id runMode,
const QString &packageName, const QStringList &selector)
: m_adbLogcatProcess(nullptr, deleter)
- , m_psIsAlive(nullptr, deleter)
+ , m_pidsFinderProcess(nullptr, deleter)
, m_selector(selector)
- , m_logCatRegExp(regExpLogcat)
, m_packageName(packageName)
{
Debugger::DebuggerRunConfigurationAspect *aspect
@@ -338,23 +313,18 @@ AndroidRunnerWorker::AndroidRunnerWorker(AndroidRunConfiguration *runConfig, Cor
AndroidRunnerWorker::~AndroidRunnerWorker()
{
- if (!m_pidFinder.isFinished())
- m_pidFinder.cancel();
}
void AndroidRunnerWorker::forceStop()
{
runAdb(selector() << "shell" << "am" << "force-stop" << m_packageName, nullptr, 30);
- // try killing it via kill -9
- const QByteArray out = Utils::SynchronousProcess()
- .runBlocking(m_adb, selector() << QStringLiteral("shell") << pidScript)
- .allRawOutput();
-
- qint64 pid = extractPID(out.simplified(), m_packageName);
- if (pid != -1) {
- adbKill(pid);
+ for (auto it = m_processPids.constBegin(); it != m_processPids.constEnd(); ++it) {
+ emit pidLost(*it);
+ adbKill(*it);
}
+ m_processPids.clear();
+ m_pidsBuffer.clear();
}
void AndroidRunnerWorker::asyncStart(const QString &intentName,
@@ -368,8 +338,12 @@ void AndroidRunnerWorker::asyncStart(const QString &intentName,
this, &AndroidRunnerWorker::logcatReadStandardOutput);
connect(logcatProcess.get(), &QProcess::readyReadStandardError,
this, &AndroidRunnerWorker::logcatReadStandardError);
+
// Its assumed that the device or avd returned by selector() is online.
- logcatProcess->start(m_adb, selector() << "logcat");
+ QStringList logcatArgs = selector() << "logcat" << "-v" << "time";
+ if (deviceSdkVersion() > 20)
+ logcatArgs << "-T" << "0";
+ logcatProcess->start(m_adb, logcatArgs);
QString errorMessage;
@@ -507,9 +481,20 @@ void AndroidRunnerWorker::asyncStart(const QString &intentName,
QTC_ASSERT(!m_adbLogcatProcess, /**/);
m_adbLogcatProcess = std::move(logcatProcess);
- m_pidFinder = Utils::onResultReady(Utils::runAsync(&findProcessPID, m_adb, selector(),
- m_packageName),
- bind(&AndroidRunnerWorker::onProcessIdChanged, this, _1));
+
+ 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);
}
bool AndroidRunnerWorker::adbShellAmNeedsQuotes()
@@ -545,6 +530,19 @@ bool AndroidRunnerWorker::runAdb(const QStringList &args, QString *exitMessage,
return response.result == Utils::SynchronousProcessResponse::Finished;
}
+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();
+}
+
void AndroidRunnerWorker::handleRemoteDebuggerRunning()
{
if (m_useCppDebugger) {
@@ -558,21 +556,79 @@ void AndroidRunnerWorker::handleRemoteDebuggerRunning()
runAdb(selector() << "push" << tmp.fileName() << m_pongFile);
}
- QTC_CHECK(m_processPID != -1);
+ QTC_CHECK(!m_processPids.isEmpty());
}
emit remoteProcessStarted(m_localGdbServerPort, m_qmlPort);
}
-void AndroidRunnerWorker::asyncStop(const QVector<QStringList> &adbCommands)
+void AndroidRunnerWorker::findProcessPids()
{
- if (!m_pidFinder.isFinished())
- m_pidFinder.cancel();
+ static QMap<qint64, QByteArray> extractedPids;
+ static auto oldPids = m_processPids;
- if (m_processPID != -1) {
- forceStop();
+ 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);
}
+}
+
+void AndroidRunnerWorker::asyncStop(const QVector<QStringList> &adbCommands)
+{
+ m_timeoutTimer.reset();
+ m_pidsFinderProcess.reset();
+ if (!m_processPids.isEmpty())
+ forceStop();
+
foreach (const QStringList &entry, adbCommands)
runAdb(selector() << entry);
+
+ m_adbLogcatProcess.reset();
+ emit remoteProcessFinished(QLatin1String("\n\n") +
+ tr("\"%1\" terminated.").arg(m_packageName));
}
void AndroidRunnerWorker::setAdbParameters(const QString &packageName, const QStringList &selector)
@@ -594,58 +650,48 @@ void AndroidRunnerWorker::logcatProcess(const QByteArray &text, QByteArray &buff
buffer.clear();
}
- QString pidString = QString::number(m_processPID);
foreach (const QByteArray &msg, lines) {
- const QString line = QString::fromUtf8(msg).trimmed() + QLatin1Char('\n');
- if (!line.contains(pidString))
- continue;
- 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")
- || messagetype == QLatin1String("W"))
- 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);
- }
+ const QString line = QString::fromUtf8(msg.trimmed());
+ if (onlyError)
+ emit remoteErrorOutput(line);
+ else
+ emit remoteOutput(line);
}
}
-void AndroidRunnerWorker::onProcessIdChanged(qint64 pid)
+void AndroidRunnerWorker::onProcessIdChanged(PidInfo pidInfo)
{
// Don't write to m_psProc from a different thread
QTC_ASSERT(QThread::currentThread() == thread(), return);
- m_processPID = pid;
- if (m_processPID == -1) {
+
+ 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) {
emit remoteProcessFinished(QLatin1String("\n\n") + tr("\"%1\" died.")
.arg(m_packageName));
// App died/killed. Reset log and monitor processes.
+ forceStop();
m_adbLogcatProcess.reset();
- m_psIsAlive.reset();
- } else {
+ m_timeoutTimer.reset();
+ } else if (isFirst) {
+ m_timeoutTimer.reset();
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());
- emit remoteServerRunning(serverChannel, m_processPID);
+ emit remoteServerRunning(serverChannel, pidInfo.pid);
} 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());
- emit remoteServerRunning(serverChannel, m_processPID);
+ emit remoteServerRunning(serverChannel, pidInfo.pid);
} else if (m_qmlDebugServices == QmlDebug::QmlProfilerServices) {
emit remoteProcessStarted(Utils::Port(), m_qmlPort);
} else {
@@ -653,27 +699,18 @@ void AndroidRunnerWorker::onProcessIdChanged(qint64 pid)
emit remoteProcessStarted(Utils::Port(), Utils::Port());
}
logcatReadStandardOutput();
- QTC_ASSERT(!m_psIsAlive, /**/);
- m_psIsAlive.reset(new QProcess);
- m_psIsAlive->setProcessChannelMode(QProcess::MergedChannels);
- connect(m_psIsAlive.get(), &QProcess::readyRead, [this](){
- if (!m_psIsAlive->readAll().simplified().isEmpty())
- onProcessIdChanged(-1);
- });
- m_psIsAlive->start(m_adb, selector() << QStringLiteral("shell")
- << pidPollingScript.arg(m_processPID));
}
}
void AndroidRunnerWorker::logcatReadStandardError()
{
- if (m_processPID != -1)
+ if (!m_processPids.isEmpty() && m_adbLogcatProcess)
logcatProcess(m_adbLogcatProcess->readAllStandardError(), m_stderrBuffer, true);
}
void AndroidRunnerWorker::logcatReadStandardOutput()
{
- if (m_processPID != -1)
+ if (!m_processPids.isEmpty() && m_adbLogcatProcess)
logcatProcess(m_adbLogcatProcess->readAllStandardOutput(), m_stdoutBuffer, false);
}
@@ -724,6 +761,10 @@ AndroidRunner::AndroidRunner(QObject *parent, RunConfiguration *runConfig, Core:
this, &AndroidRunner::remoteOutput);
connect(m_worker.data(), &AndroidRunnerWorker::remoteErrorOutput,
this, &AndroidRunner::remoteErrorOutput);
+ connect(m_worker.data(), &AndroidRunnerWorker::pidFound,
+ this, &AndroidRunner::pidFound);
+ connect(m_worker.data(), &AndroidRunnerWorker::pidLost,
+ this, &AndroidRunner::pidLost);
m_thread.start();
}
@@ -791,8 +832,9 @@ void AndroidRunner::launchAVD()
emit adbParametersChanged(m_androidRunnable.packageName,
AndroidDeviceInfo::adbSelector(info.serialNumber));
if (info.isValid()) {
- if (AndroidConfigurations::currentConfig().findAvd(info.avdname).isEmpty()) {
- bool launched = AndroidConfigurations::currentConfig().startAVDAsync(info.avdname);
+ AndroidAvdManager avdManager;
+ if (avdManager.findAvd(info.avdname).isEmpty()) {
+ bool launched = avdManager.startAvdAsync(info.avdname);
m_launchedAVDName = launched ? info.avdname:"";
} else {
m_launchedAVDName.clear();
@@ -803,11 +845,12 @@ void AndroidRunner::launchAVD()
void AndroidRunner::checkAVD()
{
const AndroidConfig &config = AndroidConfigurations::currentConfig();
- QString serialNumber = config.findAvd(m_launchedAVDName);
+ AndroidAvdManager avdManager(config);
+ QString serialNumber = avdManager.findAvd(m_launchedAVDName);
if (!serialNumber.isEmpty())
return; // try again on next timer hit
- if (config.hasFinishedBooting(serialNumber)) {
+ if (avdManager.isAvdBooted(serialNumber)) {
m_checkAVDTimer.stop();
AndroidManager::setDeviceSerialNumber(m_runConfig->target(), serialNumber);
emit asyncStart(m_androidRunnable.intentName, m_androidRunnable.beforeStartADBCommands);