From fff6188a40796caf071f7bf4827c907ef14a32fe Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Fri, 18 Oct 2024 14:21:25 +0200 Subject: [PATCH] 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 --- src/plugins/android/androiddevice.cpp | 87 +++++++++++++++------------ src/plugins/android/androiddevice.h | 3 +- src/plugins/android/avddialog.cpp | 86 ++++++++++++++++++-------- src/plugins/android/avddialog.h | 6 +- 4 files changed, 117 insertions(+), 65 deletions(-) diff --git a/src/plugins/android/androiddevice.cpp b/src/plugins/android/androiddevice.cpp index ffcd74b2be1..fb614130d7f 100644 --- a/src/plugins/android/androiddevice.cpp +++ b/src/plugins/android/androiddevice.cpp @@ -975,52 +975,65 @@ void updateAvdList() s_instance->m_avdListRunner.start(s_instance->m_avdListRecipe); } -Result createAvd(const CreateAvdInfo &info, bool force) +Group createAvdRecipe(const Storage> &errorStorage, + const CreateAvdInfo &info, bool force) { - CommandLine cmd(AndroidConfig::avdManagerToolPath(), {"create", "avd", "-n", info.name}); - cmd.addArgs({"-k", info.sdkStylePath}); - if (info.sdcardSize > 0) - cmd.addArgs({"-c", QString("%1M").arg(info.sdcardSize)}); + struct GuardWrapper { + GuardLocker locker = GuardLocker(s_instance->m_avdPathGuard); + QByteArray buffer; + }; - const QString deviceDef = info.deviceDefinition; - if (!deviceDef.isEmpty() && deviceDef != "Custom") - cmd.addArgs({"-d", deviceDef}); + const Storage storage; - if (force) - cmd.addArg("-f"); + 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) + cmd.addArgs({"-c", QString("%1M").arg(info.sdcardSize)}); - 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" + const QString deviceDef = info.deviceDefinition; + if (!deviceDef.isEmpty() && deviceDef != "Custom") + cmd.addArgs({"-d", deviceDef}); - QByteArray buffer; - QObject::connect(&process, &Process::readyReadStandardOutput, &process, [&process, &buffer] { - // This interaction is needed only if there is no "-d" arg for the avdmanager command. - buffer += process.readAllRawStandardOutput(); - if (buffer.endsWith(QByteArray("]:"))) { - // truncate to last line - const int index = buffer.lastIndexOf('\n'); - if (index != -1) - buffer = buffer.mid(index); - if (buffer.contains("hw.gpu.enabled")) - process.write("yes\n"); - else - process.write("\n"); - buffer.clear(); - } - }); + if (force) + cmd.addArg("-f"); - GuardLocker locker(s_instance->m_avdPathGuard); - process.runBlocking(); - if (process.result() != ProcessResult::FinishedWithSuccess) { + 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 = &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 += processPtr->readAllRawStandardOutput(); + if (buffer->endsWith(QByteArray("]:"))) { + // truncate to last line + const int index = buffer->lastIndexOf('\n'); + if (index != -1) + *buffer = buffer->mid(index); + if (buffer->contains("hw.gpu.enabled")) + processPtr->write("yes\n"); + else + processPtr->write("\n"); + buffer->clear(); + } + }); + }; + + 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 diff --git a/src/plugins/android/androiddevice.h b/src/plugins/android/androiddevice.h index 7896fbc9705..c262bf1aa67 100644 --- a/src/plugins/android/androiddevice.h +++ b/src/plugins/android/androiddevice.h @@ -70,7 +70,8 @@ private: void setupDevicesWatcher(); void updateAvdList(); -Utils::Result createAvd(const CreateAvdInfo &info, bool force); +Tasking::Group createAvdRecipe(const Tasking::Storage> &errorStorage, + const CreateAvdInfo &info, bool force); void setupAndroidDevice(); void setupAndroidDeviceManager(QObject *guard); diff --git a/src/plugins/android/avddialog.cpp b/src/plugins/android/avddialog.cpp index 3743715f568..052cef35368 100644 --- a/src/plugins/android/avddialog.cpp +++ b/src/plugins/android/avddialog.cpp @@ -26,12 +26,14 @@ #include #include #include +#include #include #include #include #include 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 progressDialog; + }; + + const Storage progressStorage; + + const auto onCancelSetup = [progressStorage] { + return std::make_pair(progressStorage->progressDialog.get(), &QProgressDialog::canceled); + }; + + const Storage> 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); diff --git a/src/plugins/android/avddialog.h b/src/plugins/android/avddialog.h index 550a04a158e..efbabf81c53 100644 --- a/src/plugins/android/avddialog.h +++ b/src/plugins/android/avddialog.h @@ -5,6 +5,8 @@ #include "androidconfigurations.h" +#include + #include #include #include @@ -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