diff options
| author | 2015-11-25 16:48:41 -0500 | |
|---|---|---|
| committer | 2015-11-25 16:48:41 -0500 | |
| commit | 8e640722c62692818ab840d50b3758f89a41a54e (patch) | |
| tree | 38197eb1688a5afc338081ea17e15f938976e422 /Plugins/DbAndroid/adbmanager.cpp | |
| parent | 9618f0ebbf4b88045247c01ce8c8f58203508ebf (diff) | |
Imported Upstream version 3.0.7upstream/3.0.7
Diffstat (limited to 'Plugins/DbAndroid/adbmanager.cpp')
| -rw-r--r-- | Plugins/DbAndroid/adbmanager.cpp | 426 |
1 files changed, 426 insertions, 0 deletions
diff --git a/Plugins/DbAndroid/adbmanager.cpp b/Plugins/DbAndroid/adbmanager.cpp new file mode 100644 index 0000000..ed012a1 --- /dev/null +++ b/Plugins/DbAndroid/adbmanager.cpp @@ -0,0 +1,426 @@ +#include "adbmanager.h" +#include "dbandroid.h" +#include "common/utils.h" +#include <QFileInfo> +#include <QDebug> +#include <QTimer> +#include <QRegularExpression> + +AdbManager::AdbManager(DbAndroid* dbAndroidPlugin) : + QObject(dbAndroidPlugin), plugin(dbAndroidPlugin) +{ + connect(this, SIGNAL(internalDeviceListUpdate(QStringList)), this, SLOT(handleNewDeviceList(QStringList))); + connect(this, SIGNAL(deviceDetailsChanged(QList<Device>)), this, SLOT(handleNewDetails(QList<Device>))); + + adbRunMonitor = new QTimer(this); + connect(adbRunMonitor, SIGNAL(timeout()), this, SLOT(updateDeviceList())); + adbRunMonitor->setSingleShot(false); + adbRunMonitor->setInterval(1000); + adbRunMonitor->start(); + updateDeviceList(); +} + +AdbManager::~AdbManager() +{ + adbRunMonitor->stop(); + updateDevicesFuture.waitForFinished(); +} + +const QStringList& AdbManager::getDevices(bool forceSyncUpdate) +{ + if (forceSyncUpdate) + syncDeviceListUpdate(); + + return currentDeviceList; +} + +AdbManager::Device AdbManager::getDetails(const QString& deviceId) +{ + if (!currentDeviceDetails.contains(deviceId)) + { + AdbManager::Device device; + device.id = deviceId; + return device; + } + + return currentDeviceDetails[deviceId]; +} + +QList<AdbManager::Device> AdbManager::getDeviceDetails() +{ + return currentDeviceDetails.values(); +} + +QHash<QString, QPair<int, int>> AdbManager::getForwards() +{ + QHash<QString, QPair<int, int>> forwards; + QString stdOut; + if (!exec(QStringList({"forward", "--list"}), &stdOut)) + return forwards; + + QRegularExpression re("(.*)\\s+tcp:(\\d+)\\s+tcp:(\\d+)"); + QRegularExpressionMatch match; + QPair<int, int> forward; + QStringList lines = stdOut.split("\n"); + for (const QString& line : lines) + { + match = re.match(line); + if (!match.hasMatch()) + continue; + + forward.first = match.captured(2).toInt(); + forward.second = match.captured(3).toInt(); + forwards[match.captured(1)] = forward; + } + + return forwards; +} + +int AdbManager::makeForwardFor(const QString& device, int targetPort) +{ + static_qstring(portTpl, "tcp:%1"); + + QHash<QString, QPair<int, int>> forwards = getForwards(); + if (forwards.contains(device) && forwards[device].second == targetPort) + return forwards[device].first; + + int localPort = targetPort; + QStringList args = QStringList({"-s", device, "forward"}); + args << portTpl.arg(localPort); + args << portTpl.arg(targetPort); + + int tryCount = 0; + QString stdOut; + bool res; + while (!(res = exec(args, &stdOut)) && tryCount++ < 3) + { + localPort = rand(1025, 65000); + args.replace(3, portTpl.arg(localPort)); + } + + if (!res) + return -1; + + return localPort; +} + +QString AdbManager::findAdb() +{ + QStringList candidates; +#ifdef Q_OS_WIN32 + candidates << "adb.exe"; +#endif + +#ifdef Q_OS_MACX + candidates << (QDir::homePath() + "/Library/Android/sdk/platform-tools/adb"); +#endif + +#ifdef Q_OS_UNIX + candidates << "adb" << "./adb"; + + QProcess locate; + locate.start("locate", QStringList({"adb"})); + if (waitForProc(locate, true)) + { + QFileInfo fi; + QStringList locateLines = decode(locate.readAllStandardOutput()).split("\n"); + for (const QString& filePath : locateLines) + { + fi.setFile(filePath); + if (fi.fileName() != "adb" || !fi.isReadable() || !fi.isExecutable()) + continue; + + candidates << filePath; + } + } +#endif + +#ifdef Q_OS_WIN32 + if (testAdb("adb.exe", true)) + return "adb.exe"; + + static_qstring(winAdbPath, "/../Android/sdk/platform-tools/adb.exe"); + QString fullPath; + for (const QString& path : QStandardPaths::standardLocations(QStandardPaths::AppLocalDataLocation)) + { + fullPath = QDir::cleanPath(path + winAdbPath); + if (testAdb(fullPath, true)) + return fullPath; + } +#endif + + return QString(); +} + +bool AdbManager::testCurrentAdb() +{ + return testAdb(plugin->getCurrentAdb(), false); +} + +bool AdbManager::testAdb(const QString& adbPath, bool quiet) +{ + if (adbPath.isEmpty()) + return false; + + QProcess adbApp; + adbApp.start(adbPath, QStringList({"version"})); + if (!waitForProc(adbApp, quiet)) + return false; + + QString verStr = decode(adbApp.readAllStandardOutput()); + bool res = verStr.startsWith("Android Debug Bridge", Qt::CaseInsensitive); + if (!res && !quiet) + qWarning() << "Adb binary correct, but its version string is incorrect:" << verStr; + + return res; +} + +bool AdbManager::execBytes(const QStringList& arguments, QByteArray* stdOut, QByteArray* stdErr) +{ + if (!ensureAdbRunning()) + return false; + + QProcess proc; + if (arguments.join(" ").size() > 800) + { + if (!execLongCommand(arguments, proc, stdErr)) + return false; + } + else + { + proc.start(plugin->getCurrentAdb(), arguments); + if (!waitForProc(proc, false)) + return false; + } + + if (stdOut) + *stdOut = proc.readAllStandardOutput(); + + if (stdErr) + *stdErr = proc.readAllStandardError(); + + return true; +} + +bool AdbManager::waitForProc(QProcess& proc, bool quiet) +{ + if (!proc.waitForFinished(-1)) + { + if (!quiet) + qDebug() << "DbAndroid QProcess timed out."; + + return false; + } + + if (proc.exitStatus() == QProcess::CrashExit) + { + if (!quiet) + { + qDebug() << "DbAndroid QProcess finished by crashing."; + qDebug() << proc.readAllStandardOutput() << proc.readAllStandardError(); + } + + return false; + } + + if (proc.exitCode() != 0) + { + if (!quiet) + { + qDebug() << "DbAndroid QProcess finished with code:" << proc.exitCode(); + qDebug() << proc.readAllStandardOutput() << proc.readAllStandardError(); + } + + return false; + } + + return true; +} + +bool AdbManager::ensureAdbRunning() +{ + if (!plugin->isAdbValid()) + return false; + + QProcess adbApp; + adbApp.start(plugin->getCurrentAdb(), QStringList({"start-server"})); + if (!waitForProc(adbApp, false)) + return false; + + return true; +} + +bool AdbManager::exec(const QStringList& arguments, QString* stdOut, QString* stdErr) +{ + QByteArray* out = stdOut ? new QByteArray() : nullptr; + QByteArray* err = stdErr ? new QByteArray() : nullptr; + bool res = execBytes(arguments, out, err); + + if (stdOut) + { + *stdOut = decode(*out); + delete out; + } + + if (stdErr) + { + *stdErr = decode(*err); + delete err; + } + + return res; +} + +QByteArray AdbManager::encode(const QString& input) +{ + return input.toUtf8(); +} + +QString AdbManager::decode(const QByteArray& input) +{ + return QString::fromUtf8(input); +} + +bool AdbManager::execLongCommand(const QStringList& arguments, QProcess& proc, QByteArray* stdErr) +{ + // Take off initial arguments from ADB, store it to use with "push". + QStringList primaryArguments; + QStringList args = arguments; + while (args.first() != "shell") + primaryArguments << args.takeFirst(); + + args.removeFirst(); // remove the shell itself + + // Escape remaining arguments for the script + QString cmd = " '" + args.replaceInStrings("'", "'\''").join("' '") + "'"; + + // Now, the temporary file for the script + QTemporaryFile tmpFile("SQLiteStudio-XXXXXX.sh"); + if (!tmpFile.open()) + { + if (stdErr) + *stdErr = encode(QString("Could not create temporary file: %1").arg(tmpFile.fileName())); + + return false; + } + + tmpFile.write(cmd.toUtf8()); + tmpFile.close(); + + // Push the file + args = primaryArguments; + args << "push" << tmpFile.fileName() << "/data/local/tmp"; + proc.start(plugin->getCurrentAdb(), args); + if (!waitForProc(proc, false)) + return false; + + QString remoteFile = ("/data/local/tmp/" + QFileInfo(tmpFile.fileName()).fileName()); + + // Execute the file + args = primaryArguments; + args << "shell" << "sh" << remoteFile; + proc.start(plugin->getCurrentAdb(), args); + if (!waitForProc(proc, false)) + return false; + + // Delete the file from device + args = primaryArguments; + args << "shell" << "rm" << remoteFile; + QProcess localProc; + localProc.start(plugin->getCurrentAdb(), args); + if (!waitForProc(localProc, false)) + { + // Not a critical issue... + qWarning() << "Could not clean up execution script from the device: " << remoteFile << "\nDetails:\n" + << localProc.readAllStandardOutput() << "\n" << localProc.readAllStandardError(); + } + + return true; +} + +QStringList AdbManager::getDevicesInternal(bool emitSignal) +{ + QStringList devices; + QString stdOut; + if (!exec(QStringList({"devices"}), &stdOut)) + { + if (emitSignal) + emit internalDeviceListUpdate(devices); + + return devices; + } + + QRegularExpression re("(.*)\\s+device$"); + QRegularExpressionMatch match; + QStringList lines = stdOut.split("\n"); + for (const QString& line : lines) + { + match = re.match(line.trimmed()); + if (!match.hasMatch()) + continue; + + devices << match.captured(1).trimmed(); + } + + if (emitSignal) + emit internalDeviceListUpdate(devices); + + return devices; +} + +void AdbManager::syncDeviceListUpdate() +{ + currentDeviceList = getDevicesInternal(false); + updateDetails(currentDeviceList); +} + +void AdbManager::updateDetails(const QStringList& devices) +{ + QString stdOut; + QList<Device> detailList; + for (const QString& deviceId : devices) + { + Device deviceDetails; + deviceDetails.id = deviceId; + if (exec(QStringList({"-s", deviceId, "shell", "getprop", "ro.product.manufacturer"}), &stdOut)) + deviceDetails.fullName = stdOut.trimmed(); + else + qWarning() << "Could not read brand for device" << deviceId; + + if (exec(QStringList({"-s", deviceId, "shell", "getprop", "ro.product.model"}), &stdOut)) + deviceDetails.fullName += " " + stdOut.trimmed(); + else + qWarning() << "Could not read brand for device" << deviceId; + + deviceDetails.fullName = deviceDetails.fullName.trimmed(); + detailList << deviceDetails; + } + + emit deviceDetailsChanged(detailList); +} + +void AdbManager::updateDeviceList() +{ + if (!plugin->isAdbValid()) + return; + + updateDevicesFuture = QtConcurrent::run(this, &AdbManager::getDevicesInternal, true); +} + +void AdbManager::handleNewDeviceList(const QStringList& devices) +{ + if (currentDeviceList == devices) + return; + + currentDeviceList = devices; + QtConcurrent::run(this, &AdbManager::updateDetails, devices); + + emit deviceListChanged(devices); +} + +void AdbManager::handleNewDetails(const QList<AdbManager::Device>& devices) +{ + currentDeviceDetails.clear(); + for (Device device : devices) + currentDeviceDetails[device.id] = device; +} |
