/************************************************************************** ** ** Copyright (c) 2014 BogDan Vatra ** Contact: http://www.qt-project.org/legal ** ** 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 Digia. For licensing terms and ** conditions see http://www.qt.io/licensing. For further information ** use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ****************************************************************************/ #include "androidconfigurations.h" #include "androidconstants.h" #include "androidtoolchain.h" #include "androiddevice.h" #include "androidgdbserverkitinformation.h" #include "androidqtversion.h" #include "androiddevicedialog.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 using namespace ProjectExplorer; using namespace Utils; namespace Android { using namespace Internal; namespace { const QLatin1String SettingsGroup("AndroidConfigurations"); const QLatin1String SDKLocationKey("SDKLocation"); const QLatin1String NDKLocationKey("NDKLocation"); const QLatin1String NDKToolchainVersionKey("NDKToolchainVersion"); const QLatin1String AntLocationKey("AntLocation"); const QLatin1String OpenJDKLocationKey("OpenJDKLocation"); const QLatin1String KeystoreLocationKey("KeystoreLocation"); const QLatin1String AutomaticKitCreationKey("AutomatiKitCreation"); const QLatin1String UseGradleKey("UseGradle"); const QLatin1String MakeExtraSearchDirectory("MakeExtraSearchDirectory"); const QLatin1String DefaultDevice("DefaultDevice"); const QLatin1String PartitionSizeKey("PartitionSize"); const QLatin1String ToolchainHostKey("ToolchainHost"); const QLatin1String ArmToolchainPrefix("arm-linux-androideabi"); const QLatin1String X86ToolchainPrefix("x86"); const QLatin1String MipsToolchainPrefix("mipsel-linux-android"); const QLatin1String ArmToolsPrefix("arm-linux-androideabi"); const QLatin1String X86ToolsPrefix("i686-linux-android"); const QLatin1String MipsToolsPrefix("mipsel-linux-android"); const QLatin1String Unknown("unknown"); const QLatin1String keytoolName("keytool"); const QLatin1String jarsignerName("jarsigner"); const QLatin1String changeTimeStamp("ChangeTimeStamp"); static QString sdkSettingsFileName() { return QFileInfo(Core::ICore::settings(QSettings::SystemScope)->fileName()).absolutePath() + QLatin1String("/qtcreator/android.xml"); } bool androidDevicesLessThan(const AndroidDeviceInfo &dev1, const AndroidDeviceInfo &dev2) { if (dev1.serialNumber.contains(QLatin1String("????")) != dev2.serialNumber.contains(QLatin1String("????"))) return !dev1.serialNumber.contains(QLatin1String("????")); if (dev1.type != dev2.type) return dev1.type == AndroidDeviceInfo::Hardware; if (dev1.sdk != dev2.sdk) return dev1.sdk < dev2.sdk; return dev1.serialNumber < dev2.serialNumber; } static QStringList cleanAndroidABIs(const QStringList &abis) { QStringList res; foreach (const QString &abi, abis) { int index = abi.lastIndexOf(QLatin1Char('/')); if (index == -1) res << abi; else res << abi.mid(index + 1); } return res; } 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 QProcess proc; proc.setProcessChannelMode(QProcess::MergedChannels); proc.start(executable, QStringList() << shell); if (!proc.waitForFinished(2000)) { proc.kill(); return true; } if (proc.readAll().contains("x86-64")) return false; return true; } } return false; } } ////////////////////////////////// // AndroidConfig ////////////////////////////////// Abi::Architecture AndroidConfig::architectureForToolChainPrefix(const QString& toolchainprefix) { if (toolchainprefix == ArmToolchainPrefix) return Abi::ArmArchitecture; if (toolchainprefix == X86ToolchainPrefix) return Abi::X86Architecture; if (toolchainprefix == MipsToolchainPrefix) return Abi::MipsArchitecture; return Abi::UnknownArchitecture; } QLatin1String AndroidConfig::toolchainPrefix(Abi::Architecture architecture) { switch (architecture) { case Abi::ArmArchitecture: return ArmToolchainPrefix; case Abi::X86Architecture: return X86ToolchainPrefix; case Abi::MipsArchitecture: return MipsToolchainPrefix; default: return Unknown; } } QLatin1String AndroidConfig::toolsPrefix(Abi::Architecture architecture) { switch (architecture) { case Abi::ArmArchitecture: return ArmToolsPrefix; case Abi::X86Architecture: return X86ToolsPrefix; case Abi::MipsArchitecture: return MipsToolsPrefix; default: return Unknown; } } void AndroidConfig::load(const QSettings &settings) { // user settings m_partitionSize = settings.value(PartitionSizeKey, 1024).toInt(); m_sdkLocation = FileName::fromString(settings.value(SDKLocationKey).toString()); m_ndkLocation = FileName::fromString(settings.value(NDKLocationKey).toString()); m_antLocation = FileName::fromString(settings.value(AntLocationKey).toString()); m_useGradle = settings.value(UseGradleKey, false).toBool(); m_openJDKLocation = FileName::fromString(settings.value(OpenJDKLocationKey).toString()); m_keystoreLocation = FileName::fromString(settings.value(KeystoreLocationKey).toString()); m_toolchainHost = settings.value(ToolchainHostKey).toString(); m_automaticKitCreation = settings.value(AutomaticKitCreationKey, true).toBool(); QString extraDirectory = settings.value(MakeExtraSearchDirectory).toString(); m_makeExtraSearchDirectories.clear(); if (!extraDirectory.isEmpty()) m_makeExtraSearchDirectories << extraDirectory; PersistentSettingsReader reader; if (reader.load(FileName::fromString(sdkSettingsFileName())) && settings.value(changeTimeStamp).toInt() != QFileInfo(sdkSettingsFileName()).lastModified().toMSecsSinceEpoch() / 1000) { // persisten settings m_sdkLocation = FileName::fromString(reader.restoreValue(SDKLocationKey, m_sdkLocation.toString()).toString()); m_ndkLocation = FileName::fromString(reader.restoreValue(NDKLocationKey, m_ndkLocation.toString()).toString()); m_antLocation = FileName::fromString(reader.restoreValue(AntLocationKey, m_antLocation.toString()).toString()); m_openJDKLocation = FileName::fromString(reader.restoreValue(OpenJDKLocationKey, m_openJDKLocation.toString()).toString()); m_keystoreLocation = FileName::fromString(reader.restoreValue(KeystoreLocationKey, m_keystoreLocation.toString()).toString()); m_toolchainHost = reader.restoreValue(ToolchainHostKey, m_toolchainHost).toString(); m_automaticKitCreation = reader.restoreValue(AutomaticKitCreationKey, m_automaticKitCreation).toBool(); QString extraDirectory = reader.restoreValue(MakeExtraSearchDirectory).toString(); m_makeExtraSearchDirectories.clear(); if (!extraDirectory.isEmpty()) m_makeExtraSearchDirectories << extraDirectory; // persistent settings } m_availableSdkPlatformsUpToDate = false; m_NdkInformationUpToDate = false; } AndroidConfig::AndroidConfig() : m_useGradle(false), m_availableSdkPlatformsUpToDate(false), m_NdkInformationUpToDate(false) { } 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(NDKLocationKey, m_ndkLocation.toString()); settings.setValue(AntLocationKey, m_antLocation.toString()); settings.setValue(UseGradleKey, m_useGradle); settings.setValue(OpenJDKLocationKey, m_openJDKLocation.toString()); settings.setValue(KeystoreLocationKey, m_keystoreLocation.toString()); settings.setValue(PartitionSizeKey, m_partitionSize); settings.setValue(AutomaticKitCreationKey, m_automaticKitCreation); settings.setValue(ToolchainHostKey, m_toolchainHost); settings.setValue(MakeExtraSearchDirectory, m_makeExtraSearchDirectories.isEmpty() ? QString() : m_makeExtraSearchDirectories.at(0)); } void AndroidConfig::updateNdkInformation() const { if (m_NdkInformationUpToDate) return; m_availableNdkPlatforms.clear(); FileName path = ndkLocation(); QDirIterator it(path.appendPath(QLatin1String("platforms")).toString(), QStringList() << QLatin1String("android-*"), QDir::Dirs); while (it.hasNext()) { const QString &fileName = it.next(); m_availableNdkPlatforms.push_back(fileName.mid(fileName.lastIndexOf(QLatin1Char('-')) + 1).toInt()); } Utils::sort(m_availableNdkPlatforms, std::greater()); // 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; } path = ndkLocation(); QDirIterator jt(path.appendPath(QLatin1String("prebuilt")).toString(), hostPatterns, QDir::Dirs); if (jt.hasNext()) { jt.next(); m_toolchainHost = jt.fileName(); } m_NdkInformationUpToDate = true; } bool AndroidConfig::sortSdkPlatformByApiLevel(const SdkPlatform &a, const SdkPlatform &b) { if (a.apiLevel != b.apiLevel) return a.apiLevel > b.apiLevel; if (a.name != b.name) return a.name < b.name; return false; } void AndroidConfig::updateAvailableSdkPlatforms() const { if (m_availableSdkPlatformsUpToDate) return; m_availableSdkPlatforms.clear(); QProcess proc; proc.setProcessEnvironment(androidToolEnvironment().toProcessEnvironment()); proc.start(androidToolPath().toString(), QStringList() << QLatin1String("list") << QLatin1String("target")); // list avaialbe AVDs if (!proc.waitForFinished(5000)) { proc.terminate(); return; } SdkPlatform platform; while (proc.canReadLine()) { const QString line = QString::fromLocal8Bit(proc.readLine().trimmed()); if (line.startsWith(QLatin1String("id:")) && line.contains(QLatin1String("android-"))) { int index = line.indexOf(QLatin1String("\"android-")); if (index == -1) continue; QString androidTarget = line.mid(index + 1, line.length() - index - 2); platform.apiLevel = androidTarget.mid(androidTarget.lastIndexOf(QLatin1Char('-')) + 1).toInt(); } else if (line.startsWith(QLatin1String("Name:"))) { platform.name = line.mid(6); } else if (line.startsWith(QLatin1String("Tag/ABIs :"))) { platform.abis = cleanAndroidABIs(line.mid(10).trimmed().split(QLatin1String(", "))); } else if (line.startsWith(QLatin1String("ABIs"))) { platform.abis = cleanAndroidABIs(line.mid(6).trimmed().split(QLatin1String(", "))); } else if (line.startsWith(QLatin1String("---")) || line.startsWith(QLatin1String("==="))) { if (platform.apiLevel == -1) continue; auto it = qLowerBound(m_availableSdkPlatforms.begin(), m_availableSdkPlatforms.end(), platform, sortSdkPlatformByApiLevel); m_availableSdkPlatforms.insert(it, platform); platform = SdkPlatform(); } } if (platform.apiLevel != -1) { auto it = qLowerBound(m_availableSdkPlatforms.begin(), m_availableSdkPlatforms.end(), platform, sortSdkPlatformByApiLevel); m_availableSdkPlatforms.insert(it, platform); } m_availableSdkPlatformsUpToDate = true; } QStringList AndroidConfig::apiLevelNamesFor(const QList &platforms) { return Utils::transform(platforms, AndroidConfig::apiLevelNameFor); } QString AndroidConfig::apiLevelNameFor(const SdkPlatform &platform) { return QLatin1String("android-") + QString::number(platform.apiLevel); } QList AndroidConfig::sdkTargets(int minApiLevel) const { updateAvailableSdkPlatforms(); QList result; for (int i = 0; i < m_availableSdkPlatforms.size(); ++i) { if (m_availableSdkPlatforms.at(i).apiLevel >= minApiLevel) result << m_availableSdkPlatforms.at(i); else break; } return result; } FileName AndroidConfig::adbToolPath() const { FileName path = m_sdkLocation; return path.appendPath(QLatin1String("platform-tools/adb" QTC_HOST_EXE_SUFFIX)); } Environment AndroidConfig::androidToolEnvironment() const { Environment env = Environment::systemEnvironment(); if (!m_openJDKLocation.isEmpty()) env.set(QLatin1String("JAVA_HOME"), m_openJDKLocation.toUserOutput()); return env; } FileName 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. FileName path = m_sdkLocation; path.appendPath(QLatin1String("tools/android" QTC_HOST_EXE_SUFFIX)); if (path.exists()) return path; path = m_sdkLocation; return path.appendPath(QLatin1String("tools/android" ANDROID_BAT_SUFFIX)); } else { FileName path = m_sdkLocation; return path.appendPath(QLatin1String("tools/android")); } } FileName AndroidConfig::antToolPath() const { if (!m_antLocation.isEmpty()) return m_antLocation; else return FileName::fromLatin1("ant"); } FileName AndroidConfig::emulatorToolPath() const { FileName path = m_sdkLocation; return path.appendPath(QLatin1String("tools/emulator" QTC_HOST_EXE_SUFFIX)); } FileName AndroidConfig::toolPath(Abi::Architecture architecture, const QString &ndkToolChainVersion) const { FileName path = m_ndkLocation; return path.appendPath(QString::fromLatin1("toolchains/%1-%2/prebuilt/%3/bin/%4") .arg(toolchainPrefix(architecture)) .arg(ndkToolChainVersion) .arg(toolchainHost()) .arg(toolsPrefix(architecture))); } FileName AndroidConfig::gccPath(Abi::Architecture architecture, const QString &ndkToolChainVersion) const { return toolPath(architecture, ndkToolChainVersion).appendString(QLatin1String("-gcc" QTC_HOST_EXE_SUFFIX)); } FileName AndroidConfig::gdbPath(Abi::Architecture architecture, const QString &ndkToolChainVersion) const { return toolPath(architecture, ndkToolChainVersion).appendString(QLatin1String("-gdb" QTC_HOST_EXE_SUFFIX)); } FileName AndroidConfig::openJDKBinPath() const { FileName path = m_openJDKLocation; if (!path.isEmpty()) return path.appendPath(QLatin1String("bin")); return path; } FileName AndroidConfig::keytoolPath() const { return openJDKBinPath().appendPath(keytoolName); } QVector AndroidConfig::connectedDevices(QString *error) const { QVector devices; QProcess adbProc; adbProc.start(adbToolPath().toString(), QStringList() << QLatin1String("devices")); if (!adbProc.waitForFinished(10000)) { adbProc.kill(); if (error) *error = QApplication::translate("AndroidConfiguration", "Could not run: %1") .arg(adbToolPath().toString() + QLatin1String(" devices")); return devices; } QList adbDevs = adbProc.readAll().trimmed().split('\n'); 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 QByteArray &device, adbDevs) { const QString serialNo = QString::fromLatin1(device.left(device.indexOf('\t')).trimmed()); const QString deviceType = QString::fromLatin1(device.mid(device.indexOf('\t'))).trimmed(); if (isBootToQt(serialNo)) continue; AndroidDeviceInfo dev; dev.serialNumber = serialNo; dev.type = serialNo.startsWith(QLatin1String("emulator")) ? AndroidDeviceInfo::Emulator : AndroidDeviceInfo::Hardware; dev.sdk = getSDKVersion(dev.serialNumber); dev.cpuAbi = getAbis(dev.serialNumber); if (deviceType == QLatin1String("unauthorized")) dev.state = AndroidDeviceInfo::UnAuthorizedState; else if (deviceType == QLatin1String("offline")) dev.state = AndroidDeviceInfo::OfflineState; else dev.state = AndroidDeviceInfo::OkState; devices.push_back(dev); } Utils::sort(devices, androidDevicesLessThan); if (devices.isEmpty() && error) *error = QApplication::translate("AndroidConfiguration", "No devices found in output of: %1") .arg(adbToolPath().toString() + QLatin1String(" devices")); return devices; } AndroidConfig::CreateAvdInfo AndroidConfig::gatherCreateAVDInfo(QWidget *parent, int minApiLevel, QString targetArch) const { CreateAvdInfo result; AvdDialog d(minApiLevel, targetArch, this, parent); if (d.exec() != QDialog::Accepted || !d.isValid()) return result; result.target = d.target(); result.name = d.name(); result.abi = d.abi(); result.sdcardSize = d.sdcardSize(); return result; } QFuture AndroidConfig::createAVD(CreateAvdInfo info) const { return QtConcurrent::run(&AndroidConfig::createAVDImpl, info, androidToolPath(), androidToolEnvironment()); } AndroidConfig::CreateAvdInfo AndroidConfig::createAVDImpl(CreateAvdInfo info, FileName androidToolPath, Environment env) { QProcess proc; proc.setProcessEnvironment(env.toProcessEnvironment()); QStringList arguments; arguments << QLatin1String("create") << QLatin1String("avd") << QLatin1String("-t") << info.target << QLatin1String("-n") << info.name << QLatin1String("-b") << info.abi << QLatin1String("-c") << QString::fromLatin1("%1M").arg(info.sdcardSize); proc.start(androidToolPath.toString(), arguments); if (!proc.waitForStarted()) { info.error = QApplication::translate("AndroidConfig", "Could not start process \"%1 %2\"") .arg(androidToolPath.toString(), arguments.join(QLatin1Char(' '))); return info; } proc.write(QByteArray("yes\n")); // yes to "Do you wish to create a custom hardware profile" QByteArray question; while (true) { proc.waitForReadyRead(500); question += proc.readAllStandardOutput(); if (question.endsWith(QByteArray("]:"))) { // truncate to last line int index = question.lastIndexOf(QByteArray("\n")); if (index != -1) question = question.mid(index); if (question.contains("hw.gpu.enabled")) proc.write(QByteArray("yes\n")); else proc.write(QByteArray("\n")); question.clear(); } if (proc.state() != QProcess::Running) break; } proc.waitForFinished(); QString errorOutput = QString::fromLocal8Bit(proc.readAllStandardError()); // The exit code is always 0, so we need to check stderr // For now assume that any output at all indicates a error if (!errorOutput.isEmpty()) { info.error = errorOutput; } return info; } bool AndroidConfig::removeAVD(const QString &name) const { QProcess proc; proc.setProcessEnvironment(androidToolEnvironment().toProcessEnvironment()); proc.start(androidToolPath().toString(), QStringList() << QLatin1String("delete") << QLatin1String("avd") << QLatin1String("-n") << name); if (!proc.waitForFinished(5000)) { proc.terminate(); return false; } return !proc.exitCode(); } QFuture> AndroidConfig::androidVirtualDevicesFuture() { return QtConcurrent::run(&AndroidConfig::androidVirtualDevicesImpl, androidToolPath(), androidToolEnvironment()); } QVector AndroidConfig::androidVirtualDevices() const { return androidVirtualDevicesImpl(androidToolPath(), androidToolEnvironment()); } QVector AndroidConfig::androidVirtualDevicesImpl(const FileName &androidTool, const Environment &environment) { QVector devices; QProcess proc; proc.setProcessEnvironment(environment.toProcessEnvironment()); proc.start(androidTool.toString(), QStringList() << QLatin1String("list") << QLatin1String("avd")); // list available AVDs if (!proc.waitForFinished(5000)) { proc.terminate(); return devices; } QList avds = proc.readAll().trimmed().split('\n'); if (avds.empty()) return devices; while (avds.first().startsWith("* daemon")) avds.removeFirst(); // remove the daemon logs avds.removeFirst(); // remove "List of devices attached" header line AndroidDeviceInfo dev; for (int i = 0; i < avds.size(); i++) { QString line = QLatin1String(avds.at(i)); if (!line.contains(QLatin1String("Name:"))) continue; int index = line.indexOf(QLatin1Char(':')) + 2; if (index >= line.size()) break; dev.serialNumber = line.mid(index).trimmed(); dev.sdk = -1; dev.cpuAbi.clear(); ++i; for (; i < avds.size(); ++i) { line = QLatin1String(avds[i]); if (line.contains(QLatin1String("---------"))) break; if (line.contains(QLatin1String("Target:"))) { int lastIndex = line.lastIndexOf(QLatin1Char(' ')); if (lastIndex == -1) // skip line break; QString tmp = line.mid(lastIndex).remove(QLatin1Char(')')).trimmed(); if (tmp == QLatin1String("L")) // HACK for android-L preview dev.sdk = 20; else dev.sdk = tmp.toInt(); } if (line.contains(QLatin1String("Tag/ABI:"))) { int lastIndex = line.lastIndexOf(QLatin1Char('/')) + 1; if (lastIndex >= line.size()) break; dev.cpuAbi = QStringList() << line.mid(lastIndex); } else if (line.contains(QLatin1String("ABI:"))) { int lastIndex = line.lastIndexOf(QLatin1Char(' ')) + 1; if (lastIndex >= line.size()) break; dev.cpuAbi = QStringList() << line.mid(lastIndex).trimmed(); } } // armeabi-v7a devices can also run armeabi code if (dev.cpuAbi == QStringList(QLatin1String("armeabi-v7a"))) dev.cpuAbi << QLatin1String("armeabi"); dev.state = AndroidDeviceInfo::OkState; dev.type = AndroidDeviceInfo::Emulator; if (dev.cpuAbi.isEmpty() || dev.sdk == -1) continue; devices.push_back(dev); } Utils::sort(devices, androidDevicesLessThan); return devices; } QString AndroidConfig::startAVD(const QString &name, int apiLevel, QString cpuAbi) const { if (!findAvd(apiLevel, cpuAbi).isEmpty() || startAVDAsync(name)) return waitForAvd(apiLevel, cpuAbi); return QString(); } bool AndroidConfig::startAVDAsync(const QString &avdName) const { QProcess *avdProcess = new QProcess(); avdProcess->connect(avdProcess, SIGNAL(finished(int)), avdProcess, SLOT(deleteLater())); // start the emulator QStringList arguments; if (AndroidConfigurations::force32bitEmulator()) arguments << QLatin1String("-force-32bit"); arguments << QLatin1String("-partition-size") << QString::number(partitionSize()) << QLatin1String("-avd") << avdName; avdProcess->start(emulatorToolPath().toString(), arguments); if (!avdProcess->waitForStarted(-1)) { delete avdProcess; return false; } return true; } QString AndroidConfig::findAvd(int apiLevel, const QString &cpuAbi) const { QVector devices = connectedDevices(); foreach (AndroidDeviceInfo device, devices) { if (!device.serialNumber.startsWith(QLatin1String("emulator"))) continue; if (!device.cpuAbi.contains(cpuAbi)) continue; if (device.sdk != apiLevel) continue; return device.serialNumber; } return QString(); } bool AndroidConfig::isConnected(const QString &serialNumber) const { QVector devices = connectedDevices(); foreach (AndroidDeviceInfo device, devices) { if (device.serialNumber == serialNumber) return true; } return false; } bool AndroidConfig::waitForBooted(const QString &serialNumber, const QFutureInterface &fi) const { // found a serial number, now wait until it's done booting... for (int i = 0; i < 60; ++i) { if (fi.isCanceled()) return false; if (hasFinishedBooting(serialNumber)) { return true; } else { Utils::sleep(2000); if (!isConnected(serialNumber)) // device was disconnected return false; } } return false; } QString AndroidConfig::waitForAvd(int apiLevel, const QString &cpuAbi, const QFutureInterface &fi) const { // we cannot use adb -e wait-for-device, since that doesn't work if a emulator is already running // 60 rounds of 2s sleeping, two minutes for the avd to start QString serialNumber; for (int i = 0; i < 60; ++i) { if (fi.isCanceled()) return QString(); serialNumber = findAvd(apiLevel, cpuAbi); if (!serialNumber.isEmpty()) return waitForBooted(serialNumber, fi) ? serialNumber : QString(); Utils::sleep(2000); } return QString(); } bool AndroidConfig::isBootToQt(const QString &device) const { // workaround for '????????????' serial numbers QStringList arguments = AndroidDeviceInfo::adbSelector(device); arguments << QLatin1String("shell") << QLatin1String("ls -l /system/bin/appcontroller || ls -l /usr/bin/appcontroller && echo Boot2Qt"); QProcess adbProc; adbProc.start(adbToolPath().toString(), arguments); if (!adbProc.waitForFinished(10000)) { adbProc.kill(); return false; } return adbProc.readAll().contains("Boot2Qt"); } int AndroidConfig::getSDKVersion(const QString &device) const { // workaround for '????????????' serial numbers QStringList arguments = AndroidDeviceInfo::adbSelector(device); arguments << QLatin1String("shell") << QLatin1String("getprop") << QLatin1String("ro.build.version.sdk"); QProcess adbProc; adbProc.start(adbToolPath().toString(), arguments); if (!adbProc.waitForFinished(10000)) { adbProc.kill(); return -1; } return adbProc.readAll().trimmed().toInt(); } //! //! \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); // workaround for '????????????' serial numbers QStringList arguments = AndroidDeviceInfo::adbSelector(device); arguments << QLatin1String("shell") << QLatin1String("getprop") << QLatin1String("ro.product.model"); QProcess adbProc; adbProc.start(adbToolPath().toString(), arguments); if (!adbProc.waitForFinished(10000)) { adbProc.kill(); return device; } QString model = QString::fromLocal8Bit(adbProc.readAll().trimmed()); if (model.isEmpty()) return device; if (!device.startsWith(QLatin1String("????"))) m_serialNumberToDeviceName.insert(device, model); return model; } bool AndroidConfig::hasFinishedBooting(const QString &device) const { QStringList arguments = AndroidDeviceInfo::adbSelector(device); arguments << QLatin1String("shell") << QLatin1String("getprop") << QLatin1String("init.svc.bootanim"); QProcess adbProc; adbProc.start(adbToolPath().toString(), arguments); if (!adbProc.waitForFinished(10000)) { adbProc.kill(); return false; } QString value = QString::fromLocal8Bit(adbProc.readAll().trimmed()); if (value == QLatin1String("stopped")) return true; return false; } QStringList AndroidConfig::getAbis(const QString &device) const { QStringList result; // First try via ro.product.cpu.abilist QStringList arguments = AndroidDeviceInfo::adbSelector(device); arguments << QLatin1String("shell") << QLatin1String("getprop"); arguments << QLatin1String("ro.product.cpu.abilist"); QProcess adbProc; adbProc.start(adbToolPath().toString(), arguments); if (!adbProc.waitForFinished(10000)) { adbProc.kill(); return result; } QString output = QString::fromLocal8Bit(adbProc.readAll().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); QProcess adbProc; adbProc.start(adbToolPath().toString(), arguments); if (!adbProc.waitForFinished(10000)) { adbProc.kill(); return result; } QString abi = QString::fromLocal8Bit(adbProc.readAll().trimmed()); if (abi.isEmpty()) break; result << abi; } return result; } SdkPlatform AndroidConfig::highestAndroidSdk() const { updateAvailableSdkPlatforms(); if (m_availableSdkPlatforms.isEmpty()) return SdkPlatform(); return m_availableSdkPlatforms.first(); } QString AndroidConfig::bestNdkPlatformMatch(int target) const { target = std::max(9, target); updateNdkInformation(); foreach (int apiLevel, m_availableNdkPlatforms) { if (apiLevel <= target) return QString::fromLatin1("android-%1").arg(apiLevel); } return QLatin1String("android-9"); } FileName AndroidConfig::sdkLocation() const { return m_sdkLocation; } void AndroidConfig::setSdkLocation(const FileName &sdkLocation) { m_sdkLocation = sdkLocation; m_availableSdkPlatformsUpToDate = false; } FileName AndroidConfig::ndkLocation() const { return m_ndkLocation; } void AndroidConfig::setNdkLocation(const FileName &ndkLocation) { m_ndkLocation = ndkLocation; m_NdkInformationUpToDate = false; } FileName AndroidConfig::antLocation() const { return m_antLocation; } void AndroidConfig::setAntLocation(const FileName &antLocation) { m_antLocation = antLocation; } FileName AndroidConfig::openJDKLocation() const { return m_openJDKLocation; } void AndroidConfig::setOpenJDKLocation(const FileName &openJDKLocation) { m_openJDKLocation = openJDKLocation; m_availableSdkPlatformsUpToDate = false; } FileName AndroidConfig::keystoreLocation() const { return m_keystoreLocation; } void AndroidConfig::setKeystoreLocation(const FileName &keystoreLocation) { m_keystoreLocation = keystoreLocation; } QString AndroidConfig::toolchainHost() const { updateNdkInformation(); return m_toolchainHost; } QStringList AndroidConfig::makeExtraSearchDirectories() const { return m_makeExtraSearchDirectories; } 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; } bool AndroidConfig::useGrandle() const { return m_useGradle; } void AndroidConfig::setUseGradle(bool b) { m_useGradle = b; } /////////////////////////////////// // AndroidConfigurations /////////////////////////////////// void AndroidConfigurations::setConfig(const AndroidConfig &devConfigs) { m_instance->m_config = devConfigs; m_instance->save(); m_instance->updateAndroidDevice(); m_instance->updateToolChainList(); m_instance->updateAutomaticKitList(); emit m_instance->updated(); } AndroidDeviceInfo AndroidConfigurations::showDeviceDialog(Project *project, int apiLevel, const QString &abi, Options options) { QString serialNumber = defaultDevice(project, abi); if (!serialNumber.isEmpty()) { // search for that device foreach (const AndroidDeviceInfo &info, AndroidConfigurations::currentConfig().connectedDevices()) if (info.serialNumber == serialNumber && info.sdk >= apiLevel) return info; foreach (const AndroidDeviceInfo &info, AndroidConfigurations::currentConfig().androidVirtualDevices()) if (info.serialNumber == serialNumber && info.sdk >= apiLevel) return info; } AndroidDeviceDialog dialog(apiLevel, abi, options, Core::ICore::mainWindow()); if (dialog.exec() == QDialog::Accepted) { AndroidDeviceInfo info = dialog.device(); if (dialog.saveDeviceSelection()) { if (!info.serialNumber.isEmpty()) AndroidConfigurations::setDefaultDevice(project, abi, info.serialNumber); } return info; } return AndroidDeviceInfo(); } 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 equalKits(Kit *a, Kit *b) { return ToolChainKitInformation::toolChain(a) == ToolChainKitInformation::toolChain(b) && QtSupport::QtKitInformation::qtVersion(a) == QtSupport::QtKitInformation::qtVersion(b); } void AndroidConfigurations::updateToolChainList() { QList existingToolChains = ToolChainManager::toolChains(); QList toolchains = AndroidToolChainFactory::createToolChainsForNdk(AndroidConfigurations::currentConfig().ndkLocation()); foreach (ToolChain *tc, toolchains) { bool found = false; for (int i = 0; i < existingToolChains.count(); ++i) { if (*(existingToolChains.at(i)) == *tc) { found = true; break; } } if (found) delete tc; else ToolChainManager::registerToolChain(tc); } foreach (ToolChain *tc, existingToolChains) { if (tc->type() == QLatin1String(Constants::ANDROID_TOOLCHAIN_TYPE)) { if (!tc->isValid()) ToolChainManager::deregisterToolChain(tc); } } } void AndroidConfigurations::updateAutomaticKitList() { QList toolchains; if (AndroidConfigurations::currentConfig().automaticKitCreation()) { // having a empty toolchains list will remove all autodetected kits for android // exactly what we want in that case foreach (ToolChain *tc, ToolChainManager::toolChains()) { if (!tc->isAutoDetected()) continue; if (tc->type() != QLatin1String(Constants::ANDROID_TOOLCHAIN_TYPE)) continue; toolchains << static_cast(tc); } } QList existingKits; foreach (Kit *k, KitManager::kits()) { if (DeviceTypeKitInformation::deviceTypeId(k) != Core::Id(Constants::ANDROID_DEVICE_TYPE)) continue; if (!k->isAutoDetected()) continue; if (k->isSdkProvided()) continue; // Update code for 3.0 beta, which shipped with a bug for the debugger settings ToolChain *tc =ToolChainKitInformation::toolChain(k); if (tc && Debugger::DebuggerKitInformation::debuggerCommand(k) != tc->suggestedDebugger()) { Debugger::DebuggerItem debugger; debugger.setCommand(tc->suggestedDebugger()); debugger.setEngineType(Debugger::GdbEngineType); debugger.setDisplayName(tr("Android Debugger for %1").arg(tc->displayName())); debugger.setAutoDetected(true); debugger.setAbi(tc->targetAbi()); QVariant id = Debugger::DebuggerItemManager::registerDebugger(debugger); Debugger::DebuggerKitInformation::setDebugger(k, id); } existingKits << k; } QMap > qtVersionsForArch; foreach (QtSupport::BaseQtVersion *qtVersion, QtSupport::QtVersionManager::versions()) { if (qtVersion->type() != QLatin1String(Constants::ANDROIDQT)) continue; QList qtAbis = qtVersion->qtAbis(); if (qtAbis.empty()) continue; qtVersionsForArch[qtAbis.first().architecture()].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 foreach (Kit *k, existingKits) KitManager::deregisterKit(k); return; } // register new kits QList newKits; foreach (AndroidToolChain *tc, toolchains) { if (tc->isSecondaryToolChain()) continue; QList qtVersions = qtVersionsForArch.value(tc->targetAbi().architecture()); foreach (QtSupport::BaseQtVersion *qt, qtVersions) { Kit *newKit = new Kit; newKit->setAutoDetected(true); newKit->setIconPath(FileName::fromString(QLatin1String(Constants::ANDROID_SETTINGS_CATEGORY_ICON))); DeviceTypeKitInformation::setDeviceTypeId(newKit, Core::Id(Constants::ANDROID_DEVICE_TYPE)); ToolChainKitInformation::setToolChain(newKit, tc); QtSupport::QtKitInformation::setQtVersion(newKit, qt); DeviceKitInformation::setDevice(newKit, device); Debugger::DebuggerItem debugger; debugger.setCommand(tc->suggestedDebugger()); debugger.setEngineType(Debugger::GdbEngineType); debugger.setDisplayName(tr("Android Debugger for %1").arg(tc->displayName())); debugger.setAutoDetected(true); debugger.setAbi(tc->targetAbi()); QVariant id = Debugger::DebuggerItemManager::registerDebugger(debugger); Debugger::DebuggerKitInformation::setDebugger(newKit, id); AndroidGdbServerKitInformation::setGdbSever(newKit, tc->suggestedGdbServer()); newKit->makeSticky(); newKits << newKit; } } for (int i = existingKits.count() - 1; i >= 0; --i) { Kit *existingKit = existingKits.at(i); for (int j = 0; j < newKits.count(); ++j) { Kit *newKit = newKits.at(j); if (equalKits(existingKit, newKit)) { // Kit is already registered, nothing to do newKits.removeAt(j); existingKits.at(i)->makeSticky(); existingKits.removeAt(i); KitManager::deleteKit(newKit); j = newKits.count(); } } } foreach (Kit *k, existingKits) { ToolChain *tc = ToolChainKitInformation::toolChain(k); QtSupport::BaseQtVersion *qtVersion = QtSupport::QtKitInformation::qtVersion(k); if (tc && tc->type() == QLatin1String(Constants::ANDROID_TOOLCHAIN_TYPE) && qtVersion && qtVersion->type() == QLatin1String(Constants::ANDROIDQT)) { k->makeUnSticky(); k->setAutoDetected(false); } else { KitManager::deregisterKit(k); } } foreach (Kit *kit, newKits) { AndroidToolChain *tc = static_cast(ToolChainKitInformation::toolChain(kit)); AndroidQtVersion *qt = static_cast(QtSupport::QtKitInformation::qtVersion(kit)); kit->setUnexpandedDisplayName(tr("Android for %1 (GCC %2, Qt %3)") .arg(qt->targetArch()) .arg(tc->ndkToolChainVersion()) .arg(qt->qtVersionString())); KitManager::registerKit(kit); } } bool AndroidConfigurations::force32bitEmulator() { return m_instance->m_force32bit; } /** * Workaround for '????????????' serial numbers * @return ("-d") for buggy devices, ("-s", ) for normal */ QStringList AndroidDeviceInfo::adbSelector(const QString &serialNumber) { if (serialNumber.startsWith(QLatin1String("????"))) return QStringList() << QLatin1String("-d"); return QStringList() << QLatin1String("-s") << serialNumber; } const AndroidConfig &AndroidConfigurations::currentConfig() { return m_instance->m_config; // ensure that m_instance is initialized } 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(QObject *parent) : QObject(parent) { load(); connect(ProjectExplorer::SessionManager::instance(), SIGNAL(projectRemoved(ProjectExplorer::Project*)), this, SLOT(clearDefaultDevices(ProjectExplorer::Project*))); m_force32bit = is32BitUserSpace(); m_instance = this; } static FileName javaHomeForJavac(const FileName &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 FileName::fromString(dir.path()); if (fileInfo.isSymLink()) fileInfo.setFile(fileInfo.symLinkTarget()); else break; --tries; } return FileName(); } void AndroidConfigurations::load() { bool saveSettings = false; QSettings *settings = Core::ICore::settings(); settings->beginGroup(SettingsGroup); m_config.load(*settings); if (m_config.antLocation().isEmpty()) { Environment env = Environment::systemEnvironment(); FileName location = env.searchInPath(QLatin1String("ant")); QFileInfo fi = location.toFileInfo(); if (fi.exists() && fi.isExecutable() && !fi.isDir()) { m_config.setAntLocation(location); saveSettings = true; } } if (m_config.openJDKLocation().isEmpty()) { if (HostOsInfo::isLinuxHost()) { Environment env = Environment::systemEnvironment(); FileName 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()) { QString javaHome = QLatin1String("/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home"); if (QFileInfo::exists(javaHome)) m_config.setOpenJDKLocation(FileName::fromString(javaHome)); } else if (HostOsInfo::isWindowsHost()) { QSettings settings(QLatin1String("HKEY_LOCAL_MACHINE\\SOFTWARE\\Javasoft\\Java Development Kit"), QSettings::NativeFormat); QStringList allVersions = settings.childGroups(); 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(FileName::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(IDevice::Ptr(new AndroidDevice)); else if (devMgr->find(Constants::ANDROID_DEVICE_ID)) devMgr->removeDevice(Core::Id(Constants::ANDROID_DEVICE_ID)); } AndroidConfigurations *AndroidConfigurations::m_instance = 0; } // namespace Android