Android: Create new AVD device asynchronously

Don't close the Create New AVD dialog when creating AVD device
failed (e.g. when other AVD with the same name already exists).

Show the cancellable progress dialog when creating an AVD.

Change-Id: I647bdcd2eada1773bbe4966f700febd26d4cd84c
Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io>
This commit is contained in:
Jarek Kobus
2024-10-18 14:21:25 +02:00
parent 61f6539779
commit fff6188a40
4 changed files with 117 additions and 65 deletions

View File

@@ -975,8 +975,17 @@ void updateAvdList()
s_instance->m_avdListRunner.start(s_instance->m_avdListRecipe);
}
Result createAvd(const CreateAvdInfo &info, bool force)
Group createAvdRecipe(const Storage<std::optional<QString>> &errorStorage,
const CreateAvdInfo &info, bool force)
{
struct GuardWrapper {
GuardLocker locker = GuardLocker(s_instance->m_avdPathGuard);
QByteArray buffer;
};
const Storage<GuardWrapper> storage;
const auto onSetup = [storage, info, force](Process &process) {
CommandLine cmd(AndroidConfig::avdManagerToolPath(), {"create", "avd", "-n", info.name});
cmd.addArgs({"-k", info.sdkStylePath});
if (info.sdcardSize > 0)
@@ -989,38 +998,42 @@ Result createAvd(const CreateAvdInfo &info, bool force)
if (force)
cmd.addArg("-f");
Process process;
process.setProcessMode(ProcessMode::Writer);
process.setEnvironment(AndroidConfig::toolsEnvironment());
process.setCommand(cmd);
process.setWriteData("yes\n"); // yes to "Do you wish to create a custom hardware profile"
QByteArray buffer;
QObject::connect(&process, &Process::readyReadStandardOutput, &process, [&process, &buffer] {
QByteArray *buffer = &storage->buffer;
Process *processPtr = &process;
QObject::connect(processPtr, &Process::readyReadStandardOutput, processPtr, [processPtr, buffer] {
// This interaction is needed only if there is no "-d" arg for the avdmanager command.
buffer += process.readAllRawStandardOutput();
if (buffer.endsWith(QByteArray("]:"))) {
*buffer += processPtr->readAllRawStandardOutput();
if (buffer->endsWith(QByteArray("]:"))) {
// truncate to last line
const int index = buffer.lastIndexOf('\n');
const int index = buffer->lastIndexOf('\n');
if (index != -1)
buffer = buffer.mid(index);
if (buffer.contains("hw.gpu.enabled"))
process.write("yes\n");
*buffer = buffer->mid(index);
if (buffer->contains("hw.gpu.enabled"))
processPtr->write("yes\n");
else
process.write("\n");
buffer.clear();
processPtr->write("\n");
buffer->clear();
}
});
};
GuardLocker locker(s_instance->m_avdPathGuard);
process.runBlocking();
if (process.result() != ProcessResult::FinishedWithSuccess) {
const auto onDone = [errorStorage](const Process &process) {
const QString stdErr = process.stdErr();
const QString errorMessage = stdErr.isEmpty() ? process.exitMessage()
: process.exitMessage() + "\n\n" + stdErr;
return Result::Error(errorMessage);
}
return Result::Ok;
*errorStorage = errorMessage;
};
return {
storage,
ProcessTask(onSetup, onDone, CallDoneIf::Error)
};
}
// Factory

View File

@@ -70,7 +70,8 @@ private:
void setupDevicesWatcher();
void updateAvdList();
Utils::Result createAvd(const CreateAvdInfo &info, bool force);
Tasking::Group createAvdRecipe(const Tasking::Storage<std::optional<QString>> &errorStorage,
const CreateAvdInfo &info, bool force);
void setupAndroidDevice();
void setupAndroidDeviceManager(QObject *guard);

View File

@@ -26,12 +26,14 @@
#include <QLineEdit>
#include <QLoggingCategory>
#include <QMessageBox>
#include <QProgressDialog>
#include <QPushButton>
#include <QSpinBox>
#include <QToolTip>
#include <QSysInfo>
using namespace ProjectExplorer;
using namespace Tasking;
using namespace Utils;
namespace Android::Internal {
@@ -114,7 +116,7 @@ AvdDialog::AvdDialog(QWidget *parent)
this, &AvdDialog::updateDeviceDefinitionComboBox);
connect(m_abiComboBox, &QComboBox::currentIndexChanged,
this, &AvdDialog::updateApiLevelComboBox);
connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(m_buttonBox, &QDialogButtonBox::accepted, this, &AvdDialog::createAvd);
connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
m_deviceTypeToStringMap.insert(AvdDialog::Phone, "Phone");
@@ -131,31 +133,6 @@ AvdDialog::AvdDialog(QWidget *parent)
updateApiLevelComboBox();
}
int AvdDialog::exec()
{
const int execResult = QDialog::exec();
if (execResult == QDialog::Accepted) {
const SystemImage *si = systemImage();
if (!si || !si->isValid() || name().isEmpty()) {
QMessageBox::warning(Core::ICore::dialogParent(),
Tr::tr("Create new AVD"), Tr::tr("Cannot create AVD. Invalid input."));
return QDialog::Rejected;
}
const CreateAvdInfo avdInfo{si->sdkStylePath(), si->apiLevel(), name(), abi(),
deviceDefinition(), sdcardSize()};
const Result result = createAvd(avdInfo, m_overwriteCheckBox->isChecked());
if (!result) {
QMessageBox::warning(Core::ICore::dialogParent(), Tr::tr("Create new AVD"),
result.error());
return QDialog::Rejected;
}
m_createdAvdInfo = avdInfo;
updateAvdList();
}
return execResult;
}
bool AvdDialog::isValid() const
{
return !name().isEmpty() && systemImage() && systemImage()->isValid() && !abi().isEmpty();
@@ -179,6 +156,63 @@ AvdDialog::DeviceType AvdDialog::tagToDeviceType(const QString &type_tag)
return AvdDialog::PhoneOrTablet;
}
void AvdDialog::createAvd()
{
const SystemImage *si = systemImage();
if (!si || !si->isValid() || name().isEmpty()) {
QMessageBox::warning(Core::ICore::dialogParent(),
Tr::tr("Create new AVD"), Tr::tr("Cannot create AVD. Invalid input."));
return;
}
const CreateAvdInfo avdInfo{si->sdkStylePath(), si->apiLevel(), name(), abi(),
deviceDefinition(), sdcardSize()};
struct Progress {
Progress() {
progressDialog.reset(new QProgressDialog(Core::ICore::dialogParent()));
progressDialog->setRange(0, 0);
progressDialog->setWindowModality(Qt::ApplicationModal);
progressDialog->setWindowTitle("Create new AVD");
progressDialog->setLabelText(Tr::tr("Creating new AVD device..."));
progressDialog->setFixedSize(progressDialog->sizeHint());
progressDialog->setAutoClose(false);
progressDialog->show(); // TODO: Should not be needed. Investigate possible QT_BUG
}
std::unique_ptr<QProgressDialog> progressDialog;
};
const Storage<Progress> progressStorage;
const auto onCancelSetup = [progressStorage] {
return std::make_pair(progressStorage->progressDialog.get(), &QProgressDialog::canceled);
};
const Storage<std::optional<QString>> errorStorage;
const auto onDone = [errorStorage] {
if (errorStorage->has_value()) {
QMessageBox::warning(Core::ICore::dialogParent(), Tr::tr("Create new AVD"),
errorStorage->value());
}
};
const Group recipe {
progressStorage,
errorStorage,
createAvdRecipe(errorStorage, avdInfo, m_overwriteCheckBox->isChecked())
.withCancel(onCancelSetup),
onGroupDone(onDone, CallDoneIf::Error)
};
m_taskTreeRunner.start(recipe, {}, [this, avdInfo](DoneWith result) {
if (result == DoneWith::Error)
return;
m_createdAvdInfo = avdInfo;
updateAvdList();
accept();
});
}
static bool avdManagerCommand(const QStringList &args, QString *output)
{
CommandLine cmd(AndroidConfig::avdManagerToolPath(), args);

View File

@@ -5,6 +5,8 @@
#include "androidconfigurations.h"
#include <solutions/tasking/tasktreerunner.h>
#include <QDialog>
#include <QRegularExpression>
#include <QTimer>
@@ -30,7 +32,6 @@ class AvdDialog : public QDialog
{
public:
explicit AvdDialog(QWidget *parent = nullptr);
int exec() override;
CreateAvdInfo avdInfo() const;
private:
@@ -50,6 +51,8 @@ private:
static AvdDialog::DeviceType tagToDeviceType(const QString &type_tag);
void createAvd();
struct DeviceDefinitionStruct
{
QString name_id;
@@ -72,6 +75,7 @@ private:
QComboBox *m_deviceDefinitionTypeComboBox;
QCheckBox *m_overwriteCheckBox;
QDialogButtonBox *m_buttonBox;
Tasking::TaskTreeRunner m_taskTreeRunner;
};
} // Internal