Android: Run all of AndroidRunner in a separate thread

This removes the need for blocking queued connections and runAsync(),
reducing the risk of deadlocks and eliminating unguarded concurrent
access to various members of AndroidRunner. No mutex locking is
necessary anymore as all communication between the two threads is
either done on initialization, before the worker thread starts, or
via queued signals.

Change-Id: Icc2fcc2c0ce73d1c226bc4740413e57490d1cbc6
Reviewed-by: Tobias Hunger <tobias.hunger@qt.io>
This commit is contained in:
Ulf Hermann
2016-08-05 15:40:33 +02:00
parent 8c5dd4ad49
commit 5f950a80cc
4 changed files with 326 additions and 223 deletions

View File

@@ -122,20 +122,87 @@ typedef QLatin1String _;
const int MIN_SOCKET_HANDSHAKE_PORT = 20001;
const int MAX_SOCKET_HANDSHAKE_PORT = 20999;
static int socketHandShakePort = MIN_SOCKET_HANDSHAKE_PORT;
AndroidRunner::AndroidRunner(QObject *parent,
AndroidRunConfiguration *runConfig,
Core::Id runMode)
: QObject(parent)
, m_runConfig(runConfig)
, m_handShakeMethod(SocketHandShake), m_socket(0)
, m_customPort(false)
class AndroidRunnerWorker : public QObject
{
Q_OBJECT
enum DebugHandShakeType {
PingPongFiles,
SocketHandShake
};
public:
AndroidRunnerWorker(AndroidRunConfiguration *runConfig, Core::Id runMode,
const QString &packageName, const QStringList &selector);
void init();
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);
private:
void checkPID();
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);
// Create the processes and timer in the worker thread, for correct thread affinity
QScopedPointer<QProcess> m_adbLogcatProcess;
QScopedPointer<QProcess> m_psProc;
QScopedPointer<QTimer> m_checkPIDTimer;
QScopedPointer<QTcpSocket> m_socket;
bool m_wasStarted = false;
int m_tries = 0;
QByteArray m_stdoutBuffer;
QByteArray m_stderrBuffer;
qint64 m_processPID = -1;
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;
bool m_isBusyBox = false;
QStringList m_selector;
QRegExp m_logCatRegExp;
DebugHandShakeType m_handShakeMethod = SocketHandShake;
bool m_customPort = false;
QString m_packageName;
int m_socketHandShakePort = MIN_SOCKET_HANDSHAKE_PORT;
};
AndroidRunnerWorker::AndroidRunnerWorker(AndroidRunConfiguration *runConfig, Core::Id runMode,
const QString &packageName, const QStringList &selector)
: m_selector(selector), m_packageName(packageName)
{
m_tries = 0;
Debugger::DebuggerRunConfigurationAspect *aspect
= runConfig->extraAspect<Debugger::DebuggerRunConfigurationAspect>();
const bool debuggingMode = (runMode == ProjectExplorer::Constants::DEBUG_RUN_MODE || runMode == ProjectExplorer::Constants::DEBUG_RUN_MODE_WITH_BREAK_ON_MAIN);
const bool debuggingMode =
(runMode == ProjectExplorer::Constants::DEBUG_RUN_MODE
|| runMode == ProjectExplorer::Constants::DEBUG_RUN_MODE_WITH_BREAK_ON_MAIN);
m_useCppDebugger = debuggingMode && aspect->useCppDebugger();
if (debuggingMode && aspect->useQmlDebugger())
m_qmlDebugServices = QmlDebug::QmlDebuggerServices;
@@ -156,42 +223,19 @@ AndroidRunner::AndroidRunner(QObject *parent,
} else {
m_qmlPort = Utils::Port();
}
ProjectExplorer::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_processPID = -1;
m_adb = AndroidConfigurations::currentConfig().adbToolPath().toString();
m_selector = AndroidDeviceInfo::adbSelector(m_androidRunnable.deviceSerialNumber);
QString packageDir = _("/data/data/") + m_androidRunnable.packageName;
QString packageDir = _("/data/data/") + m_packageName;
m_pingFile = packageDir + _("/debug-ping");
m_pongFile = _("/data/local/tmp/qt/debug-pong-") + m_androidRunnable.packageName;
m_pongFile = _("/data/local/tmp/qt/debug-pong-") + m_packageName;
m_gdbserverSocket = packageDir + _("/debug-socket");
const QtSupport::BaseQtVersion *version = QtSupport::QtKitInformation::qtVersion(target->kit());
const QtSupport::BaseQtVersion *version = QtSupport::QtKitInformation::qtVersion(
runConfig->target()->kit());
if (version && version->qtVersion() >= QtSupport::QtVersionNumber(5, 4, 0))
m_gdbserverPath = packageDir + _("/lib/libgdbserver.so");
else
m_gdbserverPath = packageDir + _("/lib/gdbserver");
// Detect busybox, as we need to pass -w to ps to get wide output.
Utils::SynchronousProcess psProc;
psProc.setTimeoutS(5);
Utils::SynchronousProcessResponse response
= psProc.runBlocking(m_adb, selector() << _("shell") << _("readlink") << _("$(which ps)"));
const QString which = response.allOutput();
m_isBusyBox = which.startsWith("busybox");
m_checkPIDTimer.setInterval(1000);
connect(&m_adbLogcatProcess, &QProcess::readyReadStandardOutput,
this, &AndroidRunner::logcatReadStandardOutput);
connect(&m_adbLogcatProcess, &QProcess::readyReadStandardError,
this, &AndroidRunner::logcatReadStandardError);
connect(&m_checkPIDTimer, &QTimer::timeout, this, &AndroidRunner::checkPID);
if (version && version->qtVersion() >= QtSupport::QtVersionNumber(5, 4, 0)) {
if (qEnvironmentVariableIsSet("QTC_ANDROID_USE_FILE_HANDSHAKE"))
m_handShakeMethod = PingPongFiles;
@@ -206,11 +250,39 @@ AndroidRunner::AndroidRunner(QObject *parent,
int port = 0;
port = envData.toInt(&ok);
if (ok && port > 0 && port < 65535) {
socketHandShakePort = port;
m_socketHandShakePort = port;
m_customPort = true;
}
}
}
}
// This is run from the worker thread.
void AndroidRunnerWorker::init()
{
QTC_ASSERT(m_adbLogcatProcess.isNull(), /**/);
QTC_ASSERT(m_psProc.isNull(), /**/);
QTC_ASSERT(m_checkPIDTimer.isNull(), /**/);
m_adbLogcatProcess.reset(new QProcess);
m_psProc.reset(new QProcess);
m_checkPIDTimer.reset(new QTimer);
// Detect busybox, as we need to pass -w to ps to get wide output.
Utils::SynchronousProcess psProc;
psProc.setTimeoutS(5);
Utils::SynchronousProcessResponse response = psProc.runBlocking(
m_adb, selector() << _("shell") << _("readlink") << _("$(which ps)"));
const QString which = response.allOutput();
m_isBusyBox = which.startsWith("busybox");
m_checkPIDTimer->setInterval(1000);
connect(m_adbLogcatProcess.data(), &QProcess::readyReadStandardOutput,
this, &AndroidRunnerWorker::logcatReadStandardOutput);
connect(m_adbLogcatProcess.data(), &QProcess::readyReadStandardError,
this, &AndroidRunnerWorker::logcatReadStandardError);
connect(m_checkPIDTimer.data(), &QTimer::timeout, this, &AndroidRunnerWorker::checkPID);
m_logCatRegExp = QRegExp(QLatin1String("[0-9\\-]*" // date
"\\s+"
@@ -228,12 +300,6 @@ AndroidRunner::AndroidRunner(QObject *parent,
));
}
AndroidRunner::~AndroidRunner()
{
//stop();
delete m_socket;
}
static int extractPidFromChunk(const QByteArray &chunk, int from)
{
int pos1 = chunk.indexOf(' ', from);
@@ -258,31 +324,27 @@ static int extractPid(const QString &exeName, const QByteArray &psOutput)
return extractPidFromChunk(psOutput, from);
}
void AndroidRunner::launchAVDProcesses()
{
// Its assumed that the device or avd serial returned by selector() is online.
m_adbLogcatProcess.start(m_adb, selector() << _("logcat"));
m_psProc.start(m_adb, selector() << _("shell"));
}
void AndroidRunner::checkPID()
void AndroidRunnerWorker::checkPID()
{
// Don't write to m_psProc from a different thread
QTC_ASSERT(QThread::currentThread() == thread(), return);
m_checkPIDTimer->stop();
QByteArray psLine(m_isBusyBox ? "ps -w\n" : "ps\n");
m_psProc.write(psLine);
m_psProc.waitForBytesWritten(psLine.size());
m_processPID = extractPid(m_androidRunnable.packageName, m_psProc.readAllStandardOutput());
m_psProc->write(psLine);
m_psProc->waitForBytesWritten(psLine.size());
m_processPID = extractPid(m_packageName, m_psProc->readAllStandardOutput());
if (m_processPID == -1) {
if (m_wasStarted) {
m_wasStarted = false;
m_checkPIDTimer.stop();
emit remoteProcessFinished(QLatin1String("\n\n") + tr("\"%1\" died.").arg(m_androidRunnable.packageName));
emit remoteProcessFinished(QLatin1String("\n\n") + tr("\"%1\" died.")
.arg(m_packageName));
return; // Don't restart the timer
} else {
if (++m_tries > 3)
emit remoteProcessFinished(QLatin1String("\n\n") + tr("Unable to start \"%1\".").arg(m_androidRunnable.packageName));
emit remoteProcessFinished(QLatin1String("\n\n") + tr("Unable to start \"%1\".")
.arg(m_packageName));
}
} else if (!m_wasStarted){
if (m_useCppDebugger) {
@@ -304,15 +366,12 @@ void AndroidRunner::checkPID()
m_wasStarted = true;
logcatReadStandardOutput();
}
m_checkPIDTimer->start();
}
void AndroidRunner::forceStop()
void AndroidRunnerWorker::forceStop()
{
// Don't run Utils::SynchronousProcess on the GUI thread
QTC_ASSERT(QThread::currentThread() != thread(), return);
runAdb(selector() << _("shell") << _("am") << _("force-stop") << m_androidRunnable.packageName,
nullptr, 30);
runAdb(selector() << _("shell") << _("am") << _("force-stop") << m_packageName, nullptr, 30);
// try killing it via kill -9
const QByteArray out = Utils::SynchronousProcess()
@@ -325,7 +384,7 @@ void AndroidRunner::forceStop()
if (to == -1)
break;
QString line = QString::fromUtf8(out.data() + from, to - from - 1);
if (line.endsWith(m_androidRunnable.packageName) || line.endsWith(m_gdbserverPath)) {
if (line.endsWith(m_packageName) || line.endsWith(m_gdbserverPath)) {
int pid = extractPidFromChunk(out, from);
adbKill(pid);
}
@@ -333,41 +392,12 @@ void AndroidRunner::forceStop()
}
}
void AndroidRunner::start()
void AndroidRunnerWorker::asyncStart(const QString &intentName,
const QVector<QStringList> &adbCommands)
{
if (!ProjectExplorerPlugin::projectExplorerSettings().deployBeforeRun) {
// User choose to run the app without deployment. Start the AVD if not running.
launchAVD();
}
Utils::runAsync(&AndroidRunner::asyncStart, this).waitForFinished();
}
void AndroidRunner::asyncStart()
{
if (!ProjectExplorerPlugin::projectExplorerSettings().deployBeforeRun && !m_launchedAVDName.isEmpty()) {
// AVD was started. Wait for the avd to boot before launching the app.
m_avdFutureInterface = QFutureInterface<bool>();
m_avdFutureInterface.reportStarted();
QString serialNumber = AndroidConfigurations::currentConfig()
.waitForAvd(m_launchedAVDName, m_avdFutureInterface);
if (m_avdFutureInterface.isCanceled()) {
// User stopped the run step before AVD start. Bail out.
m_avdFutureInterface.reportFinished();
return;
} else {
QMetaObject::invokeMethod(this, "launchAVDProcesses", Qt::BlockingQueuedConnection);
AndroidManager::setDeviceSerialNumber(m_runConfig->target(), serialNumber);
m_avdFutureInterface.reportFinished();
}
} else {
// AVD/device available.
QMetaObject::invokeMethod(this, "launchAVDProcesses", Qt::BlockingQueuedConnection);
}
QMutexLocker locker(&m_mutex);
// Its assumed that the device or avd serial returned by selector() is online.
m_adbLogcatProcess->start(m_adb, selector() << _("logcat"));
m_psProc->start(m_adb, selector() << _("shell"));
forceStop();
QString errorMessage;
@@ -375,11 +405,11 @@ void AndroidRunner::asyncStart()
if (m_useCppDebugger)
runAdb(selector() << _("shell") << _("rm") << m_pongFile); // Remove pong file.
foreach (const QStringList &entry, m_androidRunnable.beforeStartADBCommands)
foreach (const QStringList &entry, adbCommands)
runAdb(selector() << entry);
QStringList args = selector();
args << _("shell") << _("am") << _("start") << _("-n") << m_androidRunnable.intentName;
args << _("shell") << _("am") << _("start") << _("-n") << intentName;
if (m_useCppDebugger) {
if (!runAdb(selector() << _("forward")
@@ -389,7 +419,7 @@ void AndroidRunner::asyncStart()
return;
}
const QString pingPongSocket(m_androidRunnable.packageName + _(".ping_pong_socket"));
const QString pingPongSocket(m_packageName + _(".ping_pong_socket"));
args << _("-e") << _("debug_ping") << _("true");
if (m_handShakeMethod == SocketHandShake) {
args << _("-e") << _("ping_socket") << pingPongSocket;
@@ -404,9 +434,11 @@ void AndroidRunner::asyncStart()
args << _("-e") << _("gdbserver_socket") << m_gdbserverSocket;
if (m_handShakeMethod == SocketHandShake) {
const QString port = QString::fromLatin1("tcp:%1").arg(socketHandShakePort);
if (!runAdb(selector() << _("forward") << port << _("localabstract:") + pingPongSocket, &errorMessage)) {
emit remoteProcessFinished(tr("Failed to forward ping pong ports. Reason: %1.").arg(errorMessage));
const QString port = QString::fromLatin1("tcp:%1").arg(m_socketHandShakePort);
if (!runAdb(selector() << _("forward") << port << _("localabstract:") + pingPongSocket,
&errorMessage)) {
emit remoteProcessFinished(tr("Failed to forward ping pong ports. Reason: %1.")
.arg(errorMessage));
return;
}
}
@@ -416,7 +448,8 @@ void AndroidRunner::asyncStart()
// currently forward to same port on device and host
const QString port = QString::fromLatin1("tcp:%1").arg(m_qmlPort.number());
if (!runAdb(selector() << _("forward") << port << port, &errorMessage)) {
emit remoteProcessFinished(tr("Failed to forward QML debugging ports. Reason: %1.").arg(errorMessage));
emit remoteProcessFinished(tr("Failed to forward QML debugging ports. Reason: %1.")
.arg(errorMessage));
return;
}
@@ -427,7 +460,8 @@ void AndroidRunner::asyncStart()
}
if (!runAdb(args, &errorMessage)) {
emit remoteProcessFinished(tr("Failed to start the activity. Reason: %1.").arg(errorMessage));
emit remoteProcessFinished(tr("Failed to start the activity. Reason: %1.")
.arg(errorMessage));
return;
}
@@ -436,13 +470,12 @@ void AndroidRunner::asyncStart()
//Handling socket
bool wasSuccess = false;
const int maxAttempts = 20; //20 seconds
if (m_socket)
delete m_socket;
m_socket = new QTcpSocket();
m_socket.reset(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);
m_socket->connectToHost(QHostAddress(QStringLiteral("127.0.0.1")),
m_socketHandShakePort);
if (!m_socket->waitForConnected())
continue;
@@ -458,7 +491,6 @@ void AndroidRunner::asyncStart()
}
wasSuccess = true;
m_socket->moveToThread(QApplication::instance()->thread());
break;
}
@@ -469,10 +501,10 @@ void AndroidRunner::asyncStart()
if (!m_customPort) {
// increment running port to avoid clash when using multiple
// debug sessions at the same time
socketHandShakePort++;
m_socketHandShakePort++;
// wrap ports around to avoid overflow
if (socketHandShakePort == MAX_SOCKET_HANDSHAKE_PORT)
socketHandShakePort = MIN_SOCKET_HANDSHAKE_PORT;
if (m_socketHandShakePort == MAX_SOCKET_HANDSHAKE_PORT)
m_socketHandShakePort = MIN_SOCKET_HANDSHAKE_PORT;
}
} else {
// Handling ping.
@@ -490,7 +522,7 @@ void AndroidRunner::asyncStart()
break;
if (i == 20) {
emit remoteProcessFinished(tr("Unable to start \"%1\".").arg(m_androidRunnable.packageName));
emit remoteProcessFinished(tr("Unable to start \"%1\".").arg(m_packageName));
return;
}
qDebug() << "WAITING FOR " << tmp.fileName();
@@ -502,10 +534,10 @@ void AndroidRunner::asyncStart()
m_tries = 0;
m_wasStarted = false;
QMetaObject::invokeMethod(&m_checkPIDTimer, "start");
m_checkPIDTimer->start();
}
bool AndroidRunner::adbShellAmNeedsQuotes()
bool AndroidRunnerWorker::adbShellAmNeedsQuotes()
{
// Between Android SDK Tools version 24.3.1 and 24.3.4 the quoting
// needs for the 'adb shell am start ...' parameters changed.
@@ -527,42 +559,18 @@ bool AndroidRunner::adbShellAmNeedsQuotes()
return !oldSdk;
}
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;
m_selector = AndroidDeviceInfo::adbSelector(info.serialNumber);
if (info.isValid()) {
if (AndroidConfigurations::currentConfig().findAvd(info.avdname).isEmpty()) {
bool launched = AndroidConfigurations::currentConfig().startAVDAsync(info.avdname);
m_launchedAVDName = launched ? info.avdname:"";
} else {
m_launchedAVDName.clear();
}
}
}
bool AndroidRunner::runAdb(const QStringList &args, QString *errorMessage, int timeoutS)
bool AndroidRunnerWorker::runAdb(const QStringList &args, QString *exitMessage, int timeoutS)
{
Utils::SynchronousProcess adb;
adb.setTimeoutS(timeoutS);
Utils::SynchronousProcessResponse response
= adb.run(m_adb, args);
if (errorMessage)
*errorMessage = response.exitMessage(m_adb, timeoutS);
if (exitMessage)
*exitMessage = response.exitMessage(m_adb, timeoutS);
return response.result == Utils::SynchronousProcessResponse::Finished;
}
void AndroidRunner::handleRemoteDebuggerRunning()
void AndroidRunnerWorker::handleRemoteDebuggerRunning()
{
if (m_useCppDebugger) {
if (m_handShakeMethod == SocketHandShake) {
@@ -580,35 +588,32 @@ void AndroidRunner::handleRemoteDebuggerRunning()
emit remoteProcessStarted(m_localGdbServerPort, m_qmlPort);
}
void AndroidRunner::stop()
void AndroidRunnerWorker::asyncStop(const QVector<QStringList> &adbCommands)
{
if (m_avdFutureInterface.isRunning()) {
m_avdFutureInterface.cancel();
m_avdFutureInterface.waitForFinished();
emit remoteProcessFinished(QLatin1String("\n\n") + tr("\"%1\" terminated.").arg(m_androidRunnable.packageName));
}
m_checkPIDTimer->stop();
m_adbLogcatProcess->kill();
m_psProc->kill();
m_checkPIDTimer.stop();
m_adbLogcatProcess.kill();
m_psProc.kill();
Utils::runAsync(&AndroidRunner::asyncStop, this).waitForFinished();
m_adbLogcatProcess.waitForFinished();
m_psProc.waitForFinished();
}
void AndroidRunner::asyncStop()
{
QMutexLocker locker(&m_mutex);
m_tries = 0;
if (m_processPID != -1) {
forceStop();
emit remoteProcessFinished(QLatin1String("\n\n") + tr("\"%1\" terminated.").arg(m_androidRunnable.packageName));
emit remoteProcessFinished(QLatin1String("\n\n") + tr("\"%1\" terminated.")
.arg(m_packageName));
}
foreach (const QStringList &entry, m_androidRunnable.afterFinishADBCommands)
foreach (const QStringList &entry, adbCommands)
runAdb(selector() << entry);
m_adbLogcatProcess->waitForFinished();
m_psProc->waitForFinished();
}
void AndroidRunner::logcatProcess(const QByteArray &text, QByteArray &buffer, bool onlyError)
void AndroidRunnerWorker::setAdbParameters(const QString &packageName, const QStringList &selector)
{
m_packageName = packageName;
m_selector = selector;
}
void AndroidRunnerWorker::logcatProcess(const QByteArray &text, QByteArray &buffer, bool onlyError)
{
QList<QByteArray> lines = text.split('\n');
// lines always contains at least one item
@@ -651,25 +656,103 @@ void AndroidRunner::logcatProcess(const QByteArray &text, QByteArray &buffer, bo
}
}
void AndroidRunner::logcatReadStandardError()
void AndroidRunnerWorker::logcatReadStandardError()
{
if (m_processPID != -1)
logcatProcess(m_adbLogcatProcess.readAllStandardError(), m_stderrBuffer, true);
logcatProcess(m_adbLogcatProcess->readAllStandardError(), m_stderrBuffer, true);
}
void AndroidRunner::logcatReadStandardOutput()
void AndroidRunnerWorker::logcatReadStandardOutput()
{
if (m_processPID != -1)
logcatProcess(m_adbLogcatProcess.readAllStandardOutput(), m_stdoutBuffer, false);
logcatProcess(m_adbLogcatProcess->readAllStandardOutput(), m_stdoutBuffer, false);
}
void AndroidRunner::adbKill(qint64 pid)
void AndroidRunnerWorker::adbKill(qint64 pid)
{
runAdb(selector() << _("shell") << _("kill") << QLatin1String("-9") << QString::number(pid));
runAdb(selector() << _("shell") << _("run-as") << m_androidRunnable.packageName
runAdb(selector() << _("shell") << _("run-as") << m_packageName
<< _("kill") << QLatin1String("-9") << QString::number(pid));
}
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(&m_thread, &QThread::started, m_worker.data(), &AndroidRunnerWorker::init);
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);
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);
}
QString AndroidRunner::displayName() const
{
return m_androidRunnable.packageName;
@@ -677,9 +760,57 @@ QString AndroidRunner::displayName() const
void AndroidRunner::setRunnable(const AndroidRunnable &runnable)
{
m_androidRunnable = runnable;
m_selector = AndroidDeviceInfo::adbSelector(m_androidRunnable.deviceSerialNumber);
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()) {
if (AndroidConfigurations::currentConfig().findAvd(info.avdname).isEmpty()) {
bool launched = AndroidConfigurations::currentConfig().startAVDAsync(info.avdname);
m_launchedAVDName = launched ? info.avdname:"";
} else {
m_launchedAVDName.clear();
}
}
}
void AndroidRunner::checkAVD()
{
const AndroidConfig &config = AndroidConfigurations::currentConfig();
QString serialNumber = config.findAvd(m_launchedAVDName);
if (!serialNumber.isEmpty())
return; // try again on next timer hit
if (config.hasFinishedBooting(serialNumber)) {
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();
}
}
} // namespace Internal
} // namespace Android
#include "androidrunner.moc"