#include "adbmanager.h" #include "dbandroid.h" #include "common/utils.h" #include #include #include #include AdbManager::AdbManager(DbAndroid* dbAndroidPlugin) : QObject(dbAndroidPlugin), plugin(dbAndroidPlugin) { connect(this, SIGNAL(internalDeviceListUpdate(QStringList)), this, SLOT(handleNewDeviceList(QStringList))); connect(this, SIGNAL(deviceDetailsChanged(QList)), this, SLOT(handleNewDetails(QList))); 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::getDeviceDetails() { return currentDeviceDetails.values(); } QHash> AdbManager::getForwards() { QHash> forwards; QString stdOut; if (!exec(QStringList({"forward", "--list"}), &stdOut)) return forwards; QRegularExpression re("(.*)\\s+tcp:(\\d+)\\s+tcp:(\\d+)"); QRegularExpressionMatch match; QPair 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> 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, bool forceSafe) { if (!ensureAdbRunning()) return false; QProcess proc; if (forceSafe || 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, bool forceSafe) { QByteArray* out = stdOut ? new QByteArray() : nullptr; QByteArray* err = stdErr ? new QByteArray() : nullptr; bool res = execBytes(arguments, out, err, forceSafe); 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 (%2)").arg(tmpFile.fileName(), tmpFile.errorString())); 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 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& devices) { currentDeviceDetails.clear(); for (Device device : devices) currentDeviceDetails[device.id] = device; }