forked from qt-creator/qt-creator
Android: Hide AndroidDeviceManager methods in cpp
Change-Id: I871b1126d36367b3716ed988f581a8b5bca68693 Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
@@ -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()));
|
||||||
|
@@ -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;
|
||||||
|
Reference in New Issue
Block a user