diff --git a/src/plugins/android/androidbuildapkwidget.cpp b/src/plugins/android/androidbuildapkwidget.cpp index 4e2a9f128b3..66be50dca7d 100644 --- a/src/plugins/android/androidbuildapkwidget.cpp +++ b/src/plugins/android/androidbuildapkwidget.cpp @@ -35,6 +35,7 @@ #include "createandroidmanifestwizard.h" #include +#include #include #include #include @@ -273,8 +274,11 @@ QWidget *AndroidBuildApkWidget::createAdditionalLibrariesGroup() group->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); auto libsModel = new AndroidExtraLibraryListModel(m_step->target(), this); - connect(libsModel, &AndroidExtraLibraryListModel::enabledChanged, - group, &QWidget::setEnabled); + connect(libsModel, &AndroidExtraLibraryListModel::enabledChanged, this, + [this, group](const bool enabled) { + group->setEnabled(enabled); + m_openSslCheckBox->setChecked(isOpenSslLibsIncluded()); + }); auto libsView = new QListView; libsView->setSelectionMode(QAbstractItemView::ExtendedSelection); @@ -308,9 +312,16 @@ QWidget *AndroidBuildApkWidget::createAdditionalLibrariesGroup() libsButtonLayout->addWidget(removeLibButton); libsButtonLayout->addStretch(1); - auto hbox = new QHBoxLayout(group); - hbox->addWidget(libsView); - hbox->addLayout(libsButtonLayout); + m_openSslCheckBox = new QCheckBox(tr("Include prebuilt OpenSSL libraries")); + m_openSslCheckBox->setToolTip(tr("This is useful for apps that use SSL operations. The path " + "can be defined in Tools > Options > Devices > Android.")); + connect(m_openSslCheckBox, &QAbstractButton::clicked, this, + &AndroidBuildApkWidget::onOpenSslCheckBoxChanged); + + auto grid = new QGridLayout(group); + grid->addWidget(m_openSslCheckBox, 0, 0); + grid->addWidget(libsView, 1, 0); + grid->addLayout(libsButtonLayout, 1, 1); QItemSelectionModel *libSelection = libsView->selectionModel(); connect(libSelection, &QItemSelectionModel::selectionChanged, this, [libSelection, removeLibButton] { @@ -337,6 +348,53 @@ void AndroidBuildApkWidget::signPackageCheckBoxToggled(bool checked) setCertificates(); } +void AndroidBuildApkWidget::onOpenSslCheckBoxChanged() +{ + Utils::FilePath projectPath = m_step->buildConfiguration()->buildSystem()->projectFilePath(); + QFile projectFile(projectPath.toString()); + if (!projectFile.open(QIODevice::ReadWrite | QIODevice::Text)) { + qWarning() << "Cound't open project file to add OpenSSL extra libs: " << projectPath; + return; + } + + const QString searchStr = openSslIncludeFileContent(projectPath); + QTextStream textStream(&projectFile); + + QString fileContent = textStream.readAll(); + if (!m_openSslCheckBox->isChecked()) { + fileContent.remove("\n" + searchStr); + } else if (!fileContent.contains(searchStr, Qt::CaseSensitive)) { + fileContent.append(searchStr + "\n"); + } + + projectFile.resize(0); + textStream << fileContent; + projectFile.close(); +} + +bool AndroidBuildApkWidget::isOpenSslLibsIncluded() +{ + Utils::FilePath projectPath = m_step->buildConfiguration()->buildSystem()->projectFilePath(); + const QString searchStr = openSslIncludeFileContent(projectPath); + QFile projectFile(projectPath.toString()); + projectFile.open(QIODevice::ReadOnly); + QTextStream textStream(&projectFile); + QString fileContent = textStream.readAll(); + projectFile.close(); + return fileContent.contains(searchStr, Qt::CaseSensitive); +} + +QString AndroidBuildApkWidget::openSslIncludeFileContent(const Utils::FilePath &projectPath) +{ + QString openSslPath = AndroidConfigurations::currentConfig().openSslLocation().toString(); + if (projectPath.endsWith(".pro")) + return "android: include(" + openSslPath + "/openssl.pri)"; + if (projectPath.endsWith("CMakeLists.txt")) + return "if (ANDROID)\n include(" + openSslPath + "/CMakeLists.txt)\nendif()"; + + return QString(); +} + void AndroidBuildApkWidget::setCertificates() { QAbstractItemModel *certificates = m_step->keystoreCertificates(); diff --git a/src/plugins/android/androidbuildapkwidget.h b/src/plugins/android/androidbuildapkwidget.h index c3751484a19..5f6875abe25 100644 --- a/src/plugins/android/androidbuildapkwidget.h +++ b/src/plugins/android/androidbuildapkwidget.h @@ -56,6 +56,9 @@ private: void setCertificates(); void updateSigningWarning(); void signPackageCheckBoxToggled(bool checked); + void onOpenSslCheckBoxChanged(); + bool isOpenSslLibsIncluded(); + QString openSslIncludeFileContent(const Utils::FilePath &projectPath); QWidget *createApplicationGroup(); QWidget *createSignPackageGroup(); @@ -69,6 +72,7 @@ private: Utils::InfoLabel *m_signingDebugWarningLabel = nullptr; QComboBox *m_certificatesAliasComboBox = nullptr; QCheckBox *m_addDebuggerCheckBox = nullptr; + QCheckBox *m_openSslCheckBox = nullptr; }; } // namespace Internal diff --git a/src/plugins/android/androidconfigurations.cpp b/src/plugins/android/androidconfigurations.cpp index 5879c0e0073..e9029ce1182 100644 --- a/src/plugins/android/androidconfigurations.cpp +++ b/src/plugins/android/androidconfigurations.cpp @@ -104,6 +104,7 @@ namespace { const QLatin1String SdkFullyConfiguredKey("AllEssentialsInstalled"); const QLatin1String SDKManagerToolArgsKey("SDKManagerToolArgs"); const QLatin1String OpenJDKLocationKey("OpenJDKLocation"); + const QLatin1String OpenSslPriLocationKey("OpenSSLPriLocation"); const QLatin1String KeystoreLocationKey("KeystoreLocation"); const QLatin1String AutomaticKitCreationKey("AutomatiKitCreation"); const QLatin1String PartitionSizeKey("PartitionSize"); @@ -239,6 +240,7 @@ void AndroidConfig::load(const QSettings &settings) m_customNdkList = settings.value(CustomNdkLocationsKey).toStringList(); m_sdkManagerToolArgs = settings.value(SDKManagerToolArgsKey).toStringList(); m_openJDKLocation = FilePath::fromString(settings.value(OpenJDKLocationKey).toString()); + m_openSslLocation = FilePath::fromString(settings.value(OpenSslPriLocationKey).toString()); m_keystoreLocation = FilePath::fromString(settings.value(KeystoreLocationKey).toString()); m_automaticKitCreation = settings.value(AutomaticKitCreationKey, true).toBool(); m_sdkFullyConfigured = settings.value(SdkFullyConfiguredKey, false).toBool(); @@ -251,6 +253,7 @@ void AndroidConfig::load(const QSettings &settings) m_customNdkList = reader.restoreValue(CustomNdkLocationsKey).toStringList(); m_sdkManagerToolArgs = reader.restoreValue(SDKManagerToolArgsKey, m_sdkManagerToolArgs).toStringList(); m_openJDKLocation = FilePath::fromString(reader.restoreValue(OpenJDKLocationKey, m_openJDKLocation.toString()).toString()); + m_openSslLocation = FilePath::fromString(reader.restoreValue(OpenSslPriLocationKey, m_openSslLocation.toString()).toString()); m_automaticKitCreation = reader.restoreValue(AutomaticKitCreationKey, m_automaticKitCreation).toBool(); m_sdkFullyConfigured = reader.restoreValue(SdkFullyConfiguredKey, m_sdkFullyConfigured).toBool(); // persistent settings @@ -271,6 +274,7 @@ void AndroidConfig::save(QSettings &settings) const settings.setValue(SDKManagerToolArgsKey, m_sdkManagerToolArgs); settings.setValue(OpenJDKLocationKey, m_openJDKLocation.toString()); settings.setValue(KeystoreLocationKey, m_keystoreLocation.toString()); + settings.setValue(OpenSslPriLocationKey, m_openSslLocation.toString()); settings.setValue(PartitionSizeKey, m_partitionSize); settings.setValue(AutomaticKitCreationKey, m_automaticKitCreation); settings.setValue(SdkFullyConfiguredKey, m_sdkFullyConfigured); @@ -374,6 +378,16 @@ void AndroidConfig::removeCustomNdk(const QString &customNdk) m_customNdkList.removeAll(customNdk); } +Utils::FilePath AndroidConfig::openSslLocation() const +{ + return m_openSslLocation; +} + +void AndroidConfig::setOpenSslLocation(const Utils::FilePath &openSslLocation) +{ + m_openSslLocation = openSslLocation; +} + QStringList AndroidConfig::apiLevelNamesFor(const SdkPlatformList &platforms) { return Utils::transform(platforms, AndroidConfig::apiLevelNameFor); diff --git a/src/plugins/android/androidconfigurations.h b/src/plugins/android/androidconfigurations.h index f793ed78c3f..cf324cdcfb5 100644 --- a/src/plugins/android/androidconfigurations.h +++ b/src/plugins/android/androidconfigurations.h @@ -196,6 +196,10 @@ public: void addCustomNdk(const QString &customNdk); void removeCustomNdk(const QString &customNdk); + Utils::FilePath openSslLocation() const; + void setOpenSslLocation(const Utils::FilePath &openSslLocation); + + private: static QString getDeviceProperty(const Utils::FilePath &adbToolPath, const QString &device, const QString &property); @@ -217,6 +221,7 @@ private: QStringList m_sdkManagerToolArgs; Utils::FilePath m_openJDKLocation; Utils::FilePath m_keystoreLocation; + Utils::FilePath m_openSslLocation; unsigned m_partitionSize = 1024; bool m_automaticKitCreation = true; QUrl m_sdkToolsUrl; diff --git a/src/plugins/android/androidsettingswidget.cpp b/src/plugins/android/androidsettingswidget.cpp index 42511b893c3..30875271c10 100644 --- a/src/plugins/android/androidsettingswidget.cpp +++ b/src/plugins/android/androidsettingswidget.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -112,6 +113,7 @@ private: void openSDKDownloadUrl(); void openNDKDownloadUrl(); void openOpenJDKDownloadUrl(); + void downloadOpenSslRepo(const bool silent = false); void addAVD(); void avdAdded(); void removeAVD(); @@ -134,6 +136,7 @@ private: Utils::FilePath getDefaultSdkPath(); void showEvent(QShowEvent *event) override; void addCustomNdkItem(); + void validateOpenSsl(); Ui_AndroidSettingsWidget *m_ui; AndroidSdkManagerWidget *m_sdkManagerWidget = nullptr; @@ -167,6 +170,12 @@ enum AndroidValidation { NdkinstallDirOkRow }; +enum OpenSslValidation { + OpenSslPathExistsRow, + OpenSslPriPathExists, + OpenSslCmakeListsPathExists +}; + class SummaryWidget : public QWidget { class RowData { @@ -344,6 +353,7 @@ void AndroidSettingsWidget::showEvent(QShowEvent *event) // to let settings dialog open first. QTimer::singleShot(0, std::bind(&AndroidSdkManager::reloadPackages, m_sdkManager.get(), false)); + validateOpenSsl(); m_isInitialReloadDone = true; } } @@ -421,6 +431,18 @@ AndroidSettingsWidget::AndroidSettingsWidget() m_ui->androidDetailsWidget); m_ui->androidDetailsWidget->setWidget(androidSummary); + QMap openSslValidationPoints; + openSslValidationPoints[OpenSslPathExistsRow] = tr("OpenSSL path exists."); + openSslValidationPoints[OpenSslPriPathExists] = tr( + "QMake include project (openssl.pri) exists."); + openSslValidationPoints[OpenSslCmakeListsPathExists] = tr( + "CMake include project (CMakeLists.txt) exists."); + auto openSslSummary = new SummaryWidget(openSslValidationPoints, + tr("OpenSSL Settings are OK."), + tr("OpenSSL settings have errors."), + m_ui->openSslDetailsWidget); + m_ui->openSslDetailsWidget->setWidget(openSslSummary); + connect(m_ui->OpenJDKLocationPathChooser, &Utils::PathChooser::rawPathChanged, this, &AndroidSettingsWidget::validateJdk); Utils::FilePath currentJdkPath = m_androidConfig.openJDKLocation(); @@ -436,6 +458,12 @@ AndroidSettingsWidget::AndroidSettingsWidget() m_ui->SDKLocationPathChooser->setFileName(currentSDKPath); m_ui->SDKLocationPathChooser->setPromptDialogTitle(tr("Select Android SDK folder")); + m_ui->openSslPathChooser->setPromptDialogTitle(tr("Select OpenSSL Include Project File")); + Utils::FilePath currentOpenSslPath = m_androidConfig.openSslLocation(); + if (currentOpenSslPath.isEmpty()) + currentOpenSslPath = currentSDKPath.pathAppended("android_openssl"); + m_ui->openSslPathChooser->setFileName(currentOpenSslPath); + m_ui->DataPartitionSizeSpinBox->setValue(m_androidConfig.partitionSize()); m_ui->CreateKitCheckBox->setChecked(m_androidConfig.automaticKitCreation()); m_ui->AVDTableView->setModel(&m_AVDModel); @@ -448,6 +476,7 @@ AndroidSettingsWidget::AndroidSettingsWidget() m_ui->downloadSDKToolButton->setIcon(downloadIcon); m_ui->downloadNDKToolButton->setIcon(downloadIcon); m_ui->downloadOpenJDKToolButton->setIcon(downloadIcon); + m_ui->downloadOpenSSLPrebuiltLibs->setIcon(downloadIcon); m_ui->sdkToolsAutoDownloadButton->setIcon(Utils::Icons::RELOAD.icon()); connect(m_ui->SDKLocationPathChooser, &Utils::PathChooser::rawPathChanged, @@ -469,6 +498,10 @@ AndroidSettingsWidget::AndroidSettingsWidget() m_ui->ndkListComboBox->removeItem(m_ui->ndkListComboBox->currentIndex()); }); + connect(m_ui->ndkListComboBox, QOverload::of(&QComboBox::currentIndexChanged), + [this](const QString) { validateNdk(); }); + connect(m_ui->openSslPathChooser, &Utils::PathChooser::rawPathChanged, this, + &AndroidSettingsWidget::validateOpenSsl); connect(&m_virtualDevicesWatcher, &QFutureWatcherBase::finished, this, &AndroidSettingsWidget::updateAvds); connect(m_ui->AVDRefreshPushButton, &QAbstractButton::clicked, @@ -494,6 +527,8 @@ AndroidSettingsWidget::AndroidSettingsWidget() this, &AndroidSettingsWidget::openNDKDownloadUrl); connect(m_ui->downloadSDKToolButton, &QAbstractButton::clicked, this, &AndroidSettingsWidget::openSDKDownloadUrl); + connect(m_ui->downloadOpenSSLPrebuiltLibs, &QAbstractButton::clicked, + this, &AndroidSettingsWidget::downloadOpenSslRepo); connect(m_ui->downloadOpenJDKToolButton, &QAbstractButton::clicked, this, &AndroidSettingsWidget::openOpenJDKDownloadUrl); // Validate SDK again after any change in SDK packages. @@ -564,6 +599,22 @@ void AndroidSettingsWidget::validateJdk() updateUI(); } +void AndroidSettingsWidget::validateOpenSsl() +{ + auto openSslPath = Utils::FilePath::fromUserInput(m_ui->openSslPathChooser->rawPath()); + m_androidConfig.setOpenSslLocation(openSslPath); + + auto summaryWidget = static_cast(m_ui->openSslDetailsWidget->widget()); + summaryWidget->setPointValid(OpenSslPathExistsRow, m_androidConfig.openSslLocation().exists()); + + const bool priFileExists = m_androidConfig.openSslLocation().pathAppended("openssl.pri").exists(); + summaryWidget->setPointValid(OpenSslPriPathExists, priFileExists); + const bool cmakeListsExists + = m_androidConfig.openSslLocation().pathAppended("CMakeLists.txt").exists(); + summaryWidget->setPointValid(OpenSslCmakeListsPathExists, cmakeListsExists); + updateUI(); +} + Utils::FilePath AndroidSettingsWidget::findJdkInCommonPaths() { QString jdkFromEnvVar = QString::fromLocal8Bit(getenv("JAVA_HOME")); @@ -700,6 +751,67 @@ void AndroidSettingsWidget::openOpenJDKDownloadUrl() QDesktopServices::openUrl(QUrl::fromUserInput("http://www.oracle.com/technetwork/java/javase/downloads/")); } +void AndroidSettingsWidget::downloadOpenSslRepo(const bool silent) +{ + const Utils::FilePath openSslPath = m_ui->openSslPathChooser->fileName(); + const QString openSslCloneTitle(tr("OpenSSL Cloning")); + + auto openSslSummaryWidget = static_cast(m_ui->openSslDetailsWidget->widget()); + if (openSslSummaryWidget->allRowsOk()) { + if (silent) { + QMessageBox::information(this, openSslCloneTitle, + tr("OpenSSL prebuilt libraries repository is already configured.")); + } + return; + } + + const QString openSslRepo("https://github.com/KDAB/android_openssl.git"); + Utils::QtcProcess *gitCloner = new Utils::QtcProcess(this); + gitCloner->setCommand(Utils::CommandLine("git", {"clone", openSslRepo, openSslPath.fileName()})); + gitCloner->setWorkingDirectory(openSslPath.parentDir().toString()); + + QDir openSslDir(openSslPath.toString()); + if (openSslDir.exists()) { + auto userInput = QMessageBox::information(this, openSslCloneTitle, + tr("The selected download path (%1) for OpenSSL already exists, " + "do you want to remove and overwrite its content?") + .arg(QDir::toNativeSeparators(openSslPath.toString())), + QMessageBox::Yes | QMessageBox::No); + if (userInput == QMessageBox::Yes) + openSslDir.removeRecursively(); + else + return; + } + + QProgressDialog *openSslProgressDialog + = new QProgressDialog(tr("Cloning OpenSSL prebuilt libraries, please be patient..."), + tr("Cancel"), 0, 0); + openSslProgressDialog->setWindowModality(Qt::WindowModal); + openSslProgressDialog->setWindowTitle(openSslCloneTitle); + openSslProgressDialog->setFixedSize(openSslProgressDialog->sizeHint()); + + connect(openSslProgressDialog, &QProgressDialog::canceled, this, [gitCloner]() { + gitCloner->kill(); + }); + + gitCloner->start(); + openSslProgressDialog->show(); + + connect(gitCloner, QOverload::of(&Utils::QtcProcess::finished), + [=](int exitCode, QProcess::ExitStatus exitStatus) { + openSslProgressDialog->close(); + validateOpenSsl(); + + if (!openSslProgressDialog->wasCanceled() || + (exitStatus == Utils::QtcProcess::NormalExit && exitCode != 0)) { + QMessageBox::information(this, openSslCloneTitle, + tr("OpenSSL prebuilt libraries cloning failed. " + "Opening OpenSSL URL for manual download...")); + QDesktopServices::openUrl(QUrl::fromUserInput(openSslRepo)); + } + }); +} + void AndroidSettingsWidget::addAVD() { disableAvdControls(); @@ -767,9 +879,11 @@ void AndroidSettingsWidget::updateUI() { auto javaSummaryWidget = static_cast(m_ui->javaDetailsWidget->widget()); auto androidSummaryWidget = static_cast(m_ui->androidDetailsWidget->widget()); - bool javaSetupOk = javaSummaryWidget->allRowsOk(); - bool sdkToolsOk = androidSummaryWidget->rowsOk({SdkPathExistsRow, SdkPathWritableRow, SdkToolsInstalledRow}); - bool androidSetupOk = androidSummaryWidget->allRowsOk(); + auto openSslSummaryWidget = static_cast(m_ui->openSslDetailsWidget->widget()); + const bool javaSetupOk = javaSummaryWidget->allRowsOk(); + const bool sdkToolsOk = androidSummaryWidget->rowsOk({SdkPathExistsRow, SdkPathWritableRow, SdkToolsInstalledRow}); + const bool androidSetupOk = androidSummaryWidget->allRowsOk(); + const bool openSslOk = openSslSummaryWidget->allRowsOk(); m_ui->avdManagerTab->setEnabled(javaSetupOk && androidSetupOk); m_ui->sdkManagerTab->setEnabled(sdkToolsOk); @@ -785,6 +899,8 @@ void AndroidSettingsWidget::updateUI() Utils::DetailsWidget::Expanded); m_ui->androidDetailsWidget->setState(androidSetupOk ? Utils::DetailsWidget::Collapsed : Utils::DetailsWidget::Expanded); + m_ui->openSslDetailsWidget->setState(openSslOk ? Utils::DetailsWidget::Collapsed : + Utils::DetailsWidget::Expanded); } void AndroidSettingsWidget::manageAVD() @@ -820,6 +936,14 @@ void AndroidSettingsWidget::downloadSdk() m_sdkManager->reloadPackages(true); updateUI(); apply(); + + QMetaObject::Connection *const openSslOneShot = new QMetaObject::Connection; + *openSslOneShot = connect(m_sdkManager.get(), &AndroidSdkManager::packageReloadFinished, + this, [this, openSslOneShot]() { + QObject::disconnect(*openSslOneShot); + downloadOpenSslRepo(true); + delete openSslOneShot; + }); }); auto showErrorDialog = [this](const QString &error) { diff --git a/src/plugins/android/androidsettingswidget.ui b/src/plugins/android/androidsettingswidget.ui index 6d8272b7dfa..2a25b93386c 100644 --- a/src/plugins/android/androidsettingswidget.ui +++ b/src/plugins/android/androidsettingswidget.ui @@ -204,6 +204,48 @@ + + + + Android OpenSSL settings + + + + + + + 0 + 0 + + + + OpenSSL .pri location: + + + + + + + Select the path of the prebuilt OpenSSL binaries. + + + + + + + + + + Automatically download OpenSSL prebuilt libraries. If the automatic download fails, a URL will be opened in the browser for manual download. + + + + + + + + +