diff --git a/android/app/src/cpp/CMakeLists.txt b/android/app/src/cpp/CMakeLists.txt index 22d795e50..2e3fb11ed 100644 --- a/android/app/src/cpp/CMakeLists.txt +++ b/android/app/src/cpp/CMakeLists.txt @@ -3,6 +3,8 @@ set(SRCS android_controller_interface.h android_host_interface.cpp android_host_interface.h + android_http_downloader.cpp + android_http_downloader.h android_progress_callback.cpp android_progress_callback.h android_settings_interface.cpp diff --git a/android/app/src/cpp/android_controller_interface.h b/android/app/src/cpp/android_controller_interface.h index 9374f6e39..aaa408f84 100644 --- a/android/app/src/cpp/android_controller_interface.h +++ b/android/app/src/cpp/android_controller_interface.h @@ -1,6 +1,6 @@ #pragma once -#include "frontend-common/controller_interface.h" #include "core/types.h" +#include "frontend-common/controller_interface.h" #include #include #include diff --git a/android/app/src/cpp/android_host_interface.cpp b/android/app/src/cpp/android_host_interface.cpp index 72ae1e1fa..f06885fd0 100644 --- a/android/app/src/cpp/android_host_interface.cpp +++ b/android/app/src/cpp/android_host_interface.cpp @@ -55,6 +55,11 @@ static jclass s_SaveStateInfo_class; static jmethodID s_SaveStateInfo_constructor; namespace AndroidHelpers { +JavaVM* GetJavaVM() +{ + return s_jvm; +} + // helper for retrieving the current per-thread jni environment JNIEnv* GetJNIEnv() { diff --git a/android/app/src/cpp/android_host_interface.h b/android/app/src/cpp/android_host_interface.h index 813b0327f..c23193fca 100644 --- a/android/app/src/cpp/android_host_interface.h +++ b/android/app/src/cpp/android_host_interface.h @@ -109,6 +109,7 @@ private: namespace AndroidHelpers { +JavaVM* GetJavaVM(); JNIEnv* GetJNIEnv(); AndroidHostInterface* GetNativeClass(JNIEnv* env, jobject obj); std::string JStringToString(JNIEnv* env, jstring str); diff --git a/android/app/src/cpp/android_http_downloader.cpp b/android/app/src/cpp/android_http_downloader.cpp new file mode 100644 index 000000000..4f1db0997 --- /dev/null +++ b/android/app/src/cpp/android_http_downloader.cpp @@ -0,0 +1,162 @@ +#include "android_http_downloader.h" +#include "android_host_interface.h" +#include "common/assert.h" +#include "common/log.h" +#include "common/string_util.h" +#include "common/timer.h" +#include +#include +Log_SetChannel(AndroidHTTPDownloader); + +namespace FrontendCommon { + +AndroidHTTPDownloader::AndroidHTTPDownloader() : HTTPDownloader() {} + +AndroidHTTPDownloader::~AndroidHTTPDownloader() +{ + JNIEnv* env = AndroidHelpers::GetJNIEnv(); + if (m_URLDownloader_class) + env->DeleteGlobalRef(m_URLDownloader_class); +} + +std::unique_ptr HTTPDownloader::Create() +{ + std::unique_ptr instance(std::make_unique()); + if (!instance->Initialize()) + return {}; + + return instance; +} + +bool AndroidHTTPDownloader::Initialize() +{ + JNIEnv* env = AndroidHelpers::GetJNIEnv(); + jclass klass = env->FindClass("com/github/stenzek/duckstation/URLDownloader"); + if (!klass) + return false; + + m_URLDownloader_class = static_cast(env->NewGlobalRef(klass)); + if (!m_URLDownloader_class) + return false; + + m_URLDownloader_constructor = env->GetMethodID(klass, "", "()V"); + m_URLDownloader_get = env->GetMethodID(klass, "get", "(Ljava/lang/String;)Z"); + m_URLDownloader_post = env->GetMethodID(klass, "post", "(Ljava/lang/String;[B)Z"); + m_URLDownloader_getStatusCode = env->GetMethodID(klass, "getStatusCode", "()I"); + m_URLDownloader_getData = env->GetMethodID(klass, "getData", "()[B"); + if (!m_URLDownloader_constructor || !m_URLDownloader_get || !m_URLDownloader_post || !m_URLDownloader_getStatusCode || + !m_URLDownloader_getData) + { + return false; + } + + m_thread_pool = std::make_unique(m_max_active_requests); + return true; +} + +void AndroidHTTPDownloader::ProcessRequest(Request* req) +{ + std::unique_lock cancel_lock(m_cancel_mutex); + if (req->closed.load()) + return; + + cancel_lock.unlock(); + req->status_code = -1; + req->start_time = Common::Timer::GetValue(); + + // TODO: Move to Java side... + JNIEnv* env; + if (AndroidHelpers::GetJavaVM()->AttachCurrentThread(&env, nullptr) == JNI_OK) + { + jobject obj = env->NewObject(m_URLDownloader_class, m_URLDownloader_constructor); + jstring url_string = env->NewStringUTF(req->url.c_str()); + jboolean result; + if (req->post_data.empty()) + { + result = env->CallBooleanMethod(obj, m_URLDownloader_get, url_string); + } + else + { + jbyteArray post_data = env->NewByteArray(static_cast(req->post_data.size())); + env->SetByteArrayRegion(post_data, 0, static_cast(req->post_data.size()), + reinterpret_cast(req->post_data.data())); + result = env->CallBooleanMethod(obj, m_URLDownloader_post, url_string, post_data); + env->DeleteLocalRef(post_data); + } + + env->DeleteLocalRef(url_string); + + if (result) + { + req->status_code = env->CallIntMethod(obj, m_URLDownloader_getStatusCode); + + jbyteArray data = reinterpret_cast(env->CallObjectMethod(obj, m_URLDownloader_getData)); + if (data) + { + const u32 size = static_cast(env->GetArrayLength(data)); + req->data.resize(size); + if (size > 0) + { + jbyte* data_ptr = env->GetByteArrayElements(data, nullptr); + std::memcpy(req->data.data(), data_ptr, size); + env->ReleaseByteArrayElements(data, data_ptr, 0); + } + + env->DeleteLocalRef(data); + } + + Log_DevPrintf("Request for '%s' returned status code %d and %zu bytes", req->url.c_str(), req->status_code, + req->data.size()); + } + else + { + Log_ErrorPrintf("Request for '%s' failed", req->url.c_str()); + } + + env->DeleteLocalRef(obj); + } + else + { + Log_ErrorPrintf("AttachCurrentThread() failed"); + } + + cancel_lock.lock(); + req->state = Request::State::Complete; + if (req->closed.load()) + delete req; + else + req->closed.store(true); +} + +HTTPDownloader::Request* AndroidHTTPDownloader::InternalCreateRequest() +{ + Request* req = new Request(); + return req; +} + +void AndroidHTTPDownloader::InternalPollRequests() +{ + // noop - uses thread pool +} + +bool AndroidHTTPDownloader::StartRequest(HTTPDownloader::Request* request) +{ + Request* req = static_cast(request); + Log_DevPrintf("Started HTTP request for '%s'", req->url.c_str()); + req->state = Request::State::Started; + req->start_time = Common::Timer::GetValue(); + m_thread_pool->Schedule(std::bind(&AndroidHTTPDownloader::ProcessRequest, this, req)); + return true; +} + +void AndroidHTTPDownloader::CloseRequest(HTTPDownloader::Request* request) +{ + std::unique_lock cancel_lock(m_cancel_mutex); + Request* req = static_cast(request); + if (req->closed.load()) + delete req; + else + req->closed.store(true); +} + +} // namespace FrontendCommon diff --git a/android/app/src/cpp/android_http_downloader.h b/android/app/src/cpp/android_http_downloader.h new file mode 100644 index 000000000..420627cbb --- /dev/null +++ b/android/app/src/cpp/android_http_downloader.h @@ -0,0 +1,44 @@ +#pragma once +#include "common/thirdparty/thread_pool.h" +#include "frontend-common/http_downloader.h" +#include +#include +#include +#include + +namespace FrontendCommon { + +class AndroidHTTPDownloader final : public HTTPDownloader +{ +public: + AndroidHTTPDownloader(); + ~AndroidHTTPDownloader() override; + + bool Initialize(); + +protected: + Request* InternalCreateRequest() override; + void InternalPollRequests() override; + bool StartRequest(HTTPDownloader::Request* request) override; + void CloseRequest(HTTPDownloader::Request* request) override; + +private: + struct Request : HTTPDownloader::Request + { + std::atomic_bool closed{false}; + }; + + void ProcessRequest(Request* req); + + std::unique_ptr m_thread_pool; + std::mutex m_cancel_mutex; + + jclass m_URLDownloader_class = nullptr; + jmethodID m_URLDownloader_constructor = nullptr; + jmethodID m_URLDownloader_get = nullptr; + jmethodID m_URLDownloader_post = nullptr; + jmethodID m_URLDownloader_getStatusCode = nullptr; + jmethodID m_URLDownloader_getData = nullptr; +}; + +} // namespace FrontendCommon diff --git a/android/app/src/cpp/android_progress_callback.cpp b/android/app/src/cpp/android_progress_callback.cpp index 36efb3583..a25d02624 100644 --- a/android/app/src/cpp/android_progress_callback.cpp +++ b/android/app/src/cpp/android_progress_callback.cpp @@ -1,11 +1,10 @@ #include "android_progress_callback.h" #include "android_host_interface.h" -#include "common/log.h" #include "common/assert.h" +#include "common/log.h" Log_SetChannel(AndroidProgressCallback); -AndroidProgressCallback::AndroidProgressCallback(JNIEnv* env, jobject java_object) - : m_java_object(java_object) +AndroidProgressCallback::AndroidProgressCallback(JNIEnv* env, jobject java_object) : m_java_object(java_object) { jclass cls = env->GetObjectClass(java_object); m_set_title_method = env->GetMethodID(cls, "setTitle", "(Ljava/lang/String;)V"); @@ -15,7 +14,8 @@ AndroidProgressCallback::AndroidProgressCallback(JNIEnv* env, jobject java_objec m_modal_error_method = env->GetMethodID(cls, "modalError", "(Ljava/lang/String;)V"); m_modal_information_method = env->GetMethodID(cls, "modalInformation", "(Ljava/lang/String;)V"); m_modal_confirmation_method = env->GetMethodID(cls, "modalConfirmation", "(Ljava/lang/String;)Z"); - Assert(m_set_status_text_method && m_set_progress_range_method && m_set_progress_value_method && m_modal_error_method && m_modal_information_method && m_modal_confirmation_method); + Assert(m_set_status_text_method && m_set_progress_range_method && m_set_progress_value_method && + m_modal_error_method && m_modal_information_method && m_modal_confirmation_method); } AndroidProgressCallback::~AndroidProgressCallback() = default; diff --git a/android/app/src/cpp/android_settings_interface.cpp b/android/app/src/cpp/android_settings_interface.cpp index 0cfcf1f53..52af477c8 100644 --- a/android/app/src/cpp/android_settings_interface.cpp +++ b/android/app/src/cpp/android_settings_interface.cpp @@ -56,16 +56,25 @@ AndroidSettingsInterface::AndroidSettingsInterface(jobject java_context) Assert(m_get_boolean && m_get_int && m_get_float && m_get_string && m_get_string_set && m_set_to_array); m_edit = env->GetMethodID(m_shared_preferences_class, "edit", "()Landroid/content/SharedPreferences$Editor;"); - m_edit_set_string = env->GetMethodID(m_shared_preferences_editor_class, "putString", "(Ljava/lang/String;Ljava/lang/String;)Landroid/content/SharedPreferences$Editor;"); + m_edit_set_string = + env->GetMethodID(m_shared_preferences_editor_class, "putString", + "(Ljava/lang/String;Ljava/lang/String;)Landroid/content/SharedPreferences$Editor;"); m_edit_commit = env->GetMethodID(m_shared_preferences_editor_class, "commit", "()Z"); - m_edit_remove = env->GetMethodID(m_shared_preferences_editor_class, "remove", "(Ljava/lang/String;)Landroid/content/SharedPreferences$Editor;"); + m_edit_remove = env->GetMethodID(m_shared_preferences_editor_class, "remove", + "(Ljava/lang/String;)Landroid/content/SharedPreferences$Editor;"); Assert(m_edit && m_edit_set_string && m_edit_commit && m_edit_remove); - m_helper_clear_section = env->GetStaticMethodID(m_helper_class, "clearSection", "(Landroid/content/SharedPreferences;Ljava/lang/String;)V"); - m_helper_add_to_string_list = env->GetStaticMethodID(m_helper_class, "addToStringList", "(Landroid/content/SharedPreferences;Ljava/lang/String;Ljava/lang/String;)Z"); - m_helper_remove_from_string_list = env->GetStaticMethodID(m_helper_class, "removeFromStringList", "(Landroid/content/SharedPreferences;Ljava/lang/String;Ljava/lang/String;)Z"); - m_helper_set_string_list = env->GetStaticMethodID(m_helper_class, "setStringList", "(Landroid/content/SharedPreferences;Ljava/lang/String;[Ljava/lang/String;)V"); - Assert(m_helper_clear_section && m_helper_add_to_string_list && m_helper_remove_from_string_list && m_helper_set_string_list); + m_helper_clear_section = + env->GetStaticMethodID(m_helper_class, "clearSection", "(Landroid/content/SharedPreferences;Ljava/lang/String;)V"); + m_helper_add_to_string_list = env->GetStaticMethodID( + m_helper_class, "addToStringList", "(Landroid/content/SharedPreferences;Ljava/lang/String;Ljava/lang/String;)Z"); + m_helper_remove_from_string_list = + env->GetStaticMethodID(m_helper_class, "removeFromStringList", + "(Landroid/content/SharedPreferences;Ljava/lang/String;Ljava/lang/String;)Z"); + m_helper_set_string_list = env->GetStaticMethodID( + m_helper_class, "setStringList", "(Landroid/content/SharedPreferences;Ljava/lang/String;[Ljava/lang/String;)V"); + Assert(m_helper_clear_section && m_helper_add_to_string_list && m_helper_remove_from_string_list && + m_helper_set_string_list); } AndroidSettingsInterface::~AndroidSettingsInterface() @@ -212,7 +221,7 @@ jobject AndroidSettingsInterface::GetPreferencesEditor(JNIEnv* env) return env->CallObjectMethod(m_java_shared_preferences, m_edit); } -void AndroidSettingsInterface::CheckForException(JNIEnv *env, const char *task) +void AndroidSettingsInterface::CheckForException(JNIEnv* env, const char* task) { if (!env->ExceptionCheck()) return; @@ -230,7 +239,8 @@ void AndroidSettingsInterface::SetIntValue(const char* section, const char* key, LocalRefHolder key_string(env, env->NewStringUTF(GetSettingKey(section, key))); LocalRefHolder str_value(env, env->NewStringUTF(TinyString::FromFormat("%d", value))); - LocalRefHolder dummy(env, env->CallObjectMethod(editor, m_edit_set_string, key_string.Get(), str_value.Get())); + LocalRefHolder dummy(env, + env->CallObjectMethod(editor, m_edit_set_string, key_string.Get(), str_value.Get())); env->CallBooleanMethod(editor, m_edit_commit); CheckForException(env, "SetIntValue"); @@ -245,7 +255,8 @@ void AndroidSettingsInterface::SetFloatValue(const char* section, const char* ke LocalRefHolder key_string(env, env->NewStringUTF(GetSettingKey(section, key))); LocalRefHolder str_value(env, env->NewStringUTF(TinyString::FromFormat("%f", value))); - LocalRefHolder dummy(env, env->CallObjectMethod(editor, m_edit_set_string, key_string.Get(), str_value.Get())); + LocalRefHolder dummy(env, + env->CallObjectMethod(editor, m_edit_set_string, key_string.Get(), str_value.Get())); env->CallBooleanMethod(editor, m_edit_commit); CheckForException(env, "SetFloatValue"); @@ -260,7 +271,8 @@ void AndroidSettingsInterface::SetBoolValue(const char* section, const char* key LocalRefHolder key_string(env, env->NewStringUTF(GetSettingKey(section, key))); LocalRefHolder str_value(env, env->NewStringUTF(value ? "true" : "false")); - LocalRefHolder dummy(env, env->CallObjectMethod(editor, m_edit_set_string, key_string.Get(), str_value.Get())); + LocalRefHolder dummy(env, + env->CallObjectMethod(editor, m_edit_set_string, key_string.Get(), str_value.Get())); env->CallBooleanMethod(editor, m_edit_commit); CheckForException(env, "SetBoolValue"); @@ -275,7 +287,8 @@ void AndroidSettingsInterface::SetStringValue(const char* section, const char* k LocalRefHolder key_string(env, env->NewStringUTF(GetSettingKey(section, key))); LocalRefHolder str_value(env, env->NewStringUTF(value)); - LocalRefHolder dummy(env, env->CallObjectMethod(editor, m_edit_set_string, key_string.Get(), str_value.Get())); + LocalRefHolder dummy(env, + env->CallObjectMethod(editor, m_edit_set_string, key_string.Get(), str_value.Get())); env->CallBooleanMethod(editor, m_edit_commit); CheckForException(env, "SetStringValue"); @@ -316,10 +329,11 @@ std::vector AndroidSettingsInterface::GetStringList(const char* sec env->ExceptionClear(); // this might just be a string, not a string set - LocalRefHolder string_object( - env, reinterpret_cast(env->CallObjectMethod(m_java_shared_preferences, m_get_string, key_string.Get(), nullptr))); + LocalRefHolder string_object(env, reinterpret_cast(env->CallObjectMethod( + m_java_shared_preferences, m_get_string, key_string.Get(), nullptr))); - if (!env->ExceptionCheck()) { + if (!env->ExceptionCheck()) + { std::vector ret; if (string_object) ret.push_back(AndroidHelpers::JStringToString(env, string_object)); @@ -369,7 +383,8 @@ void AndroidSettingsInterface::SetStringList(const char* section, const char* ke } JNIEnv* env = AndroidHelpers::GetJNIEnv(); - LocalRefHolder items_array(env, env->NewObjectArray(static_cast(items.size()), AndroidHelpers::GetStringClass(), nullptr)); + LocalRefHolder items_array( + env, env->NewObjectArray(static_cast(items.size()), AndroidHelpers::GetStringClass(), nullptr)); for (size_t i = 0; i < items.size(); i++) { LocalRefHolder item_jstr(env, env->NewStringUTF(items[i].c_str())); @@ -377,7 +392,8 @@ void AndroidSettingsInterface::SetStringList(const char* section, const char* ke } LocalRefHolder key_string(env, env->NewStringUTF(GetSettingKey(section, key))); - env->CallStaticVoidMethod(m_helper_class, m_helper_set_string_list, m_java_shared_preferences, key_string.Get(), items_array.Get()); + env->CallStaticVoidMethod(m_helper_class, m_helper_set_string_list, m_java_shared_preferences, key_string.Get(), + items_array.Get()); CheckForException(env, "SetStringList"); } @@ -389,7 +405,8 @@ bool AndroidSettingsInterface::RemoveFromStringList(const char* section, const c JNIEnv* env = AndroidHelpers::GetJNIEnv(); LocalRefHolder key_string(env, env->NewStringUTF(GetSettingKey(section, key))); LocalRefHolder item_string(env, env->NewStringUTF(item)); - const bool result = env->CallStaticBooleanMethod(m_helper_class, m_helper_remove_from_string_list, m_java_shared_preferences, key_string.Get(), item_string.Get()); + const bool result = env->CallStaticBooleanMethod(m_helper_class, m_helper_remove_from_string_list, + m_java_shared_preferences, key_string.Get(), item_string.Get()); CheckForException(env, "RemoveFromStringList"); return result; } @@ -401,7 +418,8 @@ bool AndroidSettingsInterface::AddToStringList(const char* section, const char* JNIEnv* env = AndroidHelpers::GetJNIEnv(); LocalRefHolder key_string(env, env->NewStringUTF(GetSettingKey(section, key))); LocalRefHolder item_string(env, env->NewStringUTF(item)); - const bool result = env->CallStaticBooleanMethod(m_helper_class, m_helper_add_to_string_list, m_java_shared_preferences, key_string.Get(), item_string.Get()); + const bool result = env->CallStaticBooleanMethod(m_helper_class, m_helper_add_to_string_list, + m_java_shared_preferences, key_string.Get(), item_string.Get()); CheckForException(env, "AddToStringList"); return result; } diff --git a/android/app/src/cpp/opensles_audio_stream.cpp b/android/app/src/cpp/opensles_audio_stream.cpp index 13d32724d..70f2c207c 100644 --- a/android/app/src/cpp/opensles_audio_stream.cpp +++ b/android/app/src/cpp/opensles_audio_stream.cpp @@ -129,8 +129,8 @@ bool OpenSLESAudioStream::OpenDevice() for (u32 i = 0; i < NUM_BUFFERS; i++) m_buffers[i] = std::make_unique(m_buffer_size * m_channels); - Log_InfoPrintf("OpenSL ES device opened: %uhz, %u channels, %u buffer size, %u buffers", - m_output_sample_rate, m_channels, m_buffer_size, NUM_BUFFERS); + Log_InfoPrintf("OpenSL ES device opened: %uhz, %u channels, %u buffer size, %u buffers", m_output_sample_rate, + m_channels, m_buffer_size, NUM_BUFFERS); return true; } @@ -139,7 +139,8 @@ void OpenSLESAudioStream::PauseDevice(bool paused) if (m_paused == paused) return; - SLresult res = (*m_play_interface)->SetPlayState(m_play_interface, paused ? SL_PLAYSTATE_PAUSED : SL_PLAYSTATE_PLAYING); + SLresult res = + (*m_play_interface)->SetPlayState(m_play_interface, paused ? SL_PLAYSTATE_PAUSED : SL_PLAYSTATE_PLAYING); if (res != SL_RESULT_SUCCESS) Log_ErrorPrintf("SetPlayState failed: %d", res); diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/URLDownloader.java b/android/app/src/main/java/com/github/stenzek/duckstation/URLDownloader.java new file mode 100644 index 000000000..3661ece6d --- /dev/null +++ b/android/app/src/main/java/com/github/stenzek/duckstation/URLDownloader.java @@ -0,0 +1,92 @@ +package com.github.stenzek.duckstation; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; + +/** + * Helper class for exposing HTTP downloads to native code without pulling in an external + * dependency for doing so. + */ +public class URLDownloader { + private int statusCode = -1; + private byte[] data = null; + + public URLDownloader() { + } + + static private HttpURLConnection getConnection(String url) { + try { + final URL parsedUrl = new URL(url); + HttpURLConnection connection = (HttpURLConnection) parsedUrl.openConnection(); + if (connection == null) + throw new RuntimeException(String.format("openConnection(%s) returned null", url)); + + return connection; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + public int getStatusCode() { + return statusCode; + } + + public byte[] getData() { + return data; + } + + private boolean download(HttpURLConnection connection) { + try { + statusCode = connection.getResponseCode(); + if (statusCode != HttpURLConnection.HTTP_OK) + return false; + + final InputStream inStream = new BufferedInputStream(connection.getInputStream()); + final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + final int CHUNK_SIZE = 128 * 1024; + final byte[] chunk = new byte[CHUNK_SIZE]; + int len; + while ((len = inStream.read(chunk)) > 0) { + outputStream.write(chunk, 0, len); + } + + data = outputStream.toByteArray(); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + public boolean get(String url) { + final HttpURLConnection connection = getConnection(url); + if (connection == null) + return false; + + return download(connection); + } + + public boolean post(String url, byte[] postData) { + final HttpURLConnection connection = getConnection(url); + if (connection == null) + return false; + + try { + connection.setDoOutput(true); + connection.setChunkedStreamingMode(0); + + OutputStream postStream = new BufferedOutputStream(connection.getOutputStream()); + postStream.write(postData); + return download(connection); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } +} diff --git a/src/frontend-common/CMakeLists.txt b/src/frontend-common/CMakeLists.txt index 7c6d3d190..46f17a6b0 100644 --- a/src/frontend-common/CMakeLists.txt +++ b/src/frontend-common/CMakeLists.txt @@ -100,14 +100,14 @@ if(ENABLE_CHEEVOS) http_downloader_winhttp.cpp http_downloader_winhttp.h ) - else() - target_sources(frontend-common PRIVATE - http_downloader_curl.cpp - http_downloader_curl.h - ) - target_link_libraries(frontend-common PRIVATE - CURL::libcurl - ) + elseif(NOT ANDROID) + target_sources(frontend-common PRIVATE + http_downloader_curl.cpp + http_downloader_curl.h + ) + target_link_libraries(frontend-common PRIVATE + CURL::libcurl + ) endif() target_sources(frontend-common PRIVATE