/**************************************************************************** ** ** Copyright (C) 2021 The Qt Company Ltd. ** Copyright (C) 2016 BogDan Vatra ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "androiddevice.h" #include "androidavdmanager.h" #include "androidconfigurations.h" #include "androidconstants.h" #include "androidmanager.h" #include "androidsignaloperation.h" #include #include #include #include #include #include #include using namespace ProjectExplorer; 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 { class AndroidDeviceWidget : public IDeviceWidget { public: AndroidDeviceWidget(const ProjectExplorer::IDevice::Ptr &device); void updateDeviceFromUi() final {} }; AndroidDeviceWidget::AndroidDeviceWidget(const IDevice::Ptr &device) : IDeviceWidget(device) { const auto dev = qSharedPointerCast(device); const auto formLayout = new QFormLayout(this); formLayout->setFormAlignment(Qt::AlignLeft); formLayout->setContentsMargins(0, 0, 0, 0); setLayout(formLayout); formLayout->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow); if (!dev->isValid()) return; formLayout->addRow(AndroidDevice::tr("Device name:"), new QLabel(dev->displayName())); formLayout->addRow(AndroidDevice::tr("Device type:"), new QLabel(dev->deviceTypeName())); const QString serialNumber = dev->serialNumber(); const QString printableSerialNumber = serialNumber.isEmpty() ? tr("Unknown") : serialNumber; formLayout->addRow(AndroidDevice::tr("Serial number:"), new QLabel(printableSerialNumber)); const QString abis = dev->supportedAbis().join(", "); formLayout->addRow(AndroidDevice::tr("CPU architecture:"), new QLabel(abis)); const auto osString = QString("%1 (SDK %2)").arg(dev->androidVersion()).arg(dev->sdkLevel()); formLayout->addRow(AndroidDevice::tr("OS version:"), new QLabel(osString)); if (dev->machineType() == IDevice::Emulator) { const QString targetName = dev->androidTargetName(); formLayout->addRow(AndroidDevice::tr("Android target flavor:"), new QLabel(targetName)); formLayout->addRow(AndroidDevice::tr("SD card size:"), new QLabel(dev->sdcardSize())); formLayout->addRow(AndroidDevice::tr("Skin type:"), new QLabel(dev->skinName())); const QString openGlStatus = dev->openGlStatusString(); formLayout->addRow(AndroidDevice::tr("OpenGL status:"), new QLabel(openGlStatus)); } } AndroidDevice::AndroidDevice() { setupId(IDevice::AutoDetected, Constants::ANDROID_DEVICE_ID); setType(Constants::ANDROID_DEVICE_TYPE); setDefaultDisplayName(tr("Run on Android")); setDisplayType(tr("Android")); setMachineType(IDevice::Hardware); setOsType(Utils::OsTypeOtherUnix); setDeviceState(DeviceConnected); addDeviceAction({tr("Refresh"), [](const IDevice::Ptr &device, QWidget *parent) { AndroidDeviceManager::instance()->updateDevicesListOnce(); }}); } IDevice::Ptr AndroidDevice::create() { return IDevice::Ptr(new AndroidDevice); } AndroidDeviceInfo AndroidDevice::androidDeviceInfoFromIDevice(const IDevice *dev) { AndroidDeviceInfo info; AndroidDeviceInfo::State state; if (dev->deviceState() == IDevice::DeviceReadyToUse) state = AndroidDeviceInfo::OkState; else if (dev->deviceState() == IDevice::DeviceDisconnected) state = AndroidDeviceInfo::OfflineState; else if (dev->deviceState() == IDevice::DeviceConnected) state = AndroidDeviceInfo::UnAuthorizedState; info.state = state; info.avdname = dev->extraData(Constants::AndroidAvdName).toString(); info.serialNumber = dev->extraData(Constants::AndroidSerialNumber).toString(); info.cpuAbi = dev->extraData(Constants::AndroidCpuAbi).toStringList(); info.avdTarget = dev->extraData(Constants::AndroidAvdTarget).toString(); info.avdDevice = dev->extraData(Constants::AndroidAvdDevice).toString(); info.avdSkin = dev->extraData(Constants::AndroidAvdSkin).toString(); info.avdSdcardSize = dev->extraData(Constants::AndroidAvdSdcard).toString(); info.sdk = dev->extraData(Constants::AndroidSdk).toInt(); info.type = (dev->machineType() == ProjectExplorer::IDevice::Hardware ? AndroidDeviceInfo::Hardware : AndroidDeviceInfo::Emulator); return info; } void AndroidDevice::setAndroidDeviceInfoExtras(IDevice *dev, const AndroidDeviceInfo &info) { dev->setMachineType(info.type == AndroidDeviceInfo::Hardware ? ProjectExplorer::IDevice::Hardware : ProjectExplorer::IDevice::Emulator); dev->setDeviceState(deviceStateFromInfo(info.state)); dev->setExtraData(Constants::AndroidAvdName, info.avdname); dev->setExtraData(Constants::AndroidSerialNumber, info.serialNumber); dev->setExtraData(Constants::AndroidCpuAbi, info.cpuAbi); dev->setExtraData(Constants::AndroidAvdTarget, info.avdTarget); dev->setExtraData(Constants::AndroidAvdDevice, info.avdDevice); dev->setExtraData(Constants::AndroidAvdSkin, info.avdSkin); dev->setExtraData(Constants::AndroidAvdSdcard, info.avdSdcardSize); dev->setExtraData(Constants::AndroidSdk, info.sdk); } QString AndroidDevice::displayNameFromInfo(const AndroidDeviceInfo &info) { return info.type == AndroidDeviceInfo::Hardware ? AndroidConfigurations::currentConfig().getProductModel(info.serialNumber) : info.avdname; } Utils::Id AndroidDevice::idFromDeviceInfo(const AndroidDeviceInfo &info) { const QString id = (info.type == AndroidDeviceInfo::Hardware ? info.serialNumber : info.avdname); return Utils::Id(Constants::ANDROID_DEVICE_ID).withSuffix(':' + id); } Utils::Id AndroidDevice::idFromAvdInfo(const CreateAvdInfo &info) { return Utils::Id(Constants::ANDROID_DEVICE_ID).withSuffix(':' + info.name); } IDevice::DeviceState AndroidDevice::deviceStateFromInfo(AndroidDeviceInfo::State state) { if (state == AndroidDeviceInfo::OkState) return IDevice::DeviceReadyToUse; if (state == AndroidDeviceInfo::OfflineState) return IDevice::DeviceDisconnected; return IDevice::DeviceConnected; } QStringList AndroidDevice::supportedAbis() const { return extraData(Constants::AndroidCpuAbi).toStringList(); } bool AndroidDevice::canSupportAbis(const QStringList &abis) const { // If the list is empty, no valid decision can be made, this means something is wrong // somewhere, but let's not stop deployment. QTC_ASSERT(!abis.isEmpty(), return true); const QStringList ourAbis = supportedAbis(); QTC_ASSERT(!ourAbis.isEmpty(), return false); for (const QString &abi : abis) if (ourAbis.contains(abi)) return true; // it's enough if only one abi match is found // If no exact match is found, let's take ABI backward compatibility into account // https://developer.android.com/ndk/guides/abis#android-platform-abi-support // arm64 usually can run {arm, armv7}, x86 can support {arm, armv7}, and 64-bit devices // can support their 32-bit variants. using namespace ProjectExplorer::Constants; const bool isTheirsArm = abis.contains(ANDROID_ABI_ARMEABI_V7A) || abis.contains(ANDROID_ABI_ARMEABI_V7A); // The primary ABI at the first index const bool oursSupportsArm = ourAbis.first() == ANDROID_ABI_ARM64_V8A || ourAbis.first() == ANDROID_ABI_X86; // arm64 and x86 can run armv7 and arm if (isTheirsArm && oursSupportsArm) return true; // x64 can run x86 if (ourAbis.first() == ANDROID_ABI_X86_64 && abis.contains(ANDROID_ABI_X86)) return true; return false; } bool AndroidDevice::canHandleDeployments() const { // If hardware and disconned, it wouldn't be possilbe to start it, unlike an emulator if (machineType() == Hardware && deviceState() == DeviceDisconnected) return false; return true; } bool AndroidDevice::isValid() const { return !serialNumber().isEmpty() || !avdName().isEmpty(); } QString AndroidDevice::serialNumber() const { return extraData(Constants::AndroidSerialNumber).toString(); } QString AndroidDevice::avdName() const { return extraData(Constants::AndroidAvdName).toString(); } int AndroidDevice::sdkLevel() const { return extraData(Constants::AndroidSdk).toInt(); } QString AndroidDevice::androidVersion() const { return AndroidManager::androidNameForApiLevel(sdkLevel()); } QString AndroidDevice::deviceTypeName() const { if (machineType() == Emulator) return tr("Emulator for ") + extraData(Constants::AndroidAvdDevice).toString(); return tr("Physical device"); } QString AndroidDevice::skinName() const { const QString skin = extraData(Constants::AndroidAvdSkin).toString(); return skin.isEmpty() ? tr("None") : skin; } QString AndroidDevice::androidTargetName() const { const QString target = extraData(Constants::AndroidAvdTarget).toString(); return target.isEmpty() ? tr("Unknown") : target; } QString AndroidDevice::sdcardSize() const { const QString size = extraData(Constants::AndroidAvdSdcard).toString(); return size.isEmpty() ? tr("Unknown") : size; } AndroidConfig::OpenGl AndroidDevice::openGlStatus() const { return AndroidConfigurations::currentConfig().getOpenGLEnabled(displayName()); } QString AndroidDevice::openGlStatusString() const { const AndroidConfig::OpenGl glStatus = AndroidConfigurations::currentConfig() .getOpenGLEnabled(displayName()); switch (glStatus) { case (AndroidConfig::OpenGl::Enabled): return tr("Enabled"); case (AndroidConfig::OpenGl::Disabled): return tr("Disabled"); case (AndroidConfig::OpenGl::Unknown): return tr("Unknown"); } return tr("Unknown"); } IDevice::DeviceInfo AndroidDevice::deviceInformation() const { return IDevice::DeviceInfo(); } IDeviceWidget *AndroidDevice::createWidget() { return new AndroidDeviceWidget(sharedFromThis()); } bool AndroidDevice::canAutoDetectPorts() const { return true; } DeviceProcessSignalOperation::Ptr AndroidDevice::signalOperation() const { return DeviceProcessSignalOperation::Ptr(new AndroidSignalOperation()); } QUrl AndroidDevice::toolControlChannel(const ControlChannelHint &) const { QUrl url; url.setScheme(Utils::urlTcpScheme()); url.setHost("localhost"); return url; } void AndroidDeviceManager::updateDevicesList() { connect(&m_devicesUpdaterTimer, &QTimer::timeout, this, [this]() { updateDevicesListOnce(); }); updateDevicesListOnce(); m_devicesUpdaterTimer.start(deviceUpdaterMsInterval); } void AndroidDeviceManager::updateDevicesListOnce() { if (!m_avdsFutureWatcher.isRunning() && m_androidConfig.adbToolPath().exists()) { m_avdsFutureWatcher.setFuture((new AndroidAvdManager)->avdList()); m_devicesFutureWatcher.setFuture(Utils::runAsync([this]() { return m_androidConfig.connectedDevices(); })); } } void AndroidDeviceManager::setupDevicesWatcher() { // 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); updateDevicesList(); } void AndroidDeviceManager::devicesListUpdated() { QVector 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 startedAvds = Utils::transform(connectedDevicesInfos, &AndroidDeviceInfo::avdname); for (const AndroidDeviceInfo &dev : devices) if (!startedAvds.contains(dev.avdname)) connectedDevicesInfos << dev; DeviceManager *const devMgr = DeviceManager::instance(); QVector existingDevs; QVector 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); } } for (auto item : connectedDevicesInfos) { 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 = AndroidDevice::deviceStateFromInfo(item.state); if (dev->deviceState() != newState) { 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()); } } AndroidDevice *newDev = new AndroidDevice(); newDev->setupId(IDevice::AutoDetected, deviceId); newDev->setDisplayName(displayName); AndroidDevice::setAndroidDeviceInfoExtras(newDev, item); qCDebug(androidDeviceLog, "Registering new Android device id \"%s\".", newDev->id().toString().toUtf8().data()); const IDevice::ConstPtr constNewDev = IDevice::ConstPtr(newDev); devMgr->addDevice(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); } } } AndroidDeviceManager *AndroidDeviceManager::instance() { static AndroidDeviceManager obj; return &obj; } AndroidDeviceManager::AndroidDeviceManager(QObject *parent) : QObject(parent), m_androidConfig(AndroidConfigurations::currentConfig()) { connect(qApp, &QCoreApplication::aboutToQuit, this, [this]() { m_devicesUpdaterTimer.stop(); m_avdsFutureWatcher.waitForFinished(); m_devicesFutureWatcher.waitForFinished(); }); } // Factory AndroidDeviceFactory::AndroidDeviceFactory() : ProjectExplorer::IDeviceFactory(Constants::ANDROID_DEVICE_TYPE) { setDisplayName(AndroidDevice::tr("Android Device")); setCombinedIcon(":/android/images/androiddevicesmall.png", ":/android/images/androiddevice.png"); setConstructionFunction(&AndroidDevice::create); } } // namespace Internal } // namespace Android