Skip to content

Commit 6b3da2d

Browse files
committed
Merge branch 'android'
2 parents d1addec + 9dceb58 commit 6b3da2d

9 files changed

Lines changed: 469 additions & 1 deletion

File tree

Android.mk

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
LOCAL_PATH := $(call my-dir)
2+
include $(CLEAR_VARS)
3+
4+
LOCAL_MODULE := https
5+
LOCAL_MODULE_FILENAME := https
6+
7+
LOCAL_CFLAGS := -DNOMINMAX
8+
LOCAL_CPPFLAGS := -std=c++11
9+
10+
LOCAL_ARM_NEON := true
11+
12+
LOCAL_C_INCLUDES := \
13+
${LOCAL_PATH}/src \
14+
${LOCAL_PATH}/src/android \
15+
${LOCAL_PATH}/src/android/ndk-build
16+
17+
LOCAL_SRC_FILES := \
18+
src/lua/main.cpp \
19+
src/common/HTTPRequest.cpp \
20+
src/common/HTTPSClient.cpp \
21+
src/common/PlaintextConnection.cpp \
22+
src/android/AndroidClient.cpp
23+
24+
LOCAL_SHARED_LIBRARIES := liblove
25+
26+
include $(BUILD_SHARED_LIBRARY)

java.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
src/android/java

src/CMakeLists.txt

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,26 +45,41 @@ add_library (https-nsurl STATIC EXCLUDE_FROM_ALL
4545
macos/NSURLClient.mm
4646
)
4747

48+
add_library (https-android STATIC EXCLUDE_FROM_ALL
49+
android/AndroidClient.cpp
50+
)
51+
4852
### Flags
4953
if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
5054
option (USE_CURL_BACKEND "Use the libcurl backend" ON)
5155
option (USE_OPENSSL_BACKEND "Use the openssl backend" ON)
5256
option (USE_SCHANNEL_BACKEND "Use the schannel backend (windows-only)" OFF)
5357
option (USE_NSURL_BACKEND "Use the NSUrl backend (macos-only)" OFF)
58+
option (USE_ANDROID_BACKEND "Use the Android Java backend (Android-only)" OFF)
5459

5560
option (USE_WINSOCK "Use winsock instead of BSD sockets (windows-only)" OFF)
56-
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
61+
elseif (WIN32)
5762
option (USE_CURL_BACKEND "Use the libcurl backend" OFF)
5863
option (USE_OPENSSL_BACKEND "Use the openssl backend" OFF)
5964
option (USE_SCHANNEL_BACKEND "Use the schannel backend (windows-only)" ON)
6065
option (USE_NSURL_BACKEND "Use the NSUrl backend (macos-only)" OFF)
66+
option (USE_ANDROID_BACKEND "Use the Android Java backend (Android-only)" OFF)
6167

6268
option (USE_WINSOCK "Use winsock instead of BSD sockets (windows-only)" ON)
6369
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
6470
option (USE_CURL_BACKEND "Use the libcurl backend" OFF)
6571
option (USE_OPENSSL_BACKEND "Use the openssl backend" OFF)
6672
option (USE_SCHANNEL_BACKEND "Use the schannel backend (windows-only)" OFF)
6773
option (USE_NSURL_BACKEND "Use the NSUrl backend (macos-only)" ON)
74+
option (USE_ANDROID_BACKEND "Use the Android Java backend (Android-only)" OFF)
75+
76+
option (USE_WINSOCK "Use winsock instead of BSD sockets (windows-only)" OFF)
77+
elseif (ANDROID)
78+
option (USE_CURL_BACKEND "Use the libcurl backend" OFF)
79+
option (USE_OPENSSL_BACKEND "Use the openssl backend" OFF)
80+
option (USE_SCHANNEL_BACKEND "Use the schannel backend (windows-only)" OFF)
81+
option (USE_NSURL_BACKEND "Use the NSUrl backend (macos-only)" OFF)
82+
option (USE_ANDROID_BACKEND "Use the Android Java backend (Android-only)" ON)
6883

6984
option (USE_WINSOCK "Use winsock instead of BSD sockets (windows-only)" OFF)
7085
endif ()
@@ -106,6 +121,11 @@ if (USE_NSURL_BACKEND)
106121
target_link_libraries (https https-nsurl)
107122
endif ()
108123

124+
if (USE_ANDROID_BACKEND)
125+
target_link_libraries (https https-android)
126+
message(STATUS "Ensure to add the Java files to your project too!")
127+
endif ()
128+
109129
### Generate config.h
110130
configure_file (
111131
common/config.h.in

src/android/AndroidClient.cpp

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
#include "AndroidClient.h"
2+
3+
#ifdef USE_ANDROID_BACKEND
4+
5+
#include <sstream>
6+
#include <type_traits>
7+
8+
#include <dlfcn.h>
9+
10+
static std::string replace(const std::string &str, const std::string &from, const std::string &to)
11+
{
12+
std::stringstream ss;
13+
size_t oldpos = 0;
14+
15+
while (true)
16+
{
17+
size_t pos = str.find(from, oldpos);
18+
19+
if (pos == std::string::npos)
20+
{
21+
ss << str.substr(oldpos);
22+
break;
23+
}
24+
25+
ss << str.substr(oldpos, pos - oldpos) << to;
26+
oldpos = pos + from.length();
27+
}
28+
29+
return ss.str();
30+
}
31+
32+
static jstring newStringUTF(JNIEnv *env, const std::string &str)
33+
{
34+
// We want std::string that contains null byte, hence length of 1.
35+
static std::string null("", 1);
36+
37+
std::string newStr = replace(str, null, "\xC0\x80");
38+
jstring jstr = env->NewStringUTF(newStr.c_str());
39+
return jstr;
40+
}
41+
42+
static std::string getStringUTF(JNIEnv *env, jstring str)
43+
{
44+
// We want std::string that contains null byte, hence length of 1.
45+
static std::string null("", 1);
46+
47+
const char *c = env->GetStringUTFChars(str, nullptr);
48+
std::string result = replace(c, "\xC0\x80", null);
49+
50+
env->ReleaseStringUTFChars(str, c);
51+
return result;
52+
}
53+
54+
AndroidClient::AndroidClient()
55+
: HTTPSClient()
56+
, SDL_AndroidGetJNIEnv(nullptr)
57+
{
58+
// Look for SDL_AndroidGetJNIEnv
59+
SDL_AndroidGetJNIEnv = (decltype(SDL_AndroidGetJNIEnv)) dlsym(RTLD_DEFAULT, "SDL_AndroidGetJNIEnv");
60+
// Look for SDL_AndroidGetActivity
61+
SDL_AndroidGetActivity = (decltype(SDL_AndroidGetActivity)) dlsym(RTLD_DEFAULT, "SDL_AndroidGetActivity");
62+
}
63+
64+
bool AndroidClient::valid() const
65+
{
66+
if (SDL_AndroidGetJNIEnv && SDL_AndroidGetActivity)
67+
{
68+
JNIEnv *env = SDL_AndroidGetJNIEnv();
69+
70+
if (env)
71+
{
72+
jclass httpsClass = getHTTPSClass();
73+
if (env->ExceptionCheck())
74+
{
75+
env->ExceptionClear();
76+
return false;
77+
}
78+
79+
env->DeleteLocalRef(httpsClass);
80+
return true;
81+
}
82+
}
83+
84+
return false;
85+
}
86+
87+
HTTPSClient::Reply AndroidClient::request(const HTTPSClient::Request &req)
88+
{
89+
JNIEnv *env = SDL_AndroidGetJNIEnv();
90+
jclass httpsClass = getHTTPSClass();
91+
92+
if (httpsClass == nullptr)
93+
{
94+
env->ExceptionClear();
95+
throw std::runtime_error("Could not find class 'org.love2d.luahttps.LuaHTTPS'");
96+
}
97+
98+
jmethodID constructor = env->GetMethodID(httpsClass, "<init>", "()V");
99+
jmethodID setURL = env->GetMethodID(httpsClass, "setUrl", "(Ljava/lang/String;)V");
100+
jmethodID request = env->GetMethodID(httpsClass, "request", "()Z");
101+
jmethodID getInterleavedHeaders = env->GetMethodID(httpsClass, "getInterleavedHeaders", "()[Ljava/lang/String;");
102+
jmethodID getResponse = env->GetMethodID(httpsClass, "getResponse", "()[B");
103+
jmethodID getResponseCode = env->GetMethodID(httpsClass, "getResponseCode", "()I");
104+
105+
jobject httpsObject = env->NewObject(httpsClass, constructor);
106+
107+
// Set URL
108+
jstring url = env->NewStringUTF(req.url.c_str());
109+
env->CallVoidMethod(httpsObject, setURL, url);
110+
env->DeleteLocalRef(url);
111+
112+
// Set post data
113+
if (req.method == Request::POST)
114+
{
115+
jmethodID setPostData = env->GetMethodID(httpsClass, "setPostData", "([B)V");
116+
jbyteArray byteArray = env->NewByteArray((jsize) req.postdata.length());
117+
jbyte *byteArrayData = env->GetByteArrayElements(byteArray, nullptr);
118+
119+
memcpy(byteArrayData, req.postdata.data(), req.postdata.length());
120+
env->ReleaseByteArrayElements(byteArray, byteArrayData, 0);
121+
122+
env->CallVoidMethod(httpsObject, setPostData, byteArray);
123+
env->DeleteLocalRef(byteArray);
124+
}
125+
126+
// Set headers
127+
if (!req.headers.empty())
128+
{
129+
jmethodID addHeader = env->GetMethodID(httpsClass, "addHeader", "(Ljava/lang/String;Ljava/lang/String;)V");
130+
131+
for (auto &header : req.headers)
132+
{
133+
jstring headerKey = newStringUTF(env, header.first);
134+
jstring headerValue = newStringUTF(env, header.second);
135+
136+
env->CallVoidMethod(httpsObject, addHeader, headerKey, headerValue);
137+
env->DeleteLocalRef(headerKey);
138+
env->DeleteLocalRef(headerValue);
139+
}
140+
}
141+
142+
// Do request
143+
HTTPSClient::Reply response;
144+
jboolean status = env->CallBooleanMethod(httpsObject, request);
145+
146+
// Get response
147+
response.responseCode = env->CallIntMethod(httpsObject, getResponseCode);
148+
149+
if (status)
150+
{
151+
// Get headers
152+
jobjectArray interleavedHeaders = (jobjectArray) env->CallObjectMethod(httpsObject, getInterleavedHeaders);
153+
int len = env->GetArrayLength(interleavedHeaders);
154+
155+
for (int i = 0; i < len; i += 2)
156+
{
157+
jstring key = (jstring) env->GetObjectArrayElement(interleavedHeaders, i);
158+
jstring value = (jstring) env->GetObjectArrayElement(interleavedHeaders, i + 1);
159+
160+
response.headers[getStringUTF(env, key)] = getStringUTF(env, value);
161+
162+
env->DeleteLocalRef(key);
163+
env->DeleteLocalRef(value);
164+
}
165+
166+
env->DeleteLocalRef(interleavedHeaders);
167+
168+
// Get response data
169+
jbyteArray responseData = (jbyteArray) env->CallObjectMethod(httpsObject, getResponse);
170+
171+
if (responseData)
172+
{
173+
int len = env->GetArrayLength(responseData);
174+
jbyte *responseByte = env->GetByteArrayElements(responseData, nullptr);
175+
176+
response.body = std::string((char *) responseByte, len);
177+
178+
env->DeleteLocalRef(responseData);
179+
}
180+
}
181+
182+
return response;
183+
}
184+
185+
jclass AndroidClient::getHTTPSClass() const
186+
{
187+
JNIEnv *env = SDL_AndroidGetJNIEnv();
188+
189+
jclass classLoaderClass = env->FindClass("java/lang/ClassLoader");
190+
jmethodID loadClass = env->GetMethodID(classLoaderClass, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
191+
192+
jobject activity = SDL_AndroidGetActivity();
193+
194+
if (activity == nullptr)
195+
return nullptr;
196+
197+
jclass gameActivity = env->GetObjectClass(activity);
198+
jmethodID getLoader = env->GetMethodID(gameActivity, "getClassLoader", "()Ljava/lang/ClassLoader;");
199+
jobject classLoader = env->CallObjectMethod(activity, getLoader);
200+
201+
jstring httpsClassName = env->NewStringUTF("org.love2d.luahttps.LuaHTTPS");
202+
jclass httpsClass = (jclass) env->CallObjectMethod(classLoader, loadClass, httpsClassName);
203+
204+
env->DeleteLocalRef(gameActivity);
205+
env->DeleteLocalRef(httpsClassName);
206+
env->DeleteLocalRef(activity);
207+
env->DeleteLocalRef(classLoaderClass);
208+
209+
return httpsClass;
210+
}
211+
212+
#endif

src/android/AndroidClient.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#pragma once
2+
3+
#include "common/config.h"
4+
5+
#ifdef USE_ANDROID_BACKEND
6+
7+
#include <jni.h>
8+
9+
#include "common/HTTPSClient.h"
10+
11+
class AndroidClient: public HTTPSClient
12+
{
13+
public:
14+
AndroidClient();
15+
16+
bool valid() const override;
17+
HTTPSClient::Reply request(const HTTPSClient::Request &req) override;
18+
19+
private:
20+
JNIEnv *(*SDL_AndroidGetJNIEnv)();
21+
jobject (*SDL_AndroidGetActivity)();
22+
23+
jclass getHTTPSClass() const;
24+
};
25+
26+
#endif

0 commit comments

Comments
 (0)