Android: Automatically download SDK tools and essential packages

Automatically download Android SDK Tools to default path
used by Android Studio, then essential packages will be installed
using the sdkmanager tool. Automatic installation can also be
triggered by an added button in the settings page.

Essentials packages include NDK Bundle and other NDK versions
required by previous Qt versions.

An sdk_definitions.json file holds download paths for SDK Tools,
and other (Qt version <-> essential packages) combinations.

[ChangeLog][Android] Automatically download SDK Tools, NDKs and
all essential packages for Android builds.

Task-number: QTCREATORBUG-23285
Change-Id: I90e7aafecd017d2bdc959e403711d9d440a6bbb2
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
Assam Boudjelthia
2019-12-23 16:13:23 +02:00
parent 8bef0c9155
commit f46099d21e
18 changed files with 844 additions and 97 deletions

View File

@@ -1,4 +1,4 @@
set(template_directories cplusplus debugger glsl modeleditor qml qmldesigner
set(template_directories android cplusplus debugger glsl modeleditor qml qmldesigner
qmlicons qml-type-descriptions schemes scripts snippets styles templates themes welcomescreen)
add_custom_target(copy_share_to_builddir ALL

View File

@@ -0,0 +1,25 @@
{
"common": {
"sdk_tools_url": {
"linux": "https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip",
"linux_sha256": "92ffee5a1d98d856634e8b71132e8a95d96c83a63fde1099be3d86df3106def9",
"windows": "https://dl.google.com/android/repository/sdk-tools-windows-4333796.zip",
"windows_sha256": "7e81d69c303e47a4f0e748a6352d85cd0c8fd90a5a95ae4e076b5e5f960d3c7a",
"mac": "https://dl.google.com/android/repository/sdk-tools-darwin-4333796.zip",
"mac_sha256": "ecb29358bc0f13d7c2fa0f9290135a5b608e38434aad9bf7067d0252c160853e"
},
"sdk_essential_packages": ["platform-tools", "platforms;android-29"]
},
"specific_qt_versions": [
{
"versions": ["default"],
"sdk_essential_packages": ["build-tools;29.0.2", "ndk-bundle"],
"ndk_path": "ndk-bundle"
},
{
"versions": ["5.12.[0-5]", "5.13.[0-1]"],
"sdk_essential_packages": ["build-tools;28.0.2", "ndk;19.2.5345600"],
"ndk_path": "ndk/19.2.5345600"
}
]
}

View File

@@ -23,7 +23,8 @@ DATA_DIRS = \
modeleditor \
glsl \
cplusplus \
indexer_preincludes
indexer_preincludes \
android
macx: DATA_DIRS += scripts
for(data_dir, DATA_DIRS) {

View File

@@ -33,6 +33,7 @@ add_qtc_plugin(Android
androidruncontrol.cpp androidruncontrol.h
androidrunner.cpp androidrunner.h
androidrunnerworker.cpp androidrunnerworker.h
androidsdkdownloader.cpp androidsdkdownloader.h
androidsdkmanager.cpp androidsdkmanager.h
androidsdkmanagerwidget.cpp androidsdkmanagerwidget.h androidsdkmanagerwidget.ui
androidsdkmodel.cpp androidsdkmodel.h

View File

@@ -48,7 +48,8 @@ HEADERS += \
androidsdkmanagerwidget.h \
androidpackageinstallationstep.h \
androidextralibrarylistmodel.h \
createandroidmanifestwizard.h
createandroidmanifestwizard.h \
androidsdkdownloader.h
SOURCES += \
androidconfigurations.cpp \
@@ -90,7 +91,8 @@ SOURCES += \
androidsdkmanagerwidget.cpp \
androidpackageinstallationstep.cpp \
androidextralibrarylistmodel.cpp \
createandroidmanifestwizard.cpp
createandroidmanifestwizard.cpp \
androidsdkdownloader.cpp
FORMS += \
androidsettingswidget.ui \

View File

@@ -61,6 +61,9 @@
#include <QDirIterator>
#include <QFileInfo>
#include <QHostAddress>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QLoggingCategory>
#include <QProcess>
#include <QRegularExpression>
@@ -72,6 +75,7 @@
#include <functional>
#include <memory>
using namespace QtSupport;
using namespace ProjectExplorer;
using namespace Utils;
@@ -82,11 +86,21 @@ static Q_LOGGING_CATEGORY(avdConfigLog, "qtc.android.androidconfig", QtWarningMs
namespace Android {
using namespace Internal;
const char JsonFilePath[] = "/android/sdk_definitions.json";
const char SdkToolsUrlKey[] = "sdk_tools_url";
const char CommonKey[] = "common";
const char SdkEssentialPkgsKey[] = "sdk_essential_packages";
const char VersionsKey[] = "versions";
const char NdkPathKey[] = "ndk_path";
const char SpecificQtVersionsKey[] = "specific_qt_versions";
const char DefaultVersionKey[] = "default";
namespace {
const char jdkSettingsPath[] = "HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit";
const QLatin1String SettingsGroup("AndroidConfigurations");
const QLatin1String SDKLocationKey("SDKLocation");
const QLatin1String SdkFullyConfiguredKey("AllEssentialsInstalled");
const QLatin1String SDKManagerToolArgsKey("SDKManagerToolArgs");
const QLatin1String NDKLocationKey("NDKLocation");
const QLatin1String OpenJDKLocationKey("OpenJDKLocation");
@@ -228,6 +242,7 @@ void AndroidConfig::load(const QSettings &settings)
m_keystoreLocation = FilePath::fromString(settings.value(KeystoreLocationKey).toString());
m_toolchainHost = settings.value(ToolchainHostKey).toString();
m_automaticKitCreation = settings.value(AutomaticKitCreationKey, true).toBool();
m_sdkFullyConfigured = settings.value(SdkFullyConfiguredKey, false).toBool();
PersistentSettingsReader reader;
if (reader.load(FilePath::fromString(sdkSettingsFileName()))
@@ -240,8 +255,10 @@ void AndroidConfig::load(const QSettings &settings)
m_keystoreLocation = FilePath::fromString(reader.restoreValue(KeystoreLocationKey, m_keystoreLocation.toString()).toString());
m_toolchainHost = reader.restoreValue(ToolchainHostKey, m_toolchainHost).toString();
m_automaticKitCreation = reader.restoreValue(AutomaticKitCreationKey, m_automaticKitCreation).toBool();
m_sdkFullyConfigured = reader.restoreValue(SdkFullyConfiguredKey, m_sdkFullyConfigured).toBool();
// persistent settings
}
parseDependenciesJson();
m_NdkInformationUpToDate = false;
}
@@ -260,6 +277,7 @@ void AndroidConfig::save(QSettings &settings) const
settings.setValue(PartitionSizeKey, m_partitionSize);
settings.setValue(AutomaticKitCreationKey, m_automaticKitCreation);
settings.setValue(ToolchainHostKey, m_toolchainHost);
settings.setValue(SdkFullyConfiguredKey, m_sdkFullyConfigured);
}
void AndroidConfig::updateNdkInformation() const
@@ -298,6 +316,72 @@ void AndroidConfig::updateNdkInformation() const
m_NdkInformationUpToDate = true;
}
void AndroidConfig::parseDependenciesJson()
{
QString sdkConfigFile(Core::ICore::resourcePath() + JsonFilePath);
QFile jsonFile(sdkConfigFile);
if (!jsonFile.open(QIODevice::ReadOnly))
qCDebug(avdConfigLog, "Couldn't open JSON config file %s.", qPrintable(sdkConfigFile));
QJsonDocument loadDoc(QJsonDocument::fromJson(jsonFile.readAll()));
QJsonObject jsonObject = loadDoc.object();
if (jsonObject.contains(CommonKey) && jsonObject[CommonKey].isObject()) {
QJsonObject commonObject = jsonObject[CommonKey].toObject();
// Parse SDK Tools URL
if (commonObject.contains(SdkToolsUrlKey) && commonObject[SdkToolsUrlKey].isObject()) {
QJsonObject sdkToolsObj(commonObject[SdkToolsUrlKey].toObject());
if (Utils::HostOsInfo::isMacHost()) {
m_sdkToolsUrl = sdkToolsObj["mac"].toString();
m_sdkToolsSha256 = QByteArray::fromHex(sdkToolsObj["mac_sha256"].toString().toUtf8());
} else if (Utils::HostOsInfo::isWindowsHost()) {
m_sdkToolsUrl = sdkToolsObj["windows"].toString();
m_sdkToolsSha256 = QByteArray::fromHex(sdkToolsObj["windows_sha256"].toString().toUtf8());
} else {
m_sdkToolsUrl = sdkToolsObj["linux"].toString();
m_sdkToolsSha256 = QByteArray::fromHex(sdkToolsObj["linux_sha256"].toString().toUtf8());
}
}
// Parse common essential packages
QJsonArray commonEssentials = commonObject[SdkEssentialPkgsKey].toArray();
for (const QJsonValueRef &pkg : commonEssentials)
m_commonEssentialPkgs.append(pkg.toString());
}
auto fillQtVersionsRange = [](const QString &shortVersion) {
QList<QtVersionNumber> versions;
QRegularExpression re("([0-9]\\.[0-9]*\\.)\\[([0-9])\\-([0-9])\\]");
QRegularExpressionMatch match = re.match(shortVersion);
if (match.hasMatch() && match.lastCapturedIndex() == 3)
for (int i = match.captured(2).toInt(); i <= match.captured(3).toInt(); ++i)
versions.append(QtVersionNumber(match.captured(1) + QString::number(i)));
else
versions.append(QtVersionNumber(shortVersion));
return versions;
};
if (jsonObject.contains(SpecificQtVersionsKey) && jsonObject[SpecificQtVersionsKey].isArray()) {
QJsonArray versionsArray = jsonObject[SpecificQtVersionsKey].toArray();
for (const QJsonValueRef &item : versionsArray) {
QJsonObject itemObj = item.toObject();
SdkForQtVersions specificVersion;
specificVersion.ndkPath = itemObj[NdkPathKey].toString();
for (const QJsonValueRef &pkg : itemObj[SdkEssentialPkgsKey].toArray())
specificVersion.essentialPackages.append(pkg.toString());
for (const QJsonValueRef &pkg : itemObj[VersionsKey].toArray())
specificVersion.versions.append(fillQtVersionsRange(pkg.toString()));
if (itemObj[VersionsKey].toArray().first().toString() == DefaultVersionKey)
m_defaultSdkDepends = specificVersion;
else
m_specificQtVersions.append(specificVersion);
}
}
}
QStringList AndroidConfig::apiLevelNamesFor(const SdkPlatformList &platforms)
{
return Utils::transform(platforms, AndroidConfig::apiLevelNameFor);
@@ -709,6 +793,7 @@ QVersionNumber AndroidConfig::sdkToolsVersion() const
QVersionNumber AndroidConfig::buildToolsVersion() const
{
//TODO: return version according to qt version
QVersionNumber maxVersion;
QDir buildToolsDir(m_sdkLocation.pathAppended("build-tools").toString());
for (const QFileInfo &file: buildToolsDir.entryList(QDir::Dirs|QDir::NoDotAndDotDot))
@@ -732,6 +817,11 @@ FilePath AndroidConfig::ndkLocation() const
return m_ndkLocation;
}
FilePath AndroidConfig::defaultNdkLocation() const
{
return sdkLocation().pathAppended(m_defaultSdkDepends.ndkPath);
}
static inline QString gdbServerArch(const QString &androidAbi)
{
if (androidAbi == "arm64-v8a") {
@@ -808,6 +898,57 @@ void AndroidConfig::setNdkLocation(const FilePath &ndkLocation)
m_NdkInformationUpToDate = false;
}
QStringList AndroidConfig::allEssentials() const
{
QList<BaseQtVersion *> installedVersions = QtVersionManager::versions(
[](const BaseQtVersion *v) {
return v->targetDeviceTypes().contains(Android::Constants::ANDROID_DEVICE_TYPE);
});
QStringList allPackages(defaultEssentials());
for (const BaseQtVersion *version : installedVersions)
allPackages.append(essentialsFromQtVersion(*version));
allPackages.removeDuplicates();
return allPackages;
}
QStringList AndroidConfig::essentialsFromQtVersion(const BaseQtVersion &version) const
{
QtVersionNumber qtVersion = version.qtVersion();
for (const SdkForQtVersions &item : m_specificQtVersions)
if (item.containsVersion(qtVersion))
return item.essentialPackages;
return m_defaultSdkDepends.essentialPackages;
}
QString AndroidConfig::ndkPathFromQtVersion(const BaseQtVersion &version) const
{
QtVersionNumber qtVersion = version.qtVersion();
for (const SdkForQtVersions &item : m_specificQtVersions)
if (item.containsVersion(qtVersion))
return item.ndkPath;
return m_defaultSdkDepends.ndkPath;
}
QStringList AndroidConfig::defaultEssentials() const
{
return m_defaultSdkDepends.essentialPackages + m_commonEssentialPkgs;
}
void AndroidConfig::updateDependenciesConfig()
{
parseDependenciesJson();
}
bool SdkForQtVersions::containsVersion(const QtVersionNumber &qtVersion) const
{
return versions.contains(qtVersion)
|| versions.contains(QtVersionNumber(qtVersion.majorVersion, qtVersion.minorVersion));
}
FilePath AndroidConfig::openJDKLocation() const
{
return m_openJDKLocation;

View File

@@ -28,6 +28,7 @@
#include "android_global.h"
#include "androidsdkpackage.h"
#include <projectexplorer/toolchain.h>
#include <qtsupport/qtversionmanager.h>
#include <QObject>
#include <QProcessEnvironment>
@@ -95,6 +96,16 @@ public:
bool overwrite = false;
};
struct SdkForQtVersions
{
QList<QtSupport::QtVersionNumber> versions;
QStringList essentialPackages;
QString ndkPath;
public:
bool containsVersion(const QtSupport::QtVersionNumber &qtVersion) const;
};
class ANDROID_EXPORT AndroidConfig
{
public:
@@ -112,10 +123,20 @@ public:
void setSdkManagerToolArgs(const QStringList &args);
Utils::FilePath ndkLocation() const;
Utils::FilePath defaultNdkLocation() const;
Utils::FilePath gdbServer(const QString &androidAbi) const;
QVersionNumber ndkVersion() const;
void setNdkLocation(const Utils::FilePath &ndkLocation);
QUrl sdkToolsUrl() const { return m_sdkToolsUrl; };
QByteArray getSdkToolsSha256() const { return m_sdkToolsSha256; };
QString ndkPathFromQtVersion(const QtSupport::BaseQtVersion &version) const;
QStringList defaultEssentials() const;
QStringList essentialsFromQtVersion(const QtSupport::BaseQtVersion &version) const;
QStringList allEssentials() const;
void updateDependenciesConfig();
Utils::FilePath openJDKLocation() const;
void setOpenJDKLocation(const Utils::FilePath &openJDKLocation);
@@ -162,6 +183,9 @@ public:
bool useNativeUiTools() const;
bool sdkFullyConfigured() const { return m_sdkFullyConfigured; };
void setSdkFullyConfigured(bool allEssentialsInstalled) { m_sdkFullyConfigured = allEssentialsInstalled; };
private:
static QString getDeviceProperty(const Utils::FilePath &adbToolPath,
const QString &device, const QString &property);
@@ -176,6 +200,7 @@ private:
static QString getAvdName(const QString &serialnumber);
void updateNdkInformation() const;
void parseDependenciesJson();
Utils::FilePath m_sdkLocation;
QStringList m_sdkManagerToolArgs;
@@ -184,6 +209,12 @@ private:
Utils::FilePath m_keystoreLocation;
unsigned m_partitionSize = 1024;
bool m_automaticKitCreation = true;
QUrl m_sdkToolsUrl;
QByteArray m_sdkToolsSha256;
QStringList m_commonEssentialPkgs;
SdkForQtVersions m_defaultSdkDepends;
QList<SdkForQtVersions> m_specificQtVersions;
bool m_sdkFullyConfigured = false;
//caches
mutable bool m_NdkInformationUpToDate = false;

View File

@@ -46,6 +46,10 @@
# include "androidqbspropertyprovider.h"
#endif
#include <coreplugin/icore.h>
#include <coreplugin/infobar.h>
#include <utils/checkablemessagebox.h>
#include <projectexplorer/devicesupport/devicemanager.h>
#include <projectexplorer/buildconfiguration.h>
#include <projectexplorer/deployconfiguration.h>
@@ -60,6 +64,8 @@
using namespace ProjectExplorer;
using namespace ProjectExplorer::Constants;
const char kSetupAndroidSetting[] = "ConfigureAndroid";
namespace Android {
namespace Internal {
@@ -153,6 +159,10 @@ bool AndroidPlugin::initialize(const QStringList &arguments, QString *errorMessa
d = new AndroidPluginPrivate;
if (!AndroidConfigurations::currentConfig().sdkFullyConfigured()) {
connect(Core::ICore::instance(), &Core::ICore::coreOpened, this,
&AndroidPlugin::askUserAboutAndroidSetup, Qt::QueuedConnection);
}
connect(KitManager::instance(), &KitManager::kitsLoaded,
this, &AndroidPlugin::kitsRestored);
@@ -168,5 +178,25 @@ void AndroidPlugin::kitsRestored()
this, &AndroidPlugin::kitsRestored);
}
void AndroidPlugin::askUserAboutAndroidSetup()
{
if (!Utils::CheckableMessageBox::shouldAskAgain(Core::ICore::settings(), kSetupAndroidSetting)
|| !Core::ICore::infoBar()->canInfoBeAdded(kSetupAndroidSetting))
return;
Core::InfoBarEntry info(
kSetupAndroidSetting,
tr("Would you like to configure Android options? This will ensure "
"Android kits can be usable and all essential packages are installed. "
"To do it later, select Options > Devices > Android."),
Core::InfoBarEntry::GlobalSuppression::Enabled);
info.setCustomButtonInfo(tr("Configure Android"), [this] {
Core::ICore::infoBar()->removeInfo(kSetupAndroidSetting);
Core::ICore::infoBar()->globallySuppressInfo(kSetupAndroidSetting);
QTimer::singleShot(0, this, [this]() { d->potentialKit.executeFromMenu(); });
});
Core::ICore::infoBar()->addInfo(info);
}
} // namespace Internal
} // namespace Android

View File

@@ -40,6 +40,7 @@ class AndroidPlugin final : public ExtensionSystem::IPlugin
bool initialize(const QStringList &arguments, QString *errorMessage) final;
void kitsRestored();
void askUserAboutAndroidSetup();
class AndroidPluginPrivate *d = nullptr;
};

View File

@@ -0,0 +1,231 @@
/****************************************************************************
**
** Copyright (C) 2020 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 "androidsdkdownloader.h"
#include <QDir>
#include <QDirIterator>
#include <QLoggingCategory>
#include <QProcess>
#include <QCryptographicHash>
#include <QStandardPaths>
namespace {
Q_LOGGING_CATEGORY(sdkDownloaderLog, "qtc.android.sdkDownloader", QtWarningMsg)
}
namespace Android {
namespace Internal {
/**
* @class SdkDownloader
* @brief Download Android SDK tools package from within Qt Creator.
*/
AndroidSdkDownloader::AndroidSdkDownloader(const QUrl &sdkUrl, const QByteArray &sha256) :
m_sdkUrl(sdkUrl), m_sha256(sha256)
{
connect(&m_manager, &QNetworkAccessManager::finished, this, &AndroidSdkDownloader::downloadFinished);
}
#if QT_CONFIG(ssl)
void AndroidSdkDownloader::sslErrors(const QList<QSslError> &sslErrors)
{
for (const QSslError &error : sslErrors)
qCDebug(sdkDownloaderLog, "SSL error: %s\n", qPrintable(error.errorString()));
cancelWithError(tr("Encountered SSL errors, download is aborted."));
}
#endif
static void setSdkFilesExecPermission( const QString &sdkExtractPath)
{
QDirIterator it(sdkExtractPath + "/tools", QStringList() << "*",
QDir::Files, QDirIterator::Subdirectories);
while (it.hasNext()) {
QFile file(it.next());
if (!file.fileName().contains('.')) {
QFlags<QFileDevice::Permission> currentPermissions
= file.permissions();
file.setPermissions(currentPermissions | QFileDevice::ExeOwner);
}
}
}
void AndroidSdkDownloader::downloadAndExtractSdk(const QString &jdkPath, const QString &sdkExtractPath)
{
if (m_sdkUrl.isEmpty()) {
logError(tr("The SDK Tools download URL is empty."));
return;
}
QNetworkRequest request(m_sdkUrl);
m_reply = m_manager.get(request);
#if QT_CONFIG(ssl)
connect(m_reply, &QNetworkReply::sslErrors, this, &AndroidSdkDownloader::sslErrors);
#endif
m_progressDialog = new QProgressDialog(tr("Downloading SDK Tools package..."), tr("Cancel"), 0, 100);
m_progressDialog->setWindowModality(Qt::WindowModal);
m_progressDialog->setWindowTitle(dialogTitle());
m_progressDialog->setFixedSize(m_progressDialog->sizeHint());
connect(m_reply, &QNetworkReply::downloadProgress, this, [this](qint64 received, qint64 max) {
m_progressDialog->setRange(0, max);
m_progressDialog->setValue(received);
});
connect(m_progressDialog, &QProgressDialog::canceled, this, &AndroidSdkDownloader::cancel);
connect(this, &AndroidSdkDownloader::sdkPackageWriteFinished, this, [this, jdkPath, sdkExtractPath]() {
if (extractSdk(jdkPath, sdkExtractPath)) {
setSdkFilesExecPermission(sdkExtractPath);
emit sdkExtracted();
}
});
}
bool AndroidSdkDownloader::extractSdk(const QString &jdkPath, const QString &sdkExtractPath)
{
if (!QDir(sdkExtractPath).exists()) {
if (!QDir().mkdir(sdkExtractPath)) {
logError(QString(tr("Could not create the SDK folder %1.")).arg(sdkExtractPath));
return false;
}
}
QProcess *jarExtractProc = new QProcess();
jarExtractProc->setWorkingDirectory(sdkExtractPath);
QString jarCmdPath(jdkPath + "/bin/jar");
jarExtractProc->start(jarCmdPath, {"xf", m_sdkFilename});
jarExtractProc->waitForFinished();
jarExtractProc->close();
return jarExtractProc->exitCode() ? false : true;
}
bool AndroidSdkDownloader::verifyFileIntegrity()
{
QFile f(m_sdkFilename);
if (f.open(QFile::ReadOnly)) {
QCryptographicHash hash(QCryptographicHash::Sha256);
if (hash.addData(&f)) {
return hash.result() == m_sha256;
}
}
return false;
}
QString AndroidSdkDownloader::dialogTitle()
{
return tr("Download SDK Tools");
}
void AndroidSdkDownloader::cancel()
{
if (m_reply) {
m_reply->abort();
m_reply->deleteLater();
}
if (m_progressDialog)
m_progressDialog->hide();
}
void AndroidSdkDownloader::cancelWithError(const QString &error)
{
cancel();
logError(error);
}
void AndroidSdkDownloader::logError(const QString &error)
{
qCDebug(sdkDownloaderLog, "%s", error.toUtf8().data());
emit sdkDownloaderError(error);
}
QString AndroidSdkDownloader::getSaveFilename(const QUrl &url)
{
QString path = url.path();
QString basename = QFileInfo(path).fileName();
if (basename.isEmpty())
basename = "sdk-tools.zip";
if (QFile::exists(basename)) {
int i = 0;
basename += '.';
while (QFile::exists(basename + QString::number(i)))
++i;
basename += QString::number(i);
}
QString fullPath = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)
+ QDir::separator() + basename;
return fullPath;
}
bool AndroidSdkDownloader::saveToDisk(const QString &filename, QIODevice *data)
{
QFile file(filename);
if (!file.open(QIODevice::WriteOnly)) {
logError(QString(tr("Could not open %1 for writing: %2.")).arg(filename, file.errorString()));
return false;
}
file.write(data->readAll());
file.close();
return true;
}
bool AndroidSdkDownloader::isHttpRedirect(QNetworkReply *reply)
{
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
return statusCode == 301 || statusCode == 302 || statusCode == 303 || statusCode == 305
|| statusCode == 307 || statusCode == 308;
}
void AndroidSdkDownloader::downloadFinished(QNetworkReply *reply)
{
QUrl url = reply->url();
if (reply->error()) {
cancelWithError(QString(tr("Downloading Android SDK Tools from URL %1 has failed: %2."))
.arg(url.toString(), reply->errorString()));
} else {
if (isHttpRedirect(reply)) {
cancelWithError(QString(tr("Download from %1 was redirected.")).arg(url.toString()));
} else {
m_sdkFilename = getSaveFilename(url);
if (saveToDisk(m_sdkFilename, reply) && verifyFileIntegrity())
emit sdkPackageWriteFinished();
else
cancelWithError(
tr("Writing and verifying the integrity of the downloaded file has failed."));
}
}
reply->deleteLater();
}
} // Internal
} // Android

View File

@@ -0,0 +1,78 @@
/****************************************************************************
**
** Copyright (C) 2020 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.
**
****************************************************************************/
#ifndef ANDROIDSDKDOWNLOADER_H
#define ANDROIDSDKDOWNLOADER_H
#include <QNetworkReply>
#include <QObject>
#include <QProgressDialog>
namespace Android {
namespace Internal {
class AndroidSdkDownloader : public QObject
{
Q_OBJECT
public:
AndroidSdkDownloader(const QUrl &sdkUrl, const QByteArray &sha256);
void downloadAndExtractSdk(const QString &jdkPath, const QString &sdkExtractPath);
static QString dialogTitle();
void cancel();
signals:
void sdkPackageWriteFinished();
void sdkExtracted();
void sdkDownloaderError(const QString &error);
private:
static QString getSaveFilename(const QUrl &url);
bool saveToDisk(const QString &filename, QIODevice *data);
static bool isHttpRedirect(QNetworkReply *m_reply);
bool extractSdk(const QString &jdkPath, const QString &sdkExtractPath);
bool verifyFileIntegrity();
void cancelWithError(const QString &error);
void logError(const QString &error);
void downloadFinished(QNetworkReply *m_reply);
#if QT_CONFIG(ssl)
void sslErrors(const QList<QSslError> &errors);
#endif
QNetworkAccessManager m_manager;
QNetworkReply *m_reply;
QString m_sdkFilename;
QProgressDialog *m_progressDialog;
QUrl m_sdkUrl;
QByteArray m_sha256;
};
} // Internal
} // Android
#endif // ANDROIDSDKDOWNLOADER_H

View File

@@ -271,7 +271,8 @@ public:
SdkToolsMarker = 0x100,
PlatformToolsMarker = 0x200,
EmulatorToolsMarker = 0x400,
ExtrasMarker = 0x800,
NdkMarker = 0x800,
ExtrasMarker = 0x1000,
SectionMarkers = InstalledPackagesMarker | AvailablePackagesMarkers | AvailableUpdatesMarker
};
@@ -292,6 +293,7 @@ private:
SdkTools *parseSdkToolsPackage(const QStringList &data) const;
PlatformTools *parsePlatformToolsPackage(const QStringList &data) const;
EmulatorTools *parseEmulatorToolsPackage(const QStringList &data) const;
Ndk *parseNdkPackage(const QStringList &data) const;
ExtraTools *parseExtraToolsPackage(const QStringList &data) const;
MarkerTag parseMarkers(const QString &line);
@@ -309,6 +311,7 @@ const std::map<SdkManagerOutputParser::MarkerTag, const char *> markerTags {
{SdkManagerOutputParser::MarkerTag::SdkToolsMarker, "tools"},
{SdkManagerOutputParser::MarkerTag::PlatformToolsMarker, "platform-tools"},
{SdkManagerOutputParser::MarkerTag::EmulatorToolsMarker, "emulator"},
{SdkManagerOutputParser::MarkerTag::NdkMarker, "ndk"},
{SdkManagerOutputParser::MarkerTag::ExtrasMarker, "extras"}
};
@@ -360,6 +363,13 @@ SystemImageList AndroidSdkManager::installedSystemImages()
return result;
}
NdkList AndroidSdkManager::installedNdkPackages()
{
AndroidSdkPackageList list = m_d->filteredPackages(AndroidSdkPackage::Installed,
AndroidSdkPackage::NDKPackage);
return Utils::static_container_cast<Ndk *>(list);
}
SdkPlatform *AndroidSdkManager::latestAndroidSdkPlatform(AndroidSdkPackage::PackageState state)
{
SdkPlatform *result = nullptr;
@@ -601,6 +611,10 @@ void SdkManagerOutputParser::parsePackageData(MarkerTag packageMarker, const QSt
}
break;
case MarkerTag::NdkMarker:
createPackage(&SdkManagerOutputParser::parseNdkPackage);
break;
case MarkerTag::ExtrasMarker:
createPackage(&SdkManagerOutputParser::parseExtraToolsPackage);
break;
@@ -771,6 +785,24 @@ EmulatorTools *SdkManagerOutputParser::parseEmulatorToolsPackage(const QStringLi
return emulatorTools;
}
Ndk *SdkManagerOutputParser::parseNdkPackage(const QStringList &data) const
{
Ndk *ndk = nullptr;
GenericPackageData packageData;
if (parseAbstractData(packageData, data, 1, "NDK")) {
ndk = new Ndk(packageData.revision, data.at(0));
ndk->setDescriptionText(packageData.description);
ndk->setDisplayText(packageData.description);
ndk->setInstalledLocation(packageData.installedLocation);
if (packageData.description == "NDK")
ndk->setAsNdkBundle(true);
} else {
qCDebug(sdkManagerLog) << "NDK: Parsing failed. Minimum required data unavailable:"
<< data;
}
return ndk;
}
ExtraTools *SdkManagerOutputParser::parseExtraToolsPackage(const QStringList &data) const
{
ExtraTools *extraTools = nullptr;

View File

@@ -70,6 +70,7 @@ public:
AndroidSdkPackageList availableSdkPackages();
AndroidSdkPackageList installedSdkPackages();
SystemImageList installedSystemImages();
NdkList installedNdkPackages();
SdkPlatform *latestAndroidSdkPlatform(AndroidSdkPackage::PackageState state
= AndroidSdkPackage::Installed);

View File

@@ -273,37 +273,44 @@ bool AndroidSdkModel::setData(const QModelIndex &index, const QVariant &value, i
void AndroidSdkModel::selectMissingEssentials()
{
resetSelection();
bool selectPlatformTool = !m_config.adbToolPath().exists();
bool selectBuildTools = m_config.buildToolsVersion().isNull();
QStringList pendingPkgs(m_config.allEssentials());
auto addTool = [this](QList<const AndroidSdkPackage *>::const_iterator itr) {
m_changeState << *itr;
auto i = index(std::distance(m_tools.cbegin(), itr), 0, index(0, 0));
emit dataChanged(i, i, {Qt::CheckStateRole});
if ((*itr)->installedLocation().isEmpty()) {
m_changeState << *itr;
auto i = index(std::distance(m_tools.cbegin(), itr), 0, index(0, 0));
emit dataChanged(i, i, {Qt::CheckStateRole});
}
};
for (auto tool = m_tools.cbegin(); tool != m_tools.cend(); ++tool) {
if (selectPlatformTool && (*tool)->type() == AndroidSdkPackage::PlatformToolsPackage) {
// Select Platform tools
addTool(tool);
selectPlatformTool = false;
}
if (selectBuildTools && (*tool)->type() == AndroidSdkPackage::BuildToolsPackage) {
// Select build tools
addTool(tool);
selectBuildTools = false;
}
if (!selectPlatformTool && !selectBuildTools)
if (!pendingPkgs.contains((*tool)->sdkStylePath()))
continue;
if ((*tool)->type() == AndroidSdkPackage::PlatformToolsPackage)
addTool(tool); // Select Platform tools
else if ((*tool)->type() == AndroidSdkPackage::BuildToolsPackage)
addTool(tool); // Select build tools
else if ((*tool)->type() == AndroidSdkPackage::NDKPackage)
addTool(tool); // Select NDK Bundle
pendingPkgs.removeOne((*tool)->sdkStylePath());
if (pendingPkgs.isEmpty())
break;
}
// Select SDK platform
if (m_sdkManager->installedSdkPlatforms().isEmpty() && !m_sdkPlatforms.isEmpty()) {
auto i = index(0, 0, index(1,0));
m_changeState << m_sdkPlatforms.at(0);
emit dataChanged(i , i, {Qt::CheckStateRole});
for (const SdkPlatform *platform : m_sdkPlatforms) {
if (pendingPkgs.contains(platform->sdkStylePath()) &&
platform->installedLocation().isEmpty()) {
auto i = index(0, 0, index(1, 0));
m_changeState << platform;
emit dataChanged(i, i, {Qt::CheckStateRole});
pendingPkgs.removeOne(platform->sdkStylePath());
}
if (pendingPkgs.isEmpty())
break;
}
}
QList<const AndroidSdkPackage *> AndroidSdkModel::userSelection() const
{
return Utils::toList(m_changeState);

View File

@@ -294,4 +294,29 @@ AndroidSdkPackage::PackageType ExtraTools::type() const
return AndroidSdkPackage::ExtraToolsPackage;
}
Ndk::Ndk(QVersionNumber revision, QString sdkStylePathStr, QObject *parent) :
AndroidSdkPackage(revision, sdkStylePathStr, parent)
{
}
bool Ndk::isValid() const
{
return installedLocation().exists();
}
AndroidSdkPackage::PackageType Ndk::type() const
{
return AndroidSdkPackage::NDKPackage;
}
bool Ndk::isNdkBundle() const
{
return m_isBundle;
}
void Ndk::setAsNdkBundle(const bool isBundle)
{
m_isBundle = isBundle;
}
} // namespace Android

View File

@@ -53,9 +53,11 @@ public:
SdkPlatformPackage = 1 << 4,
SystemImagePackage = 1 << 5,
EmulatorToolsPackage = 1 << 6,
ExtraToolsPackage = 1 << 7,
NDKPackage = 1 << 7,
ExtraToolsPackage = 1 << 8,
AnyValidType = SdkToolsPackage | BuildToolsPackage | PlatformToolsPackage |
SdkPlatformPackage | SystemImagePackage | EmulatorToolsPackage | ExtraToolsPackage
SdkPlatformPackage | SystemImagePackage | EmulatorToolsPackage | NDKPackage |
ExtraToolsPackage
};
enum PackageState {
@@ -196,6 +198,23 @@ public:
PackageType type() const override;
};
class Ndk : public AndroidSdkPackage
{
public:
Ndk(QVersionNumber revision, QString sdkStylePathStr, QObject *parent = nullptr);
// AndroidSdkPackage Overrides
bool isValid() const override;
PackageType type() const override;
bool isNdkBundle() const;
void setAsNdkBundle(const bool isBundle);
private:
bool m_isBundle = false;
};
using NdkList = QList<Ndk *>;
class ExtraTools : public AndroidSdkPackage
{
public:

View File

@@ -33,6 +33,7 @@
#include "androidavdmanager.h"
#include "androidsdkmanager.h"
#include "avddialog.h"
#include "androidsdkdownloader.h"
#include "androidsdkmanagerwidget.h"
#include <utils/qtcassert.h>
@@ -51,6 +52,7 @@
#include <QAbstractTableModel>
#include <QDesktopServices>
#include <QDir>
#include <QFutureWatcher>
#include <QList>
#include <QMessageBox>
@@ -104,6 +106,7 @@ private:
void validateJdk();
Utils::FilePath findJdkInCommonPaths();
void validateNdk();
void updateNdkList();
void onSdkPathChanged();
void validateSdk();
void openSDKDownloadUrl();
@@ -125,6 +128,11 @@ private:
void enableAvdControls();
void disableAvdControls();
void downloadSdk();
bool allEssentialsInstalled();
bool sdkToolsOk() const;
Utils::FilePath getDefaultSdkPath();
Ui_AndroidSettingsWidget *m_ui;
AndroidSdkManagerWidget *m_sdkManagerWidget = nullptr;
AndroidConfig m_androidConfig;
@@ -150,6 +158,7 @@ enum AndroidValidation {
BuildToolsInstalledRow,
SdkManagerSuccessfulRow,
PlatformSdkInstalledRow,
AllEssentialsInstalledRow,
NdkPathExistsRow,
NdkDirStructureRow,
NdkinstallDirOkRow
@@ -302,6 +311,37 @@ int AvdModel::columnCount(const QModelIndex &/*parent*/) const
return 6;
}
Utils::FilePath AndroidSettingsWidget::getDefaultSdkPath()
{
QString sdkFromEnvVar = QString::fromLocal8Bit(getenv("ANDROID_SDK_ROOT"));
if (!sdkFromEnvVar.isEmpty())
return Utils::FilePath::fromString(sdkFromEnvVar);
// Set default path of SDK as used by Android Studio
if (Utils::HostOsInfo::isMacHost()) {
return Utils::FilePath::fromString(
QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)
+ "/../Android/sdk");
}
if (Utils::HostOsInfo::isWindowsHost()) {
return Utils::FilePath::fromString(
QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/Android/sdk");
}
return Utils::FilePath::fromString(
QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/Android/Sdk");
}
void AndroidSettingsWidget::updateNdkList()
{
m_ui->ndkListComboBox->clear();
QString currentNdk = m_androidConfig.ndkLocation().toString();
for (const Ndk *ndk : m_sdkManager->installedNdkPackages())
m_ui->ndkListComboBox->addItem(ndk->installedLocation().toString());
m_ui->ndkListComboBox->setCurrentText(currentNdk);
}
AndroidSettingsWidget::AndroidSettingsWidget()
: m_ui(new Ui_AndroidSettingsWidget),
m_androidConfig(AndroidConfigurations::currentConfig()),
@@ -339,28 +379,36 @@ AndroidSettingsWidget::AndroidSettingsWidget()
androidValidationPoints[PlatformToolsInstalledRow] = tr("Platform tools installed.");
androidValidationPoints[SdkManagerSuccessfulRow] = tr(
"SDK manager runs (requires exactly Java 1.8).");
androidValidationPoints[AllEssentialsInstalledRow] = tr(
"All essential packages installed for all installed Qt versions.");
androidValidationPoints[BuildToolsInstalledRow] = tr("Build tools installed.");
androidValidationPoints[PlatformSdkInstalledRow] = tr("Platform SDK installed.");
androidValidationPoints[NdkPathExistsRow] = tr("Android NDK path exists.");
androidValidationPoints[NdkDirStructureRow] = tr("Android NDK directory structure is correct.");
androidValidationPoints[NdkinstallDirOkRow] = tr("Android NDK installed into a path without "
androidValidationPoints[NdkPathExistsRow] = tr("Default Android NDK path exists.");
androidValidationPoints[NdkDirStructureRow] = tr("Default Android NDK directory structure is correct.");
androidValidationPoints[NdkinstallDirOkRow] = tr("Default Android NDK installed into a path without "
"spaces.");
auto androidSummary = new SummaryWidget(androidValidationPoints, tr("Android settings are OK."),
tr("Android settings have errors."),
m_ui->androidDetailsWidget);
m_ui->androidDetailsWidget->setWidget(androidSummary);
m_ui->SDKLocationPathChooser->setFileName(m_androidConfig.sdkLocation());
m_ui->SDKLocationPathChooser->setPromptDialogTitle(tr("Select Android SDK folder"));
m_ui->NDKLocationPathChooser->setFileName(m_androidConfig.ndkLocation());
m_ui->NDKLocationPathChooser->setPromptDialogTitle(tr("Select Android NDK folder"));
connect(m_ui->OpenJDKLocationPathChooser, &Utils::PathChooser::rawPathChanged,
this, &AndroidSettingsWidget::validateJdk);
Utils::FilePath currentJdkPath = m_androidConfig.openJDKLocation();
if (currentJdkPath.isEmpty())
currentJdkPath = findJdkInCommonPaths();
m_ui->OpenJDKLocationPathChooser->setFileName(currentJdkPath);
m_ui->OpenJDKLocationPathChooser->setPromptDialogTitle(tr("Select JDK Path"));
connect(m_ui->SDKLocationPathChooser, &Utils::PathChooser::rawPathChanged,
this, &AndroidSettingsWidget::onSdkPathChanged);
Utils::FilePath currentSDKPath = m_androidConfig.sdkLocation();
if (currentSDKPath.isEmpty())
currentSDKPath = getDefaultSdkPath();
m_ui->SDKLocationPathChooser->setFileName(currentSDKPath);
m_ui->SDKLocationPathChooser->setPromptDialogTitle(tr("Select Android SDK folder"));
m_ui->DataPartitionSizeSpinBox->setValue(m_androidConfig.partitionSize());
m_ui->CreateKitCheckBox->setChecked(m_androidConfig.automaticKitCreation());
m_ui->AVDTableView->setModel(&m_AVDModel);
@@ -373,19 +421,15 @@ AndroidSettingsWidget::AndroidSettingsWidget()
m_ui->downloadSDKToolButton->setIcon(downloadIcon);
m_ui->downloadNDKToolButton->setIcon(downloadIcon);
m_ui->downloadOpenJDKToolButton->setIcon(downloadIcon);
m_ui->sdkToolsAutoDownloadButton->setIcon(Utils::Icons::RELOAD.icon());
connect(m_ui->ndkListComboBox, QOverload<const QString &>::of(&QComboBox::currentIndexChanged),
[this](const QString) { validateNdk(); });
connect(&m_virtualDevicesWatcher, &QFutureWatcherBase::finished,
this, &AndroidSettingsWidget::updateAvds);
connect(m_ui->AVDRefreshPushButton, &QAbstractButton::clicked,
this, &AndroidSettingsWidget::startUpdateAvd);
connect(&m_futureWatcher, &QFutureWatcherBase::finished,
this, &AndroidSettingsWidget::avdAdded);
connect(m_ui->NDKLocationPathChooser, &Utils::PathChooser::rawPathChanged,
this, &AndroidSettingsWidget::validateNdk);
connect(m_ui->SDKLocationPathChooser, &Utils::PathChooser::rawPathChanged,
this, &AndroidSettingsWidget::onSdkPathChanged);
connect(m_ui->OpenJDKLocationPathChooser, &Utils::PathChooser::rawPathChanged,
this, &AndroidSettingsWidget::validateJdk);
connect(&m_futureWatcher, &QFutureWatcherBase::finished, this, &AndroidSettingsWidget::avdAdded);
connect(m_ui->AVDAddPushButton, &QAbstractButton::clicked,
this, &AndroidSettingsWidget::addAVD);
connect(m_ui->AVDRemovePushButton, &QAbstractButton::clicked,
@@ -402,20 +446,38 @@ AndroidSettingsWidget::AndroidSettingsWidget()
this, &AndroidSettingsWidget::manageAVD);
connect(m_ui->CreateKitCheckBox, &QAbstractButton::toggled,
this, &AndroidSettingsWidget::createKitToggled);
connect(m_ui->downloadSDKToolButton, &QAbstractButton::clicked,
this, &AndroidSettingsWidget::openSDKDownloadUrl);
connect(m_ui->downloadNDKToolButton, &QAbstractButton::clicked,
this, &AndroidSettingsWidget::openNDKDownloadUrl);
connect(m_ui->downloadSDKToolButton, &QAbstractButton::clicked,
this, &AndroidSettingsWidget::openSDKDownloadUrl);
connect(m_ui->downloadOpenJDKToolButton, &QAbstractButton::clicked,
this, &AndroidSettingsWidget::openOpenJDKDownloadUrl);
// Validate SDK again after any change in SDK packages.
connect(m_sdkManager.get(), &AndroidSdkManager::packageReloadFinished,
this, &AndroidSettingsWidget::validateSdk);
validateJdk();
validateNdk();
// Reloading SDK packages is still synchronous. Use zero timer to let settings dialog open
connect(m_sdkManager.get(),
&AndroidSdkManager::packageReloadFinished,
this,
&AndroidSettingsWidget::validateSdk);
connect(m_ui->sdkToolsAutoDownloadButton, &QAbstractButton::clicked, this, [this]() {
if (sdkToolsOk()) {
QMessageBox::warning(this, AndroidSdkDownloader::dialogTitle(),
tr("The selected path already has a valid SDK Tools package."));
validateSdk();
return;
}
downloadSdk();
});
auto startOneShot = QSharedPointer<QMetaObject::Connection>::create();
*startOneShot = connect(m_sdkManager.get(),
&AndroidSdkManager::packageReloadFinished, [this, startOneShot] {
QObject::disconnect(*startOneShot);
if (!sdkToolsOk())
downloadSdk();
});
// Reloading SDK packages (force) is still synchronous. Use zero timer to let settings dialog open
// first.
QTimer::singleShot(0, std::bind(&AndroidSdkManager::reloadPackages, m_sdkManager.get(), false));
QTimer::singleShot(0, std::bind(&AndroidSdkManager::reloadPackages, m_sdkManager.get(), true));
startUpdateAvd();
}
@@ -521,11 +583,11 @@ Utils::FilePath AndroidSettingsWidget::findJdkInCommonPaths()
void AndroidSettingsWidget::validateNdk()
{
auto ndkPath = Utils::FilePath::fromUserInput(m_ui->NDKLocationPathChooser->rawPath());
auto ndkPath = Utils::FilePath::fromString(m_ui->ndkListComboBox->currentText());
m_androidConfig.setNdkLocation(ndkPath);
auto summaryWidget = static_cast<SummaryWidget *>(m_ui->androidDetailsWidget->widget());
summaryWidget->setPointValid(NdkPathExistsRow, m_androidConfig.ndkLocation().exists());
summaryWidget->setPointValid(NdkPathExistsRow, ndkPath.exists());
const Utils::FilePath ndkPlatformsDir = ndkPath.pathAppended("platforms");
const Utils::FilePath ndkToolChainsDir = ndkPath.pathAppended("toolchains");
@@ -550,6 +612,9 @@ void AndroidSettingsWidget::onSdkPathChanged()
void AndroidSettingsWidget::validateSdk()
{
auto sdkPath = Utils::FilePath::fromString(m_ui->SDKLocationPathChooser->rawPath());
m_androidConfig.setSdkLocation(sdkPath);
auto summaryWidget = static_cast<SummaryWidget *>(m_ui->androidDetailsWidget->widget());
summaryWidget->setPointValid(SdkPathExistsRow, m_androidConfig.sdkLocation().exists());
summaryWidget->setPointValid(SdkPathWritableRow, m_androidConfig.sdkLocation().isWritablePath());
@@ -559,19 +624,21 @@ void AndroidSettingsWidget::validateSdk()
m_androidConfig.adbToolPath().exists());
summaryWidget->setPointValid(BuildToolsInstalledRow,
!m_androidConfig.buildToolsVersion().isNull());
summaryWidget->setPointValid(SdkManagerSuccessfulRow, m_sdkManager->packageListingSuccessful());
// installedSdkPlatforms should not trigger a package reload as validate SDK is only called
// after AndroidSdkManager::packageReloadFinished.
summaryWidget->setPointValid(PlatformSdkInstalledRow,
!m_sdkManager->installedSdkPlatforms().isEmpty());
summaryWidget->setPointValid(AllEssentialsInstalledRow, allEssentialsInstalled());
updateUI();
bool sdkToolsOk = summaryWidget->rowsOk(
{SdkPathExistsRow, SdkPathWritableRow, SdkToolsInstalledRow, SdkManagerSuccessfulRow});
bool componentsOk = summaryWidget->rowsOk({PlatformToolsInstalledRow,
BuildToolsInstalledRow,
PlatformSdkInstalledRow});
PlatformSdkInstalledRow,
AllEssentialsInstalledRow});
m_androidConfig.setSdkFullyConfigured(sdkToolsOk && componentsOk);
if (sdkToolsOk && !componentsOk && !m_androidConfig.useNativeUiTools()) {
// Ask user to install essential SDK components. Works only for sdk tools version >= 26.0.0
QString message = tr("Android SDK installation is missing necessary packages. Do you "
@@ -583,6 +650,9 @@ void AndroidSettingsWidget::validateSdk()
m_sdkManagerWidget->installEssentials();
}
}
updateNdkList();
validateNdk();
}
void AndroidSettingsWidget::openSDKDownloadUrl()
@@ -675,7 +745,7 @@ void AndroidSettingsWidget::updateUI()
m_ui->sdkManagerTab->setEnabled(sdkToolsOk);
m_sdkManagerWidget->setSdkManagerControlsEnabled(!m_androidConfig.useNativeUiTools());
auto infoText = tr("(SDK Version: %1, NDK Version: %2)")
auto infoText = tr("(SDK Version: %1, NDK Bundle Version: %2)")
.arg(m_androidConfig.sdkToolsVersion().toString())
.arg(m_androidConfig.ndkVersion().toString());
androidSummaryWidget->setInfoText(androidSetupOk ? infoText : "");
@@ -699,6 +769,57 @@ void AndroidSettingsWidget::manageAVD()
}
}
void AndroidSettingsWidget::downloadSdk()
{
QString message(tr("Android SDK Tools package is not installed. Do you want to download it?\n"
"The final location: ")
+ QDir::toNativeSeparators(m_ui->SDKLocationPathChooser->rawPath()));
auto userInput = QMessageBox::information(this, AndroidSdkDownloader::dialogTitle(),
message, QMessageBox::Yes | QMessageBox::No);
if (userInput == QMessageBox::Yes) {
auto javaSummaryWidget = static_cast<SummaryWidget *>(m_ui->javaDetailsWidget->widget());
if (javaSummaryWidget->allRowsOk()) {
auto javaPath = Utils::FilePath::fromUserInput(m_ui->OpenJDKLocationPathChooser->rawPath());
AndroidSdkDownloader *sdkDownloader = new AndroidSdkDownloader(
m_androidConfig.sdkToolsUrl(),
m_androidConfig.getSdkToolsSha256());
sdkDownloader->downloadAndExtractSdk(javaPath.toString(),
m_ui->SDKLocationPathChooser->path());
connect(sdkDownloader, &AndroidSdkDownloader::sdkExtracted, this, [this]() {
m_sdkManager->reloadPackages(true);
apply();
});
auto showErrorDialog = [this](const QString &error) {
QMessageBox::warning(this, AndroidSdkDownloader::dialogTitle(), error);
};
connect(sdkDownloader, &AndroidSdkDownloader::sdkDownloaderError, this, showErrorDialog);
}
}
}
bool AndroidSettingsWidget::allEssentialsInstalled()
{
QStringList essentialPkgs(m_androidConfig.allEssentials());
for (const AndroidSdkPackage *pkg : m_sdkManager->installedSdkPackages()) {
if (essentialPkgs.contains(pkg->sdkStylePath()))
essentialPkgs.removeOne(pkg->sdkStylePath());
if (essentialPkgs.isEmpty())
break;
}
return essentialPkgs.isEmpty() ? true : false;
}
bool AndroidSettingsWidget::sdkToolsOk() const
{
bool exists = m_androidConfig.sdkLocation().exists();
bool writable = m_androidConfig.sdkLocation().isWritablePath();
bool sdkToolsExist = !m_androidConfig.sdkToolsVersion().isNull();
return exists && writable && sdkToolsExist;
}
// AndroidSettingsPage
AndroidSettingsPage::AndroidSettingsPage()

View File

@@ -90,42 +90,6 @@
<string>Android Settings</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="1">
<widget class="Utils::PathChooser" name="NDKLocationPathChooser" native="true"/>
</item>
<item row="0" column="1">
<widget class="Utils::PathChooser" name="SDKLocationPathChooser" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QToolButton" name="downloadNDKToolButton">
<property name="toolTip">
<string>Download Android NDK</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="NDKLocationLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Android NDK location:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="SDKLocationLabel">
<property name="sizePolicy">
@@ -142,16 +106,53 @@
</property>
</widget>
</item>
<item row="0" column="2">
<item row="0" column="4">
<widget class="QToolButton" name="downloadSDKToolButton">
<property name="toolTip">
<string>Download Android SDK</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="3">
<item row="1" column="0">
<widget class="QLabel" name="ndkComboBoxLabel">
<property name="text">
<string>Android NDK list:</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QToolButton" name="sdkToolsAutoDownloadButton">
<property name="toolTip">
<string>Automatically download Android SDK Tools to selected location.</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="2" column="0" colspan="5">
<widget class="Utils::DetailsWidget" name="androidDetailsWidget" native="true"/>
</item>
<item row="1" column="4">
<widget class="QToolButton" name="downloadNDKToolButton">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="Utils::PathChooser" name="SDKLocationPathChooser" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QComboBox" name="ndkListComboBox"/>
</item>
</layout>
</widget>
</item>