diff --git a/src/plugins/android/androiddebugsupport.cpp b/src/plugins/android/androiddebugsupport.cpp index e52b233e561..583c2c5e117 100644 --- a/src/plugins/android/androiddebugsupport.cpp +++ b/src/plugins/android/androiddebugsupport.cpp @@ -145,7 +145,7 @@ AndroidDebugSupport::AndroidDebugSupport(AndroidRunConfiguration *runConfig, // FIXME: Move signal to base class and generalize handling. connect(m_runControl, &DebuggerRunControl::aboutToNotifyInferiorSetupOk, - m_runner, &AndroidRunner::handleRemoteDebuggerRunning); + m_runner, &AndroidRunner::remoteDebuggerRunning); connect(m_runner, &AndroidRunner::remoteServerRunning, [this](const QByteArray &serverChannel, int pid) { diff --git a/src/plugins/android/androidrunnable.h b/src/plugins/android/androidrunnable.h index 93daf8fc2d8..c6ff742112b 100644 --- a/src/plugins/android/androidrunnable.h +++ b/src/plugins/android/androidrunnable.h @@ -54,4 +54,9 @@ inline bool operator==(const AndroidRunnable &r1, const AndroidRunnable &r2) && r1.deviceSerialNumber == r2.deviceSerialNumber; } +inline bool operator!=(const AndroidRunnable &r1, const AndroidRunnable &r2) +{ + return !(r1 == r2); +} + } // namespace Android diff --git a/src/plugins/android/androidrunner.cpp b/src/plugins/android/androidrunner.cpp index 4821ebff914..dcb8e1b9b9a 100644 --- a/src/plugins/android/androidrunner.cpp +++ b/src/plugins/android/androidrunner.cpp @@ -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 &adbCommands); + void asyncStop(const QVector &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 m_adbLogcatProcess; + QScopedPointer m_psProc; + QScopedPointer m_checkPIDTimer; + QScopedPointer 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(); - 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 &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(); - - 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 &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 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"), + qRegisterMetaType("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" diff --git a/src/plugins/android/androidrunner.h b/src/plugins/android/androidrunner.h index 09ba931d7e0..c77180f874a 100644 --- a/src/plugins/android/androidrunner.h +++ b/src/plugins/android/androidrunner.h @@ -44,15 +44,11 @@ class AndroidRunConfiguration; namespace Internal { +class AndroidRunnerWorker; class AndroidRunner : public QObject { Q_OBJECT - enum DebugHandShakeType { - PingPongFiles, - SocketHandShake - }; - public: AndroidRunner(QObject *parent, AndroidRunConfiguration *runConfig, Core::Id runMode); @@ -62,63 +58,34 @@ public: void setRunnable(const AndroidRunnable &runnable); const AndroidRunnable &runnable() const { return m_androidRunnable; } -public: void start(); void stop(); - 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 remoteDebuggerRunning(); void remoteOutput(const QString &output); void remoteErrorOutput(const QString &output); + void asyncStart(const QString &intentName, const QVector &adbCommands); + void asyncStop(const QVector &adbCommands); + + void adbParametersChanged(const QString &packageName, const QStringList &selector); + void avdDetected(); + private: - void checkPID(); - void logcatReadStandardError(); - void logcatReadStandardOutput(); - void asyncStart(); - void asyncStop(); - Q_INVOKABLE void launchAVDProcesses(); - 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(); + void checkAVD(); void launchAVD(); - bool runAdb(const QStringList &args, QString *errorMessage = nullptr, int timeoutS = 10); -private: + + AndroidRunnable m_androidRunnable; AndroidRunConfiguration *m_runConfig; QString m_launchedAVDName; - QFutureInterface m_avdFutureInterface; - QProcess m_adbLogcatProcess; - QProcess m_psProc; - QTimer m_checkPIDTimer; - bool m_wasStarted; - int m_tries; - QByteArray m_stdoutBuffer; - QByteArray m_stderrBuffer; - AndroidRunnable m_androidRunnable; - qint64 m_processPID; - bool m_useCppDebugger; - 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; - QStringList m_selector; - QMutex m_mutex; - QRegExp m_logCatRegExp; - DebugHandShakeType m_handShakeMethod = SocketHandShake; - QTcpSocket *m_socket; - bool m_customPort; + QThread m_thread; + QTimer m_checkAVDTimer; + QScopedPointer m_worker; }; } // namespace Internal