From 20c0d93f7d912eb83f40e91530b770782c617450 Mon Sep 17 00:00:00 2001 From: Assam Boudjelthia Date: Sun, 6 Mar 2022 00:06:27 +0200 Subject: [PATCH] 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 --- src/plugins/android/androiddevice.cpp | 107 +++++++++++++++++++++++--- src/plugins/android/androiddevice.h | 3 +- 2 files changed, 97 insertions(+), 13 deletions(-) diff --git a/src/plugins/android/androiddevice.cpp b/src/plugins/android/androiddevice.cpp index 8609fc7a89f..6e6b83eef7e 100644 --- a/src/plugins/android/androiddevice.cpp +++ b/src/plugins/android/androiddevice.cpp @@ -67,6 +67,10 @@ static Q_LOGGING_CATEGORY(androidDeviceLog, "qtc.android.androiddevice", QtWarni namespace Android { 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 { public: @@ -74,7 +78,9 @@ public: void updateDeviceFromUi() final {} 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 infoDialog(const QString &msg, 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"); } -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()); box.QDialog::setWindowTitle(dialogTitle()); - box.setText(error); - box.setIcon(QMessageBox::Critical); + box.setText(msg); + box.setIcon(icon); box.setWindowFlag(Qt::WindowTitleHint); 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) { QMessageBox box(parent ? parent : Core::ICore::dialogParent()); @@ -168,19 +184,19 @@ AndroidDevice::AndroidDevice() Q_UNUSED(parent) AndroidDeviceManager::instance()->updateDeviceState(device); }}); - - addEmulatorActionsIfNotFound(); } -void AndroidDevice::addEmulatorActionsIfNotFound() +void AndroidDevice::addActionsIfNotFound() { static const QString startAvdAction = tr("Start AVD"); static const QString eraseAvdAction = tr("Erase AVD"); static const QString avdArgumentsAction = tr("AVD Arguments"); + static const QString setupWifi = tr("Setup Wi-Fi"); bool hasStartAction = false; bool hasEraseAction = false; bool hasAvdArgumentsAction = false; + bool hasSetupWifi = false; for (const DeviceAction &item : deviceActions()) { if (item.display == startAvdAction) @@ -189,6 +205,8 @@ void AndroidDevice::addEmulatorActionsIfNotFound() hasEraseAction = true; else if (item.display == avdArgumentsAction) hasAvdArgumentsAction = true; + else if (item.display == setupWifi) + hasSetupWifi = true; } if (machineType() == Emulator) { @@ -210,6 +228,12 @@ void AndroidDevice::addEmulatorActionsIfNotFound() 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); 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. - addEmulatorActionsIfNotFound(); + addActionsIfNotFound(); } 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(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() { const QPair result = m_removeAvdFutureWatcher.result(); @@ -723,9 +807,8 @@ void AndroidDeviceManager::HandleDevicesListChange(const QString &serialNumber) QString displayName = AndroidConfigurations::currentConfig().getProductModel(serial); // Check if the device is connected via WiFi. A sample serial of such devices can be // like: "192.168.1.190:5555" - const QRegularExpression wifiSerialRegExp( - QLatin1String("(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}):(\\d{1,5})")); - if (wifiSerialRegExp.match(serial).hasMatch()) + static const auto ipRegex = QRegularExpression(ipRegexStr + QStringLiteral(":(\\d{1,5})")); + if (ipRegex.match(serial).hasMatch()) displayName += QLatin1String(" (WiFi)"); if (IDevice::ConstPtr dev = devMgr->find(id)) { diff --git a/src/plugins/android/androiddevice.h b/src/plugins/android/androiddevice.h index d10663e1590..e9283df7efe 100644 --- a/src/plugins/android/androiddevice.h +++ b/src/plugins/android/androiddevice.h @@ -82,7 +82,7 @@ protected: void fromMap(const QVariantMap &map) final; private: - void addEmulatorActionsIfNotFound(); + void addActionsIfNotFound(); ProjectExplorer::IDevice::DeviceInfo deviceInformation() const override; ProjectExplorer::IDeviceWidget *createWidget() override; bool canAutoDetectPorts() const override; @@ -115,6 +115,7 @@ public: void startAvd(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);