diff --git a/src/plugins/android/android.pro b/src/plugins/android/android.pro index 05f29f9a5b4..3d63124af5a 100644 --- a/src/plugins/android/android.pro +++ b/src/plugins/android/android.pro @@ -49,7 +49,8 @@ HEADERS += \ androidbuildapkwidget.h \ androidrunnable.h \ androidtoolmanager.h \ - androidsdkmanager.h + androidsdkmanager.h \ + androidavdmanager.h SOURCES += \ androidconfigurations.cpp \ @@ -92,7 +93,8 @@ SOURCES += \ androidqtsupport.cpp \ androidrunnable.cpp \ androidtoolmanager.cpp \ - androidsdkmanager.cpp + androidsdkmanager.cpp \ + androidavdmanager.cpp FORMS += \ androidsettingswidget.ui \ diff --git a/src/plugins/android/android.qbs b/src/plugins/android/android.qbs index 9b146d22c7a..c3a2551bae0 100644 --- a/src/plugins/android/android.qbs +++ b/src/plugins/android/android.qbs @@ -22,6 +22,8 @@ Project { "android.qrc", "androidanalyzesupport.cpp", "androidanalyzesupport.h", + "androidavdmanager.cpp", + "androidavdmanager.h", "androidconfigurations.cpp", "androidconfigurations.h", "androidconstants.h", diff --git a/src/plugins/android/androidavdmanager.cpp b/src/plugins/android/androidavdmanager.cpp new file mode 100644 index 00000000000..028fe2c7974 --- /dev/null +++ b/src/plugins/android/androidavdmanager.cpp @@ -0,0 +1,441 @@ +/**************************************************************************** +** +** 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 "androidavdmanager.h" + +#include "androidtoolmanager.h" + +#include "utils/algorithm.h" +#include "utils/qtcassert.h" +#include "utils/runextensions.h" +#include "utils/synchronousprocess.h" + +#include +#include +#include +#include + +#include + +namespace { +Q_LOGGING_CATEGORY(avdManagerLog, "qtc.android.avdManager") +} + +namespace Android { +namespace Internal { + +using namespace std; + +// Avd list keys to parse avd +const char avdInfoNameKey[] = "Name:"; +const char avdInfoPathKey[] = "Path:"; +const char avdInfoAbiKey[] = "abi.type"; +const char avdInfoTargetKey[] = "target"; +const char avdInfoErrorKey[] = "Error:"; + +const QVersionNumber avdManagerIntroVersion(25, 3 ,0); + +const int avdCreateTimeoutMs = 30000; + +/*! + Runs the \c avdmanager tool specific to configuration \a config with arguments \a args. Returns + \c true if the command is successfully executed. Output is copied into \a output. The function + blocks the calling thread. + */ +static bool avdManagerCommand(const AndroidConfig config, const QStringList &args, QString *output) +{ + QString avdManagerToolPath = config.avdManagerToolPath().toString(); + Utils::SynchronousProcess proc; + Utils::SynchronousProcessResponse response = proc.runBlocking(avdManagerToolPath, args); + if (response.result == Utils::SynchronousProcessResponse::Finished) { + if (output) + *output = response.allOutput(); + return true; + } + return false; +} + +/*! + Parses the \a line for a [spaces]key[spaces]value[spaces] pattern and returns + \c true if the key is found, \c false otherwise. The value is copied into \a value. + */ +static bool valueForKey(QString key, const QString &line, QString *value = nullptr) +{ + auto trimmedInput = line.trimmed(); + if (trimmedInput.startsWith(key)) { + if (value) + *value = trimmedInput.section(key, 1, 1).trimmed(); + return true; + } + return false; +} + +static bool checkForTimeout(const chrono::steady_clock::time_point &start, + int msecs = 3000) +{ + bool timedOut = false; + auto end = chrono::steady_clock::now(); + if (chrono::duration_cast(end-start).count() > msecs) + timedOut = true; + return timedOut; +} + +static AndroidConfig::CreateAvdInfo createAvdCommand(const AndroidConfig config, + const AndroidConfig::CreateAvdInfo &info) +{ + AndroidConfig::CreateAvdInfo result = info; + + if (!result.isValid()) { + qCDebug(avdManagerLog) << "AVD Create failed. Invalid CreateAvdInfo" << result.name + << result.target.name << result.target.apiLevel; + result.error = QApplication::translate("AndroidAvdManager", + "Cannot create AVD. Invalid input."); + return result; + } + + QStringList arguments({"create", "avd", "-k", result.target.package, "-n", result.name}); + + if (!result.abi.isEmpty()) { + SystemImage image = Utils::findOrDefault(result.target.systemImages, + Utils::equal(&SystemImage::abiName, result.abi)); + if (image.isValid()) { + arguments << "-k" << image.package; + } else { + qCDebug(avdManagerLog) << "AVD Create failed. Cannot find system image for the platform" + << result.abi << result.target.name; + result.error = QApplication::translate("AndroidAvdManager", + "Cannot create AVD. Cannot find system image for " + "the ABI %1(%2).").arg(result.abi).arg(result.target.name); + return result; + } + + } else { + arguments << "-k" << result.target.package; + } + + if (result.sdcardSize > 0) + arguments << "-c" << QString::fromLatin1("%1M").arg(result.sdcardSize); + + QProcess proc; + proc.start(config.avdManagerToolPath().toString(), arguments); + if (!proc.waitForStarted()) { + result.error = QApplication::translate("AndroidAvdManager", + "Could not start process \"%1 %2\"") + .arg(config.avdManagerToolPath().toString(), arguments.join(' ')); + return result; + } + QTC_CHECK(proc.state() == QProcess::Running); + proc.write(QByteArray("yes\n")); // yes to "Do you wish to create a custom hardware profile" + + auto start = chrono::steady_clock::now(); + QString errorOutput; + QByteArray question; + while (errorOutput.isEmpty()) { + 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(); + } + // The exit code is always 0, so we need to check stderr + // For now assume that any output at all indicates a error + errorOutput = QString::fromLocal8Bit(proc.readAllStandardError()); + if (proc.state() != QProcess::Running) + break; + + // For a sane input and command, process should finish before timeout. + if (checkForTimeout(start, avdCreateTimeoutMs)) { + result.error = QApplication::translate("AndroidAvdManager", + "Cannot create AVD. Command timed out."); + } + } + + // Kill the running process. + if (proc.state() != QProcess::NotRunning) { + proc.terminate(); + if (!proc.waitForFinished(3000)) + proc.kill(); + } + + QTC_CHECK(proc.state() == QProcess::NotRunning); + result.error = errorOutput; + return result; +} + +/*! + \class AvdManagerOutputParser + \brief The AvdManagerOutputParser class is a helper class to parse the output of the avdmanager + commands. + */ +class AvdManagerOutputParser +{ +public: + AndroidDeviceInfoList listVirtualDevices(const AndroidConfig &config); + AndroidDeviceInfoList parseAvdList(const QString &output); + +private: + bool parseAvd(const QStringList &deviceInfo, AndroidDeviceInfo *avd); +}; + + +AndroidAvdManager::AndroidAvdManager(const AndroidConfig &config): + m_config(config), + m_androidTool(new AndroidToolManager(m_config)), + m_parser(new AvdManagerOutputParser) +{ + +} + +AndroidAvdManager::~AndroidAvdManager() +{ + +} + +bool AndroidAvdManager::avdManagerUiToolAvailable() const +{ + return m_config.sdkToolsVersion() < avdManagerIntroVersion; +} + +void AndroidAvdManager::launchAvdManagerUiTool() const +{ + if (avdManagerUiToolAvailable()) { + m_androidTool->launchAvdManager(); + } else { + qCDebug(avdManagerLog) << "AVD Ui tool launch failed. UI tool not available" + << m_config.sdkToolsVersion(); + } +} + +QFuture +AndroidAvdManager::createAvd(AndroidConfig::CreateAvdInfo info) const +{ + if (m_config.sdkToolsVersion() < avdManagerIntroVersion) + return m_androidTool->createAvd(info); + + return Utils::runAsync(&createAvdCommand, m_config, info); +} + +bool AndroidAvdManager::removeAvd(const QString &name) const +{ + if (m_config.sdkToolsVersion() < avdManagerIntroVersion) + return m_androidTool->removeAvd(name); + + Utils::SynchronousProcess proc; + proc.setTimeoutS(5); + Utils::SynchronousProcessResponse response + = proc.runBlocking(m_config.avdManagerToolPath().toString(), + QStringList({"delete", "avd", "-n", name})); + return response.result == Utils::SynchronousProcessResponse::Finished && response.exitCode == 0; +} + +QFuture AndroidAvdManager::avdList() const +{ + if (m_config.sdkToolsVersion() < avdManagerIntroVersion) + return m_androidTool->androidVirtualDevicesFuture(); + + return Utils::runAsync(&AvdManagerOutputParser::listVirtualDevices, m_parser.get(), m_config); +} + +QString AndroidAvdManager::startAvd(const QString &name) const +{ + if (!findAvd(name).isEmpty() || startAvdAsync(name)) + return waitForAvd(name); + return QString(); +} + +bool AndroidAvdManager::startAvdAsync(const QString &avdName) const +{ + QProcess *avdProcess = new QProcess(); + QObject::connect(avdProcess, static_cast(&QProcess::finished), + avdProcess, &QObject::deleteLater); + + // start the emulator + QStringList arguments; + if (AndroidConfigurations::force32bitEmulator()) + arguments << "-force-32bit"; + + arguments << "-partition-size" << QString::number(m_config.partitionSize()) + << "-avd" << avdName; + avdProcess->start(m_config.emulatorToolPath().toString(), arguments); + if (!avdProcess->waitForStarted(-1)) { + delete avdProcess; + return false; + } + return true; +} + +QString AndroidAvdManager::findAvd(const QString &avdName) const +{ + QVector devices = m_config.connectedDevices(); + foreach (AndroidDeviceInfo device, devices) { + if (device.type != AndroidDeviceInfo::Emulator) + continue; + if (device.avdname == avdName) + return device.serialNumber; + } + return QString(); +} + +QString AndroidAvdManager::waitForAvd(const QString &avdName, const QFutureInterface &fi) const +{ + // we cannot use adb -e wait-for-device, since that doesn't work if a emulator is already running + // 60 rounds of 2s sleeping, two minutes for the avd to start + QString serialNumber; + for (int i = 0; i < 60; ++i) { + if (fi.isCanceled()) + return QString(); + serialNumber = findAvd(avdName); + if (!serialNumber.isEmpty()) + return waitForBooted(serialNumber, fi) ? serialNumber : QString(); + QThread::sleep(2); + } + return QString(); +} + +bool AndroidAvdManager::isAvdBooted(const QString &device) const +{ + QStringList arguments = AndroidDeviceInfo::adbSelector(device); + arguments << "shell" << "getprop" << "init.svc.bootanim"; + + Utils::SynchronousProcess adbProc; + adbProc.setTimeoutS(10); + Utils::SynchronousProcessResponse response = + adbProc.runBlocking(m_config.adbToolPath().toString(), arguments); + if (response.result != Utils::SynchronousProcessResponse::Finished) + return false; + QString value = response.allOutput().trimmed(); + return value == "stopped"; +} + +bool AndroidAvdManager::waitForBooted(const QString &serialNumber, const QFutureInterface &fi) const +{ + // found a serial number, now wait until it's done booting... + for (int i = 0; i < 60; ++i) { + if (fi.isCanceled()) + return false; + if (isAvdBooted(serialNumber)) { + return true; + } else { + QThread::sleep(2); + if (!m_config.isConnected(serialNumber)) // device was disconnected + return false; + } + } + return false; +} + +AndroidDeviceInfoList AvdManagerOutputParser::listVirtualDevices(const AndroidConfig &config) +{ + QString output; + if (!avdManagerCommand(config, QStringList({"list", "avd"}), &output)) { + qCDebug(avdManagerLog) << "Avd list command failed" << output << config.sdkToolsVersion(); + return {}; + } + return parseAvdList(output); +} + +AndroidDeviceInfoList AvdManagerOutputParser::parseAvdList(const QString &output) +{ + AndroidDeviceInfoList avdList; + QStringList avdInfo; + auto parseAvdInfo = [&avdInfo, &avdList, this] () { + AndroidDeviceInfo avd; + if (parseAvd(avdInfo, &avd)) { + // armeabi-v7a devices can also run armeabi code + if (avd.cpuAbi.contains("armeabi-v7a")) + avd.cpuAbi << "armeabi"; + avd.state = AndroidDeviceInfo::OkState; + avd.type = AndroidDeviceInfo::Emulator; + avdList << avd; + } else { + qCDebug(avdManagerLog) << "Avd Parsing: Parsing failed: " << avdInfo; + } + avdInfo.clear(); + }; + + foreach (QString line, output.split('\n')) { + if (line.startsWith("---------") || line.isEmpty()) { + parseAvdInfo(); + } else { + avdInfo << line; + } + } + + if (!avdInfo.isEmpty()) + parseAvdInfo(); + + Utils::sort(avdList); + + return avdList; +} + +bool AvdManagerOutputParser::parseAvd(const QStringList &deviceInfo, AndroidDeviceInfo *avd) +{ + QTC_ASSERT(avd, return false); + foreach (const QString &line, deviceInfo) { + QString value; + if (valueForKey(avdInfoErrorKey, line)) { + qCDebug(avdManagerLog) << "Avd Parsing: Skip avd device. Error key found:" << line; + return false; + } else if (valueForKey(avdInfoNameKey, line, &value)) { + avd->avdname = value; + } else if (valueForKey(avdInfoPathKey, line, &value)) { + const Utils::FileName avdPath = Utils::FileName::fromString(value); + if (avdPath.exists()) + { + // Get ABI. + Utils::FileName configFile = avdPath; + configFile.appendPath("config.ini"); + QSettings config(configFile.toString(), QSettings::IniFormat); + value = config.value(avdInfoAbiKey).toString(); + if (!value.isEmpty()) + avd->cpuAbi << value; + else + qCDebug(avdManagerLog) << "Avd Parsing: Cannot find ABI:" << configFile; + + // Get Target + Utils::FileName avdInfoFile = avdPath.parentDir(); + QString avdInfoFileName = avdPath.toFileInfo().baseName() + ".ini"; + avdInfoFile.appendPath(avdInfoFileName); + QSettings avdInfo(avdInfoFile.toString(), QSettings::IniFormat); + value = avdInfo.value(avdInfoTargetKey).toString(); + if (!value.isEmpty()) + avd->sdk = value.section('-', -1).toInt(); + else + qCDebug(avdManagerLog) << "Avd Parsing: Cannot find sdk API:" << avdInfoFile.toString(); + } + } + } + return true; +} + +} // namespace Internal +} // namespace Android diff --git a/src/plugins/android/androidavdmanager.h b/src/plugins/android/androidavdmanager.h new file mode 100644 index 00000000000..4e8633efda9 --- /dev/null +++ b/src/plugins/android/androidavdmanager.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** 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 "androidconfigurations.h" + +#include + +namespace Android { +namespace Internal { + +class AndroidToolManager; +class AvdManagerOutputParser; + +class AndroidAvdManager +{ +public: + AndroidAvdManager(const AndroidConfig& config = AndroidConfigurations::currentConfig()); + ~AndroidAvdManager(); + + bool avdManagerUiToolAvailable() const; + void launchAvdManagerUiTool() const; + QFuture createAvd(AndroidConfig::CreateAvdInfo info) const; + bool removeAvd(const QString &name) const; + QFuture avdList() const; + + QString startAvd(const QString &name) const; + bool startAvdAsync(const QString &avdName) const; + QString findAvd(const QString &avdName) const; + QString waitForAvd(const QString &avdName, + const QFutureInterface &fi = QFutureInterface()) const; + bool isAvdBooted(const QString &device) const; + +private: + bool waitForBooted(const QString &serialNumber, const QFutureInterface &fi) const; + +private: + const AndroidConfig &m_config; + std::unique_ptr m_androidTool; + std::unique_ptr m_parser; +}; + +} // namespace Internal +} // namespace Android diff --git a/src/plugins/android/androidconfigurations.cpp b/src/plugins/android/androidconfigurations.cpp index 32b2ffcd2e5..44d65a13620 100644 --- a/src/plugins/android/androidconfigurations.cpp +++ b/src/plugins/android/androidconfigurations.cpp @@ -386,7 +386,10 @@ FileName AndroidConfig::antToolPath() const FileName AndroidConfig::emulatorToolPath() const { FileName path = m_sdkLocation; - return path.appendPath(QLatin1String("tools/emulator" QTC_HOST_EXE_SUFFIX)); + QString relativePath = "emulator/emulator"; + if (sdkToolsVersion() < QVersionNumber(25, 3, 0)) + relativePath = "tools/emulator"; + return path.appendPath(relativePath + QTC_HOST_EXE_SUFFIX); } FileName AndroidConfig::toolPath(const Abi &abi, const QString &ndkToolChainVersion) const @@ -409,6 +412,16 @@ FileName AndroidConfig::sdkManagerToolPath() const return sdkPath; } +FileName AndroidConfig::avdManagerToolPath() const +{ + FileName avdManagerPath = m_sdkLocation; + QString toolPath = "tools/bin/avdmanager"; + if (HostOsInfo::isWindowsHost()) + toolPath += ANDROID_BAT_SUFFIX; + avdManagerPath = avdManagerPath.appendPath(toolPath); + return avdManagerPath; +} + FileName AndroidConfig::gccPath(const Abi &abi, Core::Id lang, const QString &ndkToolChainVersion) const { @@ -515,46 +528,6 @@ AndroidConfig::CreateAvdInfo AndroidConfig::gatherCreateAVDInfo(QWidget *parent, return result; } -QString AndroidConfig::startAVD(const QString &name) const -{ - if (!findAvd(name).isEmpty() || startAVDAsync(name)) - return waitForAvd(name); - return QString(); -} - -bool AndroidConfig::startAVDAsync(const QString &avdName) const -{ - QProcess *avdProcess = new QProcess(); - QObject::connect(avdProcess, static_cast(&QProcess::finished), - avdProcess, &QObject::deleteLater); - - // start the emulator - QStringList arguments; - if (AndroidConfigurations::force32bitEmulator()) - arguments << QLatin1String("-force-32bit"); - - arguments << QLatin1String("-partition-size") << QString::number(partitionSize()) - << QLatin1String("-avd") << avdName; - avdProcess->start(emulatorToolPath().toString(), arguments); - if (!avdProcess->waitForStarted(-1)) { - delete avdProcess; - return false; - } - return true; -} - -QString AndroidConfig::findAvd(const QString &avdName) const -{ - QVector devices = connectedDevices(); - foreach (AndroidDeviceInfo device, devices) { - if (device.type != AndroidDeviceInfo::Emulator) - continue; - if (device.avdname == avdName) - return device.serialNumber; - } - return QString(); -} - bool AndroidConfig::isConnected(const QString &serialNumber) const { QVector devices = connectedDevices(); @@ -565,39 +538,6 @@ bool AndroidConfig::isConnected(const QString &serialNumber) const return false; } -bool AndroidConfig::waitForBooted(const QString &serialNumber, const QFutureInterface &fi) const -{ - // found a serial number, now wait until it's done booting... - for (int i = 0; i < 60; ++i) { - if (fi.isCanceled()) - return false; - if (hasFinishedBooting(serialNumber)) { - return true; - } else { - QThread::sleep(2); - if (!isConnected(serialNumber)) // device was disconnected - return false; - } - } - return false; -} - -QString AndroidConfig::waitForAvd(const QString &avdName, const QFutureInterface &fi) const -{ - // we cannot use adb -e wait-for-device, since that doesn't work if a emulator is already running - // 60 rounds of 2s sleeping, two minutes for the avd to start - QString serialNumber; - for (int i = 0; i < 60; ++i) { - if (fi.isCanceled()) - return QString(); - serialNumber = findAvd(avdName); - if (!serialNumber.isEmpty()) - return waitForBooted(serialNumber, fi) ? serialNumber : QString(); - QThread::sleep(2); - } - return QString(); -} - bool AndroidConfig::isBootToQt(const QString &device) const { return isBootToQt(adbToolPath().toString(), device); @@ -720,21 +660,6 @@ QString AndroidConfig::getProductModel(const QString &device) const return model; } -bool AndroidConfig::hasFinishedBooting(const QString &device) const -{ - QStringList arguments = AndroidDeviceInfo::adbSelector(device); - arguments << QLatin1String("shell") << QLatin1String("getprop") - << QLatin1String("init.svc.bootanim"); - - SynchronousProcess adbProc; - adbProc.setTimeoutS(10); - SynchronousProcessResponse response = adbProc.runBlocking(adbToolPath().toString(), arguments); - if (response.result != SynchronousProcessResponse::Finished) - return false; - QString value = response.allOutput().trimmed(); - return value == QLatin1String("stopped"); -} - QStringList AndroidConfig::getAbis(const QString &device) const { return getAbis(adbToolPath().toString(), device); diff --git a/src/plugins/android/androidconfigurations.h b/src/plugins/android/androidconfigurations.h index bc4962d8915..00c8f49fe4e 100644 --- a/src/plugins/android/androidconfigurations.h +++ b/src/plugins/android/androidconfigurations.h @@ -72,6 +72,7 @@ public: bool isValid() const { return !serialNumber.isEmpty() || !avdname.isEmpty(); } bool operator<(const AndroidDeviceInfo &other) const; }; +using AndroidDeviceInfoList = QList; //! Defines an Android system image. class SystemImage @@ -89,6 +90,7 @@ using SystemImageList = QList; class SdkPlatform { public: + bool isValid() const { return !name.isEmpty() && apiLevel != -1; } bool operator <(const SdkPlatform &other) const; int apiLevel = -1; QString name; @@ -141,6 +143,7 @@ public: Utils::FileName antToolPath() const; Utils::FileName emulatorToolPath() const; Utils::FileName sdkManagerToolPath() const; + Utils::FileName avdManagerToolPath() const; Utils::FileName gccPath(const ProjectExplorer::Abi &abi, Core::Id lang, const QString &ndkToolChainVersion) const; @@ -152,7 +155,8 @@ public: class CreateAvdInfo { public: - QString target; + bool isValid() const { return target.isValid() && !name.isEmpty(); } + SdkPlatform target; QString name; QString abi; int sdcardSize = 0; @@ -164,10 +168,6 @@ public: QVector connectedDevices(QString *error = 0) const; static QVector connectedDevices(const QString &adbToolPath, QString *error = 0); - QString startAVD(const QString &name) const; - bool startAVDAsync(const QString &avdName) const; - QString findAvd(const QString &avdName) const; - QString waitForAvd(const QString &avdName, const QFutureInterface &fi = QFutureInterface()) const; QString bestNdkPlatformMatch(int target) const; static ProjectExplorer::Abi abiForToolChainPrefix(const QString &toolchainPrefix); @@ -178,8 +178,6 @@ public: QString getProductModel(const QString &device) const; enum class OpenGl { Enabled, Disabled, Unknown }; OpenGl getOpenGLEnabled(const QString &emulator) const; - bool hasFinishedBooting(const QString &device) const; - bool waitForBooted(const QString &serialNumber, const QFutureInterface &fi) const; bool isConnected(const QString &serialNumber) const; SdkPlatform highestAndroidSdk() const; @@ -257,3 +255,5 @@ private: }; } // namespace Android +Q_DECLARE_METATYPE(Android::SdkPlatform) + diff --git a/src/plugins/android/androiddeployqtstep.cpp b/src/plugins/android/androiddeployqtstep.cpp index 98efc64ffe1..b4bfbc7bc16 100644 --- a/src/plugins/android/androiddeployqtstep.cpp +++ b/src/plugins/android/androiddeployqtstep.cpp @@ -33,6 +33,7 @@ #include "androidmanager.h" #include "androidconstants.h" #include "androidglobal.h" +#include "androidavdmanager.h" #include #include @@ -262,8 +263,9 @@ bool AndroidDeployQtStep::init(QList &earlierSteps) m_adbPath = AndroidConfigurations::currentConfig().adbToolPath().toString(); - if (AndroidConfigurations::currentConfig().findAvd(m_avdName).isEmpty()) - AndroidConfigurations::currentConfig().startAVDAsync(m_avdName); + AndroidAvdManager avdManager; + if (avdManager.findAvd(m_avdName).isEmpty()) + avdManager.startAvdAsync(m_avdName); return true; } @@ -414,7 +416,7 @@ void AndroidDeployQtStep::slotSetSerialNumber(const QString &serialNumber) void AndroidDeployQtStep::run(QFutureInterface &fi) { if (!m_avdName.isEmpty()) { - QString serialNumber = AndroidConfigurations::currentConfig().waitForAvd(m_avdName, fi); + QString serialNumber = AndroidAvdManager().waitForAvd(m_avdName, fi); if (serialNumber.isEmpty()) { reportRunResult(fi, false); return; diff --git a/src/plugins/android/androiddevicedialog.cpp b/src/plugins/android/androiddevicedialog.cpp index 7b639702cff..451abf2dc53 100644 --- a/src/plugins/android/androiddevicedialog.cpp +++ b/src/plugins/android/androiddevicedialog.cpp @@ -25,6 +25,7 @@ #include "androiddevicedialog.h" #include "androidmanager.h" +#include "androidavdmanager.h" #include "ui_androiddevicedialog.h" #include @@ -424,7 +425,7 @@ AndroidDeviceDialog::AndroidDeviceDialog(int apiLevel, const QString &abi, Andro m_apiLevel(apiLevel), m_abi(abi), m_defaultDevice(serialNumber), - m_androidToolManager(new AndroidToolManager(AndroidConfigurations::currentConfig())) + m_avdManager(new AndroidAvdManager) { m_ui->setupUi(this); m_ui->deviceView->setModel(m_model); @@ -516,7 +517,7 @@ void AndroidDeviceDialog::refreshDeviceList() m_ui->refreshDevicesButton->setEnabled(false); m_progressIndicator->show(); m_connectedDevices = AndroidConfig::connectedDevices(AndroidConfigurations::currentConfig().adbToolPath().toString()); - m_futureWatcherRefreshDevices.setFuture(m_androidToolManager->androidVirtualDevicesFuture()); + m_futureWatcherRefreshDevices.setFuture(m_avdManager->avdList()); } void AndroidDeviceDialog::devicesRefreshed() @@ -531,7 +532,7 @@ void AndroidDeviceDialog::devicesRefreshed() serialNumber = deviceType == AndroidDeviceInfo::Hardware ? info.serialNumber : info.avdname; } - QVector devices = m_futureWatcherRefreshDevices.result(); + AndroidDeviceInfoList devices = m_futureWatcherRefreshDevices.result(); QSet startedAvds = Utils::transform(m_connectedDevices, [] (const AndroidDeviceInfo &info) { return info.avdname; @@ -584,12 +585,12 @@ void AndroidDeviceDialog::createAvd() m_ui->createAVDButton->setEnabled(false); AndroidConfig::CreateAvdInfo info = AndroidConfigurations::currentConfig().gatherCreateAVDInfo(this, m_apiLevel, m_abi); - if (info.target.isEmpty()) { + if (!info.target.isValid()) { m_ui->createAVDButton->setEnabled(true); return; } - m_futureWatcherAddDevice.setFuture(m_androidToolManager->createAvd(info)); + m_futureWatcherAddDevice.setFuture(m_avdManager->createAvd(info)); } void AndroidDeviceDialog::avdAdded() diff --git a/src/plugins/android/androiddevicedialog.h b/src/plugins/android/androiddevicedialog.h index 1f360215578..8f957aa0b20 100644 --- a/src/plugins/android/androiddevicedialog.h +++ b/src/plugins/android/androiddevicedialog.h @@ -26,7 +26,6 @@ #pragma once #include "androidconfigurations.h" -#include "androidtoolmanager.h" #include #include @@ -44,6 +43,7 @@ namespace Utils { class ProgressIndicator; } namespace Android { namespace Internal { +class AndroidAvdManager; class AndroidDeviceModel; namespace Ui { class AndroidDeviceDialog; } @@ -77,10 +77,10 @@ private: QString m_abi; QString m_avdNameFromAdd; QString m_defaultDevice; - std::unique_ptr m_androidToolManager; + std::unique_ptr m_avdManager; QVector m_connectedDevices; QFutureWatcher m_futureWatcherAddDevice; - QFutureWatcher> m_futureWatcherRefreshDevices; + QFutureWatcher m_futureWatcherRefreshDevices; }; } diff --git a/src/plugins/android/androidmanager.cpp b/src/plugins/android/androidmanager.cpp index 95c8225d553..ee573990607 100644 --- a/src/plugins/android/androidmanager.cpp +++ b/src/plugins/android/androidmanager.cpp @@ -34,6 +34,7 @@ #include "androidqtsupport.h" #include "androidqtversion.h" #include "androidbuildapkstep.h" +#include "androidavdmanager.h" #include #include @@ -345,7 +346,7 @@ void AndroidManager::cleanLibsOnDevice(ProjectExplorer::Target *target) QString deviceSerialNumber = info.serialNumber; if (info.type == AndroidDeviceInfo::Emulator) { - deviceSerialNumber = AndroidConfigurations::currentConfig().startAVD(info.avdname); + deviceSerialNumber = AndroidAvdManager().startAvd(info.avdname); if (deviceSerialNumber.isEmpty()) Core::MessageManager::write(tr("Starting Android virtual device failed.")); } @@ -374,7 +375,7 @@ void AndroidManager::installQASIPackage(ProjectExplorer::Target *target, const Q QString deviceSerialNumber = info.serialNumber; if (info.type == AndroidDeviceInfo::Emulator) { - deviceSerialNumber = AndroidConfigurations::currentConfig().startAVD(info.avdname); + deviceSerialNumber = AndroidAvdManager().startAvd(info.avdname); if (deviceSerialNumber.isEmpty()) Core::MessageManager::write(tr("Starting Android virtual device failed.")); } diff --git a/src/plugins/android/androidrunner.cpp b/src/plugins/android/androidrunner.cpp index e7b95bc0767..ad1b511938c 100644 --- a/src/plugins/android/androidrunner.cpp +++ b/src/plugins/android/androidrunner.cpp @@ -31,6 +31,7 @@ #include "androidglobal.h" #include "androidrunconfiguration.h" #include "androidmanager.h" +#include "androidavdmanager.h" #include #include @@ -791,8 +792,9 @@ void AndroidRunner::launchAVD() emit adbParametersChanged(m_androidRunnable.packageName, AndroidDeviceInfo::adbSelector(info.serialNumber)); if (info.isValid()) { - if (AndroidConfigurations::currentConfig().findAvd(info.avdname).isEmpty()) { - bool launched = AndroidConfigurations::currentConfig().startAVDAsync(info.avdname); + AndroidAvdManager avdManager; + if (avdManager.findAvd(info.avdname).isEmpty()) { + bool launched = avdManager.startAvdAsync(info.avdname); m_launchedAVDName = launched ? info.avdname:""; } else { m_launchedAVDName.clear(); @@ -803,11 +805,12 @@ void AndroidRunner::launchAVD() void AndroidRunner::checkAVD() { const AndroidConfig &config = AndroidConfigurations::currentConfig(); - QString serialNumber = config.findAvd(m_launchedAVDName); + AndroidAvdManager avdManager(config); + QString serialNumber = avdManager.findAvd(m_launchedAVDName); if (!serialNumber.isEmpty()) return; // try again on next timer hit - if (config.hasFinishedBooting(serialNumber)) { + if (avdManager.isAvdBooted(serialNumber)) { m_checkAVDTimer.stop(); AndroidManager::setDeviceSerialNumber(m_runConfig->target(), serialNumber); emit asyncStart(m_androidRunnable.intentName, m_androidRunnable.beforeStartADBCommands); diff --git a/src/plugins/android/androidsettingswidget.cpp b/src/plugins/android/androidsettingswidget.cpp index 013695043c7..2e9c4c4b6b9 100644 --- a/src/plugins/android/androidsettingswidget.cpp +++ b/src/plugins/android/androidsettingswidget.cpp @@ -30,7 +30,7 @@ #include "androidconfigurations.h" #include "androidconstants.h" #include "androidtoolchain.h" -#include "androidtoolmanager.h" +#include "androidavdmanager.h" #include #include @@ -59,7 +59,7 @@ namespace Android { namespace Internal { -void AvdModel::setAvdList(const QVector &list) +void AvdModel::setAvdList(const AndroidDeviceInfoList &list) { beginResetModel(); m_list = list; @@ -130,7 +130,7 @@ AndroidSettingsWidget::AndroidSettingsWidget(QWidget *parent) m_javaState(NotSet), m_ui(new Ui_AndroidSettingsWidget), m_androidConfig(AndroidConfigurations::currentConfig()), - m_androidToolManager(new AndroidToolManager(m_androidConfig)) + m_avdManager(new AndroidAvdManager(m_androidConfig)) { m_ui->setupUi(this); @@ -465,7 +465,7 @@ void AndroidSettingsWidget::enableAvdControls() void AndroidSettingsWidget::startUpdateAvd() { disableAvdControls(); - m_virtualDevicesWatcher.setFuture(m_androidToolManager->androidVirtualDevicesFuture()); + m_virtualDevicesWatcher.setFuture(m_avdManager->avdList()); } void AndroidSettingsWidget::updateAvds() @@ -589,12 +589,12 @@ void AndroidSettingsWidget::addAVD() disableAvdControls(); AndroidConfig::CreateAvdInfo info = m_androidConfig.gatherCreateAVDInfo(this); - if (info.target.isEmpty()) { + if (!info.target.isValid()) { enableAvdControls(); return; } - m_futureWatcher.setFuture(m_androidToolManager->createAvd(info)); + m_futureWatcher.setFuture(m_avdManager->createAvd(info)); } void AndroidSettingsWidget::avdAdded() @@ -622,13 +622,13 @@ void AndroidSettingsWidget::removeAVD() return; } - m_androidToolManager->removeAvd(avdName); + m_avdManager->removeAvd(avdName); startUpdateAvd(); } void AndroidSettingsWidget::startAVD() { - m_androidConfig.startAVDAsync(m_AVDModel.avdName(m_ui->AVDTableView->currentIndex())); + m_avdManager->startAvdAsync(m_AVDModel.avdName(m_ui->AVDTableView->currentIndex())); } void AndroidSettingsWidget::avdActivated(const QModelIndex &index) @@ -673,7 +673,15 @@ void AndroidSettingsWidget::showGdbWarningDialog() void AndroidSettingsWidget::manageAVD() { - m_androidToolManager->launchAvdManager(); + if (m_avdManager->avdManagerUiToolAvailable()) { + m_avdManager->launchAvdManagerUiTool(); + } else { + QMessageBox::warning(this, tr("AVD Manager Not Available"), + tr("AVD manager UI tool is not available in the installed SDK tools" + "(version %1). Use the command line tool \"avdmanager\" for " + "advanced AVD management.") + .arg(m_androidConfig.sdkToolsVersion().toString())); + } } diff --git a/src/plugins/android/androidsettingswidget.h b/src/plugins/android/androidsettingswidget.h index 091ce25ad53..d2fc860fa13 100644 --- a/src/plugins/android/androidsettingswidget.h +++ b/src/plugins/android/androidsettingswidget.h @@ -42,13 +42,13 @@ QT_END_NAMESPACE namespace Android { namespace Internal { -class AndroidToolManager; +class AndroidAvdManager; class AvdModel: public QAbstractTableModel { Q_OBJECT public: - void setAvdList(const QVector &list); + void setAvdList(const AndroidDeviceInfoList &list); QString avdName(const QModelIndex &index) const; QModelIndex indexForAvdName(const QString &avdName) const; @@ -59,7 +59,7 @@ protected: int columnCount(const QModelIndex &parent = QModelIndex()) const; private: - QVector m_list; + AndroidDeviceInfoList m_list; }; class AndroidSettingsWidget : public QWidget @@ -116,14 +116,14 @@ private: Ui_AndroidSettingsWidget *m_ui; AndroidConfig m_androidConfig; - std::unique_ptr m_androidToolManager; AvdModel m_AVDModel; QFutureWatcher m_futureWatcher; QFutureWatcher> m_checkGdbWatcher; QStringList m_gdbCheckPaths; - QFutureWatcher> m_virtualDevicesWatcher; + QFutureWatcher m_virtualDevicesWatcher; QString m_lastAddedAvd; + std::unique_ptr m_avdManager; }; } // namespace Internal diff --git a/src/plugins/android/androidtoolmanager.cpp b/src/plugins/android/androidtoolmanager.cpp index 508f542e85f..a6e61d96d9f 100644 --- a/src/plugins/android/androidtoolmanager.cpp +++ b/src/plugins/android/androidtoolmanager.cpp @@ -133,7 +133,7 @@ bool AndroidToolManager::removeAvd(const QString &name) const return response.result == SynchronousProcessResponse::Finished && response.exitCode == 0; } -QFuture> AndroidToolManager::androidVirtualDevicesFuture() const +QFuture AndroidToolManager::androidVirtualDevicesFuture() const { return Utils::runAsync(&AndroidToolManager::androidVirtualDevices, m_config.androidToolPath(), m_config.sdkLocation(), @@ -161,7 +161,7 @@ AndroidConfig::CreateAvdInfo AndroidToolManager::createAvdImpl(AndroidConfig::Cr proc.setProcessEnvironment(env.toProcessEnvironment()); QStringList arguments; arguments << QLatin1String("create") << QLatin1String("avd") - << QLatin1String("-t") << info.target + << QLatin1String("-t") << info.target.name << QLatin1String("-n") << info.name << QLatin1String("-b") << info.abi; if (info.sdcardSize > 0) @@ -206,12 +206,11 @@ AndroidConfig::CreateAvdInfo AndroidToolManager::createAvdImpl(AndroidConfig::Cr return info; } -QVector -AndroidToolManager::androidVirtualDevices(const Utils::FileName &androidTool, - const FileName &sdkLocationPath, - const Environment &environment) +AndroidDeviceInfoList AndroidToolManager::androidVirtualDevices(const Utils::FileName &androidTool, + const FileName &sdkLocationPath, + const Environment &environment) { - QVector devices; + AndroidDeviceInfoList devices; QString output; if (!androidToolCommand(androidTool, QStringList({"list", "avd"}), environment, &output)) return devices; diff --git a/src/plugins/android/androidtoolmanager.h b/src/plugins/android/androidtoolmanager.h index 24f27e2c9f2..befb095b92a 100644 --- a/src/plugins/android/androidtoolmanager.h +++ b/src/plugins/android/androidtoolmanager.h @@ -53,14 +53,14 @@ public: QFuture createAvd(AndroidConfig::CreateAvdInfo info) const; bool removeAvd(const QString &name) const; - QFuture > androidVirtualDevicesFuture() 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, + static AndroidDeviceInfoList androidVirtualDevices(const Utils::FileName &androidTool, const Utils::FileName &sdkLlocationPath, const Utils::Environment &environment); private: diff --git a/src/plugins/android/avddialog.cpp b/src/plugins/android/avddialog.cpp index c003db7f98b..eb7da93e901 100644 --- a/src/plugins/android/avddialog.cpp +++ b/src/plugins/android/avddialog.cpp @@ -68,12 +68,12 @@ AvdDialog::AvdDialog(int minApiLevel, const QString &targetArch, const AndroidCo bool AvdDialog::isValid() const { - return !name().isEmpty() && !target().isEmpty() && !abi().isEmpty(); + return !name().isEmpty() && target().isValid() && !abi().isEmpty(); } -QString AvdDialog::target() const +SdkPlatform AvdDialog::target() const { - return m_avdDialog.targetComboBox->currentText(); + return m_avdDialog.targetComboBox->currentData().value(); } QString AvdDialog::name() const @@ -93,8 +93,8 @@ int AvdDialog::sdcardSize() const void AvdDialog::updateApiLevelComboBox() { - QList filteredList; - QList platforms = m_config->sdkTargets(m_minApiLevel); + SdkPlatformList filteredList; + SdkPlatformList platforms = m_config->sdkTargets(m_minApiLevel); QString selectedAbi = abi(); auto hasAbi = [selectedAbi](const SystemImage &image) { @@ -106,7 +106,10 @@ void AvdDialog::updateApiLevelComboBox() }); m_avdDialog.targetComboBox->clear(); - m_avdDialog.targetComboBox->addItems(AndroidConfig::apiLevelNamesFor(filteredList)); + foreach (const SdkPlatform &platform, filteredList) { + m_avdDialog.targetComboBox->addItem(AndroidConfig::apiLevelNameFor(platform), + QVariant::fromValue(platform)); + } if (platforms.isEmpty()) { m_avdDialog.warningIcon->setVisible(true); diff --git a/src/plugins/android/avddialog.h b/src/plugins/android/avddialog.h index 950285aadce..d22e6af64f5 100644 --- a/src/plugins/android/avddialog.h +++ b/src/plugins/android/avddialog.h @@ -32,6 +32,7 @@ namespace Android { class AndroidConfig; +class SdkPlatform; namespace Internal { @@ -42,7 +43,7 @@ public: explicit AvdDialog(int minApiLevel, const QString &targetArch, const AndroidConfig *config, QWidget *parent = 0); - QString target() const; + Android::SdkPlatform target() const; QString name() const; QString abi() const; int sdcardSize() const;