Android: Improve "Add new AVD" dialog

* Re-organize the implementation to create a new AVD
* Use SystemImage instead of SdKPlatform because now
an SdkPlatform can be installed partially with a SystemImage.
The current implementation does not consider the this case,
thus we end up with an uncomplete list of installed system images.
* Add Device definitions with categories (phone, tablet, tv, etc.)
to the creation process (check -d arg in avdmanager create avd).

Task-number: QTCREATORBUG-23284
Change-Id: Id02a71ad452fb423fa2781d06ef3fcf2afa328a9
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
Assam Boudjelthia
2020-01-08 14:55:16 +02:00
parent 382589f160
commit 0df5d8c2e5
13 changed files with 263 additions and 73 deletions

View File

@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>600</width>
<height>187</height>
<height>243</height>
</rect>
</property>
<property name="minimumSize">
@@ -30,45 +30,49 @@
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="nameLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Name:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="nameLineEdit"/>
</item>
<item row="1" column="0">
<item row="2" column="0">
<widget class="QLabel" name="abiLabel">
<property name="text">
<string>Architecture (ABI):</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="abiComboBox"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="targetApiLabel">
<property name="text">
<string>Target API:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="targetApiComboBox"/>
</item>
<item row="3" column="1">
<widget class="Utils::InfoLabel" name="warningText"/>
</item>
<item row="4" column="0">
<item row="5" column="0">
<widget class="QLabel" name="sdcardSizeLabel">
<property name="text">
<string>SD card size:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<item row="3" column="0">
<widget class="QLabel" name="targetApiLabel">
<property name="text">
<string>Target API:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="deviceDefinitionLabel">
<property name="text">
<string>Device definition:</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QComboBox" name="abiComboBox"/>
</item>
<item row="5" column="1" colspan="2">
<widget class="QSpinBox" name="sdcardSizeSpinBox">
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
@@ -87,6 +91,35 @@
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QLineEdit" name="nameLineEdit"/>
</item>
<item row="3" column="1" colspan="2">
<widget class="QComboBox" name="targetApiComboBox"/>
</item>
<item row="1" column="2">
<widget class="QComboBox" name="deviceDefinitionComboBox"/>
</item>
<item row="4" column="1" colspan="2">
<widget class="Utils::InfoLabel" name="warningText"/>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="deviceDefinitionTypeComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QCheckBox" name="overwriteCheckBox">
<property name="text">
<string>Overwrite existing AVD name</string>
</property>
</widget>
</item>
</layout>
</item>
<item>

View File

@@ -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 {};

View File

@@ -54,6 +54,9 @@ public:
QString waitForAvd(const QString &avdName,
const std::function<bool()> &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,

View File

@@ -85,12 +85,14 @@ using AndroidDeviceInfoList = QList<AndroidDeviceInfo>;
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

View File

@@ -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);

View File

@@ -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<SdkPlatform *> platforms = Utils::static_container_cast<SdkPlatform *>(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<SystemImage *, int> 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;

View File

@@ -69,6 +69,7 @@ public:
const AndroidSdkPackageList &allSdkPackages();
AndroidSdkPackageList availableSdkPackages();
AndroidSdkPackageList installedSdkPackages();
SystemImageList installedSystemImages();
SdkPlatform *latestAndroidSdkPlatform(AndroidSdkPackage::PackageState state
= AndroidSdkPackage::Installed);

View File

@@ -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)

View File

@@ -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<SdkPlatform> m_platform;
QString m_abiName;
int m_apiLevel = -1;
};
using SystemImageList = QList<SystemImage*>;

View File

@@ -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();

View File

@@ -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)

View File

@@ -25,6 +25,7 @@
#include "avddialog.h"
#include "androidsdkmanager.h"
#include "androidavdmanager.h"
#include <utils/algorithm.h>
#include <utils/tooltip/tooltip.h>
@@ -34,16 +35,22 @@
#include <QKeyEvent>
#include <QMessageBox>
#include <QToolTip>
#include <QLoggingCategory>
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<int>::of(&QComboBox::currentIndexChanged),
this,
&AvdDialog::updateDeviceDefinitionComboBox);
connect(m_avdDialog.abiComboBox,
QOverload<int>::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<SdkPlatform*>();
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<SystemImage*>();
}
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<SdkPlatform *>(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<SystemImage *>(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);
}
}

View File

@@ -26,6 +26,7 @@
#pragma once
#include "androidconfigurations.h"
#include "ui_addnewavddialog.h"
#include "androidconfigurations.h"
#include <QDialog>
#include <QTimer>
@@ -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<DeviceType, QString> 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<DeviceDefinitionStruct> m_deviceDefinitionsList;
AndroidConfig m_androidConfig;
};
}
}