Android: add option to connect physical device over wifi

Android's ADB allows connections over wifi, firstly, a port needs
to be opened from the device, then adb commands can be used to
connect the host to the device over an ip and port. Afer that,
a device can be disconnected from USB and the wifi connection
could be used to do all jobs that were done over USB.

The setup operation require the device to be connected via USB.
This can replace the manual process of dealing with ADB commands
to prepare the connection.

The "Setup Wifi" action is only added to Hardware USB devices,
and not AVDs nor physical devices that are already connected over
WiFi.

Change-Id: I37897a528b45cdeee2764071ec45ec1b3316cdbb
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
Assam Boudjelthia
2022-03-06 00:06:27 +02:00
parent b165e5f0ad
commit 20c0d93f7d
2 changed files with 97 additions and 13 deletions

View File

@@ -67,6 +67,10 @@ static Q_LOGGING_CATEGORY(androidDeviceLog, "qtc.android.androiddevice", QtWarni
namespace Android { namespace Android {
namespace Internal { namespace Internal {
static constexpr char ipRegexStr[] = "(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})";
static const QRegularExpression ipRegex = QRegularExpression(ipRegexStr);
static constexpr char wifiDevicePort[] = "5555";
class AndroidDeviceWidget : public IDeviceWidget class AndroidDeviceWidget : public IDeviceWidget
{ {
public: public:
@@ -74,7 +78,9 @@ public:
void updateDeviceFromUi() final {} void updateDeviceFromUi() final {}
static QString dialogTitle(); static QString dialogTitle();
static bool messageDialog(const QString &msg, QMessageBox::Icon icon, QWidget *parent = nullptr);
static bool criticalDialog(const QString &error, QWidget *parent = nullptr); static bool criticalDialog(const QString &error, QWidget *parent = nullptr);
static bool infoDialog(const QString &msg, QWidget *parent = nullptr);
static bool questionDialog(const QString &question, QWidget *parent = nullptr); static bool questionDialog(const QString &question, QWidget *parent = nullptr);
}; };
@@ -127,17 +133,27 @@ QString AndroidDeviceWidget::dialogTitle()
return AndroidDevice::tr("Android Device Manager"); return AndroidDevice::tr("Android Device Manager");
} }
bool AndroidDeviceWidget::criticalDialog(const QString &error, QWidget *parent) bool AndroidDeviceWidget::messageDialog(const QString &msg, QMessageBox::Icon icon, QWidget *parent)
{ {
qCDebug(androidDeviceLog) << error; qCDebug(androidDeviceLog) << msg;
QMessageBox box(parent ? parent : Core::ICore::dialogParent()); QMessageBox box(parent ? parent : Core::ICore::dialogParent());
box.QDialog::setWindowTitle(dialogTitle()); box.QDialog::setWindowTitle(dialogTitle());
box.setText(error); box.setText(msg);
box.setIcon(QMessageBox::Critical); box.setIcon(icon);
box.setWindowFlag(Qt::WindowTitleHint); box.setWindowFlag(Qt::WindowTitleHint);
return box.exec(); return box.exec();
} }
bool AndroidDeviceWidget::criticalDialog(const QString &error, QWidget *parent)
{
return messageDialog(error, QMessageBox::Critical, parent);
}
bool AndroidDeviceWidget::infoDialog(const QString &message, QWidget *parent)
{
return messageDialog(message, QMessageBox::Information, parent);
}
bool AndroidDeviceWidget::questionDialog(const QString &question, QWidget *parent) bool AndroidDeviceWidget::questionDialog(const QString &question, QWidget *parent)
{ {
QMessageBox box(parent ? parent : Core::ICore::dialogParent()); QMessageBox box(parent ? parent : Core::ICore::dialogParent());
@@ -168,19 +184,19 @@ AndroidDevice::AndroidDevice()
Q_UNUSED(parent) Q_UNUSED(parent)
AndroidDeviceManager::instance()->updateDeviceState(device); AndroidDeviceManager::instance()->updateDeviceState(device);
}}); }});
addEmulatorActionsIfNotFound();
} }
void AndroidDevice::addEmulatorActionsIfNotFound() void AndroidDevice::addActionsIfNotFound()
{ {
static const QString startAvdAction = tr("Start AVD"); static const QString startAvdAction = tr("Start AVD");
static const QString eraseAvdAction = tr("Erase AVD"); static const QString eraseAvdAction = tr("Erase AVD");
static const QString avdArgumentsAction = tr("AVD Arguments"); static const QString avdArgumentsAction = tr("AVD Arguments");
static const QString setupWifi = tr("Setup Wi-Fi");
bool hasStartAction = false; bool hasStartAction = false;
bool hasEraseAction = false; bool hasEraseAction = false;
bool hasAvdArgumentsAction = false; bool hasAvdArgumentsAction = false;
bool hasSetupWifi = false;
for (const DeviceAction &item : deviceActions()) { for (const DeviceAction &item : deviceActions()) {
if (item.display == startAvdAction) if (item.display == startAvdAction)
@@ -189,6 +205,8 @@ void AndroidDevice::addEmulatorActionsIfNotFound()
hasEraseAction = true; hasEraseAction = true;
else if (item.display == avdArgumentsAction) else if (item.display == avdArgumentsAction)
hasAvdArgumentsAction = true; hasAvdArgumentsAction = true;
else if (item.display == setupWifi)
hasSetupWifi = true;
} }
if (machineType() == Emulator) { if (machineType() == Emulator) {
@@ -210,6 +228,12 @@ void AndroidDevice::addEmulatorActionsIfNotFound()
AndroidDeviceManager::instance()->setEmulatorArguments(parent); AndroidDeviceManager::instance()->setEmulatorArguments(parent);
}}); }});
} }
} else if (machineType() == Hardware && !ipRegex.match(id().toString()).hasMatch()) {
if (!hasSetupWifi) {
addDeviceAction({setupWifi, [](const IDevice::Ptr &device, QWidget *parent) {
AndroidDeviceManager::instance()->setupWifiForDevice(device, parent);
}});
}
} }
} }
@@ -217,9 +241,9 @@ void AndroidDevice::fromMap(const QVariantMap &map)
{ {
IDevice::fromMap(map); IDevice::fromMap(map);
initAvdSettings(); initAvdSettings();
// Add Actions for Emulator is not added already. // Add Actions for Emulator and hardware if not added already.
// This is needed because actions for Emulators and physical devices are not the same. // This is needed because actions for Emulators and physical devices are not the same.
addEmulatorActionsIfNotFound(); addActionsIfNotFound();
} }
IDevice::Ptr AndroidDevice::create() IDevice::Ptr AndroidDevice::create()
@@ -488,6 +512,66 @@ void AndroidDeviceManager::eraseAvd(const IDevice::Ptr &device, QWidget *parent)
})); }));
} }
void AndroidDeviceManager::setupWifiForDevice(const IDevice::Ptr &device, QWidget *parent)
{
if (device->deviceState() != IDevice::DeviceReadyToUse) {
AndroidDeviceWidget::infoDialog(
AndroidDevice::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.data());
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(
AndroidDevice::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(
AndroidDevice::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(
AndroidDevice::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(
AndroidDevice::tr("Connecting to to the device IP \"%1\" failed.").arg(ip),
parent);
return;
}
});
}
void AndroidDeviceManager::handleAvdRemoved() void AndroidDeviceManager::handleAvdRemoved()
{ {
const QPair<IDevice::ConstPtr, bool> result = m_removeAvdFutureWatcher.result(); const QPair<IDevice::ConstPtr, bool> result = m_removeAvdFutureWatcher.result();
@@ -723,9 +807,8 @@ void AndroidDeviceManager::HandleDevicesListChange(const QString &serialNumber)
QString displayName = AndroidConfigurations::currentConfig().getProductModel(serial); QString displayName = AndroidConfigurations::currentConfig().getProductModel(serial);
// Check if the device is connected via WiFi. A sample serial of such devices can be // Check if the device is connected via WiFi. A sample serial of such devices can be
// like: "192.168.1.190:5555" // like: "192.168.1.190:5555"
const QRegularExpression wifiSerialRegExp( static const auto ipRegex = QRegularExpression(ipRegexStr + QStringLiteral(":(\\d{1,5})"));
QLatin1String("(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}):(\\d{1,5})")); if (ipRegex.match(serial).hasMatch())
if (wifiSerialRegExp.match(serial).hasMatch())
displayName += QLatin1String(" (WiFi)"); displayName += QLatin1String(" (WiFi)");
if (IDevice::ConstPtr dev = devMgr->find(id)) { if (IDevice::ConstPtr dev = devMgr->find(id)) {

View File

@@ -82,7 +82,7 @@ protected:
void fromMap(const QVariantMap &map) final; void fromMap(const QVariantMap &map) final;
private: private:
void addEmulatorActionsIfNotFound(); void addActionsIfNotFound();
ProjectExplorer::IDevice::DeviceInfo deviceInformation() const override; ProjectExplorer::IDevice::DeviceInfo deviceInformation() const override;
ProjectExplorer::IDeviceWidget *createWidget() override; ProjectExplorer::IDeviceWidget *createWidget() override;
bool canAutoDetectPorts() const override; bool canAutoDetectPorts() const override;
@@ -115,6 +115,7 @@ public:
void startAvd(const ProjectExplorer::IDevice::Ptr &device, QWidget *parent = nullptr); 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); void setEmulatorArguments(QWidget *parent = nullptr);