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
This commit is contained in:
Eike Ziller
2017-04-19 09:56:14 +02:00
232 changed files with 3919 additions and 1830 deletions

View File

@@ -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 \

View File

@@ -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",

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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="1" column="0">
<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="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>Use Gradle</string>
<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>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>

View File

@@ -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);
}
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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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;

View File

@@ -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()

View File

@@ -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;
};
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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();

View File

@@ -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)
enum class PidStatus {
Found,
Lost
};
struct PidInfo
{
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;
}
void findProcessPID(QFutureInterface<qint64> &fi, const QString &adbPath,
QStringList selector, const QString &packageName)
{
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::findProcessPids()
{
static QMap<qint64, QByteArray> extractedPids;
static auto oldPids = m_processPids;
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)
{
if (!m_pidFinder.isFinished())
m_pidFinder.cancel();
if (m_processPID != -1) {
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);

View File

@@ -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();

View File

@@ -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

View File

@@ -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

View File

@@ -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()));
}
}

View File

@@ -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

View File

@@ -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>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<property name="spacing">
<number>2</number>
</property>
<property name="text">
<string>Automatically create kits for Android tool chains</string>
<property name="leftMargin">
<number>0</number>
</property>
<property name="checked">
<bool>true</bool>
</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">

View File

@@ -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

View File

@@ -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

View File

@@ -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);

View File

@@ -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;