#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); AndroidHelpers::GetJavaVM()->DetachCurrentThread(); } 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