forked from qt-creator/qt-creator
Android: Use task tree for startAvd() action
As a drive-by, lower some timeouts from 2s into 1s. Change-Id: I495ad20058b0f97b6cdd5271a390b779c66b4864 Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
@@ -7,12 +7,17 @@
|
|||||||
|
|
||||||
#include <coreplugin/icore.h>
|
#include <coreplugin/icore.h>
|
||||||
|
|
||||||
|
#include <solutions/tasking/conditional.h>
|
||||||
|
#include <solutions/tasking/tcpsocket.h>
|
||||||
|
|
||||||
|
#include <utils/async.h>
|
||||||
#include <utils/qtcprocess.h>
|
#include <utils/qtcprocess.h>
|
||||||
|
|
||||||
#include <QLoggingCategory>
|
#include <QLoggingCategory>
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
|
|
||||||
|
using namespace Tasking;
|
||||||
using namespace Utils;
|
using namespace Utils;
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
@@ -27,6 +32,7 @@ QString startAvd(const QString &name, const std::optional<QFuture<void>> &future
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Make async and move out of startAvdImpl, make it a part of startAvdRecipe.
|
||||||
static bool is32BitUserSpace()
|
static bool is32BitUserSpace()
|
||||||
{
|
{
|
||||||
// Do a similar check as android's emulator is doing:
|
// Do a similar check as android's emulator is doing:
|
||||||
@@ -73,6 +79,44 @@ bool startAvdAsync(const QString &avdName)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void startAvdDetached(QPromise<void> &promise, const CommandLine &avdCommand)
|
||||||
|
{
|
||||||
|
qCDebug(avdManagerLog).noquote() << "Running command (startAvdDetached):" << avdCommand.toUserOutput();
|
||||||
|
if (!Process::startDetached(avdCommand, {}, DetachedChannelMode::Discard))
|
||||||
|
promise.future().cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
static CommandLine avdCommand(const QString &avdName)
|
||||||
|
{
|
||||||
|
CommandLine cmd(AndroidConfig::emulatorToolPath());
|
||||||
|
if (is32BitUserSpace())
|
||||||
|
cmd.addArg("-force-32bit");
|
||||||
|
cmd.addArgs(AndroidConfig::emulatorArgs(), CommandLine::Raw);
|
||||||
|
cmd.addArgs({"-avd", avdName});
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExecutableItem startAvdAsyncRecipe(const QString &avdName)
|
||||||
|
{
|
||||||
|
const auto onSetup = [avdName](Async<void> &async) {
|
||||||
|
const FilePath emulatorPath = AndroidConfig::emulatorToolPath();
|
||||||
|
if (!emulatorPath.exists()) {
|
||||||
|
QMessageBox::critical(Core::ICore::dialogParent(), Tr::tr("Emulator Tool Is Missing"),
|
||||||
|
Tr::tr("Install the missing emulator tool (%1) to the "
|
||||||
|
"installed Android SDK.").arg(emulatorPath.displayName()));
|
||||||
|
return SetupResult::StopWithError;
|
||||||
|
}
|
||||||
|
async.setConcurrentCallData(startAvdDetached, avdCommand(avdName));
|
||||||
|
return SetupResult::Continue;
|
||||||
|
};
|
||||||
|
const auto onDone = [avdName] {
|
||||||
|
QMessageBox::critical(Core::ICore::dialogParent(), Tr::tr("AVD Start Error"),
|
||||||
|
Tr::tr("Failed to start AVD emulator for \"%1\" device.").arg(avdName));
|
||||||
|
};
|
||||||
|
|
||||||
|
return AsyncTask<void>(onSetup, onDone, CallDoneIf::Error);
|
||||||
|
}
|
||||||
|
|
||||||
QString findAvd(const QString &avdName)
|
QString findAvd(const QString &avdName)
|
||||||
{
|
{
|
||||||
const QStringList lines = AndroidConfig::devicesCommandOutput();
|
const QStringList lines = AndroidConfig::devicesCommandOutput();
|
||||||
@@ -91,6 +135,69 @@ QString findAvd(const QString &avdName)
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ExecutableItem serialNumberRecipe(const QString &avdName, const Storage<QString> &serialNumberStorage)
|
||||||
|
{
|
||||||
|
const Storage<QStringList> outputStorage;
|
||||||
|
const Storage<QString> currentSerialNumberStorage;
|
||||||
|
const LoopUntil iterator([outputStorage](int iteration) { return iteration < outputStorage->size(); });
|
||||||
|
|
||||||
|
const auto onSocketSetup = [iterator, outputStorage, currentSerialNumberStorage](TcpSocket &socket) {
|
||||||
|
const QString line = outputStorage->at(iterator.iteration());
|
||||||
|
if (line.startsWith("* daemon"))
|
||||||
|
return SetupResult::StopWithError;
|
||||||
|
|
||||||
|
const QString serialNumber = line.left(line.indexOf('\t')).trimmed();
|
||||||
|
if (!serialNumber.startsWith("emulator"))
|
||||||
|
return SetupResult::StopWithError;
|
||||||
|
|
||||||
|
const int index = serialNumber.indexOf(QLatin1String("-"));
|
||||||
|
if (index == -1)
|
||||||
|
return SetupResult::StopWithError;
|
||||||
|
|
||||||
|
bool ok;
|
||||||
|
const int port = serialNumber.mid(index + 1).toInt(&ok);
|
||||||
|
if (!ok)
|
||||||
|
return SetupResult::StopWithError;
|
||||||
|
|
||||||
|
*currentSerialNumberStorage = serialNumber;
|
||||||
|
|
||||||
|
socket.setAddress(QHostAddress(QHostAddress::LocalHost));
|
||||||
|
socket.setPort(port);
|
||||||
|
socket.setWriteData("avd name\nexit\n");
|
||||||
|
return SetupResult::Continue;
|
||||||
|
};
|
||||||
|
const auto onSocketDone = [avdName, currentSerialNumberStorage, serialNumberStorage](const TcpSocket &socket) {
|
||||||
|
const QByteArrayList response = socket.socket()->readAll().split('\n');
|
||||||
|
// The input "avd name" might not be echoed as-is, but contain ASCII control sequences.
|
||||||
|
for (int i = response.size() - 1; i > 1; --i) {
|
||||||
|
if (!response.at(i).startsWith("OK"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const QString currentAvdName = QString::fromLatin1(response.at(i - 1)).trimmed();
|
||||||
|
if (avdName != currentAvdName)
|
||||||
|
break;
|
||||||
|
|
||||||
|
*serialNumberStorage = *currentSerialNumberStorage;
|
||||||
|
return DoneResult::Success;
|
||||||
|
}
|
||||||
|
return DoneResult::Error;
|
||||||
|
};
|
||||||
|
|
||||||
|
return Group {
|
||||||
|
outputStorage,
|
||||||
|
AndroidConfig::devicesCommandOutputRecipe(outputStorage),
|
||||||
|
For {
|
||||||
|
iterator,
|
||||||
|
parallel,
|
||||||
|
stopOnSuccess,
|
||||||
|
Group {
|
||||||
|
currentSerialNumberStorage,
|
||||||
|
TcpSocketTask(onSocketSetup, onSocketDone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
static bool waitForBooted(const QString &serialNumber, const std::optional<QFuture<void>> &future)
|
static bool waitForBooted(const QString &serialNumber, const std::optional<QFuture<void>> &future)
|
||||||
{
|
{
|
||||||
// found a serial number, now wait until it's done booting...
|
// found a serial number, now wait until it's done booting...
|
||||||
@@ -122,6 +229,72 @@ QString waitForAvd(const QString &avdName, const std::optional<QFuture<void>> &f
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ExecutableItem isAvdBootedRecipe(const Storage<QString> &serialNumberStorage)
|
||||||
|
{
|
||||||
|
const auto onSetup = [serialNumberStorage](Process &process) {
|
||||||
|
const CommandLine cmd{AndroidConfig::adbToolPath(),
|
||||||
|
{AndroidDeviceInfo::adbSelector(*serialNumberStorage),
|
||||||
|
"shell", "getprop", "init.svc.bootanim"}};
|
||||||
|
qCDebug(avdManagerLog).noquote() << "Running command (isAvdBooted):" << cmd.toUserOutput();
|
||||||
|
process.setCommand(cmd);
|
||||||
|
};
|
||||||
|
const auto onDone = [](const Process &process, DoneWith result) {
|
||||||
|
return result == DoneWith::Success && process.allOutput().trimmed() == "stopped";
|
||||||
|
};
|
||||||
|
return ProcessTask(onSetup, onDone);
|
||||||
|
}
|
||||||
|
|
||||||
|
ExecutableItem waitForAvdRecipe(const QString &avdName, const Storage<QString> &serialNumberStorage)
|
||||||
|
{
|
||||||
|
const Storage<QStringList> outputStorage;
|
||||||
|
const Storage<bool> stopStorage;
|
||||||
|
|
||||||
|
const auto onIsConnectedDone = [stopStorage, outputStorage, serialNumberStorage] {
|
||||||
|
const QString serialNumber = *serialNumberStorage;
|
||||||
|
for (const QString &line : *outputStorage) {
|
||||||
|
// skip the daemon logs
|
||||||
|
if (!line.startsWith("* daemon") && line.left(line.indexOf('\t')).trimmed() == serialNumber)
|
||||||
|
return DoneResult::Error;
|
||||||
|
}
|
||||||
|
serialNumberStorage->clear();
|
||||||
|
*stopStorage = true;
|
||||||
|
return DoneResult::Success;
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto onWaitForBootedDone = [stopStorage] { return !*stopStorage; };
|
||||||
|
|
||||||
|
return Group {
|
||||||
|
Forever {
|
||||||
|
stopOnSuccess,
|
||||||
|
serialNumberRecipe(avdName, serialNumberStorage),
|
||||||
|
TimeoutTask([](std::chrono::milliseconds &timeout) { timeout = 1s; }, DoneResult::Error)
|
||||||
|
}.withTimeout(120s),
|
||||||
|
Forever {
|
||||||
|
stopStorage,
|
||||||
|
stopOnSuccess,
|
||||||
|
isAvdBootedRecipe(serialNumberStorage),
|
||||||
|
TimeoutTask([](std::chrono::milliseconds &timeout) { timeout = 1s; }, DoneResult::Error),
|
||||||
|
Group {
|
||||||
|
outputStorage,
|
||||||
|
AndroidConfig::devicesCommandOutputRecipe(outputStorage),
|
||||||
|
onGroupDone(onIsConnectedDone, CallDoneIf::Success)
|
||||||
|
},
|
||||||
|
onGroupDone(onWaitForBootedDone)
|
||||||
|
}.withTimeout(120s)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ExecutableItem startAvdRecipe(const QString &avdName, const Storage<QString> &serialNumberStorage)
|
||||||
|
{
|
||||||
|
return Group {
|
||||||
|
If (serialNumberRecipe(avdName, serialNumberStorage) || startAvdAsyncRecipe(avdName)) >> Then {
|
||||||
|
waitForAvdRecipe(avdName, serialNumberStorage)
|
||||||
|
} >> Else {
|
||||||
|
errorItem
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
bool isAvdBooted(const QString &device)
|
bool isAvdBooted(const QString &device)
|
||||||
{
|
{
|
||||||
const CommandLine cmd{AndroidConfig::adbToolPath(), {AndroidDeviceInfo::adbSelector(device),
|
const CommandLine cmd{AndroidConfig::adbToolPath(), {AndroidDeviceInfo::adbSelector(device),
|
||||||
|
@@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
|
#include <solutions/tasking/tasktree.h>
|
||||||
|
|
||||||
namespace Android::Internal::AndroidAvdManager {
|
namespace Android::Internal::AndroidAvdManager {
|
||||||
|
|
||||||
QString startAvd(const QString &name, const std::optional<QFuture<void>> &future = {});
|
QString startAvd(const QString &name, const std::optional<QFuture<void>> &future = {});
|
||||||
@@ -14,4 +16,12 @@ QString findAvd(const QString &avdName);
|
|||||||
QString waitForAvd(const QString &avdName, const std::optional<QFuture<void>> &future = {});
|
QString waitForAvd(const QString &avdName, const std::optional<QFuture<void>> &future = {});
|
||||||
bool isAvdBooted(const QString &device);
|
bool isAvdBooted(const QString &device);
|
||||||
|
|
||||||
|
Tasking::ExecutableItem serialNumberRecipe(
|
||||||
|
const QString &avdName, const Tasking::Storage<QString> &serialNumberStorage);
|
||||||
|
Tasking::ExecutableItem startAvdAsyncRecipe(const QString &avdName);
|
||||||
|
Tasking::ExecutableItem waitForAvdRecipe(
|
||||||
|
const QString &avdName, const Tasking::Storage<QString> &serialNumberStorage);
|
||||||
|
Tasking::ExecutableItem startAvdRecipe(
|
||||||
|
const QString &avdName, const Tasking::Storage<QString> &serialNumberStorage);
|
||||||
|
|
||||||
} // namespace Android::Internal::AndroidAvdManager
|
} // namespace Android::Internal::AndroidAvdManager
|
||||||
|
@@ -61,6 +61,7 @@
|
|||||||
|
|
||||||
using namespace ProjectExplorer;
|
using namespace ProjectExplorer;
|
||||||
using namespace QtSupport;
|
using namespace QtSupport;
|
||||||
|
using namespace Tasking;
|
||||||
using namespace Utils;
|
using namespace Utils;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -687,6 +688,17 @@ QStringList devicesCommandOutput()
|
|||||||
return adbProcess.allOutput().split('\n', Qt::SkipEmptyParts).mid(1);
|
return adbProcess.allOutput().split('\n', Qt::SkipEmptyParts).mid(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ExecutableItem devicesCommandOutputRecipe(const Storage<QStringList> &outputStorage)
|
||||||
|
{
|
||||||
|
const auto onSetup = [](Process &process) { process.setCommand({adbToolPath(), {"devices"}}); };
|
||||||
|
const auto onDone = [outputStorage](const Process &process) {
|
||||||
|
// mid(1) - remove "List of devices attached" header line.
|
||||||
|
// Example output: "List of devices attached\nemulator-5554\tdevice\n\n".
|
||||||
|
*outputStorage = process.allOutput().split('\n', Qt::SkipEmptyParts).mid(1);
|
||||||
|
};
|
||||||
|
return ProcessTask(onSetup, onDone);
|
||||||
|
}
|
||||||
|
|
||||||
bool isConnected(const QString &serialNumber)
|
bool isConnected(const QString &serialNumber)
|
||||||
{
|
{
|
||||||
const QStringList lines = devicesCommandOutput();
|
const QStringList lines = devicesCommandOutput();
|
||||||
|
@@ -12,6 +12,8 @@
|
|||||||
|
|
||||||
#include <qtsupport/qtversionmanager.h>
|
#include <qtsupport/qtversionmanager.h>
|
||||||
|
|
||||||
|
#include <solutions/tasking/tasktree.h>
|
||||||
|
|
||||||
#include <utils/filepath.h>
|
#include <utils/filepath.h>
|
||||||
|
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
@@ -90,6 +92,7 @@ Utils::FilePath makePathFromNdk(const Utils::FilePath &ndkLocation);
|
|||||||
Utils::FilePath keytoolPath();
|
Utils::FilePath keytoolPath();
|
||||||
|
|
||||||
QStringList devicesCommandOutput();
|
QStringList devicesCommandOutput();
|
||||||
|
Tasking::ExecutableItem devicesCommandOutputRecipe(const Tasking::Storage<QStringList> &outputStorage);
|
||||||
|
|
||||||
QString bestNdkPlatformMatch(int target, const QtSupport::QtVersion *qtVersion);
|
QString bestNdkPlatformMatch(int target, const QtSupport::QtVersion *qtVersion);
|
||||||
|
|
||||||
|
@@ -16,6 +16,7 @@
|
|||||||
#include <coreplugin/icore.h>
|
#include <coreplugin/icore.h>
|
||||||
|
|
||||||
#include <projectexplorer/devicesupport/devicemanager.h>
|
#include <projectexplorer/devicesupport/devicemanager.h>
|
||||||
|
#include <projectexplorer/devicesupport/idevicefactory.h>
|
||||||
#include <projectexplorer/devicesupport/idevicewidget.h>
|
#include <projectexplorer/devicesupport/idevicewidget.h>
|
||||||
#include <projectexplorer/kitaspects.h>
|
#include <projectexplorer/kitaspects.h>
|
||||||
#include <projectexplorer/projectexplorerconstants.h>
|
#include <projectexplorer/projectexplorerconstants.h>
|
||||||
@@ -23,13 +24,12 @@
|
|||||||
#include <projectexplorer/projectmanager.h>
|
#include <projectexplorer/projectmanager.h>
|
||||||
#include <projectexplorer/target.h>
|
#include <projectexplorer/target.h>
|
||||||
|
|
||||||
#include <solutions/tasking/tasktree.h>
|
#include <utils/guard.h>
|
||||||
|
|
||||||
#include <utils/async.h>
|
|
||||||
#include <utils/qtcprocess.h>
|
#include <utils/qtcprocess.h>
|
||||||
#include <utils/qtcassert.h>
|
#include <utils/qtcassert.h>
|
||||||
#include <utils/url.h>
|
#include <utils/url.h>
|
||||||
|
|
||||||
|
#include <QFileSystemWatcher>
|
||||||
#include <QFormLayout>
|
#include <QFormLayout>
|
||||||
#include <QInputDialog>
|
#include <QInputDialog>
|
||||||
#include <QLoggingCategory>
|
#include <QLoggingCategory>
|
||||||
@@ -105,22 +105,6 @@ static void updateDeviceState(const IDevice::ConstPtr &device)
|
|||||||
devMgr->setDeviceState(id, IDevice::DeviceConnected);
|
devMgr->setDeviceState(id, IDevice::DeviceConnected);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void startAvd(const 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));
|
|
||||||
Utils::futureSynchronizer()->addFuture(Utils::asyncRun([name, device](QPromise<void> &promise) {
|
|
||||||
const QString serialNumber = AndroidAvdManager::startAvd(name, promise.future());
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void setEmulatorArguments(QWidget *parent)
|
static void setEmulatorArguments(QWidget *parent)
|
||||||
{
|
{
|
||||||
const QString helpUrl =
|
const QString helpUrl =
|
||||||
@@ -401,8 +385,8 @@ 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 *) {
|
||||||
startAvd(device, parent);
|
static_cast<AndroidDevice *>(device.get())->startAvd();
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -581,6 +565,24 @@ QString AndroidDevice::openGLStatus() const
|
|||||||
return openGL.isEmpty() ? Tr::tr("Unknown") : openGL;
|
return openGL.isEmpty() ? Tr::tr("Unknown") : openGL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AndroidDevice::startAvd()
|
||||||
|
{
|
||||||
|
const Storage<QString> serialNumberStorage;
|
||||||
|
|
||||||
|
const auto onDone = [this, serialNumberStorage] {
|
||||||
|
if (!serialNumberStorage->isEmpty())
|
||||||
|
DeviceManager::instance()->setDeviceState(id(), IDevice::DeviceReadyToUse);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Group root {
|
||||||
|
serialNumberStorage,
|
||||||
|
AndroidAvdManager::startAvdRecipe(avdName(), serialNumberStorage),
|
||||||
|
onGroupDone(onDone, CallDoneIf::Success)
|
||||||
|
};
|
||||||
|
|
||||||
|
m_taskTreeRunner.start(root);
|
||||||
|
}
|
||||||
|
|
||||||
IDevice::DeviceInfo AndroidDevice::deviceInformation() const
|
IDevice::DeviceInfo AndroidDevice::deviceInformation() const
|
||||||
{
|
{
|
||||||
return IDevice::DeviceInfo();
|
return IDevice::DeviceInfo();
|
||||||
|
@@ -8,17 +8,11 @@
|
|||||||
#include "androiddeviceinfo.h"
|
#include "androiddeviceinfo.h"
|
||||||
|
|
||||||
#include <projectexplorer/devicesupport/idevice.h>
|
#include <projectexplorer/devicesupport/idevice.h>
|
||||||
#include <projectexplorer/devicesupport/idevicefactory.h>
|
|
||||||
|
|
||||||
#include <solutions/tasking/tasktreerunner.h>
|
#include <solutions/tasking/tasktreerunner.h>
|
||||||
|
|
||||||
#include <utils/guard.h>
|
|
||||||
|
|
||||||
#include <QFileSystemWatcher>
|
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
|
|
||||||
namespace Utils { class Process; }
|
|
||||||
|
|
||||||
namespace Android::Internal {
|
namespace Android::Internal {
|
||||||
|
|
||||||
class AndroidDevice final : public ProjectExplorer::IDevice
|
class AndroidDevice final : public ProjectExplorer::IDevice
|
||||||
@@ -54,6 +48,8 @@ public:
|
|||||||
QString sdcardSize() const;
|
QString sdcardSize() const;
|
||||||
QString openGLStatus() const;
|
QString openGLStatus() const;
|
||||||
|
|
||||||
|
void startAvd();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void fromMap(const Utils::Store &map) final;
|
void fromMap(const Utils::Store &map) final;
|
||||||
|
|
||||||
@@ -68,6 +64,7 @@ private:
|
|||||||
void initAvdSettings();
|
void initAvdSettings();
|
||||||
|
|
||||||
std::unique_ptr<QSettings> m_avdSettings;
|
std::unique_ptr<QSettings> m_avdSettings;
|
||||||
|
Tasking::TaskTreeRunner m_taskTreeRunner;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace AndroidDeviceManager {
|
namespace AndroidDeviceManager {
|
||||||
|
Reference in New Issue
Block a user