CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: hrydgard/ppsspp
Path: blob/master/android/jni/app-android.cpp
Views: 1401
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
#ifndef _MSC_VER
17
18
#include <jni.h>
19
#include <android/native_window_jni.h>
20
#include <android/log.h>
21
22
#elif !defined(JNIEXPORT)
23
// Just for better highlighting in MSVC if opening this file.
24
// Not having types makes it get confused and say everything is wrong.
25
struct JavaVM;
26
typedef void *jmethodID;
27
typedef void *jfieldID;
28
29
typedef uint8_t jboolean;
30
typedef int8_t jbyte;
31
typedef int16_t jshort;
32
typedef int32_t jint;
33
typedef int64_t jlong;
34
typedef jint jsize;
35
typedef float jfloat;
36
typedef double jdouble;
37
38
class _jobject {};
39
class _jclass : public _jobject {};
40
typedef _jobject *jobject;
41
typedef _jclass *jclass;
42
typedef jobject jstring;
43
typedef jobject jbyteArray;
44
typedef jobject jintArray;
45
typedef jobject jfloatArray;
46
47
struct JNIEnv {};
48
49
#define JNIEXPORT
50
#define JNICALL
51
// Just a random value to make MSVC highlighting happy.
52
#define JNI_VERSION_1_6 16
53
#endif
54
55
#include "Common/Log.h"
56
#include "Common/LogReporting.h"
57
58
#include "Common/Net/Resolve.h"
59
#include "android/jni/AndroidAudio.h"
60
#include "Common/GPU/OpenGL/GLCommon.h"
61
#include "Common/GPU/OpenGL/GLFeatures.h"
62
63
#include "Common/System/Display.h"
64
#include "Common/System/NativeApp.h"
65
#include "Common/System/System.h"
66
#include "Common/System/OSD.h"
67
#include "Common/System/Request.h"
68
#include "Common/Thread/ThreadUtil.h"
69
#include "Common/File/Path.h"
70
#include "Common/File/DirListing.h"
71
#include "Common/File/VFS/VFS.h"
72
#include "Common/File/VFS/DirectoryReader.h"
73
#include "Common/File/VFS/ZipFileReader.h"
74
#include "Common/File/AndroidStorage.h"
75
#include "Common/Input/InputState.h"
76
#include "Common/Input/KeyCodes.h"
77
#include "Common/Profiler/Profiler.h"
78
#include "Common/Math/math_util.h"
79
#include "Common/Data/Text/Parsers.h"
80
#include "Common/VR/PPSSPPVR.h"
81
#include "Common/GPU/Vulkan/VulkanLoader.h"
82
83
#include "Common/GraphicsContext.h"
84
#include "Common/StringUtils.h"
85
#include "Common/TimeUtil.h"
86
87
#include "AndroidGraphicsContext.h"
88
#include "AndroidVulkanContext.h"
89
#include "AndroidJavaGLContext.h"
90
91
#include "Core/Config.h"
92
#include "Core/ConfigValues.h"
93
#include "Core/Loaders.h"
94
#include "Core/FileLoaders/LocalFileLoader.h"
95
#include "Core/KeyMap.h"
96
#include "Core/System.h"
97
#include "Core/HLE/sceUsbCam.h"
98
#include "Core/HLE/sceUsbGps.h"
99
#include "Common/CPUDetect.h"
100
#include "Common/Log.h"
101
#include "UI/GameInfoCache.h"
102
103
#include "app-android.h"
104
105
bool useCPUThread = true;
106
107
enum class EmuThreadState {
108
DISABLED,
109
START_REQUESTED,
110
RUNNING,
111
QUIT_REQUESTED,
112
STOPPED,
113
};
114
115
// OpenGL emu thread
116
static std::thread emuThread;
117
static std::atomic<int> emuThreadState((int)EmuThreadState::DISABLED);
118
119
AndroidAudioState *g_audioState;
120
121
struct FrameCommand {
122
FrameCommand() {}
123
FrameCommand(std::string cmd, std::string prm) : command(cmd), params(prm) {}
124
125
std::string command;
126
std::string params;
127
};
128
129
static std::mutex frameCommandLock;
130
static std::queue<FrameCommand> frameCommands;
131
132
static std::string systemName;
133
static std::string langRegion;
134
static std::string mogaVersion;
135
static std::string boardName;
136
137
std::string g_externalDir; // Original external dir (root of Android storage).
138
std::string g_extFilesDir; // App private external dir.
139
std::string g_nativeLibDir; // App native library dir
140
141
static std::vector<std::string> g_additionalStorageDirs;
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
static int display_xres;
154
static 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
extern JavaVM *gJvm;
164
165
static jobject gClassLoader;
166
static jmethodID gFindClassMethod;
167
168
static float g_safeInsetLeft = 0.0;
169
static float g_safeInsetRight = 0.0;
170
static float g_safeInsetTop = 0.0;
171
static float g_safeInsetBottom = 0.0;
172
173
static jmethodID postCommand;
174
static jmethodID getDebugString;
175
176
static jobject nativeActivity;
177
178
static std::atomic<bool> exitRenderLoop;
179
static std::atomic<bool> renderLoopRunning;
180
static bool renderer_inited = false;
181
static std::mutex renderLock;
182
183
static bool sustainedPerfSupported = false;
184
185
static std::map<SystemPermission, PermissionStatus> permissions;
186
187
static AndroidGraphicsContext *graphicsContext;
188
189
#ifndef LOG_APP_NAME
190
#define LOG_APP_NAME "PPSSPP"
191
#endif
192
193
#define MessageBox(a, b, c, d) __android_log_print(ANDROID_LOG_INFO, APP_NAME, "%s %s", (b), (c));
194
195
#if PPSSPP_ARCH(ARMV7)
196
// Old Android workaround
197
extern "C" {
198
int utimensat(int fd, const char *path, const struct timespec times[2]) {
199
return -1;
200
}
201
}
202
#endif
203
204
static void ProcessFrameCommands(JNIEnv *env);
205
206
void AndroidLogger::Log(const LogMessage &message) {
207
int mode;
208
switch (message.level) {
209
case LogLevel::LWARNING:
210
mode = ANDROID_LOG_WARN;
211
break;
212
case LogLevel::LERROR:
213
mode = ANDROID_LOG_ERROR;
214
break;
215
default:
216
mode = ANDROID_LOG_INFO;
217
break;
218
}
219
220
// Long log messages need splitting up.
221
// Not sure what the actual limit is (seems to vary), but let's be conservative.
222
const size_t maxLogLength = 512;
223
if (message.msg.length() < maxLogLength) {
224
// Log with simplified headers as Android already provides timestamp etc.
225
__android_log_print(mode, LOG_APP_NAME, "[%s] %s", message.log, message.msg.c_str());
226
} else {
227
std::string msg = message.msg;
228
229
// Ideally we should split at line breaks, but it's at least fairly usable anyway.
230
std::string first_part = msg.substr(0, maxLogLength);
231
__android_log_print(mode, LOG_APP_NAME, "[%s] %s", message.log, first_part.c_str());
232
msg = msg.substr(maxLogLength);
233
234
while (msg.length() > maxLogLength) {
235
std::string first_part = msg.substr(0, maxLogLength);
236
__android_log_print(mode, LOG_APP_NAME, "%s", first_part.c_str());
237
msg = msg.substr(maxLogLength);
238
}
239
// Print the final part.
240
__android_log_print(mode, LOG_APP_NAME, "%s", msg.c_str());
241
}
242
}
243
244
JNIEnv* getEnv() {
245
JNIEnv *env;
246
int status = gJvm->GetEnv((void**)&env, JNI_VERSION_1_6);
247
_assert_msg_(status >= 0, "'%s': Can only call getEnv if you've attached the thread already!", GetCurrentThreadName());
248
return env;
249
}
250
251
jclass findClass(const char* name) {
252
return static_cast<jclass>(getEnv()->CallObjectMethod(gClassLoader, gFindClassMethod, getEnv()->NewStringUTF(name)));
253
}
254
255
void Android_AttachThreadToJNI() {
256
JNIEnv *env;
257
int status = gJvm->GetEnv((void **)&env, JNI_VERSION_1_6);
258
if (status < 0) {
259
DEBUG_LOG(Log::System, "Attaching thread '%s' (not already attached) to JNI.", GetCurrentThreadName());
260
JavaVMAttachArgs args{};
261
args.version = JNI_VERSION_1_6;
262
args.name = GetCurrentThreadName();
263
status = gJvm->AttachCurrentThread(&env, &args);
264
265
if (status < 0) {
266
// bad, but what can we do other than report..
267
ERROR_LOG_REPORT_ONCE(threadAttachFail, Log::System, "Failed to attach thread %s to JNI.", GetCurrentThreadName());
268
}
269
} else {
270
WARN_LOG(Log::System, "Thread %s was already attached to JNI.", GetCurrentThreadName());
271
}
272
}
273
274
void Android_DetachThreadFromJNI() {
275
if (gJvm->DetachCurrentThread() == JNI_OK) {
276
DEBUG_LOG(Log::System, "Detached thread from JNI: '%s'", GetCurrentThreadName());
277
} else {
278
WARN_LOG(Log::System, "Failed to detach thread '%s' from JNI - never attached?", GetCurrentThreadName());
279
}
280
}
281
282
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *pjvm, void *reserved) {
283
INFO_LOG(Log::System, "JNI_OnLoad");
284
gJvm = pjvm; // cache the JavaVM pointer
285
auto env = getEnv();
286
//replace with one of your classes in the line below
287
auto randomClass = env->FindClass("org/ppsspp/ppsspp/NativeActivity");
288
jclass classClass = env->GetObjectClass(randomClass);
289
auto classLoaderClass = env->FindClass("java/lang/ClassLoader");
290
auto getClassLoaderMethod = env->GetMethodID(classClass, "getClassLoader",
291
"()Ljava/lang/ClassLoader;");
292
gClassLoader = env->NewGlobalRef(env->CallObjectMethod(randomClass, getClassLoaderMethod));
293
gFindClassMethod = env->GetMethodID(classLoaderClass, "findClass",
294
"(Ljava/lang/String;)Ljava/lang/Class;");
295
296
RegisterAttachDetach(&Android_AttachThreadToJNI, &Android_DetachThreadFromJNI);
297
298
TimeInit();
299
return JNI_VERSION_1_6;
300
}
301
302
// Only used in OpenGL mode.
303
static void EmuThreadFunc() {
304
SetCurrentThreadName("EmuThread");
305
306
// Name the thread in the JVM, because why not (might result in better debug output in Play Console).
307
// TODO: Do something clever with getEnv() and stored names from SetCurrentThreadName?
308
JNIEnv *env;
309
JavaVMAttachArgs args{};
310
args.version = JNI_VERSION_1_6;
311
args.name = "EmuThread";
312
gJvm->AttachCurrentThread(&env, &args);
313
314
INFO_LOG(Log::System, "Entering emu thread");
315
316
// Wait for render loop to get started.
317
INFO_LOG(Log::System, "Runloop: Waiting for displayInit...");
318
while (!graphicsContext || graphicsContext->GetState() == GraphicsContextState::PENDING) {
319
sleep_ms(5);
320
}
321
322
// Check the state of the graphics context before we try to feed it into NativeInitGraphics.
323
if (graphicsContext->GetState() != GraphicsContextState::INITIALIZED) {
324
ERROR_LOG(Log::G3D, "Failed to initialize the graphics context! %d", (int)graphicsContext->GetState());
325
emuThreadState = (int)EmuThreadState::QUIT_REQUESTED;
326
gJvm->DetachCurrentThread();
327
return;
328
}
329
330
if (!NativeInitGraphics(graphicsContext)) {
331
_assert_msg_(false, "NativeInitGraphics failed, might as well bail");
332
emuThreadState = (int)EmuThreadState::QUIT_REQUESTED;
333
gJvm->DetachCurrentThread();
334
return;
335
}
336
337
INFO_LOG(Log::System, "Graphics initialized. Entering loop.");
338
339
// There's no real requirement that NativeInit happen on this thread.
340
// We just call the update/render loop here.
341
emuThreadState = (int)EmuThreadState::RUNNING;
342
while (emuThreadState != (int)EmuThreadState::QUIT_REQUESTED) {
343
{
344
std::lock_guard<std::mutex> renderGuard(renderLock);
345
NativeFrame(graphicsContext);
346
}
347
348
std::lock_guard<std::mutex> guard(frameCommandLock);
349
if (!nativeActivity) {
350
ERROR_LOG(Log::System, "No activity, clearing commands");
351
while (!frameCommands.empty())
352
frameCommands.pop();
353
return;
354
}
355
// Still under lock here.
356
ProcessFrameCommands(env);
357
}
358
359
INFO_LOG(Log::System, "QUIT_REQUESTED found, left EmuThreadFunc loop. Setting state to STOPPED.");
360
emuThreadState = (int)EmuThreadState::STOPPED;
361
362
NativeShutdownGraphics();
363
364
// Also ask the main thread to stop, so it doesn't hang waiting for a new frame.
365
graphicsContext->StopThread();
366
367
gJvm->DetachCurrentThread();
368
INFO_LOG(Log::System, "Leaving emu thread");
369
}
370
371
static void EmuThreadStart() {
372
INFO_LOG(Log::System, "EmuThreadStart");
373
emuThreadState = (int)EmuThreadState::START_REQUESTED;
374
emuThread = std::thread(&EmuThreadFunc);
375
}
376
377
// Call EmuThreadStop first, then keep running the GPU (or eat commands)
378
// as long as emuThreadState isn't STOPPED and/or there are still things queued up.
379
// Only after that, call EmuThreadJoin.
380
static void EmuThreadStop(const char *caller) {
381
INFO_LOG(Log::System, "EmuThreadStop - stopping (%s)...", caller);
382
emuThreadState = (int)EmuThreadState::QUIT_REQUESTED;
383
}
384
385
static void EmuThreadJoin() {
386
emuThread.join();
387
emuThread = std::thread();
388
INFO_LOG(Log::System, "EmuThreadJoin - joined");
389
}
390
391
static void PushCommand(std::string cmd, std::string param) {
392
std::lock_guard<std::mutex> guard(frameCommandLock);
393
frameCommands.push(FrameCommand(cmd, param));
394
}
395
396
// Android implementation of callbacks to the Java part of the app
397
void System_Toast(std::string_view text) {
398
PushCommand("toast", std::string(text));
399
}
400
401
void System_ShowKeyboard() {
402
PushCommand("showKeyboard", "");
403
}
404
405
void System_Vibrate(int length_ms) {
406
char temp[32];
407
snprintf(temp, sizeof(temp), "%d", length_ms);
408
PushCommand("vibrate", temp);
409
}
410
411
void System_LaunchUrl(LaunchUrlType urlType, const char *url) {
412
switch (urlType) {
413
case LaunchUrlType::BROWSER_URL: PushCommand("launchBrowser", url); break;
414
case LaunchUrlType::MARKET_URL: PushCommand("launchMarket", url); break;
415
case LaunchUrlType::EMAIL_ADDRESS: PushCommand("launchEmail", url); break;
416
}
417
}
418
419
std::string System_GetProperty(SystemProperty prop) {
420
switch (prop) {
421
case SYSPROP_NAME:
422
return systemName;
423
case SYSPROP_LANGREGION: // "en_US"
424
return langRegion;
425
case SYSPROP_MOGA_VERSION:
426
return mogaVersion;
427
case SYSPROP_BOARDNAME:
428
return boardName;
429
case SYSPROP_BUILD_VERSION:
430
return PPSSPP_GIT_VERSION;
431
default:
432
return "";
433
}
434
}
435
436
std::vector<std::string> System_GetPropertyStringVec(SystemProperty prop) {
437
switch (prop) {
438
case SYSPROP_ADDITIONAL_STORAGE_DIRS:
439
return g_additionalStorageDirs;
440
441
case SYSPROP_TEMP_DIRS:
442
default:
443
return std::vector<std::string>();
444
}
445
}
446
447
int64_t System_GetPropertyInt(SystemProperty prop) {
448
switch (prop) {
449
case SYSPROP_SYSTEMVERSION:
450
return androidVersion;
451
case SYSPROP_DEVICE_TYPE:
452
return deviceType;
453
case SYSPROP_DISPLAY_XRES:
454
return display_xres;
455
case SYSPROP_DISPLAY_YRES:
456
return display_yres;
457
case SYSPROP_AUDIO_SAMPLE_RATE:
458
return sampleRate;
459
case SYSPROP_AUDIO_FRAMES_PER_BUFFER:
460
return framesPerBuffer;
461
case SYSPROP_AUDIO_OPTIMAL_SAMPLE_RATE:
462
return optimalSampleRate;
463
case SYSPROP_AUDIO_OPTIMAL_FRAMES_PER_BUFFER:
464
return optimalFramesPerBuffer;
465
default:
466
return -1;
467
}
468
}
469
470
float System_GetPropertyFloat(SystemProperty prop) {
471
switch (prop) {
472
case SYSPROP_DISPLAY_REFRESH_RATE:
473
return g_display.display_hz;
474
case SYSPROP_DISPLAY_SAFE_INSET_LEFT:
475
return g_safeInsetLeft;
476
case SYSPROP_DISPLAY_SAFE_INSET_RIGHT:
477
return g_safeInsetRight;
478
case SYSPROP_DISPLAY_SAFE_INSET_TOP:
479
return g_safeInsetTop;
480
case SYSPROP_DISPLAY_SAFE_INSET_BOTTOM:
481
return g_safeInsetBottom;
482
default:
483
return -1;
484
}
485
}
486
487
bool System_GetPropertyBool(SystemProperty prop) {
488
switch (prop) {
489
case SYSPROP_SUPPORTS_PERMISSIONS:
490
if (androidVersion < 23) {
491
// 6.0 Marshmallow introduced run time permissions.
492
return false;
493
} else {
494
// It gets a bit complicated here. If scoped storage enforcement is on,
495
// we also don't need to request permissions. We'll have the access we request
496
// on a per-folder basis.
497
return !System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE);
498
}
499
case SYSPROP_SUPPORTS_SUSTAINED_PERF_MODE:
500
return sustainedPerfSupported; // 7.0 introduced sustained performance mode as an optional feature.
501
case SYSPROP_HAS_TEXT_INPUT_DIALOG:
502
return androidVersion >= 11; // honeycomb
503
case SYSPROP_HAS_TEXT_CLIPBOARD:
504
return true;
505
case SYSPROP_HAS_OPEN_DIRECTORY:
506
return false; // We have this implemented but it may or may not work depending on if a file explorer is installed.
507
case SYSPROP_HAS_ADDITIONAL_STORAGE:
508
return !g_additionalStorageDirs.empty();
509
case SYSPROP_HAS_BACK_BUTTON:
510
return true;
511
case SYSPROP_HAS_IMAGE_BROWSER:
512
return deviceType != DEVICE_TYPE_VR;
513
case SYSPROP_HAS_FILE_BROWSER:
514
// It's only really needed with scoped storage, but why not make it available
515
// as far back as possible - works just fine.
516
return (androidVersion >= 19) && (deviceType != DEVICE_TYPE_VR); // when ACTION_OPEN_DOCUMENT was added
517
case SYSPROP_HAS_FOLDER_BROWSER:
518
// Uses OPEN_DOCUMENT_TREE to let you select a folder.
519
// Doesn't actually mean it's usable though, in many early versions of Android
520
// this dialog is complete garbage and only lets you select subfolders of the Downloads folder.
521
return (androidVersion >= 21) && (deviceType != DEVICE_TYPE_VR); // when ACTION_OPEN_DOCUMENT_TREE was added
522
case SYSPROP_SUPPORTS_OPEN_FILE_IN_EDITOR:
523
return false; // Update if we add support in FileUtil.cpp: OpenFileInEditor
524
case SYSPROP_APP_GOLD:
525
#ifdef GOLD
526
return true;
527
#else
528
return false;
529
#endif
530
case SYSPROP_CAN_JIT:
531
return true;
532
case SYSPROP_ANDROID_SCOPED_STORAGE:
533
// We turn this on for Android 30+ (11) now that when we target Android 11+.
534
// Along with adding:
535
// android:preserveLegacyExternalStorage="true"
536
// To the already requested:
537
// android:requestLegacyExternalStorage="true"
538
//
539
// This will cause Android 11+ to still behave like Android 10 until the app
540
// is manually uninstalled. We can detect this state with
541
// Android_IsExternalStoragePreservedLegacy(), but most of the app will just see
542
// that scoped storage enforcement is disabled in this case.
543
if (androidVersion >= 30) {
544
// Here we do a check to see if we ended up in the preserveLegacyExternalStorage path.
545
// That won't last if the user uninstalls/reinstalls though, but would preserve the user
546
// experience for simple upgrades so maybe let's support it.
547
return !Android_IsExternalStoragePreservedLegacy();
548
} else {
549
return false;
550
}
551
case SYSPROP_HAS_KEYBOARD:
552
return deviceType != DEVICE_TYPE_VR;
553
case SYSPROP_HAS_ACCELEROMETER:
554
return deviceType == DEVICE_TYPE_MOBILE;
555
case SYSPROP_CAN_CREATE_SHORTCUT:
556
return false; // We can't create shortcuts directly from game code, but we can from the Android UI.
557
#ifndef HTTPS_NOT_AVAILABLE
558
case SYSPROP_SUPPORTS_HTTPS:
559
return !g_Config.bDisableHTTPS;
560
#endif
561
default:
562
return false;
563
}
564
}
565
566
std::string Android_GetInputDeviceDebugString() {
567
if (!nativeActivity) {
568
return "(N/A)";
569
}
570
auto env = getEnv();
571
572
jstring jparam = env->NewStringUTF("InputDevice");
573
jstring jstr = (jstring)env->CallObjectMethod(nativeActivity, getDebugString, jparam);
574
if (!jstr) {
575
env->DeleteLocalRef(jparam);
576
return "(N/A)";
577
}
578
579
const char *charArray = env->GetStringUTFChars(jstr, 0);
580
std::string retVal = charArray;
581
env->ReleaseStringUTFChars(jstr, charArray);
582
env->DeleteLocalRef(jstr);
583
env->DeleteLocalRef(jparam);
584
return retVal;
585
}
586
587
std::string GetJavaString(JNIEnv *env, jstring jstr) {
588
if (!jstr)
589
return "";
590
const char *str = env->GetStringUTFChars(jstr, 0);
591
std::string cpp_string = std::string(str);
592
env->ReleaseStringUTFChars(jstr, str);
593
return cpp_string;
594
}
595
596
extern "C" void Java_org_ppsspp_ppsspp_NativeActivity_registerCallbacks(JNIEnv *env, jobject obj) {
597
nativeActivity = env->NewGlobalRef(obj);
598
postCommand = env->GetMethodID(env->GetObjectClass(obj), "postCommand", "(Ljava/lang/String;Ljava/lang/String;)V");
599
getDebugString = env->GetMethodID(env->GetObjectClass(obj), "getDebugString", "(Ljava/lang/String;)Ljava/lang/String;");
600
_dbg_assert_(postCommand);
601
_dbg_assert_(getDebugString);
602
603
Android_RegisterStorageCallbacks(env, obj);
604
Android_StorageSetNativeActivity(nativeActivity);
605
}
606
607
extern "C" void Java_org_ppsspp_ppsspp_NativeActivity_unregisterCallbacks(JNIEnv *env, jobject obj) {
608
Android_StorageSetNativeActivity(nullptr);
609
env->DeleteGlobalRef(nativeActivity);
610
nativeActivity = nullptr;
611
}
612
613
// This is now only used as a trigger for GetAppInfo as a function to all before Init.
614
// On Android we don't use any of the values it returns.
615
extern "C" jboolean Java_org_ppsspp_ppsspp_NativeApp_isLandscape(JNIEnv *env, jclass) {
616
std::string app_name, app_nice_name, version;
617
bool landscape;
618
NativeGetAppInfo(&app_name, &app_nice_name, &landscape, &version);
619
return landscape;
620
}
621
622
// Allow the app to intercept the back button.
623
extern "C" jboolean Java_org_ppsspp_ppsspp_NativeApp_isAtTopLevel(JNIEnv *env, jclass) {
624
return NativeIsAtTopLevel();
625
}
626
627
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_audioConfig
628
(JNIEnv *env, jclass, jint optimalFPB, jint optimalSR) {
629
optimalFramesPerBuffer = optimalFPB;
630
optimalSampleRate = optimalSR;
631
}
632
633
// Easy way for the Java side to ask the C++ side for configuration options, such as
634
// the rotation lock which must be controlled from Java on Android.
635
static std::string QueryConfig(std::string query) {
636
char temp[128];
637
if (query == "screenRotation") {
638
INFO_LOG(Log::G3D, "g_Config.screenRotation = %d", g_Config.iScreenRotation);
639
snprintf(temp, sizeof(temp), "%d", g_Config.iScreenRotation);
640
return std::string(temp);
641
} else if (query == "immersiveMode") {
642
return std::string(g_Config.bImmersiveMode ? "1" : "0");
643
} else if (query == "sustainedPerformanceMode") {
644
return std::string(g_Config.bSustainedPerformanceMode ? "1" : "0");
645
} else if (query == "androidJavaGL") {
646
// If we're using Vulkan, we say no... need C++ to use Vulkan.
647
if (GetGPUBackend() == GPUBackend::VULKAN) {
648
return "false";
649
}
650
// Otherwise, some devices prefer the Java init so play it safe.
651
return "true";
652
} else {
653
return "";
654
}
655
}
656
657
extern "C" jstring Java_org_ppsspp_ppsspp_NativeApp_queryConfig
658
(JNIEnv *env, jclass, jstring jquery) {
659
std::string query = GetJavaString(env, jquery);
660
std::string result = QueryConfig(query);
661
jstring jresult = env->NewStringUTF(result.c_str());
662
return jresult;
663
}
664
665
static void parse_args(std::vector<std::string> &args, const std::string value) {
666
// Simple argument parser so we can take args from extra params.
667
const char *p = value.c_str();
668
669
while (*p != '\0') {
670
while (isspace(*p)) {
671
p++;
672
}
673
if (*p == '\0') {
674
break;
675
}
676
677
bool done = false;
678
bool quote = false;
679
std::string arg;
680
681
while (!done) {
682
size_t sz = strcspn(p, "\"\\ \r\n\t");
683
arg += std::string(p, sz);
684
p += sz;
685
686
switch (*p) {
687
case '"':
688
quote = !quote;
689
p++;
690
break;
691
692
case '\\':
693
p++;
694
arg += std::string(p, 1);
695
p++;
696
break;
697
698
case '\0':
699
done = true;
700
break;
701
702
default:
703
// If it's not the above, it's whitespace.
704
if (!quote) {
705
done = true;
706
} else {
707
sz = strspn(p, " \r\n\t");
708
arg += std::string(p, sz);
709
p += sz;
710
}
711
break;
712
}
713
}
714
715
args.push_back(arg);
716
717
while (isspace(*p)) {
718
p++;
719
}
720
}
721
}
722
723
// Need to use raw Android logging before NativeInit.
724
#define EARLY_LOG(...) __android_log_print(ANDROID_LOG_INFO, "PPSSPP", __VA_ARGS__)
725
726
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init
727
(JNIEnv * env, jclass, jstring jmodel, jint jdeviceType, jstring jlangRegion, jstring japkpath,
728
jstring jdataDir, jstring jexternalStorageDir, jstring jexternalFilesDir, jstring jNativeLibDir, jstring jadditionalStorageDirs, jstring jcacheDir, jstring jshortcutParam,
729
jint jAndroidVersion, jstring jboard) {
730
SetCurrentThreadName("androidInit");
731
732
// Makes sure we get early permission grants.
733
ProcessFrameCommands(env);
734
735
EARLY_LOG("NativeApp.init() -- begin");
736
PROFILE_INIT();
737
738
std::lock_guard<std::mutex> guard(renderLock);
739
renderer_inited = false;
740
exitRenderLoop = false;
741
androidVersion = jAndroidVersion;
742
deviceType = jdeviceType;
743
744
Path apkPath(GetJavaString(env, japkpath));
745
g_VFS.Register("", ZipFileReader::Create(apkPath, "assets/"));
746
747
systemName = GetJavaString(env, jmodel);
748
langRegion = GetJavaString(env, jlangRegion);
749
750
EARLY_LOG("NativeApp.init(): device name: '%s'", systemName.c_str());
751
752
std::string externalStorageDir = GetJavaString(env, jexternalStorageDir);
753
std::string additionalStorageDirsString = GetJavaString(env, jadditionalStorageDirs);
754
std::string externalFilesDir = GetJavaString(env, jexternalFilesDir);
755
std::string nativeLibDir = GetJavaString(env, jNativeLibDir);
756
757
g_externalDir = externalStorageDir;
758
g_extFilesDir = externalFilesDir;
759
g_nativeLibDir = nativeLibDir;
760
761
if (!additionalStorageDirsString.empty()) {
762
SplitString(additionalStorageDirsString, ':', g_additionalStorageDirs);
763
for (auto &str : g_additionalStorageDirs) {
764
EARLY_LOG("Additional storage: %s", str.c_str());
765
}
766
}
767
768
std::string user_data_path = GetJavaString(env, jdataDir);
769
if (user_data_path.size() > 0)
770
user_data_path += "/";
771
std::string shortcut_param = GetJavaString(env, jshortcutParam);
772
std::string cacheDir = GetJavaString(env, jcacheDir);
773
std::string buildBoard = GetJavaString(env, jboard);
774
boardName = buildBoard;
775
EARLY_LOG("NativeApp.init(): External storage path: %s", externalStorageDir.c_str());
776
EARLY_LOG("NativeApp.init(): Launch shortcut parameter: %s", shortcut_param.c_str());
777
778
std::string app_name;
779
std::string app_nice_name;
780
std::string version;
781
bool landscape;
782
783
// Unfortunately, on the Samsung Galaxy S7, this isn't in /proc/cpuinfo.
784
// We also can't read it from __system_property_get.
785
if (buildBoard == "universal8890") {
786
cpu_info.sQuirks.bExynos8890DifferingCachelineSizes = true;
787
}
788
789
NativeGetAppInfo(&app_name, &app_nice_name, &landscape, &version);
790
791
// If shortcut_param is not empty, pass it as additional arguments to the NativeInit() method.
792
// NativeInit() is expected to treat extra argument as boot_filename, which in turn will start game immediately.
793
// NOTE: Will only work if ppsspp started from Activity.onCreate(). Won't work if ppsspp app start from onResume().
794
795
std::vector<const char *> args;
796
std::vector<std::string> temp;
797
args.push_back(app_name.c_str());
798
if (!shortcut_param.empty()) {
799
EARLY_LOG("NativeInit shortcut param %s", shortcut_param.c_str());
800
parse_args(temp, shortcut_param);
801
for (const auto &arg : temp) {
802
args.push_back(arg.c_str());
803
}
804
}
805
806
NativeInit((int)args.size(), &args[0], user_data_path.c_str(), externalStorageDir.c_str(), cacheDir.c_str());
807
808
// In debug mode, don't allow creating software Vulkan devices (reject by VulkanMaybeAvailable).
809
// Needed for #16931.
810
#ifdef NDEBUG
811
if (!VulkanMayBeAvailable()) {
812
// If VulkanLoader decided on no viable backend, let's force Vulkan off in release builds at least.
813
g_Config.iGPUBackend = 0;
814
}
815
#endif
816
817
// No need to use EARLY_LOG anymore.
818
819
retry:
820
switch (g_Config.iGPUBackend) {
821
case (int)GPUBackend::OPENGL:
822
useCPUThread = true;
823
INFO_LOG(Log::System, "NativeApp.init() -- creating OpenGL context (JavaGL)");
824
graphicsContext = new AndroidJavaEGLGraphicsContext();
825
INFO_LOG(Log::System, "NativeApp.init() - launching emu thread");
826
EmuThreadStart();
827
break;
828
case (int)GPUBackend::VULKAN:
829
{
830
INFO_LOG(Log::System, "NativeApp.init() -- creating Vulkan context");
831
useCPUThread = false;
832
// The Vulkan render manager manages its own thread.
833
// We create and destroy the Vulkan graphics context in the app main thread though.
834
AndroidVulkanContext *ctx = new AndroidVulkanContext();
835
if (!ctx->InitAPI()) {
836
INFO_LOG(Log::System, "Failed to initialize Vulkan, switching to OpenGL");
837
g_Config.iGPUBackend = (int)GPUBackend::OPENGL;
838
SetGPUBackend(GPUBackend::OPENGL);
839
goto retry;
840
} else {
841
graphicsContext = ctx;
842
}
843
break;
844
}
845
default:
846
ERROR_LOG(Log::System, "NativeApp.init(): iGPUBackend %d not supported. Switching to OpenGL.", (int)g_Config.iGPUBackend);
847
g_Config.iGPUBackend = (int)GPUBackend::OPENGL;
848
goto retry;
849
}
850
851
if (IsVREnabled()) {
852
Version gitVer(PPSSPP_GIT_VERSION);
853
InitVROnAndroid(gJvm, nativeActivity, systemName.c_str(), gitVer.ToInteger(), "PPSSPP");
854
SetVRCallbacks(NativeAxis, NativeKey, NativeTouch);
855
}
856
}
857
858
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_audioInit(JNIEnv *, jclass) {
859
sampleRate = optimalSampleRate;
860
if (optimalSampleRate == 0) {
861
sampleRate = 44100;
862
}
863
if (optimalFramesPerBuffer > 0) {
864
framesPerBuffer = optimalFramesPerBuffer;
865
} else {
866
framesPerBuffer = 512;
867
}
868
869
// Some devices have totally bonkers buffer sizes like 8192. They will have terrible latency anyway, so to avoid having to
870
// create extra smart buffering code, we'll just let their regular mixer deal with it, missing the fast path (as if they had one...)
871
if (framesPerBuffer > 512) {
872
framesPerBuffer = 512;
873
sampleRate = 44100;
874
}
875
876
INFO_LOG(Log::Audio, "NativeApp.audioInit() -- Using OpenSL audio! frames/buffer: %i optimal sr: %i actual sr: %i", optimalFramesPerBuffer, optimalSampleRate, sampleRate);
877
if (!g_audioState) {
878
g_audioState = AndroidAudio_Init(&NativeMix, framesPerBuffer, sampleRate);
879
} else {
880
ERROR_LOG(Log::Audio, "Audio state already initialized");
881
}
882
}
883
884
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_audioShutdown(JNIEnv *, jclass) {
885
if (g_audioState) {
886
AndroidAudio_Shutdown(g_audioState);
887
g_audioState = nullptr;
888
} else {
889
ERROR_LOG(Log::Audio, "Audio state already shutdown!");
890
}
891
}
892
893
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_audioRecording_1SetSampleRate(JNIEnv *, jclass, jint sampleRate) {
894
AndroidAudio_Recording_SetSampleRate(g_audioState, sampleRate);
895
}
896
897
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_audioRecording_1Start(JNIEnv *, jclass) {
898
AndroidAudio_Recording_Start(g_audioState);
899
}
900
901
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_audioRecording_1Stop(JNIEnv *, jclass) {
902
AndroidAudio_Recording_Stop(g_audioState);
903
}
904
905
bool System_AudioRecordingIsAvailable() {
906
return true;
907
}
908
909
bool System_AudioRecordingState() {
910
return AndroidAudio_Recording_State(g_audioState);
911
}
912
913
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_resume(JNIEnv *, jclass) {
914
INFO_LOG(Log::System, "NativeApp.resume() - resuming audio");
915
AndroidAudio_Resume(g_audioState);
916
917
System_PostUIMessage(UIMessage::APP_RESUMED);
918
}
919
920
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_pause(JNIEnv *, jclass) {
921
INFO_LOG(Log::System, "NativeApp.pause() - pausing audio");
922
AndroidAudio_Pause(g_audioState);
923
}
924
925
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_shutdown(JNIEnv *, jclass) {
926
INFO_LOG(Log::System, "NativeApp.shutdown() -- begin");
927
928
if (renderer_inited && useCPUThread && graphicsContext) {
929
// Only used in Java EGL path.
930
931
// We can't lock renderLock here because the emu thread will be in NativeFrame
932
// which locks renderLock already, and only gets out once we call ThreadFrame()
933
// in a loop before, to empty the queue.
934
EmuThreadStop("shutdown");
935
INFO_LOG(Log::System, "BeginAndroidShutdown");
936
graphicsContext->BeginAndroidShutdown();
937
// Now, it could be that we had some frames queued up. Get through them.
938
// We're on the render thread, so this is synchronous.
939
do {
940
INFO_LOG(Log::System, "Executing graphicsContext->ThreadFrame to clear buffers");
941
} while (graphicsContext->ThreadFrame());
942
graphicsContext->ThreadEnd();
943
INFO_LOG(Log::System, "ThreadEnd called.");
944
graphicsContext->ShutdownFromRenderThread();
945
INFO_LOG(Log::System, "Graphics context now shut down from NativeApp_shutdown");
946
947
INFO_LOG(Log::System, "Joining emuthread");
948
EmuThreadJoin();
949
}
950
951
{
952
std::lock_guard<std::mutex> guard(renderLock);
953
954
if (graphicsContext) {
955
INFO_LOG(Log::G3D, "Shutting down renderer");
956
graphicsContext->Shutdown();
957
delete graphicsContext;
958
graphicsContext = nullptr;
959
renderer_inited = false;
960
} else {
961
INFO_LOG(Log::G3D, "Not shutting down renderer - not initialized");
962
}
963
964
NativeShutdown();
965
g_VFS.Clear();
966
}
967
968
{
969
std::lock_guard<std::mutex> guard(frameCommandLock);
970
while (frameCommands.size())
971
frameCommands.pop();
972
}
973
INFO_LOG(Log::System, "NativeApp.shutdown() -- end");
974
}
975
976
// JavaEGL. This doesn't get called on the Vulkan path.
977
// This gets called from onSurfaceCreated.
978
extern "C" bool Java_org_ppsspp_ppsspp_NativeRenderer_displayInit(JNIEnv * env, jobject obj) {
979
_assert_(useCPUThread);
980
981
INFO_LOG(Log::G3D, "NativeApp.displayInit()");
982
bool firstStart = !renderer_inited;
983
984
// We should be running on the render thread here.
985
std::string errorMessage;
986
if (renderer_inited) {
987
// Would be really nice if we could get something on the GL thread immediately when shutting down,
988
// but the only mechanism for handling lost devices seems to be that onSurfaceCreated is called again,
989
// which ends up calling displayInit.
990
991
INFO_LOG(Log::G3D, "NativeApp.displayInit() restoring");
992
EmuThreadStop("displayInit");
993
graphicsContext->BeginAndroidShutdown();
994
INFO_LOG(Log::G3D, "BeginAndroidShutdown. Looping until emu thread done...");
995
// Skipping GL calls here because the old context is lost.
996
while (graphicsContext->ThreadFrame()) {
997
continue;
998
}
999
INFO_LOG(Log::G3D, "Joining emu thread");
1000
EmuThreadJoin();
1001
1002
graphicsContext->ThreadEnd();
1003
graphicsContext->ShutdownFromRenderThread();
1004
1005
INFO_LOG(Log::G3D, "Shut down both threads. Now let's bring it up again!");
1006
1007
if (!graphicsContext->InitFromRenderThread(nullptr, 0, 0, 0, 0)) {
1008
System_Toast("Graphics initialization failed. Quitting.");
1009
return false;
1010
}
1011
1012
graphicsContext->GetDrawContext()->SetErrorCallback([](const char *shortDesc, const char *details, void *userdata) {
1013
g_OSD.Show(OSDType::MESSAGE_ERROR, details, 5.0);
1014
}, nullptr);
1015
1016
EmuThreadStart();
1017
1018
graphicsContext->ThreadStart();
1019
1020
INFO_LOG(Log::G3D, "Restored.");
1021
} else {
1022
INFO_LOG(Log::G3D, "NativeApp.displayInit() first time");
1023
if (!graphicsContext || !graphicsContext->InitFromRenderThread(nullptr, 0, 0, 0, 0)) {
1024
System_Toast("Graphics initialization failed. Quitting.");
1025
return false;
1026
}
1027
1028
graphicsContext->GetDrawContext()->SetErrorCallback([](const char *shortDesc, const char *details, void *userdata) {
1029
g_OSD.Show(OSDType::MESSAGE_ERROR, details, 5.0);
1030
}, nullptr);
1031
1032
graphicsContext->ThreadStart();
1033
renderer_inited = true;
1034
}
1035
1036
System_PostUIMessage(UIMessage::RECREATE_VIEWS);
1037
1038
if (IsVREnabled()) {
1039
EnterVR(firstStart, graphicsContext->GetAPIContext());
1040
}
1041
return true;
1042
}
1043
1044
static void recalculateDpi() {
1045
g_display.dpi = display_dpi_x;
1046
g_display.dpi_scale_x = 240.0f / display_dpi_x;
1047
g_display.dpi_scale_y = 240.0f / display_dpi_y;
1048
g_display.dpi_scale_real_x = g_display.dpi_scale_x;
1049
g_display.dpi_scale_real_y = g_display.dpi_scale_y;
1050
1051
g_display.dp_xres = display_xres * g_display.dpi_scale_x;
1052
g_display.dp_yres = display_yres * g_display.dpi_scale_y;
1053
1054
g_display.pixel_in_dps_x = (float)g_display.pixel_xres / g_display.dp_xres;
1055
g_display.pixel_in_dps_y = (float)g_display.pixel_yres / g_display.dp_yres;
1056
1057
INFO_LOG(Log::G3D, "RecalcDPI: display_xres=%d display_yres=%d pixel_xres=%d pixel_yres=%d", display_xres, display_yres, g_display.pixel_xres, g_display.pixel_yres);
1058
INFO_LOG(Log::G3D, "RecalcDPI: g_dpi=%f g_dpi_scale_x=%f g_dpi_scale_y=%f dp_xres=%d dp_yres=%d", g_display.dpi, g_display.dpi_scale_x, g_display.dpi_scale_y, g_display.dp_xres, g_display.dp_yres);
1059
}
1060
1061
extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_backbufferResize(JNIEnv *, jclass, jint bufw, jint bufh, jint format) {
1062
INFO_LOG(Log::System, "NativeApp.backbufferResize(%d x %d)", bufw, bufh);
1063
1064
bool new_size = g_display.pixel_xres != bufw || g_display.pixel_yres != bufh;
1065
int old_w = g_display.pixel_xres;
1066
int old_h = g_display.pixel_yres;
1067
// pixel_*res is the backbuffer resolution.
1068
g_display.pixel_xres = bufw;
1069
g_display.pixel_yres = bufh;
1070
backbuffer_format = format;
1071
1072
if (IsVREnabled()) {
1073
GetVRResolutionPerEye(&g_display.pixel_xres, &g_display.pixel_yres);
1074
}
1075
1076
recalculateDpi();
1077
1078
if (new_size) {
1079
INFO_LOG(Log::G3D, "Size change detected (previously %d,%d) - calling NativeResized()", old_w, old_h);
1080
NativeResized();
1081
} else {
1082
INFO_LOG(Log::G3D, "NativeApp::backbufferResize: Size didn't change.");
1083
}
1084
}
1085
1086
void System_Notify(SystemNotification notification) {
1087
switch (notification) {
1088
case SystemNotification::ROTATE_UPDATED:
1089
PushCommand("rotate", "");
1090
break;
1091
case SystemNotification::FORCE_RECREATE_ACTIVITY:
1092
PushCommand("recreate", "");
1093
break;
1094
case SystemNotification::IMMERSIVE_MODE_CHANGE:
1095
PushCommand("immersive", "");
1096
break;
1097
case SystemNotification::SUSTAINED_PERF_CHANGE:
1098
PushCommand("sustainedPerfMode", "");
1099
break;
1100
case SystemNotification::TEST_JAVA_EXCEPTION:
1101
PushCommand("testException", "This is a test exception");
1102
break;
1103
default:
1104
break;
1105
}
1106
}
1107
1108
bool System_MakeRequest(SystemRequestType type, int requestId, const std::string &param1, const std::string &param2, int64_t param3, int64_t param4) {
1109
switch (type) {
1110
case SystemRequestType::EXIT_APP:
1111
PushCommand("finish", "");
1112
return true;
1113
case SystemRequestType::RESTART_APP:
1114
PushCommand("graphics_restart", param1);
1115
return true;
1116
case SystemRequestType::RECREATE_ACTIVITY:
1117
PushCommand("recreate", param1);
1118
return true;
1119
case SystemRequestType::COPY_TO_CLIPBOARD:
1120
PushCommand("copy_to_clipboard", param1);
1121
return true;
1122
case SystemRequestType::INPUT_TEXT_MODAL:
1123
{
1124
std::string serialized = StringFromFormat("%d:@:%s:@:%s", requestId, param1.c_str(), param2.c_str());
1125
PushCommand("inputbox", serialized.c_str());
1126
return true;
1127
}
1128
case SystemRequestType::BROWSE_FOR_IMAGE:
1129
PushCommand("browse_image", StringFromFormat("%d", requestId));
1130
return true;
1131
case SystemRequestType::BROWSE_FOR_FILE:
1132
{
1133
BrowseFileType fileType = (BrowseFileType)param3;
1134
std::string params = StringFromFormat("%d", requestId);
1135
switch (fileType) {
1136
case BrowseFileType::SOUND_EFFECT:
1137
PushCommand("browse_file_audio", params);
1138
break;
1139
case BrowseFileType::ZIP:
1140
PushCommand("browse_file_zip", params);
1141
break;
1142
default:
1143
PushCommand("browse_file", params);
1144
break;
1145
}
1146
return true;
1147
}
1148
case SystemRequestType::BROWSE_FOR_FOLDER:
1149
PushCommand("browse_folder", StringFromFormat("%d", requestId));
1150
return true;
1151
1152
case SystemRequestType::CAMERA_COMMAND:
1153
PushCommand("camera_command", param1);
1154
return true;
1155
case SystemRequestType::GPS_COMMAND:
1156
PushCommand("gps_command", param1);
1157
return true;
1158
case SystemRequestType::INFRARED_COMMAND:
1159
PushCommand("infrared_command", param1);
1160
return true;
1161
case SystemRequestType::MICROPHONE_COMMAND:
1162
PushCommand("microphone_command", param1);
1163
return true;
1164
case SystemRequestType::SHARE_TEXT:
1165
PushCommand("share_text", param1);
1166
return true;
1167
case SystemRequestType::SET_KEEP_SCREEN_BRIGHT:
1168
PushCommand("set_keep_screen_bright", param3 ? "on" : "off");
1169
return true;
1170
case SystemRequestType::SHOW_FILE_IN_FOLDER:
1171
PushCommand("show_folder", param1);
1172
return true;
1173
default:
1174
return false;
1175
}
1176
}
1177
1178
extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_sendRequestResult(JNIEnv *env, jclass, jint jrequestID, jboolean result, jstring jvalue, jint jintValue) {
1179
std::string value = jvalue ? GetJavaString(env, jvalue) : "(no value)";
1180
INFO_LOG(Log::System, "Received result of request %d from Java: %d: %d '%s'", jrequestID, (int)result, jintValue, value.c_str());
1181
if (result) {
1182
g_requestManager.PostSystemSuccess(jrequestID, value.c_str());
1183
} else {
1184
g_requestManager.PostSystemFailure(jrequestID);
1185
}
1186
}
1187
1188
extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayRender(JNIEnv *env, jobject obj) {
1189
// This doesn't get called on the Vulkan path.
1190
_assert_(useCPUThread);
1191
1192
static bool hasSetThreadName = false;
1193
if (!hasSetThreadName) {
1194
hasSetThreadName = true;
1195
SetCurrentThreadName("AndroidRender");
1196
}
1197
1198
if (IsVREnabled() && !StartVRRender())
1199
return;
1200
1201
// This is the "GPU thread". Call ThreadFrame.
1202
if (!graphicsContext || !graphicsContext->ThreadFrame()) {
1203
return;
1204
}
1205
1206
if (IsVREnabled()) {
1207
UpdateVRInput(g_Config.bHapticFeedback, g_display.dpi_scale_x, g_display.dpi_scale_y);
1208
FinishVRRender();
1209
}
1210
}
1211
1212
void System_AskForPermission(SystemPermission permission) {
1213
switch (permission) {
1214
case SYSTEM_PERMISSION_STORAGE:
1215
PushCommand("ask_permission", "storage");
1216
break;
1217
}
1218
}
1219
1220
PermissionStatus System_GetPermissionStatus(SystemPermission permission) {
1221
if (androidVersion < 23) {
1222
return PERMISSION_STATUS_GRANTED;
1223
} else {
1224
return permissions[permission];
1225
}
1226
}
1227
1228
extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_touch
1229
(JNIEnv *, jclass, float x, float y, int code, int pointerId) {
1230
if (!renderer_inited)
1231
return;
1232
TouchInput touch;
1233
touch.id = pointerId;
1234
touch.x = x * g_display.dpi_scale_x;
1235
touch.y = y * g_display.dpi_scale_y;
1236
touch.flags = code;
1237
NativeTouch(touch);
1238
}
1239
1240
extern "C" jboolean Java_org_ppsspp_ppsspp_NativeApp_keyDown(JNIEnv *, jclass, jint deviceId, jint key, jboolean isRepeat) {
1241
if (!renderer_inited) {
1242
return false; // could probably return true here too..
1243
}
1244
if (key == 0 && deviceId >= DEVICE_ID_PAD_0 && deviceId <= DEVICE_ID_PAD_9) {
1245
// Ignore keycode 0 from pads. Stadia controllers seem to produce them when pressing L2/R2 for some reason, confusing things.
1246
return true; // need to eat the key so it doesn't go through legacy path
1247
}
1248
1249
KeyInput keyInput;
1250
keyInput.deviceId = (InputDeviceID)deviceId;
1251
keyInput.keyCode = (InputKeyCode)key;
1252
keyInput.flags = KEY_DOWN;
1253
if (isRepeat) {
1254
keyInput.flags |= KEY_IS_REPEAT;
1255
}
1256
return NativeKey(keyInput);
1257
}
1258
1259
extern "C" jboolean Java_org_ppsspp_ppsspp_NativeApp_keyUp(JNIEnv *, jclass, jint deviceId, jint key) {
1260
if (!renderer_inited) {
1261
return false; // could probably return true here too..
1262
}
1263
if (key == 0 && deviceId >= DEVICE_ID_PAD_0 && deviceId <= DEVICE_ID_PAD_9) {
1264
// Ignore keycode 0 from pads. Stadia controllers seem to produce them when pressing L2/R2 for some reason, confusing things.
1265
return true; // need to eat the key so it doesn't go through legacy path
1266
}
1267
1268
KeyInput keyInput;
1269
keyInput.deviceId = (InputDeviceID)deviceId;
1270
keyInput.keyCode = (InputKeyCode)key;
1271
keyInput.flags = KEY_UP;
1272
return NativeKey(keyInput);
1273
}
1274
1275
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_joystickAxis(
1276
JNIEnv *env, jclass, jint deviceId, jintArray axisIds, jfloatArray values, jint count) {
1277
if (!renderer_inited)
1278
return;
1279
1280
AxisInput *axis = new AxisInput[count];
1281
_dbg_assert_(count <= env->GetArrayLength(axisIds));
1282
_dbg_assert_(count <= env->GetArrayLength(values));
1283
jint *axisIdBuffer = env->GetIntArrayElements(axisIds, NULL);
1284
jfloat *valueBuffer = env->GetFloatArrayElements(values, NULL);
1285
1286
// These are dirty-filtered on the Java side.
1287
for (int i = 0; i < count; i++) {
1288
axis[i].deviceId = (InputDeviceID)(int)deviceId;
1289
axis[i].axisId = (InputAxis)(int)axisIdBuffer[i];
1290
axis[i].value = valueBuffer[i];
1291
}
1292
NativeAxis(axis, count);
1293
delete[] axis;
1294
}
1295
1296
extern "C" jboolean Java_org_ppsspp_ppsspp_NativeApp_mouseWheelEvent(
1297
JNIEnv *env, jclass, jint stick, jfloat x, jfloat y) {
1298
if (!renderer_inited)
1299
return false;
1300
// TODO: Mousewheel should probably be an axis instead.
1301
int wheelDelta = y * 30.0f;
1302
if (wheelDelta > 500) wheelDelta = 500;
1303
if (wheelDelta < -500) wheelDelta = -500;
1304
1305
KeyInput key;
1306
key.deviceId = DEVICE_ID_MOUSE;
1307
if (wheelDelta < 0) {
1308
key.keyCode = NKCODE_EXT_MOUSEWHEEL_DOWN;
1309
wheelDelta = -wheelDelta;
1310
} else {
1311
key.keyCode = NKCODE_EXT_MOUSEWHEEL_UP;
1312
}
1313
// There's no separate keyup event for mousewheel events,
1314
// so we release it with a slight delay.
1315
key.flags = KEY_DOWN | KEY_HASWHEELDELTA | (wheelDelta << 16);
1316
NativeKey(key);
1317
return true;
1318
}
1319
1320
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_mouseDelta(
1321
JNIEnv * env, jclass, jfloat x, jfloat y) {
1322
if (!renderer_inited)
1323
return;
1324
NativeMouseDelta(x, y);
1325
}
1326
1327
extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_accelerometer(JNIEnv *, jclass, float x, float y, float z) {
1328
if (!renderer_inited)
1329
return;
1330
NativeAccelerometer(x, y, z);
1331
}
1332
1333
extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_sendMessageFromJava(JNIEnv *env, jclass, jstring jmessage, jstring jparam) {
1334
std::string msg = GetJavaString(env, jmessage);
1335
std::string prm = GetJavaString(env, jparam);
1336
1337
// A bit ugly, see InputDeviceState.java.
1338
static InputDeviceID nextInputDeviceID = DEVICE_ID_ANY;
1339
1340
// Some messages are caught by app-android. TODO: Should be all.
1341
if (msg == "moga") {
1342
mogaVersion = prm;
1343
} else if (msg == "permission_pending") {
1344
INFO_LOG(Log::System, "STORAGE PERMISSION: PENDING");
1345
// TODO: Add support for other permissions
1346
permissions[SYSTEM_PERMISSION_STORAGE] = PERMISSION_STATUS_PENDING;
1347
// Don't need to send along, nothing else is listening.
1348
} else if (msg == "permission_denied") {
1349
INFO_LOG(Log::System, "STORAGE PERMISSION: DENIED");
1350
permissions[SYSTEM_PERMISSION_STORAGE] = PERMISSION_STATUS_DENIED;
1351
// Don't need to send along, nothing else is listening.
1352
} else if (msg == "permission_granted") {
1353
INFO_LOG(Log::System, "STORAGE PERMISSION: GRANTED");
1354
permissions[SYSTEM_PERMISSION_STORAGE] = PERMISSION_STATUS_GRANTED;
1355
// Send along.
1356
System_PostUIMessage(UIMessage::PERMISSION_GRANTED, prm);
1357
} else if (msg == "sustained_perf_supported") {
1358
sustainedPerfSupported = true;
1359
} else if (msg == "safe_insets") {
1360
// INFO_LOG(Log::System, "Got insets: %s", prm.c_str());
1361
// We don't bother with supporting exact rectangular regions. Safe insets are good enough.
1362
int left, right, top, bottom;
1363
if (4 == sscanf(prm.c_str(), "%d:%d:%d:%d", &left, &right, &top, &bottom)) {
1364
g_safeInsetLeft = (float)left * g_display.dpi_scale_x;
1365
g_safeInsetRight = (float)right * g_display.dpi_scale_x;
1366
g_safeInsetTop = (float)top * g_display.dpi_scale_y;
1367
g_safeInsetBottom = (float)bottom * g_display.dpi_scale_y;
1368
}
1369
} else if (msg == "inputDeviceConnectedID") {
1370
nextInputDeviceID = (InputDeviceID)parseLong(prm);
1371
} else if (msg == "inputDeviceConnected") {
1372
KeyMap::NotifyPadConnected(nextInputDeviceID, prm);
1373
} else if (msg == "core_powerSaving") {
1374
// Forward.
1375
System_PostUIMessage(UIMessage::POWER_SAVING, prm);
1376
} else if (msg == "exception") {
1377
g_OSD.Show(OSDType::MESSAGE_ERROR, std::string("Java Exception"), prm, 10.0f);
1378
} else if (msg == "shortcutParam") {
1379
if (prm.empty()) {
1380
WARN_LOG(Log::System, "shortcutParam empty");
1381
return;
1382
}
1383
INFO_LOG(Log::System, "shortcutParam received: %s", prm.c_str());
1384
System_PostUIMessage(UIMessage::REQUEST_GAME_BOOT, StripQuotes(prm));
1385
} else {
1386
ERROR_LOG(Log::System, "Got unexpected message from Java, ignoring: %s / %s", msg.c_str(), prm.c_str());
1387
}
1388
}
1389
1390
void correctRatio(int &sz_x, int &sz_y, float scale) {
1391
float x = (float)sz_x;
1392
float y = (float)sz_y;
1393
float ratio = x / y;
1394
INFO_LOG(Log::G3D, "CorrectRatio: Considering size: %0.2f/%0.2f=%0.2f for scale %f", x, y, ratio, scale);
1395
float targetRatio;
1396
1397
// Try to get the longest dimension to match scale*PSP resolution.
1398
if (x >= y) {
1399
targetRatio = 480.0f / 272.0f;
1400
x = 480.f * scale;
1401
y = 272.f * scale;
1402
} else {
1403
targetRatio = 272.0f / 480.0f;
1404
x = 272.0f * scale;
1405
y = 480.0f * scale;
1406
}
1407
1408
float correction = targetRatio / ratio;
1409
INFO_LOG(Log::G3D, "Target ratio: %0.2f ratio: %0.2f correction: %0.2f", targetRatio, ratio, correction);
1410
if (ratio < targetRatio) {
1411
y *= correction;
1412
} else {
1413
x /= correction;
1414
}
1415
1416
sz_x = x;
1417
sz_y = y;
1418
INFO_LOG(Log::G3D, "Corrected ratio: %dx%d", sz_x, sz_y);
1419
}
1420
1421
void getDesiredBackbufferSize(int &sz_x, int &sz_y) {
1422
sz_x = display_xres;
1423
sz_y = display_yres;
1424
1425
int scale = g_Config.iAndroidHwScale;
1426
// Override hw scale for TV type devices.
1427
if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_TV)
1428
scale = 0;
1429
1430
if (scale == 1) {
1431
// If g_Config.iInternalResolution is also set to Auto (1), we fall back to "Device resolution" (0). It works out.
1432
scale = g_Config.iInternalResolution;
1433
} else if (scale >= 2) {
1434
scale -= 1;
1435
}
1436
1437
int max_res = std::max(System_GetPropertyInt(SYSPROP_DISPLAY_XRES), System_GetPropertyInt(SYSPROP_DISPLAY_YRES)) / 480 + 1;
1438
1439
scale = std::min(scale, max_res);
1440
1441
if (scale > 0) {
1442
correctRatio(sz_x, sz_y, scale);
1443
} else {
1444
sz_x = 0;
1445
sz_y = 0;
1446
}
1447
}
1448
1449
extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_setDisplayParameters(JNIEnv *, jclass, jint xres, jint yres, jint dpi, jfloat refreshRate) {
1450
INFO_LOG(Log::G3D, "NativeApp.setDisplayParameters(%d x %d, dpi=%d, refresh=%0.2f)", xres, yres, dpi, refreshRate);
1451
1452
if (IsVREnabled()) {
1453
int width, height;
1454
GetVRResolutionPerEye(&width, &height);
1455
xres = width;
1456
yres = height * 272 / 480;
1457
dpi = 320;
1458
}
1459
1460
bool changed = false;
1461
changed = changed || display_xres != xres || display_yres != yres;
1462
changed = changed || display_dpi_x != dpi || display_dpi_y != dpi;
1463
changed = changed || g_display.display_hz != refreshRate;
1464
1465
if (changed) {
1466
display_xres = xres;
1467
display_yres = yres;
1468
display_dpi_x = dpi;
1469
display_dpi_y = dpi;
1470
g_display.display_hz = refreshRate;
1471
1472
recalculateDpi();
1473
NativeResized();
1474
}
1475
}
1476
1477
extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_computeDesiredBackbufferDimensions() {
1478
getDesiredBackbufferSize(desiredBackbufferSizeX, desiredBackbufferSizeY);
1479
}
1480
1481
extern "C" jint JNICALL Java_org_ppsspp_ppsspp_NativeApp_getDesiredBackbufferWidth(JNIEnv *, jclass) {
1482
return desiredBackbufferSizeX;
1483
}
1484
1485
extern "C" jint JNICALL Java_org_ppsspp_ppsspp_NativeApp_getDesiredBackbufferHeight(JNIEnv *, jclass) {
1486
return desiredBackbufferSizeY;
1487
}
1488
1489
extern "C" jint JNICALL Java_org_ppsspp_ppsspp_NativeApp_getDisplayFramerateMode(JNIEnv *, jclass) {
1490
return g_Config.iDisplayFramerateMode;
1491
}
1492
1493
std::vector<std::string> System_GetCameraDeviceList() {
1494
jclass cameraClass = findClass("org/ppsspp/ppsspp/CameraHelper");
1495
jmethodID deviceListMethod = getEnv()->GetStaticMethodID(cameraClass, "getDeviceList", "()Ljava/util/ArrayList;");
1496
jobject deviceListObject = getEnv()->CallStaticObjectMethod(cameraClass, deviceListMethod);
1497
jclass arrayListClass = getEnv()->FindClass("java/util/ArrayList");
1498
jmethodID arrayListSize = getEnv()->GetMethodID(arrayListClass, "size", "()I");
1499
jmethodID arrayListGet = getEnv()->GetMethodID(arrayListClass, "get", "(I)Ljava/lang/Object;");
1500
1501
jint arrayListObjectLen = getEnv()->CallIntMethod(deviceListObject, arrayListSize);
1502
std::vector<std::string> deviceListVector;
1503
1504
for (int i = 0; i < arrayListObjectLen; i++) {
1505
jstring dev = static_cast<jstring>(getEnv()->CallObjectMethod(deviceListObject, arrayListGet, i));
1506
const char *cdev = getEnv()->GetStringUTFChars(dev, nullptr);
1507
if (!cdev) {
1508
getEnv()->DeleteLocalRef(dev);
1509
continue;
1510
}
1511
deviceListVector.push_back(std::string(cdev));
1512
getEnv()->ReleaseStringUTFChars(dev, cdev);
1513
getEnv()->DeleteLocalRef(dev);
1514
}
1515
return deviceListVector;
1516
}
1517
1518
extern "C" jint Java_org_ppsspp_ppsspp_NativeApp_getSelectedCamera(JNIEnv *, jclass) {
1519
int cameraId = 0;
1520
sscanf(g_Config.sCameraDevice.c_str(), "%d:", &cameraId);
1521
return cameraId;
1522
}
1523
1524
extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_setGpsDataAndroid(JNIEnv *, jclass,
1525
jlong time, jfloat hdop, jfloat latitude, jfloat longitude, jfloat altitude, jfloat speed, jfloat bearing) {
1526
GPS::setGpsData(time, hdop, latitude, longitude, altitude, speed, bearing);
1527
}
1528
1529
extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_setSatInfoAndroid(JNIEnv *, jclass,
1530
jshort index, jshort id, jshort elevation, jshort azimuth, jshort snr, jshort good) {
1531
GPS::setSatInfo(index, id, elevation, azimuth, snr, good);
1532
}
1533
1534
extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_pushCameraImageAndroid(JNIEnv *env, jclass, jbyteArray image) {
1535
if (image != NULL) {
1536
jlong size = env->GetArrayLength(image);
1537
jbyte* buffer = env->GetByteArrayElements(image, NULL);
1538
Camera::pushCameraImage(size, (unsigned char *)buffer);
1539
env->ReleaseByteArrayElements(image, buffer, JNI_ABORT);
1540
}
1541
}
1542
1543
// Call this under frameCommandLock.
1544
static void ProcessFrameCommands(JNIEnv *env) {
1545
while (!frameCommands.empty()) {
1546
FrameCommand frameCmd;
1547
frameCmd = frameCommands.front();
1548
frameCommands.pop();
1549
1550
INFO_LOG(Log::System, "frameCommand '%s' '%s'", frameCmd.command.c_str(), frameCmd.params.c_str());
1551
1552
jstring cmd = env->NewStringUTF(frameCmd.command.c_str());
1553
jstring param = env->NewStringUTF(frameCmd.params.c_str());
1554
env->CallVoidMethod(nativeActivity, postCommand, cmd, param);
1555
env->DeleteLocalRef(cmd);
1556
env->DeleteLocalRef(param);
1557
}
1558
}
1559
1560
std::thread g_renderLoopThread;
1561
1562
static void VulkanEmuThread(ANativeWindow *wnd);
1563
1564
// This runs in Vulkan mode only.
1565
// This handles the entire lifecycle of the Vulkan context, init and exit.
1566
extern "C" bool JNICALL Java_org_ppsspp_ppsspp_NativeActivity_runVulkanRenderLoop(JNIEnv * env, jobject obj, jobject _surf) {
1567
_assert_(!useCPUThread);
1568
1569
if (!graphicsContext) {
1570
ERROR_LOG(Log::G3D, "runVulkanRenderLoop: Tried to enter without a created graphics context.");
1571
return false;
1572
}
1573
1574
if (g_renderLoopThread.joinable()) {
1575
ERROR_LOG(Log::G3D, "runVulkanRenderLoop: Already running");
1576
return false;
1577
}
1578
1579
ANativeWindow *wnd = _surf ? ANativeWindow_fromSurface(env, _surf) : nullptr;
1580
1581
if (!wnd) {
1582
// This shouldn't ever happen.
1583
ERROR_LOG(Log::G3D, "Error: Surface is null.");
1584
renderLoopRunning = false;
1585
return false;
1586
}
1587
1588
g_renderLoopThread = std::thread(VulkanEmuThread, wnd);
1589
return true;
1590
}
1591
1592
extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeActivity_requestExitVulkanRenderLoop(JNIEnv * env, jobject obj) {
1593
if (!renderLoopRunning) {
1594
ERROR_LOG(Log::System, "Render loop already exited");
1595
return;
1596
}
1597
_assert_(g_renderLoopThread.joinable());
1598
exitRenderLoop = true;
1599
g_renderLoopThread.join();
1600
_assert_(!g_renderLoopThread.joinable());
1601
g_renderLoopThread = std::thread();
1602
}
1603
1604
// TODO: Merge with the Win32 EmuThread and so on, and the Java EmuThread?
1605
static void VulkanEmuThread(ANativeWindow *wnd) {
1606
SetCurrentThreadName("EmuThread");
1607
1608
AndroidJNIThreadContext ctx;
1609
JNIEnv *env = getEnv();
1610
1611
if (!graphicsContext) {
1612
ERROR_LOG(Log::G3D, "runVulkanRenderLoop: Tried to enter without a created graphics context.");
1613
renderLoopRunning = false;
1614
exitRenderLoop = false;
1615
return;
1616
}
1617
1618
if (exitRenderLoop) {
1619
WARN_LOG(Log::G3D, "runVulkanRenderLoop: ExitRenderLoop requested at start, skipping the whole thing.");
1620
renderLoopRunning = false;
1621
exitRenderLoop = false;
1622
return;
1623
}
1624
1625
// This is up here to prevent race conditions, in case we pause during init.
1626
renderLoopRunning = true;
1627
1628
WARN_LOG(Log::G3D, "runVulkanRenderLoop. display_xres=%d display_yres=%d desiredBackbufferSizeX=%d desiredBackbufferSizeY=%d",
1629
display_xres, display_yres, desiredBackbufferSizeX, desiredBackbufferSizeY);
1630
1631
if (!graphicsContext->InitFromRenderThread(wnd, desiredBackbufferSizeX, desiredBackbufferSizeY, backbuffer_format, androidVersion)) {
1632
// On Android, if we get here, really no point in continuing.
1633
// The UI is supposed to render on any device both on OpenGL and Vulkan. If either of those don't work
1634
// on a device, we blacklist it. Hopefully we should have already failed in InitAPI anyway and reverted to GL back then.
1635
ERROR_LOG(Log::G3D, "Failed to initialize graphics context.");
1636
System_Toast("Failed to initialize graphics context.");
1637
1638
delete graphicsContext;
1639
graphicsContext = nullptr;
1640
renderLoopRunning = false;
1641
return;
1642
}
1643
1644
if (!exitRenderLoop) {
1645
if (!NativeInitGraphics(graphicsContext)) {
1646
ERROR_LOG(Log::G3D, "Failed to initialize graphics.");
1647
// Gonna be in a weird state here..
1648
}
1649
graphicsContext->ThreadStart();
1650
renderer_inited = true;
1651
1652
while (!exitRenderLoop) {
1653
{
1654
std::lock_guard<std::mutex> renderGuard(renderLock);
1655
NativeFrame(graphicsContext);
1656
}
1657
{
1658
std::lock_guard<std::mutex> guard(frameCommandLock);
1659
ProcessFrameCommands(env);
1660
}
1661
}
1662
INFO_LOG(Log::G3D, "Leaving Vulkan main loop.");
1663
} else {
1664
INFO_LOG(Log::G3D, "Not entering main loop.");
1665
}
1666
1667
NativeShutdownGraphics();
1668
1669
renderer_inited = false;
1670
graphicsContext->ThreadEnd();
1671
1672
// Shut the graphics context down to the same state it was in when we entered the render thread.
1673
INFO_LOG(Log::G3D, "Shutting down graphics context...");
1674
graphicsContext->ShutdownFromRenderThread();
1675
renderLoopRunning = false;
1676
exitRenderLoop = false;
1677
1678
WARN_LOG(Log::G3D, "Render loop function exited.");
1679
}
1680
1681
// NOTE: This is defunct and not working, due to how the Android storage functions currently require
1682
// a PpssppActivity specifically and we don't have one here.
1683
extern "C" jstring Java_org_ppsspp_ppsspp_ShortcutActivity_queryGameName(JNIEnv * env, jclass, jstring jpath) {
1684
bool teardownThreadManager = false;
1685
if (!g_threadManager.IsInitialized()) {
1686
INFO_LOG(Log::System, "No thread manager - initializing one");
1687
// Need a thread manager.
1688
teardownThreadManager = true;
1689
g_threadManager.Init(1, 1);
1690
}
1691
1692
Path path = Path(GetJavaString(env, jpath));
1693
1694
INFO_LOG(Log::System, "queryGameName(%s)", path.c_str());
1695
1696
std::string result = "";
1697
1698
GameInfoCache *cache = new GameInfoCache();
1699
std::shared_ptr<GameInfo> info = cache->GetInfo(nullptr, path, GameInfoFlags::PARAM_SFO);
1700
// Wait until it's done: this is synchronous, unfortunately.
1701
if (info) {
1702
INFO_LOG(Log::System, "GetInfo successful, waiting");
1703
while (!info->Ready(GameInfoFlags::PARAM_SFO)) {
1704
sleep_ms(1);
1705
}
1706
INFO_LOG(Log::System, "Done waiting");
1707
if (info->fileType != IdentifiedFileType::UNKNOWN) {
1708
result = info->GetTitle();
1709
1710
// Pretty arbitrary, but the home screen will often truncate titles.
1711
// Let's remove "The " from names since it's common in English titles.
1712
if (result.length() > strlen("The ") && startsWithNoCase(result, "The ")) {
1713
result = result.substr(strlen("The "));
1714
}
1715
1716
INFO_LOG(Log::System, "queryGameName: Got '%s'", result.c_str());
1717
} else {
1718
INFO_LOG(Log::System, "queryGameName: Filetype unknown");
1719
}
1720
} else {
1721
INFO_LOG(Log::System, "No info from cache");
1722
}
1723
delete cache;
1724
1725
if (teardownThreadManager) {
1726
g_threadManager.Teardown();
1727
}
1728
1729
return env->NewStringUTF(result.c_str());
1730
}
1731
1732
1733
extern "C"
1734
JNIEXPORT jbyteArray JNICALL
1735
Java_org_ppsspp_ppsspp_ShortcutActivity_queryGameIcon(JNIEnv * env, jclass clazz, jstring jpath) {
1736
bool teardownThreadManager = false;
1737
if (!g_threadManager.IsInitialized()) {
1738
INFO_LOG(Log::System, "No thread manager - initializing one");
1739
// Need a thread manager.
1740
teardownThreadManager = true;
1741
g_threadManager.Init(1, 1);
1742
}
1743
// TODO: implement requestIcon()
1744
1745
Path path = Path(GetJavaString(env, jpath));
1746
1747
INFO_LOG(Log::System, "queryGameIcon(%s)", path.c_str());
1748
1749
jbyteArray result = nullptr;
1750
1751
GameInfoCache *cache = new GameInfoCache();
1752
std::shared_ptr<GameInfo> info = cache->GetInfo(nullptr, path, GameInfoFlags::ICON);
1753
// Wait until it's done: this is synchronous, unfortunately.
1754
if (info) {
1755
INFO_LOG(Log::System, "GetInfo successful, waiting");
1756
int attempts = 1000;
1757
while (!info->Ready(GameInfoFlags::ICON)) {
1758
sleep_ms(1);
1759
attempts--;
1760
if (!attempts) {
1761
break;
1762
}
1763
}
1764
INFO_LOG(Log::System, "Done waiting");
1765
if (info->Ready(GameInfoFlags::ICON)) {
1766
if (!info->icon.data.empty()) {
1767
INFO_LOG(Log::System, "requestIcon: Got icon");
1768
result = env->NewByteArray(info->icon.data.size());
1769
env->SetByteArrayRegion(result, 0, info->icon.data.size(), (const jbyte *)info->icon.data.data());
1770
}
1771
} else {
1772
INFO_LOG(Log::System, "requestIcon: Filetype unknown");
1773
}
1774
} else {
1775
INFO_LOG(Log::System, "No info from cache");
1776
}
1777
1778
delete cache;
1779
1780
if (teardownThreadManager) {
1781
g_threadManager.Teardown();
1782
}
1783
1784
return result;
1785
}
1786
1787