summaryrefslogtreecommitdiff
path: root/src/plugins/android
diff options
context:
space:
mode:
authorEike Ziller <eike.ziller@qt.io>2017-04-19 09:56:14 +0200
committerEike Ziller <eike.ziller@qt.io>2017-04-19 09:56:14 +0200
commit88897f3a870de7b756356907801f09a90902e490 (patch)
treea947c7fdb92d9050e9ade763dcb87db192b4585e /src/plugins/android
parent8b6eb5aabb48fb83c3cd92be9a77401ca26a810b (diff)
parent01b2ed7904132f845819e78c84477ac9a66bd1e3 (diff)
downloadqt-creator-88897f3a870de7b756356907801f09a90902e490.tar.gz
Merge remote-tracking branch 'origin/4.3'
Conflicts: src/plugins/genericprojectmanager/genericproject.cpp src/plugins/genericprojectmanager/genericproject.h src/plugins/genericprojectmanager/genericprojectnodes.cpp src/plugins/genericprojectmanager/genericprojectnodes.h Change-Id: Ie0c870f68c8d200a75489b75860987655b2f6175
Diffstat (limited to 'src/plugins/android')
-rw-r--r--src/plugins/android/android.pro10
-rw-r--r--src/plugins/android/android.qbs6
-rw-r--r--src/plugins/android/androidavdmanager.cpp441
-rw-r--r--src/plugins/android/androidavdmanager.h66
-rw-r--r--src/plugins/android/androidbuildapkstep.cpp21
-rw-r--r--src/plugins/android/androidbuildapkstep.h6
-rw-r--r--src/plugins/android/androidbuildapkwidget.cpp13
-rw-r--r--src/plugins/android/androidbuildapkwidget.ui86
-rw-r--r--src/plugins/android/androidconfigurations.cpp432
-rw-r--r--src/plugins/android/androidconfigurations.h60
-rw-r--r--src/plugins/android/androiddebugsupport.cpp10
-rw-r--r--src/plugins/android/androiddeployqtstep.cpp8
-rw-r--r--src/plugins/android/androiddevicedialog.cpp12
-rw-r--r--src/plugins/android/androiddevicedialog.h6
-rw-r--r--src/plugins/android/androidmanager.cpp30
-rw-r--r--src/plugins/android/androidmanager.h1
-rw-r--r--src/plugins/android/androidrunconfiguration.cpp191
-rw-r--r--src/plugins/android/androidrunconfiguration.h66
-rw-r--r--src/plugins/android/androidruncontrol.cpp7
-rw-r--r--src/plugins/android/androidrunner.cpp291
-rw-r--r--src/plugins/android/androidrunner.h3
-rw-r--r--src/plugins/android/androidsdkmanager.cpp337
-rw-r--r--src/plugins/android/androidsdkmanager.h51
-rw-r--r--src/plugins/android/androidsettingswidget.cpp48
-rw-r--r--src/plugins/android/androidsettingswidget.h12
-rw-r--r--src/plugins/android/androidsettingswidget.ui94
-rw-r--r--src/plugins/android/androidtoolmanager.cpp346
-rw-r--r--src/plugins/android/androidtoolmanager.h72
-rw-r--r--src/plugins/android/avddialog.cpp29
-rw-r--r--src/plugins/android/avddialog.h3
30 files changed, 2177 insertions, 581 deletions
diff --git a/src/plugins/android/android.pro b/src/plugins/android/android.pro
index 0df9a4a4ae..3d63124af5 100644
--- a/src/plugins/android/android.pro
+++ b/src/plugins/android/android.pro
@@ -47,7 +47,10 @@ HEADERS += \
android_global.h \
androidbuildapkstep.h \
androidbuildapkwidget.h \
- androidrunnable.h
+ androidrunnable.h \
+ androidtoolmanager.h \
+ androidsdkmanager.h \
+ androidavdmanager.h
SOURCES += \
androidconfigurations.cpp \
@@ -88,7 +91,10 @@ SOURCES += \
androidbuildapkstep.cpp \
androidbuildapkwidget.cpp \
androidqtsupport.cpp \
- androidrunnable.cpp
+ androidrunnable.cpp \
+ androidtoolmanager.cpp \
+ androidsdkmanager.cpp \
+ androidavdmanager.cpp
FORMS += \
androidsettingswidget.ui \
diff --git a/src/plugins/android/android.qbs b/src/plugins/android/android.qbs
index 1bb3296453..c3a2551bae 100644
--- a/src/plugins/android/android.qbs
+++ b/src/plugins/android/android.qbs
@@ -22,6 +22,8 @@ Project {
"android.qrc",
"androidanalyzesupport.cpp",
"androidanalyzesupport.h",
+ "androidavdmanager.cpp",
+ "androidavdmanager.h",
"androidconfigurations.cpp",
"androidconfigurations.h",
"androidconstants.h",
@@ -84,6 +86,8 @@ Project {
"androidrunnable.h",
"androidrunner.cpp",
"androidrunner.h",
+ "androidsdkmanager.cpp",
+ "androidsdkmanager.h",
"androidsettingspage.cpp",
"androidsettingspage.h",
"androidsettingswidget.cpp",
@@ -93,6 +97,8 @@ Project {
"androidsignaloperation.h",
"androidtoolchain.cpp",
"androidtoolchain.h",
+ "androidtoolmanager.cpp",
+ "androidtoolmanager.h",
"avddialog.cpp",
"avddialog.h",
"certificatesmodel.cpp",
diff --git a/src/plugins/android/androidavdmanager.cpp b/src/plugins/android/androidavdmanager.cpp
new file mode 100644
index 0000000000..028fe2c797
--- /dev/null
+++ b/src/plugins/android/androidavdmanager.cpp
@@ -0,0 +1,441 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** 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 "androidavdmanager.h"
+
+#include "androidtoolmanager.h"
+
+#include "utils/algorithm.h"
+#include "utils/qtcassert.h"
+#include "utils/runextensions.h"
+#include "utils/synchronousprocess.h"
+
+#include <QApplication>
+#include <QFileInfo>
+#include <QLoggingCategory>
+#include <QSettings>
+
+#include <chrono>
+
+namespace {
+Q_LOGGING_CATEGORY(avdManagerLog, "qtc.android.avdManager")
+}
+
+namespace Android {
+namespace Internal {
+
+using namespace std;
+
+// Avd list keys to parse avd
+const char avdInfoNameKey[] = "Name:";
+const char avdInfoPathKey[] = "Path:";
+const char avdInfoAbiKey[] = "abi.type";
+const char avdInfoTargetKey[] = "target";
+const char avdInfoErrorKey[] = "Error:";
+
+const QVersionNumber avdManagerIntroVersion(25, 3 ,0);
+
+const int avdCreateTimeoutMs = 30000;
+
+/*!
+ Runs the \c avdmanager tool specific to configuration \a config with arguments \a args. Returns
+ \c true if the command is successfully executed. Output is copied into \a output. The function
+ blocks the calling thread.
+ */
+static bool avdManagerCommand(const AndroidConfig config, const QStringList &args, QString *output)
+{
+ QString avdManagerToolPath = config.avdManagerToolPath().toString();
+ Utils::SynchronousProcess proc;
+ Utils::SynchronousProcessResponse response = proc.runBlocking(avdManagerToolPath, args);
+ if (response.result == Utils::SynchronousProcessResponse::Finished) {
+ if (output)
+ *output = response.allOutput();
+ return true;
+ }
+ return false;
+}
+
+/*!
+ Parses the \a line for a [spaces]key[spaces]value[spaces] pattern and returns
+ \c true if the key is found, \c false otherwise. The value is copied into \a value.
+ */
+static bool valueForKey(QString key, const QString &line, QString *value = nullptr)
+{
+ auto trimmedInput = line.trimmed();
+ if (trimmedInput.startsWith(key)) {
+ if (value)
+ *value = trimmedInput.section(key, 1, 1).trimmed();
+ return true;
+ }
+ return false;
+}
+
+static bool checkForTimeout(const chrono::steady_clock::time_point &start,
+ int msecs = 3000)
+{
+ bool timedOut = false;
+ auto end = chrono::steady_clock::now();
+ if (chrono::duration_cast<chrono::milliseconds>(end-start).count() > msecs)
+ timedOut = true;
+ return timedOut;
+}
+
+static AndroidConfig::CreateAvdInfo createAvdCommand(const AndroidConfig config,
+ const AndroidConfig::CreateAvdInfo &info)
+{
+ AndroidConfig::CreateAvdInfo result = info;
+
+ if (!result.isValid()) {
+ qCDebug(avdManagerLog) << "AVD Create failed. Invalid CreateAvdInfo" << result.name
+ << result.target.name << result.target.apiLevel;
+ result.error = QApplication::translate("AndroidAvdManager",
+ "Cannot create AVD. Invalid input.");
+ return result;
+ }
+
+ QStringList arguments({"create", "avd", "-k", result.target.package, "-n", result.name});
+
+ if (!result.abi.isEmpty()) {
+ SystemImage image = Utils::findOrDefault(result.target.systemImages,
+ Utils::equal(&SystemImage::abiName, result.abi));
+ if (image.isValid()) {
+ arguments << "-k" << image.package;
+ } else {
+ qCDebug(avdManagerLog) << "AVD Create failed. Cannot find system image for the platform"
+ << result.abi << result.target.name;
+ result.error = QApplication::translate("AndroidAvdManager",
+ "Cannot create AVD. Cannot find system image for "
+ "the ABI %1(%2).").arg(result.abi).arg(result.target.name);
+ return result;
+ }
+
+ } else {
+ arguments << "-k" << result.target.package;
+ }
+
+ if (result.sdcardSize > 0)
+ arguments << "-c" << QString::fromLatin1("%1M").arg(result.sdcardSize);
+
+ QProcess proc;
+ proc.start(config.avdManagerToolPath().toString(), arguments);
+ if (!proc.waitForStarted()) {
+ result.error = QApplication::translate("AndroidAvdManager",
+ "Could not start process \"%1 %2\"")
+ .arg(config.avdManagerToolPath().toString(), arguments.join(' '));
+ return result;
+ }
+ QTC_CHECK(proc.state() == QProcess::Running);
+ proc.write(QByteArray("yes\n")); // yes to "Do you wish to create a custom hardware profile"
+
+ auto start = chrono::steady_clock::now();
+ QString errorOutput;
+ QByteArray question;
+ while (errorOutput.isEmpty()) {
+ 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();
+ }
+ // The exit code is always 0, so we need to check stderr
+ // For now assume that any output at all indicates a error
+ errorOutput = QString::fromLocal8Bit(proc.readAllStandardError());
+ if (proc.state() != QProcess::Running)
+ break;
+
+ // For a sane input and command, process should finish before timeout.
+ if (checkForTimeout(start, avdCreateTimeoutMs)) {
+ result.error = QApplication::translate("AndroidAvdManager",
+ "Cannot create AVD. Command timed out.");
+ }
+ }
+
+ // Kill the running process.
+ if (proc.state() != QProcess::NotRunning) {
+ proc.terminate();
+ if (!proc.waitForFinished(3000))
+ proc.kill();
+ }
+
+ QTC_CHECK(proc.state() == QProcess::NotRunning);
+ result.error = errorOutput;
+ return result;
+}
+
+/*!
+ \class AvdManagerOutputParser
+ \brief The AvdManagerOutputParser class is a helper class to parse the output of the avdmanager
+ commands.
+ */
+class AvdManagerOutputParser
+{
+public:
+ AndroidDeviceInfoList listVirtualDevices(const AndroidConfig &config);
+ AndroidDeviceInfoList parseAvdList(const QString &output);
+
+private:
+ bool parseAvd(const QStringList &deviceInfo, AndroidDeviceInfo *avd);
+};
+
+
+AndroidAvdManager::AndroidAvdManager(const AndroidConfig &config):
+ m_config(config),
+ m_androidTool(new AndroidToolManager(m_config)),
+ m_parser(new AvdManagerOutputParser)
+{
+
+}
+
+AndroidAvdManager::~AndroidAvdManager()
+{
+
+}
+
+bool AndroidAvdManager::avdManagerUiToolAvailable() const
+{
+ return m_config.sdkToolsVersion() < avdManagerIntroVersion;
+}
+
+void AndroidAvdManager::launchAvdManagerUiTool() const
+{
+ if (avdManagerUiToolAvailable()) {
+ m_androidTool->launchAvdManager();
+ } else {
+ qCDebug(avdManagerLog) << "AVD Ui tool launch failed. UI tool not available"
+ << m_config.sdkToolsVersion();
+ }
+}
+
+QFuture<AndroidConfig::CreateAvdInfo>
+AndroidAvdManager::createAvd(AndroidConfig::CreateAvdInfo info) const
+{
+ if (m_config.sdkToolsVersion() < avdManagerIntroVersion)
+ return m_androidTool->createAvd(info);
+
+ return Utils::runAsync(&createAvdCommand, m_config, info);
+}
+
+bool AndroidAvdManager::removeAvd(const QString &name) const
+{
+ if (m_config.sdkToolsVersion() < avdManagerIntroVersion)
+ return m_androidTool->removeAvd(name);
+
+ Utils::SynchronousProcess proc;
+ proc.setTimeoutS(5);
+ Utils::SynchronousProcessResponse response
+ = proc.runBlocking(m_config.avdManagerToolPath().toString(),
+ QStringList({"delete", "avd", "-n", name}));
+ return response.result == Utils::SynchronousProcessResponse::Finished && response.exitCode == 0;
+}
+
+QFuture<AndroidDeviceInfoList> AndroidAvdManager::avdList() const
+{
+ if (m_config.sdkToolsVersion() < avdManagerIntroVersion)
+ return m_androidTool->androidVirtualDevicesFuture();
+
+ return Utils::runAsync(&AvdManagerOutputParser::listVirtualDevices, m_parser.get(), m_config);
+}
+
+QString AndroidAvdManager::startAvd(const QString &name) const
+{
+ if (!findAvd(name).isEmpty() || startAvdAsync(name))
+ return waitForAvd(name);
+ return QString();
+}
+
+bool AndroidAvdManager::startAvdAsync(const QString &avdName) const
+{
+ QProcess *avdProcess = new QProcess();
+ QObject::connect(avdProcess, static_cast<void (QProcess::*)(int)>(&QProcess::finished),
+ avdProcess, &QObject::deleteLater);
+
+ // start the emulator
+ QStringList arguments;
+ if (AndroidConfigurations::force32bitEmulator())
+ arguments << "-force-32bit";
+
+ arguments << "-partition-size" << QString::number(m_config.partitionSize())
+ << "-avd" << avdName;
+ avdProcess->start(m_config.emulatorToolPath().toString(), arguments);
+ if (!avdProcess->waitForStarted(-1)) {
+ delete avdProcess;
+ return false;
+ }
+ return true;
+}
+
+QString AndroidAvdManager::findAvd(const QString &avdName) const
+{
+ QVector<AndroidDeviceInfo> devices = m_config.connectedDevices();
+ foreach (AndroidDeviceInfo device, devices) {
+ if (device.type != AndroidDeviceInfo::Emulator)
+ continue;
+ if (device.avdname == avdName)
+ return device.serialNumber;
+ }
+ return QString();
+}
+
+QString AndroidAvdManager::waitForAvd(const QString &avdName, const QFutureInterface<bool> &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(avdName);
+ if (!serialNumber.isEmpty())
+ return waitForBooted(serialNumber, fi) ? serialNumber : QString();
+ QThread::sleep(2);
+ }
+ return QString();
+}
+
+bool AndroidAvdManager::isAvdBooted(const QString &device) const
+{
+ QStringList arguments = AndroidDeviceInfo::adbSelector(device);
+ arguments << "shell" << "getprop" << "init.svc.bootanim";
+
+ Utils::SynchronousProcess adbProc;
+ adbProc.setTimeoutS(10);
+ Utils::SynchronousProcessResponse response =
+ adbProc.runBlocking(m_config.adbToolPath().toString(), arguments);
+ if (response.result != Utils::SynchronousProcessResponse::Finished)
+ return false;
+ QString value = response.allOutput().trimmed();
+ return value == "stopped";
+}
+
+bool AndroidAvdManager::waitForBooted(const QString &serialNumber, const QFutureInterface<bool> &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 (isAvdBooted(serialNumber)) {
+ return true;
+ } else {
+ QThread::sleep(2);
+ if (!m_config.isConnected(serialNumber)) // device was disconnected
+ return false;
+ }
+ }
+ return false;
+}
+
+AndroidDeviceInfoList AvdManagerOutputParser::listVirtualDevices(const AndroidConfig &config)
+{
+ QString output;
+ if (!avdManagerCommand(config, QStringList({"list", "avd"}), &output)) {
+ qCDebug(avdManagerLog) << "Avd list command failed" << output << config.sdkToolsVersion();
+ return {};
+ }
+ return parseAvdList(output);
+}
+
+AndroidDeviceInfoList AvdManagerOutputParser::parseAvdList(const QString &output)
+{
+ AndroidDeviceInfoList avdList;
+ QStringList avdInfo;
+ auto parseAvdInfo = [&avdInfo, &avdList, this] () {
+ AndroidDeviceInfo avd;
+ if (parseAvd(avdInfo, &avd)) {
+ // armeabi-v7a devices can also run armeabi code
+ if (avd.cpuAbi.contains("armeabi-v7a"))
+ avd.cpuAbi << "armeabi";
+ avd.state = AndroidDeviceInfo::OkState;
+ avd.type = AndroidDeviceInfo::Emulator;
+ avdList << avd;
+ } else {
+ qCDebug(avdManagerLog) << "Avd Parsing: Parsing failed: " << avdInfo;
+ }
+ avdInfo.clear();
+ };
+
+ foreach (QString line, output.split('\n')) {
+ if (line.startsWith("---------") || line.isEmpty()) {
+ parseAvdInfo();
+ } else {
+ avdInfo << line;
+ }
+ }
+
+ if (!avdInfo.isEmpty())
+ parseAvdInfo();
+
+ Utils::sort(avdList);
+
+ return avdList;
+}
+
+bool AvdManagerOutputParser::parseAvd(const QStringList &deviceInfo, AndroidDeviceInfo *avd)
+{
+ QTC_ASSERT(avd, return false);
+ foreach (const QString &line, deviceInfo) {
+ QString value;
+ if (valueForKey(avdInfoErrorKey, line)) {
+ qCDebug(avdManagerLog) << "Avd Parsing: Skip avd device. Error key found:" << line;
+ return false;
+ } else if (valueForKey(avdInfoNameKey, line, &value)) {
+ avd->avdname = value;
+ } else if (valueForKey(avdInfoPathKey, line, &value)) {
+ const Utils::FileName avdPath = Utils::FileName::fromString(value);
+ if (avdPath.exists())
+ {
+ // Get ABI.
+ Utils::FileName configFile = avdPath;
+ configFile.appendPath("config.ini");
+ QSettings config(configFile.toString(), QSettings::IniFormat);
+ value = config.value(avdInfoAbiKey).toString();
+ if (!value.isEmpty())
+ avd->cpuAbi << value;
+ else
+ qCDebug(avdManagerLog) << "Avd Parsing: Cannot find ABI:" << configFile;
+
+ // Get Target
+ Utils::FileName avdInfoFile = avdPath.parentDir();
+ QString avdInfoFileName = avdPath.toFileInfo().baseName() + ".ini";
+ avdInfoFile.appendPath(avdInfoFileName);
+ QSettings avdInfo(avdInfoFile.toString(), QSettings::IniFormat);
+ value = avdInfo.value(avdInfoTargetKey).toString();
+ if (!value.isEmpty())
+ avd->sdk = value.section('-', -1).toInt();
+ else
+ qCDebug(avdManagerLog) << "Avd Parsing: Cannot find sdk API:" << avdInfoFile.toString();
+ }
+ }
+ }
+ return true;
+}
+
+} // namespace Internal
+} // namespace Android
diff --git a/src/plugins/android/androidavdmanager.h b/src/plugins/android/androidavdmanager.h
new file mode 100644
index 0000000000..4e8633efda
--- /dev/null
+++ b/src/plugins/android/androidavdmanager.h
@@ -0,0 +1,66 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** 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.
+**
+****************************************************************************/
+#pragma once
+
+#include "androidconfigurations.h"
+
+#include <memory>
+
+namespace Android {
+namespace Internal {
+
+class AndroidToolManager;
+class AvdManagerOutputParser;
+
+class AndroidAvdManager
+{
+public:
+ AndroidAvdManager(const AndroidConfig& config = AndroidConfigurations::currentConfig());
+ ~AndroidAvdManager();
+
+ bool avdManagerUiToolAvailable() const;
+ void launchAvdManagerUiTool() const;
+ QFuture<AndroidConfig::CreateAvdInfo> createAvd(AndroidConfig::CreateAvdInfo info) const;
+ bool removeAvd(const QString &name) const;
+ QFuture<AndroidDeviceInfoList> avdList() const;
+
+ QString startAvd(const QString &name) const;
+ bool startAvdAsync(const QString &avdName) const;
+ QString findAvd(const QString &avdName) const;
+ QString waitForAvd(const QString &avdName,
+ const QFutureInterface<bool> &fi = QFutureInterface<bool>()) const;
+ bool isAvdBooted(const QString &device) const;
+
+private:
+ bool waitForBooted(const QString &serialNumber, const QFutureInterface<bool> &fi) const;
+
+private:
+ const AndroidConfig &m_config;
+ std::unique_ptr<AndroidToolManager> m_androidTool;
+ std::unique_ptr<AvdManagerOutputParser> m_parser;
+};
+
+} // namespace Internal
+} // namespace Android
diff --git a/src/plugins/android/androidbuildapkstep.cpp b/src/plugins/android/androidbuildapkstep.cpp
index 0ff998809f..9966b21faf 100644
--- a/src/plugins/android/androidbuildapkstep.cpp
+++ b/src/plugins/android/androidbuildapkstep.cpp
@@ -60,6 +60,8 @@
namespace Android {
using namespace Internal;
+const QVersionNumber gradleScriptRevokedSdkVersion(25, 3, 0);
+const QVersionNumber gradleScriptsContainedQtVersion(5, 9, 0);
const QLatin1String DeployActionKey("Qt4ProjectManager.AndroidDeployQtStep.DeployQtAction");
const QLatin1String KeystoreLocationKey("KeystoreLocation");
const QLatin1String BuildTargetSdkKey("BuildTargetSdk");
@@ -140,6 +142,15 @@ bool AndroidBuildApkStep::init(QList<const BuildStep *> &earlierSteps)
if (!version)
return false;
+ if (AndroidConfigurations::currentConfig().sdkToolsVersion() >= gradleScriptRevokedSdkVersion &&
+ QVersionNumber::fromString(version->qtVersionString()) < gradleScriptsContainedQtVersion) {
+ emit addOutput(tr("The installed SDK tools version (%1) does not include Gradle scripts. The "
+ "minimum Qt version required for Gradle build to work is %2")
+ .arg(gradleScriptRevokedSdkVersion.toString())
+ .arg(gradleScriptsContainedQtVersion.toString()), OutputFormat::Stderr);
+ return false;
+ }
+
int minSDKForKit = AndroidManager::minimumSDK(target()->kit());
if (AndroidManager::minimumSDK(target()) < minSDKForKit) {
emit addOutput(tr("The API level set for the APK is less than the minimum required by the kit."
@@ -342,6 +353,16 @@ void AndroidBuildApkStep::setUseGradle(bool b)
}
}
+bool AndroidBuildApkStep::addDebugger() const
+{
+ return m_addDebugger;
+}
+
+void AndroidBuildApkStep::setAddDebugger(bool debug)
+{
+ m_addDebugger = debug;
+}
+
bool AndroidBuildApkStep::verboseOutput() const
{
return m_verbose;
diff --git a/src/plugins/android/androidbuildapkstep.h b/src/plugins/android/androidbuildapkstep.h
index 0d044c0ccc..eae827eb15 100644
--- a/src/plugins/android/androidbuildapkstep.h
+++ b/src/plugins/android/androidbuildapkstep.h
@@ -73,6 +73,9 @@ public:
bool useGradle() const;
void setUseGradle(bool b);
+ bool addDebugger() const;
+ void setAddDebugger(bool debug);
+
QString buildTargetSdk() const;
void setBuildTargetSdk(const QString &sdk);
@@ -99,9 +102,10 @@ protected:
AndroidDeployAction m_deployAction = BundleLibrariesDeployment;
bool m_signPackage = false;
bool m_verbose = false;
- bool m_useGradle = false;
+ bool m_useGradle = true; // Ant builds are deprecated.
bool m_openPackageLocation = false;
bool m_openPackageLocationForRun = false;
+ bool m_addDebugger = true;
QString m_buildTargetSdk;
Utils::FileName m_keystorePath;
diff --git a/src/plugins/android/androidbuildapkwidget.cpp b/src/plugins/android/androidbuildapkwidget.cpp
index 2e85c243fc..97dd4695bd 100644
--- a/src/plugins/android/androidbuildapkwidget.cpp
+++ b/src/plugins/android/androidbuildapkwidget.cpp
@@ -54,9 +54,12 @@ AndroidBuildApkWidget::AndroidBuildApkWidget(AndroidBuildApkStep *step)
{
m_ui->setupUi(this);
+ m_ui->deprecatedInfoIconLabel->setPixmap(Utils::Icons::INFO.pixmap());
+
// Target sdk combobox
int minApiLevel = 9;
- QStringList targets = AndroidConfig::apiLevelNamesFor(AndroidConfigurations::currentConfig().sdkTargets(minApiLevel));
+ const AndroidConfig &config = AndroidConfigurations::currentConfig();
+ QStringList targets = AndroidConfig::apiLevelNamesFor(config.sdkTargets(minApiLevel));
targets.removeDuplicates();
m_ui->targetSDKComboBox->addItems(targets);
m_ui->targetSDKComboBox->setCurrentIndex(targets.indexOf(AndroidManager::buildTargetSDK(step->target())));
@@ -91,9 +94,12 @@ AndroidBuildApkWidget::AndroidBuildApkWidget(AndroidBuildApkStep *step)
m_ui->signingDebugDeployErrorIcon->setPixmap(Utils::Icons::CRITICAL.pixmap());
signPackageCheckBoxToggled(m_step->signPackage());
- m_ui->useGradleCheckBox->setChecked(m_step->useGradle());
+ m_ui->useGradleCheckBox->setEnabled(config.antScriptsAvailable());
+ m_ui->useGradleCheckBox->setChecked(!config.antScriptsAvailable() ||
+ m_step->useGradle());
m_ui->verboseOutputCheckBox->setChecked(m_step->verboseOutput());
m_ui->openPackageLocationCheckBox->setChecked(m_step->openPackageLocation());
+ m_ui->addDebuggerCheckBox->setChecked(m_step->addDebugger());
// target sdk
connect(m_ui->targetSDKComboBox,
@@ -120,6 +126,8 @@ AndroidBuildApkWidget::AndroidBuildApkWidget(AndroidBuildApkStep *step)
this, &AndroidBuildApkWidget::openPackageLocationCheckBoxToggled);
connect(m_ui->verboseOutputCheckBox, &QAbstractButton::toggled,
this, &AndroidBuildApkWidget::verboseOutputCheckBoxToggled);
+ connect(m_ui->addDebuggerCheckBox, &QAbstractButton::toggled,
+ m_step, &AndroidBuildApkStep::setAddDebugger);
//signing
connect(m_ui->signPackageCheckBox, &QAbstractButton::toggled,
@@ -185,6 +193,7 @@ void AndroidBuildApkWidget::signPackageCheckBoxToggled(bool checked)
{
m_ui->certificatesAliasComboBox->setEnabled(checked);
m_step->setSignPackage(checked);
+ m_ui->addDebuggerCheckBox->setChecked(!checked);
updateSigningWarning();
if (!checked)
return;
diff --git a/src/plugins/android/androidbuildapkwidget.ui b/src/plugins/android/androidbuildapkwidget.ui
index e5565873af..fa3c1ef3d6 100644
--- a/src/plugins/android/androidbuildapkwidget.ui
+++ b/src/plugins/android/androidbuildapkwidget.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>819</width>
- <height>390</height>
+ <height>478</height>
</rect>
</property>
<property name="windowTitle">
@@ -176,24 +176,75 @@ Deploying local Qt libraries is incompatible with Android 5.</string>
<string>Advanced Actions</string>
</property>
<layout class="QGridLayout" name="gridLayout">
- <item row="2" column="0">
- <widget class="QCheckBox" name="verboseOutputCheckBox">
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="useGradleCheckBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
<property name="text">
- <string>Verbose output</string>
+ <string>Use Gradle (Ant builds are deprecated)</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="deprecatedInfoIconLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Gradle builds are forced from Android SDK tools version 25.3.0 onwards as Ant scripts are no longer available.</string>
+ </property>
+ <property name="text">
+ <string/>
</property>
</widget>
</item>
- <item row="1" column="0">
+ <item row="0" column="2">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Preferred</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="0" colspan="3">
<widget class="QCheckBox" name="openPackageLocationCheckBox">
<property name="text">
<string>Open package location after build</string>
</property>
</widget>
</item>
- <item row="0" column="0">
- <widget class="QCheckBox" name="useGradleCheckBox">
+ <item row="2" column="0" colspan="3">
+ <widget class="QCheckBox" name="verboseOutputCheckBox">
+ <property name="text">
+ <string>Verbose output</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0" colspan="3">
+ <widget class="QCheckBox" name="addDebuggerCheckBox">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="toolTip">
+ <string>Packages debug server with the APK to enable debugging. For the signed APK this option is unchecked by default.</string>
+ </property>
<property name="text">
- <string>Use Gradle</string>
+ <string>Add debug server</string>
</property>
</widget>
</item>
@@ -254,5 +305,22 @@ The APK will not be usable on any other device.</string>
</customwidget>
</customwidgets>
<resources/>
- <connections/>
+ <connections>
+ <connection>
+ <sender>signPackageCheckBox</sender>
+ <signal>clicked(bool)</signal>
+ <receiver>addDebuggerCheckBox</receiver>
+ <slot>setEnabled(bool)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>113</x>
+ <y>178</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>510</x>
+ <y>452</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
</ui>
diff --git a/src/plugins/android/androidconfigurations.cpp b/src/plugins/android/androidconfigurations.cpp
index 851b0f2350..42027743b8 100644
--- a/src/plugins/android/androidconfigurations.cpp
+++ b/src/plugins/android/androidconfigurations.cpp
@@ -28,8 +28,11 @@
#include "androidtoolchain.h"
#include "androiddevice.h"
#include "androidgdbserverkitinformation.h"
+#include "androidmanager.h"
#include "androidqtversion.h"
#include "androiddevicedialog.h"
+#include "androidsdkmanager.h"
+#include "androidtoolmanager.h"
#include "avddialog.h"
#include <coreplugin/icore.h>
@@ -73,6 +76,9 @@ namespace Android {
using namespace Internal;
namespace {
+
+ const QVersionNumber sdkToolsAntMissingVersion(25, 3, 0);
+
const QLatin1String SettingsGroup("AndroidConfigurations");
const QLatin1String SDKLocationKey("SDKLocation");
const QLatin1String NDKLocationKey("NDKLocation");
@@ -107,39 +113,14 @@ namespace {
const QLatin1String keytoolName("keytool");
const QLatin1String changeTimeStamp("ChangeTimeStamp");
+ const QLatin1String sdkToolsVersionKey("Pkg.Revision");
+
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;
- if (dev1.avdname != dev2.avdname)
- return dev1.avdname < dev2.avdname;
-
- 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:
@@ -162,25 +143,6 @@ namespace {
}
return false;
}
-
- // Some preview sdks use a non integer version
- int apiLevelFromAndroidList(const QString &string)
- {
- bool ok;
- int result = string.toInt(&ok);
- if (ok)
- return result;
- Utils::FileName sdkLocation = AndroidConfigurations::currentConfig().sdkLocation();
- sdkLocation.appendPath(QLatin1String("/platforms/android-") + string + QLatin1String("/source.properties"));
- result = QSettings(sdkLocation.toString(), QSettings::IniFormat).value(QLatin1String("AndroidVersion.ApiLevel")).toInt(&ok);
- if (ok)
- return result;
- if (string == QLatin1String("L"))
- return 21;
- if (string == QLatin1String("MNC"))
- return 22;
- return 23; // At least
- }
}
//////////////////////////////////
@@ -359,61 +321,14 @@ void AndroidConfig::updateNdkInformation() const
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();
-
- SynchronousProcess proc;
- proc.setProcessEnvironment(androidToolEnvironment().toProcessEnvironment());
- SynchronousProcessResponse response
- = proc.runBlocking(androidToolPath().toString(),
- QStringList({"list", "target"})); // list available AVDs
- if (response.result != SynchronousProcessResponse::Finished)
- return;
-
- SdkPlatform platform;
- foreach (const QString &l, response.allOutput().split('\n')) {
- const QString line = l.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);
- const QString tmp = androidTarget.mid(androidTarget.lastIndexOf(QLatin1Char('-')) + 1);
- platform.apiLevel = apiLevelFromAndroidList(tmp);
- } 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 = std::lower_bound(m_availableSdkPlatforms.begin(), m_availableSdkPlatforms.end(),
- platform, sortSdkPlatformByApiLevel);
- m_availableSdkPlatforms.insert(it, platform);
- platform = SdkPlatform();
- }
- }
-
- if (platform.apiLevel != -1) {
- auto it = std::lower_bound(m_availableSdkPlatforms.begin(), m_availableSdkPlatforms.end(),
- platform, sortSdkPlatformByApiLevel);
- m_availableSdkPlatforms.insert(it, platform);
- }
+ m_availableSdkPlatforms.clear();
+ AndroidSdkManager sdkManager(*this);
+ m_availableSdkPlatforms = sdkManager.availableSdkPlatforms();
m_availableSdkPlatformsUpToDate = true;
}
@@ -446,18 +361,6 @@ FileName AndroidConfig::adbToolPath() const
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());
- Utils::FileName binPath = m_openJDKLocation;
- binPath.appendPath(QLatin1String("bin"));
- env.prependOrSetPath(binPath.toUserOutput());
- }
- return env;
-}
-
FileName AndroidConfig::androidToolPath() const
{
if (HostOsInfo::isWindowsHost()) {
@@ -486,7 +389,10 @@ FileName AndroidConfig::antToolPath() const
FileName AndroidConfig::emulatorToolPath() const
{
FileName path = m_sdkLocation;
- return path.appendPath(QLatin1String("tools/emulator" QTC_HOST_EXE_SUFFIX));
+ QString relativePath = "emulator/emulator";
+ if (sdkToolsVersion() < QVersionNumber(25, 3, 0))
+ relativePath = "tools/emulator";
+ return path.appendPath(relativePath + QTC_HOST_EXE_SUFFIX);
}
FileName AndroidConfig::toolPath(const Abi &abi, const QString &ndkToolChainVersion) const
@@ -499,6 +405,26 @@ FileName AndroidConfig::toolPath(const Abi &abi, const QString &ndkToolChainVers
.arg(toolsPrefix(abi)));
}
+FileName AndroidConfig::sdkManagerToolPath() const
+{
+ FileName sdkPath = m_sdkLocation;
+ QString toolPath = "tools/bin/sdkmanager";
+ if (HostOsInfo::isWindowsHost())
+ toolPath += ANDROID_BAT_SUFFIX;
+ sdkPath = sdkPath.appendPath(toolPath);
+ return sdkPath;
+}
+
+FileName AndroidConfig::avdManagerToolPath() const
+{
+ FileName avdManagerPath = m_sdkLocation;
+ QString toolPath = "tools/bin/avdmanager";
+ if (HostOsInfo::isWindowsHost())
+ toolPath += ANDROID_BAT_SUFFIX;
+ avdManagerPath = avdManagerPath.appendPath(toolPath);
+ return avdManagerPath;
+}
+
FileName AndroidConfig::gccPath(const Abi &abi, Core::Id lang,
const QString &ndkToolChainVersion) const
{
@@ -583,7 +509,7 @@ QVector<AndroidDeviceInfo> AndroidConfig::connectedDevices(const QString &adbToo
devices.push_back(dev);
}
- Utils::sort(devices, androidDevicesLessThan);
+ Utils::sort(devices);
if (devices.isEmpty() && error)
*error = QApplication::translate("AndroidConfiguration",
"No devices found in output of: %1")
@@ -605,197 +531,6 @@ AndroidConfig::CreateAvdInfo AndroidConfig::gatherCreateAVDInfo(QWidget *parent,
return result;
}
-QFuture<AndroidConfig::CreateAvdInfo> AndroidConfig::createAVD(CreateAvdInfo info) const
-{
- return Utils::runAsync(&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;
- if (info.sdcardSize > 0)
- arguments << 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;
- }
- QTC_CHECK(proc.state() == QProcess::Running);
- 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;
- }
- QTC_CHECK(proc.state() == QProcess::NotRunning);
-
- 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
-{
- SynchronousProcess proc;
- proc.setTimeoutS(5);
- proc.setProcessEnvironment(androidToolEnvironment().toProcessEnvironment());
- SynchronousProcessResponse response
- = proc.runBlocking(androidToolPath().toString(), QStringList({"delete", "avd", "-n", name}));
- return response.result == SynchronousProcessResponse::Finished && response.exitCode == 0;
-}
-
-QFuture<QVector<AndroidDeviceInfo>> AndroidConfig::androidVirtualDevicesFuture() const
-{
- return Utils::runAsync(&AndroidConfig::androidVirtualDevices,
- androidToolPath().toString(), androidToolEnvironment());
-}
-
-QVector<AndroidDeviceInfo> AndroidConfig::androidVirtualDevices(const QString &androidTool, const Environment &environment)
-{
- QVector<AndroidDeviceInfo> devices;
- SynchronousProcess proc;
- proc.setTimeoutS(20);
- proc.setProcessEnvironment(environment.toProcessEnvironment());
- SynchronousProcessResponse response = proc.run(androidTool, {"list", "avd"}); // list available AVDs
- if (response.result != SynchronousProcessResponse::Finished)
- return devices;
-
- QStringList avds = response.allOutput().split('\n');
- if (avds.empty())
- return devices;
-
- while (avds.first().startsWith(QLatin1String("* daemon")))
- avds.removeFirst(); // remove the daemon logs
- avds.removeFirst(); // remove "List of devices attached" header line
-
- bool nextLineIsTargetLine = false;
-
- AndroidDeviceInfo dev;
- for (int i = 0; i < avds.size(); i++) {
- QString line = avds.at(i);
- if (!line.contains(QLatin1String("Name:")))
- continue;
-
- int index = line.indexOf(QLatin1Char(':')) + 2;
- if (index >= line.size())
- break;
- dev.avdname = line.mid(index).trimmed();
- dev.sdk = -1;
- dev.cpuAbi.clear();
- ++i;
- for (; i < avds.size(); ++i) {
- line = avds.at(i);
- if (line.contains(QLatin1String("---------")))
- break;
-
- if (line.contains(QLatin1String("Target:")) || nextLineIsTargetLine) {
- if (line.contains(QLatin1String("Google APIs"))) {
- nextLineIsTargetLine = true;
- continue;
- }
-
- nextLineIsTargetLine = false;
-
- int lastIndex = line.lastIndexOf(QLatin1Char(' '));
- if (lastIndex == -1) // skip line
- break;
- QString tmp = line.mid(lastIndex).remove(QLatin1Char(')')).trimmed();
- dev.sdk = apiLevelFromAndroidList(tmp);
- }
- 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("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) const
-{
- if (!findAvd(name).isEmpty() || startAVDAsync(name))
- return waitForAvd(name);
- return QString();
-}
-
-bool AndroidConfig::startAVDAsync(const QString &avdName) const
-{
- QProcess *avdProcess = new QProcess();
- QObject::connect(avdProcess, static_cast<void (QProcess::*)(int)>(&QProcess::finished),
- avdProcess, &QObject::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(const QString &avdName) const
-{
- QVector<AndroidDeviceInfo> devices = connectedDevices();
- foreach (AndroidDeviceInfo device, devices) {
- if (device.type != AndroidDeviceInfo::Emulator)
- continue;
- if (device.avdname == avdName)
- return device.serialNumber;
- }
- return QString();
-}
-
bool AndroidConfig::isConnected(const QString &serialNumber) const
{
QVector<AndroidDeviceInfo> devices = connectedDevices();
@@ -806,39 +541,6 @@ bool AndroidConfig::isConnected(const QString &serialNumber) const
return false;
}
-bool AndroidConfig::waitForBooted(const QString &serialNumber, const QFutureInterface<bool> &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 {
- QThread::sleep(2);
- if (!isConnected(serialNumber)) // device was disconnected
- return false;
- }
- }
- return false;
-}
-
-QString AndroidConfig::waitForAvd(const QString &avdName, const QFutureInterface<bool> &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(avdName);
- if (!serialNumber.isEmpty())
- return waitForBooted(serialNumber, fi) ? serialNumber : QString();
- QThread::sleep(2);
- }
- return QString();
-}
-
bool AndroidConfig::isBootToQt(const QString &device) const
{
return isBootToQt(adbToolPath().toString(), device);
@@ -961,21 +663,6 @@ QString AndroidConfig::getProductModel(const QString &device) const
return model;
}
-bool AndroidConfig::hasFinishedBooting(const QString &device) const
-{
- QStringList arguments = AndroidDeviceInfo::adbSelector(device);
- arguments << QLatin1String("shell") << QLatin1String("getprop")
- << QLatin1String("init.svc.bootanim");
-
- SynchronousProcess adbProc;
- adbProc.setTimeoutS(10);
- SynchronousProcessResponse response = adbProc.runBlocking(adbToolPath().toString(), arguments);
- if (response.result != SynchronousProcessResponse::Finished)
- return false;
- QString value = response.allOutput().trimmed();
- return value == QLatin1String("stopped");
-}
-
QStringList AndroidConfig::getAbis(const QString &device) const
{
return getAbis(adbToolPath().toString(), device);
@@ -1053,6 +740,19 @@ void AndroidConfig::setSdkLocation(const FileName &sdkLocation)
m_availableSdkPlatformsUpToDate = false;
}
+QVersionNumber AndroidConfig::sdkToolsVersion() const
+{
+ QVersionNumber version;
+ if (m_sdkLocation.exists()) {
+ Utils::FileName sdkToolsPropertiesPath(m_sdkLocation);
+ sdkToolsPropertiesPath.appendPath("tools/source.properties");
+ QSettings settings(sdkToolsPropertiesPath.toString(), QSettings::IniFormat);
+ auto versionStr = settings.value(sdkToolsVersionKey).toString();
+ version = QVersionNumber::fromString(versionStr);
+ }
+ return version;
+}
+
FileName AndroidConfig::ndkLocation() const
{
return m_ndkLocation;
@@ -1126,9 +826,18 @@ void AndroidConfig::setAutomaticKitCreation(bool b)
m_automaticKitCreation = b;
}
+bool AndroidConfig::antScriptsAvailable() const
+{
+ return sdkToolsVersion() < sdkToolsAntMissingVersion;
+}
+
bool AndroidConfig::useGrandle() const
{
- return m_useGradle;
+ if (antScriptsAvailable()) {
+ return m_useGradle;
+ }
+ // Force gradle builds.
+ return true;
}
void AndroidConfig::setUseGradle(bool b)
@@ -1353,6 +1062,20 @@ QStringList AndroidDeviceInfo::adbSelector(const QString &serialNumber)
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
@@ -1494,4 +1217,13 @@ void AndroidConfigurations::updateAndroidDevice()
AndroidConfigurations *AndroidConfigurations::m_instance = 0;
+bool SdkPlatform::operator <(const SdkPlatform &other) const
+{
+ if (apiLevel != other.apiLevel)
+ return apiLevel > other.apiLevel;
+ if (name != other.name)
+ return name < other.name;
+ return false;
+}
+
} // namespace Android
diff --git a/src/plugins/android/androidconfigurations.h b/src/plugins/android/androidconfigurations.h
index 751e68d113..9107680810 100644
--- a/src/plugins/android/androidconfigurations.h
+++ b/src/plugins/android/androidconfigurations.h
@@ -36,6 +36,7 @@
#include <QHash>
#include <QMap>
#include <QFutureInterface>
+#include <QVersionNumber>
#include <utils/fileutils.h>
@@ -68,19 +69,36 @@ public:
static QStringList adbSelector(const QString &serialNumber);
- bool isValid() { return !serialNumber.isEmpty() || !avdname.isEmpty(); }
+ bool isValid() const { return !serialNumber.isEmpty() || !avdname.isEmpty(); }
+ bool operator<(const AndroidDeviceInfo &other) const;
};
+using AndroidDeviceInfoList = QList<AndroidDeviceInfo>;
+
+//! Defines an Android system image.
+class SystemImage
+{
+public:
+ bool isValid() const { return (apiLevel != -1) && !abiName.isEmpty(); }
+ int apiLevel = -1;
+ QString abiName;
+ QString package;
+ Utils::FileName installedLocation;
+};
+using SystemImageList = QList<SystemImage>;
+
class SdkPlatform
{
public:
- SdkPlatform()
- : apiLevel(-1)
- {}
- int apiLevel;
+ bool isValid() const { return !name.isEmpty() && apiLevel != -1; }
+ bool operator <(const SdkPlatform &other) const;
+ int apiLevel = -1;
QString name;
- QStringList abis;
+ QString package;
+ Utils::FileName installedLocation;
+ SystemImageList systemImages;
};
+using SdkPlatformList = QList<SdkPlatform>;
class ANDROID_EXPORT AndroidConfig
{
@@ -94,6 +112,7 @@ public:
Utils::FileName sdkLocation() const;
void setSdkLocation(const Utils::FileName &sdkLocation);
+ QVersionNumber sdkToolsVersion() const;
Utils::FileName ndkLocation() const;
void setNdkLocation(const Utils::FileName &ndkLocation);
@@ -116,18 +135,21 @@ public:
bool automaticKitCreation() const;
void setAutomaticKitCreation(bool b);
+ bool antScriptsAvailable() const;
+
bool useGrandle() const;
void setUseGradle(bool b);
Utils::FileName adbToolPath() const;
Utils::FileName androidToolPath() const;
- Utils::Environment androidToolEnvironment() const;
Utils::FileName antToolPath() const;
Utils::FileName emulatorToolPath() const;
-
+ Utils::FileName sdkManagerToolPath() const;
+ Utils::FileName avdManagerToolPath() const;
Utils::FileName gccPath(const ProjectExplorer::Abi &abi, Core::Id lang,
const QString &ndkToolChainVersion) const;
+
Utils::FileName gdbPath(const ProjectExplorer::Abi &abi, const QString &ndkToolChainVersion) const;
Utils::FileName keytoolPath() const;
@@ -135,7 +157,8 @@ public:
class CreateAvdInfo
{
public:
- QString target;
+ bool isValid() const { return target.isValid() && !name.isEmpty(); }
+ SdkPlatform target;
QString name;
QString abi;
int sdcardSize = 0;
@@ -143,19 +166,10 @@ public:
};
CreateAvdInfo gatherCreateAVDInfo(QWidget *parent, int minApiLevel = 0, QString targetArch = QString()) const;
- QFuture<CreateAvdInfo> createAVD(CreateAvdInfo info) const;
- bool removeAVD(const QString &name) const;
QVector<AndroidDeviceInfo> connectedDevices(QString *error = 0) const;
static QVector<AndroidDeviceInfo> connectedDevices(const QString &adbToolPath, QString *error = 0);
- QFuture<QVector<AndroidDeviceInfo> > androidVirtualDevicesFuture() const;
- static QVector<AndroidDeviceInfo> androidVirtualDevices(const QString &androidTool, const Utils::Environment &environment);
-
- QString startAVD(const QString &name) const;
- bool startAVDAsync(const QString &avdName) const;
- QString findAvd(const QString &avdName) const;
- QString waitForAvd(const QString &avdName, const QFutureInterface<bool> &fi = QFutureInterface<bool>()) const;
QString bestNdkPlatformMatch(int target) const;
static ProjectExplorer::Abi abiForToolChainPrefix(const QString &toolchainPrefix);
@@ -166,13 +180,10 @@ public:
QString getProductModel(const QString &device) const;
enum class OpenGl { Enabled, Disabled, Unknown };
OpenGl getOpenGLEnabled(const QString &emulator) const;
- bool hasFinishedBooting(const QString &device) const;
- bool waitForBooted(const QString &serialNumber, const QFutureInterface<bool> &fi) const;
bool isConnected(const QString &serialNumber) const;
SdkPlatform highestAndroidSdk() const;
private:
- static CreateAvdInfo createAVDImpl(CreateAvdInfo info, Utils::FileName androidToolPath, Utils::Environment env);
static QString getDeviceProperty(const QString &adbToolPath, const QString &device, const QString &property);
Utils::FileName toolPath(const ProjectExplorer::Abi &abi, const QString &ndkToolChainVersion) const;
@@ -196,12 +207,11 @@ private:
QStringList m_makeExtraSearchDirectories;
unsigned m_partitionSize = 1024;
bool m_automaticKitCreation = true;
- bool m_useGradle = false;
+ bool m_useGradle = true; // Ant builds are deprecated.
//caches
mutable bool m_availableSdkPlatformsUpToDate = false;
- mutable QVector<SdkPlatform> m_availableSdkPlatforms;
- static bool sortSdkPlatformByApiLevel(const SdkPlatform &a, const SdkPlatform &b);
+ mutable SdkPlatformList m_availableSdkPlatforms;
mutable bool m_NdkInformationUpToDate = false;
mutable QString m_toolchainHost;
@@ -247,3 +257,5 @@ private:
};
} // namespace Android
+Q_DECLARE_METATYPE(Android::SdkPlatform)
+
diff --git a/src/plugins/android/androiddebugsupport.cpp b/src/plugins/android/androiddebugsupport.cpp
index 76a3493c7e..aaffde81d4 100644
--- a/src/plugins/android/androiddebugsupport.cpp
+++ b/src/plugins/android/androiddebugsupport.cpp
@@ -180,6 +180,16 @@ AndroidDebugSupport::AndroidDebugSupport(RunControl *runControl)
[this](const QString &output) {
this->runControl()->showMessage(output, AppOutput);
});
+
+ QTC_ASSERT(runControl, return);
+ auto formatter = qobject_cast<AndroidOutputFormatter*>(runControl->outputFormatter());
+ QTC_ASSERT(formatter, return);
+ connect(m_runner, &AndroidRunner::pidFound, formatter, &AndroidOutputFormatter::appendPid);
+ connect(m_runner, &AndroidRunner::pidLost, formatter, &AndroidOutputFormatter::removePid);
+ connect(m_runner, &AndroidRunner::remoteProcessFinished, formatter,
+ [formatter] {
+ formatter->removePid(-1);
+ });
}
void AndroidDebugSupport::handleRemoteProcessStarted(Utils::Port gdbServerPort, Utils::Port qmlPort)
diff --git a/src/plugins/android/androiddeployqtstep.cpp b/src/plugins/android/androiddeployqtstep.cpp
index 98efc64ffe..b4bfbc7bc1 100644
--- a/src/plugins/android/androiddeployqtstep.cpp
+++ b/src/plugins/android/androiddeployqtstep.cpp
@@ -33,6 +33,7 @@
#include "androidmanager.h"
#include "androidconstants.h"
#include "androidglobal.h"
+#include "androidavdmanager.h"
#include <coreplugin/fileutils.h>
#include <coreplugin/icore.h>
@@ -262,8 +263,9 @@ bool AndroidDeployQtStep::init(QList<const BuildStep *> &earlierSteps)
m_adbPath = AndroidConfigurations::currentConfig().adbToolPath().toString();
- if (AndroidConfigurations::currentConfig().findAvd(m_avdName).isEmpty())
- AndroidConfigurations::currentConfig().startAVDAsync(m_avdName);
+ AndroidAvdManager avdManager;
+ if (avdManager.findAvd(m_avdName).isEmpty())
+ avdManager.startAvdAsync(m_avdName);
return true;
}
@@ -414,7 +416,7 @@ void AndroidDeployQtStep::slotSetSerialNumber(const QString &serialNumber)
void AndroidDeployQtStep::run(QFutureInterface<bool> &fi)
{
if (!m_avdName.isEmpty()) {
- QString serialNumber = AndroidConfigurations::currentConfig().waitForAvd(m_avdName, fi);
+ QString serialNumber = AndroidAvdManager().waitForAvd(m_avdName, fi);
if (serialNumber.isEmpty()) {
reportRunResult(fi, false);
return;
diff --git a/src/plugins/android/androiddevicedialog.cpp b/src/plugins/android/androiddevicedialog.cpp
index 519f125f45..451abf2dc5 100644
--- a/src/plugins/android/androiddevicedialog.cpp
+++ b/src/plugins/android/androiddevicedialog.cpp
@@ -25,6 +25,7 @@
#include "androiddevicedialog.h"
#include "androidmanager.h"
+#include "androidavdmanager.h"
#include "ui_androiddevicedialog.h"
#include <utils/environment.h>
@@ -423,7 +424,8 @@ AndroidDeviceDialog::AndroidDeviceDialog(int apiLevel, const QString &abi, Andro
m_ui(new Ui::AndroidDeviceDialog),
m_apiLevel(apiLevel),
m_abi(abi),
- m_defaultDevice(serialNumber)
+ m_defaultDevice(serialNumber),
+ m_avdManager(new AndroidAvdManager)
{
m_ui->setupUi(this);
m_ui->deviceView->setModel(m_model);
@@ -515,7 +517,7 @@ void AndroidDeviceDialog::refreshDeviceList()
m_ui->refreshDevicesButton->setEnabled(false);
m_progressIndicator->show();
m_connectedDevices = AndroidConfig::connectedDevices(AndroidConfigurations::currentConfig().adbToolPath().toString());
- m_futureWatcherRefreshDevices.setFuture(AndroidConfigurations::currentConfig().androidVirtualDevicesFuture());
+ m_futureWatcherRefreshDevices.setFuture(m_avdManager->avdList());
}
void AndroidDeviceDialog::devicesRefreshed()
@@ -530,7 +532,7 @@ void AndroidDeviceDialog::devicesRefreshed()
serialNumber = deviceType == AndroidDeviceInfo::Hardware ? info.serialNumber : info.avdname;
}
- QVector<AndroidDeviceInfo> devices = m_futureWatcherRefreshDevices.result();
+ AndroidDeviceInfoList devices = m_futureWatcherRefreshDevices.result();
QSet<QString> startedAvds = Utils::transform<QSet>(m_connectedDevices,
[] (const AndroidDeviceInfo &info) {
return info.avdname;
@@ -583,12 +585,12 @@ void AndroidDeviceDialog::createAvd()
m_ui->createAVDButton->setEnabled(false);
AndroidConfig::CreateAvdInfo info = AndroidConfigurations::currentConfig().gatherCreateAVDInfo(this, m_apiLevel, m_abi);
- if (info.target.isEmpty()) {
+ if (!info.target.isValid()) {
m_ui->createAVDButton->setEnabled(true);
return;
}
- m_futureWatcherAddDevice.setFuture(AndroidConfigurations::currentConfig().createAVD(info));
+ m_futureWatcherAddDevice.setFuture(m_avdManager->createAvd(info));
}
void AndroidDeviceDialog::avdAdded()
diff --git a/src/plugins/android/androiddevicedialog.h b/src/plugins/android/androiddevicedialog.h
index b4d7940048..8f957aa0b2 100644
--- a/src/plugins/android/androiddevicedialog.h
+++ b/src/plugins/android/androiddevicedialog.h
@@ -32,6 +32,8 @@
#include <QFutureWatcher>
#include <QTime>
+#include <memory>
+
QT_BEGIN_NAMESPACE
class QModelIndex;
QT_END_NAMESPACE
@@ -41,6 +43,7 @@ namespace Utils { class ProgressIndicator; }
namespace Android {
namespace Internal {
+class AndroidAvdManager;
class AndroidDeviceModel;
namespace Ui { class AndroidDeviceDialog; }
@@ -74,9 +77,10 @@ private:
QString m_abi;
QString m_avdNameFromAdd;
QString m_defaultDevice;
+ std::unique_ptr<AndroidAvdManager> m_avdManager;
QVector<AndroidDeviceInfo> m_connectedDevices;
QFutureWatcher<AndroidConfig::CreateAvdInfo> m_futureWatcherAddDevice;
- QFutureWatcher<QVector<AndroidDeviceInfo>> m_futureWatcherRefreshDevices;
+ QFutureWatcher<AndroidDeviceInfoList> m_futureWatcherRefreshDevices;
};
}
diff --git a/src/plugins/android/androidmanager.cpp b/src/plugins/android/androidmanager.cpp
index 0459355b04..ee57399060 100644
--- a/src/plugins/android/androidmanager.cpp
+++ b/src/plugins/android/androidmanager.cpp
@@ -34,6 +34,7 @@
#include "androidqtsupport.h"
#include "androidqtversion.h"
#include "androidbuildapkstep.h"
+#include "androidavdmanager.h"
#include <coreplugin/documentmanager.h>
#include <coreplugin/messagemanager.h>
@@ -60,11 +61,13 @@
#include <QMessageBox>
#include <QApplication>
#include <QDomDocument>
+#include <QVersionNumber>
namespace {
const QLatin1String AndroidManifestName("AndroidManifest.xml");
const QLatin1String AndroidDefaultPropertiesName("project.properties");
const QLatin1String AndroidDeviceSn("AndroidDeviceSerialNumber");
+ const QLatin1String ApiLevelKey("AndroidVersion.ApiLevel");
} // anonymous namespace
@@ -343,7 +346,7 @@ void AndroidManager::cleanLibsOnDevice(ProjectExplorer::Target *target)
QString deviceSerialNumber = info.serialNumber;
if (info.type == AndroidDeviceInfo::Emulator) {
- deviceSerialNumber = AndroidConfigurations::currentConfig().startAVD(info.avdname);
+ deviceSerialNumber = AndroidAvdManager().startAvd(info.avdname);
if (deviceSerialNumber.isEmpty())
Core::MessageManager::write(tr("Starting Android virtual device failed."));
}
@@ -372,7 +375,7 @@ void AndroidManager::installQASIPackage(ProjectExplorer::Target *target, const Q
QString deviceSerialNumber = info.serialNumber;
if (info.type == AndroidDeviceInfo::Emulator) {
- deviceSerialNumber = AndroidConfigurations::currentConfig().startAVD(info.avdname);
+ deviceSerialNumber = AndroidAvdManager().startAvd(info.avdname);
if (deviceSerialNumber.isEmpty())
Core::MessageManager::write(tr("Starting Android virtual device failed."));
}
@@ -565,18 +568,33 @@ bool AndroidManager::updateGradleProperties(ProjectExplorer::Target *target)
gradleProperties["buildDir"] = ".build";
gradleProperties["androidCompileSdkVersion"] = buildTargetSDK(target).split(QLatin1Char('-')).last().toLocal8Bit();
if (gradleProperties["androidBuildToolsVersion"].isEmpty()) {
- QString maxVersion;
+ QVersionNumber maxVersion;
QDir buildToolsDir(AndroidConfigurations::currentConfig().sdkLocation().appendPath(QLatin1String("build-tools")).toString());
foreach (const QFileInfo &file, buildToolsDir.entryList(QDir::Dirs|QDir::NoDotAndDotDot)) {
- QString ver(file.fileName());
+ QVersionNumber ver = QVersionNumber::fromString(file.fileName());
if (maxVersion < ver)
maxVersion = ver;
}
- if (maxVersion.isEmpty())
+ if (maxVersion.isNull())
return false;
- gradleProperties["androidBuildToolsVersion"] = maxVersion.toLocal8Bit();
+ gradleProperties["androidBuildToolsVersion"] = maxVersion.toString().toLocal8Bit();
}
return mergeGradleProperties(gradlePropertiesPath, gradleProperties);
}
+int AndroidManager::findApiLevel(const Utils::FileName &platformPath)
+{
+ int apiLevel = -1;
+ Utils::FileName propertiesPath = platformPath;
+ propertiesPath.appendPath("/source.properties");
+ if (propertiesPath.exists()) {
+ QSettings sdkProperties(propertiesPath.toString(), QSettings::IniFormat);
+ bool validInt = false;
+ apiLevel = sdkProperties.value(ApiLevelKey).toInt(&validInt);
+ if (!validInt)
+ apiLevel = -1;
+ }
+ return apiLevel;
+}
+
} // namespace Android
diff --git a/src/plugins/android/androidmanager.h b/src/plugins/android/androidmanager.h
index 2b79cb4c6a..ad50f45529 100644
--- a/src/plugins/android/androidmanager.h
+++ b/src/plugins/android/androidmanager.h
@@ -89,6 +89,7 @@ public:
static AndroidQtSupport *androidQtSupport(ProjectExplorer::Target *target);
static bool useGradle(ProjectExplorer::Target *target);
static bool updateGradleProperties(ProjectExplorer::Target *target);
+ static int findApiLevel(const Utils::FileName &platformPath);
};
} // namespace Android
diff --git a/src/plugins/android/androidrunconfiguration.cpp b/src/plugins/android/androidrunconfiguration.cpp
index d2d7844e5c..54f6449753 100644
--- a/src/plugins/android/androidrunconfiguration.cpp
+++ b/src/plugins/android/androidrunconfiguration.cpp
@@ -29,16 +29,205 @@
#include "androidmanager.h"
#include <projectexplorer/kitinformation.h>
+#include <projectexplorer/projectexplorer.h>
+#include <projectexplorer/projectexplorersettings.h>
#include <projectexplorer/target.h>
#include <qtsupport/qtoutputformatter.h>
#include <qtsupport/qtkitinformation.h>
+#include <QPlainTextEdit>
+#include <QRegularExpression>
+#include <QToolButton>
#include <utils/qtcassert.h>
+#include <utils/utilsicons.h>
using namespace ProjectExplorer;
namespace Android {
+static QRegularExpression logCatRegExp("([0-9\\-]*\\s+[0-9\\-:.]*)" // 1. time
+ "\\s*"
+ "([DEIVWF])" // 2. log level
+ "\\/"
+ "(.*)" // 3. TAG
+ "\\(\\s*"
+ "(\\d+)" // 4. PID
+ "\\)\\:\\s"
+ "(.*)"); // 5. Message
+
+AndroidOutputFormatter::AndroidOutputFormatter(Project *project)
+ : QtSupport::QtOutputFormatter(project)
+ , m_filtersButton(new QToolButton)
+{
+ auto filtersMenu = new QMenu(m_filtersButton.data());
+
+ m_filtersButton->setToolTip(tr("Filters"));
+ m_filtersButton->setIcon(Utils::Icons::FILTER.icon());
+ m_filtersButton->setProperty("noArrow", true);
+ m_filtersButton->setAutoRaise(true);
+ m_filtersButton->setPopupMode(QToolButton::InstantPopup);
+ m_filtersButton->setMenu(filtersMenu);
+
+ auto logsMenu = filtersMenu->addMenu(tr("Log Level"));
+ addLogAction(All, logsMenu, tr("All"));
+ addLogAction(Verbose, logsMenu, tr("Verbose"));
+ addLogAction(Info, logsMenu, tr("Info"));
+ addLogAction(Debug, logsMenu, tr("Debug"));
+ addLogAction(Warning, logsMenu, tr("Warning"));
+ addLogAction(Error, logsMenu, tr("Error"));
+ addLogAction(Fatal, logsMenu, tr("Fatal"));
+ updateLogMenu();
+ m_appsMenu = filtersMenu->addMenu(tr("Applications"));
+ appendPid(-1, tr("All"));
+}
+
+AndroidOutputFormatter::~AndroidOutputFormatter()
+{}
+
+QList<QWidget *> AndroidOutputFormatter::toolbarWidgets() const
+{
+ return QList<QWidget *>{m_filtersButton.data()};
+}
+
+void AndroidOutputFormatter::appendMessage(const QString &text, Utils::OutputFormat format)
+{
+ if (text.isEmpty())
+ return;
+
+ CachedLine line;
+ line.content = text;
+
+ if (format < Utils::StdOutFormat) {
+ line.level = SkipFiltering;
+ line.pid = -1;
+ } else {
+ QRegularExpressionMatch match = logCatRegExp.match(text);
+ if (!match.hasMatch())
+ return;
+ line.level = None;
+
+ switch (match.captured(2).toLatin1()[0]) {
+ case 'D': line.level = Debug; break;
+ case 'I': line.level = Info; break;
+ case 'V': line.level = Verbose; break;
+ case 'W': line.level = Warning; break;
+ case 'E': line.level = Error; break;
+ case 'F': line.level = Fatal; break;
+ default: return;
+ }
+ line.pid = match.captured(4).toLongLong();
+ }
+
+ m_cachedLines.append(line);
+ if (m_cachedLines.size() > ProjectExplorerPlugin::projectExplorerSettings().maxAppOutputLines)
+ m_cachedLines.pop_front();
+
+ filterMessage(line);
+}
+
+void AndroidOutputFormatter::clear()
+{
+ m_cachedLines.clear();
+}
+
+void AndroidOutputFormatter::appendPid(qint64 pid, const QString &name)
+{
+ if (m_pids.contains(pid))
+ return;
+
+ auto action = m_appsMenu->addAction(name);
+ m_pids[pid] = action;
+ action->setCheckable(true);
+ action->setChecked(pid != -1);
+ connect(action, &QAction::triggered, this, &AndroidOutputFormatter::applyFilter);
+ applyFilter();
+}
+
+void AndroidOutputFormatter::removePid(qint64 pid)
+{
+ if (pid == -1) {
+ for (auto action : m_pids)
+ m_appsMenu->removeAction(action);
+ m_pids.clear();
+ } else {
+ m_appsMenu->removeAction(m_pids[pid]);
+ m_pids.remove(pid);
+ }
+}
+
+void AndroidOutputFormatter::updateLogMenu(LogLevel set, LogLevel reset)
+{
+ m_logLevelFlags |= set;
+ m_logLevelFlags &= ~reset;
+ for (const auto & pair : m_logLevels)
+ pair.second->setChecked((m_logLevelFlags & pair.first) == pair.first);
+
+ applyFilter();
+}
+
+void AndroidOutputFormatter::filterMessage(const CachedLine &line)
+{
+ if (line.level == SkipFiltering || m_pids[-1]->isChecked()) {
+ QtOutputFormatter::appendMessage(line.content, Utils::NormalMessageFormat);
+ } else {
+ // Filter Log Level
+ if (!(m_logLevelFlags & line.level))
+ return;
+
+ // Filter PIDs
+ if (!m_pids[-1]->isChecked()) {
+ auto it = m_pids.find(line.pid);
+ if (it == m_pids.end() || !(*it)->isChecked())
+ return;
+ }
+
+ Utils::OutputFormat format = Utils::NormalMessageFormat;
+ switch (line.level) {
+ case Debug:
+ format = Utils::DebugFormat;
+ break;
+ case Info:
+ case Verbose:
+ format = Utils::StdOutFormat;
+ break;
+
+ case Warning:
+ case Error:
+ case Fatal:
+ format = Utils::StdErrFormat;
+ break;
+ default:
+ return;
+ }
+
+ Utils::OutputFormatter::appendMessage(line.content, format);
+ }
+}
+
+void AndroidOutputFormatter::applyFilter()
+{
+ if (!plainTextEdit())
+ return;
+
+ plainTextEdit()->clear();
+ if (!m_pids[-1]->isChecked()) {
+ bool allOn = true;
+ for (auto action : m_pids) {
+ if (!action->isChecked()) {
+ allOn = false;
+ break;
+ }
+ }
+ m_pids[-1]->setChecked(allOn);
+ } else {
+ for (auto action : m_pids)
+ action->setChecked(true);
+ }
+
+ for (const auto &line : m_cachedLines)
+ filterMessage(line);
+}
+
AndroidRunConfiguration::AndroidRunConfiguration(Target *parent, Core::Id id)
: RunConfiguration(parent, id)
{
@@ -56,7 +245,7 @@ QWidget *AndroidRunConfiguration::createConfigurationWidget()
Utils::OutputFormatter *AndroidRunConfiguration::createOutputFormatter() const
{
- return new QtSupport::QtOutputFormatter(target()->project());
+ return new AndroidOutputFormatter(target()->project());
}
const QString AndroidRunConfiguration::remoteChannel() const
diff --git a/src/plugins/android/androidrunconfiguration.h b/src/plugins/android/androidrunconfiguration.h
index cfa54312d9..9ec42e3d78 100644
--- a/src/plugins/android/androidrunconfiguration.h
+++ b/src/plugins/android/androidrunconfiguration.h
@@ -28,9 +28,75 @@
#include "android_global.h"
#include <projectexplorer/runconfiguration.h>
+#include <qtsupport/qtoutputformatter.h>
+
+#include <QMenu>
+
+QT_BEGIN_NAMESPACE
+class QToolButton;
+QT_END_NAMESPACE
namespace Android {
+class AndroidOutputFormatter : public QtSupport::QtOutputFormatter
+{
+ Q_OBJECT
+public:
+ enum LogLevel {
+ None = 0,
+ Verbose = 1,
+ Info = 1 << 1,
+ Debug = 1 << 2,
+ Warning = 1 << 3,
+ Error = 1 << 4,
+ Fatal = 1 << 5,
+ All = Verbose | Info | Debug | Warning | Error | Fatal,
+ SkipFiltering = ~All
+ };
+
+public:
+ explicit AndroidOutputFormatter(ProjectExplorer::Project *project);
+ ~AndroidOutputFormatter();
+
+ // OutputFormatter interface
+ QList<QWidget*> toolbarWidgets() const override;
+ void appendMessage(const QString &text, Utils::OutputFormat format) override;
+ void clear() override;
+
+public slots:
+ void appendPid(qint64 pid, const QString &name);
+ void removePid(qint64 pid);
+
+private:
+ struct CachedLine {
+ qint64 pid;
+ LogLevel level;
+ QString content;
+ };
+
+private:
+ void updateLogMenu(LogLevel set = None, LogLevel reset = None);
+ void filterMessage(const CachedLine &line);
+
+ void applyFilter();
+ void addLogAction(LogLevel level, QMenu *logsMenu, const QString &name) {
+ auto action = logsMenu->addAction(name);
+ m_logLevels.push_back(qMakePair(level, action));
+ action->setCheckable(true);
+ connect(action, &QAction::triggered, this, [level, this](bool checked) {
+ updateLogMenu(checked ? level : None , checked ? None : level);
+ });
+ }
+
+private:
+ int m_logLevelFlags = All;
+ QVector<QPair<LogLevel, QAction*>> m_logLevels;
+ QHash<qint64, QAction*> m_pids;
+ QScopedPointer<QToolButton> m_filtersButton;
+ QMenu *m_appsMenu;
+ QList<CachedLine> m_cachedLines;
+};
+
class ANDROID_EXPORT AndroidRunConfiguration : public ProjectExplorer::RunConfiguration
{
Q_OBJECT
diff --git a/src/plugins/android/androidruncontrol.cpp b/src/plugins/android/androidruncontrol.cpp
index 2ac929a0b1..d2a4bb1ea6 100644
--- a/src/plugins/android/androidruncontrol.cpp
+++ b/src/plugins/android/androidruncontrol.cpp
@@ -62,6 +62,13 @@ void AndroidRunControl::start()
this, &AndroidRunControl::handleRemoteOutput);
connect(m_runner, &AndroidRunner::remoteProcessFinished,
this, &AndroidRunControl::handleRemoteProcessFinished);
+
+ auto formatter = static_cast<AndroidOutputFormatter *>(outputFormatter());
+ connect(m_runner, &AndroidRunner::pidFound,
+ formatter, &AndroidOutputFormatter::appendPid);
+ connect(m_runner, &AndroidRunner::pidLost,
+ formatter, &AndroidOutputFormatter::removePid);
+
appendMessage(tr("Starting remote process."), Utils::NormalMessageFormat);
m_runner->setRunnable(runnable().as<AndroidRunnable>());
m_runner->start();
diff --git a/src/plugins/android/androidrunner.cpp b/src/plugins/android/androidrunner.cpp
index 778d348b68..9208e69953 100644
--- a/src/plugins/android/androidrunner.cpp
+++ b/src/plugins/android/androidrunner.cpp
@@ -31,6 +31,7 @@
#include "androidglobal.h"
#include "androidrunconfiguration.h"
#include "androidmanager.h"
+#include "androidavdmanager.h"
#include <debugger/debuggerrunconfigurationaspect.h>
#include <projectexplorer/projectexplorer.h>
@@ -51,6 +52,7 @@
#include <QTime>
#include <QTcpServer>
#include <QTcpSocket>
+#include <QRegularExpression>
using namespace std;
using namespace std::placeholders;
@@ -125,10 +127,10 @@ namespace Internal {
const int MIN_SOCKET_HANDSHAKE_PORT = 20001;
const int MAX_SOCKET_HANDSHAKE_PORT = 20999;
-static const QString pidScript = QStringLiteral("for p in /proc/[0-9]*; "
- "do cat <$p/cmdline && echo :${p##*/}; done");
-static const QString pidPollingScript = QStringLiteral("while true; do sleep 1; "
- "cat /proc/%1/cmdline > /dev/null; done");
+static const QString pidScript = QStringLiteral("input keyevent KEYCODE_WAKEUP; "
+ "while true; do sleep 1; echo \"=\"; "
+ "for p in /proc/[0-9]*; "
+ "do cat <$p/cmdline && echo :${p##*/}; done; done");
static const QString regExpLogcat = QStringLiteral("[0-9\\-]*" // date
"\\s+"
@@ -146,55 +148,26 @@ static const QString regExpLogcat = QStringLiteral("[0-9\\-]*" // date
);
static int APP_START_TIMEOUT = 45000;
-static bool isTimedOut(const chrono::high_resolution_clock::time_point &start,
- int msecs = APP_START_TIMEOUT)
-{
- bool timedOut = false;
- auto end = chrono::high_resolution_clock::now();
- if (chrono::duration_cast<chrono::milliseconds>(end-start).count() > msecs)
- timedOut = true;
- return timedOut;
-}
-
-static qint64 extractPID(const QByteArray &output, const QString &packageName)
-{
- qint64 pid = -1;
- foreach (auto tuple, output.split('\n')) {
- tuple = tuple.simplified();
- if (!tuple.isEmpty()) {
- auto parts = tuple.split(':');
- QString commandName = QString::fromLocal8Bit(parts.first());
- if (parts.length() == 2 && commandName == packageName) {
- pid = parts.last().toLongLong();
- break;
- }
- }
- }
- return pid;
-}
+enum class PidStatus {
+ Found,
+ Lost
+};
-void findProcessPID(QFutureInterface<qint64> &fi, const QString &adbPath,
- QStringList selector, const QString &packageName)
+struct PidInfo
{
- if (packageName.isEmpty())
- return;
-
- qint64 processPID = -1;
- chrono::high_resolution_clock::time_point start = chrono::high_resolution_clock::now();
- do {
- QThread::msleep(200);
- const QByteArray out = Utils::SynchronousProcess()
- .runBlocking(adbPath, selector << QStringLiteral("shell") << pidScript)
- .allRawOutput();
- processPID = extractPID(out, packageName);
- } while (processPID == -1 && !isTimedOut(start) && !fi.isCanceled());
-
- if (!fi.isCanceled())
- fi.reportResult(processPID);
-}
+ PidInfo(qint64 pid = -1, PidStatus status = PidStatus::Lost, QString name = {})
+ : pid(pid)
+ , status(status)
+ , name(name)
+ {}
+ qint64 pid;
+ PidStatus status;
+ QString name;
+};
static void deleter(QProcess *p)
{
+ p->disconnect();
p->kill();
p->waitForFinished();
// Might get deleted from its own signal handler.
@@ -228,29 +201,31 @@ signals:
void remoteOutput(const QString &output);
void remoteErrorOutput(const QString &output);
+ void pidFound(qint64, const QString &name);
+ void pidLost(qint64);
private:
- void onProcessIdChanged(qint64 pid);
+ void findProcessPids();
+ void onProcessIdChanged(PidInfo pidInfo);
void logcatReadStandardError();
void logcatReadStandardOutput();
void adbKill(qint64 pid);
QStringList selector() const { return m_selector; }
void forceStop();
- void findPs();
void logcatProcess(const QByteArray &text, QByteArray &buffer, bool onlyError);
bool adbShellAmNeedsQuotes();
bool runAdb(const QStringList &args, QString *exitMessage = nullptr, int timeoutS = 10);
+ int deviceSdkVersion();
// Create the processes and timer in the worker thread, for correct thread affinity
std::unique_ptr<QProcess, decltype(&deleter)> m_adbLogcatProcess;
- std::unique_ptr<QProcess, decltype(&deleter)> m_psIsAlive;
+ std::unique_ptr<QProcess, decltype(&deleter)> m_pidsFinderProcess;
QScopedPointer<QTcpSocket> m_socket;
QByteArray m_stdoutBuffer;
QByteArray m_stderrBuffer;
- QFuture<qint64> m_pidFinder;
- qint64 m_processPID = -1;
+ QSet<qint64> m_processPids;
bool m_useCppDebugger = false;
QmlDebug::QmlDebugServicesPreset m_qmlDebugServices;
Utils::Port m_localGdbServerPort; // Local end of forwarded debug socket.
@@ -261,20 +236,20 @@ private:
QString m_gdbserverSocket;
QString m_adb;
QStringList m_selector;
- QRegExp m_logCatRegExp;
DebugHandShakeType m_handShakeMethod = SocketHandShake;
bool m_customPort = false;
QString m_packageName;
int m_socketHandShakePort = MIN_SOCKET_HANDSHAKE_PORT;
+ QByteArray m_pidsBuffer;
+ QScopedPointer<QTimer> m_timeoutTimer;
};
AndroidRunnerWorker::AndroidRunnerWorker(AndroidRunConfiguration *runConfig, Core::Id runMode,
const QString &packageName, const QStringList &selector)
: m_adbLogcatProcess(nullptr, deleter)
- , m_psIsAlive(nullptr, deleter)
+ , m_pidsFinderProcess(nullptr, deleter)
, m_selector(selector)
- , m_logCatRegExp(regExpLogcat)
, m_packageName(packageName)
{
Debugger::DebuggerRunConfigurationAspect *aspect
@@ -338,23 +313,18 @@ AndroidRunnerWorker::AndroidRunnerWorker(AndroidRunConfiguration *runConfig, Cor
AndroidRunnerWorker::~AndroidRunnerWorker()
{
- if (!m_pidFinder.isFinished())
- m_pidFinder.cancel();
}
void AndroidRunnerWorker::forceStop()
{
runAdb(selector() << "shell" << "am" << "force-stop" << m_packageName, nullptr, 30);
- // try killing it via kill -9
- const QByteArray out = Utils::SynchronousProcess()
- .runBlocking(m_adb, selector() << QStringLiteral("shell") << pidScript)
- .allRawOutput();
-
- qint64 pid = extractPID(out.simplified(), m_packageName);
- if (pid != -1) {
- adbKill(pid);
+ for (auto it = m_processPids.constBegin(); it != m_processPids.constEnd(); ++it) {
+ emit pidLost(*it);
+ adbKill(*it);
}
+ m_processPids.clear();
+ m_pidsBuffer.clear();
}
void AndroidRunnerWorker::asyncStart(const QString &intentName,
@@ -368,8 +338,12 @@ void AndroidRunnerWorker::asyncStart(const QString &intentName,
this, &AndroidRunnerWorker::logcatReadStandardOutput);
connect(logcatProcess.get(), &QProcess::readyReadStandardError,
this, &AndroidRunnerWorker::logcatReadStandardError);
+
// Its assumed that the device or avd returned by selector() is online.
- logcatProcess->start(m_adb, selector() << "logcat");
+ QStringList logcatArgs = selector() << "logcat" << "-v" << "time";
+ if (deviceSdkVersion() > 20)
+ logcatArgs << "-T" << "0";
+ logcatProcess->start(m_adb, logcatArgs);
QString errorMessage;
@@ -507,9 +481,20 @@ void AndroidRunnerWorker::asyncStart(const QString &intentName,
QTC_ASSERT(!m_adbLogcatProcess, /**/);
m_adbLogcatProcess = std::move(logcatProcess);
- m_pidFinder = Utils::onResultReady(Utils::runAsync(&findProcessPID, m_adb, selector(),
- m_packageName),
- bind(&AndroidRunnerWorker::onProcessIdChanged, this, _1));
+
+ m_timeoutTimer.reset(new QTimer);
+ m_timeoutTimer->setSingleShot(true);
+ connect(m_timeoutTimer.data(), &QTimer::timeout,
+ this,[this] { onProcessIdChanged(PidInfo{}); });
+ m_timeoutTimer->start(APP_START_TIMEOUT);
+
+ m_pidsFinderProcess.reset(new QProcess);
+ m_pidsFinderProcess->setProcessChannelMode(QProcess::MergedChannels);
+ connect(m_pidsFinderProcess.get(), &QProcess::readyRead, this, &AndroidRunnerWorker::findProcessPids);
+ connect(m_pidsFinderProcess.get(),
+ static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
+ this, [this] { onProcessIdChanged(PidInfo{}); });
+ m_pidsFinderProcess->start(m_adb, selector() << "shell" << pidScript);
}
bool AndroidRunnerWorker::adbShellAmNeedsQuotes()
@@ -545,6 +530,19 @@ bool AndroidRunnerWorker::runAdb(const QStringList &args, QString *exitMessage,
return response.result == Utils::SynchronousProcessResponse::Finished;
}
+int AndroidRunnerWorker::deviceSdkVersion()
+{
+ Utils::SynchronousProcess adb;
+ adb.setTimeoutS(10);
+ Utils::SynchronousProcessResponse response
+ = adb.run(m_adb, selector() << "shell" << "getprop" << "ro.build.version.sdk");
+ if (response.result == Utils::SynchronousProcessResponse::StartFailed
+ || response.result != Utils::SynchronousProcessResponse::Finished)
+ return -1;
+
+ return response.allOutput().trimmed().toInt();
+}
+
void AndroidRunnerWorker::handleRemoteDebuggerRunning()
{
if (m_useCppDebugger) {
@@ -558,21 +556,79 @@ void AndroidRunnerWorker::handleRemoteDebuggerRunning()
runAdb(selector() << "push" << tmp.fileName() << m_pongFile);
}
- QTC_CHECK(m_processPID != -1);
+ QTC_CHECK(!m_processPids.isEmpty());
}
emit remoteProcessStarted(m_localGdbServerPort, m_qmlPort);
}
-void AndroidRunnerWorker::asyncStop(const QVector<QStringList> &adbCommands)
+void AndroidRunnerWorker::findProcessPids()
{
- if (!m_pidFinder.isFinished())
- m_pidFinder.cancel();
+ static QMap<qint64, QByteArray> extractedPids;
+ static auto oldPids = m_processPids;
- if (m_processPID != -1) {
- forceStop();
+ m_pidsBuffer += m_pidsFinderProcess->readAll();
+ while (!m_pidsBuffer.isEmpty()) {
+ const int to = m_pidsBuffer.indexOf('\n');
+ if (to < 0)
+ break;
+
+ if (to == 0) {
+ m_pidsBuffer = m_pidsBuffer.mid(1);
+ continue;
+ }
+
+ // = is used to delimit ps outputs
+ // is needed to know when an existins PID is killed
+ if (m_pidsBuffer[0] != '=') {
+ QByteArray tuple = m_pidsBuffer.left(to + 1).simplified();
+ QList<QByteArray> parts = tuple.split(':');
+ QByteArray commandName = parts.takeFirst();
+ if (QString::fromLocal8Bit(commandName) == m_packageName) {
+ auto pid = parts.last().toLongLong();
+ if (!m_processPids.contains(pid)) {
+ extractedPids[pid] = commandName + (parts.length() == 2
+ ? ":" + parts.first() : QByteArray{});
+ } else {
+ oldPids.remove(pid);
+ }
+ }
+ } else {
+ // Add new PIDs
+ for (auto it = extractedPids.constBegin(); it != extractedPids.constEnd(); ++it) {
+ onProcessIdChanged(PidInfo(it.key(), PidStatus::Found,
+ QString::fromLocal8Bit(it.value())));
+ }
+ extractedPids.clear();
+
+ // Remove the dead ones
+ for (auto it = oldPids.constBegin(); it != oldPids.constEnd(); ++it)
+ onProcessIdChanged(PidInfo(*it, PidStatus::Lost));
+
+ // Save the current non dead PIDs
+ oldPids = m_processPids;
+ if (m_processPids.isEmpty()) {
+ extractedPids.clear();
+ m_pidsBuffer.clear();
+ break;
+ }
+ }
+ m_pidsBuffer = m_pidsBuffer.mid(to + 1);
}
+}
+
+void AndroidRunnerWorker::asyncStop(const QVector<QStringList> &adbCommands)
+{
+ m_timeoutTimer.reset();
+ m_pidsFinderProcess.reset();
+ if (!m_processPids.isEmpty())
+ forceStop();
+
foreach (const QStringList &entry, adbCommands)
runAdb(selector() << entry);
+
+ m_adbLogcatProcess.reset();
+ emit remoteProcessFinished(QLatin1String("\n\n") +
+ tr("\"%1\" terminated.").arg(m_packageName));
}
void AndroidRunnerWorker::setAdbParameters(const QString &packageName, const QStringList &selector)
@@ -594,58 +650,48 @@ void AndroidRunnerWorker::logcatProcess(const QByteArray &text, QByteArray &buff
buffer.clear();
}
- QString pidString = QString::number(m_processPID);
foreach (const QByteArray &msg, lines) {
- const QString line = QString::fromUtf8(msg).trimmed() + QLatin1Char('\n');
- if (!line.contains(pidString))
- continue;
- if (m_logCatRegExp.exactMatch(line)) {
- // Android M
- if (m_logCatRegExp.cap(1) == pidString) {
- const QString &messagetype = m_logCatRegExp.cap(2);
- QString output = line.mid(m_logCatRegExp.pos(2));
-
- if (onlyError
- || messagetype == QLatin1String("F")
- || messagetype == QLatin1String("E")
- || messagetype == QLatin1String("W"))
- emit remoteErrorOutput(output);
- else
- emit remoteOutput(output);
- }
- } else {
- if (onlyError || line.startsWith("F/")
- || line.startsWith("E/")
- || line.startsWith("W/"))
- emit remoteErrorOutput(line);
- else
- emit remoteOutput(line);
- }
+ const QString line = QString::fromUtf8(msg.trimmed());
+ if (onlyError)
+ emit remoteErrorOutput(line);
+ else
+ emit remoteOutput(line);
}
}
-void AndroidRunnerWorker::onProcessIdChanged(qint64 pid)
+void AndroidRunnerWorker::onProcessIdChanged(PidInfo pidInfo)
{
// Don't write to m_psProc from a different thread
QTC_ASSERT(QThread::currentThread() == thread(), return);
- m_processPID = pid;
- if (m_processPID == -1) {
+
+ auto isFirst = m_processPids.isEmpty();
+ if (pidInfo.status == PidStatus::Lost) {
+ m_processPids.remove(pidInfo.pid);
+ emit pidLost(pidInfo.pid);
+ } else {
+ m_processPids.insert(pidInfo.pid);
+ emit pidFound(pidInfo.pid, pidInfo.name);
+ }
+
+ if (m_processPids.isEmpty() || pidInfo.pid == -1) {
emit remoteProcessFinished(QLatin1String("\n\n") + tr("\"%1\" died.")
.arg(m_packageName));
// App died/killed. Reset log and monitor processes.
+ forceStop();
m_adbLogcatProcess.reset();
- m_psIsAlive.reset();
- } else {
+ m_timeoutTimer.reset();
+ } else if (isFirst) {
+ m_timeoutTimer.reset();
if (m_useCppDebugger) {
// This will be funneled to the engine to actually start and attach
// gdb. Afterwards this ends up in handleRemoteDebuggerRunning() below.
QByteArray serverChannel = ':' + QByteArray::number(m_localGdbServerPort.number());
- emit remoteServerRunning(serverChannel, m_processPID);
+ emit remoteServerRunning(serverChannel, pidInfo.pid);
} else if (m_qmlDebugServices == QmlDebug::QmlDebuggerServices) {
// This will be funneled to the engine to actually start and attach
// gdb. Afterwards this ends up in handleRemoteDebuggerRunning() below.
QByteArray serverChannel = QByteArray::number(m_qmlPort.number());
- emit remoteServerRunning(serverChannel, m_processPID);
+ emit remoteServerRunning(serverChannel, pidInfo.pid);
} else if (m_qmlDebugServices == QmlDebug::QmlProfilerServices) {
emit remoteProcessStarted(Utils::Port(), m_qmlPort);
} else {
@@ -653,27 +699,18 @@ void AndroidRunnerWorker::onProcessIdChanged(qint64 pid)
emit remoteProcessStarted(Utils::Port(), Utils::Port());
}
logcatReadStandardOutput();
- QTC_ASSERT(!m_psIsAlive, /**/);
- m_psIsAlive.reset(new QProcess);
- m_psIsAlive->setProcessChannelMode(QProcess::MergedChannels);
- connect(m_psIsAlive.get(), &QProcess::readyRead, [this](){
- if (!m_psIsAlive->readAll().simplified().isEmpty())
- onProcessIdChanged(-1);
- });
- m_psIsAlive->start(m_adb, selector() << QStringLiteral("shell")
- << pidPollingScript.arg(m_processPID));
}
}
void AndroidRunnerWorker::logcatReadStandardError()
{
- if (m_processPID != -1)
+ if (!m_processPids.isEmpty() && m_adbLogcatProcess)
logcatProcess(m_adbLogcatProcess->readAllStandardError(), m_stderrBuffer, true);
}
void AndroidRunnerWorker::logcatReadStandardOutput()
{
- if (m_processPID != -1)
+ if (!m_processPids.isEmpty() && m_adbLogcatProcess)
logcatProcess(m_adbLogcatProcess->readAllStandardOutput(), m_stdoutBuffer, false);
}
@@ -724,6 +761,10 @@ AndroidRunner::AndroidRunner(QObject *parent, RunConfiguration *runConfig, Core:
this, &AndroidRunner::remoteOutput);
connect(m_worker.data(), &AndroidRunnerWorker::remoteErrorOutput,
this, &AndroidRunner::remoteErrorOutput);
+ connect(m_worker.data(), &AndroidRunnerWorker::pidFound,
+ this, &AndroidRunner::pidFound);
+ connect(m_worker.data(), &AndroidRunnerWorker::pidLost,
+ this, &AndroidRunner::pidLost);
m_thread.start();
}
@@ -791,8 +832,9 @@ void AndroidRunner::launchAVD()
emit adbParametersChanged(m_androidRunnable.packageName,
AndroidDeviceInfo::adbSelector(info.serialNumber));
if (info.isValid()) {
- if (AndroidConfigurations::currentConfig().findAvd(info.avdname).isEmpty()) {
- bool launched = AndroidConfigurations::currentConfig().startAVDAsync(info.avdname);
+ AndroidAvdManager avdManager;
+ if (avdManager.findAvd(info.avdname).isEmpty()) {
+ bool launched = avdManager.startAvdAsync(info.avdname);
m_launchedAVDName = launched ? info.avdname:"";
} else {
m_launchedAVDName.clear();
@@ -803,11 +845,12 @@ void AndroidRunner::launchAVD()
void AndroidRunner::checkAVD()
{
const AndroidConfig &config = AndroidConfigurations::currentConfig();
- QString serialNumber = config.findAvd(m_launchedAVDName);
+ AndroidAvdManager avdManager(config);
+ QString serialNumber = avdManager.findAvd(m_launchedAVDName);
if (!serialNumber.isEmpty())
return; // try again on next timer hit
- if (config.hasFinishedBooting(serialNumber)) {
+ if (avdManager.isAvdBooted(serialNumber)) {
m_checkAVDTimer.stop();
AndroidManager::setDeviceSerialNumber(m_runConfig->target(), serialNumber);
emit asyncStart(m_androidRunnable.intentName, m_androidRunnable.beforeStartADBCommands);
diff --git a/src/plugins/android/androidrunner.h b/src/plugins/android/androidrunner.h
index dd35534bd6..f30dc4754b 100644
--- a/src/plugins/android/androidrunner.h
+++ b/src/plugins/android/androidrunner.h
@@ -75,6 +75,9 @@ signals:
void adbParametersChanged(const QString &packageName, const QStringList &selector);
void avdDetected();
+ void pidFound(qint64, const QString &name);
+ void pidLost(qint64);
+
private:
void checkAVD();
void launchAVD();
diff --git a/src/plugins/android/androidsdkmanager.cpp b/src/plugins/android/androidsdkmanager.cpp
new file mode 100644
index 0000000000..0ed23660a6
--- /dev/null
+++ b/src/plugins/android/androidsdkmanager.cpp
@@ -0,0 +1,337 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** 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 "androidsdkmanager.h"
+
+#include "androidmanager.h"
+#include "androidtoolmanager.h"
+
+#include "utils/algorithm.h"
+#include "utils/qtcassert.h"
+#include "utils/synchronousprocess.h"
+#include "utils/environment.h"
+
+#include <QLoggingCategory>
+#include <QSettings>
+
+namespace {
+Q_LOGGING_CATEGORY(sdkManagerLog, "qtc.android.sdkManager")
+}
+
+namespace Android {
+namespace Internal {
+
+// Though sdk manager is introduced in 25.2.3 but the verbose mode is avaialble in 25.3.0
+// and android tool is supported in 25.2.3
+const QVersionNumber sdkManagerIntroVersion(25, 3 ,0);
+
+const char installLocationKey[] = "Installed Location:";
+const char apiLevelPropertyKey[] = "AndroidVersion.ApiLevel";
+const char abiPropertyKey[] = "SystemImage.Abi";
+
+using namespace Utils;
+
+/*!
+ Parses the \a line for a [spaces]key[spaces]value[spaces] pattern and returns
+ \c true if \a key is found, false otherwise. Result is copied into \a value.
+ */
+static bool valueForKey(QString key, const QString &line, QString *value = nullptr)
+{
+ auto trimmedInput = line.trimmed();
+ if (trimmedInput.startsWith(key)) {
+ if (value)
+ *value = trimmedInput.section(key, 1, 1).trimmed();
+ return true;
+ }
+ return false;
+}
+
+/*!
+ Runs the \c sdkmanger tool specific to configuration \a config with arguments \a args. Returns
+ \c true if the command is successfully executed. Output is copied into \a output. The function
+ blocks the calling thread.
+ */
+static bool sdkManagerCommand(const AndroidConfig config, const QStringList &args, QString *output)
+{
+ QString sdkManagerToolPath = config.sdkManagerToolPath().toString();
+ SynchronousProcess proc;
+ SynchronousProcessResponse response = proc.runBlocking(sdkManagerToolPath, args);
+ if (response.result == SynchronousProcessResponse::Finished) {
+ if (output)
+ *output = response.allOutput();
+ return true;
+ }
+ return false;
+}
+
+/*!
+ \class SdkManagerOutputParser
+ \brief The SdkManagerOutputParser class is a helper class to parse the output of the \c sdkmanager
+ commands.
+ */
+class SdkManagerOutputParser
+{
+public:
+ enum MarkerTag
+ {
+ None = 0x01,
+ InstalledPackagesMarker = 0x02,
+ AvailablePackagesMarkers = 0x04,
+ AvailableUpdatesMarker = 0x08,
+ EmptyMarker = 0x10,
+ PlatformMarker = 0x20,
+ SystemImageMarker = 0x40,
+ SectionMarkers = InstalledPackagesMarker | AvailablePackagesMarkers | AvailableUpdatesMarker
+ };
+
+ void parsePackageListing(const QString &output);
+
+ SdkPlatformList m_installedPlatforms;
+
+private:
+ void compileData();
+ void parsePackageData(MarkerTag packageMarker, const QStringList &data);
+ bool parsePlatform(const QStringList &data, SdkPlatform *platform) const;
+ bool parseSystemImage(const QStringList &data, SystemImage *image);
+ MarkerTag parseMarkers(const QString &line);
+
+ MarkerTag m_currentSection = MarkerTag::None;
+ SystemImageList m_installedSystemImages;
+};
+
+const std::map<SdkManagerOutputParser::MarkerTag, const char *> markerTags {
+ {SdkManagerOutputParser::MarkerTag::InstalledPackagesMarker, "Installed packages:"},
+ {SdkManagerOutputParser::MarkerTag::AvailablePackagesMarkers, "Available Packages:"},
+ {SdkManagerOutputParser::MarkerTag::AvailablePackagesMarkers, "Available Updates:"},
+ {SdkManagerOutputParser::MarkerTag::PlatformMarker, "platforms"},
+ {SdkManagerOutputParser::MarkerTag::SystemImageMarker, "system-images"}
+};
+
+AndroidSdkManager::AndroidSdkManager(const AndroidConfig &config):
+ m_config(config),
+ m_parser(new SdkManagerOutputParser)
+{
+ QString packageListing;
+ if (sdkManagerCommand(config, QStringList({"--list", "--verbose"}), &packageListing)) {
+ m_parser->parsePackageListing(packageListing);
+ }
+}
+
+AndroidSdkManager::~AndroidSdkManager()
+{
+
+}
+
+SdkPlatformList AndroidSdkManager::availableSdkPlatforms()
+{
+ if (m_config.sdkToolsVersion() < sdkManagerIntroVersion) {
+ AndroidToolManager toolManager(m_config);
+ return toolManager.availableSdkPlatforms();
+ }
+
+ return m_parser->m_installedPlatforms;
+}
+
+void SdkManagerOutputParser::parsePackageListing(const QString &output)
+{
+ QStringList packageData;
+ bool collectingPackageData = false;
+ MarkerTag currentPackageMarker = MarkerTag::None;
+
+ auto processCurrentPackage = [&]() {
+ if (collectingPackageData) {
+ collectingPackageData = false;
+ parsePackageData(currentPackageMarker, packageData);
+ packageData.clear();
+ }
+ };
+
+ foreach (QString outputLine, output.split('\n')) {
+ MarkerTag marker = parseMarkers(outputLine);
+
+ if (marker & SectionMarkers) {
+ // Section marker found. Update the current section being parsed.
+ m_currentSection = marker;
+ processCurrentPackage();
+ continue;
+ }
+
+ if (m_currentSection == None
+ || m_currentSection == AvailablePackagesMarkers // At this point. Not interested in
+ || m_currentSection == AvailableUpdatesMarker) { // available or update packages.
+ // Let go of verbose output utill a valid section starts.
+ continue;
+ }
+
+ if (marker == EmptyMarker) {
+ // Empty marker. Occurs at the end of a package details.
+ // Process the collected package data, if any.
+ processCurrentPackage();
+ continue;
+ }
+
+ if (marker == None) {
+ if (collectingPackageData)
+ packageData << outputLine; // Collect data until next marker.
+ else
+ continue;
+ } else {
+ // Package marker found.
+ processCurrentPackage(); // New package starts. Process the collected package data, if any.
+ currentPackageMarker = marker;
+ collectingPackageData = true;
+ packageData << outputLine;
+ }
+ }
+ compileData();
+ Utils::sort(m_installedPlatforms);
+}
+
+void SdkManagerOutputParser::compileData()
+{
+ // Associate the system images with sdk platforms.
+ for (auto &image : m_installedSystemImages) {
+ auto findPlatfom = [image](const SdkPlatform &platform) {
+ return platform.apiLevel == image.apiLevel;
+ };
+ auto itr = std::find_if(m_installedPlatforms.begin(), m_installedPlatforms.end(), findPlatfom);
+ if (itr != m_installedPlatforms.end())
+ itr->systemImages.append(image);
+ }
+}
+
+void SdkManagerOutputParser::parsePackageData(MarkerTag packageMarker, const QStringList &data)
+{
+ QTC_ASSERT(!data.isEmpty() && packageMarker != None, return);
+
+ if (m_currentSection != MarkerTag::InstalledPackagesMarker)
+ return; // For now, only interested in installed packages.
+
+ switch (packageMarker) {
+ case MarkerTag::PlatformMarker:
+ {
+ SdkPlatform platform;
+ if (parsePlatform(data, &platform))
+ m_installedPlatforms.append(platform);
+ else
+ qCDebug(sdkManagerLog) << "Platform: Parsing failed: " << data;
+ }
+ break;
+
+ case MarkerTag::SystemImageMarker:
+ {
+ SystemImage image;
+ if (parseSystemImage(data, &image))
+ m_installedSystemImages.append(image);
+ else
+ qCDebug(sdkManagerLog) << "System Image: Parsing failed: " << data;
+ }
+ break;
+
+ default:
+ qCDebug(sdkManagerLog) << "Unhandled package: " << markerTags.at(packageMarker);
+ break;
+ }
+}
+
+bool SdkManagerOutputParser::parsePlatform(const QStringList &data, SdkPlatform *platform) const
+{
+ QTC_ASSERT(platform && !data.isEmpty(), return false);
+
+ QStringList parts = data.at(0).split(';');
+ if (parts.count() < 2) {
+ qCDebug(sdkManagerLog) << "Platform: Unexpected header: "<< data;
+ return false;
+ }
+ platform->name = parts[1];
+ platform->package = data.at(0);
+
+ foreach (QString line, data) {
+ QString value;
+ if (valueForKey(installLocationKey, line, &value))
+ platform->installedLocation = Utils::FileName::fromString(value);
+ }
+
+ int apiLevel = AndroidManager::findApiLevel(platform->installedLocation);
+ if (apiLevel != -1)
+ platform->apiLevel = apiLevel;
+ else
+ qCDebug(sdkManagerLog) << "Platform: Can not parse api level: "<< data;
+
+ return apiLevel != -1;
+}
+
+bool SdkManagerOutputParser::parseSystemImage(const QStringList &data, SystemImage *image)
+{
+ QTC_ASSERT(image && !data.isEmpty(), return false);
+
+ QStringList parts = data.at(0).split(';');
+ QTC_ASSERT(!data.isEmpty() && parts.count() >= 4,
+ qCDebug(sdkManagerLog) << "System Image: Unexpected header: " << data);
+
+ image->package = data.at(0);
+ foreach (QString line, data) {
+ QString value;
+ if (valueForKey(installLocationKey, line, &value))
+ image->installedLocation = Utils::FileName::fromString(value);
+ }
+
+ Utils::FileName propertiesPath = image->installedLocation;
+ propertiesPath.appendPath("/source.properties");
+ if (propertiesPath.exists()) {
+ // Installed System Image.
+ QSettings imageProperties(propertiesPath.toString(), QSettings::IniFormat);
+ bool validApiLevel = false;
+ image->apiLevel = imageProperties.value(apiLevelPropertyKey).toInt(&validApiLevel);
+ if (!validApiLevel) {
+ qCDebug(sdkManagerLog) << "System Image: Can not parse api level: "<< data;
+ return false;
+ }
+ image->abiName = imageProperties.value(abiPropertyKey).toString();
+ } else if (parts.count() >= 4){
+ image->apiLevel = parts[1].section('-', 1, 1).toInt();
+ image->abiName = parts[3];
+ } else {
+ qCDebug(sdkManagerLog) << "System Image: Can not parse: "<< data;
+ return false;
+ }
+
+ return true;
+}
+
+SdkManagerOutputParser::MarkerTag SdkManagerOutputParser::parseMarkers(const QString &line)
+{
+ if (line.isEmpty())
+ return EmptyMarker;
+
+ for (auto pair: markerTags) {
+ if (line.startsWith(QLatin1String(pair.second)))
+ return pair.first;
+ }
+
+ return None;
+}
+
+} // namespace Internal
+} // namespace Android
diff --git a/src/plugins/android/androidsdkmanager.h b/src/plugins/android/androidsdkmanager.h
new file mode 100644
index 0000000000..2c11148072
--- /dev/null
+++ b/src/plugins/android/androidsdkmanager.h
@@ -0,0 +1,51 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** 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.
+**
+****************************************************************************/
+#pragma once
+
+#include "utils/fileutils.h"
+#include "androidconfigurations.h"
+
+#include <memory>
+
+namespace Android {
+namespace Internal {
+
+class SdkManagerOutputParser;
+
+class AndroidSdkManager
+{
+public:
+ AndroidSdkManager(const AndroidConfig &config);
+ ~AndroidSdkManager();
+
+ SdkPlatformList availableSdkPlatforms();
+
+private:
+ const AndroidConfig &m_config;
+ std::unique_ptr<SdkManagerOutputParser> m_parser;
+};
+
+} // namespace Internal
+} // namespace Android
diff --git a/src/plugins/android/androidsettingswidget.cpp b/src/plugins/android/androidsettingswidget.cpp
index 5cdf42cdf3..a0a24ea087 100644
--- a/src/plugins/android/androidsettingswidget.cpp
+++ b/src/plugins/android/androidsettingswidget.cpp
@@ -30,6 +30,7 @@
#include "androidconfigurations.h"
#include "androidconstants.h"
#include "androidtoolchain.h"
+#include "androidavdmanager.h"
#include <utils/environment.h>
#include <utils/hostosinfo.h>
@@ -58,7 +59,7 @@
namespace Android {
namespace Internal {
-void AvdModel::setAvdList(const QVector<AndroidDeviceInfo> &list)
+void AvdModel::setAvdList(const AndroidDeviceInfoList &list)
{
beginResetModel();
m_list = list;
@@ -128,10 +129,13 @@ AndroidSettingsWidget::AndroidSettingsWidget(QWidget *parent)
m_ndkState(NotSet),
m_javaState(NotSet),
m_ui(new Ui_AndroidSettingsWidget),
- m_androidConfig(AndroidConfigurations::currentConfig())
+ m_androidConfig(AndroidConfigurations::currentConfig()),
+ m_avdManager(new AndroidAvdManager(m_androidConfig))
{
m_ui->setupUi(this);
+ m_ui->deprecatedInfoIconLabel->setPixmap(Utils::Icons::INFO.pixmap());
+
connect(&m_checkGdbWatcher, &QFutureWatcherBase::finished,
this, &AndroidSettingsWidget::checkGdbFinished);
@@ -158,7 +162,8 @@ AndroidSettingsWidget::AndroidSettingsWidget(QWidget *parent)
m_ui->AntLocationPathChooser->setPromptDialogTitle(tr("Select ant Script"));
m_ui->AntLocationPathChooser->setInitialBrowsePathBackup(dir);
m_ui->AntLocationPathChooser->setPromptDialogFilter(filter);
- m_ui->UseGradleCheckBox->setChecked(m_androidConfig.useGrandle());
+
+ updateGradleBuildUi();
m_ui->OpenJDKLocationPathChooser->setFileName(m_androidConfig.openJDKLocation());
m_ui->OpenJDKLocationPathChooser->setPromptDialogTitle(tr("Select JDK Path"));
@@ -463,7 +468,7 @@ void AndroidSettingsWidget::enableAvdControls()
void AndroidSettingsWidget::startUpdateAvd()
{
disableAvdControls();
- m_virtualDevicesWatcher.setFuture(m_androidConfig.androidVirtualDevicesFuture());
+ m_virtualDevicesWatcher.setFuture(m_avdManager->avdList());
}
void AndroidSettingsWidget::updateAvds()
@@ -476,6 +481,13 @@ void AndroidSettingsWidget::updateAvds()
enableAvdControls();
}
+void AndroidSettingsWidget::updateGradleBuildUi()
+{
+ m_ui->UseGradleCheckBox->setEnabled(m_androidConfig.antScriptsAvailable());
+ m_ui->UseGradleCheckBox->setChecked(!m_androidConfig.antScriptsAvailable() ||
+ m_androidConfig.useGrandle());
+}
+
bool AndroidSettingsWidget::sdkLocationIsValid() const
{
Utils::FileName androidExe = m_androidConfig.sdkLocation();
@@ -505,6 +517,7 @@ void AndroidSettingsWidget::saveSettings()
void AndroidSettingsWidget::sdkLocationEditingFinished()
{
m_androidConfig.setSdkLocation(Utils::FileName::fromUserInput(m_ui->SDKLocationPathChooser->rawPath()));
+ updateGradleBuildUi();
check(Sdk);
@@ -587,12 +600,12 @@ void AndroidSettingsWidget::addAVD()
disableAvdControls();
AndroidConfig::CreateAvdInfo info = m_androidConfig.gatherCreateAVDInfo(this);
- if (info.target.isEmpty()) {
+ if (!info.target.isValid()) {
enableAvdControls();
return;
}
- m_futureWatcher.setFuture(m_androidConfig.createAVD(info));
+ m_futureWatcher.setFuture(m_avdManager->createAvd(info));
}
void AndroidSettingsWidget::avdAdded()
@@ -620,13 +633,13 @@ void AndroidSettingsWidget::removeAVD()
return;
}
- m_androidConfig.removeAVD(avdName);
+ m_avdManager->removeAvd(avdName);
startUpdateAvd();
}
void AndroidSettingsWidget::startAVD()
{
- m_androidConfig.startAVDAsync(m_AVDModel.avdName(m_ui->AVDTableView->currentIndex()));
+ m_avdManager->startAvdAsync(m_AVDModel.avdName(m_ui->AVDTableView->currentIndex()));
}
void AndroidSettingsWidget::avdActivated(const QModelIndex &index)
@@ -671,16 +684,15 @@ void AndroidSettingsWidget::showGdbWarningDialog()
void AndroidSettingsWidget::manageAVD()
{
- QProcess *avdProcess = new QProcess();
- connect(this, &QObject::destroyed, avdProcess, &QObject::deleteLater);
- connect(avdProcess, static_cast<void (QProcess::*)(int)>(&QProcess::finished),
- avdProcess, &QObject::deleteLater);
-
- avdProcess->setProcessEnvironment(m_androidConfig.androidToolEnvironment().toProcessEnvironment());
- QString executable = m_androidConfig.androidToolPath().toString();
- QStringList arguments = QStringList("avd");
-
- avdProcess->start(executable, arguments);
+ if (m_avdManager->avdManagerUiToolAvailable()) {
+ m_avdManager->launchAvdManagerUiTool();
+ } else {
+ QMessageBox::warning(this, tr("AVD Manager Not Available"),
+ tr("AVD manager UI tool is not available in the installed SDK tools"
+ "(version %1). Use the command line tool \"avdmanager\" for "
+ "advanced AVD management.")
+ .arg(m_androidConfig.sdkToolsVersion().toString()));
+ }
}
diff --git a/src/plugins/android/androidsettingswidget.h b/src/plugins/android/androidsettingswidget.h
index 3572ffef1a..50be7c1049 100644
--- a/src/plugins/android/androidsettingswidget.h
+++ b/src/plugins/android/androidsettingswidget.h
@@ -33,6 +33,8 @@
#include <QAbstractTableModel>
#include <QFutureWatcher>
+#include <memory>
+
QT_BEGIN_NAMESPACE
class Ui_AndroidSettingsWidget;
QT_END_NAMESPACE
@@ -40,11 +42,13 @@ QT_END_NAMESPACE
namespace Android {
namespace Internal {
+class AndroidAvdManager;
+
class AvdModel: public QAbstractTableModel
{
Q_OBJECT
public:
- void setAvdList(const QVector<AndroidDeviceInfo> &list);
+ void setAvdList(const AndroidDeviceInfoList &list);
QString avdName(const QModelIndex &index) const;
QModelIndex indexForAvdName(const QString &avdName) const;
@@ -55,7 +59,7 @@ protected:
int columnCount(const QModelIndex &parent = QModelIndex()) const;
private:
- QVector<AndroidDeviceInfo> m_list;
+ AndroidDeviceInfoList m_list;
};
class AndroidSettingsWidget : public QWidget
@@ -91,6 +95,7 @@ private:
void checkGdbFinished();
void showGdbWarningDialog();
void updateAvds();
+ void updateGradleBuildUi();
private:
enum Mode { Sdk = 1, Ndk = 2, Java = 4, All = Sdk | Ndk | Java };
@@ -117,8 +122,9 @@ private:
QFutureWatcher<QPair<QStringList, bool>> m_checkGdbWatcher;
QStringList m_gdbCheckPaths;
- QFutureWatcher<QVector<AndroidDeviceInfo>> m_virtualDevicesWatcher;
+ QFutureWatcher<AndroidDeviceInfoList> m_virtualDevicesWatcher;
QString m_lastAddedAvd;
+ std::unique_ptr<AndroidAvdManager> m_avdManager;
};
} // namespace Internal
diff --git a/src/plugins/android/androidsettingswidget.ui b/src/plugins/android/androidsettingswidget.ui
index 952dc444cc..338c73da04 100644
--- a/src/plugins/android/androidsettingswidget.ui
+++ b/src/plugins/android/androidsettingswidget.ui
@@ -237,20 +237,30 @@
</layout>
</item>
<item row="8" column="1">
- <widget class="QCheckBox" name="CreateKitCheckBox">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Automatically create kits for Android tool chains</string>
+ <layout class="QHBoxLayout" name="horizontalLayout_8">
+ <property name="spacing">
+ <number>2</number>
</property>
- <property name="checked">
- <bool>true</bool>
+ <property name="leftMargin">
+ <number>0</number>
</property>
- </widget>
+ <item>
+ <widget class="QCheckBox" name="CreateKitCheckBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Automatically create kits for Android tool chains</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
</item>
<item row="9" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
@@ -289,17 +299,59 @@
</layout>
</item>
<item row="10" column="1">
- <widget class="QCheckBox" name="UseGradleCheckBox">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
+ <layout class="QHBoxLayout" name="horizontalLayout_7">
+ <property name="spacing">
+ <number>4</number>
</property>
- <property name="text">
- <string>Use Gradle instead of Ant</string>
+ <property name="leftMargin">
+ <number>0</number>
</property>
- </widget>
+ <item>
+ <widget class="QCheckBox" name="UseGradleCheckBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Use Gradle instead of Ant (Ant builds are deprecated)</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="deprecatedInfoIconLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Gradle builds are forced from Android SDK tools version 25.3.0 onwards as Ant scripts are no longer available.</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
</item>
<item row="11" column="0">
<widget class="QLabel" name="AntLocationLabel">
diff --git a/src/plugins/android/androidtoolmanager.cpp b/src/plugins/android/androidtoolmanager.cpp
new file mode 100644
index 0000000000..a6e61d96d9
--- /dev/null
+++ b/src/plugins/android/androidtoolmanager.cpp
@@ -0,0 +1,346 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** 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 "androidtoolmanager.h"
+
+#include "androidmanager.h"
+
+#include "utils/algorithm.h"
+#include "utils/environment.h"
+#include "utils/qtcassert.h"
+#include "utils/runextensions.h"
+#include "utils/synchronousprocess.h"
+
+#include <QLoggingCategory>
+
+namespace {
+Q_LOGGING_CATEGORY(androidToolLog, "qtc.android.sdkManager")
+}
+
+namespace Android {
+namespace Internal {
+
+using namespace Utils;
+
+class AndroidToolOutputParser
+{
+public:
+ void parseTargetListing(const QString &output, const FileName &sdkLocation,
+ SdkPlatformList *platformList);
+
+ QList<SdkPlatform> m_installedPlatforms;
+};
+
+/*!
+ Runs the \c android tool located at \a toolPath with arguments \a args and environment \a
+ environment. Returns \c true for successful execution. Command's output is copied to \a
+ output.
+ */
+static bool androidToolCommand(Utils::FileName toolPath, const QStringList &args,
+ const Environment &environment, QString *output)
+{
+ QString androidToolPath = toolPath.toString();
+ SynchronousProcess proc;
+ proc.setProcessEnvironment(environment.toProcessEnvironment());
+ SynchronousProcessResponse response = proc.runBlocking(androidToolPath, args);
+ if (response.result == SynchronousProcessResponse::Finished) {
+ if (output)
+ *output = response.allOutput();
+ return true;
+ }
+ return false;
+}
+
+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;
+}
+
+AndroidToolManager::AndroidToolManager(const AndroidConfig &config) :
+ m_config(config),
+ m_parser(new AndroidToolOutputParser)
+{
+
+}
+
+AndroidToolManager::~AndroidToolManager()
+{
+
+}
+
+SdkPlatformList AndroidToolManager::availableSdkPlatforms() const
+{
+ SdkPlatformList list;
+ QString targetListing;
+ if (androidToolCommand(m_config.androidToolPath(), QStringList({"list", "target"}),
+ androidToolEnvironment(), &targetListing)) {
+ m_parser->parseTargetListing(targetListing, m_config.sdkLocation(), &list);
+ } else {
+ qCDebug(androidToolLog) << "Android tool target listing failed";
+ }
+ return list;
+}
+
+void AndroidToolManager::launchAvdManager() const
+{
+ QProcess::startDetached(m_config.androidToolPath().toString(), QStringList("avd"));
+}
+
+QFuture<AndroidConfig::CreateAvdInfo>
+AndroidToolManager::createAvd(AndroidConfig::CreateAvdInfo info) const
+{
+ return Utils::runAsync(&AndroidToolManager::createAvdImpl, info,
+ m_config.androidToolPath(), androidToolEnvironment());
+}
+
+bool AndroidToolManager::removeAvd(const QString &name) const
+{
+ SynchronousProcess proc;
+ proc.setTimeoutS(5);
+ proc.setProcessEnvironment(androidToolEnvironment().toProcessEnvironment());
+ SynchronousProcessResponse response
+ = proc.runBlocking(m_config.androidToolPath().toString(),
+ QStringList({"delete", "avd", "-n", name}));
+ return response.result == SynchronousProcessResponse::Finished && response.exitCode == 0;
+}
+
+QFuture<AndroidDeviceInfoList> AndroidToolManager::androidVirtualDevicesFuture() const
+{
+ return Utils::runAsync(&AndroidToolManager::androidVirtualDevices,
+ m_config.androidToolPath(), m_config.sdkLocation(),
+ androidToolEnvironment());
+}
+
+Environment AndroidToolManager::androidToolEnvironment() const
+{
+ Environment env = Environment::systemEnvironment();
+ Utils::FileName jdkLocation = m_config.openJDKLocation();
+ if (!jdkLocation.isEmpty()) {
+ env.set(QLatin1String("JAVA_HOME"), jdkLocation.toUserOutput());
+ Utils::FileName binPath = jdkLocation;
+ binPath.appendPath(QLatin1String("bin"));
+ env.prependOrSetPath(binPath.toUserOutput());
+ }
+ return env;
+}
+
+AndroidConfig::CreateAvdInfo AndroidToolManager::createAvdImpl(AndroidConfig::CreateAvdInfo info,
+ FileName androidToolPath,
+ Environment env)
+{
+ QProcess proc;
+ proc.setProcessEnvironment(env.toProcessEnvironment());
+ QStringList arguments;
+ arguments << QLatin1String("create") << QLatin1String("avd")
+ << QLatin1String("-t") << info.target.name
+ << QLatin1String("-n") << info.name
+ << QLatin1String("-b") << info.abi;
+ if (info.sdcardSize > 0)
+ arguments << QLatin1String("-c") << QString::fromLatin1("%1M").arg(info.sdcardSize);
+ proc.start(androidToolPath.toString(), arguments);
+ if (!proc.waitForStarted()) {
+ info.error = tr("Could not start process \"%1 %2\"")
+ .arg(androidToolPath.toString(), arguments.join(QLatin1Char(' ')));
+ return info;
+ }
+ QTC_CHECK(proc.state() == QProcess::Running);
+ 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;
+ }
+ QTC_CHECK(proc.state() == QProcess::NotRunning);
+
+ 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;
+}
+
+AndroidDeviceInfoList AndroidToolManager::androidVirtualDevices(const Utils::FileName &androidTool,
+ const FileName &sdkLocationPath,
+ const Environment &environment)
+{
+ AndroidDeviceInfoList devices;
+ QString output;
+ if (!androidToolCommand(androidTool, QStringList({"list", "avd"}), environment, &output))
+ return devices;
+
+ QStringList avds = output.split('\n');
+ if (avds.empty())
+ return devices;
+
+ while (avds.first().startsWith(QLatin1String("* daemon")))
+ avds.removeFirst(); // remove the daemon logs
+ avds.removeFirst(); // remove "List of devices attached" header line
+
+ bool nextLineIsTargetLine = false;
+
+ AndroidDeviceInfo dev;
+ for (int i = 0; i < avds.size(); i++) {
+ QString line = avds.at(i);
+ if (!line.contains(QLatin1String("Name:")))
+ continue;
+
+ int index = line.indexOf(QLatin1Char(':')) + 2;
+ if (index >= line.size())
+ break;
+ dev.avdname = line.mid(index).trimmed();
+ dev.sdk = -1;
+ dev.cpuAbi.clear();
+ ++i;
+ for (; i < avds.size(); ++i) {
+ line = avds.at(i);
+ if (line.contains(QLatin1String("---------")))
+ break;
+
+ if (line.contains(QLatin1String("Target:")) || nextLineIsTargetLine) {
+ if (line.contains(QLatin1String("Google APIs"))) {
+ nextLineIsTargetLine = true;
+ continue;
+ }
+
+ nextLineIsTargetLine = false;
+
+ int lastIndex = line.lastIndexOf(QLatin1Char(' '));
+ if (lastIndex == -1) // skip line
+ break;
+ QString tmp = line.mid(lastIndex).remove(QLatin1Char(')')).trimmed();
+ Utils::FileName platformPath = sdkLocationPath;
+ platformPath.appendPath(QString("/platforms/android-%1").arg(tmp));
+ dev.sdk = AndroidManager::findApiLevel(platformPath);
+ }
+ 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("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);
+
+ return devices;
+}
+
+void AndroidToolOutputParser::parseTargetListing(const QString &output,
+ const Utils::FileName &sdkLocation,
+ SdkPlatformList *platformList)
+{
+ if (!platformList)
+ return;
+
+ auto addSystemImage = [](const QStringList& abiList, SdkPlatform &platform) {
+ foreach (auto imageAbi, abiList) {
+ SystemImage image;
+ image.abiName = imageAbi;
+ image.apiLevel = platform.apiLevel;
+ platform.systemImages.append(image);
+ }
+ };
+
+ SdkPlatform platform;
+ QStringList abiList;
+ foreach (const QString &l, output.split('\n')) {
+ const QString line = l.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);
+ const QString tmp = androidTarget.mid(androidTarget.lastIndexOf(QLatin1Char('-')) + 1);
+ Utils::FileName platformPath = sdkLocation;
+ platformPath.appendPath(QString("/platforms/android-%1").arg(tmp));
+ platform.installedLocation = platformPath;
+ platform.apiLevel = AndroidManager::findApiLevel(platformPath);
+ } else if (line.startsWith(QLatin1String("Name:"))) {
+ platform.name = line.mid(6);
+ } else if (line.startsWith(QLatin1String("Tag/ABIs :"))) {
+ abiList = cleanAndroidABIs(line.mid(10).trimmed().split(QLatin1String(", ")));
+ } else if (line.startsWith(QLatin1String("ABIs"))) {
+ abiList = cleanAndroidABIs(line.mid(6).trimmed().split(QLatin1String(", ")));
+ } else if (line.startsWith(QLatin1String("---")) || line.startsWith(QLatin1String("==="))) {
+ if (platform.apiLevel == -1)
+ continue;
+
+ addSystemImage(abiList, platform);
+ *platformList << platform;
+
+ platform = SdkPlatform();
+ abiList.clear();
+ }
+ }
+
+ // The last parsed Platform.
+ if (platform.apiLevel != -1) {
+ addSystemImage(abiList, platform);
+ *platformList << platform;
+ }
+
+ Utils::sort(*platformList);
+}
+
+} // namespace Internal
+} // namespace Android
diff --git a/src/plugins/android/androidtoolmanager.h b/src/plugins/android/androidtoolmanager.h
new file mode 100644
index 0000000000..befb095b92
--- /dev/null
+++ b/src/plugins/android/androidtoolmanager.h
@@ -0,0 +1,72 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** 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.
+**
+****************************************************************************/
+#pragma once
+
+#include "utils/fileutils.h"
+#include "androidconfigurations.h"
+
+#include <QStringList>
+
+#include <memory>
+
+namespace Android {
+class AndroidConfig;
+
+namespace Internal {
+
+class AndroidToolOutputParser;
+/*!
+ Wraps the \c android tool's usage. The tool itself is deprecated since SDK tools version 25.3.0.
+ */
+class AndroidToolManager
+{
+ Q_DECLARE_TR_FUNCTIONS(AndroidToolManager)
+
+public:
+ AndroidToolManager(const AndroidConfig &config);
+ ~AndroidToolManager();
+
+ SdkPlatformList availableSdkPlatforms() const;
+ void launchAvdManager() const;
+
+ QFuture<AndroidConfig::CreateAvdInfo> createAvd(AndroidConfig::CreateAvdInfo info) const;
+ bool removeAvd(const QString &name) const;
+ QFuture<AndroidDeviceInfoList> androidVirtualDevicesFuture() const;
+
+// Helper methods
+private:
+ Utils::Environment androidToolEnvironment() const;
+ static AndroidConfig::CreateAvdInfo createAvdImpl(AndroidConfig::CreateAvdInfo info,
+ Utils::FileName androidToolPath, Utils::Environment env);
+ static AndroidDeviceInfoList androidVirtualDevices(const Utils::FileName &androidTool,
+ const Utils::FileName &sdkLlocationPath,
+ const Utils::Environment &environment);
+private:
+ const AndroidConfig &m_config;
+ std::unique_ptr<AndroidToolOutputParser> m_parser;
+};
+
+} // namespace Internal
+} // namespace Android
diff --git a/src/plugins/android/avddialog.cpp b/src/plugins/android/avddialog.cpp
index 9bfea8ef21..eb7da93e90 100644
--- a/src/plugins/android/avddialog.cpp
+++ b/src/plugins/android/avddialog.cpp
@@ -26,6 +26,7 @@
#include "avddialog.h"
#include "androidconfigurations.h"
+#include <utils/algorithm.h>
#include <utils/tooltip/tooltip.h>
#include <utils/utilsicons.h>
@@ -67,12 +68,12 @@ AvdDialog::AvdDialog(int minApiLevel, const QString &targetArch, const AndroidCo
bool AvdDialog::isValid() const
{
- return !name().isEmpty() && !target().isEmpty() && !abi().isEmpty();
+ return !name().isEmpty() && target().isValid() && !abi().isEmpty();
}
-QString AvdDialog::target() const
+SdkPlatform AvdDialog::target() const
{
- return m_avdDialog.targetComboBox->currentText();
+ return m_avdDialog.targetComboBox->currentData().value<SdkPlatform>();
}
QString AvdDialog::name() const
@@ -92,15 +93,23 @@ int AvdDialog::sdcardSize() const
void AvdDialog::updateApiLevelComboBox()
{
- QList<SdkPlatform> filteredList;
- QList<SdkPlatform> platforms = m_config->sdkTargets(m_minApiLevel);
- foreach (const SdkPlatform &platform, platforms) {
- if (platform.abis.contains(abi()))
- filteredList << platform;
- }
+ SdkPlatformList filteredList;
+ SdkPlatformList platforms = m_config->sdkTargets(m_minApiLevel);
+
+ QString selectedAbi = abi();
+ auto hasAbi = [selectedAbi](const SystemImage &image) {
+ return image.isValid() && (image.abiName == selectedAbi);
+ };
+
+ filteredList = Utils::filtered(platforms, [hasAbi](const SdkPlatform &platform) {
+ return Utils::anyOf(platform.systemImages,hasAbi);
+ });
m_avdDialog.targetComboBox->clear();
- m_avdDialog.targetComboBox->addItems(AndroidConfig::apiLevelNamesFor(filteredList));
+ foreach (const SdkPlatform &platform, filteredList) {
+ m_avdDialog.targetComboBox->addItem(AndroidConfig::apiLevelNameFor(platform),
+ QVariant::fromValue<SdkPlatform>(platform));
+ }
if (platforms.isEmpty()) {
m_avdDialog.warningIcon->setVisible(true);
diff --git a/src/plugins/android/avddialog.h b/src/plugins/android/avddialog.h
index 950285aadc..d22e6af64f 100644
--- a/src/plugins/android/avddialog.h
+++ b/src/plugins/android/avddialog.h
@@ -32,6 +32,7 @@
namespace Android {
class AndroidConfig;
+class SdkPlatform;
namespace Internal {
@@ -42,7 +43,7 @@ public:
explicit AvdDialog(int minApiLevel, const QString &targetArch,
const AndroidConfig *config, QWidget *parent = 0);
- QString target() const;
+ Android::SdkPlatform target() const;
QString name() const;
QString abi() const;
int sdcardSize() const;