Commit 198c83ea authored by Vikas Pachdha's avatar Vikas Pachdha
Browse files

Android: Add Android tool manager



Refactor the use of android tool and groundwork for the new sdk
and avd management tool's integration

Task-number: QTCREATORBUG-17814
Change-Id: I6a5920f9ba92508f904cd8cf28bf62c82de2d820
Reviewed-by: default avatarBogDan Vatra <bogdan@kdab.com>
parent f173dc82
......@@ -47,7 +47,8 @@ HEADERS += \
android_global.h \
androidbuildapkstep.h \
androidbuildapkwidget.h \
androidrunnable.h
androidrunnable.h \
androidtoolmanager.h
SOURCES += \
androidconfigurations.cpp \
......@@ -88,7 +89,8 @@ SOURCES += \
androidbuildapkstep.cpp \
androidbuildapkwidget.cpp \
androidqtsupport.cpp \
androidrunnable.cpp
androidrunnable.cpp \
androidtoolmanager.cpp
FORMS += \
androidsettingswidget.ui \
......
......@@ -93,6 +93,8 @@ Project {
"androidsignaloperation.h",
"androidtoolchain.cpp",
"androidtoolchain.h",
"androidtoolmanager.cpp",
"androidtoolmanager.h",
"avddialog.cpp",
"avddialog.h",
"certificatesmodel.cpp",
......
......@@ -28,8 +28,10 @@
#include "androidtoolchain.h"
#include "androiddevice.h"
#include "androidgdbserverkitinformation.h"
#include "androidmanager.h"
#include "androidqtversion.h"
#include "androiddevicedialog.h"
#include "androidtoolmanager.h"
#include "avddialog.h"
#include <coreplugin/icore.h>
......@@ -113,33 +115,6 @@ namespace {
+ 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 +137,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
}
}
//////////////////////////////////
......@@ -372,48 +328,11 @@ void AndroidConfig::updateAvailableSdkPlatforms() const
{
if (m_availableSdkPlatformsUpToDate)
return;
m_availableSdkPlatforms.clear();
SynchronousProcess proc;
proc.setProcessEnvironment(androidToolEnvironment().toProcessEnvironment());
SynchronousProcessResponse response
= proc.runBlocking(androidToolPath().toString(),
QStringList({"list", "target"})); // list available AVDs
if (response.result != SynchronousProcessResponse::Finished)
return;
SdkPlatform platform;
foreach (const QString &l, response.allOutput().split('\n')) {
const QString line = l.trimmed();
if (line.startsWith(QLatin1String("id:")) && line.contains(QLatin1String("android-"))) {
int index = line.indexOf(QLatin1String("\"android-"));
if (index == -1)
continue;
QString androidTarget = line.mid(index + 1, line.length() - index - 2);
const QString tmp = androidTarget.mid(androidTarget.lastIndexOf(QLatin1Char('-')) + 1);
platform.apiLevel = apiLevelFromAndroidList(tmp);
} else if (line.startsWith(QLatin1String("Name:"))) {
platform.name = line.mid(6);
} else if (line.startsWith(QLatin1String("Tag/ABIs :"))) {
platform.abis = cleanAndroidABIs(line.mid(10).trimmed().split(QLatin1String(", ")));
} else if (line.startsWith(QLatin1String("ABIs"))) {
platform.abis = cleanAndroidABIs(line.mid(6).trimmed().split(QLatin1String(", ")));
} else if (line.startsWith(QLatin1String("---")) || line.startsWith(QLatin1String("==="))) {
if (platform.apiLevel == -1)
continue;
auto it = std::lower_bound(m_availableSdkPlatforms.begin(), m_availableSdkPlatforms.end(),
platform, sortSdkPlatformByApiLevel);
m_availableSdkPlatforms.insert(it, platform);
platform = SdkPlatform();
}
}
if (platform.apiLevel != -1) {
auto it = std::lower_bound(m_availableSdkPlatforms.begin(), m_availableSdkPlatforms.end(),
platform, sortSdkPlatformByApiLevel);
m_availableSdkPlatforms.insert(it, platform);
}
m_availableSdkPlatforms.clear();
AndroidToolManager toolManager(*this);
m_availableSdkPlatforms = toolManager.availableSdkPlatforms();
Utils::sort(m_availableSdkPlatforms, sortSdkPlatformByApiLevel);
m_availableSdkPlatformsUpToDate = true;
}
......@@ -446,18 +365,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()) {
......@@ -583,7 +490,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,157 +512,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))
......@@ -1353,6 +1109,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
......
......@@ -68,19 +68,19 @@ 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;
};
class SdkPlatform
{
public:
SdkPlatform()
: apiLevel(-1)
{}
int apiLevel;
int apiLevel = -1;
QString name;
Utils::FileName installedLocation;
QStringList abis;
};
using SdkPlatformList = QList<SdkPlatform>;
class ANDROID_EXPORT AndroidConfig
{
......@@ -121,13 +121,12 @@ public:
Utils::FileName adbToolPath() const;
Utils::FileName androidToolPath() const;
Utils::Environment androidToolEnvironment() const;
Utils::FileName antToolPath() const;
Utils::FileName emulatorToolPath() 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;
......@@ -143,15 +142,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;
......@@ -172,7 +166,6 @@ public:
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;
......@@ -200,7 +193,7 @@ private:
//caches
mutable bool m_availableSdkPlatformsUpToDate = false;
mutable QVector<SdkPlatform> m_availableSdkPlatforms;
mutable SdkPlatformList m_availableSdkPlatforms;
static bool sortSdkPlatformByApiLevel(const SdkPlatform &a, const SdkPlatform &b);
mutable bool m_NdkInformationUpToDate = false;
......
......@@ -423,7 +423,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_androidToolManager(new AndroidToolManager(AndroidConfigurations::currentConfig()))
{
m_ui->setupUi(this);
m_ui->deviceView->setModel(m_model);
......@@ -515,7 +516,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_androidToolManager->androidVirtualDevicesFuture());
}
void AndroidDeviceDialog::devicesRefreshed()
......@@ -588,7 +589,7 @@ void AndroidDeviceDialog::createAvd()
return;
}
m_futureWatcherAddDevice.setFuture(AndroidConfigurations::currentConfig().createAVD(info));
m_futureWatcherAddDevice.setFuture(m_androidToolManager->createAvd(info));
}
void AndroidDeviceDialog::avdAdded()
......
......@@ -26,12 +26,15 @@
#pragma once
#include "androidconfigurations.h"
#include "androidtoolmanager.h"
#include <QVector>
#include <QDialog>
#include <QFutureWatcher>
#include <QTime>
#include <memory>
QT_BEGIN_NAMESPACE
class QModelIndex;
QT_END_NAMESPACE
......@@ -74,6 +77,7 @@ private:
QString m_abi;
QString m_avdNameFromAdd;
QString m_defaultDevice;
std::unique_ptr<AndroidToolManager> m_androidToolManager;
QVector<AndroidDeviceInfo> m_connectedDevices;
QFutureWatcher<AndroidConfig::CreateAvdInfo> m_futureWatcherAddDevice;
QFutureWatcher<QVector<AndroidDeviceInfo>> m_futureWatcherRefreshDevices;
......
......@@ -66,6 +66,7 @@ namespace {
const QLatin1String AndroidManifestName("AndroidManifest.xml");
const QLatin1String AndroidDefaultPropertiesName("project.properties");
const QLatin1String AndroidDeviceSn("AndroidDeviceSerialNumber");
const QLatin1String ApiLevelKey("AndroidVersion.ApiLevel");
} // anonymous namespace
......@@ -580,4 +581,19 @@ bool AndroidManager::updateGradleProperties(ProjectExplorer::Target *target)
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
......@@ -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
......@@ -30,6 +30,7 @@
#include "androidconfigurations.h"
#include "androidconstants.h"
#include "androidtoolchain.h"
#include "androidtoolmanager.h"
#include <utils/environment.h>
#include <utils/hostosinfo.h>
......@@ -128,7 +129,8 @@ AndroidSettingsWidget::AndroidSettingsWidget(QWidget *parent)
m_ndkState(NotSet),
m_javaState(NotSet),
m_ui(new Ui_AndroidSettingsWidget),
m_androidConfig(AndroidConfigurations::currentConfig())
m_androidConfig(AndroidConfigurations::currentConfig()),
m_androidToolManager(new AndroidToolManager(m_androidConfig))
{
m_ui->setupUi(this);
......@@ -463,7 +465,7 @@ void AndroidSettingsWidget::enableAvdControls()
void AndroidSettingsWidget::startUpdateAvd()
{
disableAvdControls();
m_virtualDevicesWatcher.setFuture(m_androidConfig.androidVirtualDevicesFuture());
m_virtualDevicesWatcher.setFuture(m_androidToolManager->androidVirtualDevicesFuture());
}
void AndroidSettingsWidget::updateAvds()
......@@ -592,7 +594,7 @@ void AndroidSettingsWidget::addAVD()
return;
}
m_futureWatcher.setFuture(m_androidConfig.createAVD(info));
m_futureWatcher.setFuture(m_androidToolManager->createAvd(info));
}
void AndroidSettingsWidget::avdAdded()
......@@ -620,7 +622,7 @@ void AndroidSettingsWidget::removeAVD()
return;
}
m_androidConfig.removeAVD(avdName);
m_androidToolManager->removeAvd(avdName);
startUpdateAvd();
}
......@@ -671,16 +673,7 @@ 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);
m_androidToolManager->launchAvdManager();
}
......
......@@ -33,6 +33,8 @@
#include <QAbstractTableModel>
#include <QFutureWatcher>
#include <memory>
QT_BEGIN_NAMESPACE
class Ui_AndroidSettingsWidget;
QT_END_NAMESPACE
......@@ -40,6 +42,8 @@ QT_END_NAMESPACE
namespace Android {
namespace Internal {