From 198c83ea700604f908a78c7db0448ae79b08b856 Mon Sep 17 00:00:00 2001 From: Vikas Pachdha Date: Thu, 30 Mar 2017 14:43:13 +0200 Subject: [PATCH] Android: Add Android tool manager Refactor the use of android tool and groundwork for the new sdk and avd management tool's integration Task-number: QTCREATORBUG-17814 Change-Id: I6a5920f9ba92508f904cd8cf28bf62c82de2d820 Reviewed-by: BogDan Vatra --- src/plugins/android/android.pro | 6 +- src/plugins/android/android.qbs | 2 + src/plugins/android/androidconfigurations.cpp | 272 ++------------- src/plugins/android/androidconfigurations.h | 21 +- src/plugins/android/androiddevicedialog.cpp | 7 +- src/plugins/android/androiddevicedialog.h | 4 + src/plugins/android/androidmanager.cpp | 16 + src/plugins/android/androidmanager.h | 1 + src/plugins/android/androidsettingswidget.cpp | 21 +- src/plugins/android/androidsettingswidget.h | 5 + src/plugins/android/androidtoolmanager.cpp | 329 ++++++++++++++++++ src/plugins/android/androidtoolmanager.h | 72 ++++ 12 files changed, 472 insertions(+), 284 deletions(-) create mode 100644 src/plugins/android/androidtoolmanager.cpp create mode 100644 src/plugins/android/androidtoolmanager.h diff --git a/src/plugins/android/android.pro b/src/plugins/android/android.pro index 0df9a4a4ae0..9ca1e98de45 100644 --- a/src/plugins/android/android.pro +++ b/src/plugins/android/android.pro @@ -47,7 +47,8 @@ HEADERS += \ android_global.h \ androidbuildapkstep.h \ androidbuildapkwidget.h \ - androidrunnable.h + androidrunnable.h \ + androidtoolmanager.h SOURCES += \ androidconfigurations.cpp \ @@ -88,7 +89,8 @@ SOURCES += \ androidbuildapkstep.cpp \ androidbuildapkwidget.cpp \ androidqtsupport.cpp \ - androidrunnable.cpp + androidrunnable.cpp \ + androidtoolmanager.cpp FORMS += \ androidsettingswidget.ui \ diff --git a/src/plugins/android/android.qbs b/src/plugins/android/android.qbs index 1bb3296453b..21786397336 100644 --- a/src/plugins/android/android.qbs +++ b/src/plugins/android/android.qbs @@ -93,6 +93,8 @@ Project { "androidsignaloperation.h", "androidtoolchain.cpp", "androidtoolchain.h", + "androidtoolmanager.cpp", + "androidtoolmanager.h", "avddialog.cpp", "avddialog.h", "certificatesmodel.cpp", diff --git a/src/plugins/android/androidconfigurations.cpp b/src/plugins/android/androidconfigurations.cpp index 851b0f23506..c7aeeb237d6 100644 --- a/src/plugins/android/androidconfigurations.cpp +++ b/src/plugins/android/androidconfigurations.cpp @@ -28,8 +28,10 @@ #include "androidtoolchain.h" #include "androiddevice.h" #include "androidgdbserverkitinformation.h" +#include "androidmanager.h" #include "androidqtversion.h" #include "androiddevicedialog.h" +#include "androidtoolmanager.h" #include "avddialog.h" #include @@ -113,33 +115,6 @@ namespace { + QLatin1String("/qtcreator/android.xml"); } - bool androidDevicesLessThan(const AndroidDeviceInfo &dev1, const AndroidDeviceInfo &dev2) - { - if (dev1.serialNumber.contains(QLatin1String("????")) != dev2.serialNumber.contains(QLatin1String("????"))) - return !dev1.serialNumber.contains(QLatin1String("????")); - if (dev1.type != dev2.type) - return dev1.type == AndroidDeviceInfo::Hardware; - if (dev1.sdk != dev2.sdk) - return dev1.sdk < dev2.sdk; - if (dev1.avdname != dev2.avdname) - return dev1.avdname < dev2.avdname; - - return dev1.serialNumber < dev2.serialNumber; - } - - static QStringList cleanAndroidABIs(const QStringList &abis) - { - QStringList res; - foreach (const QString &abi, abis) { - int index = abi.lastIndexOf(QLatin1Char('/')); - if (index == -1) - res << abi; - else - res << abi.mid(index + 1); - } - return res; - } - static bool is32BitUserSpace() { // Do the exact same check as android's emulator is doing: @@ -162,25 +137,6 @@ namespace { } return false; } - - // Some preview sdks use a non integer version - int apiLevelFromAndroidList(const QString &string) - { - bool ok; - int result = string.toInt(&ok); - if (ok) - return result; - Utils::FileName sdkLocation = AndroidConfigurations::currentConfig().sdkLocation(); - sdkLocation.appendPath(QLatin1String("/platforms/android-") + string + QLatin1String("/source.properties")); - result = QSettings(sdkLocation.toString(), QSettings::IniFormat).value(QLatin1String("AndroidVersion.ApiLevel")).toInt(&ok); - if (ok) - return result; - if (string == QLatin1String("L")) - return 21; - if (string == QLatin1String("MNC")) - return 22; - return 23; // At least - } } ////////////////////////////////// @@ -372,48 +328,11 @@ void AndroidConfig::updateAvailableSdkPlatforms() const { if (m_availableSdkPlatformsUpToDate) return; + m_availableSdkPlatforms.clear(); - - SynchronousProcess proc; - proc.setProcessEnvironment(androidToolEnvironment().toProcessEnvironment()); - SynchronousProcessResponse response - = proc.runBlocking(androidToolPath().toString(), - QStringList({"list", "target"})); // list available AVDs - if (response.result != SynchronousProcessResponse::Finished) - return; - - SdkPlatform platform; - foreach (const QString &l, response.allOutput().split('\n')) { - const QString line = l.trimmed(); - if (line.startsWith(QLatin1String("id:")) && line.contains(QLatin1String("android-"))) { - int index = line.indexOf(QLatin1String("\"android-")); - if (index == -1) - continue; - QString androidTarget = line.mid(index + 1, line.length() - index - 2); - const QString tmp = androidTarget.mid(androidTarget.lastIndexOf(QLatin1Char('-')) + 1); - platform.apiLevel = apiLevelFromAndroidList(tmp); - } else if (line.startsWith(QLatin1String("Name:"))) { - platform.name = line.mid(6); - } else if (line.startsWith(QLatin1String("Tag/ABIs :"))) { - platform.abis = cleanAndroidABIs(line.mid(10).trimmed().split(QLatin1String(", "))); - } else if (line.startsWith(QLatin1String("ABIs"))) { - platform.abis = cleanAndroidABIs(line.mid(6).trimmed().split(QLatin1String(", "))); - } else if (line.startsWith(QLatin1String("---")) || line.startsWith(QLatin1String("==="))) { - if (platform.apiLevel == -1) - continue; - auto it = std::lower_bound(m_availableSdkPlatforms.begin(), m_availableSdkPlatforms.end(), - platform, sortSdkPlatformByApiLevel); - m_availableSdkPlatforms.insert(it, platform); - platform = SdkPlatform(); - } - } - - if (platform.apiLevel != -1) { - auto it = std::lower_bound(m_availableSdkPlatforms.begin(), m_availableSdkPlatforms.end(), - platform, sortSdkPlatformByApiLevel); - m_availableSdkPlatforms.insert(it, platform); - } - + AndroidToolManager toolManager(*this); + m_availableSdkPlatforms = toolManager.availableSdkPlatforms(); + Utils::sort(m_availableSdkPlatforms, sortSdkPlatformByApiLevel); m_availableSdkPlatformsUpToDate = true; } @@ -446,18 +365,6 @@ FileName AndroidConfig::adbToolPath() const return path.appendPath(QLatin1String("platform-tools/adb" QTC_HOST_EXE_SUFFIX)); } -Environment AndroidConfig::androidToolEnvironment() const -{ - Environment env = Environment::systemEnvironment(); - if (!m_openJDKLocation.isEmpty()) { - env.set(QLatin1String("JAVA_HOME"), m_openJDKLocation.toUserOutput()); - Utils::FileName binPath = m_openJDKLocation; - binPath.appendPath(QLatin1String("bin")); - env.prependOrSetPath(binPath.toUserOutput()); - } - return env; -} - FileName AndroidConfig::androidToolPath() const { if (HostOsInfo::isWindowsHost()) { @@ -583,7 +490,7 @@ QVector AndroidConfig::connectedDevices(const QString &adbToo devices.push_back(dev); } - Utils::sort(devices, androidDevicesLessThan); + Utils::sort(devices); if (devices.isEmpty() && error) *error = QApplication::translate("AndroidConfiguration", "No devices found in output of: %1") @@ -605,157 +512,6 @@ AndroidConfig::CreateAvdInfo AndroidConfig::gatherCreateAVDInfo(QWidget *parent, return result; } -QFuture AndroidConfig::createAVD(CreateAvdInfo info) const -{ - return Utils::runAsync(&AndroidConfig::createAVDImpl, info, - androidToolPath(), androidToolEnvironment()); -} - -AndroidConfig::CreateAvdInfo AndroidConfig::createAVDImpl(CreateAvdInfo info, FileName androidToolPath, Environment env) -{ - QProcess proc; - proc.setProcessEnvironment(env.toProcessEnvironment()); - QStringList arguments; - arguments << QLatin1String("create") << QLatin1String("avd") - << QLatin1String("-t") << info.target - << QLatin1String("-n") << info.name - << QLatin1String("-b") << info.abi; - if (info.sdcardSize > 0) - arguments << QLatin1String("-c") << QString::fromLatin1("%1M").arg(info.sdcardSize); - proc.start(androidToolPath.toString(), arguments); - if (!proc.waitForStarted()) { - info.error = QApplication::translate("AndroidConfig", "Could not start process \"%1 %2\"") - .arg(androidToolPath.toString(), arguments.join(QLatin1Char(' '))); - return info; - } - QTC_CHECK(proc.state() == QProcess::Running); - proc.write(QByteArray("yes\n")); // yes to "Do you wish to create a custom hardware profile" - - QByteArray question; - while (true) { - proc.waitForReadyRead(500); - question += proc.readAllStandardOutput(); - if (question.endsWith(QByteArray("]:"))) { - // truncate to last line - int index = question.lastIndexOf(QByteArray("\n")); - if (index != -1) - question = question.mid(index); - if (question.contains("hw.gpu.enabled")) - proc.write(QByteArray("yes\n")); - else - proc.write(QByteArray("\n")); - question.clear(); - } - - if (proc.state() != QProcess::Running) - break; - } - QTC_CHECK(proc.state() == QProcess::NotRunning); - - QString errorOutput = QString::fromLocal8Bit(proc.readAllStandardError()); - // The exit code is always 0, so we need to check stderr - // For now assume that any output at all indicates a error - if (!errorOutput.isEmpty()) { - info.error = errorOutput; - } - - return info; -} - -bool AndroidConfig::removeAVD(const QString &name) const -{ - SynchronousProcess proc; - proc.setTimeoutS(5); - proc.setProcessEnvironment(androidToolEnvironment().toProcessEnvironment()); - SynchronousProcessResponse response - = proc.runBlocking(androidToolPath().toString(), QStringList({"delete", "avd", "-n", name})); - return response.result == SynchronousProcessResponse::Finished && response.exitCode == 0; -} - -QFuture> AndroidConfig::androidVirtualDevicesFuture() const -{ - return Utils::runAsync(&AndroidConfig::androidVirtualDevices, - androidToolPath().toString(), androidToolEnvironment()); -} - -QVector AndroidConfig::androidVirtualDevices(const QString &androidTool, const Environment &environment) -{ - QVector devices; - SynchronousProcess proc; - proc.setTimeoutS(20); - proc.setProcessEnvironment(environment.toProcessEnvironment()); - SynchronousProcessResponse response = proc.run(androidTool, {"list", "avd"}); // list available AVDs - if (response.result != SynchronousProcessResponse::Finished) - return devices; - - QStringList avds = response.allOutput().split('\n'); - if (avds.empty()) - return devices; - - while (avds.first().startsWith(QLatin1String("* daemon"))) - avds.removeFirst(); // remove the daemon logs - avds.removeFirst(); // remove "List of devices attached" header line - - bool nextLineIsTargetLine = false; - - AndroidDeviceInfo dev; - for (int i = 0; i < avds.size(); i++) { - QString line = avds.at(i); - if (!line.contains(QLatin1String("Name:"))) - continue; - - int index = line.indexOf(QLatin1Char(':')) + 2; - if (index >= line.size()) - break; - dev.avdname = line.mid(index).trimmed(); - dev.sdk = -1; - dev.cpuAbi.clear(); - ++i; - for (; i < avds.size(); ++i) { - line = avds.at(i); - if (line.contains(QLatin1String("---------"))) - break; - - if (line.contains(QLatin1String("Target:")) || nextLineIsTargetLine) { - if (line.contains(QLatin1String("Google APIs"))) { - nextLineIsTargetLine = true; - continue; - } - - nextLineIsTargetLine = false; - - int lastIndex = line.lastIndexOf(QLatin1Char(' ')); - if (lastIndex == -1) // skip line - break; - QString tmp = line.mid(lastIndex).remove(QLatin1Char(')')).trimmed(); - dev.sdk = apiLevelFromAndroidList(tmp); - } - if (line.contains(QLatin1String("Tag/ABI:"))) { - int lastIndex = line.lastIndexOf(QLatin1Char('/')) + 1; - if (lastIndex >= line.size()) - break; - dev.cpuAbi = QStringList(line.mid(lastIndex)); - } else if (line.contains(QLatin1String("ABI:"))) { - int lastIndex = line.lastIndexOf(QLatin1Char(' ')) + 1; - if (lastIndex >= line.size()) - break; - dev.cpuAbi = QStringList(line.mid(lastIndex).trimmed()); - } - } - // armeabi-v7a devices can also run armeabi code - if (dev.cpuAbi == QStringList("armeabi-v7a")) - dev.cpuAbi << QLatin1String("armeabi"); - dev.state = AndroidDeviceInfo::OkState; - dev.type = AndroidDeviceInfo::Emulator; - if (dev.cpuAbi.isEmpty() || dev.sdk == -1) - continue; - devices.push_back(dev); - } - Utils::sort(devices, androidDevicesLessThan); - - return devices; -} - QString AndroidConfig::startAVD(const QString &name) const { if (!findAvd(name).isEmpty() || startAVDAsync(name)) @@ -1353,6 +1109,20 @@ QStringList AndroidDeviceInfo::adbSelector(const QString &serialNumber) return QStringList({"-s", serialNumber}); } +bool AndroidDeviceInfo::operator<(const AndroidDeviceInfo &other) const +{ + if (serialNumber.contains("????") != other.serialNumber.contains("????")) + return !serialNumber.contains("????"); + if (type != other.type) + return type == AndroidDeviceInfo::Hardware; + if (sdk != other.sdk) + return sdk < other.sdk; + if (avdname != other.avdname) + return avdname < other.avdname; + + return serialNumber < other.serialNumber; +} + const AndroidConfig &AndroidConfigurations::currentConfig() { return m_instance->m_config; // ensure that m_instance is initialized diff --git a/src/plugins/android/androidconfigurations.h b/src/plugins/android/androidconfigurations.h index 751e68d113d..6ad5c4a7ed9 100644 --- a/src/plugins/android/androidconfigurations.h +++ b/src/plugins/android/androidconfigurations.h @@ -68,19 +68,19 @@ public: static QStringList adbSelector(const QString &serialNumber); - bool isValid() { return !serialNumber.isEmpty() || !avdname.isEmpty(); } + bool isValid() const { return !serialNumber.isEmpty() || !avdname.isEmpty(); } + bool operator<(const AndroidDeviceInfo &other) const; }; class SdkPlatform { public: - SdkPlatform() - : apiLevel(-1) - {} - int apiLevel; + int apiLevel = -1; QString name; + Utils::FileName installedLocation; QStringList abis; }; +using SdkPlatformList = QList; class ANDROID_EXPORT AndroidConfig { @@ -121,13 +121,12 @@ public: Utils::FileName adbToolPath() const; Utils::FileName androidToolPath() const; - Utils::Environment androidToolEnvironment() const; Utils::FileName antToolPath() const; Utils::FileName emulatorToolPath() const; - Utils::FileName gccPath(const ProjectExplorer::Abi &abi, Core::Id lang, const QString &ndkToolChainVersion) const; + Utils::FileName gdbPath(const ProjectExplorer::Abi &abi, const QString &ndkToolChainVersion) const; Utils::FileName keytoolPath() const; @@ -143,15 +142,10 @@ public: }; CreateAvdInfo gatherCreateAVDInfo(QWidget *parent, int minApiLevel = 0, QString targetArch = QString()) const; - QFuture createAVD(CreateAvdInfo info) const; - bool removeAVD(const QString &name) const; QVector connectedDevices(QString *error = 0) const; static QVector connectedDevices(const QString &adbToolPath, QString *error = 0); - QFuture > androidVirtualDevicesFuture() const; - static QVector androidVirtualDevices(const QString &androidTool, const Utils::Environment &environment); - QString startAVD(const QString &name) const; bool startAVDAsync(const QString &avdName) const; QString findAvd(const QString &avdName) const; @@ -172,7 +166,6 @@ public: SdkPlatform highestAndroidSdk() const; private: - static CreateAvdInfo createAVDImpl(CreateAvdInfo info, Utils::FileName androidToolPath, Utils::Environment env); static QString getDeviceProperty(const QString &adbToolPath, const QString &device, const QString &property); Utils::FileName toolPath(const ProjectExplorer::Abi &abi, const QString &ndkToolChainVersion) const; @@ -200,7 +193,7 @@ private: //caches mutable bool m_availableSdkPlatformsUpToDate = false; - mutable QVector m_availableSdkPlatforms; + mutable SdkPlatformList m_availableSdkPlatforms; static bool sortSdkPlatformByApiLevel(const SdkPlatform &a, const SdkPlatform &b); mutable bool m_NdkInformationUpToDate = false; diff --git a/src/plugins/android/androiddevicedialog.cpp b/src/plugins/android/androiddevicedialog.cpp index 519f125f457..7b639702cff 100644 --- a/src/plugins/android/androiddevicedialog.cpp +++ b/src/plugins/android/androiddevicedialog.cpp @@ -423,7 +423,8 @@ AndroidDeviceDialog::AndroidDeviceDialog(int apiLevel, const QString &abi, Andro m_ui(new Ui::AndroidDeviceDialog), m_apiLevel(apiLevel), m_abi(abi), - m_defaultDevice(serialNumber) + m_defaultDevice(serialNumber), + m_androidToolManager(new AndroidToolManager(AndroidConfigurations::currentConfig())) { m_ui->setupUi(this); m_ui->deviceView->setModel(m_model); @@ -515,7 +516,7 @@ void AndroidDeviceDialog::refreshDeviceList() m_ui->refreshDevicesButton->setEnabled(false); m_progressIndicator->show(); m_connectedDevices = AndroidConfig::connectedDevices(AndroidConfigurations::currentConfig().adbToolPath().toString()); - m_futureWatcherRefreshDevices.setFuture(AndroidConfigurations::currentConfig().androidVirtualDevicesFuture()); + m_futureWatcherRefreshDevices.setFuture(m_androidToolManager->androidVirtualDevicesFuture()); } void AndroidDeviceDialog::devicesRefreshed() @@ -588,7 +589,7 @@ void AndroidDeviceDialog::createAvd() return; } - m_futureWatcherAddDevice.setFuture(AndroidConfigurations::currentConfig().createAVD(info)); + m_futureWatcherAddDevice.setFuture(m_androidToolManager->createAvd(info)); } void AndroidDeviceDialog::avdAdded() diff --git a/src/plugins/android/androiddevicedialog.h b/src/plugins/android/androiddevicedialog.h index b4d79400488..1f360215578 100644 --- a/src/plugins/android/androiddevicedialog.h +++ b/src/plugins/android/androiddevicedialog.h @@ -26,12 +26,15 @@ #pragma once #include "androidconfigurations.h" +#include "androidtoolmanager.h" #include #include #include #include +#include + QT_BEGIN_NAMESPACE class QModelIndex; QT_END_NAMESPACE @@ -74,6 +77,7 @@ private: QString m_abi; QString m_avdNameFromAdd; QString m_defaultDevice; + std::unique_ptr m_androidToolManager; QVector m_connectedDevices; QFutureWatcher m_futureWatcherAddDevice; QFutureWatcher> m_futureWatcherRefreshDevices; diff --git a/src/plugins/android/androidmanager.cpp b/src/plugins/android/androidmanager.cpp index 69e43e178c8..95c8225d553 100644 --- a/src/plugins/android/androidmanager.cpp +++ b/src/plugins/android/androidmanager.cpp @@ -66,6 +66,7 @@ namespace { const QLatin1String AndroidManifestName("AndroidManifest.xml"); const QLatin1String AndroidDefaultPropertiesName("project.properties"); const QLatin1String AndroidDeviceSn("AndroidDeviceSerialNumber"); + const QLatin1String ApiLevelKey("AndroidVersion.ApiLevel"); } // anonymous namespace @@ -580,4 +581,19 @@ bool AndroidManager::updateGradleProperties(ProjectExplorer::Target *target) return mergeGradleProperties(gradlePropertiesPath, gradleProperties); } +int AndroidManager::findApiLevel(const Utils::FileName &platformPath) +{ + int apiLevel = -1; + Utils::FileName propertiesPath = platformPath; + propertiesPath.appendPath("/source.properties"); + if (propertiesPath.exists()) { + QSettings sdkProperties(propertiesPath.toString(), QSettings::IniFormat); + bool validInt = false; + apiLevel = sdkProperties.value(ApiLevelKey).toInt(&validInt); + if (!validInt) + apiLevel = -1; + } + return apiLevel; +} + } // namespace Android diff --git a/src/plugins/android/androidmanager.h b/src/plugins/android/androidmanager.h index 2b79cb4c6a3..ad50f455290 100644 --- a/src/plugins/android/androidmanager.h +++ b/src/plugins/android/androidmanager.h @@ -89,6 +89,7 @@ public: static AndroidQtSupport *androidQtSupport(ProjectExplorer::Target *target); static bool useGradle(ProjectExplorer::Target *target); static bool updateGradleProperties(ProjectExplorer::Target *target); + static int findApiLevel(const Utils::FileName &platformPath); }; } // namespace Android diff --git a/src/plugins/android/androidsettingswidget.cpp b/src/plugins/android/androidsettingswidget.cpp index 5cdf42cdf3c..013695043c7 100644 --- a/src/plugins/android/androidsettingswidget.cpp +++ b/src/plugins/android/androidsettingswidget.cpp @@ -30,6 +30,7 @@ #include "androidconfigurations.h" #include "androidconstants.h" #include "androidtoolchain.h" +#include "androidtoolmanager.h" #include #include @@ -128,7 +129,8 @@ AndroidSettingsWidget::AndroidSettingsWidget(QWidget *parent) m_ndkState(NotSet), m_javaState(NotSet), m_ui(new Ui_AndroidSettingsWidget), - m_androidConfig(AndroidConfigurations::currentConfig()) + m_androidConfig(AndroidConfigurations::currentConfig()), + m_androidToolManager(new AndroidToolManager(m_androidConfig)) { m_ui->setupUi(this); @@ -463,7 +465,7 @@ void AndroidSettingsWidget::enableAvdControls() void AndroidSettingsWidget::startUpdateAvd() { disableAvdControls(); - m_virtualDevicesWatcher.setFuture(m_androidConfig.androidVirtualDevicesFuture()); + m_virtualDevicesWatcher.setFuture(m_androidToolManager->androidVirtualDevicesFuture()); } void AndroidSettingsWidget::updateAvds() @@ -592,7 +594,7 @@ void AndroidSettingsWidget::addAVD() return; } - m_futureWatcher.setFuture(m_androidConfig.createAVD(info)); + m_futureWatcher.setFuture(m_androidToolManager->createAvd(info)); } void AndroidSettingsWidget::avdAdded() @@ -620,7 +622,7 @@ void AndroidSettingsWidget::removeAVD() return; } - m_androidConfig.removeAVD(avdName); + m_androidToolManager->removeAvd(avdName); startUpdateAvd(); } @@ -671,16 +673,7 @@ void AndroidSettingsWidget::showGdbWarningDialog() void AndroidSettingsWidget::manageAVD() { - QProcess *avdProcess = new QProcess(); - connect(this, &QObject::destroyed, avdProcess, &QObject::deleteLater); - connect(avdProcess, static_cast(&QProcess::finished), - avdProcess, &QObject::deleteLater); - - avdProcess->setProcessEnvironment(m_androidConfig.androidToolEnvironment().toProcessEnvironment()); - QString executable = m_androidConfig.androidToolPath().toString(); - QStringList arguments = QStringList("avd"); - - avdProcess->start(executable, arguments); + m_androidToolManager->launchAvdManager(); } diff --git a/src/plugins/android/androidsettingswidget.h b/src/plugins/android/androidsettingswidget.h index 3572ffef1a7..091ce25ad53 100644 --- a/src/plugins/android/androidsettingswidget.h +++ b/src/plugins/android/androidsettingswidget.h @@ -33,6 +33,8 @@ #include #include +#include + QT_BEGIN_NAMESPACE class Ui_AndroidSettingsWidget; QT_END_NAMESPACE @@ -40,6 +42,8 @@ QT_END_NAMESPACE namespace Android { namespace Internal { +class AndroidToolManager; + class AvdModel: public QAbstractTableModel { Q_OBJECT @@ -112,6 +116,7 @@ private: Ui_AndroidSettingsWidget *m_ui; AndroidConfig m_androidConfig; + std::unique_ptr m_androidToolManager; AvdModel m_AVDModel; QFutureWatcher m_futureWatcher; QFutureWatcher> m_checkGdbWatcher; diff --git a/src/plugins/android/androidtoolmanager.cpp b/src/plugins/android/androidtoolmanager.cpp new file mode 100644 index 00000000000..1bf48279726 --- /dev/null +++ b/src/plugins/android/androidtoolmanager.cpp @@ -0,0 +1,329 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** 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 "androidtoolmanager.h" + +#include "androidmanager.h" + +#include "utils/algorithm.h" +#include "utils/environment.h" +#include "utils/qtcassert.h" +#include "utils/runextensions.h" +#include "utils/synchronousprocess.h" + +#include + +namespace { +Q_LOGGING_CATEGORY(androidToolLog, "qtc.android.sdkManager") +} + +namespace Android { +namespace Internal { + +using namespace Utils; + +class AndroidToolOutputParser +{ +public: + void parseTargetListing(const QString &output, const FileName &sdkLocation, + SdkPlatformList *platformList); + + QList m_installedPlatforms; +}; + +/*! + Runs the \c android tool located at \a toolPath with arguments \a args and environment \a + environment. Returns \c true for successful execution. Command's output is copied to \a + output. + */ +static bool androidToolCommand(Utils::FileName toolPath, const QStringList &args, + const Environment &environment, QString *output) +{ + QString androidToolPath = toolPath.toString(); + SynchronousProcess proc; + proc.setProcessEnvironment(environment.toProcessEnvironment()); + SynchronousProcessResponse response = proc.runBlocking(androidToolPath, args); + if (response.result == SynchronousProcessResponse::Finished) { + if (output) + *output = response.allOutput(); + return true; + } + return false; +} + +static QStringList cleanAndroidABIs(const QStringList &abis) +{ + QStringList res; + foreach (const QString &abi, abis) { + int index = abi.lastIndexOf(QLatin1Char('/')); + if (index == -1) + res << abi; + else + res << abi.mid(index + 1); + } + return res; +} + +AndroidToolManager::AndroidToolManager(const AndroidConfig &config) : + m_config(config), + m_parser(new AndroidToolOutputParser) +{ + +} + +AndroidToolManager::~AndroidToolManager() +{ + +} + +SdkPlatformList AndroidToolManager::availableSdkPlatforms() const +{ + SdkPlatformList list; + QString targetListing; + if (androidToolCommand(m_config.androidToolPath(), QStringList({"list", "target"}), + androidToolEnvironment(), &targetListing)) { + m_parser->parseTargetListing(targetListing, m_config.sdkLocation(), &list); + } else { + qCDebug(androidToolLog) << "Android tool target listing failed"; + } + return list; +} + +void AndroidToolManager::launchAvdManager() const +{ + QProcess::startDetached(m_config.androidToolPath().toString(), QStringList("avd")); +} + +QFuture +AndroidToolManager::createAvd(AndroidConfig::CreateAvdInfo info) const +{ + return Utils::runAsync(&AndroidToolManager::createAvdImpl, info, + m_config.androidToolPath(), androidToolEnvironment()); +} + +bool AndroidToolManager::removeAvd(const QString &name) const +{ + SynchronousProcess proc; + proc.setTimeoutS(5); + proc.setProcessEnvironment(androidToolEnvironment().toProcessEnvironment()); + SynchronousProcessResponse response + = proc.runBlocking(m_config.androidToolPath().toString(), + QStringList({"delete", "avd", "-n", name})); + return response.result == SynchronousProcessResponse::Finished && response.exitCode == 0; +} + +QFuture> AndroidToolManager::androidVirtualDevicesFuture() const +{ + return Utils::runAsync(&AndroidToolManager::androidVirtualDevices, + m_config.androidToolPath(), m_config.sdkLocation(), + androidToolEnvironment()); +} + +Environment AndroidToolManager::androidToolEnvironment() const +{ + Environment env = Environment::systemEnvironment(); + Utils::FileName jdkLocation = m_config.openJDKLocation(); + if (!jdkLocation.isEmpty()) { + env.set(QLatin1String("JAVA_HOME"), jdkLocation.toUserOutput()); + Utils::FileName binPath = jdkLocation; + binPath.appendPath(QLatin1String("bin")); + env.prependOrSetPath(binPath.toUserOutput()); + } + return env; +} + +AndroidConfig::CreateAvdInfo AndroidToolManager::createAvdImpl(AndroidConfig::CreateAvdInfo info, + FileName androidToolPath, + Environment env) +{ + QProcess proc; + proc.setProcessEnvironment(env.toProcessEnvironment()); + QStringList arguments; + arguments << QLatin1String("create") << QLatin1String("avd") + << QLatin1String("-t") << info.target + << QLatin1String("-n") << info.name + << QLatin1String("-b") << info.abi; + if (info.sdcardSize > 0) + arguments << QLatin1String("-c") << QString::fromLatin1("%1M").arg(info.sdcardSize); + proc.start(androidToolPath.toString(), arguments); + if (!proc.waitForStarted()) { + info.error = tr("Could not start process \"%1 %2\"") + .arg(androidToolPath.toString(), arguments.join(QLatin1Char(' '))); + return info; + } + QTC_CHECK(proc.state() == QProcess::Running); + proc.write(QByteArray("yes\n")); // yes to "Do you wish to create a custom hardware profile" + + QByteArray question; + while (true) { + proc.waitForReadyRead(500); + question += proc.readAllStandardOutput(); + if (question.endsWith(QByteArray("]:"))) { + // truncate to last line + int index = question.lastIndexOf(QByteArray("\n")); + if (index != -1) + question = question.mid(index); + if (question.contains("hw.gpu.enabled")) + proc.write(QByteArray("yes\n")); + else + proc.write(QByteArray("\n")); + question.clear(); + } + + if (proc.state() != QProcess::Running) + break; + } + QTC_CHECK(proc.state() == QProcess::NotRunning); + + QString errorOutput = QString::fromLocal8Bit(proc.readAllStandardError()); + // The exit code is always 0, so we need to check stderr + // For now assume that any output at all indicates a error + if (!errorOutput.isEmpty()) { + info.error = errorOutput; + } + + return info; +} + +QVector +AndroidToolManager::androidVirtualDevices(const Utils::FileName &androidTool, + const FileName &sdkLocationPath, + const Environment &environment) +{ + QVector devices; + QString output; + if (!androidToolCommand(androidTool, QStringList({"list", "avd"}), environment, &output)) + return devices; + + QStringList avds = output.split('\n'); + if (avds.empty()) + return devices; + + while (avds.first().startsWith(QLatin1String("* daemon"))) + avds.removeFirst(); // remove the daemon logs + avds.removeFirst(); // remove "List of devices attached" header line + + bool nextLineIsTargetLine = false; + + AndroidDeviceInfo dev; + for (int i = 0; i < avds.size(); i++) { + QString line = avds.at(i); + if (!line.contains(QLatin1String("Name:"))) + continue; + + int index = line.indexOf(QLatin1Char(':')) + 2; + if (index >= line.size()) + break; + dev.avdname = line.mid(index).trimmed(); + dev.sdk = -1; + dev.cpuAbi.clear(); + ++i; + for (; i < avds.size(); ++i) { + line = avds.at(i); + if (line.contains(QLatin1String("---------"))) + break; + + if (line.contains(QLatin1String("Target:")) || nextLineIsTargetLine) { + if (line.contains(QLatin1String("Google APIs"))) { + nextLineIsTargetLine = true; + continue; + } + + nextLineIsTargetLine = false; + + int lastIndex = line.lastIndexOf(QLatin1Char(' ')); + if (lastIndex == -1) // skip line + break; + QString tmp = line.mid(lastIndex).remove(QLatin1Char(')')).trimmed(); + Utils::FileName platformPath = sdkLocationPath; + platformPath.appendPath(QString("/platforms/android-%1").arg(tmp)); + dev.sdk = AndroidManager::findApiLevel(platformPath); + } + if (line.contains(QLatin1String("Tag/ABI:"))) { + int lastIndex = line.lastIndexOf(QLatin1Char('/')) + 1; + if (lastIndex >= line.size()) + break; + dev.cpuAbi = QStringList(line.mid(lastIndex)); + } else if (line.contains(QLatin1String("ABI:"))) { + int lastIndex = line.lastIndexOf(QLatin1Char(' ')) + 1; + if (lastIndex >= line.size()) + break; + dev.cpuAbi = QStringList(line.mid(lastIndex).trimmed()); + } + } + // armeabi-v7a devices can also run armeabi code + if (dev.cpuAbi == QStringList("armeabi-v7a")) + dev.cpuAbi << QLatin1String("armeabi"); + dev.state = AndroidDeviceInfo::OkState; + dev.type = AndroidDeviceInfo::Emulator; + if (dev.cpuAbi.isEmpty() || dev.sdk == -1) + continue; + devices.push_back(dev); + } + Utils::sort(devices); + + return devices; +} + +void AndroidToolOutputParser::parseTargetListing(const QString &output, + const Utils::FileName &sdkLocation, + SdkPlatformList *platformList) +{ + if (!platformList) + return; + + SdkPlatform platform; + foreach (const QString &l, output.split('\n')) { + const QString line = l.trimmed(); + if (line.startsWith(QLatin1String("id:")) && line.contains(QLatin1String("android-"))) { + int index = line.indexOf(QLatin1String("\"android-")); + if (index == -1) + continue; + QString androidTarget = line.mid(index + 1, line.length() - index - 2); + const QString tmp = androidTarget.mid(androidTarget.lastIndexOf(QLatin1Char('-')) + 1); + Utils::FileName platformPath = sdkLocation; + platformPath.appendPath(QString("/platforms/android-%1").arg(tmp)); + platform.installedLocation = platformPath; + platform.apiLevel = AndroidManager::findApiLevel(platformPath); + } else if (line.startsWith(QLatin1String("Name:"))) { + platform.name = line.mid(6); + } else if (line.startsWith(QLatin1String("Tag/ABIs :"))) { + platform.abis = cleanAndroidABIs(line.mid(10).trimmed().split(QLatin1String(", "))); + } else if (line.startsWith(QLatin1String("ABIs"))) { + platform.abis = cleanAndroidABIs(line.mid(6).trimmed().split(QLatin1String(", "))); + } else if (line.startsWith(QLatin1String("---")) || line.startsWith(QLatin1String("==="))) { + if (platform.apiLevel == -1) + continue; + *platformList << platform; + platform = SdkPlatform(); + } + } + + // The last parsed Platform. + if (platform.apiLevel != -1) + *platformList << platform; +} + +} // namespace Internal +} // namespace Android diff --git a/src/plugins/android/androidtoolmanager.h b/src/plugins/android/androidtoolmanager.h new file mode 100644 index 00000000000..24f27e2c9f2 --- /dev/null +++ b/src/plugins/android/androidtoolmanager.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** 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 "utils/fileutils.h" +#include "androidconfigurations.h" + +#include + +#include + +namespace Android { +class AndroidConfig; + +namespace Internal { + +class AndroidToolOutputParser; +/*! + Wraps the \c android tool's usage. The tool itself is deprecated since SDK tools version 25.3.0. + */ +class AndroidToolManager +{ + Q_DECLARE_TR_FUNCTIONS(AndroidToolManager) + +public: + AndroidToolManager(const AndroidConfig &config); + ~AndroidToolManager(); + + SdkPlatformList availableSdkPlatforms() const; + void launchAvdManager() const; + + QFuture createAvd(AndroidConfig::CreateAvdInfo info) const; + bool removeAvd(const QString &name) const; + QFuture > androidVirtualDevicesFuture() const; + +// Helper methods +private: + Utils::Environment androidToolEnvironment() const; + static AndroidConfig::CreateAvdInfo createAvdImpl(AndroidConfig::CreateAvdInfo info, + Utils::FileName androidToolPath, Utils::Environment env); + static QVector androidVirtualDevices(const Utils::FileName &androidTool, + const Utils::FileName &sdkLlocationPath, + const Utils::Environment &environment); +private: + const AndroidConfig &m_config; + std::unique_ptr m_parser; +}; + +} // namespace Internal +} // namespace Android