Android: Hide AndroidDeviceManager methods in cpp

Change-Id: I871b1126d36367b3716ed988f581a8b5bca68693
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
Jarek Kobus
2024-05-22 14:50:42 +02:00
parent 5a12b3618f
commit ccd6872608
2 changed files with 317 additions and 338 deletions

View File

@@ -57,6 +57,133 @@ static QString displayNameFromInfo(const AndroidDeviceInfo &info)
: info.avdName; : 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<const AndroidDevice *>(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<const AndroidDevice *>(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 "
"(<a href=\"%1\">Help Web Page</a>):")
.arg(helpUrl));
dialog.setTextValue(AndroidConfig::emulatorArgs());
if (auto label = dialog.findChild<QLabel *>()) {
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 class AndroidDeviceWidget : public IDeviceWidget
{ {
public: public:
@@ -70,6 +197,66 @@ public:
static bool questionDialog(const QString &question, QWidget *parent = nullptr); 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<const AndroidDevice *>(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) AndroidDeviceWidget::AndroidDeviceWidget(const IDevice::Ptr &device)
: IDeviceWidget(device) : IDeviceWidget(device)
{ {
@@ -168,7 +355,7 @@ AndroidDevice::AndroidDevice()
addDeviceAction({Tr::tr("Refresh"), [](const IDevice::Ptr &device, QWidget *parent) { addDeviceAction({Tr::tr("Refresh"), [](const IDevice::Ptr &device, QWidget *parent) {
Q_UNUSED(parent) Q_UNUSED(parent)
AndroidDeviceManager::instance()->updateDeviceState(device); updateDeviceState(device);
}}); }});
} }
@@ -198,7 +385,7 @@ void AndroidDevice::addActionsIfNotFound()
if (machineType() == Emulator) { if (machineType() == Emulator) {
if (!hasStartAction) { if (!hasStartAction) {
addDeviceAction({startAvdAction, [](const IDevice::Ptr &device, QWidget *parent) { 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) { if (!hasAvdArgumentsAction) {
addDeviceAction({avdArgumentsAction, [](const IDevice::Ptr &device, QWidget *parent) { addDeviceAction({avdArgumentsAction, [](const IDevice::Ptr &device, QWidget *parent) {
Q_UNUSED(device) Q_UNUSED(device)
AndroidDeviceManager::instance()->setEmulatorArguments(parent); setEmulatorArguments(parent);
}}); }});
} }
} else if (machineType() == Hardware && !ipRegex.match(id().toString()).hasMatch()) { } else if (machineType() == Hardware && !ipRegex.match(id().toString()).hasMatch()) {
if (!hasSetupWifi) { if (!hasSetupWifi) {
addDeviceAction({setupWifi, [](const IDevice::Ptr &device, QWidget *parent) { 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(); const QString serialNumber = extraData(Constants::AndroidSerialNumber).toString();
if (machineType() == Hardware) if (machineType() == Hardware)
return serialNumber; return serialNumber;
return getRunningAvdsSerialNumber(avdName());
return AndroidDeviceManager::instance()->getRunningAvdsSerialNumber(avdName());
} }
QString AndroidDevice::avdName() const QString AndroidDevice::avdName() const
@@ -419,31 +605,6 @@ void AndroidDeviceManager::updateAvdList()
m_avdListRunner.start(m_avdListRecipe); 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<const AndroidDevice *>(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<void> AndroidDeviceManager::createAvd(const CreateAvdInfo &info, bool force) expected_str<void> AndroidDeviceManager::createAvd(const CreateAvdInfo &info, bool force)
{ {
CommandLine cmd(AndroidConfig::avdManagerToolPath(), {"create", "avd", "-n", info.name}); CommandLine cmd(AndroidConfig::avdManagerToolPath(), {"create", "avd", "-n", info.name});
@@ -488,23 +649,6 @@ expected_str<void> AndroidDeviceManager::createAvd(const CreateAvdInfo &info, bo
return {}; return {};
} }
void AndroidDeviceManager::startAvd(const ProjectExplorer::IDevice::Ptr &device, QWidget *parent)
{
Q_UNUSED(parent)
const AndroidDevice *androidDev = static_cast<const AndroidDevice *>(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) void AndroidDeviceManager::eraseAvd(const IDevice::Ptr &device, QWidget *parent)
{ {
if (!device) if (!device)
@@ -541,280 +685,7 @@ void AndroidDeviceManager::eraseAvd(const IDevice::Ptr &device, QWidget *parent)
m_removeAvdProcess->start(); m_removeAvdProcess->start();
} }
void AndroidDeviceManager::setupWifiForDevice(const IDevice::Ptr &device, QWidget *parent) static void handleDevicesListChange(const QString &serialNumber)
{
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<const AndroidDevice *>(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 "
"(<a href=\"%1\">Help Web Page</a>):")
.arg(helpUrl));
dialog.setTextValue(AndroidConfig::emulatorArgs());
if (auto label = dialog.findChild<QLabel*>()) {
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<Id> 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<Id> 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<const AndroidDevice *>(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)
{ {
DeviceManager *const devMgr = DeviceManager::instance(); DeviceManager *const devMgr = DeviceManager::instance();
const QStringList serialBits = serialNumber.split('\t'); 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; static AndroidDeviceManager *s_instance = nullptr;
AndroidDeviceManager *AndroidDeviceManager::instance() AndroidDeviceManager *AndroidDeviceManager::instance()
@@ -918,6 +840,78 @@ static void modifyManufacturerTag(const FilePath &avdPath, TagModification modif
saver.finalize(); saver.finalize();
} }
static void handleAvdListChange(const AndroidDeviceInfoList &avdList)
{
DeviceManager *const devMgr = DeviceManager::instance();
QList<Id> 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<Id> 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<const AndroidDevice *>(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) AndroidDeviceManager::AndroidDeviceManager(QObject *parent)
: QObject(parent) : QObject(parent)
, m_avdListRecipe{} , m_avdListRecipe{}
@@ -1003,7 +997,7 @@ public:
if (dialog.exec() != QDialog::Accepted) if (dialog.exec() != QDialog::Accepted)
return IDevice::Ptr(); return IDevice::Ptr();
const IDevice::Ptr dev = AndroidDeviceManager::createDeviceFromInfo(dialog.avdInfo()); const IDevice::Ptr dev = createDeviceFromInfo(dialog.avdInfo());
if (const auto androidDev = static_cast<AndroidDevice *>(dev.get())) { if (const auto androidDev = static_cast<AndroidDevice *>(dev.get())) {
qCDebug(androidDeviceLog, "Created new Android AVD id \"%s\".", qCDebug(androidDeviceLog, "Created new Android AVD id \"%s\".",
qPrintable(androidDev->avdName())); qPrintable(androidDev->avdName()));

View File

@@ -76,29 +76,14 @@ public:
static AndroidDeviceManager *instance(); static AndroidDeviceManager *instance();
void setupDevicesWatcher(); void setupDevicesWatcher();
void updateAvdList(); void updateAvdList();
IDevice::DeviceState getDeviceState(const QString &serial, IDevice::MachineType type) const;
void updateDeviceState(const ProjectExplorer::IDevice::ConstPtr &device);
Utils::expected_str<void> createAvd(const CreateAvdInfo &info, bool force); Utils::expected_str<void> 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 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: private:
explicit AndroidDeviceManager(QObject *parent); explicit AndroidDeviceManager(QObject *parent);
~AndroidDeviceManager(); ~AndroidDeviceManager();
void handleDevicesListChange(const QString &serialNumber);
void handleAvdListChange(const AndroidDeviceInfoList &avdList);
QString emulatorName(const QString &serialNumber) const;
Tasking::Group m_avdListRecipe; Tasking::Group m_avdListRecipe;
Tasking::TaskTreeRunner m_avdListRunner; Tasking::TaskTreeRunner m_avdListRunner;
std::unique_ptr<Utils::Process> m_removeAvdProcess; std::unique_ptr<Utils::Process> m_removeAvdProcess;