summaryrefslogtreecommitdiff
path: root/src/plugins/android
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/android')
-rw-r--r--src/plugins/android/android.pro2
-rw-r--r--src/plugins/android/android.qbs2
-rw-r--r--src/plugins/android/androiddeployqtstep.cpp12
-rw-r--r--src/plugins/android/androidmanager.cpp10
-rw-r--r--src/plugins/android/androidmanager.h3
-rw-r--r--src/plugins/android/androidrunnable.h1
-rw-r--r--src/plugins/android/androidrunner.cpp611
-rw-r--r--src/plugins/android/androidrunner.h4
-rw-r--r--src/plugins/android/androidrunnerworker.cpp569
-rw-r--r--src/plugins/android/androidrunnerworker.h134
10 files changed, 749 insertions, 599 deletions
diff --git a/src/plugins/android/android.pro b/src/plugins/android/android.pro
index 9d2803e551..434966b075 100644
--- a/src/plugins/android/android.pro
+++ b/src/plugins/android/android.pro
@@ -18,6 +18,7 @@ HEADERS += \
androiderrormessage.h \
androidglobal.h \
androidrunner.h \
+ androidrunnerworker.h \
androiddebugsupport.h \
androidqtversionfactory.h \
androidqtversion.h \
@@ -66,6 +67,7 @@ SOURCES += \
androidtoolchain.cpp \
androiderrormessage.cpp \
androidrunner.cpp \
+ androidrunnerworker.cpp \
androiddebugsupport.cpp \
androidqtversionfactory.cpp \
androidqtversion.cpp \
diff --git a/src/plugins/android/android.qbs b/src/plugins/android/android.qbs
index 8f095013b5..18187f00e0 100644
--- a/src/plugins/android/android.qbs
+++ b/src/plugins/android/android.qbs
@@ -93,6 +93,8 @@ Project {
"androidrunnable.h",
"androidrunner.cpp",
"androidrunner.h",
+ "androidrunnerworker.cpp",
+ "androidrunnerworker.h",
"androidsdkmanager.cpp",
"androidsdkmanager.h",
"androidsdkmanagerwidget.cpp",
diff --git a/src/plugins/android/androiddeployqtstep.cpp b/src/plugins/android/androiddeployqtstep.cpp
index 5e264117e9..5cf723fe69 100644
--- a/src/plugins/android/androiddeployqtstep.cpp
+++ b/src/plugins/android/androiddeployqtstep.cpp
@@ -164,6 +164,7 @@ bool AndroidDeployQtStep::init(QList<const BuildStep *> &earlierSteps)
m_filesToPull["/system/" + libDirName + "/libc.so"] = buildDir + "libc.so";
AndroidManager::setDeviceSerialNumber(target(), m_serialNumber);
+ AndroidManager::setDeviceApiLevel(target(), info.sdk);
QtSupport::BaseQtVersion *version = QtSupport::QtKitInformation::qtVersion(target()->kit());
@@ -325,11 +326,12 @@ AndroidDeployQtStep::DeployErrorCode AndroidDeployQtStep::runDeploy(QFutureInter
emit addOutput(tr("The process \"%1\" crashed.").arg(m_command), BuildStep::OutputFormat::ErrorMessage);
}
- if (exitCode == 0 && exitStatus == QProcess::NormalExit) {
- if (deployError != NoError && m_uninstallPreviousPackageRun) {
- deployError = Failure;
- }
- } else {
+ if (deployError != NoError) {
+ if (m_uninstallPreviousPackageRun)
+ deployError = Failure; // Even re-install failed. Set to Failure.
+ } else if (exitCode != 0 || exitStatus != QProcess::NormalExit) {
+ // Set the deployError to Failure when no deployError code was detected
+ // but the adb tool failed otherwise relay the detected deployError.
deployError = Failure;
}
diff --git a/src/plugins/android/androidmanager.cpp b/src/plugins/android/androidmanager.cpp
index c361ce2490..7a757d7f07 100644
--- a/src/plugins/android/androidmanager.cpp
+++ b/src/plugins/android/androidmanager.cpp
@@ -241,6 +241,16 @@ void AndroidManager::setDeviceSerialNumber(ProjectExplorer::Target *target, cons
target->setNamedSettings(AndroidDeviceSn, deviceSerialNumber);
}
+int AndroidManager::deviceApiLevel(ProjectExplorer::Target *target)
+{
+ return target->namedSettings(ApiLevelKey).toInt();
+}
+
+void AndroidManager::setDeviceApiLevel(ProjectExplorer::Target *target, int level)
+{
+ target->setNamedSettings(ApiLevelKey, level);
+}
+
QPair<int, int> AndroidManager::apiLevelRange()
{
return qMakePair(9, 26);
diff --git a/src/plugins/android/androidmanager.h b/src/plugins/android/androidmanager.h
index d6e175c66f..b1ce332131 100644
--- a/src/plugins/android/androidmanager.h
+++ b/src/plugins/android/androidmanager.h
@@ -56,6 +56,9 @@ public:
static QString deviceSerialNumber(ProjectExplorer::Target *target);
static void setDeviceSerialNumber(ProjectExplorer::Target *target, const QString &deviceSerialNumber);
+ static int deviceApiLevel(ProjectExplorer::Target *target);
+ static void setDeviceApiLevel(ProjectExplorer::Target *target, int level);
+
static QString buildTargetSDK(ProjectExplorer::Target *target);
static bool signPackage(ProjectExplorer::Target *target);
diff --git a/src/plugins/android/androidrunnable.h b/src/plugins/android/androidrunnable.h
index b9cf920285..6d6fdb8f90 100644
--- a/src/plugins/android/androidrunnable.h
+++ b/src/plugins/android/androidrunnable.h
@@ -42,6 +42,7 @@ struct ANDROID_EXPORT AndroidRunnable
QString deviceSerialNumber;
QString extraAppParams;
Utils::Environment extraEnvVars;
+ int apiLevel = -1;
QString displayName() const { return packageName; }
static void *staticTypeId;
diff --git a/src/plugins/android/androidrunner.cpp b/src/plugins/android/androidrunner.cpp
index 9e98789c8c..b4e3d11e9a 100644
--- a/src/plugins/android/androidrunner.cpp
+++ b/src/plugins/android/androidrunner.cpp
@@ -28,35 +28,17 @@
#include "androiddeployqtstep.h"
#include "androidconfigurations.h"
-#include "androidglobal.h"
#include "androidrunconfiguration.h"
#include "androidmanager.h"
#include "androidavdmanager.h"
+#include "androidrunnerworker.h"
-#include <debugger/debuggerrunconfigurationaspect.h>
#include <coreplugin/messagemanager.h>
#include <projectexplorer/projectexplorer.h>
-#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/projectexplorersettings.h>
#include <projectexplorer/target.h>
-#include <qtsupport/qtkitinformation.h>
-#include <utils/qtcassert.h>
-#include <utils/runextensions.h>
-#include <utils/synchronousprocess.h>
-#include <utils/temporaryfile.h>
#include <utils/url.h>
-#include <chrono>
-#include <memory>
-#include <QApplication>
-#include <QDir>
-#include <QRegExp>
-#include <QTime>
-#include <QTcpServer>
-#include <QTcpSocket>
-
-using namespace std;
-using namespace std::placeholders;
using namespace ProjectExplorer;
using namespace Utils;
@@ -127,568 +109,10 @@ using namespace Utils;
namespace Android {
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 regExpLogcat = QStringLiteral("[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]*"
- );
-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;
-}
-
-void findProcessPID(QFutureInterface<qint64> &fi, const QString &adbPath,
- QStringList selector, const QString &packageName)
-{
- 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);
-}
-
-static void deleter(QProcess *p)
-{
- p->kill();
- p->waitForFinished();
- // Might get deleted from its own signal handler.
- p->deleteLater();
-}
-
-class AndroidRunnerWorker : public QObject
-{
- Q_OBJECT
-
- enum DebugHandShakeType {
- PingPongFiles,
- SocketHandShake
- };
-
-public:
- AndroidRunnerWorker(RunControl *runControl, const AndroidRunnable &runnable);
- ~AndroidRunnerWorker();
-
- void asyncStart();
- void asyncStop();
-
- void setAndroidRunnable(const AndroidRunnable &runnable);
- void handleRemoteDebuggerRunning();
-
-signals:
- void remoteProcessStarted(Utils::Port gdbServerPort, const QUrl &qmlServer, int pid);
- void remoteProcessFinished(const QString &errString = QString());
-
- void remoteOutput(const QString &output);
- void remoteErrorOutput(const QString &output);
-
-private:
- void onProcessIdChanged(qint64 pid);
- void logcatReadStandardError();
- void logcatReadStandardOutput();
- void adbKill(qint64 pid);
- QStringList selector() const;
- 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
- std::unique_ptr<QProcess, decltype(&deleter)> m_adbLogcatProcess;
- std::unique_ptr<QProcess, decltype(&deleter)> m_psIsAlive;
- QScopedPointer<QTcpSocket> m_socket;
-
- QByteArray m_stdoutBuffer;
- QByteArray m_stderrBuffer;
-
- QFuture<qint64> m_pidFinder;
- qint64 m_processPID = -1;
- bool m_useCppDebugger = false;
- QmlDebug::QmlDebugServicesPreset m_qmlDebugServices;
- Utils::Port m_localGdbServerPort; // Local end of forwarded debug socket.
- QUrl m_qmlServer;
- QString m_pingFile;
- QString m_pongFile;
- QString m_gdbserverPath;
- QString m_gdbserverSocket;
- QString m_adb;
- QRegExp m_logCatRegExp;
- DebugHandShakeType m_handShakeMethod = SocketHandShake;
- bool m_customPort = false;
-
- AndroidRunnable m_androidRunnable;
- int m_socketHandShakePort = MIN_SOCKET_HANDSHAKE_PORT;
-};
-
-AndroidRunnerWorker::AndroidRunnerWorker(RunControl *runControl, const AndroidRunnable &runnable)
- : m_adbLogcatProcess(nullptr, deleter)
- , m_psIsAlive(nullptr, deleter)
- , m_logCatRegExp(regExpLogcat)
- , m_androidRunnable(runnable)
-{
- auto runConfig = runControl->runConfiguration();
- auto aspect = runConfig->extraAspect<Debugger::DebuggerRunConfigurationAspect>();
- Core::Id runMode = runControl->runMode();
- const bool debuggingMode = runMode == ProjectExplorer::Constants::DEBUG_RUN_MODE;
- m_useCppDebugger = debuggingMode && aspect->useCppDebugger();
- if (debuggingMode && aspect->useQmlDebugger())
- m_qmlDebugServices = QmlDebug::QmlDebuggerServices;
- else if (runMode == ProjectExplorer::Constants::QML_PROFILER_RUN_MODE)
- m_qmlDebugServices = QmlDebug::QmlProfilerServices;
- else if (runMode == ProjectExplorer::Constants::QML_PREVIEW_RUN_MODE)
- m_qmlDebugServices = QmlDebug::QmlPreviewServices;
- else
- m_qmlDebugServices = QmlDebug::NoQmlDebugServices;
- m_localGdbServerPort = Utils::Port(5039);
- QTC_CHECK(m_localGdbServerPort.isValid());
- if (m_qmlDebugServices != QmlDebug::NoQmlDebugServices) {
- QTcpServer server;
- QTC_ASSERT(server.listen(QHostAddress::LocalHost)
- || server.listen(QHostAddress::LocalHostIPv6),
- qDebug() << tr("No free ports available on host for QML debugging."));
- m_qmlServer.setScheme(Utils::urlTcpScheme());
- m_qmlServer.setHost(server.serverAddress().toString());
- m_qmlServer.setPort(server.serverPort());
- }
- m_adb = AndroidConfigurations::currentConfig().adbToolPath().toString();
-
- QString packageDir = "/data/data/" + m_androidRunnable.packageName;
- m_pingFile = packageDir + "/debug-ping";
- m_pongFile = "/data/local/tmp/qt/debug-pong-" + m_androidRunnable.packageName;
- m_gdbserverSocket = packageDir + "/debug-socket";
- 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";
-
- 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) {
- m_socketHandShakePort = port;
- m_customPort = true;
- }
- }
- }
-}
-
-AndroidRunnerWorker::~AndroidRunnerWorker()
-{
- if (!m_pidFinder.isFinished())
- m_pidFinder.cancel();
-}
-
-void AndroidRunnerWorker::forceStop()
-{
- runAdb({"shell", "am", "force-stop", m_androidRunnable.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_androidRunnable.packageName);
- if (pid != -1) {
- adbKill(pid);
- }
-}
-
-void AndroidRunnerWorker::asyncStart()
-{
- forceStop();
-
- // Start the logcat process before app starts.
- std::unique_ptr<QProcess, decltype(&deleter)> logcatProcess(new QProcess, deleter);
- connect(logcatProcess.get(), &QProcess::readyReadStandardOutput,
- 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");
-
- QString errorMessage;
-
- if (m_useCppDebugger)
- runAdb({"shell", "rm", m_pongFile}); // Remove pong file.
-
- for (const QString &entry: m_androidRunnable.beforeStartAdbCommands)
- runAdb(entry.split(' ', QString::SkipEmptyParts));
-
- QStringList args({"shell", "am", "start"});
- args << m_androidRunnable.amStartExtraArgs;
- args << "-n" << m_androidRunnable.intentName;
-
- if (m_useCppDebugger) {
- if (!runAdb({"forward", "--remove", "tcp:" + m_localGdbServerPort.toString()})){
- QTC_CHECK(false);
- }
- if (!runAdb({"forward", "tcp:" + m_localGdbServerPort.toString(),
- "localfilesystem:" + m_gdbserverSocket}, &errorMessage)) {
- emit remoteProcessFinished(tr("Failed to forward C++ debugging ports. Reason: %1.").arg(errorMessage));
- return;
- }
-
- const QString pingPongSocket(m_androidRunnable.packageName + ".ping_pong_socket");
- args << "-e" << "debug_ping" << "true";
- 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;
- }
-
- QString gdbserverCommand = QString::fromLatin1(adbShellAmNeedsQuotes() ? "\"%1 --multi +%2\"" : "%1 --multi +%2")
- .arg(m_gdbserverPath).arg(m_gdbserverSocket);
- args << "-e" << "gdbserver_command" << gdbserverCommand;
- args << "-e" << "gdbserver_socket" << m_gdbserverSocket;
-
- if (m_handShakeMethod == SocketHandShake) {
- const QString port = QString("tcp:%1").arg(m_socketHandShakePort);
- if (!runAdb({"forward", port, "localabstract:" + pingPongSocket}, &errorMessage)) {
- emit remoteProcessFinished(tr("Failed to forward ping pong ports. Reason: %1.")
- .arg(errorMessage));
- return;
- }
- }
- }
-
- if (m_qmlDebugServices != QmlDebug::NoQmlDebugServices) {
- // currently forward to same port on device and host
- const QString port = QString("tcp:%1").arg(m_qmlServer.port());
- if (!runAdb({"forward", port, port}, &errorMessage)) {
- emit remoteProcessFinished(tr("Failed to forward QML debugging ports. Reason: %1.")
- .arg(errorMessage));
- return;
- }
-
- args << "-e" << "qml_debug" << "true"
- << "-e" << "qmljsdebugger"
- << QString("port:%1,block,services:%2")
- .arg(m_qmlServer.port()).arg(QmlDebug::qmlDebugServices(m_qmlDebugServices));
- }
-
- if (!m_androidRunnable.extraAppParams.isEmpty()) {
- args << "-e" << "extraappparams"
- << QString::fromLatin1(m_androidRunnable.extraAppParams.toUtf8().toBase64());
- }
-
- if (m_androidRunnable.extraEnvVars.size() > 0) {
- args << "-e" << "extraenvvars"
- << QString::fromLatin1(m_androidRunnable.extraEnvVars.toStringList().join('\t')
- .toUtf8().toBase64());
- }
-
- if (!runAdb(args, &errorMessage)) {
- emit remoteProcessFinished(tr("Failed to start the activity. Reason: %1.")
- .arg(errorMessage));
- return;
- }
-
- if (m_useCppDebugger) {
- if (m_handShakeMethod == SocketHandShake) {
- //Handling socket
- bool wasSuccess = false;
- const int maxAttempts = 20; //20 seconds
- 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")),
- m_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;
-
- break;
- }
-
- if (!m_customPort) {
- // increment running port to avoid clash when using multiple
- // debug sessions at the same time
- m_socketHandShakePort++;
- // wrap ports around to avoid overflow
- if (m_socketHandShakePort == MAX_SOCKET_HANDSHAKE_PORT)
- m_socketHandShakePort = MIN_SOCKET_HANDSHAKE_PORT;
- }
-
- if (!wasSuccess) {
- emit remoteProcessFinished(tr("Failed to contact debugging port."));
- return;
- }
- } else {
- // Handling ping.
- for (int i = 0; ; ++i) {
- Utils::TemporaryFile tmp("pingpong");
- tmp.open();
- tmp.close();
-
- runAdb({"pull", m_pingFile, tmp.fileName()});
-
- QFile res(tmp.fileName());
- const bool doBreak = res.size();
- res.remove();
- if (doBreak)
- break;
-
- if (i == 20) {
- emit remoteProcessFinished(tr("Unable to start \"%1\".")
- .arg(m_androidRunnable.packageName));
- return;
- }
- qDebug() << "WAITING FOR " << tmp.fileName();
- QThread::msleep(500);
- }
- }
-
- }
-
- QTC_ASSERT(!m_adbLogcatProcess, /**/);
- m_adbLogcatProcess = std::move(logcatProcess);
- m_pidFinder = Utils::onResultReady(Utils::runAsync(&findProcessPID, m_adb, selector(),
- m_androidRunnable.packageName),
- bind(&AndroidRunnerWorker::onProcessIdChanged, this, _1));
-
-}
-
-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.
- // 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.
- Utils::SynchronousProcess adb;
- adb.setTimeoutS(10);
- Utils::SynchronousProcessResponse response
- = adb.run(m_adb, selector() << "shell" << "am" << "start"
- << "-e" << "dummy" << "dummy --dummy");
- if (response.result == Utils::SynchronousProcessResponse::StartFailed
- || response.result != Utils::SynchronousProcessResponse::Finished)
- return true;
-
- const QString output = response.allOutput();
- const bool oldSdk = output.contains("Error: No intent supplied");
- return !oldSdk;
-}
-
-bool AndroidRunnerWorker::runAdb(const QStringList &args, QString *exitMessage, int timeoutS)
-{
- Utils::SynchronousProcess adb;
- adb.setTimeoutS(timeoutS);
- Utils::SynchronousProcessResponse response = adb.run(m_adb, selector() + args);
- if (exitMessage)
- *exitMessage = response.exitMessage(m_adb, timeoutS);
- return response.result == Utils::SynchronousProcessResponse::Finished;
-}
-
-void AndroidRunnerWorker::handleRemoteDebuggerRunning()
-{
- if (m_useCppDebugger) {
- if (m_handShakeMethod == SocketHandShake) {
- m_socket->write("OK");
- m_socket->waitForBytesWritten();
- m_socket->close();
- } else {
- Utils::TemporaryFile tmp("pingpong");
- tmp.open();
-
- runAdb({"push", tmp.fileName(), m_pongFile});
- }
- QTC_CHECK(m_processPID != -1);
- }
-// emit remoteProcessStarted(m_localGdbServerPort, m_qmlPort);
-}
-
-void AndroidRunnerWorker::asyncStop()
-{
- if (!m_pidFinder.isFinished())
- m_pidFinder.cancel();
-
- if (m_processPID != -1) {
- forceStop();
- }
-}
-
-void AndroidRunnerWorker::setAndroidRunnable(const AndroidRunnable &runnable)
-{
- m_androidRunnable = runnable;
-}
-
-void AndroidRunnerWorker::logcatProcess(const QByteArray &text, QByteArray &buffer, bool onlyError)
-{
- QList<QByteArray> lines = text.split('\n');
- // lines always contains at least one item
- lines[0].prepend(buffer);
- if (!lines.last().endsWith('\n')) {
- // incomplete line
- buffer = lines.last();
- lines.removeLast();
- } else {
- 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);
- }
- }
-}
-
-void AndroidRunnerWorker::onProcessIdChanged(qint64 pid)
-{
- // Don't write to m_psProc from a different thread
- QTC_ASSERT(QThread::currentThread() == thread(), return);
- m_processPID = pid;
- if (pid == -1) {
- emit remoteProcessFinished(QLatin1String("\n\n") + tr("\"%1\" died.")
- .arg(m_androidRunnable.packageName));
- // App died/killed. Reset log and monitor processes.
- m_adbLogcatProcess.reset();
- m_psIsAlive.reset();
-
- // Run adb commands after application quit.
- for (const QString &entry: m_androidRunnable.afterFinishAdbCommands)
- runAdb(entry.split(' ', QString::SkipEmptyParts));
- } else {
- // In debugging cases this will be funneled to the engine to actually start
- // and attach gdb. Afterwards this ends up in handleRemoteDebuggerRunning() below.
- emit remoteProcessStarted(m_localGdbServerPort, m_qmlServer, m_processPID);
- 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)
- logcatProcess(m_adbLogcatProcess->readAllStandardError(), m_stderrBuffer, true);
-}
-
-void AndroidRunnerWorker::logcatReadStandardOutput()
-{
- if (m_processPID != -1)
- logcatProcess(m_adbLogcatProcess->readAllStandardOutput(), m_stdoutBuffer, false);
-}
-
-void AndroidRunnerWorker::adbKill(qint64 pid)
-{
- runAdb({"shell", "kill", "-9", QString::number(pid)});
- runAdb({"shell", "run-as", m_androidRunnable.packageName, "kill", "-9", QString::number(pid)});
-}
-
-QStringList AndroidRunnerWorker::selector() const
-{
- return AndroidDeviceInfo::adbSelector(m_androidRunnable.deviceSerialNumber);
-}
-
-AndroidRunner::AndroidRunner(RunControl *runControl, const QString &intentName,
- const QString &extraAppParams, const Utils::Environment &extraEnvVars)
+AndroidRunner::AndroidRunner(RunControl *runControl,
+ const QString &intentName,
+ const QString &extraAppParams,
+ const Utils::Environment &extraEnvVars)
: RunWorker(runControl), m_target(runControl->runConfiguration()->target())
{
setDisplayName("AndroidRunner");
@@ -709,6 +133,7 @@ AndroidRunner::AndroidRunner(RunControl *runControl, const QString &intentName,
m_androidRunnable.extraAppParams = extraAppParams;
m_androidRunnable.extraEnvVars = extraEnvVars;
m_androidRunnable.deviceSerialNumber = AndroidManager::deviceSerialNumber(m_target);
+ m_androidRunnable.apiLevel = AndroidManager::deviceApiLevel(m_target);
if (auto androidRunConfig = qobject_cast<AndroidRunConfiguration *>(
runControl->runConfiguration())) {
@@ -720,23 +145,26 @@ AndroidRunner::AndroidRunner(RunControl *runControl, const QString &intentName,
m_androidRunnable.afterFinishAdbCommands.append(QString("shell %1").arg(shellCmd));
}
- m_worker.reset(new AndroidRunnerWorker(runControl, m_androidRunnable));
+ if (m_androidRunnable.apiLevel > 23)
+ m_worker.reset(new AndroidRunnerWorker(runControl, m_androidRunnable));
+ else
+ m_worker.reset(new AndroidRunnerWorkerPreNougat(runControl, m_androidRunnable));
m_worker->moveToThread(&m_thread);
- connect(this, &AndroidRunner::asyncStart, m_worker.data(), &AndroidRunnerWorker::asyncStart);
- connect(this, &AndroidRunner::asyncStop, m_worker.data(), &AndroidRunnerWorker::asyncStop);
+ connect(this, &AndroidRunner::asyncStart, m_worker.data(), &AndroidRunnerWorkerBase::asyncStart);
+ connect(this, &AndroidRunner::asyncStop, m_worker.data(), &AndroidRunnerWorkerBase::asyncStop);
connect(this, &AndroidRunner::androidRunnableChanged,
- m_worker.data(), &AndroidRunnerWorker::setAndroidRunnable);
+ m_worker.data(), &AndroidRunnerWorkerBase::setAndroidRunnable);
connect(this, &AndroidRunner::remoteDebuggerRunning,
- m_worker.data(), &AndroidRunnerWorker::handleRemoteDebuggerRunning);
+ m_worker.data(), &AndroidRunnerWorkerBase::handleRemoteDebuggerRunning);
- connect(m_worker.data(), &AndroidRunnerWorker::remoteProcessStarted,
+ connect(m_worker.data(), &AndroidRunnerWorkerBase::remoteProcessStarted,
this, &AndroidRunner::handleRemoteProcessStarted);
- connect(m_worker.data(), &AndroidRunnerWorker::remoteProcessFinished,
+ connect(m_worker.data(), &AndroidRunnerWorkerBase::remoteProcessFinished,
this, &AndroidRunner::handleRemoteProcessFinished);
- connect(m_worker.data(), &AndroidRunnerWorker::remoteOutput,
+ connect(m_worker.data(), &AndroidRunnerWorkerBase::remoteOutput,
this, &AndroidRunner::remoteOutput);
- connect(m_worker.data(), &AndroidRunnerWorker::remoteErrorOutput,
+ connect(m_worker.data(), &AndroidRunnerWorkerBase::remoteErrorOutput,
this, &AndroidRunner::remoteErrorOutput);
connect(&m_outputParser, &QmlDebug::QmlOutputParser::waitingForConnectionOnPort,
@@ -840,6 +268,7 @@ void AndroidRunner::launchAVD()
m_target->project(), deviceAPILevel, targetArch);
AndroidManager::setDeviceSerialNumber(m_target, info.serialNumber);
m_androidRunnable.deviceSerialNumber = info.serialNumber;
+ m_androidRunnable.apiLevel = info.sdk;
emit androidRunnableChanged(m_androidRunnable);
if (info.isValid()) {
AndroidAvdManager avdManager;
@@ -872,5 +301,3 @@ void AndroidRunner::checkAVD()
} // namespace Internal
} // namespace Android
-
-#include "androidrunner.moc"
diff --git a/src/plugins/android/androidrunner.h b/src/plugins/android/androidrunner.h
index 0032fc3720..fc6b4e6470 100644
--- a/src/plugins/android/androidrunner.h
+++ b/src/plugins/android/androidrunner.h
@@ -43,7 +43,7 @@
namespace Android {
namespace Internal {
-class AndroidRunnerWorker;
+class AndroidRunnerWorkerBase;
class AndroidRunner : public ProjectExplorer::RunWorker
{
@@ -88,7 +88,7 @@ private:
QString m_launchedAVDName;
QThread m_thread;
QTimer m_checkAVDTimer;
- QScopedPointer<AndroidRunnerWorker> m_worker;
+ QScopedPointer<AndroidRunnerWorkerBase> m_worker;
QPointer<ProjectExplorer::Target> m_target;
Utils::Port m_gdbServerPort;
QUrl m_qmlServer;
diff --git a/src/plugins/android/androidrunnerworker.cpp b/src/plugins/android/androidrunnerworker.cpp
new file mode 100644
index 0000000000..11ae7d3db8
--- /dev/null
+++ b/src/plugins/android/androidrunnerworker.cpp
@@ -0,0 +1,569 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 BogDan Vatra <bog_dan_ro@yahoo.com>
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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 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.
+**
+** 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.
+**
+****************************************************************************/
+
+#include "androidconfigurations.h"
+#include "androidrunnerworker.h"
+
+#include <QThread>
+
+#include <debugger/debuggerrunconfigurationaspect.h>
+#include <projectexplorer/target.h>
+#include <qtsupport/baseqtversion.h>
+#include <qtsupport/qtkitinformation.h>
+#include <utils/hostosinfo.h>
+#include <utils/runextensions.h>
+#include <utils/synchronousprocess.h>
+#include <utils/temporaryfile.h>
+#include <utils/url.h>
+
+#include <QTcpServer>
+#include <chrono>
+
+using namespace std;
+using namespace std::placeholders;
+using namespace ProjectExplorer;
+
+namespace Android {
+namespace Internal {
+
+
+static const QString pidScript = "pidof -s \"%1\"";
+static const QString pidScriptPreNougat = QStringLiteral("for p in /proc/[0-9]*; "
+ "do cat <$p/cmdline && echo :${p##*/}; done");
+static const QString pidPollingScript = QStringLiteral("while [ -d /proc/%1 ]; do sleep 1; done");
+
+static const QString regExpLogcat = QStringLiteral("[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]*"
+ );
+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;
+}
+
+void findProcessPIDPreNougat(QFutureInterface<qint64> &fi, const QString &adbPath,
+ QStringList selector, const QString &packageName)
+{
+ 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") << pidScriptPreNougat)
+ .allRawOutput();
+ processPID = extractPID(out, packageName);
+ } while (processPID == -1 && !isTimedOut(start) && !fi.isCanceled());
+
+ if (!fi.isCanceled())
+ fi.reportResult(processPID);
+}
+
+void findProcessPID(QFutureInterface<qint64> &fi, const QString &adbPath,
+ QStringList selector, const QString &packageName)
+{
+ 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.arg(packageName))
+ .allRawOutput();
+ if (!out.isEmpty())
+ processPID = out.trimmed().toLongLong();
+ } while (processPID == -1 && !isTimedOut(start) && !fi.isCanceled());
+
+ if (!fi.isCanceled())
+ fi.reportResult(processPID);
+}
+
+AndroidRunnerWorkerBase::AndroidRunnerWorkerBase(RunControl *runControl, const AndroidRunnable &runnable)
+ : m_androidRunnable(runnable)
+ , m_adbLogcatProcess(nullptr, deleter)
+ , m_psIsAlive(nullptr, deleter)
+ , m_logCatRegExp(regExpLogcat)
+ , m_gdbServerProcess(nullptr, deleter)
+ , m_jdbProcess(nullptr, deleter)
+
+{
+ auto runConfig = runControl->runConfiguration();
+ auto aspect = runConfig->extraAspect<Debugger::DebuggerRunConfigurationAspect>();
+ Core::Id runMode = runControl->runMode();
+ const bool debuggingMode = runMode == ProjectExplorer::Constants::DEBUG_RUN_MODE;
+ m_useCppDebugger = debuggingMode && aspect->useCppDebugger();
+ if (debuggingMode && aspect->useQmlDebugger())
+ m_qmlDebugServices = QmlDebug::QmlDebuggerServices;
+ else if (runMode == ProjectExplorer::Constants::QML_PROFILER_RUN_MODE)
+ m_qmlDebugServices = QmlDebug::QmlProfilerServices;
+ else if (runMode == ProjectExplorer::Constants::QML_PREVIEW_RUN_MODE)
+ m_qmlDebugServices = QmlDebug::QmlPreviewServices;
+ else
+ m_qmlDebugServices = QmlDebug::NoQmlDebugServices;
+ m_localGdbServerPort = Utils::Port(5039);
+ QTC_CHECK(m_localGdbServerPort.isValid());
+ if (m_qmlDebugServices != QmlDebug::NoQmlDebugServices) {
+ QTcpServer server;
+ QTC_ASSERT(server.listen(QHostAddress::LocalHost)
+ || server.listen(QHostAddress::LocalHostIPv6),
+ qDebug() << tr("No free ports available on host for QML debugging."));
+ m_qmlServer.setScheme(Utils::urlTcpScheme());
+ m_qmlServer.setHost(server.serverAddress().toString());
+ m_qmlServer.setPort(server.serverPort());
+ }
+ m_adb = AndroidConfigurations::currentConfig().adbToolPath().toString();
+ m_localJdbServerPort = Utils::Port(5038);
+ QTC_CHECK(m_localJdbServerPort.isValid());
+}
+
+AndroidRunnerWorkerBase::~AndroidRunnerWorkerBase()
+{
+ if (m_processPID != -1)
+ forceStop();
+
+ if (!m_pidFinder.isFinished())
+ m_pidFinder.cancel();
+}
+
+bool AndroidRunnerWorkerBase::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.
+ Utils::SynchronousProcess adb;
+ adb.setTimeoutS(10);
+ Utils::SynchronousProcessResponse response
+ = adb.run(m_adb, selector() << "shell" << "am" << "start"
+ << "-e" << "dummy" << "dummy --dummy");
+ if (response.result == Utils::SynchronousProcessResponse::StartFailed
+ || response.result != Utils::SynchronousProcessResponse::Finished)
+ return true;
+
+ const QString output = response.allOutput();
+ const bool oldSdk = output.contains("Error: No intent supplied");
+ return !oldSdk;
+}
+
+bool AndroidRunnerWorkerBase::runAdb(const QStringList &args, int timeoutS)
+{
+ Utils::SynchronousProcess adb;
+ adb.setTimeoutS(timeoutS);
+ Utils::SynchronousProcessResponse response = adb.run(m_adb, selector() + args);
+ m_lastRunAdbError = response.exitMessage(m_adb, timeoutS);
+ m_lastRunAdbRawOutput = response.allRawOutput();
+ return response.result == Utils::SynchronousProcessResponse::Finished;
+}
+
+void AndroidRunnerWorkerBase::adbKill(qint64 pid)
+{
+ runAdb({"shell", "kill", "-9", QString::number(pid)});
+ runAdb({"shell", "run-as", m_androidRunnable.packageName, "kill", "-9", QString::number(pid)});
+}
+
+
+QStringList AndroidRunnerWorkerBase::selector() const
+{
+ return AndroidDeviceInfo::adbSelector(m_androidRunnable.deviceSerialNumber);
+}
+
+void AndroidRunnerWorkerBase::forceStop()
+{
+ runAdb({"shell", "am", "force-stop", m_androidRunnable.packageName}, 30);
+
+ // try killing it via kill -9
+ const QByteArray out = Utils::SynchronousProcess()
+ .runBlocking(m_adb, selector() << QStringLiteral("shell") << pidScriptPreNougat)
+ .allRawOutput();
+
+ qint64 pid = extractPID(out.simplified(), m_androidRunnable.packageName);
+ if (pid != -1) {
+ adbKill(pid);
+ }
+}
+
+void AndroidRunnerWorkerBase::logcatReadStandardError()
+{
+ if (m_processPID != -1)
+ logcatProcess(m_adbLogcatProcess->readAllStandardError(), m_stderrBuffer, true);
+}
+
+void AndroidRunnerWorkerBase::logcatReadStandardOutput()
+{
+ if (m_processPID != -1)
+ logcatProcess(m_adbLogcatProcess->readAllStandardOutput(), m_stdoutBuffer, false);
+}
+
+void AndroidRunnerWorkerBase::logcatProcess(const QByteArray &text, QByteArray &buffer, bool onlyError)
+{
+ QList<QByteArray> lines = text.split('\n');
+ // lines always contains at least one item
+ lines[0].prepend(buffer);
+ if (!lines.last().endsWith('\n')) {
+ // incomplete line
+ buffer = lines.last();
+ lines.removeLast();
+ } else {
+ 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_useCppDebugger) {
+ switch (m_jdbState) {
+ case JDBState::Idle:
+ if (msg.trimmed().endsWith("Sending WAIT chunk")) {
+ m_jdbState = JDBState::Waiting;
+ handleJdbWaiting();
+ }
+ break;
+ case JDBState::Waiting:
+ if (msg.indexOf("debugger has settled") > 0) {
+ m_jdbState = JDBState::Settled;
+ handleJdbSettled();
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ 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);
+ }
+ }
+}
+
+void AndroidRunnerWorkerBase::setAndroidRunnable(const AndroidRunnable &runnable)
+{
+ m_androidRunnable = runnable;
+}
+
+void AndroidRunnerWorkerBase::asyncStart()
+{
+ forceStop();
+
+ // Start the logcat process before app starts.
+ std::unique_ptr<QProcess, decltype(&deleter)> logcatProcess(new QProcess, deleter);
+ connect(logcatProcess.get(), &QProcess::readyReadStandardOutput,
+ this, &AndroidRunnerWorkerBase::logcatReadStandardOutput);
+ connect(logcatProcess.get(), &QProcess::readyReadStandardError,
+ this, &AndroidRunnerWorkerBase::logcatReadStandardError);
+ // Its assumed that the device or avd returned by selector() is online.
+ logcatProcess->start(m_adb, selector() << "logcat");
+ QTC_ASSERT(!m_adbLogcatProcess, /**/);
+ m_adbLogcatProcess = std::move(logcatProcess);
+
+ for (const QString &entry: m_androidRunnable.beforeStartAdbCommands)
+ runAdb(entry.split(' ', QString::SkipEmptyParts));
+
+ QStringList args({"shell", "am", "start"});
+ args << m_androidRunnable.amStartExtraArgs;
+ args << "-n" << m_androidRunnable.intentName;
+ if (m_useCppDebugger) {
+ args << "-D";
+ QString gdbServerSocket;
+ // run-as <package-name> pwd fails on API 22 so route the pwd through shell.
+ if (!runAdb({"shell", "run-as", m_androidRunnable.packageName, "/system/bin/sh", "-c", "pwd"})) {
+ emit remoteProcessFinished(tr("Failed to get process path. Reason: %1.").arg(m_lastRunAdbError));
+ return;
+ }
+ gdbServerSocket = QString::fromUtf8(m_lastRunAdbRawOutput.trimmed()) + "/debug-socket";
+
+ QString gdbServerExecutable;
+ if (!runAdb({"shell", "run-as", m_androidRunnable.packageName, "ls", "lib/"})) {
+ emit remoteProcessFinished(tr("Failed to get process path. Reason: %1.").arg(m_lastRunAdbError));
+ return;
+ }
+
+ for (const auto &line: m_lastRunAdbRawOutput.split('\n')) {
+ if (line.indexOf("gdbserver") != -1/* || line.indexOf("lldb-server") != -1*/) {
+ gdbServerExecutable = QString::fromUtf8(line.trimmed());
+ break;
+ }
+ }
+
+ if (gdbServerExecutable.isEmpty()) {
+ emit remoteProcessFinished(tr("Can't find C++ debugger."));
+ return;
+ }
+
+ runAdb({"shell", "run-as", m_androidRunnable.packageName, "killall", gdbServerExecutable});
+ runAdb({"shell", "run-as", m_androidRunnable.packageName, "rm", gdbServerSocket});
+ std::unique_ptr<QProcess, decltype(&deleter)> gdbServerProcess(new QProcess, deleter);
+ gdbServerProcess->start(m_adb, selector() << "shell" << "run-as"
+ << m_androidRunnable.packageName << "lib/" + gdbServerExecutable
+ << "--multi" << "+" + gdbServerSocket);
+ if (!gdbServerProcess->waitForStarted()) {
+ emit remoteProcessFinished(tr("Failed to start C++ debugger."));
+ return;
+ }
+ m_gdbServerProcess = std::move(gdbServerProcess);
+ QStringList removeForward{"forward", "--remove", "tcp:" + m_localGdbServerPort.toString()};
+ runAdb(removeForward);
+ if (!runAdb({"forward", "tcp:" + m_localGdbServerPort.toString(),
+ "localfilesystem:" + gdbServerSocket})) {
+ emit remoteProcessFinished(tr("Failed to forward C++ debugging ports. Reason: %1.").arg(m_lastRunAdbError));
+ return;
+ }
+ m_androidRunnable.afterFinishAdbCommands.push_back(removeForward.join(' '));
+ }
+
+ if (m_qmlDebugServices != QmlDebug::NoQmlDebugServices) {
+ // currently forward to same port on device and host
+ const QString port = QString("tcp:%1").arg(m_qmlServer.port());
+ QStringList removeForward{{"forward", "--remove", port}};
+ runAdb(removeForward);
+ if (!runAdb({"forward", port, port})) {
+ emit remoteProcessFinished(tr("Failed to forward QML debugging ports. Reason: %1.")
+ .arg(m_lastRunAdbError));
+ return;
+ }
+ m_androidRunnable.afterFinishAdbCommands.push_back(removeForward.join(' '));
+
+ args << "-e" << "qml_debug" << "true"
+ << "-e" << "qmljsdebugger"
+ << QString("port:%1,block,services:%2")
+ .arg(m_qmlServer.port()).arg(QmlDebug::qmlDebugServices(m_qmlDebugServices));
+ }
+
+ if (!m_androidRunnable.extraAppParams.isEmpty()) {
+ args << "-e" << "extraappparams"
+ << QString::fromLatin1(m_androidRunnable.extraAppParams.toUtf8().toBase64());
+ }
+
+ if (m_androidRunnable.extraEnvVars.size() > 0) {
+ args << "-e" << "extraenvvars"
+ << QString::fromLatin1(m_androidRunnable.extraEnvVars.toStringList().join('\t')
+ .toUtf8().toBase64());
+ }
+
+ if (!runAdb(args)) {
+ emit remoteProcessFinished(tr("Failed to start the activity. Reason: %1.")
+ .arg(m_lastRunAdbError));
+ return;
+ }
+}
+
+void AndroidRunnerWorkerBase::asyncStop()
+{
+ if (!m_pidFinder.isFinished())
+ m_pidFinder.cancel();
+
+ if (m_processPID != -1)
+ forceStop();
+
+ m_jdbProcess.reset();
+ m_gdbServerProcess.reset();
+}
+
+void AndroidRunnerWorkerBase::handleRemoteDebuggerRunning()
+{}
+
+void AndroidRunnerWorkerBase::handleJdbWaiting()
+{
+ QStringList removeForward{"forward", "--remove", "tcp:" + m_localJdbServerPort.toString()};
+ runAdb(removeForward);
+ if (!runAdb({"forward", "tcp:" + m_localJdbServerPort.toString(),
+ "jdwp:" + QString::number(m_processPID)})) {
+ emit remoteProcessFinished(tr("Failed to forward jdb debugging ports. Reason: %1.").arg(m_lastRunAdbError));
+ return;
+ }
+ m_androidRunnable.afterFinishAdbCommands.push_back(removeForward.join(' '));
+
+ auto jdbPath = AndroidConfigurations::currentConfig().openJDKLocation().appendPath("bin");
+ if (Utils::HostOsInfo::isWindowsHost())
+ jdbPath.appendPath("jdb.exe");
+ else
+ jdbPath.appendPath("jdb");
+
+ std::unique_ptr<QProcess, decltype(&deleter)> jdbProcess(new QProcess, deleter);
+ jdbProcess->setProcessChannelMode(QProcess::MergedChannels);
+ jdbProcess->start(jdbPath.toString(), QStringList() << "-connect" <<
+ QString("com.sun.jdi.SocketAttach:hostname=localhost,port=%1")
+ .arg(m_localJdbServerPort.toString()));
+ if (!jdbProcess->waitForStarted()) {
+ emit remoteProcessFinished(tr("Failed to start JDB"));
+ return;
+ }
+ m_jdbProcess = std::move(jdbProcess);
+}
+
+void AndroidRunnerWorkerBase::handleJdbSettled()
+{
+ auto waitForCommand = [&]() {
+ for (int i= 0; i < 5 && m_jdbProcess->state() == QProcess::Running; ++i) {
+ m_jdbProcess->waitForReadyRead(500);
+ QByteArray lines = m_jdbProcess->readAll();
+ for (const auto &line: lines.split('\n')) {
+ auto msg = line.trimmed();
+ if (msg.startsWith(">"))
+ return true;
+ }
+ }
+ return false;
+ };
+ if (waitForCommand()) {
+ m_jdbProcess->write("cont\n");
+ if (m_jdbProcess->waitForBytesWritten(5000) && waitForCommand()) {
+ m_jdbProcess->write("exit\n");
+ m_jdbProcess->waitForBytesWritten(5000);
+ if (!m_jdbProcess->waitForFinished(5000)) {
+ m_jdbProcess->terminate();
+ if (!m_jdbProcess->waitForFinished(5000)) {
+ m_jdbProcess->kill();
+ m_jdbProcess->waitForFinished();
+ }
+ } else if (m_jdbProcess->exitStatus() == QProcess::NormalExit && m_jdbProcess->exitCode() == 0) {
+ return;
+ }
+ }
+ }
+ emit remoteProcessFinished(tr("Can't attach jdb to the running application").arg(m_lastRunAdbError));
+}
+
+void AndroidRunnerWorkerBase::onProcessIdChanged(qint64 pid)
+{
+ // Don't write to m_psProc from a different thread
+ QTC_ASSERT(QThread::currentThread() == thread(), return);
+ m_processPID = pid;
+ if (pid == -1) {
+ emit remoteProcessFinished(QLatin1String("\n\n") + tr("\"%1\" died.")
+ .arg(m_androidRunnable.packageName));
+ // App died/killed. Reset log, monitor, jdb & gdb processes.
+ m_adbLogcatProcess.reset();
+ m_psIsAlive.reset();
+ m_jdbProcess.reset();
+ m_gdbServerProcess.reset();
+
+ // Run adb commands after application quit.
+ for (const QString &entry: m_androidRunnable.afterFinishAdbCommands)
+ runAdb(entry.split(' ', QString::SkipEmptyParts));
+ } else {
+ // In debugging cases this will be funneled to the engine to actually start
+ // and attach gdb. Afterwards this ends up in handleRemoteDebuggerRunning() below.
+ emit remoteProcessStarted(m_localGdbServerPort, m_qmlServer, m_processPID);
+ logcatReadStandardOutput();
+ QTC_ASSERT(!m_psIsAlive, /**/);
+ m_psIsAlive.reset(new QProcess);
+ m_psIsAlive->setProcessChannelMode(QProcess::MergedChannels);
+ connect(m_psIsAlive.get(), static_cast<void(QProcess::*)(int)>(&QProcess::finished),
+ this, bind(&AndroidRunnerWorkerBase::onProcessIdChanged, this, -1));
+ m_psIsAlive->start(m_adb, selector() << QStringLiteral("shell")
+ << pidPollingScript.arg(m_processPID));
+ }
+}
+
+
+AndroidRunnerWorker::AndroidRunnerWorker(RunControl *runControl, const AndroidRunnable &runnable)
+ : AndroidRunnerWorkerBase(runControl, runnable)
+{
+}
+
+void AndroidRunnerWorker::asyncStart()
+{
+ AndroidRunnerWorkerBase::asyncStart();
+ m_pidFinder = Utils::onResultReady(Utils::runAsync(&findProcessPID, m_adb, selector(),
+ m_androidRunnable.packageName),
+ bind(&AndroidRunnerWorker::onProcessIdChanged, this, _1));
+}
+
+AndroidRunnerWorkerPreNougat::AndroidRunnerWorkerPreNougat(RunControl *runControl, const AndroidRunnable &runnable)
+ : AndroidRunnerWorkerBase(runControl, runnable)
+{
+}
+
+void AndroidRunnerWorkerPreNougat::asyncStart()
+{
+ AndroidRunnerWorkerBase::asyncStart();
+ m_pidFinder = Utils::onResultReady(Utils::runAsync(&findProcessPIDPreNougat, m_adb, selector(),
+ m_androidRunnable.packageName),
+ bind(&AndroidRunnerWorkerPreNougat::onProcessIdChanged, this, _1));
+
+}
+
+} // namespace Internal
+} // namespace Android
diff --git a/src/plugins/android/androidrunnerworker.h b/src/plugins/android/androidrunnerworker.h
new file mode 100644
index 0000000000..7a5c6614b8
--- /dev/null
+++ b/src/plugins/android/androidrunnerworker.h
@@ -0,0 +1,134 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 BogDan Vatra <bog_dan_ro@yahoo.com>
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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 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.
+**
+** 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.
+**
+****************************************************************************/
+
+
+#pragma once
+
+#include <qmldebug/qmldebugcommandlinearguments.h>
+
+#include <QFuture>
+#include <QTcpSocket>
+
+#include "androidrunnable.h"
+
+namespace ProjectExplorer {
+class RunControl;
+}
+
+namespace Android {
+namespace Internal {
+
+const int MIN_SOCKET_HANDSHAKE_PORT = 20001;
+
+static void deleter(QProcess *p)
+{
+ p->terminate();
+ if (!p->waitForFinished(1000)) {
+ p->kill();
+ p->waitForFinished();
+ }
+ // Might get deleted from its own signal handler.
+ p->deleteLater();
+}
+
+class AndroidRunnerWorkerBase : public QObject
+{
+ Q_OBJECT
+public:
+ AndroidRunnerWorkerBase(ProjectExplorer::RunControl *runControl, const AndroidRunnable &runnable);
+ ~AndroidRunnerWorkerBase() override;
+ bool adbShellAmNeedsQuotes();
+ bool runAdb(const QStringList &args, int timeoutS = 10);
+ void adbKill(qint64 pid);
+ QStringList selector() const;
+ void forceStop();
+ void logcatReadStandardError();
+ void logcatReadStandardOutput();
+ void logcatProcess(const QByteArray &text, QByteArray &buffer, bool onlyError);
+ void setAndroidRunnable(const AndroidRunnable &runnable);
+
+ virtual void asyncStart();
+ virtual void asyncStop();
+ virtual void handleRemoteDebuggerRunning();
+ virtual void handleJdbWaiting();
+ virtual void handleJdbSettled();
+
+signals:
+ void remoteProcessStarted(Utils::Port gdbServerPort, const QUrl &qmlServer, int pid);
+ void remoteProcessFinished(const QString &errString = QString());
+
+ void remoteOutput(const QString &output);
+ void remoteErrorOutput(const QString &output);
+
+protected:
+ enum class JDBState {
+ Idle,
+ Waiting,
+ Settled
+ };
+ virtual void onProcessIdChanged(qint64 pid);
+
+ // Create the processes and timer in the worker thread, for correct thread affinity
+ AndroidRunnable m_androidRunnable;
+ QString m_adb;
+ qint64 m_processPID = -1;
+ std::unique_ptr<QProcess, decltype(&deleter)> m_adbLogcatProcess;
+ std::unique_ptr<QProcess, decltype(&deleter)> m_psIsAlive;
+ QByteArray m_stdoutBuffer;
+ QByteArray m_stderrBuffer;
+ QRegExp m_logCatRegExp;
+ QFuture<qint64> m_pidFinder;
+ bool m_useCppDebugger = false;
+ QmlDebug::QmlDebugServicesPreset m_qmlDebugServices;
+ Utils::Port m_localGdbServerPort; // Local end of forwarded debug socket.
+ QUrl m_qmlServer;
+ QByteArray m_lastRunAdbRawOutput;
+ QString m_lastRunAdbError;
+ JDBState m_jdbState = JDBState::Idle;
+ Utils::Port m_localJdbServerPort;
+ std::unique_ptr<QProcess, decltype(&deleter)> m_gdbServerProcess;
+ std::unique_ptr<QProcess, decltype(&deleter)> m_jdbProcess;
+};
+
+
+class AndroidRunnerWorker : public AndroidRunnerWorkerBase
+{
+ Q_OBJECT
+public:
+ AndroidRunnerWorker(ProjectExplorer::RunControl *runControl, const AndroidRunnable &runnable);
+ void asyncStart() override;
+};
+
+
+class AndroidRunnerWorkerPreNougat : public AndroidRunnerWorkerBase
+{
+ Q_OBJECT
+public:
+ AndroidRunnerWorkerPreNougat(ProjectExplorer::RunControl *runControl, const AndroidRunnable &runnable);
+ void asyncStart() override;
+};
+
+} // namespace Internal
+} // namespace Android