forked from qt-creator/qt-creator
Monitor Android devices in non-polling method
Use ADB's track-devices command to watch for device events which would replace the current polling method with a timer. For AVDs, a QFileSystemWatcher is used to watch for changes in the AVDs home folder which would allow updating the AVDs only when a change is done like edition, deletion, start, stop, even from outside Qt Creator. This method would also make device updates faster, instead of unexpected waits due to timer use. Task-number: QTCREATORBUG-23991 Change-Id: I08a92252c99c02bc111e597d671f2350817458c7 Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2021 The Qt Company Ltd.
|
||||
** Copyright (C) 2022 The Qt Company Ltd.
|
||||
** Copyright (C) 2016 BogDan Vatra <bog_dan_ro@yahoo.com>
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
@@ -52,6 +52,9 @@
|
||||
#include <QLoggingCategory>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QTimer>
|
||||
|
||||
#include <utils/qtcprocess.h>
|
||||
|
||||
using namespace ProjectExplorer;
|
||||
|
||||
@@ -59,9 +62,6 @@ namespace {
|
||||
static Q_LOGGING_CATEGORY(androidDeviceLog, "qtc.android.androiddevice", QtWarningMsg)
|
||||
}
|
||||
|
||||
// interval for updating the list of connected Android devices and emulators
|
||||
constexpr int deviceUpdaterMsInterval = 30000;
|
||||
|
||||
namespace Android {
|
||||
namespace Internal {
|
||||
|
||||
@@ -328,7 +328,7 @@ QString AndroidDevice::serialNumber() const
|
||||
if (machineType() == Hardware)
|
||||
return serialNumber;
|
||||
|
||||
return AndroidConfigurations::currentConfig().getRunningAvdsSerialNumber(avdName());
|
||||
return AndroidDeviceManager::instance()->getRunningAvdsSerialNumber(avdName());
|
||||
}
|
||||
|
||||
QString AndroidDevice::avdName() const
|
||||
@@ -419,36 +419,28 @@ QUrl AndroidDevice::toolControlChannel(const ControlChannelHint &) const
|
||||
return url;
|
||||
}
|
||||
|
||||
void AndroidDeviceManager::updateDevicesList()
|
||||
void AndroidDeviceManager::updateAvdsList()
|
||||
{
|
||||
// If a non-Android Kit is currently active, skip the device list update
|
||||
const Target *startupTarget = SessionManager::startupTarget();
|
||||
if (!startupTarget)
|
||||
return;
|
||||
|
||||
const Kit *kit = startupTarget->kit();
|
||||
if (!kit)
|
||||
return;
|
||||
|
||||
if (DeviceTypeKitAspect::deviceTypeId(kit) != Constants::ANDROID_DEVICE_TYPE)
|
||||
return;
|
||||
|
||||
updateDevicesListOnce();
|
||||
}
|
||||
|
||||
void AndroidDeviceManager::updateDevicesListOnce()
|
||||
{
|
||||
if (!m_avdsFutureWatcher.isRunning() && m_androidConfig.adbToolPath().exists()) {
|
||||
if (!m_avdsFutureWatcher.isRunning() && m_androidConfig.adbToolPath().exists())
|
||||
m_avdsFutureWatcher.setFuture(m_avdManager.avdList());
|
||||
m_devicesFutureWatcher.setFuture(Utils::runAsync([this]() {
|
||||
return m_androidConfig.connectedDevices();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
void AndroidDeviceManager::updateDeviceState(const ProjectExplorer::IDevice::Ptr &device)
|
||||
IDevice::DeviceState AndroidDeviceManager::getDeviceState(const QString &serial,
|
||||
IDevice::MachineType type) const
|
||||
{
|
||||
const AndroidDevice *dev = static_cast<AndroidDevice *>(device.data());
|
||||
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.data());
|
||||
const QString serial = dev->serialNumber();
|
||||
DeviceManager *const devMgr = DeviceManager::instance();
|
||||
const Utils::Id id = dev->id();
|
||||
@@ -457,15 +449,7 @@ void AndroidDeviceManager::updateDeviceState(const ProjectExplorer::IDevice::Ptr
|
||||
return;
|
||||
}
|
||||
|
||||
const QStringList args = AndroidDeviceInfo::adbSelector(serial) << "shell" << "echo" << "1";
|
||||
const SdkToolResult result = AndroidManager::runAdbCommand(args);
|
||||
const int success = result.success();
|
||||
if (success)
|
||||
devMgr->setDeviceState(id, IDevice::DeviceReadyToUse);
|
||||
else if (dev->machineType() == IDevice::Emulator || result.stdErr().contains("unauthorized"))
|
||||
devMgr->setDeviceState(id, IDevice::DeviceConnected);
|
||||
else
|
||||
devMgr->setDeviceState(id, IDevice::DeviceDisconnected);
|
||||
devMgr->setDeviceState(id, getDeviceState(serial, dev->machineType()));
|
||||
}
|
||||
|
||||
void AndroidDeviceManager::startAvd(const ProjectExplorer::IDevice::Ptr &device, QWidget *parent)
|
||||
@@ -523,6 +507,13 @@ void AndroidDeviceManager::handleAvdRemoved()
|
||||
}
|
||||
}
|
||||
|
||||
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 =
|
||||
@@ -546,73 +537,121 @@ void AndroidDeviceManager::setEmulatorArguments(QWidget *parent)
|
||||
m_androidConfig.setEmulatorArgs(Utils::ProcessArgs::splitArgs(dialog.textValue()));
|
||||
}
|
||||
|
||||
void AndroidDeviceManager::setupDevicesWatcher()
|
||||
QString AndroidDeviceManager::getRunningAvdsSerialNumber(const QString &name) const
|
||||
{
|
||||
if (!m_devicesUpdaterTimer.isActive()) {
|
||||
// The call to avdmanager is always slower than the call to adb devices,
|
||||
// so connecting the slot to the slower call should be enough.
|
||||
connect(&m_avdsFutureWatcher, &QFutureWatcherBase::finished,
|
||||
this, &AndroidDeviceManager::devicesListUpdated);
|
||||
connect(&m_devicesUpdaterTimer, &QTimer::timeout, this, [this]() {
|
||||
updateDevicesList();
|
||||
});
|
||||
m_devicesUpdaterTimer.start(deviceUpdaterMsInterval);
|
||||
for (const AndroidDeviceInfo &dev : m_androidConfig.connectedDevices()) {
|
||||
if (!dev.serialNumber.startsWith("emulator"))
|
||||
continue;
|
||||
const QString stdOut = emulatorName(dev.serialNumber);
|
||||
if (stdOut.isEmpty())
|
||||
continue; // Not an avd
|
||||
const QStringList outputLines = stdOut.split('\n');
|
||||
if (outputLines.size() > 1 && outputLines.first() == name)
|
||||
return dev.serialNumber;
|
||||
}
|
||||
updateDevicesListOnce();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void AndroidDeviceManager::devicesListUpdated()
|
||||
void AndroidDeviceManager::setupDevicesWatcher()
|
||||
{
|
||||
QVector<AndroidDeviceInfo> connectedDevicesInfos;
|
||||
connectedDevicesInfos = m_devicesFutureWatcher.result();
|
||||
|
||||
// For checking the state of avds, since running avds are assigned a serial number of
|
||||
// the form emulator-xxxx, thus we have to manually check for the names.
|
||||
const QStringList runningAvds = m_androidConfig.getRunningAvdsFromDevices(connectedDevicesInfos);
|
||||
|
||||
AndroidDeviceInfoList devices = m_avdsFutureWatcher.result();
|
||||
const QSet<QString> startedAvds = Utils::transform<QSet>(connectedDevicesInfos,
|
||||
&AndroidDeviceInfo::avdname);
|
||||
for (const AndroidDeviceInfo &dev : devices)
|
||||
if (!startedAvds.contains(dev.avdname))
|
||||
connectedDevicesInfos << dev;
|
||||
|
||||
DeviceManager *const devMgr = DeviceManager::instance();
|
||||
|
||||
QVector<IDevice::ConstPtr> existingDevs;
|
||||
QVector<IDevice::ConstPtr> connectedDevs;
|
||||
|
||||
for (int i = 0; i < devMgr->deviceCount(); ++i) {
|
||||
const IDevice::ConstPtr dev = devMgr->deviceAt(i);
|
||||
if (dev->id().toString().startsWith(Constants::ANDROID_DEVICE_ID)) {
|
||||
existingDevs.append(dev);
|
||||
}
|
||||
if (!m_androidConfig.adbToolPath().exists()) {
|
||||
qCDebug(androidDeviceLog) << "Cannot start ADB device watcher"
|
||||
<< "because adb path does not exist.";
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto item : connectedDevicesInfos) {
|
||||
if (!m_adbDeviceWatcherProcess)
|
||||
m_adbDeviceWatcherProcess.reset(new Utils::QtcProcess(this));
|
||||
|
||||
if (m_adbDeviceWatcherProcess->isRunning()) {
|
||||
qCDebug(androidDeviceLog) << "ADB device watcher is already running.";
|
||||
return;
|
||||
}
|
||||
|
||||
connect(m_adbDeviceWatcherProcess.get(), &Utils::QtcProcess::finished, this,
|
||||
[]() { qCDebug(androidDeviceLog) << "ADB device watcher finished."; });
|
||||
|
||||
connect(m_adbDeviceWatcherProcess.get(), &Utils::QtcProcess::errorOccurred, this,
|
||||
[this](QProcess::ProcessError error) {
|
||||
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(), &Utils::QtcProcess::start);
|
||||
}
|
||||
});
|
||||
|
||||
m_adbDeviceWatcherProcess->setStdErrLineCallback([](const QString &error) {
|
||||
qCDebug(androidDeviceLog) << "ADB device watcher error" << error; });
|
||||
m_adbDeviceWatcherProcess->setStdOutLineCallback([this](const QString &output) {
|
||||
HandleDevicesListChange(output);
|
||||
});
|
||||
|
||||
const Utils::CommandLine command = Utils::CommandLine(m_androidConfig.adbToolPath(),
|
||||
{"track-devices"});
|
||||
m_adbDeviceWatcherProcess->setCommand(command);
|
||||
m_adbDeviceWatcherProcess->setEnvironment(AndroidConfigurations::toolsEnvironment(m_androidConfig));
|
||||
m_adbDeviceWatcherProcess->start();
|
||||
|
||||
// Setup AVD filesystem watcher to listen for changes when an avd is created/deleted,
|
||||
// or started/stopped
|
||||
QString avdEnvVar = qEnvironmentVariable("ANDROID_AVD_HOME");
|
||||
if (avdEnvVar.isEmpty()) {
|
||||
avdEnvVar = qEnvironmentVariable("ANDROID_SDK_HOME");
|
||||
if (avdEnvVar.isEmpty())
|
||||
avdEnvVar = qEnvironmentVariable("HOME");
|
||||
avdEnvVar.append("/.android/avd");
|
||||
}
|
||||
const Utils::FilePath avdPath = Utils::FilePath::fromUserInput(avdEnvVar);
|
||||
m_avdFileSystemWatcher.addPath(avdPath.toString());
|
||||
connect(&m_avdsFutureWatcher, &QFutureWatcherBase::finished,
|
||||
this, &AndroidDeviceManager::HandleAvdsListChange);
|
||||
connect(&m_avdFileSystemWatcher, &QFileSystemWatcher::directoryChanged, this, [this]() {
|
||||
// If the avd list upate command is running no need to call it again.
|
||||
if (!m_avdsFutureWatcher.isRunning())
|
||||
updateAvdsList();
|
||||
});
|
||||
// Call initial update
|
||||
updateAvdsList();
|
||||
}
|
||||
|
||||
void AndroidDeviceManager::HandleAvdsListChange()
|
||||
{
|
||||
DeviceManager *const devMgr = DeviceManager::instance();
|
||||
|
||||
QVector<IDevice::ConstPtr> 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);
|
||||
}
|
||||
|
||||
QVector<IDevice::ConstPtr> connectedDevs;
|
||||
for (auto item : m_avdsFutureWatcher.result()) {
|
||||
const Utils::Id deviceId = AndroidDevice::idFromDeviceInfo(item);
|
||||
const QString displayName = AndroidDevice::displayNameFromInfo(item);
|
||||
IDevice::ConstPtr dev = devMgr->find(deviceId);
|
||||
if (!dev.isNull()) {
|
||||
if (dev->displayName() == displayName) {
|
||||
IDevice::DeviceState newState;
|
||||
// If an AVD is not already running set its state to Connected instead of
|
||||
// ReadyToUse.
|
||||
if (dev->machineType() == IDevice::Emulator && !runningAvds.contains(displayName))
|
||||
newState = IDevice::DeviceConnected;
|
||||
else
|
||||
newState = item.state;
|
||||
if (dev->deviceState() != newState) {
|
||||
const auto androidDev = static_cast<const AndroidDevice *>(dev.data());
|
||||
// DeviceManager doens't seem to hav 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 the sdcard size value.
|
||||
if (dev->displayName() != displayName || androidDev->sdcardSize() == tr("Unknown")) {
|
||||
devMgr->removeDevice(dev->id());
|
||||
} else {
|
||||
// Find the state of the AVD retrieved from the AVD watcher
|
||||
const QString serial = getRunningAvdsSerialNumber(item.avdname);
|
||||
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());
|
||||
devMgr->setDeviceState(dev->id(), newState);
|
||||
}
|
||||
connectedDevs.append(dev);
|
||||
continue;
|
||||
} else {
|
||||
// DeviceManager doens't seem to hav 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.
|
||||
devMgr->removeDevice(dev->id());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -625,17 +664,79 @@ void AndroidDeviceManager::devicesListUpdated()
|
||||
qCDebug(androidDeviceLog, "Registering new Android device id \"%s\".",
|
||||
newDev->id().toString().toUtf8().data());
|
||||
const IDevice::ConstPtr constNewDev = IDevice::ConstPtr(newDev);
|
||||
devMgr->addDevice(constNewDev);
|
||||
devMgr->addDevice(IDevice::ConstPtr(constNewDev));
|
||||
connectedDevs.append(constNewDev);
|
||||
}
|
||||
|
||||
// Set devices no longer connected to disconnected state.
|
||||
for (const IDevice::ConstPtr &dev : existingDevs) {
|
||||
if (dev->id() != Constants::ANDROID_DEVICE_ID && !connectedDevs.contains(dev)
|
||||
&& dev->deviceState() != IDevice::DeviceDisconnected) {
|
||||
qCDebug(androidDeviceLog, "Device id \"%s\" is no longer connected.",
|
||||
dev->id().toString().toUtf8().data());
|
||||
devMgr->setDeviceState(dev->id(), IDevice::DeviceDisconnected);
|
||||
// Set devices no longer connected to disconnected state.
|
||||
for (const IDevice::ConstPtr &dev : existingAvds) {
|
||||
if (!connectedDevs.contains(dev)) {
|
||||
qCDebug(androidDeviceLog, "Removing AVD id \"%s\" because it no longer exists.",
|
||||
dev->id().toString().toUtf8().data());
|
||||
devMgr->removeDevice(dev->id());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AndroidDeviceManager::HandleDevicesListChange(const QString &serialNumber)
|
||||
{
|
||||
DeviceManager *const devMgr = DeviceManager::instance();
|
||||
const QStringList serialBits = serialNumber.split('\t');
|
||||
if (serialBits.size() < 2)
|
||||
return;
|
||||
|
||||
// Sample output of adb track-devices, the first 4 digits are for state type
|
||||
// and sometimes 4 zeros are reported as part for the serial number.
|
||||
// 00546db0e8d7 authorizing
|
||||
// 00546db0e8d7 device
|
||||
// 0000001711201JEC207789 offline
|
||||
// emulator-5554 device
|
||||
QString dirtySerial = serialBits.first().trimmed();
|
||||
if (dirtySerial.startsWith("0000"))
|
||||
dirtySerial = dirtySerial.mid(4);
|
||||
if (dirtySerial.startsWith("00"))
|
||||
dirtySerial = dirtySerial.mid(4);
|
||||
const bool isEmulator = dirtySerial.startsWith("emulator");
|
||||
|
||||
const QString &serial = dirtySerial;
|
||||
const QString stateStr = serialBits.at(1).trimmed();
|
||||
|
||||
IDevice::DeviceState state;
|
||||
if (stateStr == "device")
|
||||
state = IDevice::DeviceReadyToUse;
|
||||
else if (stateStr == "offline")
|
||||
state = IDevice::DeviceDisconnected;
|
||||
else
|
||||
state = IDevice::DeviceConnected;
|
||||
|
||||
if (isEmulator) {
|
||||
const QString avdName = emulatorName(serial);
|
||||
const Utils::Id avdId = Utils::Id(Constants::ANDROID_DEVICE_ID).withSuffix(':' + avdName);
|
||||
devMgr->setDeviceState(avdId, state);
|
||||
} else {
|
||||
const Utils::Id id = Utils::Id(Constants::ANDROID_DEVICE_ID).withSuffix(':' + serial);
|
||||
const QString displayName = AndroidConfigurations::currentConfig().getProductModel(serial);
|
||||
if (IDevice::ConstPtr dev = devMgr->find(id)) {
|
||||
// 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.
|
||||
if (dev->displayName() == displayName)
|
||||
devMgr->setDeviceState(id, state);
|
||||
else
|
||||
devMgr->removeDevice(id);
|
||||
} else {
|
||||
AndroidDevice *newDev = new AndroidDevice();
|
||||
newDev->setupId(IDevice::AutoDetected, id);
|
||||
newDev->setDisplayName(displayName);
|
||||
newDev->setMachineType(IDevice::Hardware);
|
||||
newDev->setDeviceState(state);
|
||||
|
||||
newDev->setExtraData(Constants::AndroidSerialNumber, serial);
|
||||
newDev->setExtraData(Constants::AndroidCpuAbi, m_androidConfig.getAbis(serial));
|
||||
newDev->setExtraData(Constants::AndroidSdk, m_androidConfig.getSDKVersion(serial));
|
||||
|
||||
qCDebug(androidDeviceLog, "Registering new Android device id \"%s\".",
|
||||
newDev->id().toString().toUtf8().data());
|
||||
devMgr->addDevice(IDevice::ConstPtr(newDev));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -652,9 +753,10 @@ AndroidDeviceManager::AndroidDeviceManager(QObject *parent)
|
||||
m_avdManager(m_androidConfig)
|
||||
{
|
||||
connect(qApp, &QCoreApplication::aboutToQuit, this, [this]() {
|
||||
m_devicesUpdaterTimer.stop();
|
||||
m_adbDeviceWatcherProcess->terminate();
|
||||
m_adbDeviceWatcherProcess->waitForFinished();
|
||||
m_adbDeviceWatcherProcess.reset();
|
||||
m_avdsFutureWatcher.waitForFinished();
|
||||
m_devicesFutureWatcher.waitForFinished();
|
||||
m_removeAvdFutureWatcher.waitForFinished();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user