diff --git a/src/plugins/android/androidconfigurations.h b/src/plugins/android/androidconfigurations.h index 6e5978a58eb..09f7ac85f1d 100644 --- a/src/plugins/android/androidconfigurations.h +++ b/src/plugins/android/androidconfigurations.h @@ -70,6 +70,7 @@ public: int sdcardSize = 0; QString error; // only used in the return value of createAVD bool overwrite = false; + bool cancelled = false; }; struct SdkForQtVersions diff --git a/src/plugins/android/androiddevice.cpp b/src/plugins/android/androiddevice.cpp index 9a276ce6f71..26f9a446e2b 100644 --- a/src/plugins/android/androiddevice.cpp +++ b/src/plugins/android/androiddevice.cpp @@ -31,16 +31,24 @@ #include "androidconstants.h" #include "androidmanager.h" #include "androidsignaloperation.h" +#include "avddialog.h" + +#include #include #include #include +#include #include #include +#include #include +#include #include +#include +#include using namespace ProjectExplorer; @@ -60,6 +68,9 @@ public: AndroidDeviceWidget(const ProjectExplorer::IDevice::Ptr &device); void updateDeviceFromUi() final {} + static QString dialogTitle(); + static bool criticalDialog(const QString &error); + static bool questionDialog(const QString &question); }; AndroidDeviceWidget::AndroidDeviceWidget(const IDevice::Ptr &device) @@ -98,6 +109,38 @@ AndroidDeviceWidget::AndroidDeviceWidget(const IDevice::Ptr &device) } } +QString AndroidDeviceWidget::dialogTitle() +{ + return tr("Android Device Manager"); +} + +bool AndroidDeviceWidget::criticalDialog(const QString &error) +{ + qCDebug(androidDeviceLog) << error; + QMessageBox box(Core::ICore::dialogParent()); + box.QDialog::setWindowTitle(dialogTitle()); + box.setText(error); + box.setIcon(QMessageBox::Critical); + box.setWindowFlag(Qt::WindowTitleHint); + return box.exec(); +} + +bool AndroidDeviceWidget::questionDialog(const QString &question) +{ + QMessageBox box(Core::ICore::dialogParent()); + box.QDialog::setWindowTitle(dialogTitle()); + box.setText(question); + box.setIcon(QMessageBox::Question); + QPushButton *YesButton = box.addButton(QMessageBox::Yes); + box.addButton(QMessageBox::No); + box.setWindowFlag(Qt::WindowTitleHint); + box.exec(); + + if (box.clickedButton() == YesButton) + return true; + return false; +} + AndroidDevice::AndroidDevice() { setupId(IDevice::AutoDetected, Constants::ANDROID_DEVICE_ID); @@ -111,6 +154,20 @@ AndroidDevice::AndroidDevice() addDeviceAction({tr("Refresh"), [](const IDevice::Ptr &device, QWidget *parent) { AndroidDeviceManager::instance()->updateDevicesListOnce(); }}); + + addDeviceAction({tr("Start AVD"), [](const IDevice::Ptr &device, QWidget *parent) { + if (device->machineType() == IDevice::Emulator) + AndroidDeviceManager::instance()->startAvd(device); + }}); + + addDeviceAction({tr("Erase AVD"), [](const IDevice::Ptr &device, QWidget *parent) { + if (device->machineType() == IDevice::Emulator) + AndroidDeviceManager::instance()->eraseAvd(device); + }}); + + addDeviceAction({tr("AVD Arguments"), [](const IDevice::Ptr &device, QWidget *parent) { + AndroidDeviceManager::instance()->setEmulatorArguments(); + }}); } IDevice::Ptr AndroidDevice::create() @@ -350,6 +407,81 @@ void AndroidDeviceManager::updateDevicesListOnce() } } +void AndroidDeviceManager::startAvd(const ProjectExplorer::IDevice::Ptr &device) +{ + const AndroidDevice *androidDev = static_cast(device.data()); + const QString name = androidDev->avdName(); + qCDebug(androidDeviceLog, "Starting Android AVD id \"%s\".", qPrintable(name)); + Utils::runAsync([this, name, device]() { + const QString serialNumber = m_avdManager.startAvd(name); + // Mark the AVD as ReadyToUse once we know it's started + if (!serialNumber.isEmpty()) { + DeviceManager *const devMgr = DeviceManager::instance(); + devMgr->setDeviceState(device->id(), IDevice::DeviceReadyToUse); + } + }); +} + +void AndroidDeviceManager::eraseAvd(const IDevice::Ptr &device) +{ + if (device.isNull()) + return; + + if (device->machineType() == IDevice::Hardware) + return; + + const QString name = static_cast(device.data())->avdName(); + const QString question = tr("Erase the Android AVD \"%1\"?\nThis cannot be undone.").arg(name); + if (!AndroidDeviceWidget::questionDialog(question)) + return; + + qCDebug(androidDeviceLog) << QString("Erasing Android AVD \"%1\" from the system.").arg(name); + m_removeAvdFutureWatcher.setFuture(Utils::runAsync([this, name, device]() { + QPair pair; + pair.first = device; + pair.second = false; + if (m_avdManager.removeAvd(name)) + pair.second = true; + return pair; + })); +} + +void AndroidDeviceManager::handleAvdRemoved() +{ + const QPair result = m_removeAvdFutureWatcher.result(); + const QString name = result.first->displayName(); + if (result.second) { + qCDebug(androidDeviceLog, "Android AVD id \"%s\" removed from the system.", qPrintable(name)); + // Remove the device from QtC after it's been removed using avdmanager. + DeviceManager::instance()->removeDevice(result.first->id()); + } else { + AndroidDeviceWidget::criticalDialog(QObject::tr("An error occurred while removing the " + "Android AVD \"%1\" using avdmanager tool.").arg(name)); + } +} + +void AndroidDeviceManager::setEmulatorArguments() +{ + const QString helpUrl = + "https://developer.android.com/studio/run/emulator-commandline#startup-options"; + + QInputDialog dialog(Core::ICore::dialogParent()); + dialog.setWindowTitle(tr("Emulator Command-line Startup Options")); + dialog.setLabelText(tr("Emulator command-line startup options " + "(Help Web Page):").arg(helpUrl)); + dialog.setTextValue(m_androidConfig.emulatorArgs().join(' ')); + + if (auto label = dialog.findChild()) { + label->setOpenExternalLinks(true); + label->setMinimumWidth(500); + } + + if (dialog.exec() != QDialog::Accepted) + return; + + m_androidConfig.setEmulatorArgs(Utils::ProcessArgs::splitArgs(dialog.textValue())); +} + void AndroidDeviceManager::setupDevicesWatcher() { // The call to avdmanager is always slower than the call to adb devices, @@ -443,23 +575,80 @@ AndroidDeviceManager *AndroidDeviceManager::instance() } AndroidDeviceManager::AndroidDeviceManager(QObject *parent) - : QObject(parent), m_androidConfig(AndroidConfigurations::currentConfig()) + : QObject(parent), + m_androidConfig(AndroidConfigurations::currentConfig()), + m_avdManager(m_androidConfig) { connect(qApp, &QCoreApplication::aboutToQuit, this, [this]() { m_devicesUpdaterTimer.stop(); m_avdsFutureWatcher.waitForFinished(); m_devicesFutureWatcher.waitForFinished(); + m_removeAvdFutureWatcher.waitForFinished(); }); + + connect(&m_removeAvdFutureWatcher, &QFutureWatcherBase::finished, + this, &AndroidDeviceManager::handleAvdRemoved); } // Factory AndroidDeviceFactory::AndroidDeviceFactory() - : ProjectExplorer::IDeviceFactory(Constants::ANDROID_DEVICE_TYPE) + : ProjectExplorer::IDeviceFactory(Constants::ANDROID_DEVICE_TYPE), + m_androidConfig(AndroidConfigurations::currentConfig()) { - setDisplayName(AndroidDevice::tr("Android Device")); + setDisplayName(AndroidDevice::tr("Android Virtual Device")); setCombinedIcon(":/android/images/androiddevicesmall.png", ":/android/images/androiddevice.png"); setConstructionFunction(&AndroidDevice::create); + setCanCreate(m_androidConfig.sdkToolsOk()); +} + +IDevice::Ptr AndroidDeviceFactory::create() const +{ + AndroidSdkManager sdkManager = AndroidSdkManager(m_androidConfig); + const CreateAvdInfo info = AvdDialog::gatherCreateAVDInfo(Core::ICore::dialogParent(), + &sdkManager, m_androidConfig); + if (!info.isValid()) { + if (!info.cancelled) { + AndroidDeviceWidget::criticalDialog( + QObject::tr("The returned device info is invalid.")); + } + return nullptr; + } + + const AndroidAvdManager avdManager = AndroidAvdManager(m_androidConfig); + QFutureWatcher createAvdFutureWatcher; + createAvdFutureWatcher.setFuture(avdManager.createAvd(info)); + + QEventLoop loop; + QObject::connect(&createAvdFutureWatcher, &QFutureWatcher::finished, + &loop, &QEventLoop::quit); + QObject::connect(&createAvdFutureWatcher, &QFutureWatcher::canceled, + &loop, &QEventLoop::quit); + loop.exec(QEventLoop::ExcludeUserInputEvents); + + QFuture future = createAvdFutureWatcher.future(); + if (!(future.isResultReadyAt(0) && future.result().isValid())) { + AndroidDeviceWidget::criticalDialog(QObject::tr("The device info returned by " + "avdmanager tool is invalid for the device name \"%1\".").arg(info.name)); + return nullptr; + } + + const CreateAvdInfo newAvdInfo = createAvdFutureWatcher.result(); + + AndroidDevice *dev = new AndroidDevice(); + const Utils::Id deviceId = AndroidDevice::idFromAvdInfo(newAvdInfo); + dev->setupId(IDevice::AutoDetected, deviceId); + dev->setMachineType(IDevice::Emulator); + dev->setDisplayName(newAvdInfo.name); + dev->setDeviceState(IDevice::DeviceConnected); + dev->setExtraData(Constants::AndroidAvdName, newAvdInfo.name); + dev->setExtraData(Constants::AndroidCpuAbi, {newAvdInfo.abi}); + dev->setExtraData(Constants::AndroidSdk, newAvdInfo.systemImage->apiLevel()); + dev->setExtraData(Constants::AndroidAvdSdcard, QString("%1 MB").arg(newAvdInfo.sdcardSize)); + dev->setExtraData(Constants::AndroidAvdDevice, newAvdInfo.deviceDefinition); + + qCDebug(androidDeviceLog, "Created new Android AVD id \"%s\".", qPrintable(newAvdInfo.name)); + return IDevice::Ptr(dev); } } // namespace Internal diff --git a/src/plugins/android/androiddevice.h b/src/plugins/android/androiddevice.h index 7f90122fc5a..5e6fabb43b6 100644 --- a/src/plugins/android/androiddevice.h +++ b/src/plugins/android/androiddevice.h @@ -26,6 +26,7 @@ #pragma once +#include "androidavdmanager.h" #include "androidconfigurations.h" #include "androiddeviceinfo.h" @@ -84,6 +85,10 @@ class AndroidDeviceFactory final : public ProjectExplorer::IDeviceFactory { public: AndroidDeviceFactory(); + ProjectExplorer::IDevice::Ptr create() const override; + +private: + AndroidConfig m_androidConfig; }; class AndroidDeviceManager : public QObject @@ -94,13 +99,21 @@ public: void updateDevicesList(); void updateDevicesListOnce(); + void startAvd(const ProjectExplorer::IDevice::Ptr &device); + void eraseAvd(const ProjectExplorer::IDevice::Ptr &device); + + void setEmulatorArguments(); + private: AndroidDeviceManager(QObject *parent = nullptr); void devicesListUpdated(); + void handleAvdRemoved(); QFutureWatcher m_avdsFutureWatcher; QFutureWatcher> m_devicesFutureWatcher; + QFutureWatcher> m_removeAvdFutureWatcher; QTimer m_devicesUpdaterTimer; + AndroidAvdManager m_avdManager; AndroidConfig m_androidConfig; }; diff --git a/src/plugins/android/androidsettingswidget.cpp b/src/plugins/android/androidsettingswidget.cpp index 7e3b51ca130..d67c53a9637 100644 --- a/src/plugins/android/androidsettingswidget.cpp +++ b/src/plugins/android/androidsettingswidget.cpp @@ -27,14 +27,12 @@ #include "ui_androidsettingswidget.h" -#include "androidavdmanager.h" #include "androidconfigurations.h" #include "androidconstants.h" #include "androidsdkdownloader.h" #include "androidsdkmanager.h" #include "androidsdkmanagerwidget.h" #include "androidtoolchain.h" -#include "avddialog.h" #include @@ -76,22 +74,8 @@ namespace Android { namespace Internal { class AndroidSdkManagerWidget; -class AndroidAvdManager; class SummaryWidget; -class AvdModel final : public ListModel -{ - Q_DECLARE_TR_FUNCTIONS(Android::Internal::AvdModel) - -public: - AvdModel(); - - QVariant itemData(const AndroidDeviceInfo &info, int column, int role) const final; - - QString avdName(const QModelIndex &index) const; - QModelIndex indexForAvdName(const QString &avdName) const; -}; - class AndroidSettingsWidget final : public Core::IOptionsPageWidget { Q_DECLARE_TR_FUNCTIONS(Android::Internal::AndroidSettingsWidget) @@ -114,20 +98,9 @@ private: void openNDKDownloadUrl(); void openOpenJDKDownloadUrl(); void downloadOpenSslRepo(const bool silent = false); - void addAVD(); - void avdAdded(); - void removeAVD(); - void startAVD(); - void avdActivated(const QModelIndex &); - void editEmulatorArgsAVD(); void createKitToggled(); void updateUI(); - void updateAvds(); - - void startUpdateAvd(); - void enableAvdControls(); - void disableAvdControls(); void downloadSdk(); void addCustomNdkItem(); @@ -136,12 +109,7 @@ private: Ui_AndroidSettingsWidget m_ui; AndroidSdkManagerWidget *m_sdkManagerWidget = nullptr; AndroidConfig m_androidConfig{AndroidConfigurations::currentConfig()}; - AvdModel m_AVDModel; - QFutureWatcher m_futureWatcher; - QFutureWatcher m_virtualDevicesWatcher; - QString m_lastAddedAvd; - AndroidAvdManager m_avdManager{m_androidConfig}; AndroidSdkManager m_sdkManager{m_androidConfig}; AndroidSdkDownloader m_sdkDownloader; bool m_isInitialReloadDone = false; @@ -253,44 +221,6 @@ private: QMap m_validationData; }; -QModelIndex AvdModel::indexForAvdName(const QString &avdName) const -{ - return findIndex([avdName](const AndroidDeviceInfo &info) { return info.avdname == avdName; }); -} - -QString AvdModel::avdName(const QModelIndex &index) const -{ - return dataAt(index.row()).avdname; -} - -QVariant AvdModel::itemData(const AndroidDeviceInfo &info, int column, int role) const -{ - if (role != Qt::DisplayRole) - return {}; - - switch (column) { - case 0: - return info.avdname; - case 1: - return info.sdk; - case 2: - return info.cpuAbi.isEmpty() ? QVariant() : QVariant(info.cpuAbi.first()); - case 3: - return info.avdDevice.isEmpty() ? QVariant("Custom") : info.avdDevice; - case 4: - return info.avdTarget; - case 5: - return info.avdSdcardSize; - } - return {}; -} - -AvdModel::AvdModel() -{ - //: AVD - Android Virtual Device - setHeader({tr("AVD Name"), tr("API"), tr("CPU/ABI"), tr("Device Type"), tr("Target"), tr("SD-card Size")}); -} - void AndroidSettingsWidget::showEvent(QShowEvent *event) { Q_UNUSED(event) @@ -432,11 +362,6 @@ AndroidSettingsWidget::AndroidSettingsWidget() m_ui.openSslPathChooser->setFilePath(m_androidConfig.openSslLocation()); m_ui.CreateKitCheckBox->setChecked(m_androidConfig.automaticKitCreation()); - m_ui.AVDTableView->setModel(&m_AVDModel); - m_ui.AVDTableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); - for (int column : {1, 2, 5}) - m_ui.AVDTableView->horizontalHeader()->setSectionResizeMode( - column, QHeaderView::ResizeToContents); const QIcon downloadIcon = Icons::ONLINE.icon(); m_ui.downloadSDKToolButton->setIcon(downloadIcon); @@ -475,24 +400,6 @@ AndroidSettingsWidget::AndroidSettingsWidget() connect(m_ui.openSslPathChooser, &PathChooser::rawPathChanged, this, &AndroidSettingsWidget::validateOpenSsl); - connect(&m_virtualDevicesWatcher, &QFutureWatcherBase::finished, - this, &AndroidSettingsWidget::updateAvds); - connect(m_ui.AVDRefreshPushButton, &QAbstractButton::clicked, - this, &AndroidSettingsWidget::startUpdateAvd); - connect(&m_futureWatcher, &QFutureWatcherBase::finished, - this, &AndroidSettingsWidget::avdAdded); - connect(m_ui.AVDAddPushButton, &QAbstractButton::clicked, - this, &AndroidSettingsWidget::addAVD); - connect(m_ui.AVDRemovePushButton, &QAbstractButton::clicked, - this, &AndroidSettingsWidget::removeAVD); - connect(m_ui.AVDStartPushButton, &QAbstractButton::clicked, - this, &AndroidSettingsWidget::startAVD); - connect(m_ui.AVDTableView, &QAbstractItemView::activated, - this, &AndroidSettingsWidget::avdActivated); - connect(m_ui.AVDTableView, &QAbstractItemView::clicked, - this, &AndroidSettingsWidget::avdActivated); - connect(m_ui.AVDAdvancedOptionsPushButton, &QAbstractButton::clicked, - this, &AndroidSettingsWidget::editEmulatorArgsAVD); connect(m_ui.CreateKitCheckBox, &QAbstractButton::toggled, this, &AndroidSettingsWidget::createKitToggled); connect(m_ui.downloadNDKToolButton, &QAbstractButton::clicked, @@ -530,38 +437,6 @@ AndroidSettingsWidget::~AndroidSettingsWidget() { // Deleting m_sdkManagerWidget will cancel all ongoing and pending sdkmanager operations. delete m_sdkManagerWidget; - m_futureWatcher.waitForFinished(); -} - -void AndroidSettingsWidget::disableAvdControls() -{ - m_ui.AVDAddPushButton->setEnabled(false); - m_ui.AVDTableView->setEnabled(false); - m_ui.AVDRemovePushButton->setEnabled(false); - m_ui.AVDStartPushButton->setEnabled(false); -} - -void AndroidSettingsWidget::enableAvdControls() -{ - m_ui.AVDTableView->setEnabled(true); - m_ui.AVDAddPushButton->setEnabled(true); - avdActivated(m_ui.AVDTableView->currentIndex()); -} - -void AndroidSettingsWidget::startUpdateAvd() -{ - disableAvdControls(); - m_virtualDevicesWatcher.setFuture(m_avdManager.avdList()); -} - -void AndroidSettingsWidget::updateAvds() -{ - m_AVDModel.setAllData(m_virtualDevicesWatcher.result()); - if (!m_lastAddedAvd.isEmpty()) { - m_ui.AVDTableView->setCurrentIndex(m_AVDModel.indexForAvdName(m_lastAddedAvd)); - m_lastAddedAvd.clear(); - } - enableAvdControls(); } void AndroidSettingsWidget::validateJdk() @@ -648,7 +523,6 @@ void AndroidSettingsWidget::validateSdk() } } - startUpdateAvd(); updateNdkList(); updateUI(); } @@ -754,80 +628,6 @@ void AndroidSettingsWidget::downloadOpenSslRepo(const bool silent) gitCloner->start(); } -void AndroidSettingsWidget::addAVD() -{ - disableAvdControls(); - CreateAvdInfo info = AvdDialog::gatherCreateAVDInfo(this, &m_sdkManager, m_androidConfig); - - if (!info.isValid()) { - enableAvdControls(); - return; - } - - m_futureWatcher.setFuture(m_avdManager.createAvd(info)); -} - -void AndroidSettingsWidget::avdAdded() -{ - CreateAvdInfo info = m_futureWatcher.result(); - if (!info.error.isEmpty()) { - enableAvdControls(); - QMessageBox::critical(this, QApplication::translate("AndroidConfig", "Error Creating AVD"), info.error); - return; - } - - startUpdateAvd(); - m_lastAddedAvd = info.name; -} - -void AndroidSettingsWidget::removeAVD() -{ - disableAvdControls(); - QString avdName = m_AVDModel.avdName(m_ui.AVDTableView->currentIndex()); - if (QMessageBox::question(this, tr("Remove Android Virtual Device"), - tr("Remove device \"%1\"? This cannot be undone.").arg(avdName), - QMessageBox::Yes | QMessageBox::No) - == QMessageBox::No) { - enableAvdControls(); - return; - } - - m_avdManager.removeAvd(avdName); - startUpdateAvd(); -} - -void AndroidSettingsWidget::startAVD() -{ - m_avdManager.startAvdAsync(m_AVDModel.avdName(m_ui.AVDTableView->currentIndex())); -} - -void AndroidSettingsWidget::avdActivated(const QModelIndex &index) -{ - m_ui.AVDRemovePushButton->setEnabled(index.isValid()); - m_ui.AVDStartPushButton->setEnabled(index.isValid()); -} - -void AndroidSettingsWidget::editEmulatorArgsAVD() -{ - const QString helpUrl = - "https://developer.android.com/studio/run/emulator-commandline#startup-options"; - - QInputDialog dialog(this); - dialog.setWindowTitle(tr("Emulator Command-line Startup Options")); - dialog.setLabelText(tr("Emulator command-line startup options (Help Web Page):").arg(helpUrl)); - dialog.setTextValue(m_androidConfig.emulatorArgs().join(' ')); - - if (auto label = dialog.findChild()) { - label->setOpenExternalLinks(true); - label->setMinimumWidth(500); - } - - if (dialog.exec() != QDialog::Accepted) - return; - - m_androidConfig.setEmulatorArgs(ProcessArgs::splitArgs(dialog.textValue())); -} - void AndroidSettingsWidget::createKitToggled() { m_androidConfig.setAutomaticKitCreation(m_ui.CreateKitCheckBox->isChecked()); @@ -840,7 +640,6 @@ void AndroidSettingsWidget::updateUI() const bool androidSetupOk = m_androidSummary->allRowsOk(); const bool openSslOk = m_openSslSummary->allRowsOk(); - m_ui.avdManagerTab->setEnabled(javaSetupOk && androidSetupOk); m_ui.sdkManagerTab->setEnabled(sdkToolsOk); const QListWidgetItem *currentItem = m_ui.ndkListWidget->currentItem(); diff --git a/src/plugins/android/androidsettingswidget.ui b/src/plugins/android/androidsettingswidget.ui index 0a1cb22af31..6ad64eb2cac 100644 --- a/src/plugins/android/androidsettingswidget.ui +++ b/src/plugins/android/androidsettingswidget.ui @@ -269,142 +269,6 @@ 0 - - - AVD Manager - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - Qt::ElideMiddle - - - false - - - false - - - - - - - - - false - - - - 0 - 0 - - - - Start... - - - - - - - Refresh List - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 0 - 8 - - - - - - - - - 0 - 0 - - - - Add... - - - - - - - false - - - - 0 - 0 - - - - Remove - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Advanced Options... - - - - - - - SDK Manager diff --git a/src/plugins/android/avddialog.cpp b/src/plugins/android/avddialog.cpp index dd3224701d2..5f5a5cf6821 100644 --- a/src/plugins/android/avddialog.cpp +++ b/src/plugins/android/avddialog.cpp @@ -105,7 +105,8 @@ CreateAvdInfo AvdDialog::gatherCreateAVDInfo(QWidget *parent, AndroidSdkManager { CreateAvdInfo result; AvdDialog d(minApiLevel, sdkManager, abis, config, parent); - if (d.exec() != QDialog::Accepted || !d.isValid()) + result.cancelled = (d.exec() != QDialog::Accepted); + if (result.cancelled || !d.isValid()) return result; result.systemImage = d.systemImage();