1 // This is generic code that is included in all Android apps that use the
2 // Native framework by Henrik Rydgard (https://github.com/hrydgard/native).
3 
4 // It calls a set of methods defined in NativeApp.h. These should be implemented
5 // by your game or app.
6 
7 #include <cstdlib>
8 #include <cstdint>
9 
10 #include <sstream>
11 #include <queue>
12 #include <mutex>
13 #include <thread>
14 #include <atomic>
15 
16 #include <android/log.h>
17 
18 #ifndef _MSC_VER
19 #include <jni.h>
20 #include <android/native_window_jni.h>
21 #include <android/log.h>
22 
23 #elif !defined(JNIEXPORT)
24 // Just for better highlighting in MSVC if opening this file.
25 // Not having types makes it get confused and say everything is wrong.
26 struct JavaVM;
27 typedef void *jmethodID;
28 typedef void *jfieldID;
29 
30 typedef uint8_t jboolean;
31 typedef int8_t jbyte;
32 typedef int16_t jshort;
33 typedef int32_t jint;
34 typedef int64_t jlong;
35 typedef jint jsize;
36 typedef float jfloat;
37 typedef double jdouble;
38 
39 class _jobject {};
40 class _jclass : public _jobject {};
41 typedef _jobject *jobject;
42 typedef _jclass *jclass;
43 typedef jobject jstring;
44 typedef jobject jbyteArray;
45 
46 struct JNIEnv {};
47 
48 #define JNIEXPORT
49 #define JNICALL
50 // Just a random value to make MSVC highlighting happy.
51 #define JNI_VERSION_1_6 16
52 #endif
53 
54 #include "Common/Net/Resolve.h"
55 #include "android/jni/AndroidAudio.h"
56 #include "Common/GPU/OpenGL/GLCommon.h"
57 #include "Common/GPU/OpenGL/GLFeatures.h"
58 
59 #include "Common/System/Display.h"
60 #include "Common/System/NativeApp.h"
61 #include "Common/System/System.h"
62 #include "Common/Thread/ThreadUtil.h"
63 #include "Common/File/Path.h"
64 #include "Common/File/DirListing.h"
65 #include "Common/File/VFS/VFS.h"
66 #include "Common/File/VFS/AssetReader.h"
67 #include "Common/File/AndroidStorage.h"
68 #include "Common/Input/InputState.h"
69 #include "Common/Input/KeyCodes.h"
70 #include "Common/Profiler/Profiler.h"
71 #include "Common/Math/math_util.h"
72 #include "Common/Data/Text/Parsers.h"
73 
74 #include "Common/Log.h"
75 #include "Common/GraphicsContext.h"
76 #include "Common/StringUtils.h"
77 #include "Common/TimeUtil.h"
78 
79 #include "AndroidGraphicsContext.h"
80 #include "AndroidVulkanContext.h"
81 #include "AndroidEGLContext.h"
82 #include "AndroidJavaGLContext.h"
83 
84 #include "Core/Config.h"
85 #include "Core/ConfigValues.h"
86 #include "Core/Loaders.h"
87 #include "Core/FileLoaders/LocalFileLoader.h"
88 #include "Core/System.h"
89 #include "Core/HLE/sceUsbCam.h"
90 #include "Core/HLE/sceUsbGps.h"
91 #include "Core/Host.h"
92 #include "Common/CPUDetect.h"
93 #include "Common/Log.h"
94 #include "UI/GameInfoCache.h"
95 
96 #include "app-android.h"
97 
98 bool useCPUThread = true;
99 
100 enum class EmuThreadState {
101 	DISABLED,
102 	START_REQUESTED,
103 	RUNNING,
104 	QUIT_REQUESTED,
105 	STOPPED,
106 };
107 
108 static std::thread emuThread;
109 static std::atomic<int> emuThreadState((int)EmuThreadState::DISABLED);
110 
111 void UpdateRunLoopAndroid(JNIEnv *env);
112 
113 AndroidAudioState *g_audioState;
114 
115 struct FrameCommand {
FrameCommandFrameCommand116 	FrameCommand() {}
FrameCommandFrameCommand117 	FrameCommand(std::string cmd, std::string prm) : command(cmd), params(prm) {}
118 
119 	std::string command;
120 	std::string params;
121 };
122 
123 static std::mutex frameCommandLock;
124 static std::queue<FrameCommand> frameCommands;
125 
126 std::string systemName;
127 std::string langRegion;
128 std::string mogaVersion;
129 std::string boardName;
130 
131 std::string g_externalDir;  // Original external dir (root of Android storage).
132 std::string g_extFilesDir;  // App private external dir.
133 
134 std::vector<std::string> g_additionalStorageDirs;
135 
136 static float left_joystick_x_async;
137 static float left_joystick_y_async;
138 static float right_joystick_x_async;
139 static float right_joystick_y_async;
140 static float hat_joystick_x_async;
141 static float hat_joystick_y_async;
142 
143 static int optimalFramesPerBuffer = 0;
144 static int optimalSampleRate = 0;
145 static int sampleRate = 0;
146 static int framesPerBuffer = 0;
147 static int androidVersion;
148 static int deviceType;
149 
150 // Should only be used for display detection during startup (for config defaults etc)
151 // This is the ACTUAL display size, not the hardware scaled display size.
152 // Exposed so it can be displayed on the touchscreen test.
153 int display_xres;
154 int display_yres;
155 static int display_dpi_x;
156 static int display_dpi_y;
157 static int backbuffer_format;	// Android PixelFormat enum
158 
159 static int desiredBackbufferSizeX;
160 static int desiredBackbufferSizeY;
161 
162 // Cache the class loader so we can use it from native threads. Required for TextAndroid.
163 JavaVM* gJvm = nullptr;
164 static jobject gClassLoader;
165 static jmethodID gFindClassMethod;
166 
167 static float g_safeInsetLeft = 0.0;
168 static float g_safeInsetRight = 0.0;
169 static float g_safeInsetTop = 0.0;
170 static float g_safeInsetBottom = 0.0;
171 
172 static jmethodID postCommand;
173 static jmethodID getDebugString;
174 
175 static jobject nativeActivity;
176 static volatile bool exitRenderLoop;
177 static bool renderLoopRunning;
178 static int inputBoxSequence = 1;
179 std::map<int, std::function<void(bool, const std::string &)>> inputBoxCallbacks;
180 
181 static float dp_xscale = 1.0f;
182 static float dp_yscale = 1.0f;
183 
184 static bool renderer_inited = false;
185 static bool sustainedPerfSupported = false;
186 static std::mutex renderLock;
187 
188 // See NativeQueryConfig("androidJavaGL") to change this value.
189 static bool javaGL = true;
190 
191 static std::string library_path;
192 static std::map<SystemPermission, PermissionStatus> permissions;
193 
194 AndroidGraphicsContext *graphicsContext;
195 
196 #ifndef LOG_APP_NAME
197 #define LOG_APP_NAME "PPSSPP"
198 #endif
199 
200 #ifdef _DEBUG
201 #define DLOG(...)    __android_log_print(ANDROID_LOG_INFO, LOG_APP_NAME, __VA_ARGS__);
202 #else
203 #define DLOG(...)
204 #endif
205 
206 #define ILOG(...)    __android_log_print(ANDROID_LOG_INFO, LOG_APP_NAME, __VA_ARGS__);
207 #define WLOG(...)    __android_log_print(ANDROID_LOG_WARN, LOG_APP_NAME, __VA_ARGS__);
208 #define ELOG(...)    __android_log_print(ANDROID_LOG_ERROR, LOG_APP_NAME, __VA_ARGS__);
209 #define FLOG(...)    __android_log_print(ANDROID_LOG_FATAL, LOG_APP_NAME, __VA_ARGS__);
210 
211 #define MessageBox(a, b, c, d) __android_log_print(ANDROID_LOG_INFO, APP_NAME, "%s %s", (b), (c));
212 
Log(const LogMessage & message)213 void AndroidLogger::Log(const LogMessage &message) {
214 	// Log with simplified headers as Android already provides timestamp etc.
215 	switch (message.level) {
216 	case LogTypes::LVERBOSE:
217 	case LogTypes::LDEBUG:
218 	case LogTypes::LINFO:
219 		ILOG("[%s] %s", message.log, message.msg.c_str());
220 		break;
221 	case LogTypes::LERROR:
222 		ELOG("[%s] %s", message.log, message.msg.c_str());
223 		break;
224 	case LogTypes::LWARNING:
225 		WLOG("[%s] %s", message.log, message.msg.c_str());
226 		break;
227 	case LogTypes::LNOTICE:
228 	default:
229 		ILOG("[%s] !!! %s", message.log, message.msg.c_str());
230 		break;
231 	}
232 }
233 
getEnv()234 JNIEnv* getEnv() {
235 	JNIEnv *env;
236 	int status = gJvm->GetEnv((void**)&env, JNI_VERSION_1_6);
237 	if(status < 0) {
238 		status = gJvm->AttachCurrentThread(&env, NULL);
239 		if(status < 0) {
240 			return nullptr;
241 		}
242 	}
243 	return env;
244 }
245 
findClass(const char * name)246 jclass findClass(const char* name) {
247 	return static_cast<jclass>(getEnv()->CallObjectMethod(gClassLoader, gFindClassMethod, getEnv()->NewStringUTF(name)));
248 }
249 
JNI_OnLoad(JavaVM * pjvm,void * reserved)250 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *pjvm, void *reserved) {
251 	INFO_LOG(SYSTEM, "JNI_OnLoad");
252 	gJvm = pjvm;  // cache the JavaVM pointer
253 	auto env = getEnv();
254 	//replace with one of your classes in the line below
255 	auto randomClass = env->FindClass("org/ppsspp/ppsspp/NativeActivity");
256 	jclass classClass = env->GetObjectClass(randomClass);
257 	auto classLoaderClass = env->FindClass("java/lang/ClassLoader");
258 	auto getClassLoaderMethod = env->GetMethodID(classClass, "getClassLoader",
259 												 "()Ljava/lang/ClassLoader;");
260 	gClassLoader = env->NewGlobalRef(env->CallObjectMethod(randomClass, getClassLoaderMethod));
261 	gFindClassMethod = env->GetMethodID(classLoaderClass, "findClass",
262 										"(Ljava/lang/String;)Ljava/lang/Class;");
263 	return JNI_VERSION_1_6;
264 }
265 
EmuThreadFunc()266 static void EmuThreadFunc() {
267 	JNIEnv *env;
268 	gJvm->AttachCurrentThread(&env, nullptr);
269 
270 	SetCurrentThreadName("Emu");
271 	INFO_LOG(SYSTEM, "Entering emu thread");
272 
273 	// Wait for render loop to get started.
274 	if (!graphicsContext || !graphicsContext->Initialized()) {
275 		INFO_LOG(SYSTEM, "Runloop: Waiting for displayInit...");
276 		while (!graphicsContext || !graphicsContext->Initialized()) {
277 			sleep_ms(20);
278 		}
279 	} else {
280 		INFO_LOG(SYSTEM, "Runloop: Graphics context available! %p", graphicsContext);
281 	}
282 	NativeInitGraphics(graphicsContext);
283 
284 	INFO_LOG(SYSTEM, "Graphics initialized. Entering loop.");
285 
286 	// There's no real requirement that NativeInit happen on this thread.
287 	// We just call the update/render loop here.
288 	emuThreadState = (int)EmuThreadState::RUNNING;
289 	while (emuThreadState != (int)EmuThreadState::QUIT_REQUESTED) {
290 		UpdateRunLoopAndroid(env);
291 	}
292 	INFO_LOG(SYSTEM, "QUIT_REQUESTED found, left loop. Setting state to STOPPED.");
293 	emuThreadState = (int)EmuThreadState::STOPPED;
294 
295 	NativeShutdownGraphics();
296 
297 	// Also ask the main thread to stop, so it doesn't hang waiting for a new frame.
298 	graphicsContext->StopThread();
299 
300 	gJvm->DetachCurrentThread();
301 	INFO_LOG(SYSTEM, "Leaving emu thread");
302 }
303 
EmuThreadStart()304 static void EmuThreadStart() {
305 	INFO_LOG(SYSTEM, "EmuThreadStart");
306 	emuThreadState = (int)EmuThreadState::START_REQUESTED;
307 	emuThread = std::thread(&EmuThreadFunc);
308 }
309 
310 // Call EmuThreadStop first, then keep running the GPU (or eat commands)
311 // as long as emuThreadState isn't STOPPED and/or there are still things queued up.
312 // Only after that, call EmuThreadJoin.
EmuThreadStop(const char * caller)313 static void EmuThreadStop(const char *caller) {
314 	INFO_LOG(SYSTEM, "EmuThreadStop - stopping (%s)...", caller);
315 	emuThreadState = (int)EmuThreadState::QUIT_REQUESTED;
316 }
317 
EmuThreadJoin()318 static void EmuThreadJoin() {
319 	emuThread.join();
320 	emuThread = std::thread();
321 	INFO_LOG(SYSTEM, "EmuThreadJoin - joined");
322 }
323 
324 static void ProcessFrameCommands(JNIEnv *env);
325 
PushCommand(std::string cmd,std::string param)326 void PushCommand(std::string cmd, std::string param) {
327 	std::lock_guard<std::mutex> guard(frameCommandLock);
328 	frameCommands.push(FrameCommand(cmd, param));
329 }
330 
331 // Android implementation of callbacks to the Java part of the app
SystemToast(const char * text)332 void SystemToast(const char *text) {
333 	PushCommand("toast", text);
334 }
335 
ShowKeyboard()336 void ShowKeyboard() {
337 	PushCommand("showKeyboard", "");
338 }
339 
Vibrate(int length_ms)340 void Vibrate(int length_ms) {
341 	char temp[32];
342 	sprintf(temp, "%i", length_ms);
343 	PushCommand("vibrate", temp);
344 }
345 
OpenDirectory(const char * path)346 void OpenDirectory(const char *path) {
347 	// Unsupported
348 }
349 
LaunchBrowser(const char * url)350 void LaunchBrowser(const char *url) {
351 	PushCommand("launchBrowser", url);
352 }
353 
LaunchMarket(const char * url)354 void LaunchMarket(const char *url) {
355 	PushCommand("launchMarket", url);
356 }
357 
LaunchEmail(const char * email_address)358 void LaunchEmail(const char *email_address) {
359 	PushCommand("launchEmail", email_address);
360 }
361 
System_SendMessage(const char * command,const char * parameter)362 void System_SendMessage(const char *command, const char *parameter) {
363 	PushCommand(command, parameter);
364 }
365 
System_GetProperty(SystemProperty prop)366 std::string System_GetProperty(SystemProperty prop) {
367 	switch (prop) {
368 	case SYSPROP_NAME:
369 		return systemName;
370 	case SYSPROP_LANGREGION:	// "en_US"
371 		return langRegion;
372 	case SYSPROP_MOGA_VERSION:
373 		return mogaVersion;
374 	case SYSPROP_BOARDNAME:
375 		return boardName;
376 	default:
377 		return "";
378 	}
379 }
380 
System_GetPropertyStringVec(SystemProperty prop)381 std::vector<std::string> System_GetPropertyStringVec(SystemProperty prop) {
382 	switch (prop) {
383 	case SYSPROP_ADDITIONAL_STORAGE_DIRS:
384 		return g_additionalStorageDirs;
385 
386 	case SYSPROP_TEMP_DIRS:
387 	default:
388 		return std::vector<std::string>();
389 	}
390 }
391 
System_GetPropertyInt(SystemProperty prop)392 int System_GetPropertyInt(SystemProperty prop) {
393 	switch (prop) {
394 	case SYSPROP_SYSTEMVERSION:
395 		return androidVersion;
396 	case SYSPROP_DEVICE_TYPE:
397 		return deviceType;
398 	case SYSPROP_DISPLAY_XRES:
399 		return display_xres;
400 	case SYSPROP_DISPLAY_YRES:
401 		return display_yres;
402 	case SYSPROP_AUDIO_SAMPLE_RATE:
403 		return sampleRate;
404 	case SYSPROP_AUDIO_FRAMES_PER_BUFFER:
405 		return framesPerBuffer;
406 	case SYSPROP_AUDIO_OPTIMAL_SAMPLE_RATE:
407 		return optimalSampleRate;
408 	case SYSPROP_AUDIO_OPTIMAL_FRAMES_PER_BUFFER:
409 		return optimalFramesPerBuffer;
410 	default:
411 		return -1;
412 	}
413 }
414 
System_GetPropertyFloat(SystemProperty prop)415 float System_GetPropertyFloat(SystemProperty prop) {
416 	switch (prop) {
417 	case SYSPROP_DISPLAY_REFRESH_RATE:
418 		return display_hz;
419 	case SYSPROP_DISPLAY_SAFE_INSET_LEFT:
420 		return g_safeInsetLeft;
421 	case SYSPROP_DISPLAY_SAFE_INSET_RIGHT:
422 		return g_safeInsetRight;
423 	case SYSPROP_DISPLAY_SAFE_INSET_TOP:
424 		return g_safeInsetTop;
425 	case SYSPROP_DISPLAY_SAFE_INSET_BOTTOM:
426 		return g_safeInsetBottom;
427 	default:
428 		return -1;
429 	}
430 }
431 
System_GetPropertyBool(SystemProperty prop)432 bool System_GetPropertyBool(SystemProperty prop) {
433 	switch (prop) {
434 	case SYSPROP_SUPPORTS_PERMISSIONS:
435 		if (androidVersion < 23) {
436 			// 6.0 Marshmallow introduced run time permissions.
437 			return false;
438 		} else {
439 			// It gets a bit complicated here. If scoped storage enforcement is on,
440 			// we also don't need to request permissions. We'll have the access we request
441 			// on a per-folder basis.
442 			return !System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE);
443 		}
444 	case SYSPROP_SUPPORTS_SUSTAINED_PERF_MODE:
445 		return sustainedPerfSupported;  // 7.0 introduced sustained performance mode as an optional feature.
446 	case SYSPROP_HAS_ADDITIONAL_STORAGE:
447 		return !g_additionalStorageDirs.empty();
448 	case SYSPROP_HAS_BACK_BUTTON:
449 		return true;
450 	case SYSPROP_HAS_IMAGE_BROWSER:
451 		return true;
452 	case SYSPROP_HAS_FILE_BROWSER:
453 		// It's only really needed with scoped storage, but why not make it available
454 		// as far back as possible - works just fine.
455 		return androidVersion >= 19;  // when ACTION_OPEN_DOCUMENT was added
456 	case SYSPROP_HAS_FOLDER_BROWSER:
457 		// Uses OPEN_DOCUMENT_TREE to let you select a folder.
458 		// Doesn't actually mean it's usable though, in many early versions of Android
459 		// this dialog is complete garbage and only lets you select subfolders of the Downloads folder.
460 		return androidVersion >= 21;  // when ACTION_OPEN_DOCUMENT_TREE was added
461 	case SYSPROP_APP_GOLD:
462 #ifdef GOLD
463 		return true;
464 #else
465 		return false;
466 #endif
467 	case SYSPROP_CAN_JIT:
468 		return true;
469 	case SYSPROP_ANDROID_SCOPED_STORAGE:
470 		// We turn this on for Android 30+ (11) now that when we target Android 11+.
471 		// Along with adding:
472 		//   android:preserveLegacyExternalStorage="true"
473 		// To the already requested:
474 		//   android:requestLegacyExternalStorage="true"
475 		//
476 		// This will cause Android 11+ to still behave like Android 10 until the app
477 		// is manually uninstalled. We can detect this state with
478 		// Android_IsExternalStoragePreservedLegacy(), but most of the app will just see
479 		// that scoped storage enforcement is disabled in this case.
480 		if (androidVersion >= 30) {
481 			// Here we do a check to see if we ended up in the preserveLegacyExternalStorage path.
482 			// That won't last if the user uninstalls/reinstalls though, but would preserve the user
483 			// experience for simple upgrades so maybe let's support it.
484 			return !Android_IsExternalStoragePreservedLegacy();
485 		} else {
486 			return false;
487 		}
488 	default:
489 		return false;
490 	}
491 }
492 
Android_GetInputDeviceDebugString()493 std::string Android_GetInputDeviceDebugString() {
494 	if (!nativeActivity) {
495 		return "(N/A)";
496 	}
497 	auto env = getEnv();
498 	jstring param = env->NewStringUTF("InputDevice");
499 
500 	jstring str = (jstring)env->CallObjectMethod(nativeActivity, getDebugString, param);
501 	if (!str) {
502 		return "(N/A)";
503 	}
504 
505 	const char *charArray = env->GetStringUTFChars(str, 0);
506 	std::string retVal = charArray;
507 	env->DeleteLocalRef(str);
508 	return retVal;
509 }
510 
GetJavaString(JNIEnv * env,jstring jstr)511 std::string GetJavaString(JNIEnv *env, jstring jstr) {
512 	if (!jstr)
513 		return "";
514 	const char *str = env->GetStringUTFChars(jstr, 0);
515 	std::string cpp_string = std::string(str);
516 	env->ReleaseStringUTFChars(jstr, str);
517 	return cpp_string;
518 }
519 
Java_org_ppsspp_ppsspp_NativeActivity_registerCallbacks(JNIEnv * env,jobject obj)520 extern "C" void Java_org_ppsspp_ppsspp_NativeActivity_registerCallbacks(JNIEnv *env, jobject obj) {
521 	nativeActivity = env->NewGlobalRef(obj);
522 	postCommand = env->GetMethodID(env->GetObjectClass(obj), "postCommand", "(Ljava/lang/String;Ljava/lang/String;)V");
523 	getDebugString = env->GetMethodID(env->GetObjectClass(obj), "getDebugString", "(Ljava/lang/String;)Ljava/lang/String;");
524 	_dbg_assert_(postCommand);
525 	_dbg_assert_(getDebugString);
526 
527 	Android_RegisterStorageCallbacks(env, obj);
528 	Android_StorageSetNativeActivity(nativeActivity);
529 }
530 
Java_org_ppsspp_ppsspp_NativeActivity_unregisterCallbacks(JNIEnv * env,jobject obj)531 extern "C" void Java_org_ppsspp_ppsspp_NativeActivity_unregisterCallbacks(JNIEnv *env, jobject obj) {
532 	Android_StorageSetNativeActivity(nullptr);
533 	env->DeleteGlobalRef(nativeActivity);
534 	nativeActivity = nullptr;
535 }
536 
537 // This is now only used as a trigger for GetAppInfo as a function to all before Init.
538 // On Android we don't use any of the values it returns.
Java_org_ppsspp_ppsspp_NativeApp_isLandscape(JNIEnv * env,jclass)539 extern "C" jboolean Java_org_ppsspp_ppsspp_NativeApp_isLandscape(JNIEnv *env, jclass) {
540 	std::string app_name, app_nice_name, version;
541 	bool landscape;
542 	NativeGetAppInfo(&app_name, &app_nice_name, &landscape, &version);
543 	return landscape;
544 }
545 
546 // Allow the app to intercept the back button.
Java_org_ppsspp_ppsspp_NativeApp_isAtTopLevel(JNIEnv * env,jclass)547 extern "C" jboolean Java_org_ppsspp_ppsspp_NativeApp_isAtTopLevel(JNIEnv *env, jclass) {
548 	return NativeIsAtTopLevel();
549 }
550 
Java_org_ppsspp_ppsspp_NativeApp_audioConfig(JNIEnv * env,jclass,jint optimalFPB,jint optimalSR)551 extern "C" void Java_org_ppsspp_ppsspp_NativeApp_audioConfig
552 	(JNIEnv *env, jclass, jint optimalFPB, jint optimalSR) {
553 	optimalFramesPerBuffer = optimalFPB;
554 	optimalSampleRate = optimalSR;
555 }
556 
Java_org_ppsspp_ppsspp_NativeApp_queryConfig(JNIEnv * env,jclass,jstring jquery)557 extern "C" jstring Java_org_ppsspp_ppsspp_NativeApp_queryConfig
558 	(JNIEnv *env, jclass, jstring jquery) {
559 	std::string query = GetJavaString(env, jquery);
560 	std::string result = NativeQueryConfig(query);
561 	jstring jresult = env->NewStringUTF(result.c_str());
562 	return jresult;
563 }
564 
parse_args(std::vector<std::string> & args,const std::string value)565 static void parse_args(std::vector<std::string> &args, const std::string value) {
566 	// Simple argument parser so we can take args from extra params.
567 	const char *p = value.c_str();
568 
569 	while (*p != '\0') {
570 		while (isspace(*p)) {
571 			p++;
572 		}
573 		if (*p == '\0') {
574 			break;
575 		}
576 
577 		bool done = false;
578 		bool quote = false;
579 		std::string arg;
580 
581 		while (!done) {
582 			size_t sz = strcspn(p, "\"\\ \r\n\t");
583 			arg += std::string(p, sz);
584 			p += sz;
585 
586 			switch (*p) {
587 			case '"':
588 				quote = !quote;
589 				p++;
590 				break;
591 
592 			case '\\':
593 				p++;
594 				arg += std::string(p, 1);
595 				p++;
596 				break;
597 
598 			case '\0':
599 				done = true;
600 				break;
601 
602 			default:
603 				// If it's not the above, it's whitespace.
604 				if (!quote) {
605 					done = true;
606 				} else {
607 					sz = strspn(p, " \r\n\t");
608 					arg += std::string(p, sz);
609 					p += sz;
610 				}
611 				break;
612 			}
613 		}
614 
615 		args.push_back(arg);
616 
617 		while (isspace(*p)) {
618 			p++;
619 		}
620 	}
621 }
622 
623 // Need to use raw Android logging before NativeInit.
624 #define EARLY_LOG(...)  __android_log_print(ANDROID_LOG_INFO, "PPSSPP", __VA_ARGS__)
625 
Java_org_ppsspp_ppsspp_NativeApp_init(JNIEnv * env,jclass,jstring jmodel,jint jdeviceType,jstring jlangRegion,jstring japkpath,jstring jdataDir,jstring jexternalStorageDir,jstring jexternalFilesDir,jstring jadditionalStorageDirs,jstring jlibraryDir,jstring jcacheDir,jstring jshortcutParam,jint jAndroidVersion,jstring jboard)626 extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init
627 	(JNIEnv *env, jclass, jstring jmodel, jint jdeviceType, jstring jlangRegion, jstring japkpath,
628 		jstring jdataDir, jstring jexternalStorageDir, jstring jexternalFilesDir, jstring jadditionalStorageDirs, jstring jlibraryDir, jstring jcacheDir, jstring jshortcutParam,
629 		jint jAndroidVersion, jstring jboard) {
630 	SetCurrentThreadName("androidInit");
631 
632 	// Makes sure we get early permission grants.
633 	ProcessFrameCommands(env);
634 
635 	EARLY_LOG("NativeApp.init() -- begin");
636 	PROFILE_INIT();
637 
638 	std::lock_guard<std::mutex> guard(renderLock);
639 	renderer_inited = false;
640 	androidVersion = jAndroidVersion;
641 	deviceType = jdeviceType;
642 
643 	left_joystick_x_async = 0;
644 	left_joystick_y_async = 0;
645 	right_joystick_x_async = 0;
646 	right_joystick_y_async = 0;
647 	hat_joystick_x_async = 0;
648 	hat_joystick_y_async = 0;
649 
650 	std::string apkPath = GetJavaString(env, japkpath);
651 	VFSRegister("", new ZipAssetReader(apkPath.c_str(), "assets/"));
652 
653 	systemName = GetJavaString(env, jmodel);
654 	langRegion = GetJavaString(env, jlangRegion);
655 
656 	EARLY_LOG("NativeApp.init(): device name: '%s'", systemName.c_str());
657 
658 	std::string externalStorageDir = GetJavaString(env, jexternalStorageDir);
659 	std::string additionalStorageDirsString = GetJavaString(env, jadditionalStorageDirs);
660 	std::string externalFilesDir = GetJavaString(env, jexternalFilesDir);
661 
662 	g_externalDir = externalStorageDir;
663 	g_extFilesDir = externalFilesDir;
664 
665 	if (!additionalStorageDirsString.empty()) {
666 		SplitString(additionalStorageDirsString, ':', g_additionalStorageDirs);
667 		for (auto &str : g_additionalStorageDirs) {
668 			EARLY_LOG("Additional storage: %s", str.c_str());
669 		}
670 	}
671 
672 	std::string user_data_path = GetJavaString(env, jdataDir);
673 	if (user_data_path.size() > 0)
674 		user_data_path += "/";
675 	library_path = GetJavaString(env, jlibraryDir) + "/";
676 	std::string shortcut_param = GetJavaString(env, jshortcutParam);
677 	std::string cacheDir = GetJavaString(env, jcacheDir);
678 	std::string buildBoard = GetJavaString(env, jboard);
679 	boardName = buildBoard;
680 	EARLY_LOG("NativeApp.init(): External storage path: %s", externalStorageDir.c_str());
681 	EARLY_LOG("NativeApp.init(): Launch shortcut parameter: %s", shortcut_param.c_str());
682 
683 	std::string app_name;
684 	std::string app_nice_name;
685 	std::string version;
686 	bool landscape;
687 
688 	// Unfortunately, on the Samsung Galaxy S7, this isn't in /proc/cpuinfo.
689 	// We also can't read it from __system_property_get.
690 	if (buildBoard == "universal8890") {
691 		cpu_info.sQuirks.bExynos8890DifferingCachelineSizes = true;
692 	}
693 
694 	NativeGetAppInfo(&app_name, &app_nice_name, &landscape, &version);
695 
696 	// If shortcut_param is not empty, pass it as additional arguments to the NativeInit() method.
697 	// NativeInit() is expected to treat extra argument as boot_filename, which in turn will start game immediately.
698 	// NOTE: Will only work if ppsspp started from Activity.onCreate(). Won't work if ppsspp app start from onResume().
699 
700 	std::vector<const char *> args;
701 	std::vector<std::string> temp;
702 	args.push_back(app_name.c_str());
703 	if (!shortcut_param.empty()) {
704 		parse_args(temp, shortcut_param);
705 		for (const auto &arg : temp) {
706 			args.push_back(arg.c_str());
707 		}
708 	}
709 
710 	NativeInit((int)args.size(), &args[0], user_data_path.c_str(), externalStorageDir.c_str(), cacheDir.c_str());
711 
712 	// No need to use EARLY_LOG anymore.
713 
714 retry:
715 	// Now that we've loaded config, set javaGL.
716 	javaGL = NativeQueryConfig("androidJavaGL") == "true";
717 
718 	switch (g_Config.iGPUBackend) {
719 	case (int)GPUBackend::OPENGL:
720 		useCPUThread = true;
721 		if (javaGL) {
722 			INFO_LOG(SYSTEM, "NativeApp.init() -- creating OpenGL context (JavaGL)");
723 			graphicsContext = new AndroidJavaEGLGraphicsContext();
724 		} else {
725 			graphicsContext = new AndroidEGLGraphicsContext();
726 		}
727 		break;
728 	case (int)GPUBackend::VULKAN:
729 	{
730 		INFO_LOG(SYSTEM, "NativeApp.init() -- creating Vulkan context");
731 		useCPUThread = false;  // The Vulkan render manager manages its own thread.
732 		// We create and destroy the Vulkan graphics context in the "EGL" thread.
733 		AndroidVulkanContext *ctx = new AndroidVulkanContext();
734 		if (!ctx->InitAPI()) {
735 			INFO_LOG(SYSTEM, "Failed to initialize Vulkan, switching to OpenGL");
736 			g_Config.iGPUBackend = (int)GPUBackend::OPENGL;
737 			SetGPUBackend(GPUBackend::OPENGL);
738 			goto retry;
739 		} else {
740 			graphicsContext = ctx;
741 		}
742 		break;
743 	}
744 	default:
745 		ERROR_LOG(SYSTEM, "NativeApp.init(): iGPUBackend %d not supported. Switching to OpenGL.", (int)g_Config.iGPUBackend);
746 		g_Config.iGPUBackend = (int)GPUBackend::OPENGL;
747 		goto retry;
748 	}
749 
750 	if (useCPUThread) {
751 		INFO_LOG(SYSTEM, "NativeApp.init() - launching emu thread");
752 		EmuThreadStart();
753 	}
754 }
755 
Java_org_ppsspp_ppsspp_NativeApp_audioInit(JNIEnv *,jclass)756 extern "C" void Java_org_ppsspp_ppsspp_NativeApp_audioInit(JNIEnv *, jclass) {
757 	sampleRate = optimalSampleRate;
758 	if (optimalSampleRate == 0) {
759 		sampleRate = 44100;
760 	}
761 	if (optimalFramesPerBuffer > 0) {
762 		framesPerBuffer = optimalFramesPerBuffer;
763 	} else {
764 		framesPerBuffer = 512;
765 	}
766 
767 	// Some devices have totally bonkers buffer sizes like 8192. They will have terrible latency anyway, so to avoid having to
768 	// create extra smart buffering code, we'll just let their regular mixer deal with it, missing the fast path (as if they had one...)
769 	if (framesPerBuffer > 512) {
770 		framesPerBuffer = 512;
771 		sampleRate = 44100;
772 	}
773 
774 	INFO_LOG(AUDIO, "NativeApp.audioInit() -- Using OpenSL audio! frames/buffer: %i	 optimal sr: %i	 actual sr: %i", optimalFramesPerBuffer, optimalSampleRate, sampleRate);
775 	if (!g_audioState) {
776 		g_audioState = AndroidAudio_Init(&NativeMix, framesPerBuffer, sampleRate);
777 	} else {
778 		ERROR_LOG(AUDIO, "Audio state already initialized");
779 	}
780 }
781 
Java_org_ppsspp_ppsspp_NativeApp_audioShutdown(JNIEnv *,jclass)782 extern "C" void Java_org_ppsspp_ppsspp_NativeApp_audioShutdown(JNIEnv *, jclass) {
783 	if (g_audioState) {
784 		AndroidAudio_Shutdown(g_audioState);
785 		g_audioState = nullptr;
786 	} else {
787 		ERROR_LOG(AUDIO, "Audio state already shutdown!");
788 	}
789 }
790 
Java_org_ppsspp_ppsspp_NativeApp_audioRecording_1SetSampleRate(JNIEnv *,jclass,jint sampleRate)791 extern "C" void Java_org_ppsspp_ppsspp_NativeApp_audioRecording_1SetSampleRate(JNIEnv *, jclass, jint sampleRate) {
792 	AndroidAudio_Recording_SetSampleRate(g_audioState, sampleRate);
793 }
794 
Java_org_ppsspp_ppsspp_NativeApp_audioRecording_1Start(JNIEnv *,jclass)795 extern "C" void Java_org_ppsspp_ppsspp_NativeApp_audioRecording_1Start(JNIEnv *, jclass) {
796 	AndroidAudio_Recording_Start(g_audioState);
797 }
798 
Java_org_ppsspp_ppsspp_NativeApp_audioRecording_1Stop(JNIEnv *,jclass)799 extern "C" void Java_org_ppsspp_ppsspp_NativeApp_audioRecording_1Stop(JNIEnv *, jclass) {
800 	AndroidAudio_Recording_Stop(g_audioState);
801 }
802 
audioRecording_Available()803 bool audioRecording_Available() {
804 	return true;
805 }
806 
audioRecording_State()807 bool audioRecording_State() {
808 	return AndroidAudio_Recording_State(g_audioState);
809 }
810 
Java_org_ppsspp_ppsspp_NativeApp_resume(JNIEnv *,jclass)811 extern "C" void Java_org_ppsspp_ppsspp_NativeApp_resume(JNIEnv *, jclass) {
812 	INFO_LOG(SYSTEM, "NativeApp.resume() - resuming audio");
813 	AndroidAudio_Resume(g_audioState);
814 
815 	NativeMessageReceived("app_resumed", "");
816 }
817 
Java_org_ppsspp_ppsspp_NativeApp_pause(JNIEnv *,jclass)818 extern "C" void Java_org_ppsspp_ppsspp_NativeApp_pause(JNIEnv *, jclass) {
819 	INFO_LOG(SYSTEM, "NativeApp.pause() - pausing audio");
820 	AndroidAudio_Pause(g_audioState);
821 }
822 
Java_org_ppsspp_ppsspp_NativeApp_shutdown(JNIEnv *,jclass)823 extern "C" void Java_org_ppsspp_ppsspp_NativeApp_shutdown(JNIEnv *, jclass) {
824 	if (renderer_inited && useCPUThread && graphicsContext) {
825 		// Only used in Java EGL path.
826 		EmuThreadStop("shutdown");
827 		INFO_LOG(SYSTEM, "BeginAndroidShutdown");
828 		graphicsContext->BeginAndroidShutdown();
829 		// Skipping GL calls, the old context is gone.
830 		while (graphicsContext->ThreadFrame()) {
831 			INFO_LOG(SYSTEM, "graphicsContext->ThreadFrame executed to clear buffers");
832 		}
833 		INFO_LOG(SYSTEM, "Joining emuthread");
834 		EmuThreadJoin();
835 		INFO_LOG(SYSTEM, "Joined emuthread");
836 
837 		graphicsContext->ThreadEnd();
838 		graphicsContext->ShutdownFromRenderThread();
839 		INFO_LOG(SYSTEM, "Graphics context now shut down from NativeApp_shutdown");
840 	}
841 
842 	INFO_LOG(SYSTEM, "NativeApp.shutdown() -- begin");
843 	if (renderer_inited) {
844 		INFO_LOG(G3D, "Shutting down renderer");
845 		// This will be from the wrong thread? :/
846 		graphicsContext->Shutdown();
847 		delete graphicsContext;
848 		graphicsContext = nullptr;
849 		renderer_inited = false;
850 	} else {
851 		INFO_LOG(G3D, "Not shutting down renderer - not initialized");
852 	}
853 
854 	{
855 		std::lock_guard<std::mutex> guard(renderLock);
856 		inputBoxCallbacks.clear();
857 		NativeShutdown();
858 		VFSShutdown();
859 	}
860 
861 	std::lock_guard<std::mutex> guard(frameCommandLock);
862 	while (frameCommands.size())
863 		frameCommands.pop();
864 	INFO_LOG(SYSTEM, "NativeApp.shutdown() -- end");
865 }
866 
867 // JavaEGL
Java_org_ppsspp_ppsspp_NativeRenderer_displayInit(JNIEnv * env,jobject obj)868 extern "C" bool Java_org_ppsspp_ppsspp_NativeRenderer_displayInit(JNIEnv * env, jobject obj) {
869 	// We should be running on the render thread here.
870 	std::string errorMessage;
871 	if (renderer_inited) {
872 		// Would be really nice if we could get something on the GL thread immediately when shutting down.
873 		INFO_LOG(G3D, "NativeApp.displayInit() restoring");
874 		if (useCPUThread) {
875 			EmuThreadStop("displayInit");
876 			graphicsContext->BeginAndroidShutdown();
877 			INFO_LOG(G3D, "BeginAndroidShutdown. Looping until emu thread done...");
878 			// Skipping GL calls here because the old context is lost.
879 			while (graphicsContext->ThreadFrame()) {
880 				continue;
881 			}
882 			INFO_LOG(G3D, "Joining emu thread");
883 			EmuThreadJoin();
884 		} else {
885 			NativeShutdownGraphics();
886 		}
887 		graphicsContext->ThreadEnd();
888 		graphicsContext->ShutdownFromRenderThread();
889 
890 		INFO_LOG(G3D, "Shut down both threads. Now let's bring it up again!");
891 
892 		if (!graphicsContext->InitFromRenderThread(nullptr, 0, 0, 0, 0)) {
893 			SystemToast("Graphics initialization failed. Quitting.");
894 			return false;
895 		}
896 
897 		graphicsContext->GetDrawContext()->SetErrorCallback([](const char *shortDesc, const char *details, void *userdata) {
898 			host->NotifyUserMessage(details, 5.0, 0xFFFFFFFF, "error_callback");
899 		}, nullptr);
900 
901 		if (useCPUThread) {
902 			EmuThreadStart();
903 		} else {
904 			if (!NativeInitGraphics(graphicsContext)) {
905 				// Gonna be in a weird state here, not good.
906 				SystemToast("Failed to initialize graphics.");
907 				return false;
908 			}
909 		}
910 
911 		graphicsContext->ThreadStart();
912 		INFO_LOG(G3D, "Restored.");
913 	} else {
914 		INFO_LOG(G3D, "NativeApp.displayInit() first time");
915 		if (!graphicsContext->InitFromRenderThread(nullptr, 0, 0, 0, 0)) {
916 			SystemToast("Graphics initialization failed. Quitting.");
917 			return false;
918 		}
919 
920 		graphicsContext->GetDrawContext()->SetErrorCallback([](const char *shortDesc, const char *details, void *userdata) {
921 			host->NotifyUserMessage(details, 5.0, 0xFFFFFFFF, "error_callback");
922 		}, nullptr);
923 
924 		graphicsContext->ThreadStart();
925 		renderer_inited = true;
926 	}
927 	NativeMessageReceived("recreateviews", "");
928 	return true;
929 }
930 
recalculateDpi()931 static void recalculateDpi() {
932 	g_dpi = display_dpi_x;
933 	g_dpi_scale_x = 240.0f / display_dpi_x;
934 	g_dpi_scale_y = 240.0f / display_dpi_y;
935 	g_dpi_scale_real_x = g_dpi_scale_x;
936 	g_dpi_scale_real_y = g_dpi_scale_y;
937 
938 	dp_xres = display_xres * g_dpi_scale_x;
939 	dp_yres = display_yres * g_dpi_scale_y;
940 
941 	// Touch scaling is from display pixels to dp pixels.
942 	// Wait, doesn't even make sense... this is equal to g_dpi_scale_x. TODO: Figure out what's going on!
943 	dp_xscale = (float)dp_xres / (float)display_xres;
944 	dp_yscale = (float)dp_yres / (float)display_yres;
945 
946 	pixel_in_dps_x = (float)pixel_xres / dp_xres;
947 	pixel_in_dps_y = (float)pixel_yres / dp_yres;
948 
949 	INFO_LOG(G3D, "RecalcDPI: display_xres=%d display_yres=%d", display_xres, display_yres);
950 	INFO_LOG(G3D, "RecalcDPI: g_dpi=%f g_dpi_scale_x=%f g_dpi_scale_y=%f", g_dpi, g_dpi_scale_x, g_dpi_scale_y);
951 	INFO_LOG(G3D, "RecalcDPI: dp_xscale=%f dp_yscale=%f", dp_xscale, dp_yscale);
952 	INFO_LOG(G3D, "RecalcDPI: dp_xres=%d dp_yres=%d", dp_xres, dp_yres);
953 	INFO_LOG(G3D, "RecalcDPI: pixel_xres=%d pixel_yres=%d", pixel_xres, pixel_yres);
954 }
955 
Java_org_ppsspp_ppsspp_NativeApp_backbufferResize(JNIEnv *,jclass,jint bufw,jint bufh,jint format)956 extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_backbufferResize(JNIEnv *, jclass, jint bufw, jint bufh, jint format) {
957 	INFO_LOG(SYSTEM, "NativeApp.backbufferResize(%d x %d)", bufw, bufh);
958 
959 	bool new_size = pixel_xres != bufw || pixel_yres != bufh;
960 	int old_w = pixel_xres;
961 	int old_h = pixel_yres;
962 	// pixel_*res is the backbuffer resolution.
963 	pixel_xres = bufw;
964 	pixel_yres = bufh;
965 	backbuffer_format = format;
966 
967 	recalculateDpi();
968 
969 	if (new_size) {
970 		INFO_LOG(G3D, "Size change detected (previously %d,%d) - calling NativeResized()", old_w, old_h);
971 		NativeResized();
972 	} else {
973 		INFO_LOG(G3D, "NativeApp::backbufferResize: Size didn't change.");
974 	}
975 }
976 
System_InputBoxGetString(const std::string & title,const std::string & defaultValue,std::function<void (bool,const std::string &)> cb)977 void System_InputBoxGetString(const std::string &title, const std::string &defaultValue, std::function<void(bool, const std::string &)> cb) {
978 	int seq = inputBoxSequence++;
979 	inputBoxCallbacks[seq] = cb;
980 
981 	std::string serialized = StringFromFormat("%d:@:%s:@:%s", seq, title.c_str(), defaultValue.c_str());
982 	System_SendMessage("inputbox", serialized.c_str());
983 }
984 
Java_org_ppsspp_ppsspp_NativeApp_sendInputBox(JNIEnv * env,jclass,jstring jseqID,jboolean result,jstring jvalue)985 extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_sendInputBox(JNIEnv *env, jclass, jstring jseqID, jboolean result, jstring jvalue) {
986 	std::string seqID = GetJavaString(env, jseqID);
987 	std::string value = GetJavaString(env, jvalue);
988 
989 	static std::string lastSeqID = "";
990 	if (lastSeqID == seqID) {
991 		// We send this on dismiss, so twice in many cases.
992 		DEBUG_LOG(SYSTEM, "Ignoring duplicate sendInputBox");
993 		return;
994 	}
995 	lastSeqID = seqID;
996 
997 	int seq = 0;
998 	if (!TryParse(seqID, &seq)) {
999 		ERROR_LOG(SYSTEM, "Invalid inputbox seqID value: %s", seqID.c_str());
1000 		return;
1001 	}
1002 
1003 	auto entry = inputBoxCallbacks.find(seq);
1004 	if (entry == inputBoxCallbacks.end()) {
1005 		ERROR_LOG(SYSTEM, "Did not find inputbox callback for %s, shutdown?", seqID.c_str());
1006 		return;
1007 	}
1008 
1009 	NativeInputBoxReceived(entry->second, result, value);
1010 }
1011 
LockedNativeUpdateRender()1012 void LockedNativeUpdateRender() {
1013 	std::lock_guard<std::mutex> renderGuard(renderLock);
1014 	NativeUpdate();
1015 	NativeRender(graphicsContext);
1016 }
1017 
UpdateRunLoopAndroid(JNIEnv * env)1018 void UpdateRunLoopAndroid(JNIEnv *env) {
1019 	LockedNativeUpdateRender();
1020 
1021 	std::lock_guard<std::mutex> guard(frameCommandLock);
1022 	if (!nativeActivity) {
1023 		while (!frameCommands.empty())
1024 			frameCommands.pop();
1025 		return;
1026 	}
1027 	// Still under lock here.
1028 	ProcessFrameCommands(env);
1029 }
1030 
Java_org_ppsspp_ppsspp_NativeRenderer_displayRender(JNIEnv * env,jobject obj)1031 extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayRender(JNIEnv *env, jobject obj) {
1032 	static bool hasSetThreadName = false;
1033 	if (!hasSetThreadName) {
1034 		hasSetThreadName = true;
1035 		SetCurrentThreadName("AndroidRender");
1036 	}
1037 
1038 	if (useCPUThread) {
1039 		// This is the "GPU thread".
1040 		if (graphicsContext)
1041 			graphicsContext->ThreadFrame();
1042 	} else {
1043 		UpdateRunLoopAndroid(env);
1044 	}
1045 }
1046 
System_AskForPermission(SystemPermission permission)1047 void System_AskForPermission(SystemPermission permission) {
1048 	switch (permission) {
1049 	case SYSTEM_PERMISSION_STORAGE:
1050 		PushCommand("ask_permission", "storage");
1051 		break;
1052 	}
1053 }
1054 
System_GetPermissionStatus(SystemPermission permission)1055 PermissionStatus System_GetPermissionStatus(SystemPermission permission) {
1056 	if (androidVersion < 23) {
1057 		return PERMISSION_STATUS_GRANTED;
1058 	} else {
1059 		return permissions[permission];
1060 	}
1061 }
1062 
Java_org_ppsspp_ppsspp_NativeApp_touch(JNIEnv *,jclass,float x,float y,int code,int pointerId)1063 extern "C" jboolean JNICALL Java_org_ppsspp_ppsspp_NativeApp_touch
1064 	(JNIEnv *, jclass, float x, float y, int code, int pointerId) {
1065 
1066 	float scaledX = x * dp_xscale;
1067 	float scaledY = y * dp_yscale;
1068 
1069 	TouchInput touch;
1070 	touch.id = pointerId;
1071 	touch.x = scaledX;
1072 	touch.y = scaledY;
1073 	touch.flags = code;
1074 
1075 	bool retval = NativeTouch(touch);
1076 	return retval;
1077 }
1078 
Java_org_ppsspp_ppsspp_NativeApp_keyDown(JNIEnv *,jclass,jint deviceId,jint key,jboolean isRepeat)1079 extern "C" jboolean Java_org_ppsspp_ppsspp_NativeApp_keyDown(JNIEnv *, jclass, jint deviceId, jint key, jboolean isRepeat) {
1080 	KeyInput keyInput;
1081 	keyInput.deviceId = deviceId;
1082 	keyInput.keyCode = key;
1083 	keyInput.flags = KEY_DOWN;
1084 	if (isRepeat) {
1085 		keyInput.flags |= KEY_IS_REPEAT;
1086 	}
1087 	return NativeKey(keyInput);
1088 }
1089 
Java_org_ppsspp_ppsspp_NativeApp_keyUp(JNIEnv *,jclass,jint deviceId,jint key)1090 extern "C" jboolean Java_org_ppsspp_ppsspp_NativeApp_keyUp(JNIEnv *, jclass, jint deviceId, jint key) {
1091 	KeyInput keyInput;
1092 	keyInput.deviceId = deviceId;
1093 	keyInput.keyCode = key;
1094 	keyInput.flags = KEY_UP;
1095 	return NativeKey(keyInput);
1096 }
1097 
Java_org_ppsspp_ppsspp_NativeApp_beginJoystickEvent(JNIEnv * env,jclass)1098 extern "C" void Java_org_ppsspp_ppsspp_NativeApp_beginJoystickEvent(
1099 	JNIEnv *env, jclass) {
1100 	// mutex lock?
1101 }
1102 
Java_org_ppsspp_ppsspp_NativeApp_joystickAxis(JNIEnv * env,jclass,jint deviceId,jint axisId,jfloat value)1103 extern "C" jboolean Java_org_ppsspp_ppsspp_NativeApp_joystickAxis(
1104 		JNIEnv *env, jclass, jint deviceId, jint axisId, jfloat value) {
1105 	if (!renderer_inited)
1106 		return false;
1107 	switch (axisId) {
1108 	case JOYSTICK_AXIS_X:
1109 		left_joystick_x_async = value;
1110 		break;
1111 	case JOYSTICK_AXIS_Y:
1112 		left_joystick_y_async = -value;
1113 		break;
1114 	case JOYSTICK_AXIS_Z:
1115 		right_joystick_x_async = value;
1116 		break;
1117 	case JOYSTICK_AXIS_RZ:
1118 		right_joystick_y_async = -value;
1119 		break;
1120 	case JOYSTICK_AXIS_HAT_X:
1121 		hat_joystick_x_async = value;
1122 		break;
1123 	case JOYSTICK_AXIS_HAT_Y:
1124 		hat_joystick_y_async = -value;
1125 		break;
1126 	}
1127 
1128 	AxisInput axis;
1129 	axis.axisId = axisId;
1130 	axis.deviceId = deviceId;
1131 	axis.value = value;
1132 
1133 	return NativeAxis(axis);
1134 }
1135 
Java_org_ppsspp_ppsspp_NativeApp_endJoystickEvent(JNIEnv * env,jclass)1136 extern "C" void Java_org_ppsspp_ppsspp_NativeApp_endJoystickEvent(
1137 	JNIEnv *env, jclass) {
1138 	// mutex unlock?
1139 }
1140 
1141 
Java_org_ppsspp_ppsspp_NativeApp_mouseWheelEvent(JNIEnv * env,jclass,jint stick,jfloat x,jfloat y)1142 extern "C" jboolean Java_org_ppsspp_ppsspp_NativeApp_mouseWheelEvent(
1143 	JNIEnv *env, jclass, jint stick, jfloat x, jfloat y) {
1144 	// TODO: Support mousewheel for android
1145 	return true;
1146 }
1147 
Java_org_ppsspp_ppsspp_NativeApp_accelerometer(JNIEnv *,jclass,float x,float y,float z)1148 extern "C" jboolean JNICALL Java_org_ppsspp_ppsspp_NativeApp_accelerometer(JNIEnv *, jclass, float x, float y, float z) {
1149 	if (!renderer_inited)
1150 		return false;
1151 
1152 	AxisInput axis;
1153 	axis.deviceId = DEVICE_ID_ACCELEROMETER;
1154 	axis.flags = 0;
1155 
1156 	axis.axisId = JOYSTICK_AXIS_ACCELEROMETER_X;
1157 	axis.value = x;
1158 	bool retvalX = NativeAxis(axis);
1159 
1160 	axis.axisId = JOYSTICK_AXIS_ACCELEROMETER_Y;
1161 	axis.value = y;
1162 	bool retvalY = NativeAxis(axis);
1163 
1164 	axis.axisId = JOYSTICK_AXIS_ACCELEROMETER_Z;
1165 	axis.value = z;
1166 	bool retvalZ = NativeAxis(axis);
1167 
1168 	return retvalX || retvalY || retvalZ;
1169 }
1170 
Java_org_ppsspp_ppsspp_NativeApp_sendMessage(JNIEnv * env,jclass,jstring message,jstring param)1171 extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_sendMessage(JNIEnv *env, jclass, jstring message, jstring param) {
1172 	std::string msg = GetJavaString(env, message);
1173 	std::string prm = GetJavaString(env, param);
1174 
1175 	// Some messages are caught by app-android.
1176 	if (msg == "moga") {
1177 		mogaVersion = prm;
1178 	} else if (msg == "permission_pending") {
1179 		INFO_LOG(SYSTEM, "STORAGE PERMISSION: PENDING");
1180 		// TODO: Add support for other permissions
1181 		permissions[SYSTEM_PERMISSION_STORAGE] = PERMISSION_STATUS_PENDING;
1182 	} else if (msg == "permission_denied") {
1183 		INFO_LOG(SYSTEM, "STORAGE PERMISSION: DENIED");
1184 		permissions[SYSTEM_PERMISSION_STORAGE] = PERMISSION_STATUS_DENIED;
1185 	} else if (msg == "permission_granted") {
1186 		INFO_LOG(SYSTEM, "STORAGE PERMISSION: GRANTED");
1187 		permissions[SYSTEM_PERMISSION_STORAGE] = PERMISSION_STATUS_GRANTED;
1188 	} else if (msg == "sustained_perf_supported") {
1189 		sustainedPerfSupported = true;
1190 	} else if (msg == "safe_insets") {
1191 		INFO_LOG(SYSTEM, "Got insets: %s", prm.c_str());
1192 		// We don't bother with supporting exact rectangular regions. Safe insets are good enough.
1193 		int left, right, top, bottom;
1194 		if (4 == sscanf(prm.c_str(), "%d:%d:%d:%d", &left, &right, &top, &bottom)) {
1195 			g_safeInsetLeft = (float)left * g_dpi_scale_x;
1196 			g_safeInsetRight = (float)right * g_dpi_scale_x;
1197 			g_safeInsetTop = (float)top * g_dpi_scale_y;
1198 			g_safeInsetBottom = (float)bottom * g_dpi_scale_y;
1199 		}
1200 	}
1201 
1202 	// Ensures that the receiver can handle it on a sensible thread.
1203 	NativeMessageReceived(msg.c_str(), prm.c_str());
1204 }
1205 
Java_org_ppsspp_ppsspp_NativeActivity_exitEGLRenderLoop(JNIEnv * env,jobject obj)1206 extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeActivity_exitEGLRenderLoop(JNIEnv *env, jobject obj) {
1207 	if (!renderLoopRunning) {
1208 		ERROR_LOG(SYSTEM, "Render loop already exited");
1209 		return;
1210 	}
1211 	exitRenderLoop = true;
1212 	while (renderLoopRunning) {
1213 		sleep_ms(10);
1214 	}
1215 }
1216 
correctRatio(int & sz_x,int & sz_y,float scale)1217 void correctRatio(int &sz_x, int &sz_y, float scale) {
1218 	float x = (float)sz_x;
1219 	float y = (float)sz_y;
1220 	float ratio = x / y;
1221 	INFO_LOG(G3D, "CorrectRatio: Considering size: %0.2f/%0.2f=%0.2f for scale %f", x, y, ratio, scale);
1222 	float targetRatio;
1223 
1224 	// Try to get the longest dimension to match scale*PSP resolution.
1225 	if (x >= y) {
1226 		targetRatio = 480.0f / 272.0f;
1227 		x = 480.f * scale;
1228 		y = 272.f * scale;
1229 	} else {
1230 		targetRatio = 272.0f / 480.0f;
1231 		x = 272.0f * scale;
1232 		y = 480.0f * scale;
1233 	}
1234 
1235 	float correction = targetRatio / ratio;
1236 	INFO_LOG(G3D, "Target ratio: %0.2f ratio: %0.2f correction: %0.2f", targetRatio, ratio, correction);
1237 	if (ratio < targetRatio) {
1238 		y *= correction;
1239 	} else {
1240 		x /= correction;
1241 	}
1242 
1243 	sz_x = x;
1244 	sz_y = y;
1245 	INFO_LOG(G3D, "Corrected ratio: %dx%d", sz_x, sz_y);
1246 }
1247 
getDesiredBackbufferSize(int & sz_x,int & sz_y)1248 void getDesiredBackbufferSize(int &sz_x, int &sz_y) {
1249 	sz_x = display_xres;
1250 	sz_y = display_yres;
1251 	std::string config = NativeQueryConfig("hwScale");
1252 	int scale;
1253 	if (1 == sscanf(config.c_str(), "%d", &scale) && scale > 0) {
1254 		correctRatio(sz_x, sz_y, scale);
1255 	} else {
1256 		sz_x = 0;
1257 		sz_y = 0;
1258 	}
1259 }
1260 
Java_org_ppsspp_ppsspp_NativeApp_setDisplayParameters(JNIEnv *,jclass,jint xres,jint yres,jint dpi,jfloat refreshRate)1261 extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_setDisplayParameters(JNIEnv *, jclass, jint xres, jint yres, jint dpi, jfloat refreshRate) {
1262 	INFO_LOG(G3D, "NativeApp.setDisplayParameters(%d x %d, dpi=%d, refresh=%0.2f)", xres, yres, dpi, refreshRate);
1263 	bool changed = false;
1264 	changed = changed || display_xres != xres || display_yres != yres;
1265 	changed = changed || display_dpi_x != dpi || display_dpi_y != dpi;
1266 	changed = changed || display_hz != refreshRate;
1267 
1268 	if (changed) {
1269 		display_xres = xres;
1270 		display_yres = yres;
1271 		display_dpi_x = dpi;
1272 		display_dpi_y = dpi;
1273 		display_hz = refreshRate;
1274 
1275 		recalculateDpi();
1276 		NativeResized();
1277 	}
1278 }
1279 
Java_org_ppsspp_ppsspp_NativeApp_computeDesiredBackbufferDimensions()1280 extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_computeDesiredBackbufferDimensions() {
1281 	getDesiredBackbufferSize(desiredBackbufferSizeX, desiredBackbufferSizeY);
1282 }
1283 
Java_org_ppsspp_ppsspp_NativeApp_getDesiredBackbufferWidth(JNIEnv *,jclass)1284 extern "C" jint JNICALL Java_org_ppsspp_ppsspp_NativeApp_getDesiredBackbufferWidth(JNIEnv *, jclass) {
1285 	return desiredBackbufferSizeX;
1286 }
1287 
Java_org_ppsspp_ppsspp_NativeApp_getDesiredBackbufferHeight(JNIEnv *,jclass)1288 extern "C" jint JNICALL Java_org_ppsspp_ppsspp_NativeApp_getDesiredBackbufferHeight(JNIEnv *, jclass) {
1289 	return desiredBackbufferSizeY;
1290 }
1291 
__cameraGetDeviceList()1292 std::vector<std::string> __cameraGetDeviceList() {
1293 	jclass cameraClass = findClass("org/ppsspp/ppsspp/CameraHelper");
1294 	jmethodID deviceListMethod = getEnv()->GetStaticMethodID(cameraClass, "getDeviceList", "()Ljava/util/ArrayList;");
1295 	jobject deviceListObject = getEnv()->CallStaticObjectMethod(cameraClass, deviceListMethod);
1296 	jclass arrayListClass = getEnv()->FindClass("java/util/ArrayList");
1297 	jmethodID arrayListSize = getEnv()->GetMethodID(arrayListClass, "size", "()I");
1298 	jmethodID arrayListGet = getEnv()->GetMethodID(arrayListClass, "get", "(I)Ljava/lang/Object;");
1299 
1300 	jint arrayListObjectLen = getEnv()->CallIntMethod(deviceListObject, arrayListSize);
1301 	std::vector<std::string> deviceListVector;
1302 
1303 	for (int i=0; i < arrayListObjectLen; i++) {
1304 		jstring dev = static_cast<jstring>(getEnv()->CallObjectMethod(deviceListObject, arrayListGet, i));
1305 		const char* cdev = getEnv()->GetStringUTFChars(dev, nullptr);
1306 		deviceListVector.push_back(cdev);
1307 		getEnv()->ReleaseStringUTFChars(dev, cdev);
1308 		getEnv()->DeleteLocalRef(dev);
1309 	}
1310 	return deviceListVector;
1311 }
1312 
Java_org_ppsspp_ppsspp_NativeApp_getSelectedCamera(JNIEnv *,jclass)1313 extern "C" jint Java_org_ppsspp_ppsspp_NativeApp_getSelectedCamera(JNIEnv *, jclass) {
1314 	int cameraId = 0;
1315 	sscanf(g_Config.sCameraDevice.c_str(), "%d:", &cameraId);
1316 	return cameraId;
1317 }
1318 
Java_org_ppsspp_ppsspp_NativeApp_setGpsDataAndroid(JNIEnv *,jclass,jlong time,jfloat hdop,jfloat latitude,jfloat longitude,jfloat altitude,jfloat speed,jfloat bearing)1319 extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_setGpsDataAndroid(JNIEnv *, jclass,
1320        jlong time, jfloat hdop, jfloat latitude, jfloat longitude, jfloat altitude, jfloat speed, jfloat bearing) {
1321 	GPS::setGpsData(time, hdop, latitude, longitude, altitude, speed, bearing);
1322 }
1323 
Java_org_ppsspp_ppsspp_NativeApp_setSatInfoAndroid(JNIEnv *,jclass,jshort index,jshort id,jshort elevation,jshort azimuth,jshort snr,jshort good)1324 extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_setSatInfoAndroid(JNIEnv *, jclass,
1325 	   jshort index, jshort id, jshort elevation, jshort azimuth, jshort snr, jshort good) {
1326 	GPS::setSatInfo(index, id, elevation, azimuth, snr, good);
1327 }
1328 
Java_org_ppsspp_ppsspp_NativeApp_pushCameraImageAndroid(JNIEnv * env,jclass,jbyteArray image)1329 extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_pushCameraImageAndroid(JNIEnv *env, jclass,
1330 		jbyteArray image) {
1331 
1332 	if (image != NULL) {
1333 		jlong size = env->GetArrayLength(image);
1334 		jbyte* buffer = env->GetByteArrayElements(image, NULL);
1335 		Camera::pushCameraImage(size, (unsigned char *)buffer);
1336 		env->ReleaseByteArrayElements(image, buffer, JNI_ABORT);
1337 	}
1338 }
1339 
1340 // Call this under frameCommandLock.
ProcessFrameCommands(JNIEnv * env)1341 static void ProcessFrameCommands(JNIEnv *env) {
1342 	while (!frameCommands.empty()) {
1343 		FrameCommand frameCmd;
1344 		frameCmd = frameCommands.front();
1345 		frameCommands.pop();
1346 
1347 		INFO_LOG(SYSTEM, "frameCommand '%s' '%s'", frameCmd.command.c_str(), frameCmd.params.c_str());
1348 
1349 		jstring cmd = env->NewStringUTF(frameCmd.command.c_str());
1350 		jstring param = env->NewStringUTF(frameCmd.params.c_str());
1351 		env->CallVoidMethod(nativeActivity, postCommand, cmd, param);
1352 		env->DeleteLocalRef(cmd);
1353 		env->DeleteLocalRef(param);
1354 	}
1355 }
1356 
Java_org_ppsspp_ppsspp_NativeActivity_runEGLRenderLoop(JNIEnv * env,jobject obj,jobject _surf)1357 extern "C" bool JNICALL Java_org_ppsspp_ppsspp_NativeActivity_runEGLRenderLoop(JNIEnv *env, jobject obj, jobject _surf) {
1358 	if (!graphicsContext) {
1359 		ERROR_LOG(G3D, "runEGLRenderLoop: Tried to enter without a created graphics context.");
1360 		return false;
1361 	}
1362 
1363 	// Needed for Vulkan, even if we're not using the old EGL path.
1364 
1365 	exitRenderLoop = false;
1366 	// This is up here to prevent race conditions, in case we pause during init.
1367 	renderLoopRunning = true;
1368 
1369 	ANativeWindow *wnd = _surf ? ANativeWindow_fromSurface(env, _surf) : nullptr;
1370 
1371 	WARN_LOG(G3D, "runEGLRenderLoop. display_xres=%d display_yres=%d", display_xres, display_yres);
1372 
1373 	if (wnd == nullptr) {
1374 		ERROR_LOG(G3D, "Error: Surface is null.");
1375 		renderLoopRunning = false;
1376 		return false;
1377 	}
1378 
1379 	auto tryInit = [&]() {
1380 		if (graphicsContext->InitFromRenderThread(wnd, desiredBackbufferSizeX, desiredBackbufferSizeY, backbuffer_format, androidVersion)) {
1381 			return true;
1382 		} else {
1383 			ERROR_LOG(G3D, "Failed to initialize graphics context.");
1384 			SystemToast("Failed to initialize graphics context.");
1385 			return false;
1386 		}
1387 	};
1388 
1389 	bool initSuccess = tryInit();
1390 	if (!initSuccess) {
1391 		if (!exitRenderLoop && g_Config.iGPUBackend == (int)GPUBackend::VULKAN) {
1392 			INFO_LOG(G3D, "Trying again, this time with OpenGL.");
1393 			SetGPUBackend(GPUBackend::OPENGL);
1394 			g_Config.iGPUBackend = (int)GetGPUBackend();
1395 
1396 			// If we were still supporting EGL for GL, we'd retry here:
1397 			//initSuccess = tryInit();
1398 		}
1399 
1400 		if (!initSuccess) {
1401 			delete graphicsContext;
1402 			graphicsContext = nullptr;
1403 			renderLoopRunning = false;
1404 			return false;
1405 		}
1406 	}
1407 
1408 	if (!exitRenderLoop) {
1409 		if (!useCPUThread) {
1410 			if (!NativeInitGraphics(graphicsContext)) {
1411 				ERROR_LOG(G3D, "Failed to initialize graphics.");
1412 				// Gonna be in a weird state here..
1413 			}
1414 		}
1415 		graphicsContext->ThreadStart();
1416 		renderer_inited = true;
1417 	}
1418 
1419 	if (!exitRenderLoop) {
1420 		static bool hasSetThreadName = false;
1421 		if (!hasSetThreadName) {
1422 			hasSetThreadName = true;
1423 			SetCurrentThreadName("AndroidRender");
1424 		}
1425 	}
1426 
1427 	if (useCPUThread) {
1428 		ERROR_LOG(SYSTEM, "Running graphics loop");
1429 		while (!exitRenderLoop) {
1430 			// This is the "GPU thread".
1431 			graphicsContext->ThreadFrame();
1432 			graphicsContext->SwapBuffers();
1433 		}
1434 	} else {
1435 		while (!exitRenderLoop) {
1436 			LockedNativeUpdateRender();
1437 			graphicsContext->SwapBuffers();
1438 
1439 			ProcessFrameCommands(env);
1440 		}
1441 	}
1442 
1443 	INFO_LOG(G3D, "Leaving EGL/Vulkan render loop.");
1444 
1445 	if (useCPUThread) {
1446 		EmuThreadStop("exitrenderloop");
1447 		while (graphicsContext->ThreadFrame()) {
1448 			continue;
1449 		}
1450 		EmuThreadJoin();
1451 	} else {
1452 		NativeShutdownGraphics();
1453 	}
1454 	renderer_inited = false;
1455 	graphicsContext->ThreadEnd();
1456 
1457 	// Shut the graphics context down to the same state it was in when we entered the render thread.
1458 	INFO_LOG(G3D, "Shutting down graphics context from render thread...");
1459 	graphicsContext->ShutdownFromRenderThread();
1460 	renderLoopRunning = false;
1461 	WARN_LOG(G3D, "Render loop function exited.");
1462 	return true;
1463 }
1464 
1465 // NOTE: This is defunct and not working, due to how the Android storage functions currently require
1466 // a PpssppActivity specifically and we don't have one here.
Java_org_ppsspp_ppsspp_ShortcutActivity_queryGameName(JNIEnv * env,jclass,jstring jpath)1467 extern "C" jstring Java_org_ppsspp_ppsspp_ShortcutActivity_queryGameName(JNIEnv *env, jclass, jstring jpath) {
1468 	bool teardownThreadManager = false;
1469 	if (!g_threadManager.IsInitialized()) {
1470 		INFO_LOG(SYSTEM, "No thread manager - initializing one");
1471 		// Need a thread manager.
1472 		teardownThreadManager = true;
1473 		g_threadManager.Init(1, 1);
1474 	}
1475 
1476 	Path path = Path(GetJavaString(env, jpath));
1477 
1478 	INFO_LOG(SYSTEM, "queryGameName(%s)", path.c_str());
1479 
1480 	std::string result = "";
1481 
1482 	GameInfoCache *cache = new GameInfoCache();
1483 	std::shared_ptr<GameInfo> info = cache->GetInfo(nullptr, path, 0);
1484 	// Wait until it's done: this is synchronous, unfortunately.
1485 	if (info) {
1486 		INFO_LOG(SYSTEM, "GetInfo successful, waiting");
1487 		cache->WaitUntilDone(info);
1488 		INFO_LOG(SYSTEM, "Done waiting");
1489 		if (info->fileType != IdentifiedFileType::UNKNOWN) {
1490 			result = info->GetTitle();
1491 
1492 			// Pretty arbitrary, but the home screen will often truncate titles.
1493 			// Let's remove "The " from names since it's common in English titles.
1494 			if (result.length() > strlen("The ") && startsWithNoCase(result, "The ")) {
1495 				result = result.substr(strlen("The "));
1496 			}
1497 
1498 			INFO_LOG(SYSTEM, "queryGameName: Got '%s'", result.c_str());
1499 		} else {
1500 			INFO_LOG(SYSTEM, "queryGameName: Filetype unknown");
1501 		}
1502 	} else {
1503 		INFO_LOG(SYSTEM, "No info from cache");
1504 	}
1505 	delete cache;
1506 
1507 	if (teardownThreadManager) {
1508 		g_threadManager.Teardown();
1509 	}
1510 
1511 	return env->NewStringUTF(result.c_str());
1512 }
1513