mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-17 11:11:10 +02:00
@@ -231,6 +231,28 @@ bool syncHealthSteps()
|
||||
return result;
|
||||
}
|
||||
|
||||
bool restartApp()
|
||||
{
|
||||
JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
|
||||
jclass activity = env->FindClass("org/love2d/android/GameActivity");
|
||||
|
||||
// Old APK / new liblove skew: fail soft so HostShell.restart can fall
|
||||
// back to a clean quit instead of aborting on a missing method (#575).
|
||||
jmethodID method = env->GetStaticMethodID(activity, "restartApp", "()Z");
|
||||
if (method == nullptr)
|
||||
{
|
||||
env->ExceptionClear();
|
||||
env->DeleteLocalRef(activity);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Does not return on success: the Java side exits the process.
|
||||
jboolean result = env->CallStaticBooleanMethod(activity, method);
|
||||
|
||||
env->DeleteLocalRef(activity);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper functions for the filesystem module
|
||||
*/
|
||||
|
||||
@@ -81,6 +81,15 @@ bool showCreateDocument(const char *suggestedName = nullptr);
|
||||
*/
|
||||
bool syncHealthSteps();
|
||||
|
||||
/**
|
||||
* Full process relaunch (GameActivity.restartApp): schedules the app's
|
||||
* launch intent and kills the process, because the in-process
|
||||
* quit("restart") loop double-inits physfs and crashes (#575). On success
|
||||
* the process dies inside the Java call and this never returns; false
|
||||
* means the relaunch could not be scheduled.
|
||||
**/
|
||||
bool restartApp();
|
||||
|
||||
/*
|
||||
* Helper functions for the filesystem module
|
||||
*/
|
||||
|
||||
@@ -221,6 +221,15 @@ bool System::syncHealthSteps() const
|
||||
#endif
|
||||
}
|
||||
|
||||
bool System::restartApp() const
|
||||
{
|
||||
#ifdef LOVE_ANDROID
|
||||
return love::android::restartApp();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool System::hasBackgroundMusic() const
|
||||
{
|
||||
#if defined(LOVE_ANDROID)
|
||||
|
||||
@@ -133,6 +133,14 @@ public:
|
||||
*/
|
||||
virtual bool syncHealthSteps() const;
|
||||
|
||||
/**
|
||||
* Relaunches the whole app with a fresh process (Android only; false
|
||||
* elsewhere). The in-process love.event.quit("restart") double-inits
|
||||
* physfs on Android and crashes, so src/core/HostShell.lua calls this
|
||||
* instead (#575). Does not return on success -- the process exits.
|
||||
**/
|
||||
virtual bool restartApp() const;
|
||||
|
||||
/**
|
||||
* Gets if the user is playing music on background.
|
||||
* Throws an exception on unsupported platforms.
|
||||
|
||||
@@ -115,6 +115,14 @@ int w_syncHealthSteps(lua_State *L)
|
||||
return 1;
|
||||
}
|
||||
|
||||
int w_restartApp(lua_State *L)
|
||||
{
|
||||
// Does not return on success: GameActivity.restartApp exits the process
|
||||
// after scheduling the relaunch (#575).
|
||||
luax_pushboolean(L, instance()->restartApp());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int w_hasBackgroundMusic(lua_State *L)
|
||||
{
|
||||
lua_pushboolean(L, instance()->hasBackgroundMusic());
|
||||
@@ -133,6 +141,7 @@ static const luaL_Reg functions[] =
|
||||
{ "pickFile", w_pickFile },
|
||||
{ "createFile", w_createFile },
|
||||
{ "syncHealthSteps", w_syncHealthSteps },
|
||||
{ "restartApp", w_restartApp },
|
||||
{ "hasBackgroundMusic", w_hasBackgroundMusic },
|
||||
{ 0, 0 }
|
||||
};
|
||||
|
||||
@@ -37,7 +37,9 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.AlarmManager;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
@@ -75,6 +77,7 @@ public class GameActivity extends SDLActivity {
|
||||
public static final int FILE_PICKER_REQUEST_CODE = 4;
|
||||
public static final int FILE_CREATE_REQUEST_CODE = 5;
|
||||
public static final int STEP_PERMISSION_REQUEST_CODE = 6;
|
||||
public static final int RESTART_REQUEST_CODE = 7;
|
||||
/** @deprecated Prefer FILE_PICKER_REQUEST_CODE; kept for older call sites. */
|
||||
public static final int ROM_PICKER_REQUEST_CODE = FILE_PICKER_REQUEST_CODE;
|
||||
// Mirrors conf.lua's t.identity ("pokemon-love2d"): where the picked file
|
||||
@@ -447,15 +450,23 @@ public class GameActivity extends SDLActivity {
|
||||
* Shows the system document picker (Storage Access Framework) so the
|
||||
* player can pick a ROM / mod / save from anywhere (Downloads, Drive,
|
||||
* etc.) without needing to know where the app's external files folder
|
||||
* is. Requires API 19+ (ACTION_OPEN_DOCUMENT); the picked file (if any)
|
||||
* arrives later in onActivityResult, not synchronously here.
|
||||
* is. The picked file (if any) arrives later in onActivityResult, not
|
||||
* synchronously here.
|
||||
*
|
||||
* API 21+ uses ACTION_OPEN_DOCUMENT; API 16-20 uses an ACTION_GET_CONTENT
|
||||
* chooser instead. Below 19 OPEN_DOCUMENT does not exist, and on 19/20
|
||||
* the stock DocumentsUI is unreliable -- it launches and then hands back
|
||||
* RESULT_CANCELED with no data, which onActivityResult cannot tell apart
|
||||
* from the player cancelling (#584). GET_CONTENT lets any installed file
|
||||
* manager serve the pick, and both intents return the same content:// or
|
||||
* file:// URI shapes, so the result path in onActivityResult stays
|
||||
* picker-agnostic and unchanged.
|
||||
*
|
||||
* @param destFilename basename under the app save identity (e.g.
|
||||
* picked_rom.gb, picked_mod.zip, picked_save.sav)
|
||||
*/
|
||||
@Keep
|
||||
public static boolean showFilePicker(String destFilename) {
|
||||
if (android.os.Build.VERSION.SDK_INT < 19) return false;
|
||||
GameActivity self = (GameActivity) mSingleton;
|
||||
if (self == null) return false;
|
||||
if (destFilename == null || destFilename.length() == 0) {
|
||||
@@ -469,11 +480,26 @@ public class GameActivity extends SDLActivity {
|
||||
}
|
||||
|
||||
self.pendingPickFilename = destFilename;
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
||||
if (android.os.Build.VERSION.SDK_INT >= 21) {
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intent.setType("*/*");
|
||||
try {
|
||||
self.startActivityForResult(intent, FILE_PICKER_REQUEST_CODE);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
// Some OEM / TV builds ship without DocumentsUI; fall through
|
||||
// to the GET_CONTENT chooser below instead of giving up (#584).
|
||||
Log.d("GameActivity", "could not open document picker: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intent.setType("*/*");
|
||||
try {
|
||||
self.startActivityForResult(intent, FILE_PICKER_REQUEST_CODE);
|
||||
self.startActivityForResult(
|
||||
Intent.createChooser(intent, "Choose a file"),
|
||||
FILE_PICKER_REQUEST_CODE);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
Log.d("GameActivity", "could not open file picker: " + e.getMessage());
|
||||
@@ -499,14 +525,60 @@ public class GameActivity extends SDLActivity {
|
||||
return showFilePicker(PICKED_SAVE_FILENAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Relaunches the whole app for love.system.restartApp, used by
|
||||
* src/core/HostShell.lua when a mod toggle needs a cold boot (#575).
|
||||
* love.event.quit("restart") re-runs LOVE's boot inside the same
|
||||
* process, and the second love.filesystem.init throws once physfs
|
||||
* failed to deinit ("already initialized"), killing the app. Instead
|
||||
* we hand our launch intent to AlarmManager and then exit the process:
|
||||
* the alarm lives in system_server, so it survives our death and
|
||||
* cannot race the exit the way a plain startActivity right before
|
||||
* Runtime.exit can on some OEMs, and the dead process guarantees no
|
||||
* native (physfs / SDL / JNI) state leaks into the fresh run.
|
||||
*/
|
||||
@Keep
|
||||
public static boolean restartApp() {
|
||||
GameActivity self = (GameActivity) mSingleton;
|
||||
if (self == null) return false;
|
||||
try {
|
||||
Context context = self.getApplicationContext();
|
||||
Intent intent = context.getPackageManager()
|
||||
.getLaunchIntentForPackage(context.getPackageName());
|
||||
if (intent == null) return false;
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
int pendingFlags = PendingIntent.FLAG_CANCEL_CURRENT;
|
||||
if (android.os.Build.VERSION.SDK_INT >= 23) {
|
||||
// Mandatory mutability flag on API 31+; harmless from 23 up.
|
||||
pendingFlags |= PendingIntent.FLAG_IMMUTABLE;
|
||||
}
|
||||
PendingIntent pending = PendingIntent.getActivity(
|
||||
context, RESTART_REQUEST_CODE, intent, pendingFlags);
|
||||
AlarmManager alarm = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||
if (alarm == null) return false;
|
||||
alarm.set(AlarmManager.RTC, System.currentTimeMillis() + 250, pending);
|
||||
} catch (Exception e) {
|
||||
Log.d("GameActivity", "could not schedule restart: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
Runtime.getRuntime().exit(0);
|
||||
return true; // unreachable, but keeps the JNI signature honest
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows ACTION_CREATE_DOCUMENT so the player can save a staged export
|
||||
* (pending_export.sav in the app save identity) to Downloads / Drive /
|
||||
* etc. Suggested name is the dialog's default filename.
|
||||
*
|
||||
* CREATE_DOCUMENT does not exist below API 19 and has no pre-SAF
|
||||
* equivalent, so unlike showFilePicker this stays 19+ (#584); the false
|
||||
* return degrades on the Lua side (RomImporter export) to "Exported
|
||||
* inside the app folder", which is the correct pre-KitKat behavior.
|
||||
*/
|
||||
@Keep
|
||||
public static boolean showCreateDocument(String suggestedName) {
|
||||
if (android.os.Build.VERSION.SDK_INT < 19) return false;
|
||||
// (see showFilePicker for why the import side got a pre-19 path)
|
||||
GameActivity self = (GameActivity) mSingleton;
|
||||
if (self == null) return false;
|
||||
if (suggestedName == null || suggestedName.length() == 0) {
|
||||
|
||||
Reference in New Issue
Block a user