diff --git a/share/qtcreator/CMakeLists.txt b/share/qtcreator/CMakeLists.txt index 63393293c1a..05f64cc2f15 100644 --- a/share/qtcreator/CMakeLists.txt +++ b/share/qtcreator/CMakeLists.txt @@ -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 diff --git a/share/qtcreator/android/sdk_definitions.json b/share/qtcreator/android/sdk_definitions.json new file mode 100644 index 00000000000..106bb3e353a --- /dev/null +++ b/share/qtcreator/android/sdk_definitions.json @@ -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" + } + ] +} diff --git a/share/qtcreator/static.pro b/share/qtcreator/static.pro index 7f39ae2faed..d3fdfdd2816 100644 --- a/share/qtcreator/static.pro +++ b/share/qtcreator/static.pro @@ -23,7 +23,8 @@ DATA_DIRS = \ modeleditor \ glsl \ cplusplus \ - indexer_preincludes + indexer_preincludes \ + android macx: DATA_DIRS += scripts for(data_dir, DATA_DIRS) { diff --git a/src/plugins/android/CMakeLists.txt b/src/plugins/android/CMakeLists.txt index 9d97455dccf..0f2eb17d7ba 100644 --- a/src/plugins/android/CMakeLists.txt +++ b/src/plugins/android/CMakeLists.txt @@ -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 diff --git a/src/plugins/android/android.pro b/src/plugins/android/android.pro index 101ed63fa6f..f3745d18c07 100644 --- a/src/plugins/android/android.pro +++ b/src/plugins/android/android.pro @@ -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 \ diff --git a/src/plugins/android/androidconfigurations.cpp b/src/plugins/android/androidconfigurations.cpp index 2826bcb8a24..d8e9b92f5f8 100644 --- a/src/plugins/android/androidconfigurations.cpp +++ b/src/plugins/android/androidconfigurations.cpp @@ -61,6 +61,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -72,6 +75,7 @@ #include #include +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 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 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; diff --git a/src/plugins/android/androidconfigurations.h b/src/plugins/android/androidconfigurations.h index 1756056b25d..93250713aa7 100644 --- a/src/plugins/android/androidconfigurations.h +++ b/src/plugins/android/androidconfigurations.h @@ -28,6 +28,7 @@ #include "android_global.h" #include "androidsdkpackage.h" #include +#include #include #include @@ -95,6 +96,16 @@ public: bool overwrite = false; }; +struct SdkForQtVersions +{ + QList 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 m_specificQtVersions; + bool m_sdkFullyConfigured = false; //caches mutable bool m_NdkInformationUpToDate = false; diff --git a/src/plugins/android/androidplugin.cpp b/src/plugins/android/androidplugin.cpp index 30e4c9d55af..93850bb4831 100644 --- a/src/plugins/android/androidplugin.cpp +++ b/src/plugins/android/androidplugin.cpp @@ -46,6 +46,10 @@ # include "androidqbspropertyprovider.h" #endif +#include +#include +#include + #include #include #include @@ -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 diff --git a/src/plugins/android/androidplugin.h b/src/plugins/android/androidplugin.h index ea2d7461c4a..45db6ed2372 100644 --- a/src/plugins/android/androidplugin.h +++ b/src/plugins/android/androidplugin.h @@ -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; }; diff --git a/src/plugins/android/androidsdkdownloader.cpp b/src/plugins/android/androidsdkdownloader.cpp new file mode 100644 index 00000000000..da3fb672b26 --- /dev/null +++ b/src/plugins/android/androidsdkdownloader.cpp @@ -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 +#include +#include +#include +#include +#include + +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 &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 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 diff --git a/src/plugins/android/androidsdkdownloader.h b/src/plugins/android/androidsdkdownloader.h new file mode 100644 index 00000000000..0801f763add --- /dev/null +++ b/src/plugins/android/androidsdkdownloader.h @@ -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 +#include +#include + +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 &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 diff --git a/src/plugins/android/androidsdkmanager.cpp b/src/plugins/android/androidsdkmanager.cpp index 494fa8cf256..cc5959a369f 100644 --- a/src/plugins/android/androidsdkmanager.cpp +++ b/src/plugins/android/androidsdkmanager.cpp @@ -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 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(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; diff --git a/src/plugins/android/androidsdkmanager.h b/src/plugins/android/androidsdkmanager.h index fa0d52e1acc..ce5cf4e299e 100644 --- a/src/plugins/android/androidsdkmanager.h +++ b/src/plugins/android/androidsdkmanager.h @@ -70,6 +70,7 @@ public: AndroidSdkPackageList availableSdkPackages(); AndroidSdkPackageList installedSdkPackages(); SystemImageList installedSystemImages(); + NdkList installedNdkPackages(); SdkPlatform *latestAndroidSdkPlatform(AndroidSdkPackage::PackageState state = AndroidSdkPackage::Installed); diff --git a/src/plugins/android/androidsdkmodel.cpp b/src/plugins/android/androidsdkmodel.cpp index 5506ad23787..7e18b19ff4a 100644 --- a/src/plugins/android/androidsdkmodel.cpp +++ b/src/plugins/android/androidsdkmodel.cpp @@ -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_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 AndroidSdkModel::userSelection() const { return Utils::toList(m_changeState); diff --git a/src/plugins/android/androidsdkpackage.cpp b/src/plugins/android/androidsdkpackage.cpp index 8c6e0d79114..a3da9625bc6 100644 --- a/src/plugins/android/androidsdkpackage.cpp +++ b/src/plugins/android/androidsdkpackage.cpp @@ -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 diff --git a/src/plugins/android/androidsdkpackage.h b/src/plugins/android/androidsdkpackage.h index 035e146f97b..eed0fd43959 100644 --- a/src/plugins/android/androidsdkpackage.h +++ b/src/plugins/android/androidsdkpackage.h @@ -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; + class ExtraTools : public AndroidSdkPackage { public: diff --git a/src/plugins/android/androidsettingswidget.cpp b/src/plugins/android/androidsettingswidget.cpp index 1991f63681d..d34f3c4ab9c 100644 --- a/src/plugins/android/androidsettingswidget.cpp +++ b/src/plugins/android/androidsettingswidget.cpp @@ -33,6 +33,7 @@ #include "androidavdmanager.h" #include "androidsdkmanager.h" #include "avddialog.h" +#include "androidsdkdownloader.h" #include "androidsdkmanagerwidget.h" #include @@ -51,6 +52,7 @@ #include #include +#include #include #include #include @@ -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::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::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(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(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(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() diff --git a/src/plugins/android/androidsettingswidget.ui b/src/plugins/android/androidsettingswidget.ui index cc548753573..5d149dddcf5 100644 --- a/src/plugins/android/androidsettingswidget.ui +++ b/src/plugins/android/androidsettingswidget.ui @@ -90,42 +90,6 @@ Android Settings - - - - - - - - 0 - 0 - - - - - - - - Download Android NDK - - - - - - - - 0 - 0 - - - - Android NDK location: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - @@ -142,16 +106,53 @@ - + Download Android SDK - + + + + Android NDK list: + + + + + + + Automatically download Android SDK Tools to selected location. + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + + + +