/**************************************************************************** ** ** 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 "androidconfigurations.h" #include "androidconstants.h" #include "androidtoolchain.h" #include "androiddevice.h" #include "androidmanager.h" #include "androidqtversion.h" #include "androiddevicedialog.h" #include "androidsdkmanager.h" #include "androidtoolmanager.h" #include "avddialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace QtSupport; using namespace ProjectExplorer; using namespace Utils; namespace { static Q_LOGGING_CATEGORY(avdConfigLog, "qtc.android.androidconfig", QtWarningMsg) } namespace Android { using namespace Internal; const char JsonFilePath[] = "/android/sdk_definitions.json"; const char SdkToolsUrlKey[] = "sdk_tools_url"; const char CommonKey[] = "common"; const char SdkEssentialPkgsKey[] = "sdk_essential_packages"; const char VersionsKey[] = "versions"; const char NdkPathKey[] = "ndk_path"; const char SpecificQtVersionsKey[] = "specific_qt_versions"; const char DefaultVersionKey[] = "default"; const char LinuxOsKey[] = "linux"; const char WindowsOsKey[] = "windows"; const char macOsKey[] = "mac"; namespace { const char jdkSettingsPath[] = "HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit"; const QLatin1String SettingsGroup("AndroidConfigurations"); const QLatin1String SDKLocationKey("SDKLocation"); const QLatin1String CustomNdkLocationsKey("CustomNdkLocations"); const QLatin1String SdkFullyConfiguredKey("AllEssentialsInstalled"); const QLatin1String SDKManagerToolArgsKey("SDKManagerToolArgs"); const QLatin1String OpenJDKLocationKey("OpenJDKLocation"); const QLatin1String OpenSslPriLocationKey("OpenSSLPriLocation"); const QLatin1String KeystoreLocationKey("KeystoreLocation"); const QLatin1String AutomaticKitCreationKey("AutomatiKitCreation"); const QLatin1String PartitionSizeKey("PartitionSize"); const QLatin1String ToolchainHostKey("ToolchainHost"); const QLatin1String ArmToolchainPrefix("arm-linux-androideabi"); const QLatin1String X86ToolchainPrefix("x86"); const QLatin1String AArch64ToolchainPrefix("aarch64-linux-android"); const QLatin1String X86_64ToolchainPrefix("x86_64"); const QLatin1String ArmToolsPrefix("arm-linux-androideabi"); const QLatin1String X86ToolsPrefix("i686-linux-android"); const QLatin1String AArch64ToolsPrefix("aarch64-linux-android"); const QLatin1String X86_64ToolsPrefix("x86_64-linux-android"); const QLatin1String ArmToolsDisplayName("arm"); const QLatin1String X86ToolsDisplayName("i686"); const QLatin1String AArch64ToolsDisplayName("aarch64"); const QLatin1String X86_64ToolsDisplayName("x86_64"); const QLatin1String Unknown("unknown"); const QLatin1String keytoolName("keytool"); const QLatin1String changeTimeStamp("ChangeTimeStamp"); const QLatin1String sdkToolsVersionKey("Pkg.Revision"); const QLatin1String ndkRevisionKey("Pkg.Revision"); static QString sdkSettingsFileName() { return Core::ICore::installerResourcePath() + "/android.xml"; } static bool is32BitUserSpace() { // Do the exact same check as android's emulator is doing: if (HostOsInfo::isLinuxHost()) { if (QSysInfo::WordSize == 32 ) { Environment env = Environment::systemEnvironment(); QString executable = env.searchInPath(QLatin1String("file")).toString(); QString shell = env.value(QLatin1String("SHELL")); if (executable.isEmpty() || shell.isEmpty()) return true; // we can't detect, but creator is 32bit so assume 32bit SynchronousProcess proc; proc.setProcessChannelMode(QProcess::MergedChannels); proc.setTimeoutS(30); SynchronousProcessResponse response = proc.runBlocking({executable, {shell}}); if (response.result != SynchronousProcessResponse::Finished) return true; return !response.allOutput().contains("x86-64"); } } return false; } } ////////////////////////////////// // AndroidConfig ////////////////////////////////// Abi AndroidConfig::abiForToolChainPrefix(const QString &toolchainPrefix) { Abi::Architecture arch = Abi::UnknownArchitecture; unsigned char wordWidth = 32; if (toolchainPrefix == ArmToolchainPrefix) { arch = Abi::ArmArchitecture; } else if (toolchainPrefix == X86ToolchainPrefix) { arch = Abi::X86Architecture; } else if (toolchainPrefix == AArch64ToolchainPrefix) { arch = Abi::ArmArchitecture; wordWidth = 64; } else if (toolchainPrefix == X86_64ToolchainPrefix) { arch = Abi::X86Architecture; wordWidth = 64; } return Abi(arch, Abi::LinuxOS, Abi::AndroidLinuxFlavor, Abi::ElfFormat, wordWidth); } QLatin1String AndroidConfig::toolchainPrefix(const Abi &abi) { switch (abi.architecture()) { case Abi::ArmArchitecture: if (abi.wordWidth() == 64) return AArch64ToolchainPrefix; return ArmToolchainPrefix; case Abi::X86Architecture: if (abi.wordWidth() == 64) return X86_64ToolchainPrefix; return X86ToolchainPrefix; default: return Unknown; } } QLatin1String AndroidConfig::toolsPrefix(const Abi &abi) { switch (abi.architecture()) { case Abi::ArmArchitecture: if (abi.wordWidth() == 64) return AArch64ToolsPrefix; return ArmToolsPrefix; case Abi::X86Architecture: if (abi.wordWidth() == 64) return X86_64ToolsPrefix; return X86ToolsPrefix; default: return Unknown; } } QLatin1String AndroidConfig::displayName(const Abi &abi) { switch (abi.architecture()) { case Abi::ArmArchitecture: if (abi.wordWidth() == 64) return AArch64ToolsDisplayName; return ArmToolsDisplayName; case Abi::X86Architecture: if (abi.wordWidth() == 64) return X86_64ToolsDisplayName; return X86ToolsDisplayName; default: return Unknown; } } void AndroidConfig::load(const QSettings &settings) { // user settings m_partitionSize = settings.value(PartitionSizeKey, 1024).toInt(); m_sdkLocation = FilePath::fromString(settings.value(SDKLocationKey).toString()); m_customNdkList = settings.value(CustomNdkLocationsKey).toStringList(); m_sdkManagerToolArgs = settings.value(SDKManagerToolArgsKey).toStringList(); m_openJDKLocation = FilePath::fromString(settings.value(OpenJDKLocationKey).toString()); m_openSslLocation = FilePath::fromString(settings.value(OpenSslPriLocationKey).toString()); m_keystoreLocation = FilePath::fromString(settings.value(KeystoreLocationKey).toString()); m_automaticKitCreation = settings.value(AutomaticKitCreationKey, true).toBool(); m_sdkFullyConfigured = settings.value(SdkFullyConfiguredKey, false).toBool(); PersistentSettingsReader reader; if (reader.load(FilePath::fromString(sdkSettingsFileName())) && settings.value(changeTimeStamp).toInt() != QFileInfo(sdkSettingsFileName()).lastModified().toMSecsSinceEpoch() / 1000) { // persisten settings m_sdkLocation = FilePath::fromString(reader.restoreValue(SDKLocationKey, m_sdkLocation.toString()).toString()); m_customNdkList = reader.restoreValue(CustomNdkLocationsKey).toStringList(); m_sdkManagerToolArgs = reader.restoreValue(SDKManagerToolArgsKey, m_sdkManagerToolArgs).toStringList(); m_openJDKLocation = FilePath::fromString(reader.restoreValue(OpenJDKLocationKey, m_openJDKLocation.toString()).toString()); m_openSslLocation = FilePath::fromString(reader.restoreValue(OpenSslPriLocationKey, m_openSslLocation.toString()).toString()); m_automaticKitCreation = reader.restoreValue(AutomaticKitCreationKey, m_automaticKitCreation).toBool(); m_sdkFullyConfigured = reader.restoreValue(SdkFullyConfiguredKey, m_sdkFullyConfigured).toBool(); // persistent settings } m_customNdkList.removeAll(""); parseDependenciesJson(); } void AndroidConfig::save(QSettings &settings) const { QFileInfo fileInfo(sdkSettingsFileName()); if (fileInfo.exists()) settings.setValue(changeTimeStamp, fileInfo.lastModified().toMSecsSinceEpoch() / 1000); // user settings settings.setValue(SDKLocationKey, m_sdkLocation.toString()); settings.setValue(CustomNdkLocationsKey, m_customNdkList); settings.setValue(SDKManagerToolArgsKey, m_sdkManagerToolArgs); settings.setValue(OpenJDKLocationKey, m_openJDKLocation.toString()); settings.setValue(KeystoreLocationKey, m_keystoreLocation.toString()); settings.setValue(OpenSslPriLocationKey, m_openSslLocation.toString()); settings.setValue(PartitionSizeKey, m_partitionSize); settings.setValue(AutomaticKitCreationKey, m_automaticKitCreation); settings.setValue(SdkFullyConfiguredKey, m_sdkFullyConfigured); } void AndroidConfig::parseDependenciesJson() { QString sdkConfigUserFile(Core::ICore::userResourcePath() + JsonFilePath); QString sdkConfigFile(Core::ICore::resourcePath() + JsonFilePath); if (!QFile::exists(sdkConfigUserFile)) { QDir(QFileInfo(sdkConfigUserFile).absolutePath()).mkpath("."); QFile::copy(sdkConfigFile, sdkConfigUserFile); } if (QFileInfo(sdkConfigFile).lastModified() > QFileInfo(sdkConfigUserFile).lastModified()) { QFile::remove(sdkConfigUserFile + ".old"); QFile::rename(sdkConfigUserFile, sdkConfigUserFile + ".old"); QFile::copy(sdkConfigFile, sdkConfigUserFile); } QFile jsonFile(sdkConfigUserFile); if (!jsonFile.open(QIODevice::ReadOnly)) { qCDebug(avdConfigLog, "Couldn't open JSON config file %s.", qPrintable(jsonFile.fileName())); return; } QJsonObject jsonObject = QJsonDocument::fromJson(jsonFile.readAll()).object(); if (jsonObject.contains(CommonKey) && jsonObject[CommonKey].isObject()) { QJsonObject commonObject = jsonObject[CommonKey].toObject(); // Parse SDK Tools URL if (commonObject.contains(SdkToolsUrlKey) && commonObject[SdkToolsUrlKey].isObject()) { QJsonObject sdkToolsObj(commonObject[SdkToolsUrlKey].toObject()); if (Utils::HostOsInfo::isMacHost()) { m_sdkToolsUrl = sdkToolsObj[macOsKey].toString(); m_sdkToolsSha256 = QByteArray::fromHex(sdkToolsObj["mac_sha256"].toString().toUtf8()); } else if (Utils::HostOsInfo::isWindowsHost()) { m_sdkToolsUrl = sdkToolsObj[WindowsOsKey].toString(); m_sdkToolsSha256 = QByteArray::fromHex(sdkToolsObj["windows_sha256"].toString().toUtf8()); } else { m_sdkToolsUrl = sdkToolsObj[LinuxOsKey].toString(); m_sdkToolsSha256 = QByteArray::fromHex(sdkToolsObj["linux_sha256"].toString().toUtf8()); } } // Parse common essential packages auto appendEssentialsFromArray = [this](QJsonArray array) { for (const QJsonValueRef &pkg : array) m_commonEssentialPkgs.append(pkg.toString()); }; QJsonObject commonEssentials = commonObject[SdkEssentialPkgsKey].toObject(); appendEssentialsFromArray(commonEssentials[DefaultVersionKey].toArray()); if (Utils::HostOsInfo::isWindowsHost()) appendEssentialsFromArray(commonEssentials[WindowsOsKey].toArray()); if (Utils::HostOsInfo::isMacHost()) appendEssentialsFromArray(commonEssentials[macOsKey].toArray()); else appendEssentialsFromArray(commonEssentials[LinuxOsKey].toArray()); } auto fillQtVersionsRange = [](const QString &shortVersion) { QList versions; QRegularExpression re("([0-9]\\.[0-9]*\\.)\\[([0-9])\\-([0-9])\\]"); QRegularExpressionMatch match = re.match(shortVersion); if (match.hasMatch() && match.lastCapturedIndex() == 3) for (int i = match.captured(2).toInt(); i <= match.captured(3).toInt(); ++i) versions.append(QtVersionNumber(match.captured(1) + QString::number(i))); else versions.append(QtVersionNumber(shortVersion)); return versions; }; if (jsonObject.contains(SpecificQtVersionsKey) && jsonObject[SpecificQtVersionsKey].isArray()) { QJsonArray versionsArray = jsonObject[SpecificQtVersionsKey].toArray(); for (const QJsonValueRef &item : versionsArray) { QJsonObject itemObj = item.toObject(); SdkForQtVersions specificVersion; specificVersion.ndkPath = itemObj[NdkPathKey].toString(); for (const QJsonValueRef &pkg : itemObj[SdkEssentialPkgsKey].toArray()) specificVersion.essentialPackages.append(pkg.toString()); for (const QJsonValueRef &pkg : itemObj[VersionsKey].toArray()) specificVersion.versions.append(fillQtVersionsRange(pkg.toString())); if (itemObj[VersionsKey].toArray().first().toString() == DefaultVersionKey) m_defaultSdkDepends = specificVersion; else m_specificQtVersions.append(specificVersion); } } } QVector AndroidConfig::availableNdkPlatforms(const BaseQtVersion *qtVersion) const { QVector availableNdkPlatforms; QDirIterator it(ndkLocation(qtVersion).pathAppended("platforms").toString(), QStringList("android-*"), QDir::Dirs); while (it.hasNext()) { const QString &fileName = it.next(); availableNdkPlatforms.push_back( fileName.midRef(fileName.lastIndexOf(QLatin1Char('-')) + 1).toInt()); } Utils::sort(availableNdkPlatforms, std::greater<>()); return availableNdkPlatforms; } QStringList AndroidConfig::getCustomNdkList() const { return m_customNdkList; } void AndroidConfig::addCustomNdk(const QString &customNdk) { if (!m_customNdkList.contains(customNdk)) m_customNdkList.append(customNdk); } void AndroidConfig::removeCustomNdk(const QString &customNdk) { m_customNdkList.removeAll(customNdk); } Utils::FilePath AndroidConfig::openSslLocation() const { return m_openSslLocation; } void AndroidConfig::setOpenSslLocation(const Utils::FilePath &openSslLocation) { m_openSslLocation = openSslLocation; } QStringList AndroidConfig::apiLevelNamesFor(const SdkPlatformList &platforms) { return Utils::transform(platforms, AndroidConfig::apiLevelNameFor); } QString AndroidConfig::apiLevelNameFor(const SdkPlatform *platform) { return platform && platform->apiLevel() > 0 ? QString("android-%1").arg(platform->apiLevel()) : ""; } FilePath AndroidConfig::adbToolPath() const { return m_sdkLocation.pathAppended("platform-tools/adb" QTC_HOST_EXE_SUFFIX); } FilePath AndroidConfig::androidToolPath() const { if (HostOsInfo::isWindowsHost()) { // I want to switch from using android.bat to using an executable. All it really does is call // Java and I've made some progress on it. So if android.exe exists, return that instead. const FilePath path = m_sdkLocation.pathAppended("tools/android" QTC_HOST_EXE_SUFFIX); if (path.exists()) return path; return m_sdkLocation.pathAppended("tools/android" ANDROID_BAT_SUFFIX); } return m_sdkLocation.pathAppended("tools/android"); } FilePath AndroidConfig::emulatorToolPath() const { QString relativePath = "emulator/emulator"; if (sdkToolsVersion() < QVersionNumber(25, 3, 0)) relativePath = "tools/emulator"; return m_sdkLocation.pathAppended(relativePath + QTC_HOST_EXE_SUFFIX); } FilePath AndroidConfig::sdkManagerToolPath() const { QString toolPath = "tools/bin/sdkmanager"; if (HostOsInfo::isWindowsHost()) toolPath += ANDROID_BAT_SUFFIX; return m_sdkLocation.pathAppended(toolPath); } FilePath AndroidConfig::avdManagerToolPath() const { QString toolPath = "tools/bin/avdmanager"; if (HostOsInfo::isWindowsHost()) toolPath += ANDROID_BAT_SUFFIX; return m_sdkLocation.pathAppended(toolPath); } FilePath AndroidConfig::aaptToolPath() const { const Utils::FilePath aaptToolPath = m_sdkLocation.pathAppended("build-tools"); QString toolPath = QString("%1/aapt").arg(buildToolsVersion().toString()); if (HostOsInfo::isWindowsHost()) toolPath += QTC_HOST_EXE_SUFFIX; return aaptToolPath.pathAppended(toolPath); } FilePath AndroidConfig::toolchainPathFromNdk(const Utils::FilePath &ndkLocation) const { const FilePath toolchainPath = ndkLocation.pathAppended("toolchains/llvm/prebuilt/"); // detect toolchain host QStringList hostPatterns; switch (HostOsInfo::hostOs()) { case OsTypeLinux: hostPatterns << QLatin1String("linux*"); break; case OsTypeWindows: hostPatterns << QLatin1String("windows*"); break; case OsTypeMac: hostPatterns << QLatin1String("darwin*"); break; default: /* unknown host */ return FilePath(); } QDirIterator iter(toolchainPath.toString(), hostPatterns, QDir::Dirs); if (iter.hasNext()) { iter.next(); return toolchainPath.pathAppended(iter.fileName()); } return {}; } FilePath AndroidConfig::toolchainPath(const BaseQtVersion *qtVersion) const { return toolchainPathFromNdk(ndkLocation(qtVersion)); } FilePath AndroidConfig::clangPathFromNdk(const Utils::FilePath &ndkLocation) const { const FilePath path = toolchainPathFromNdk(ndkLocation); if (path.isEmpty()) return {}; return path.pathAppended(HostOsInfo::withExecutableSuffix("bin/clang")); } FilePath AndroidConfig::clangPath(const BaseQtVersion *qtVersion) const { return clangPathFromNdk(ndkLocation(qtVersion)); } FilePath AndroidConfig::gdbPath(const ProjectExplorer::Abi &abi, const BaseQtVersion *qtVersion) const { return gdbPathFromNdk(abi, ndkLocation(qtVersion)); } FilePath AndroidConfig::gdbPathFromNdk(const Abi &abi, const FilePath &ndkLocation) const { const FilePath path = ndkLocation.pathAppended( QString("prebuilt/%1/bin/gdb%2").arg(toolchainHostFromNdk(ndkLocation), QTC_HOST_EXE_SUFFIX)); if (path.exists()) return path; // fallback for old NDKs (e.g. 10e) return ndkLocation.pathAppended(QString("toolchains/%1-4.9/prebuilt/%2/bin/%3-gdb%4") .arg(toolchainPrefix(abi), toolchainHostFromNdk(ndkLocation), toolsPrefix(abi), QTC_HOST_EXE_SUFFIX)); } FilePath AndroidConfig::makePath(const BaseQtVersion *qtVersion) const { return makePathFromNdk(ndkLocation(qtVersion)); } FilePath AndroidConfig::makePathFromNdk(const FilePath &ndkLocation) const { return ndkLocation.pathAppended( QString("prebuilt/%1/bin/make%2").arg(toolchainHostFromNdk(ndkLocation), QTC_HOST_EXE_SUFFIX)); } FilePath AndroidConfig::openJDKBinPath() const { const FilePath path = m_openJDKLocation; if (!path.isEmpty()) return path.pathAppended("bin"); return path; } FilePath AndroidConfig::keytoolPath() const { return openJDKBinPath().pathAppended(keytoolName); } QVector AndroidConfig::connectedDevices(QString *error) const { return connectedDevices(adbToolPath(), error); } QVector AndroidConfig::connectedDevices(const FilePath &adbToolPath, QString *error) { QVector devices; SynchronousProcess adbProc; adbProc.setTimeoutS(30); CommandLine cmd{adbToolPath, {"devices"}}; SynchronousProcessResponse response = adbProc.runBlocking(cmd); if (response.result != SynchronousProcessResponse::Finished) { if (error) *error = QApplication::translate("AndroidConfiguration", "Could not run: %1") .arg(cmd.toUserOutput()); return devices; } QStringList adbDevs = response.allOutput().split('\n', QString::SkipEmptyParts); if (adbDevs.empty()) return devices; while (adbDevs.first().startsWith("* daemon")) adbDevs.removeFirst(); // remove the daemon logs adbDevs.removeFirst(); // remove "List of devices attached" header line // workaround for '????????????' serial numbers: // can use "adb -d" when only one usb device attached foreach (const QString &device, adbDevs) { const QString serialNo = device.left(device.indexOf('\t')).trimmed(); const QString deviceType = device.mid(device.indexOf('\t')).trimmed(); if (isBootToQt(adbToolPath, serialNo)) continue; AndroidDeviceInfo dev; dev.serialNumber = serialNo; dev.type = serialNo.startsWith(QLatin1String("emulator")) ? AndroidDeviceInfo::Emulator : AndroidDeviceInfo::Hardware; dev.sdk = getSDKVersion(adbToolPath, dev.serialNumber); dev.cpuAbi = getAbis(adbToolPath, dev.serialNumber); if (deviceType == QLatin1String("unauthorized")) dev.state = AndroidDeviceInfo::UnAuthorizedState; else if (deviceType == QLatin1String("offline")) dev.state = AndroidDeviceInfo::OfflineState; else dev.state = AndroidDeviceInfo::OkState; if (dev.type == AndroidDeviceInfo::Emulator) { dev.avdname = getAvdName(dev.serialNumber); if (dev.avdname.isEmpty()) dev.avdname = serialNo; } devices.push_back(dev); } Utils::sort(devices); if (devices.isEmpty() && error) *error = QApplication::translate("AndroidConfiguration", "No devices found in output of: %1") .arg(cmd.toUserOutput()); return devices; } bool AndroidConfig::isConnected(const QString &serialNumber) const { QVector devices = connectedDevices(); foreach (AndroidDeviceInfo device, devices) { if (device.serialNumber == serialNumber) return true; } return false; } bool AndroidConfig::isBootToQt(const QString &device) const { return isBootToQt(adbToolPath(), device); } bool AndroidConfig::isBootToQt(const FilePath &adbToolPath, const QString &device) { // workaround for '????????????' serial numbers CommandLine cmd(adbToolPath, AndroidDeviceInfo::adbSelector(device)); cmd.addArg("shell"); cmd.addArg("ls -l /system/bin/appcontroller || ls -l /usr/bin/appcontroller && echo Boot2Qt"); SynchronousProcess adbProc; adbProc.setTimeoutS(10); SynchronousProcessResponse response = adbProc.runBlocking(cmd); return response.result == SynchronousProcessResponse::Finished && response.allOutput().contains(QLatin1String("Boot2Qt")); } QString AndroidConfig::getDeviceProperty(const FilePath &adbToolPath, const QString &device, const QString &property) { // workaround for '????????????' serial numbers CommandLine cmd(adbToolPath, AndroidDeviceInfo::adbSelector(device)); cmd.addArgs({"shell", "getprop", property}); SynchronousProcess adbProc; adbProc.setTimeoutS(10); SynchronousProcessResponse response = adbProc.runBlocking(cmd); if (response.result != SynchronousProcessResponse::Finished) return QString(); return response.allOutput(); } int AndroidConfig::getSDKVersion(const QString &device) const { return getSDKVersion(adbToolPath(), device); } int AndroidConfig::getSDKVersion(const FilePath &adbToolPath, const QString &device) { QString tmp = getDeviceProperty(adbToolPath, device, "ro.build.version.sdk"); if (tmp.isEmpty()) return -1; return tmp.trimmed().toInt(); } QString AndroidConfig::getAvdName(const QString &serialnumber) { int index = serialnumber.indexOf(QLatin1String("-")); if (index == -1) return QString(); bool ok; int port = serialnumber.midRef(index + 1).toInt(&ok); if (!ok) return QString(); const QByteArray avdName = "avd name\n"; QTcpSocket tcpSocket; tcpSocket.connectToHost(QHostAddress(QHostAddress::LocalHost), port); if (!tcpSocket.waitForConnected(100)) // Don't wait more than 100ms for a local connection return QString{}; tcpSocket.write(avdName + "exit\n"); tcpSocket.waitForDisconnected(500); QByteArray name; const QByteArrayList response = tcpSocket.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")) { name = response.at(i - 1); break; } } return QString::fromLatin1(name).trimmed(); } AndroidConfig::OpenGl AndroidConfig::getOpenGLEnabled(const QString &emulator) const { QDir dir = QDir::home(); if (!dir.cd(QLatin1String(".android"))) return OpenGl::Unknown; if (!dir.cd(QLatin1String("avd"))) return OpenGl::Unknown; if (!dir.cd(emulator + QLatin1String(".avd"))) return OpenGl::Unknown; QFile file(dir.filePath(QLatin1String("config.ini"))); if (!file.exists()) return OpenGl::Unknown; if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) return OpenGl::Unknown; while (!file.atEnd()) { QByteArray line = file.readLine(); if (line.contains("hw.gpu.enabled") && line.contains("yes")) return OpenGl::Enabled; } return OpenGl::Disabled; } //! //! \brief AndroidConfigurations::getProductModel //! \param device serial number //! \return the produce model of the device or if that cannot be read the serial number //! QString AndroidConfig::getProductModel(const QString &device) const { if (m_serialNumberToDeviceName.contains(device)) return m_serialNumberToDeviceName.value(device); QString model = getDeviceProperty(adbToolPath(), device, "ro.product.model").trimmed(); if (model.isEmpty()) return device; if (!device.startsWith(QLatin1String("????"))) m_serialNumberToDeviceName.insert(device, model); return model; } QStringList AndroidConfig::getAbis(const QString &device) const { return getAbis(adbToolPath(), device); } QStringList AndroidConfig::getAbis(const FilePath &adbToolPath, const QString &device) { QStringList result; // First try via ro.product.cpu.abilist QStringList arguments = AndroidDeviceInfo::adbSelector(device); arguments << "shell" << "getprop" << "ro.product.cpu.abilist"; SynchronousProcess adbProc; adbProc.setTimeoutS(10); SynchronousProcessResponse response = adbProc.runBlocking({adbToolPath, arguments}); if (response.result != SynchronousProcessResponse::Finished) return result; QString output = response.allOutput().trimmed(); if (!output.isEmpty()) { QStringList result = output.split(QLatin1Char(',')); if (!result.isEmpty()) return result; } // Fall back to ro.product.cpu.abi, ro.product.cpu.abi2 ... for (int i = 1; i < 6; ++i) { QStringList arguments = AndroidDeviceInfo::adbSelector(device); arguments << QLatin1String("shell") << QLatin1String("getprop"); if (i == 1) arguments << QLatin1String("ro.product.cpu.abi"); else arguments << QString::fromLatin1("ro.product.cpu.abi%1").arg(i); SynchronousProcess abiProc; abiProc.setTimeoutS(10); SynchronousProcessResponse abiResponse = abiProc.runBlocking({adbToolPath, arguments}); if (abiResponse.result != SynchronousProcessResponse::Finished) return result; QString abi = abiResponse.allOutput().trimmed(); if (abi.isEmpty()) break; result << abi; } return result; } bool AndroidConfig::useNativeUiTools() const { const QVersionNumber version = sdkToolsVersion(); return !version.isNull() && version <= QVersionNumber(25, 3 ,0); } bool AndroidConfig::isValidNdk(const QString &ndkLocation) const { auto ndkPath = Utils::FilePath::fromUserInput(ndkLocation); const Utils::FilePath ndkPlatformsDir = ndkPath.pathAppended("platforms"); return ndkPath.exists() && ndkPath.pathAppended("toolchains").exists() && ndkPlatformsDir.exists() && !ndkPlatformsDir.toString().contains(' ') && !ndkVersion(ndkPath).isNull(); } QString AndroidConfig::bestNdkPlatformMatch(int target, const BaseQtVersion *qtVersion) const { target = std::max(AndroidManager::apiLevelRange().first, target); foreach (int apiLevel, availableNdkPlatforms(qtVersion)) { if (apiLevel <= target) return QString::fromLatin1("android-%1").arg(apiLevel); } return QString("android-%1").arg(AndroidManager::apiLevelRange().first); } FilePath AndroidConfig::sdkLocation() const { return m_sdkLocation; } void AndroidConfig::setSdkLocation(const FilePath &sdkLocation) { m_sdkLocation = sdkLocation; } QVersionNumber AndroidConfig::sdkToolsVersion() const { QVersionNumber version; if (m_sdkLocation.exists()) { const Utils::FilePath sdkToolsPropertiesPath = m_sdkLocation.pathAppended("tools/source.properties"); QSettings settings(sdkToolsPropertiesPath.toString(), QSettings::IniFormat); auto versionStr = settings.value(sdkToolsVersionKey).toString(); version = QVersionNumber::fromString(versionStr); } return version; } QVersionNumber AndroidConfig::buildToolsVersion() const { //TODO: return version according to qt version QVersionNumber maxVersion; QDir buildToolsDir(m_sdkLocation.pathAppended("build-tools").toString()); for (const QFileInfo &file: buildToolsDir.entryList(QDir::Dirs|QDir::NoDotAndDotDot)) maxVersion = qMax(maxVersion, QVersionNumber::fromString(file.fileName())); return maxVersion; } QStringList AndroidConfig::sdkManagerToolArgs() const { return m_sdkManagerToolArgs; } void AndroidConfig::setSdkManagerToolArgs(const QStringList &args) { m_sdkManagerToolArgs = args; } FilePath AndroidConfig::ndkLocation(const BaseQtVersion *qtVersion) const { return sdkLocation().pathAppended(ndkPathFromQtVersion(*qtVersion)); } FilePath AndroidConfig::defaultNdkLocation() const { return sdkLocation().pathAppended(m_defaultSdkDepends.ndkPath); } static inline QString gdbServerArch(const QString &androidAbi) { if (androidAbi == "arm64-v8a") { return QString("arm64"); } else if (androidAbi == "armeabi-v7a") { return QString("arm"); } else if (androidAbi == "x86_64") { return QString("x86_64"); } else if (androidAbi == "x86") { return QString("x86"); } else { return {}; } } FilePath AndroidConfig::gdbServer(const QString &androidAbi, const BaseQtVersion *qtVersion) const { const FilePath path = AndroidConfigurations::currentConfig().ndkLocation(qtVersion) .pathAppended(QString("prebuilt/android-%1/gdbserver/gdbserver") .arg(gdbServerArch(androidAbi))); if (path.exists()) return path; return {}; } QVersionNumber AndroidConfig::ndkVersion(const BaseQtVersion *qtVersion) const { return ndkVersion(ndkLocation(qtVersion)); } QVersionNumber AndroidConfig::ndkVersion(const FilePath &ndkPath) const { QVersionNumber version; if (!ndkPath.exists()) { qCDebug(avdConfigLog) << "Cannot find ndk version. Check NDK path." << ndkPath.toString(); return version; } const FilePath ndkPropertiesPath = ndkPath.pathAppended("source.properties"); if (ndkPropertiesPath.exists()) { // source.properties files exists in NDK version > 11 QSettings settings(ndkPropertiesPath.toString(), QSettings::IniFormat); auto versionStr = settings.value(ndkRevisionKey).toString(); version = QVersionNumber::fromString(versionStr); } else { // No source.properties. There should be a file named RELEASE.TXT const FilePath ndkReleaseTxtPath = ndkPath.pathAppended("RELEASE.TXT"); Utils::FileReader reader; QString errorString; if (reader.fetch(ndkReleaseTxtPath.toString(), &errorString)) { // RELEASE.TXT contains the ndk version in either of the following formats: // r6a // r10e (64 bit) QString content = QString::fromUtf8(reader.data()); QRegularExpression re("(r)(?[0-9]{1,2})(?[a-z]{1,1})"); QRegularExpressionMatch match = re.match(content); if (match.hasMatch()) { QString major = match.captured("major"); QString minor = match.captured("minor"); // Minor version: a = 0, b = 1, c = 2 and so on. // Int equivalent = minorVersionChar - 'a'. i.e. minorVersionChar - 97. version = QVersionNumber::fromString(QString("%1.%2.0").arg(major) .arg((int)minor[0].toLatin1() - 97)); } else { qCDebug(avdConfigLog) << "Cannot find ndk version. Cannot parse RELEASE.TXT." << content; } } else { qCDebug(avdConfigLog) << "Cannot find ndk version." << errorString; } } return version; } QStringList AndroidConfig::allEssentials() const { QList installedVersions = QtVersionManager::versions( [](const BaseQtVersion *v) { return v->targetDeviceTypes().contains(Android::Constants::ANDROID_DEVICE_TYPE); }); QStringList allPackages(defaultEssentials()); for (const BaseQtVersion *version : installedVersions) allPackages.append(essentialsFromQtVersion(*version)); allPackages.removeDuplicates(); return allPackages; } QStringList AndroidConfig::essentialsFromQtVersion(const BaseQtVersion &version) const { QtVersionNumber qtVersion = version.qtVersion(); for (const SdkForQtVersions &item : m_specificQtVersions) if (item.containsVersion(qtVersion)) return item.essentialPackages; return m_defaultSdkDepends.essentialPackages; } QString AndroidConfig::ndkPathFromQtVersion(const BaseQtVersion &version) const { QtVersionNumber qtVersion(version.qtVersionString()); for (const SdkForQtVersions &item : m_specificQtVersions) if (item.containsVersion(qtVersion)) return item.ndkPath; return m_defaultSdkDepends.ndkPath; } QStringList AndroidConfig::defaultEssentials() const { return m_defaultSdkDepends.essentialPackages + m_commonEssentialPkgs; } bool SdkForQtVersions::containsVersion(const QtVersionNumber &qtVersion) const { return versions.contains(qtVersion) || versions.contains(QtVersionNumber(qtVersion.majorVersion, qtVersion.minorVersion)); } FilePath AndroidConfig::openJDKLocation() const { return m_openJDKLocation; } void AndroidConfig::setOpenJDKLocation(const FilePath &openJDKLocation) { m_openJDKLocation = openJDKLocation; } FilePath AndroidConfig::keystoreLocation() const { return m_keystoreLocation; } void AndroidConfig::setKeystoreLocation(const FilePath &keystoreLocation) { m_keystoreLocation = keystoreLocation; } QString AndroidConfig::toolchainHost(const BaseQtVersion *qtVersion) const { return toolchainHostFromNdk(ndkLocation(qtVersion)); } QString AndroidConfig::toolchainHostFromNdk(const FilePath &ndkPath) const { // detect toolchain host QString toolchainHost; QStringList hostPatterns; switch (HostOsInfo::hostOs()) { case OsTypeLinux: hostPatterns << QLatin1String("linux*"); break; case OsTypeWindows: hostPatterns << QLatin1String("windows*"); break; case OsTypeMac: hostPatterns << QLatin1String("darwin*"); break; default: /* unknown host */ return toolchainHost; } QDirIterator jt(ndkPath.pathAppended("prebuilt").toString(), hostPatterns, QDir::Dirs); if (jt.hasNext()) { jt.next(); toolchainHost = jt.fileName(); } return toolchainHost; } unsigned AndroidConfig::partitionSize() const { return m_partitionSize; } void AndroidConfig::setPartitionSize(unsigned partitionSize) { m_partitionSize = partitionSize; } bool AndroidConfig::automaticKitCreation() const { return m_automaticKitCreation; } void AndroidConfig::setAutomaticKitCreation(bool b) { m_automaticKitCreation = b; } /////////////////////////////////// // AndroidConfigurations /////////////////////////////////// void AndroidConfigurations::setConfig(const AndroidConfig &devConfigs) { emit m_instance->aboutToUpdate(); m_instance->m_config = devConfigs; m_instance->save(); m_instance->updateAndroidDevice(); m_instance->registerNewToolChains(); m_instance->updateAutomaticKitList(); m_instance->removeOldToolChains(); emit m_instance->updated(); } AndroidDeviceInfo AndroidConfigurations::showDeviceDialog(Project *project, int apiLevel, const QStringList &abis) { QString serialNumber; for (const QString &abi : abis) { serialNumber = defaultDevice(project, abi); if (!serialNumber.isEmpty()) break; } AndroidDeviceDialog dialog(apiLevel, abis, serialNumber, Core::ICore::mainWindow()); AndroidDeviceInfo info = dialog.device(); if (dialog.saveDeviceSelection() && info.isValid()) { const QString serialNumber = info.type == AndroidDeviceInfo::Hardware ? info.serialNumber : info.avdname; if (!serialNumber.isEmpty()) AndroidConfigurations::setDefaultDevice(project, AndroidManager::devicePreferredAbi(info.cpuAbi, abis), serialNumber); } return info; } void AndroidConfigurations::clearDefaultDevices(Project *project) { if (m_instance->m_defaultDeviceForAbi.contains(project)) m_instance->m_defaultDeviceForAbi.remove(project); } void AndroidConfigurations::setDefaultDevice(Project *project, const QString &abi, const QString &serialNumber) { m_instance->m_defaultDeviceForAbi[project][abi] = serialNumber; } QString AndroidConfigurations::defaultDevice(Project *project, const QString &abi) { if (!m_instance->m_defaultDeviceForAbi.contains(project)) return QString(); const QMap &map = m_instance->m_defaultDeviceForAbi.value(project); if (!map.contains(abi)) return QString(); return map.value(abi); } static bool matchToolChain(const ToolChain *atc, const ToolChain *btc) { if (atc == btc) return true; if (!atc || !btc) return false; if (atc->typeId() != Constants::ANDROID_TOOLCHAIN_TYPEID || btc->typeId() != Constants::ANDROID_TOOLCHAIN_TYPEID) return false; auto aatc = static_cast(atc); auto abtc = static_cast(btc); return aatc->targetAbi() == abtc->targetAbi(); } void AndroidConfigurations::registerNewToolChains() { const QList existingAndroidToolChains = ToolChainManager::toolChains(Utils::equal(&ToolChain::typeId, Core::Id(Constants::ANDROID_TOOLCHAIN_TYPEID))); QList newToolchains = AndroidToolChainFactory::autodetectToolChains( existingAndroidToolChains); foreach (ToolChain *tc, newToolchains) ToolChainManager::registerToolChain(tc); registerCustomToolChainsAndDebuggers(); } void AndroidConfigurations::registerCustomToolChainsAndDebuggers() { const QList existingAndroidToolChains = ToolChainManager::toolChains( Utils::equal(&ToolChain::typeId, Core::Id(Constants::ANDROID_TOOLCHAIN_TYPEID))); QList customNdks = Utils::transform(currentConfig().getCustomNdkList(), FilePath::fromString); QList customToolchains = AndroidToolChainFactory::autodetectToolChainsFromNdks(existingAndroidToolChains, customNdks, true); for (ToolChain *tc : customToolchains) { ToolChainManager::registerToolChain(tc); const FilePath ndk = static_cast(tc)->ndkLocation(); const FilePath command = AndroidConfigurations::currentConfig() .gdbPathFromNdk(tc->targetAbi(), ndk); const Debugger::DebuggerItem *existing = Debugger::DebuggerItemManager::findByCommand( command); QString abiStr = static_cast(tc)->platformLinkerFlags().at(1).split('-').first(); Abi abi = Abi::abiFromTargetTriplet(abiStr); if (existing && existing->abis().contains(abi)) continue; Debugger::DebuggerItem debugger; debugger.setCommand(command); debugger.setEngineType(Debugger::GdbEngineType); debugger.setUnexpandedDisplayName( AndroidConfigurations::tr("Custom Android Debugger (%1, NDK %2)") .arg(abiStr, AndroidConfigurations::currentConfig().ndkVersion(ndk).toString())); debugger.setAutoDetected(true); debugger.setAbi(abi); debugger.reinitializeFromFile(); Debugger::DebuggerItemManager::registerDebugger(debugger); } } void AndroidConfigurations::removeOldToolChains() { foreach (ToolChain *tc, ToolChainManager::toolChains(Utils::equal(&ToolChain::typeId, Core::Id(Constants::ANDROID_TOOLCHAIN_TYPEID)))) { if (!tc->isValid()) ToolChainManager::deregisterToolChain(tc); } } void AndroidConfigurations::removeUnusedDebuggers() { QList uniqueNdks; const QList qtVersions = QtSupport::QtVersionManager::versions([](const QtSupport::BaseQtVersion *v) { return v->type() == Constants::ANDROIDQT; }); for (const QtSupport::BaseQtVersion *qt : qtVersions) { FilePath ndkLocation = currentConfig().ndkLocation(qt); if (!uniqueNdks.contains(ndkLocation)) uniqueNdks.append(ndkLocation); } uniqueNdks.append(Utils::transform(currentConfig().getCustomNdkList(), FilePath::fromString)); const QList allDebuggers = Debugger::DebuggerItemManager::debuggers(); for (const Debugger::DebuggerItem &debugger : allDebuggers) { if (!debugger.displayName().contains("Android")) continue; bool isChildOfNdk = false; for (const FilePath &path : uniqueNdks) { if (debugger.command().isChildOf(path)) { isChildOfNdk = true; break; } } if (!isChildOfNdk && debugger.isAutoDetected()) Debugger::DebuggerItemManager::deregisterDebugger(debugger.id()); } } static QVariant findOrRegisterDebugger(ToolChain *tc, const QStringList &abisList, const BaseQtVersion *qtVersion) { const FilePath command = AndroidConfigurations::currentConfig().gdbPath(tc->targetAbi(), qtVersion); // check if the debugger is already registered, but ignoring the display name const Debugger::DebuggerItem *existing = Debugger::DebuggerItemManager::findByCommand(command); QList abis = Utils::transform(abisList, Abi::abiFromTargetTriplet); auto containsAbis = [abis](const Abis &secondAbis) { for (const Abi &abi : secondAbis) { if (!abis.contains(abi)) return false; } return true; }; if (existing && existing->engineType() == Debugger::GdbEngineType && existing->isAutoDetected() && containsAbis(existing->abis())) { // update debugger info with new return existing->id(); } // debugger not found, register a new one Debugger::DebuggerItem debugger; debugger.setCommand(command); debugger.setEngineType(Debugger::GdbEngineType); debugger.setUnexpandedDisplayName( AndroidConfigurations::tr("Android Debugger (%1, NDK %2)") .arg(abisList.join(", "), AndroidConfigurations::currentConfig().ndkVersion(qtVersion).toString())); debugger.setAutoDetected(true); debugger.setAbis(abis.toVector()); debugger.reinitializeFromFile(); return Debugger::DebuggerItemManager::registerDebugger(debugger); } void AndroidConfigurations::updateAutomaticKitList() { const QList androidKits = Utils::filtered(KitManager::kits(), [](Kit *k) { Core::Id deviceTypeId = DeviceTypeKitAspect::deviceTypeId(k); return deviceTypeId == Core::Id(Constants::ANDROID_DEVICE_TYPE); }); for (auto k: androidKits) { if (k->value(Constants::ANDROID_KIT_NDK).isNull() || k->value(Constants::ANDROID_KIT_SDK).isNull()) { k->setValueSilently(Constants::ANDROID_KIT_NDK, currentConfig().ndkLocation(QtSupport::QtKitAspect::qtVersion(k)).toString()); k->setValue(Constants::ANDROID_KIT_SDK, currentConfig().sdkLocation().toString()); } } const QList existingKits = Utils::filtered(KitManager::kits(), [](Kit *k) { Core::Id deviceTypeId = DeviceTypeKitAspect::deviceTypeId(k); if (k->isAutoDetected() && !k->isSdkProvided() && deviceTypeId == Core::Id(Constants::ANDROID_DEVICE_TYPE)) { if (!QtSupport::QtKitAspect::qtVersion(k)) KitManager::deregisterKit(k); // Remove autoDetected kits without Qt. else return true; } return false; }); removeUnusedDebuggers(); QHash > qtVersionsForArch; const QList qtVersions = QtSupport::QtVersionManager::versions([](const QtSupport::BaseQtVersion *v) { return v->type() == Constants::ANDROIDQT; }); for (const QtSupport::BaseQtVersion *qtVersion : qtVersions) { const Abis qtAbis = qtVersion->qtAbis(); if (qtAbis.empty()) continue; qtVersionsForArch[qtAbis.first()].append(qtVersion); } DeviceManager *dm = DeviceManager::instance(); IDevice::ConstPtr device = dm->find(Core::Id(Constants::ANDROID_DEVICE_ID)); if (device.isNull()) { // no device, means no sdk path for (Kit *k : existingKits) KitManager::deregisterKit(k); return; } // register new kits const QList toolchains = ToolChainManager::toolChains([](const ToolChain *tc) { return tc->isAutoDetected() && tc->isValid() && tc->typeId() == Constants::ANDROID_TOOLCHAIN_TYPEID; }); for (ToolChain *tc : toolchains) { if (tc->language() != Core::Id(ProjectExplorer::Constants::CXX_LANGUAGE_ID)) continue; for (const QtSupport::BaseQtVersion *qt : qtVersionsForArch.value(tc->targetAbi())) { FilePath tcNdk = static_cast(tc)->ndkLocation(); if (tcNdk != currentConfig().ndkLocation(qt)) continue; const QList allLanguages = Utils::filtered(toolchains, [tc, tcNdk](ToolChain *otherTc) { FilePath otherNdk = static_cast(otherTc)->ndkLocation(); return tc->targetAbi() == otherTc->targetAbi() && tcNdk == otherNdk; }); QHash toolChainForLanguage; for (ToolChain *tc : allLanguages) toolChainForLanguage[tc->language()] = tc; Kit *existingKit = Utils::findOrDefault(existingKits, [&](const Kit *b) { if (qt != QtSupport::QtKitAspect::qtVersion(b)) return false; return matchToolChain(toolChainForLanguage[ProjectExplorer::Constants::CXX_LANGUAGE_ID], ToolChainKitAspect::toolChain(b, ProjectExplorer::Constants::CXX_LANGUAGE_ID)) && matchToolChain(toolChainForLanguage[ProjectExplorer::Constants::C_LANGUAGE_ID], ToolChainKitAspect::toolChain(b, ProjectExplorer::Constants::C_LANGUAGE_ID)); }); const auto initializeKit = [allLanguages, device, tc, qt](Kit *k) { k->setAutoDetected(true); k->setAutoDetectionSource("AndroidConfiguration"); DeviceTypeKitAspect::setDeviceTypeId(k, Core::Id(Constants::ANDROID_DEVICE_TYPE)); for (ToolChain *tc : allLanguages) ToolChainKitAspect::setToolChain(k, tc); QtSupport::QtKitAspect::setQtVersion(k, qt); DeviceKitAspect::setDevice(k, device); QStringList abis = static_cast(qt)->androidAbis(); Debugger::DebuggerKitAspect::setDebugger(k, findOrRegisterDebugger(tc, abis, QtKitAspect::qtVersion(k))); k->makeSticky(); k->setUnexpandedDisplayName(tr("Android for %1 (Clang %2)") .arg(abis.join(",")) .arg(qt->displayName())); k->setValueSilently(Constants::ANDROID_KIT_NDK, currentConfig().ndkLocation(qt).toString()); k->setValueSilently(Constants::ANDROID_KIT_SDK, currentConfig().sdkLocation().toString()); }; if (existingKit) initializeKit(existingKit); // Update the existing kit with new data. else KitManager::registerKit(initializeKit); } } } bool AndroidConfigurations::force32bitEmulator() { return m_instance->m_force32bit; } QProcessEnvironment AndroidConfigurations::toolsEnvironment(const AndroidConfig &config) { Environment env = Environment::systemEnvironment(); Utils::FilePath jdkLocation = config.openJDKLocation(); if (!jdkLocation.isEmpty()) { env.set("JAVA_HOME", jdkLocation.toUserOutput()); env.prependOrSetPath(jdkLocation.pathAppended("bin").toUserOutput()); } return env.toProcessEnvironment(); } /** * Workaround for '????????????' serial numbers * @return ("-d") for buggy devices, ("-s", ) for normal */ QStringList AndroidDeviceInfo::adbSelector(const QString &serialNumber) { if (serialNumber.startsWith(QLatin1String("????"))) return QStringList("-d"); return QStringList({"-s", serialNumber}); } bool AndroidDeviceInfo::operator<(const AndroidDeviceInfo &other) const { if (serialNumber.contains("????") != other.serialNumber.contains("????")) return !serialNumber.contains("????"); if (type != other.type) return type == AndroidDeviceInfo::Hardware; if (sdk != other.sdk) return sdk < other.sdk; if (avdname != other.avdname) return avdname < other.avdname; return serialNumber < other.serialNumber; } const AndroidConfig &AndroidConfigurations::currentConfig() { return m_instance->m_config; // ensure that m_instance is initialized } AndroidSdkManager *AndroidConfigurations::sdkManager() { return m_instance->m_sdkManager.get(); } AndroidConfigurations *AndroidConfigurations::instance() { return m_instance; } void AndroidConfigurations::save() { QSettings *settings = Core::ICore::settings(); settings->beginGroup(SettingsGroup); m_config.save(*settings); settings->endGroup(); } AndroidConfigurations::AndroidConfigurations() : m_sdkManager(new AndroidSdkManager(m_config)) { load(); connect(SessionManager::instance(), &SessionManager::projectRemoved, this, &AndroidConfigurations::clearDefaultDevices); connect(DeviceManager::instance(), &DeviceManager::devicesLoaded, this, &AndroidConfigurations::updateAndroidDevice); m_force32bit = is32BitUserSpace(); m_instance = this; } AndroidConfigurations::~AndroidConfigurations() = default; static FilePath javaHomeForJavac(const FilePath &location) { QFileInfo fileInfo = location.toFileInfo(); int tries = 5; while (tries > 0) { QDir dir = fileInfo.dir(); dir.cdUp(); if (QFileInfo::exists(dir.filePath(QLatin1String("lib/tools.jar")))) return FilePath::fromString(dir.path()); if (fileInfo.isSymLink()) fileInfo.setFile(fileInfo.symLinkTarget()); else break; --tries; } return FilePath(); } void AndroidConfigurations::load() { bool saveSettings = false; QSettings *settings = Core::ICore::settings(); settings->beginGroup(SettingsGroup); m_config.load(*settings); if (m_config.openJDKLocation().isEmpty()) { if (HostOsInfo::isLinuxHost()) { Environment env = Environment::systemEnvironment(); FilePath location = env.searchInPath(QLatin1String("javac")); QFileInfo fi = location.toFileInfo(); if (fi.exists() && fi.isExecutable() && !fi.isDir()) { m_config.setOpenJDKLocation(javaHomeForJavac(location)); saveSettings = true; } } else if (HostOsInfo::isMacHost()) { QFileInfo javaHomeExec(QLatin1String("/usr/libexec/java_home")); if (javaHomeExec.isExecutable() && !javaHomeExec.isDir()) { SynchronousProcess proc; proc.setTimeoutS(2); proc.setProcessChannelMode(QProcess::MergedChannels); SynchronousProcessResponse response = proc.runBlocking({javaHomeExec.absoluteFilePath(), {}}); if (response.result == SynchronousProcessResponse::Finished) { const QString &javaHome = response.allOutput().trimmed(); if (!javaHome.isEmpty() && QFileInfo::exists(javaHome)) m_config.setOpenJDKLocation(FilePath::fromString(javaHome)); } } } else if (HostOsInfo::isWindowsHost()) { QStringList allVersions; std::unique_ptr settings(new QSettings(jdkSettingsPath, QSettings::NativeFormat)); allVersions = settings->childGroups(); #ifdef Q_OS_WIN if (allVersions.isEmpty()) { settings.reset(new QSettings(jdkSettingsPath, QSettings::Registry64Format)); allVersions = settings->childGroups(); } #endif // Q_OS_WIN QString javaHome; int major = -1; int minor = -1; foreach (const QString &version, allVersions) { QStringList parts = version.split(QLatin1Char('.')); if (parts.size() != 2) // not interested in 1.7.0_u21 continue; bool okMajor, okMinor; int tmpMajor = parts.at(0).toInt(&okMajor); int tmpMinor = parts.at(1).toInt(&okMinor); if (!okMajor || !okMinor) continue; if (tmpMajor > major || (tmpMajor == major && tmpMinor > minor)) { settings->beginGroup(version); QString tmpJavaHome = settings->value(QLatin1String("JavaHome")).toString(); settings->endGroup(); if (!QFileInfo::exists(tmpJavaHome)) continue; major = tmpMajor; minor = tmpMinor; javaHome = tmpJavaHome; } } if (!javaHome.isEmpty()) { m_config.setOpenJDKLocation(FilePath::fromString(javaHome)); saveSettings = true; } } } settings->endGroup(); if (saveSettings) save(); } void AndroidConfigurations::updateAndroidDevice() { DeviceManager * const devMgr = DeviceManager::instance(); if (m_instance->m_config.adbToolPath().exists()) devMgr->addDevice(AndroidDevice::create()); else if (devMgr->find(Constants::ANDROID_DEVICE_ID)) devMgr->removeDevice(Core::Id(Constants::ANDROID_DEVICE_ID)); } AndroidConfigurations *AndroidConfigurations::m_instance = nullptr; QDebug &operator<<(QDebug &stream, const AndroidDeviceInfo &device) { stream << "Type:"<< (device.type == AndroidDeviceInfo::Emulator ? "Emulator" : "Device") << ", ABI:" << device.cpuAbi << ", Serial:" << device.serialNumber << ", Name:" << device.avdname << ", API:" << device.sdk << ", Authorised:" << !device.unauthorized; return stream; } } // namespace Android