diff --git a/src/plugins/android/addnewavddialog.ui b/src/plugins/android/addnewavddialog.ui index 71abe36e9be..c60b43fdb3e 100644 --- a/src/plugins/android/addnewavddialog.ui +++ b/src/plugins/android/addnewavddialog.ui @@ -7,7 +7,7 @@ 0 0 600 - 187 + 243 @@ -30,45 +30,49 @@ + + + 0 + 0 + + Name: - - - - + Architecture (ABI): - - - - - - - Target API: - - - - - - - - - - + SD card size: - + + + + Target API: + + + + + + + Device definition: + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -87,6 +91,35 @@ + + + + + + + + + + + + + + + + + 0 + 0 + + + + + + + + Overwrite existing AVD name + + + diff --git a/src/plugins/android/androidavdmanager.cpp b/src/plugins/android/androidavdmanager.cpp index 10ec89118a6..9d871a6fff8 100644 --- a/src/plugins/android/androidavdmanager.cpp +++ b/src/plugins/android/androidavdmanager.cpp @@ -71,7 +71,7 @@ const int avdCreateTimeoutMs = 30000; \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) +bool AndroidAvdManager::avdManagerCommand(const AndroidConfig config, const QStringList &args, QString *output) { CommandLine cmd(config.avdManagerToolPath(), args); Utils::SynchronousProcess proc; @@ -118,7 +118,7 @@ static CreateAvdInfo createAvdCommand(const AndroidConfig config, const CreateAv if (!result.isValid()) { qCDebug(avdManagerLog) << "AVD Create failed. Invalid CreateAvdInfo" << result.name - << result.sdkPlatform->displayText() << result.sdkPlatform->apiLevel(); + << result.systemImage->displayText() << result.systemImage->apiLevel(); result.error = QApplication::translate("AndroidAvdManager", "Cannot create AVD. Invalid input."); return result; @@ -126,28 +126,17 @@ static CreateAvdInfo createAvdCommand(const AndroidConfig config, const CreateAv QStringList arguments({"create", "avd", "-n", result.name}); - if (!result.abi.isEmpty()) { - SystemImage *image = Utils::findOrDefault(result.sdkPlatform->systemImages(), - Utils::equal(&SystemImage::abiName, result.abi)); - if (image && image->isValid()) { - arguments << "-k" << image->sdkStylePath(); - } else { - QString name = result.sdkPlatform->displayText(); - qCDebug(avdManagerLog) << "AVD Create failed. Cannot find system image for the platform" - << result.abi << name; - result.error = QApplication::translate("AndroidAvdManager", - "Cannot create AVD. Cannot find system image for " - "the ABI %1(%2).").arg(result.abi).arg(name); - return result; - } - - } else { - arguments << "-k" << result.sdkPlatform->sdkStylePath(); - } + arguments << "-k" << result.systemImage->sdkStylePath(); if (result.sdcardSize > 0) arguments << "-c" << QString::fromLatin1("%1M").arg(result.sdcardSize); + if (!result.deviceDefinition.isEmpty() && result.deviceDefinition != "Custom") + arguments << "-d" << QString::fromLatin1("%1").arg(result.deviceDefinition); + + if (result.overwrite) + arguments << "-f"; + QProcess proc; proc.start(config.avdManagerToolPath().toString(), arguments); if (!proc.waitForStarted()) { @@ -419,7 +408,7 @@ AndroidDeviceInfoList AvdManagerOutputParser::listVirtualDevices(const AndroidCo AndroidDeviceInfoList avdList; do { - if (!avdManagerCommand(config, {"list", "avd"}, &output)) { + if (!AndroidAvdManager::avdManagerCommand(config, {"list", "avd"}, &output)) { qCDebug(avdManagerLog) << "Avd list command failed" << output << config.sdkToolsVersion(); return {}; diff --git a/src/plugins/android/androidavdmanager.h b/src/plugins/android/androidavdmanager.h index 777fa5a9f8b..41244b32724 100644 --- a/src/plugins/android/androidavdmanager.h +++ b/src/plugins/android/androidavdmanager.h @@ -54,6 +54,9 @@ public: QString waitForAvd(const QString &avdName, const std::function &cancelChecker = {}) const; bool isAvdBooted(const QString &device) const; + static bool avdManagerCommand(const AndroidConfig config, + const QStringList &args, + QString *output); private: bool waitForBooted(const QString &serialNumber, diff --git a/src/plugins/android/androidconfigurations.h b/src/plugins/android/androidconfigurations.h index 55a72747e2f..1756056b25d 100644 --- a/src/plugins/android/androidconfigurations.h +++ b/src/plugins/android/androidconfigurations.h @@ -85,12 +85,14 @@ using AndroidDeviceInfoList = QList; class CreateAvdInfo { public: - bool isValid() const { return sdkPlatform && sdkPlatform->isValid() && !name.isEmpty(); } - const SdkPlatform *sdkPlatform = nullptr; + bool isValid() const { return systemImage && systemImage->isValid() && !name.isEmpty(); } + const SystemImage *systemImage = nullptr; QString name; QString abi; + QString deviceDefinition; int sdcardSize = 0; QString error; // only used in the return value of createAVD + bool overwrite = false; }; class ANDROID_EXPORT AndroidConfig diff --git a/src/plugins/android/androiddevicedialog.cpp b/src/plugins/android/androiddevicedialog.cpp index cd8ae2f6114..4fb41262126 100644 --- a/src/plugins/android/androiddevicedialog.cpp +++ b/src/plugins/android/androiddevicedialog.cpp @@ -578,7 +578,7 @@ void AndroidDeviceDialog::createAvd() { m_ui->createAVDButton->setEnabled(false); CreateAvdInfo info = AvdDialog::gatherCreateAVDInfo(this, AndroidConfigurations::sdkManager(), - m_apiLevel, m_abis); + AndroidConfigurations::currentConfig(), m_apiLevel, m_abis); if (!info.isValid()) { m_ui->createAVDButton->setEnabled(true); diff --git a/src/plugins/android/androidsdkmanager.cpp b/src/plugins/android/androidsdkmanager.cpp index b77bdd679ae..6dc76a5f780 100644 --- a/src/plugins/android/androidsdkmanager.cpp +++ b/src/plugins/android/androidsdkmanager.cpp @@ -345,6 +345,21 @@ AndroidSdkPackageList AndroidSdkManager::installedSdkPackages() return m_d->filteredPackages(AndroidSdkPackage::Installed, AndroidSdkPackage::AnyValidType); } +SystemImageList AndroidSdkManager::installedSystemImages() +{ + AndroidSdkPackageList list = m_d->filteredPackages(AndroidSdkPackage::AnyValidState, + AndroidSdkPackage::SdkPlatformPackage); + QList platforms = Utils::static_container_cast(list); + + SystemImageList result; + for (SdkPlatform *platform : platforms) { + if (platform->systemImages().size() > 0) + result.append(platform->systemImages()); + } + + return result; +} + SdkPlatform *AndroidSdkManager::latestAndroidSdkPlatform(AndroidSdkPackage::PackageState state) { SdkPlatform *result = nullptr; @@ -679,6 +694,7 @@ QPair SdkManagerOutputParser::parseSystemImage(const QString image->setInstalledLocation(packageData.installedLocation); image->setDisplayText(packageData.description); image->setDescriptionText(packageData.description); + image->setApiLevel(apiLevel); result = qMakePair(image, apiLevel); } else { qCDebug(sdkManagerLog) << "System-image: Minimum required data unavailable: "<< data; diff --git a/src/plugins/android/androidsdkmanager.h b/src/plugins/android/androidsdkmanager.h index a78a9fbaab9..fa0d52e1acc 100644 --- a/src/plugins/android/androidsdkmanager.h +++ b/src/plugins/android/androidsdkmanager.h @@ -69,6 +69,7 @@ public: const AndroidSdkPackageList &allSdkPackages(); AndroidSdkPackageList availableSdkPackages(); AndroidSdkPackageList installedSdkPackages(); + SystemImageList installedSystemImages(); SdkPlatform *latestAndroidSdkPlatform(AndroidSdkPackage::PackageState state = AndroidSdkPackage::Installed); diff --git a/src/plugins/android/androidsdkpackage.cpp b/src/plugins/android/androidsdkpackage.cpp index 59978b3b7d6..8c6e0d79114 100644 --- a/src/plugins/android/androidsdkpackage.cpp +++ b/src/plugins/android/androidsdkpackage.cpp @@ -134,6 +134,16 @@ void SystemImage::setPlatform(SdkPlatform *platform) m_platform = platform; } +int SystemImage::apiLevel() const +{ + return m_apiLevel; +} + +void SystemImage::setApiLevel(const int apiLevel) +{ + m_apiLevel = apiLevel; +} + SdkPlatform::SdkPlatform(QVersionNumber version, QString sdkStylePathStr, int api, QObject *parent) : AndroidSdkPackage(version, sdkStylePathStr, parent), m_apiLevel(api) diff --git a/src/plugins/android/androidsdkpackage.h b/src/plugins/android/androidsdkpackage.h index 9f85e87be9c..035e146f97b 100644 --- a/src/plugins/android/androidsdkpackage.h +++ b/src/plugins/android/androidsdkpackage.h @@ -114,10 +114,13 @@ public: const QString &abiName() const; const SdkPlatform *platform() const; void setPlatform(SdkPlatform *platform); + int apiLevel() const; + void setApiLevel(const int apiLevel); private: QPointer m_platform; QString m_abiName; + int m_apiLevel = -1; }; using SystemImageList = QList; diff --git a/src/plugins/android/androidsettingswidget.cpp b/src/plugins/android/androidsettingswidget.cpp index 0e9a97e98a4..65015240f0e 100644 --- a/src/plugins/android/androidsettingswidget.cpp +++ b/src/plugins/android/androidsettingswidget.cpp @@ -260,7 +260,8 @@ QVariant AvdModel::data(const QModelIndex &index, int role) const return cpuAbis.isEmpty() ? QVariant() : QVariant(cpuAbis.first()); } case 3: - return currentRow.avdDevice; + return currentRow.avdDevice.isEmpty() ? QVariant("Custom") + : currentRow.avdDevice; case 4: return currentRow.avdTarget; case 5: @@ -556,7 +557,7 @@ void AndroidSettingsWidget::openOpenJDKDownloadUrl() void AndroidSettingsWidget::addAVD() { disableAvdControls(); - CreateAvdInfo info = AvdDialog::gatherCreateAVDInfo(this, m_sdkManager.get()); + CreateAvdInfo info = AvdDialog::gatherCreateAVDInfo(this, m_sdkManager.get(), m_androidConfig); if (!info.isValid()) { enableAvdControls(); diff --git a/src/plugins/android/androidtoolmanager.cpp b/src/plugins/android/androidtoolmanager.cpp index 2db6cecb265..b52a2916df1 100644 --- a/src/plugins/android/androidtoolmanager.cpp +++ b/src/plugins/android/androidtoolmanager.cpp @@ -148,7 +148,7 @@ CreateAvdInfo AndroidToolManager::createAvdImpl(CreateAvdInfo info, FilePath and proc.setProcessEnvironment(env); QStringList arguments; arguments << QLatin1String("create") << QLatin1String("avd") - << QLatin1String("-t") << AndroidConfig::apiLevelNameFor(info.sdkPlatform) + << QLatin1String("-t") << QString("android-%1").arg(info.systemImage->apiLevel()) << QLatin1String("-n") << info.name << QLatin1String("-b") << info.abi; if (info.sdcardSize > 0) diff --git a/src/plugins/android/avddialog.cpp b/src/plugins/android/avddialog.cpp index aa1b0422f89..0c2ce72acc5 100644 --- a/src/plugins/android/avddialog.cpp +++ b/src/plugins/android/avddialog.cpp @@ -25,6 +25,7 @@ #include "avddialog.h" #include "androidsdkmanager.h" +#include "androidavdmanager.h" #include #include @@ -34,16 +35,22 @@ #include #include #include +#include using namespace Android; using namespace Android::Internal; +namespace { +Q_LOGGING_CATEGORY(avdDialogLog, "qtc.android.avdDialog", QtWarningMsg) +} + AvdDialog::AvdDialog(int minApiLevel, AndroidSdkManager *sdkManager, const QStringList &abis, - QWidget *parent) : + const AndroidConfig &config, QWidget *parent) : QDialog(parent), m_sdkManager(sdkManager), m_minApiLevel(minApiLevel), - m_allowedNameChars(QLatin1String("[a-z|A-Z|0-9|._-]*")) + m_allowedNameChars(QLatin1String("[a-z|A-Z|0-9|._-]*")), + m_androidConfig(config) { QTC_CHECK(m_sdkManager); m_avdDialog.setupUi(this); @@ -63,39 +70,119 @@ AvdDialog::AvdDialog(int minApiLevel, AndroidSdkManager *sdkManager, const QStri m_avdDialog.warningText->setType(Utils::InfoLabel::Warning); - updateApiLevelComboBox(); + connect(&m_hideTipTimer, &QTimer::timeout, this, []() { Utils::ToolTip::hide(); }); + parseDeviceDefinitionsList(); + for (const QString &type : DeviceTypeToStringMap.values()) + m_avdDialog.deviceDefinitionTypeComboBox->addItem(type); + + connect(m_avdDialog.deviceDefinitionTypeComboBox, + QOverload::of(&QComboBox::currentIndexChanged), + this, + &AvdDialog::updateDeviceDefinitionComboBox); connect(m_avdDialog.abiComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &AvdDialog::updateApiLevelComboBox); - connect(&m_hideTipTimer, &QTimer::timeout, - this, [](){Utils::ToolTip::hide();}); + m_avdDialog.deviceDefinitionTypeComboBox->setCurrentIndex(1); // Set Phone type as default index + updateApiLevelComboBox(); } bool AvdDialog::isValid() const { - return !name().isEmpty() && sdkPlatform() && sdkPlatform()->isValid() && !abi().isEmpty(); + return !name().isEmpty() && systemImage() && systemImage()->isValid() && !abi().isEmpty(); } CreateAvdInfo AvdDialog::gatherCreateAVDInfo(QWidget *parent, AndroidSdkManager *sdkManager, - int minApiLevel, const QStringList &abis) + const AndroidConfig &config, int minApiLevel, const QStringList &abis) { CreateAvdInfo result; - AvdDialog d(minApiLevel, sdkManager, abis, parent); + AvdDialog d(minApiLevel, sdkManager, abis, config, parent); if (d.exec() != QDialog::Accepted || !d.isValid()) return result; - result.sdkPlatform = d.sdkPlatform(); + result.systemImage = d.systemImage(); result.name = d.name(); result.abi = d.abi(); + result.deviceDefinition = d.deviceDefinition(); result.sdcardSize = d.sdcardSize(); + result.overwrite = d.m_avdDialog.overwriteCheckBox->isChecked(); + return result; } -const SdkPlatform* AvdDialog::sdkPlatform() const +AvdDialog::DeviceType AvdDialog::tagToDeviceType(const QString &type_tag) { - return m_avdDialog.targetApiComboBox->currentData().value(); + if (type_tag.contains("android-wear")) + return AvdDialog::Wear; + else if (type_tag.contains("android-tv")) + return AvdDialog::TV; + else if (type_tag.contains("android-automotive")) + return AvdDialog::Automotive; + else + return AvdDialog::PhoneOrTablet; +} + +void AvdDialog::parseDeviceDefinitionsList() +{ + QString output; + + if (!AndroidAvdManager::avdManagerCommand(m_androidConfig, {"list", "device"}, &output)) { + qCDebug(avdDialogLog) << "Avd list command failed" << output + << m_androidConfig.sdkToolsVersion(); + return; + } + + QStringList avdDeviceInfo; + + for (QString line : output.split('\n')) { + if (line.startsWith("---------") || line.isEmpty()) { + DeviceDefinitionStruct deviceDefinition; + for (const QString &line : avdDeviceInfo) { + QString value; + if (line.contains("id:")) { + deviceDefinition.name_id = line.split("or").at(1); + deviceDefinition.name_id = deviceDefinition.name_id.remove(0, 1).remove('"'); + } else if (line.contains("Tag :")) { + deviceDefinition.type_str = line.split(':').at(1); + deviceDefinition.type_str = deviceDefinition.type_str.remove(0, 1); + } + } + + DeviceType deviceType = tagToDeviceType(deviceDefinition.type_str); + if (deviceType == PhoneOrTablet) { + if (deviceDefinition.name_id.contains("Tablet")) + deviceType = Tablet; + else + deviceType = Phone; + } + deviceDefinition.deviceType = deviceType; + m_deviceDefinitionsList.append(deviceDefinition); + avdDeviceInfo.clear(); + } else { + avdDeviceInfo << line; + } + } +} + +void AvdDialog::updateDeviceDefinitionComboBox() +{ + DeviceType curDeviceType = DeviceTypeToStringMap.key( + m_avdDialog.deviceDefinitionTypeComboBox->currentText()); + + m_avdDialog.deviceDefinitionComboBox->clear(); + for (auto item : m_deviceDefinitionsList) { + if (item.deviceType == curDeviceType) + m_avdDialog.deviceDefinitionComboBox->addItem(item.name_id); + } + m_avdDialog.deviceDefinitionComboBox->addItem("Custom"); + + updateApiLevelComboBox(); +} + +const SystemImage* AvdDialog::systemImage() const +{ + return m_avdDialog.targetApiComboBox->currentData().value(); } QString AvdDialog::name() const @@ -108,6 +195,11 @@ QString AvdDialog::abi() const return m_avdDialog.abiComboBox->currentText(); } +QString AvdDialog::deviceDefinition() const +{ + return m_avdDialog.deviceDefinitionComboBox->currentText(); +} + int AvdDialog::sdcardSize() const { return m_avdDialog.sdcardSizeSpinBox->value(); @@ -115,37 +207,49 @@ int AvdDialog::sdcardSize() const void AvdDialog::updateApiLevelComboBox() { - SdkPlatformList filteredList; - const SdkPlatformList platforms = m_sdkManager->filteredSdkPlatforms(m_minApiLevel); + SystemImageList installedSystemImages = m_sdkManager->installedSystemImages(); + DeviceType curDeviceType = DeviceTypeToStringMap.key( + m_avdDialog.deviceDefinitionTypeComboBox->currentText()); QString selectedAbi = abi(); auto hasAbi = [selectedAbi](const SystemImage *image) { return image && image->isValid() && (image->abiName() == selectedAbi); }; - filteredList = Utils::filtered(platforms, [hasAbi](const SdkPlatform *platform) { - return platform && Utils::anyOf(platform->systemImages(), hasAbi); + SystemImageList filteredList; + filteredList = Utils::filtered(installedSystemImages, [hasAbi, &curDeviceType](const SystemImage *image) { + DeviceType deviceType = tagToDeviceType(image->sdkStylePath().split(';').at(2)); + if (deviceType == PhoneOrTablet && (curDeviceType == Phone || curDeviceType == Tablet)) + curDeviceType = PhoneOrTablet; + return image && deviceType == curDeviceType && hasAbi(image); }); m_avdDialog.targetApiComboBox->clear(); - for (SdkPlatform *platform: filteredList) { - m_avdDialog.targetApiComboBox->addItem(platform->displayText(), - QVariant::fromValue(platform)); - m_avdDialog.targetApiComboBox->setItemData(m_avdDialog.targetApiComboBox->count() - 1, - platform->descriptionText(), Qt::ToolTipRole); + for (SystemImage *image : filteredList) { + QString imageString = "android-" % QString::number(image->apiLevel()); + if (image->sdkStylePath().contains("playstore")) + imageString += " (Google PlayStore)"; + m_avdDialog.targetApiComboBox->addItem(imageString, + QVariant::fromValue(image)); + m_avdDialog.targetApiComboBox->setItemData(m_avdDialog.targetApiComboBox->count() - 1, + image->descriptionText(), + Qt::ToolTipRole); } - if (platforms.isEmpty()) { + if (installedSystemImages.isEmpty()) { + m_avdDialog.targetApiComboBox->setEnabled(false); m_avdDialog.warningText->setVisible(true); m_avdDialog.warningText->setText(tr("Cannot create a new AVD. No sufficiently recent Android SDK available.\n" "Install an SDK of at least API version %1.") .arg(m_minApiLevel)); } else if (filteredList.isEmpty()) { + m_avdDialog.targetApiComboBox->setEnabled(false); m_avdDialog.warningText->setVisible(true); m_avdDialog.warningText->setText(tr("Cannot create a AVD for ABI %1. Install an image for it.") .arg(abi())); } else { m_avdDialog.warningText->setVisible(false); + m_avdDialog.targetApiComboBox->setEnabled(true); } } diff --git a/src/plugins/android/avddialog.h b/src/plugins/android/avddialog.h index 6f32cd43a6e..ba3d75837cd 100644 --- a/src/plugins/android/avddialog.h +++ b/src/plugins/android/avddialog.h @@ -26,6 +26,7 @@ #pragma once #include "androidconfigurations.h" #include "ui_addnewavddialog.h" +#include "androidconfigurations.h" #include #include @@ -40,18 +41,43 @@ class AvdDialog : public QDialog { Q_OBJECT public: - explicit AvdDialog(int minApiLevel, AndroidSdkManager *sdkManager, const QStringList &abis, + explicit AvdDialog(int minApiLevel, + AndroidSdkManager *sdkManager, + const QStringList &abis, + const AndroidConfig &config, QWidget *parent = nullptr); - const SdkPlatform *sdkPlatform() const; + enum DeviceType { TV, Phone, Wear, Tablet, Automotive, PhoneOrTablet }; + + const QMap DeviceTypeToStringMap{ + {TV, "TV"}, + {Phone, "Phone"}, + {Wear, "Wear"}, + {Tablet, "Tablet"}, + {Automotive, "Automotive"} + }; + + struct DeviceDefinitionStruct + { + QString name_id; + QString type_str; + DeviceType deviceType; + }; + + const SystemImage *systemImage() const; QString name() const; QString abi() const; + QString deviceDefinition() const; int sdcardSize() const; bool isValid() const; + static AvdDialog::DeviceType tagToDeviceType(const QString &type_tag); static CreateAvdInfo gatherCreateAVDInfo(QWidget *parent, AndroidSdkManager *sdkManager, + const AndroidConfig &config, int minApiLevel = 0, const QStringList &abis = {}); private: + void parseDeviceDefinitionsList(); + void updateDeviceDefinitionComboBox(); void updateApiLevelComboBox(); bool eventFilter(QObject *obj, QEvent *event) override; @@ -60,6 +86,8 @@ private: int m_minApiLevel; QTimer m_hideTipTimer; QRegExp m_allowedNameChars; + QList m_deviceDefinitionsList; + AndroidConfig m_androidConfig; }; } }