diff --git a/src/plugins/android/androidrunner.cpp b/src/plugins/android/androidrunner.cpp index 47740bd9b22..50729eda925 100644 --- a/src/plugins/android/androidrunner.cpp +++ b/src/plugins/android/androidrunner.cpp @@ -1,6 +1,7 @@ /************************************************************************** ** ** Copyright (c) 2014 BogDan Vatra +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of Qt Creator. @@ -41,17 +42,30 @@ #include #include +#include #include #include #include #include #include +#include /* This uses explicit handshakes between the application and the - gdbserver start and the host side by using the gdbserver socket - and two files ("ping" file in the application dir, "pong" file - in /data/local/tmp/qt) + gdbserver start and the host side by using the gdbserver socket. + + For the handshake there are two mechanisms. Only the first method works + on Android 5.x devices and is chosen as default option. The second + method can be enabled by setting the QTC_ANDROID_USE_FILE_HANDSHAKE + environment variable before starting Qt Creator. + + 1.) This method uses a TCP server on the Android device which starts + listening for incoming connections. The socket is forwarded by adb + and creator connects to it. This is the only method that works + on Android 5.x devices. + + 2.) This method uses two files ("ping" file in the application dir, + "pong" file in /data/local/tmp/qt). The sequence is as follows: @@ -67,8 +81,8 @@ gdbserver: normal start up including opening debug-socket, not yet attached to any process - app start up: touch ping file - app start up: loop until pong file appears + app start up: 1.) set up ping connection or 2.) touch ping file + app start up: 1.) accept() or 2.) loop until pong file appears host: start gdb host: gdb: set up binary, breakpoints, path etc @@ -90,7 +104,7 @@ app start up: resumed (still waiting for the pong) - host: write pong file + host: 1) write "ok" to ping pong connection or 2.) write pong file app start up: java code continues now, the process is already fully under control @@ -104,11 +118,16 @@ namespace Android { namespace Internal { 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, ProjectExplorer::RunMode runMode) - : QThread(parent) + : QThread(parent), m_handShakeMethod(SocketHandShake), m_socket(0), + m_customPort(false) { m_tries = 0; Debugger::DebuggerRunConfigurationAspect *aspect @@ -170,11 +189,32 @@ AndroidRunner::AndroidRunner(QObject *parent, connect(&m_adbLogcatProcess, SIGNAL(readyReadStandardOutput()), SLOT(logcatReadStandardOutput())); connect(&m_adbLogcatProcess, SIGNAL(readyReadStandardError()), SLOT(logcatReadStandardError())); connect(&m_checkPIDTimer, SIGNAL(timeout()), SLOT(checkPID())); + + 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) { + socketHandShakePort = port; + m_customPort = true; + } + } + } } AndroidRunner::~AndroidRunner() { //stop(); + delete m_socket; } static int extractPidFromChunk(const QByteArray &chunk, int from) @@ -308,11 +348,30 @@ void AndroidRunner::asyncStart() return; } + const QString pingPongSocket(m_packageName + _(".ping_pong_socket")); args << _("-e") << _("debug_ping") << _("true"); - args << _("-e") << _("ping_file") << m_pingFile; - args << _("-e") << _("pong_file") << m_pongFile; + 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; + } args << _("-e") << _("gdbserver_command") << m_gdbserverCommand; args << _("-e") << _("gdbserver_socket") << m_gdbserverSocket; + + if (m_handShakeMethod == SocketHandShake) { + QProcess adb; + const QString port = QString::fromLatin1("tcp:%1").arg(socketHandShakePort); + adb.start(m_adb, selector() << _("forward") << port << _("localabstract:") + pingPongSocket); + if (!adb.waitForStarted()) { + emit remoteProcessFinished(tr("Failed to forward ping pong ports. Reason: %1.").arg(adb.errorString())); + return; + } + if (!adb.waitForFinished()) { + emit remoteProcessFinished(tr("Failed to forward ping pong ports.")); + return; + } + } } if (m_useQmlDebugger || m_useQmlProfiler) { @@ -353,29 +412,72 @@ void AndroidRunner::asyncStart() } if (m_useCppDebugger) { + if (m_handShakeMethod == SocketHandShake) { + //Handling socket + bool wasSuccess = false; + const int maxAttempts = 20; //20 seconds + if (m_socket) + delete m_socket; + m_socket = new QTcpSocket(); + for (int i = 0; i < maxAttempts; i++) { - // Handling ping. - for (int i = 0; ; ++i) { - QTemporaryFile tmp(QDir::tempPath() + _("/pingpong")); - tmp.open(); - tmp.close(); + QThread::sleep(1); // give Android time to start process + m_socket->connectToHost(QHostAddress(QStringLiteral("127.0.0.1")), socketHandShakePort); + if (!m_socket->waitForConnected()) + continue; - QProcess process; - process.start(m_adb, selector() << _("pull") << m_pingFile << tmp.fileName()); - process.waitForFinished(); + if (!m_socket->waitForReadyRead()) { + m_socket->close(); + continue; + } + + const QByteArray pid = m_socket->readLine(); + if (pid.isEmpty()) { + m_socket->close(); + continue; + } + + wasSuccess = true; + m_socket->moveToThread(QApplication::instance()->thread()); - 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_packageName)); - return; } - qDebug() << "WAITING FOR " << tmp.fileName(); - QThread::msleep(500); + + if (!wasSuccess) + emit remoteProcessFinished(tr("Failed to contact debugging port.")); + + if (!m_customPort) { + // increment running port to avoid clash when using multiple + // debug sessions at the same time + socketHandShakePort++; + // wrap ports around to avoid overflow + if (socketHandShakePort == MAX_SOCKET_HANDSHAKE_PORT) + socketHandShakePort = MIN_SOCKET_HANDSHAKE_PORT; + } + } else { + // Handling ping. + for (int i = 0; ; ++i) { + QTemporaryFile tmp(QDir::tempPath() + _("/pingpong")); + tmp.open(); + tmp.close(); + + QProcess process; + process.start(m_adb, selector() << _("pull") << m_pingFile << tmp.fileName()); + process.waitForFinished(); + + 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_packageName)); + return; + } + qDebug() << "WAITING FOR " << tmp.fileName(); + QThread::msleep(500); + } } } @@ -388,13 +490,18 @@ void AndroidRunner::asyncStart() void AndroidRunner::handleRemoteDebuggerRunning() { if (m_useCppDebugger) { - QTemporaryFile tmp(QDir::tempPath() + _("/pingpong")); - tmp.open(); - - QProcess process; - process.start(m_adb, selector() << _("push") << tmp.fileName() << m_pongFile); - process.waitForFinished(); + if (m_handShakeMethod == SocketHandShake) { + m_socket->write("OK"); + m_socket->waitForBytesWritten(); + m_socket->close(); + } else { + QTemporaryFile tmp(QDir::tempPath() + _("/pingpong")); + tmp.open(); + QProcess process; + process.start(m_adb, selector() << _("push") << tmp.fileName() << m_pongFile); + process.waitForFinished(); + } QTC_CHECK(m_processPID != -1); } emit remoteProcessStarted(m_localGdbServerPort, m_qmlPort); diff --git a/src/plugins/android/androidrunner.h b/src/plugins/android/androidrunner.h index 1af23bc88fe..ac21371d7df 100644 --- a/src/plugins/android/androidrunner.h +++ b/src/plugins/android/androidrunner.h @@ -37,6 +37,7 @@ #include #include +#include #include #include #include @@ -50,6 +51,11 @@ class AndroidRunner : public QThread { Q_OBJECT + enum DebugHandShakeType { + PingPongFiles, + SocketHandShake + }; + public: AndroidRunner(QObject *parent, AndroidRunConfiguration *runConfig, ProjectExplorer::RunMode runMode); @@ -114,6 +120,9 @@ private: bool m_isBusyBox; QStringList m_selector; QMutex m_mutex; + DebugHandShakeType m_handShakeMethod; + QTcpSocket *m_socket; + bool m_customPort; }; } // namespace Internal