From ccd68726085a820ecb2cf4af62cea01ede900d4b Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 22 May 2024 14:50:42 +0200 Subject: [PATCH] Android: Hide AndroidDeviceManager methods in cpp Change-Id: I871b1126d36367b3716ed988f581a8b5bca68693 Reviewed-by: Alessandro Portale --- src/plugins/android/androiddevice.cpp | 640 +++++++++++++------------- src/plugins/android/androiddevice.h | 15 - 2 files changed, 317 insertions(+), 338 deletions(-) diff --git a/src/plugins/android/androiddevice.cpp b/src/plugins/android/androiddevice.cpp index 6a83548644e..1b69456bbd9 100644 --- a/src/plugins/android/androiddevice.cpp +++ b/src/plugins/android/androiddevice.cpp @@ -57,6 +57,133 @@ static QString displayNameFromInfo(const AndroidDeviceInfo &info) : info.avdName; } +static IDevice::DeviceState getDeviceState(const QString &serial, IDevice::MachineType type) +{ + const QStringList args = AndroidDeviceInfo::adbSelector(serial) << "shell" << "echo 1"; + const SdkToolResult result = AndroidManager::runAdbCommand(args); + if (result.success()) + return IDevice::DeviceReadyToUse; + else if (type == IDevice::Emulator || result.stdErr().contains("unauthorized")) + return IDevice::DeviceConnected; + return IDevice::DeviceDisconnected; +} + +static void updateDeviceState(const ProjectExplorer::IDevice::ConstPtr &device) +{ + const AndroidDevice *dev = static_cast(device.get()); + const QString serial = dev->serialNumber(); + DeviceManager *const devMgr = DeviceManager::instance(); + const Id id = dev->id(); + if (!serial.isEmpty()) + devMgr->setDeviceState(id, getDeviceState(serial, dev->machineType())); + else if (dev->machineType() == IDevice::Emulator) + devMgr->setDeviceState(id, IDevice::DeviceConnected); +} + +static void startAvd(const ProjectExplorer::IDevice::Ptr &device, QWidget *parent) +{ + Q_UNUSED(parent) + const AndroidDevice *androidDev = static_cast(device.get()); + const QString name = androidDev->avdName(); + qCDebug(androidDeviceLog, "Starting Android AVD id \"%s\".", qPrintable(name)); + auto future = Utils::asyncRun([name, device] { + const QString serialNumber = AndroidAvdManager::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); + } + }); + // TODO: use future! +} + +static void setEmulatorArguments(QWidget *parent) +{ + const QString helpUrl = + "https://developer.android.com/studio/run/emulator-commandline#startup-options"; + + QInputDialog dialog(parent ? parent : Core::ICore::dialogParent()); + dialog.setWindowTitle(Tr::tr("Emulator Command-line Startup Options")); + dialog.setLabelText(Tr::tr("Emulator command-line startup options " + "(Help Web Page):") + .arg(helpUrl)); + dialog.setTextValue(AndroidConfig::emulatorArgs()); + + if (auto label = dialog.findChild()) { + label->setOpenExternalLinks(true); + label->setMinimumWidth(500); + } + + if (dialog.exec() == QDialog::Accepted) + AndroidConfig::setEmulatorArgs(dialog.textValue()); +} + +static QString emulatorName(const QString &serialNumber) +{ + const QStringList args = AndroidDeviceInfo::adbSelector(serialNumber) << "emu" << "avd" << "name"; + return AndroidManager::runAdbCommand(args).stdOut(); +} + +static QString getRunningAvdsSerialNumber(const QString &name) +{ + Process adbProcess; + adbProcess.setCommand({AndroidConfig::adbToolPath(), {"devices"}}); + adbProcess.runBlocking(); + if (adbProcess.result() != ProcessResult::FinishedWithSuccess) + return {}; + + // mid(1) - remove "List of devices attached" header line + const QStringList lines = adbProcess.allOutput().split('\n', Qt::SkipEmptyParts).mid(1); + for (const QString &line : lines) { + // skip the daemon logs + if (line.startsWith("* daemon")) + continue; + + const QString serialNumber = line.left(line.indexOf('\t')).trimmed(); + if (!serialNumber.startsWith("emulator")) + continue; + + const QString stdOut = emulatorName(serialNumber); + if (stdOut.isEmpty()) + continue; // Not an avd + + if (stdOut.left(stdOut.indexOf('\n')) == name) + return serialNumber; + } + return {}; +} + +static FilePath avdFilePath() +{ + QString avdEnvVar = qtcEnvironmentVariable("ANDROID_AVD_HOME"); + if (avdEnvVar.isEmpty()) { + avdEnvVar = qtcEnvironmentVariable("ANDROID_SDK_HOME"); + if (avdEnvVar.isEmpty()) + avdEnvVar = qtcEnvironmentVariable("HOME"); + avdEnvVar.append("/.android/avd"); + } + return FilePath::fromUserInput(avdEnvVar); +} + +static IDevice::Ptr createDeviceFromInfo(const CreateAvdInfo &info) +{ + if (info.apiLevel < 0) { + qCWarning(androidDeviceLog) << "System image of the created AVD is nullptr"; + return IDevice::Ptr(); + } + AndroidDevice *dev = new AndroidDevice; + const Utils::Id deviceId = AndroidDevice::idFromAvdInfo(info); + dev->setupId(IDevice::AutoDetected, deviceId); + dev->setMachineType(IDevice::Emulator); + dev->settings()->displayName.setValue(info.name); + dev->setDeviceState(IDevice::DeviceConnected); + dev->setAvdPath(avdFilePath() / (info.name + ".avd")); + dev->setExtraData(Constants::AndroidAvdName, info.name); + dev->setExtraData(Constants::AndroidCpuAbi, {info.abi}); + dev->setExtraData(Constants::AndroidSdk, info.apiLevel); + return IDevice::Ptr(dev); +} + class AndroidDeviceWidget : public IDeviceWidget { public: @@ -70,6 +197,66 @@ public: static bool questionDialog(const QString &question, QWidget *parent = nullptr); }; +static void setupWifiForDevice(const IDevice::Ptr &device, QWidget *parent) +{ + if (device->deviceState() != IDevice::DeviceReadyToUse) { + AndroidDeviceWidget::infoDialog( + Tr::tr("The device has to be connected with ADB debugging " + "enabled to use this feature."), parent); + return; + } + + const auto androidDev = static_cast(device.get()); + const QStringList adbSelector = AndroidDeviceInfo::adbSelector(androidDev->serialNumber()); + // prepare port + QStringList args = adbSelector; + args.append({"tcpip", wifiDevicePort}); + const SdkToolResult result = AndroidManager::runAdbCommand(args); + if (!result.success()) { + AndroidDeviceWidget::criticalDialog( + Tr::tr("Opening connection port %1 failed.").arg(wifiDevicePort), + parent); + return; + } + + QTimer::singleShot(2000, parent, [adbSelector, parent] { + // Get device IP address + QStringList args = adbSelector; + args.append({"shell", "ip", "route"}); + const SdkToolResult ipRes = AndroidManager::runAdbCommand(args); + if (!ipRes.success()) { + AndroidDeviceWidget::criticalDialog( + Tr::tr("Retrieving the device IP address failed."), parent); + return; + } + + // Expected output from "ip route" is: + // 192.168.1.0/24 dev wlan0 proto kernel scope link src 192.168.1.190 + // where the ip of interest is at the end of the line + const QStringList ipParts = ipRes.stdOut().split(" "); + QString ip; + if (!ipParts.isEmpty()) { + ip = ipParts.last(); + } + if (!ipRegex.match(ipParts.last()).hasMatch()) { + AndroidDeviceWidget::criticalDialog( + Tr::tr("The retrieved IP address is invalid."), parent); + return; + } + + // Connect to device + args = adbSelector; + args.append({"connect", QString("%1:%2").arg(ip).arg(wifiDevicePort)}); + const SdkToolResult connectRes = AndroidManager::runAdbCommand(args); + if (!connectRes.success()) { + AndroidDeviceWidget::criticalDialog( + Tr::tr("Connecting to the device IP \"%1\" failed.").arg(ip), + parent); + return; + } + }); +} + AndroidDeviceWidget::AndroidDeviceWidget(const IDevice::Ptr &device) : IDeviceWidget(device) { @@ -168,7 +355,7 @@ AndroidDevice::AndroidDevice() addDeviceAction({Tr::tr("Refresh"), [](const IDevice::Ptr &device, QWidget *parent) { Q_UNUSED(parent) - AndroidDeviceManager::instance()->updateDeviceState(device); + updateDeviceState(device); }}); } @@ -198,7 +385,7 @@ void AndroidDevice::addActionsIfNotFound() if (machineType() == Emulator) { if (!hasStartAction) { addDeviceAction({startAvdAction, [](const IDevice::Ptr &device, QWidget *parent) { - AndroidDeviceManager::instance()->startAvd(device, parent); + startAvd(device, parent); }}); } @@ -211,13 +398,13 @@ void AndroidDevice::addActionsIfNotFound() if (!hasAvdArgumentsAction) { addDeviceAction({avdArgumentsAction, [](const IDevice::Ptr &device, QWidget *parent) { Q_UNUSED(device) - AndroidDeviceManager::instance()->setEmulatorArguments(parent); + setEmulatorArguments(parent); }}); } } else if (machineType() == Hardware && !ipRegex.match(id().toString()).hasMatch()) { if (!hasSetupWifi) { addDeviceAction({setupWifi, [](const IDevice::Ptr &device, QWidget *parent) { - AndroidDeviceManager::instance()->setupWifiForDevice(device, parent); + setupWifiForDevice(device, parent); }}); } } @@ -318,8 +505,7 @@ QString AndroidDevice::serialNumber() const const QString serialNumber = extraData(Constants::AndroidSerialNumber).toString(); if (machineType() == Hardware) return serialNumber; - - return AndroidDeviceManager::instance()->getRunningAvdsSerialNumber(avdName()); + return getRunningAvdsSerialNumber(avdName()); } QString AndroidDevice::avdName() const @@ -419,31 +605,6 @@ void AndroidDeviceManager::updateAvdList() m_avdListRunner.start(m_avdListRecipe); } -IDevice::DeviceState AndroidDeviceManager::getDeviceState(const QString &serial, - IDevice::MachineType type) const -{ - const QStringList args = AndroidDeviceInfo::adbSelector(serial) << "shell" << "echo 1"; - const SdkToolResult result = AndroidManager::runAdbCommand(args); - if (result.success()) - return IDevice::DeviceReadyToUse; - else if (type == IDevice::Emulator || result.stdErr().contains("unauthorized")) - return IDevice::DeviceConnected; - - return IDevice::DeviceDisconnected; -} - -void AndroidDeviceManager::updateDeviceState(const ProjectExplorer::IDevice::ConstPtr &device) -{ - const AndroidDevice *dev = static_cast(device.get()); - const QString serial = dev->serialNumber(); - DeviceManager *const devMgr = DeviceManager::instance(); - const Id id = dev->id(); - if (!serial.isEmpty()) - devMgr->setDeviceState(id, getDeviceState(serial, dev->machineType())); - else if (dev->machineType() == IDevice::Emulator) - devMgr->setDeviceState(id, IDevice::DeviceConnected); -} - expected_str AndroidDeviceManager::createAvd(const CreateAvdInfo &info, bool force) { CommandLine cmd(AndroidConfig::avdManagerToolPath(), {"create", "avd", "-n", info.name}); @@ -488,23 +649,6 @@ expected_str AndroidDeviceManager::createAvd(const CreateAvdInfo &info, bo return {}; } -void AndroidDeviceManager::startAvd(const ProjectExplorer::IDevice::Ptr &device, QWidget *parent) -{ - Q_UNUSED(parent) - const AndroidDevice *androidDev = static_cast(device.get()); - const QString name = androidDev->avdName(); - qCDebug(androidDeviceLog, "Starting Android AVD id \"%s\".", qPrintable(name)); - auto future = Utils::asyncRun([name, device] { - const QString serialNumber = AndroidAvdManager::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); - } - }); - // TODO: use future! -} - void AndroidDeviceManager::eraseAvd(const IDevice::Ptr &device, QWidget *parent) { if (!device) @@ -541,280 +685,7 @@ void AndroidDeviceManager::eraseAvd(const IDevice::Ptr &device, QWidget *parent) m_removeAvdProcess->start(); } -void AndroidDeviceManager::setupWifiForDevice(const IDevice::Ptr &device, QWidget *parent) -{ - if (device->deviceState() != IDevice::DeviceReadyToUse) { - AndroidDeviceWidget::infoDialog( - Tr::tr("The device has to be connected with ADB debugging " - "enabled to use this feature."), parent); - return; - } - - const auto androidDev = static_cast(device.get()); - const QStringList adbSelector = AndroidDeviceInfo::adbSelector(androidDev->serialNumber()); - // prepare port - QStringList args = adbSelector; - args.append({"tcpip", wifiDevicePort}); - const SdkToolResult result = AndroidManager::runAdbCommand(args); - if (!result.success()) { - AndroidDeviceWidget::criticalDialog( - Tr::tr("Opening connection port %1 failed.").arg(wifiDevicePort), - parent); - return; - } - - QTimer::singleShot(2000, parent, [adbSelector, parent] { - // Get device IP address - QStringList args = adbSelector; - args.append({"shell", "ip", "route"}); - const SdkToolResult ipRes = AndroidManager::runAdbCommand(args); - if (!ipRes.success()) { - AndroidDeviceWidget::criticalDialog( - Tr::tr("Retrieving the device IP address failed."), parent); - return; - } - - // Expected output from "ip route" is: - // 192.168.1.0/24 dev wlan0 proto kernel scope link src 192.168.1.190 - // where the ip of interest is at the end of the line - const QStringList ipParts = ipRes.stdOut().split(" "); - QString ip; - if (!ipParts.isEmpty()) { - ip = ipParts.last(); - } - if (!ipRegex.match(ipParts.last()).hasMatch()) { - AndroidDeviceWidget::criticalDialog( - Tr::tr("The retrieved IP address is invalid."), parent); - return; - } - - // Connect to device - args = adbSelector; - args.append({"connect", QString("%1:%2").arg(ip).arg(wifiDevicePort)}); - const SdkToolResult connectRes = AndroidManager::runAdbCommand(args); - if (!connectRes.success()) { - AndroidDeviceWidget::criticalDialog( - Tr::tr("Connecting to the device IP \"%1\" failed.").arg(ip), - parent); - return; - } - }); -} - -QString AndroidDeviceManager::emulatorName(const QString &serialNumber) const -{ - QStringList args = AndroidDeviceInfo::adbSelector(serialNumber); - args.append({"emu", "avd", "name"}); - return AndroidManager::runAdbCommand(args).stdOut(); -} - -void AndroidDeviceManager::setEmulatorArguments(QWidget *parent) -{ - const QString helpUrl = - "https://developer.android.com/studio/run/emulator-commandline#startup-options"; - - QInputDialog dialog(parent ? parent : Core::ICore::dialogParent()); - dialog.setWindowTitle(Tr::tr("Emulator Command-line Startup Options")); - dialog.setLabelText(Tr::tr("Emulator command-line startup options " - "(Help Web Page):") - .arg(helpUrl)); - dialog.setTextValue(AndroidConfig::emulatorArgs()); - - if (auto label = dialog.findChild()) { - label->setOpenExternalLinks(true); - label->setMinimumWidth(500); - } - - if (dialog.exec() != QDialog::Accepted) - return; - - AndroidConfig::setEmulatorArgs(dialog.textValue()); -} - -QString AndroidDeviceManager::getRunningAvdsSerialNumber(const QString &name) const -{ - Process adbProcess; - adbProcess.setCommand({AndroidConfig::adbToolPath(), {"devices"}}); - adbProcess.runBlocking(); - if (adbProcess.result() != ProcessResult::FinishedWithSuccess) - return {}; - - // mid(1) - remove "List of devices attached" header line - const QStringList lines = adbProcess.allOutput().split('\n', Qt::SkipEmptyParts).mid(1); - for (const QString &line : lines) { - // skip the daemon logs - if (line.startsWith("* daemon")) - continue; - - const QString serialNumber = line.left(line.indexOf('\t')).trimmed(); - if (!serialNumber.startsWith("emulator")) - continue; - - const QString stdOut = emulatorName(serialNumber); - if (stdOut.isEmpty()) - continue; // Not an avd - - if (stdOut.left(stdOut.indexOf('\n')) == name) - return serialNumber; - } - return {}; -} - -static FilePath avdFilePath() -{ - QString avdEnvVar = qtcEnvironmentVariable("ANDROID_AVD_HOME"); - if (avdEnvVar.isEmpty()) { - avdEnvVar = qtcEnvironmentVariable("ANDROID_SDK_HOME"); - if (avdEnvVar.isEmpty()) - avdEnvVar = qtcEnvironmentVariable("HOME"); - avdEnvVar.append("/.android/avd"); - } - return FilePath::fromUserInput(avdEnvVar); -} - -void AndroidDeviceManager::setupDevicesWatcher() -{ - if (!AndroidConfig::adbToolPath().exists()) { - qCDebug(androidDeviceLog) << "Cannot start ADB device watcher" - << "because adb path does not exist."; - return; - } - - if (!m_adbDeviceWatcherProcess) - m_adbDeviceWatcherProcess.reset(new Process(this)); - - if (m_adbDeviceWatcherProcess->isRunning()) { - qCDebug(androidDeviceLog) << "ADB device watcher is already running."; - return; - } - - connect(m_adbDeviceWatcherProcess.get(), &Process::done, this, [this] { - if (m_adbDeviceWatcherProcess->error() != QProcess::UnknownError) { - qCDebug(androidDeviceLog) << "ADB device watcher encountered an error:" - << m_adbDeviceWatcherProcess->errorString(); - if (!m_adbDeviceWatcherProcess->isRunning()) { - qCDebug(androidDeviceLog) << "Restarting the ADB device watcher now."; - QTimer::singleShot(0, m_adbDeviceWatcherProcess.get(), &Process::start); - } - } - qCDebug(androidDeviceLog) << "ADB device watcher finished."; - }); - - m_adbDeviceWatcherProcess->setStdErrLineCallback([](const QString &error) { - qCDebug(androidDeviceLog) << "ADB device watcher error" << error; }); - m_adbDeviceWatcherProcess->setStdOutLineCallback([this](const QString &output) { - handleDevicesListChange(output); - }); - - const CommandLine command{AndroidConfig::adbToolPath(), {"track-devices"}}; - m_adbDeviceWatcherProcess->setCommand(command); - m_adbDeviceWatcherProcess->setWorkingDirectory(command.executable().parentDir()); - m_adbDeviceWatcherProcess->setEnvironment(AndroidConfig::toolsEnvironment()); - m_adbDeviceWatcherProcess->start(); - - // Setup AVD filesystem watcher to listen for changes when an avd is created/deleted, - // or started/stopped - m_avdFileSystemWatcher.addPath(avdFilePath().toString()); - connect(&m_avdFileSystemWatcher, &QFileSystemWatcher::directoryChanged, this, [this] { - if (!m_avdPathGuard.isLocked()) - updateAvdList(); - }); - // Call initial update - updateAvdList(); -} - -IDevice::Ptr AndroidDeviceManager::createDeviceFromInfo(const CreateAvdInfo &info) -{ - if (info.apiLevel < 0) { - qCWarning(androidDeviceLog) << "System image of the created AVD is nullptr"; - return IDevice::Ptr(); - } - AndroidDevice *dev = new AndroidDevice; - const Utils::Id deviceId = AndroidDevice::idFromAvdInfo(info); - dev->setupId(IDevice::AutoDetected, deviceId); - dev->setMachineType(IDevice::Emulator); - dev->settings()->displayName.setValue(info.name); - dev->setDeviceState(IDevice::DeviceConnected); - dev->setAvdPath(avdFilePath() / (info.name + ".avd")); - dev->setExtraData(Constants::AndroidAvdName, info.name); - dev->setExtraData(Constants::AndroidCpuAbi, {info.abi}); - dev->setExtraData(Constants::AndroidSdk, info.apiLevel); - return IDevice::Ptr(dev); -} - -void AndroidDeviceManager::handleAvdListChange(const AndroidDeviceInfoList &avdList) -{ - DeviceManager *const devMgr = DeviceManager::instance(); - - QList existingAvds; - for (int i = 0; i < devMgr->deviceCount(); ++i) { - const IDevice::ConstPtr dev = devMgr->deviceAt(i); - const bool isEmulator = dev->machineType() == IDevice::Emulator; - if (isEmulator && dev->type() == Constants::ANDROID_DEVICE_TYPE) - existingAvds.append(dev->id()); - } - - QList connectedDevs; - for (const AndroidDeviceInfo &item : avdList) { - const Id deviceId = AndroidDevice::idFromDeviceInfo(item); - const QString displayName = displayNameFromInfo(item); - IDevice::ConstPtr dev = devMgr->find(deviceId); - if (dev) { - const auto androidDev = static_cast(dev.get()); - // DeviceManager doens't seem to have a way to directly update the name, if the name - // of the device has changed, remove it and register it again with the new name. - // Also account for the case of an AVD registered through old QC which might have - // invalid data by checking if the avdPath is not empty. - if (dev->displayName() != displayName || androidDev->avdPath().toString().isEmpty()) { - devMgr->removeDevice(dev->id()); - } else { - // Find the state of the AVD retrieved from the AVD watcher - const QString serial = getRunningAvdsSerialNumber(item.avdName); - if (!serial.isEmpty()) { - const IDevice::DeviceState state = getDeviceState(serial, IDevice::Emulator); - if (dev->deviceState() != state) { - devMgr->setDeviceState(dev->id(), state); - qCDebug(androidDeviceLog, "Device id \"%s\" changed its state.", - dev->id().toString().toUtf8().data()); - } - } else { - devMgr->setDeviceState(dev->id(), IDevice::DeviceConnected); - } - connectedDevs.append(dev->id()); - continue; - } - } - - AndroidDevice *newDev = new AndroidDevice(); - newDev->setupId(IDevice::AutoDetected, deviceId); - newDev->settings()->displayName.setValue(displayName); - newDev->setMachineType(item.type); - newDev->setDeviceState(item.state); - - newDev->setExtraData(Constants::AndroidAvdName, item.avdName); - newDev->setExtraData(Constants::AndroidSerialNumber, item.serialNumber); - newDev->setExtraData(Constants::AndroidCpuAbi, item.cpuAbi); - newDev->setExtraData(Constants::AndroidSdk, item.sdk); - newDev->setAvdPath(item.avdPath); - - qCDebug(androidDeviceLog, "Registering new Android device id \"%s\".", - newDev->id().toString().toUtf8().data()); - const IDevice::ConstPtr constNewDev = IDevice::ConstPtr(newDev); - devMgr->addDevice(IDevice::ConstPtr(constNewDev)); - connectedDevs.append(constNewDev->id()); - } - - // Set devices no longer connected to disconnected state. - for (const Id &id : existingAvds) { - if (!connectedDevs.contains(id)) { - qCDebug(androidDeviceLog, "Removing AVD id \"%s\" because it no longer exists.", - id.toString().toUtf8().data()); - devMgr->removeDevice(id); - } - } -} - -void AndroidDeviceManager::handleDevicesListChange(const QString &serialNumber) +static void handleDevicesListChange(const QString &serialNumber) { DeviceManager *const devMgr = DeviceManager::instance(); const QStringList serialBits = serialNumber.split('\t'); @@ -883,6 +754,57 @@ void AndroidDeviceManager::handleDevicesListChange(const QString &serialNumber) } } +void AndroidDeviceManager::setupDevicesWatcher() +{ + if (!AndroidConfig::adbToolPath().exists()) { + qCDebug(androidDeviceLog) << "Cannot start ADB device watcher" + << "because adb path does not exist."; + return; + } + + if (!m_adbDeviceWatcherProcess) + m_adbDeviceWatcherProcess.reset(new Process(this)); + + if (m_adbDeviceWatcherProcess->isRunning()) { + qCDebug(androidDeviceLog) << "ADB device watcher is already running."; + return; + } + + connect(m_adbDeviceWatcherProcess.get(), &Process::done, this, [this] { + if (m_adbDeviceWatcherProcess->error() != QProcess::UnknownError) { + qCDebug(androidDeviceLog) << "ADB device watcher encountered an error:" + << m_adbDeviceWatcherProcess->errorString(); + if (!m_adbDeviceWatcherProcess->isRunning()) { + qCDebug(androidDeviceLog) << "Restarting the ADB device watcher now."; + QTimer::singleShot(0, m_adbDeviceWatcherProcess.get(), &Process::start); + } + } + qCDebug(androidDeviceLog) << "ADB device watcher finished."; + }); + + m_adbDeviceWatcherProcess->setStdErrLineCallback([](const QString &error) { + qCDebug(androidDeviceLog) << "ADB device watcher error" << error; }); + m_adbDeviceWatcherProcess->setStdOutLineCallback([this](const QString &output) { + handleDevicesListChange(output); + }); + + const CommandLine command{AndroidConfig::adbToolPath(), {"track-devices"}}; + m_adbDeviceWatcherProcess->setCommand(command); + m_adbDeviceWatcherProcess->setWorkingDirectory(command.executable().parentDir()); + m_adbDeviceWatcherProcess->setEnvironment(AndroidConfig::toolsEnvironment()); + m_adbDeviceWatcherProcess->start(); + + // Setup AVD filesystem watcher to listen for changes when an avd is created/deleted, + // or started/stopped + m_avdFileSystemWatcher.addPath(avdFilePath().toString()); + connect(&m_avdFileSystemWatcher, &QFileSystemWatcher::directoryChanged, this, [this] { + if (!m_avdPathGuard.isLocked()) + updateAvdList(); + }); + // Call initial update + updateAvdList(); +} + static AndroidDeviceManager *s_instance = nullptr; AndroidDeviceManager *AndroidDeviceManager::instance() @@ -918,6 +840,78 @@ static void modifyManufacturerTag(const FilePath &avdPath, TagModification modif saver.finalize(); } +static void handleAvdListChange(const AndroidDeviceInfoList &avdList) +{ + DeviceManager *const devMgr = DeviceManager::instance(); + + QList existingAvds; + for (int i = 0; i < devMgr->deviceCount(); ++i) { + const IDevice::ConstPtr dev = devMgr->deviceAt(i); + const bool isEmulator = dev->machineType() == IDevice::Emulator; + if (isEmulator && dev->type() == Constants::ANDROID_DEVICE_TYPE) + existingAvds.append(dev->id()); + } + + QList connectedDevs; + for (const AndroidDeviceInfo &item : avdList) { + const Id deviceId = AndroidDevice::idFromDeviceInfo(item); + const QString displayName = displayNameFromInfo(item); + IDevice::ConstPtr dev = devMgr->find(deviceId); + if (dev) { + const auto androidDev = static_cast(dev.get()); + // DeviceManager doens't seem to have a way to directly update the name, if the name + // of the device has changed, remove it and register it again with the new name. + // Also account for the case of an AVD registered through old QC which might have + // invalid data by checking if the avdPath is not empty. + if (dev->displayName() != displayName || androidDev->avdPath().toString().isEmpty()) { + devMgr->removeDevice(dev->id()); + } else { + // Find the state of the AVD retrieved from the AVD watcher + const QString serial = getRunningAvdsSerialNumber(item.avdName); + if (!serial.isEmpty()) { + const IDevice::DeviceState state = getDeviceState(serial, IDevice::Emulator); + if (dev->deviceState() != state) { + devMgr->setDeviceState(dev->id(), state); + qCDebug(androidDeviceLog, "Device id \"%s\" changed its state.", + dev->id().toString().toUtf8().data()); + } + } else { + devMgr->setDeviceState(dev->id(), IDevice::DeviceConnected); + } + connectedDevs.append(dev->id()); + continue; + } + } + + AndroidDevice *newDev = new AndroidDevice; + newDev->setupId(IDevice::AutoDetected, deviceId); + newDev->settings()->displayName.setValue(displayName); + newDev->setMachineType(item.type); + newDev->setDeviceState(item.state); + + newDev->setExtraData(Constants::AndroidAvdName, item.avdName); + newDev->setExtraData(Constants::AndroidSerialNumber, item.serialNumber); + newDev->setExtraData(Constants::AndroidCpuAbi, item.cpuAbi); + newDev->setExtraData(Constants::AndroidSdk, item.sdk); + newDev->setAvdPath(item.avdPath); + + qCDebug(androidDeviceLog, "Registering new Android device id \"%s\".", + newDev->id().toString().toUtf8().data()); + const IDevice::ConstPtr constNewDev = IDevice::ConstPtr(newDev); + devMgr->addDevice(IDevice::ConstPtr(constNewDev)); + connectedDevs.append(constNewDev->id()); + } + + // Set devices no longer connected to disconnected state. + for (const Id &id : existingAvds) { + if (!connectedDevs.contains(id)) { + qCDebug(androidDeviceLog, "Removing AVD id \"%s\" because it no longer exists.", + id.toString().toUtf8().data()); + devMgr->removeDevice(id); + } + } +} + AndroidDeviceManager::AndroidDeviceManager(QObject *parent) : QObject(parent) , m_avdListRecipe{} @@ -1003,7 +997,7 @@ public: if (dialog.exec() != QDialog::Accepted) return IDevice::Ptr(); - const IDevice::Ptr dev = AndroidDeviceManager::createDeviceFromInfo(dialog.avdInfo()); + const IDevice::Ptr dev = createDeviceFromInfo(dialog.avdInfo()); if (const auto androidDev = static_cast(dev.get())) { qCDebug(androidDeviceLog, "Created new Android AVD id \"%s\".", qPrintable(androidDev->avdName())); diff --git a/src/plugins/android/androiddevice.h b/src/plugins/android/androiddevice.h index 789fa75ff12..7e2b4960f9f 100644 --- a/src/plugins/android/androiddevice.h +++ b/src/plugins/android/androiddevice.h @@ -76,29 +76,14 @@ public: static AndroidDeviceManager *instance(); void setupDevicesWatcher(); void updateAvdList(); - IDevice::DeviceState getDeviceState(const QString &serial, IDevice::MachineType type) const; - void updateDeviceState(const ProjectExplorer::IDevice::ConstPtr &device); Utils::expected_str createAvd(const CreateAvdInfo &info, bool force); - void startAvd(const ProjectExplorer::IDevice::Ptr &device, QWidget *parent = nullptr); void eraseAvd(const ProjectExplorer::IDevice::Ptr &device, QWidget *parent = nullptr); - void setupWifiForDevice(const ProjectExplorer::IDevice::Ptr &device, QWidget *parent = nullptr); - - void setEmulatorArguments(QWidget *parent = nullptr); - - QString getRunningAvdsSerialNumber(const QString &name) const; - - static ProjectExplorer::IDevice::Ptr createDeviceFromInfo(const CreateAvdInfo &info); private: explicit AndroidDeviceManager(QObject *parent); ~AndroidDeviceManager(); - void handleDevicesListChange(const QString &serialNumber); - void handleAvdListChange(const AndroidDeviceInfoList &avdList); - - QString emulatorName(const QString &serialNumber) const; - Tasking::Group m_avdListRecipe; Tasking::TaskTreeRunner m_avdListRunner; std::unique_ptr m_removeAvdProcess;