Feat: Handle MainActivity destruction (#4817)

* Feat[launcher]: begin implementing MainActivity destruction handling

* Feat[lifecycle]: finalize MainActivity lifecycle awareness implementation

* Clean[dialog]: unified halting LifecycleAwareAlertDialog implementation

* Fix[mainactivity]: comment truncated
This commit is contained in:
Maksim Belov 2023-11-07 21:21:19 +03:00 committed by GitHub
parent 682fe04ef6
commit d5f74af94a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 218 additions and 68 deletions

View File

@ -1,7 +1,5 @@
package net.kdt.pojavlaunch;
import static net.kdt.pojavlaunch.MainActivity.fullyExit;
import android.annotation.SuppressLint;
import android.content.ClipboardManager;
import android.os.Bundle;
@ -173,14 +171,10 @@ public class JavaGUILauncherActivity extends BaseActivity implements View.OnTouc
openLogOutput(null);
new Thread(() -> {
try {
final int exit = doCustomInstall(runtime, modFile, javaArgs);
Logger.appendToLog(getString(R.string.toast_optifine_success));
if (exit != 0) return;
runOnUiThread(() -> {
Toast.makeText(JavaGUILauncherActivity.this, R.string.toast_optifine_success, Toast.LENGTH_SHORT).show();
fullyExit();
});
// Due to time, the code here became, like, actually useless
// So it was removed
// Tbh this whole class needs a refactor...
doCustomInstall(runtime, modFile, javaArgs);
} catch (Throwable e) {
Logger.appendToLog("Install failed:");
Logger.appendToLog(Log.getStackTraceString(e));
@ -287,7 +281,7 @@ public class JavaGUILauncherActivity extends BaseActivity implements View.OnTouc
Toast.LENGTH_SHORT).show();
}
public int launchJavaRuntime(Runtime runtime, File modFile, String javaArgs) {
public void launchJavaRuntime(Runtime runtime, File modFile, String javaArgs) {
JREUtils.redirectAndPrintJRELog();
try {
List<String> javaArgList = new ArrayList<>();
@ -313,18 +307,17 @@ public class JavaGUILauncherActivity extends BaseActivity implements View.OnTouc
Logger.appendToLog("Info: Java arguments: " + Arrays.toString(javaArgList.toArray(new String[0])));
return JREUtils.launchJavaVM(this, runtime,null,javaArgList, LauncherPreferences.PREF_CUSTOM_JAVA_ARGS);
JREUtils.launchJavaVM(this, runtime,null,javaArgList, LauncherPreferences.PREF_CUSTOM_JAVA_ARGS);
} catch (Throwable th) {
Tools.showError(this, th, true);
return -1;
}
}
private int doCustomInstall(Runtime runtime, File modFile, String javaArgs) {
private void doCustomInstall(Runtime runtime, File modFile, String javaArgs) {
mSkipDetectMod = true;
return launchJavaRuntime(runtime, modFile, javaArgs);
launchJavaRuntime(runtime, modFile, javaArgs);
}
public void toggleKeyboard(View view) {

View File

@ -23,7 +23,7 @@ import androidx.fragment.app.FragmentManager;
import com.kdt.mcgui.ProgressLayout;
import com.kdt.mcgui.mcAccountSpinner;
import net.kdt.pojavlaunch.contextexecutor.ContextExecutor;
import net.kdt.pojavlaunch.lifecycle.ContextExecutor;
import net.kdt.pojavlaunch.contracts.OpenDocumentWithExtension;
import net.kdt.pojavlaunch.extra.ExtraConstants;
import net.kdt.pojavlaunch.extra.ExtraCore;
@ -40,7 +40,7 @@ import net.kdt.pojavlaunch.progresskeeper.TaskCountListener;
import net.kdt.pojavlaunch.services.ProgressServiceKeeper;
import net.kdt.pojavlaunch.tasks.AsyncMinecraftDownloader;
import net.kdt.pojavlaunch.tasks.AsyncVersionList;
import net.kdt.pojavlaunch.tasks.ContextAwareDoneListener;
import net.kdt.pojavlaunch.lifecycle.ContextAwareDoneListener;
import net.kdt.pojavlaunch.utils.NotificationUtils;
import net.kdt.pojavlaunch.value.launcherprofiles.LauncherProfiles;
import net.kdt.pojavlaunch.value.launcherprofiles.MinecraftProfile;

View File

@ -13,8 +13,10 @@ import android.app.Activity;
import android.app.AlertDialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Color;
@ -22,6 +24,7 @@ import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.provider.DocumentsContract;
import android.util.Log;
import android.view.KeyEvent;
@ -36,10 +39,12 @@ import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import com.kdt.LoggerView;
import net.kdt.pojavlaunch.lifecycle.ContextExecutor;
import net.kdt.pojavlaunch.customcontrols.ControlButtonMenuListener;
import net.kdt.pojavlaunch.customcontrols.ControlData;
import net.kdt.pojavlaunch.customcontrols.ControlDrawerData;
@ -62,7 +67,7 @@ import org.lwjgl.glfw.CallbackBridge;
import java.io.File;
import java.io.IOException;
public class MainActivity extends BaseActivity implements ControlButtonMenuListener, EditorExitable {
public class MainActivity extends BaseActivity implements ControlButtonMenuListener, EditorExitable, ServiceConnection {
public static volatile ClipboardManager GLOBAL_CLIPBOARD;
public static final String INTENT_MINECRAFT_VERSION = "intent_version";
@ -84,13 +89,17 @@ public class MainActivity extends BaseActivity implements ControlButtonMenuListe
private AdapterView.OnItemClickListener gameActionClickListener;
public ArrayAdapter<String> ingameControlsEditorArrayAdapter;
public AdapterView.OnItemClickListener ingameControlsEditorListener;
private GameService.LocalBinder mServiceBinder;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
minecraftProfile = LauncherProfiles.getCurrentProfile();
MCOptionUtils.load(Tools.getGameDirPath(minecraftProfile).getAbsolutePath());
GameService.startService(this);
Intent gameServiceIntent = new Intent(this, GameService.class);
// Start the service a bit early
ContextCompat.startForegroundService(this, gameServiceIntent);
initLayout(R.layout.activity_basemain);
CallbackBridge.addGrabListener(touchpad);
CallbackBridge.addGrabListener(minecraftGLView);
@ -122,6 +131,12 @@ public class MainActivity extends BaseActivity implements ControlButtonMenuListe
MCOptionUtils.MCOptionListener optionListener = MCOptionUtils::getMcScale;
MCOptionUtils.addMCOptionListener(optionListener);
mControlLayout.setModifiable(false);
// Set the activity for the executor. Must do this here, or else Tools.showErrorRemote() may not
// execute the correct method
ContextExecutor.setActivity(this);
//Now, attach to the service. The game will only start when this happens, to make sure that we know the right state.
bindService(gameServiceIntent, this, 0);
}
protected void initLayout(int resId) {
@ -191,11 +206,9 @@ public class MainActivity extends BaseActivity implements ControlButtonMenuListe
runCraft(finalVersion, mVersionInfo);
}catch (Throwable e){
Tools.showError(getApplicationContext(), e, true);
Tools.showErrorRemote(e);
}
});
minecraftGLView.start();
} catch (Throwable e) {
Tools.showError(this, e, true);
}
@ -275,6 +288,7 @@ public class MainActivity extends BaseActivity implements ControlButtonMenuListe
super.onDestroy();
CallbackBridge.removeGrabListener(touchpad);
CallbackBridge.removeGrabListener(minecraftGLView);
ContextExecutor.clearActivity();
}
@Override
@ -336,6 +350,8 @@ public class MainActivity extends BaseActivity implements ControlButtonMenuListe
int requiredJavaVersion = 8;
if(version.javaVersion != null) requiredJavaVersion = version.javaVersion.majorVersion;
Tools.launchMinecraft(this, minecraftAccount, minecraftProfile, versionId, requiredJavaVersion);
//Note that we actually stall in the above function, even if the game crashes. But let's be safe.
Tools.runOnUiThread(()-> mServiceBinder.isActive = false);
}
private void printLauncherInfo(String gameVersion, String javaArguments) {
@ -609,4 +625,17 @@ public class MainActivity extends BaseActivity implements ControlButtonMenuListe
navDrawer.setOnItemClickListener(gameActionClickListener);
isInEditor = false;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
GameService.LocalBinder localBinder = (GameService.LocalBinder) service;
mServiceBinder = localBinder;
minecraftGLView.start(localBinder.isActive);
localBinder.isActive = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}

View File

@ -144,14 +144,17 @@ public class MinecraftGLSurface extends View implements GrabListener {
MCOptionUtils.addMCOptionListener(mGuiScaleListener);
}
/** Initialize the view and all its settings */
public void start(){
/** Initialize the view and all its settings
* @param isAlreadyRunning set to true to tell the view that the game is already running
* (only updates the window without calling the start listener)
*/
public void start(boolean isAlreadyRunning){
if(LauncherPreferences.PREF_USE_ALTERNATE_SURFACE){
SurfaceView surfaceView = new SurfaceView(getContext());
mSurface = surfaceView;
surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
private boolean isCalled = false;
private boolean isCalled = isAlreadyRunning;
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
if(isCalled) {
@ -180,7 +183,7 @@ public class MinecraftGLSurface extends View implements GrabListener {
mSurface = textureView;
textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
private boolean isCalled = false;
private boolean isCalled = isAlreadyRunning;
@Override
public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
Surface tSurface = new Surface(surface);

View File

@ -18,7 +18,7 @@ import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import net.kdt.pojavlaunch.contextexecutor.ContextExecutor;
import net.kdt.pojavlaunch.lifecycle.ContextExecutor;
import net.kdt.pojavlaunch.tasks.AsyncAssetManager;
import net.kdt.pojavlaunch.utils.*;

View File

@ -9,7 +9,7 @@ import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import net.kdt.pojavlaunch.contextexecutor.ContextExecutorTask;
import net.kdt.pojavlaunch.lifecycle.ContextExecutorTask;
import net.kdt.pojavlaunch.utils.NotificationUtils;
import java.io.Serializable;

View File

@ -8,7 +8,6 @@ import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_NOTCH_SIZE;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.ProgressDialog;
@ -39,6 +38,8 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.NotificationManagerCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
@ -47,8 +48,9 @@ import androidx.fragment.app.FragmentTransaction;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import net.kdt.pojavlaunch.contextexecutor.ContextExecutor;
import net.kdt.pojavlaunch.contextexecutor.ContextExecutorTask;
import net.kdt.pojavlaunch.lifecycle.ContextExecutor;
import net.kdt.pojavlaunch.lifecycle.ContextExecutorTask;
import net.kdt.pojavlaunch.lifecycle.LifecycleAwareAlertDialog;
import net.kdt.pojavlaunch.multirt.MultiRTUtils;
import net.kdt.pojavlaunch.multirt.Runtime;
import net.kdt.pojavlaunch.plugins.FFmpegPlugin;
@ -161,21 +163,18 @@ public final class Tools {
NATIVE_LIB_DIR = ctx.getApplicationInfo().nativeLibraryDir;
}
public static void launchMinecraft(final Activity activity, MinecraftAccount minecraftAccount,
public static void launchMinecraft(final AppCompatActivity activity, MinecraftAccount minecraftAccount,
MinecraftProfile minecraftProfile, String versionId, int versionJavaRequirement) throws Throwable {
int freeDeviceMemory = getFreeDeviceMemory(activity);
if(LauncherPreferences.PREF_RAM_ALLOCATION > freeDeviceMemory) {
Object memoryErrorLock = new Object();
activity.runOnUiThread(() -> {
androidx.appcompat.app.AlertDialog.Builder b = new androidx.appcompat.app.AlertDialog.Builder(activity)
.setMessage(activity.getString(R.string.memory_warning_msg, freeDeviceMemory ,LauncherPreferences.PREF_RAM_ALLOCATION))
.setPositiveButton(android.R.string.ok, (dialogInterface, i) -> {synchronized(memoryErrorLock){memoryErrorLock.notifyAll();}})
.setOnCancelListener((i) -> {synchronized(memoryErrorLock){memoryErrorLock.notifyAll();}});
b.show();
});
synchronized (memoryErrorLock) {
memoryErrorLock.wait();
LifecycleAwareAlertDialog.DialogCreator dialogCreator = (dialog, builder) ->
builder.setMessage(activity.getString(R.string.memory_warning_msg, freeDeviceMemory, LauncherPreferences.PREF_RAM_ALLOCATION))
.setPositiveButton(android.R.string.ok, (d, w)->{});
if(LifecycleAwareAlertDialog.haltOnDialog(activity.getLifecycle(), activity, dialogCreator)) {
return; // If the dialog's lifecycle has ended, return without
// actually launching the game, thus giving us the opportunity
// to start after the activity is shown again
}
}
Runtime runtime = MultiRTUtils.forceReread(Tools.pickRuntime(minecraftProfile, versionJavaRequirement));
@ -216,6 +215,8 @@ public final class Tools {
if(Tools.isValidString(minecraftProfile.javaArgs)) args = minecraftProfile.javaArgs;
FFmpegPlugin.discover(activity);
JREUtils.launchJavaVM(activity, runtime, gamedir, javaArgList, args);
// If we returned, this means that the JVM exit dialog has been shown and we don't need to be active anymore.
// We never return otherwise. The process will be killed anyway, and thus we will become inactive
}
public static File getGameDirPath(@NonNull MinecraftProfile minecraftProfile){

View File

@ -1,4 +1,4 @@
package net.kdt.pojavlaunch.tasks;
package net.kdt.pojavlaunch.lifecycle;
import static net.kdt.pojavlaunch.MainActivity.INTENT_MINECRAFT_VERSION;
@ -9,9 +9,10 @@ import android.content.Intent;
import net.kdt.pojavlaunch.MainActivity;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.contextexecutor.ContextExecutor;
import net.kdt.pojavlaunch.contextexecutor.ContextExecutorTask;
import net.kdt.pojavlaunch.lifecycle.ContextExecutor;
import net.kdt.pojavlaunch.lifecycle.ContextExecutorTask;
import net.kdt.pojavlaunch.progresskeeper.ProgressKeeper;
import net.kdt.pojavlaunch.tasks.AsyncMinecraftDownloader;
import net.kdt.pojavlaunch.utils.NotificationUtils;
public class ContextAwareDoneListener implements AsyncMinecraftDownloader.DoneListener, ContextExecutorTask {

View File

@ -1,4 +1,4 @@
package net.kdt.pojavlaunch.contextexecutor;
package net.kdt.pojavlaunch.lifecycle;
import android.app.Activity;
import android.app.Application;

View File

@ -1,4 +1,4 @@
package net.kdt.pojavlaunch.contextexecutor;
package net.kdt.pojavlaunch.lifecycle;
import android.app.Activity;
import android.content.Context;

View File

@ -0,0 +1,125 @@
package net.kdt.pojavlaunch.lifecycle;
import android.content.Context;
import android.content.DialogInterface;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleEventObserver;
import androidx.lifecycle.LifecycleOwner;
import net.kdt.pojavlaunch.Tools;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* A class that implements a form of lifecycle awareness for AlertDialog
*/
public abstract class LifecycleAwareAlertDialog implements LifecycleEventObserver {
private Lifecycle mLifecycle;
private AlertDialog mDialog;
private boolean mLifecycleEnded = false;
/**
* Show the lifecycle-aware dialog.
* Note that the DialogCreator may not be always invoked.
* @param lifecycle the lifecycle to follow
* @param context the context for the dialog
* @param dialogCreator an interface used to create the dialog.
* Note that any dismiss listeners added to the dialog must be wrapped
* with wrapDismissListener().
*/
public void show(Lifecycle lifecycle, Context context, DialogCreator dialogCreator) {
this.mLifecycleEnded = false;
this.mLifecycle = lifecycle;
if(mLifecycle.getCurrentState().equals(Lifecycle.State.DESTROYED)) {
this.mLifecycleEnded = true;
dialogHidden(mLifecycleEnded);
return;
}
AlertDialog.Builder builder = new AlertDialog.Builder(context);
// Install the default cancel/dismiss handling
builder.setOnDismissListener(wrapDismissListener(null));
dialogCreator.createDialog(this, builder);
mLifecycle.addObserver(this);
mDialog = builder.show();
}
/**
* Invoked when the dialog gets hidden either by cancel()/dismiss(), or if a lifecycle event
* happens.
* @param lifecycleEnded if the dialog was hidden due to a lifecycle event
*/
abstract protected void dialogHidden(boolean lifecycleEnded);
protected void dispatchDialogHidden() {
new Exception().printStackTrace();
dialogHidden(mLifecycleEnded);
mLifecycle.removeObserver(this);
}
public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
if(event.equals(Lifecycle.Event.ON_DESTROY)) {
mDialog.dismiss();
mLifecycleEnded = true;
}
}
/**
* Wrap an OnDismissListener for use with this LifecycleAwareAlertDialog. Pass null to only invoke the
* default dialog hidden handling.
* @param listener your listener
* @return the wrapped listener
*/
public DialogInterface.OnDismissListener wrapDismissListener(DialogInterface.OnCancelListener listener) {
return dialog -> {
dispatchDialogHidden();
if(listener != null) listener.onCancel(dialog);
};
}
public interface DialogCreator {
/**
* This methods is called when the LifecycleAwareAlertDialog needs to set up its dialog.
* @param alertDialog an instance of LifecycleAwareAlertDialog for wrapping listeners
* @param dialogBuilder the AlertDialog builder
*/
void createDialog(LifecycleAwareAlertDialog alertDialog, AlertDialog.Builder dialogBuilder);
}
/**
* Show a dialog and halt the current thread until the dialog gets closed either due to user action or a lifecycle event.
* @param lifecycle the Lifecycle object that this dialog will track to automatically close upon destruction
* @param context the context used to show the dialog
* @param dialogCreator a DialogCreator that creates the dialog
* @return true if the dialog was automatically dismissed due to a lifecycle event. This may happen
* before the dialog creator is used, so make sure to to handle the return value of the function.
* false otherwise
* @throws InterruptedException if the thread was interrupted while waiting for the dialog
*/
public static boolean haltOnDialog(Lifecycle lifecycle, Context context, DialogCreator dialogCreator) throws InterruptedException {
Object waitLock = new Object();
AtomicBoolean hasLifecycleEnded = new AtomicBoolean(false);
// This runnable is moved here in order to reduce bracket/lambda hell
Runnable showDialogRunnable = () -> {
LifecycleAwareAlertDialog lifecycleAwareDialog = new LifecycleAwareAlertDialog() {
@Override
protected void dialogHidden(boolean lifecycleEnded) {
hasLifecycleEnded.set(lifecycleEnded);
synchronized(waitLock){waitLock.notifyAll();}
}
};
lifecycleAwareDialog.show(lifecycle, context, dialogCreator);
};
synchronized (waitLock) {
Tools.runOnUiThread(showDialogRunnable);
// the wait() method makes the thread wait on the end of the synchronized block.
// so we put it here to make sure that the thread won't get notified before wait()
// is called
waitLock.wait();
}
return hasLifecycleEnded.get();
}
}

View File

@ -7,7 +7,7 @@ import android.text.Html;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.ShowErrorActivity;
import net.kdt.pojavlaunch.contextexecutor.ContextExecutorTask;
import net.kdt.pojavlaunch.lifecycle.ContextExecutorTask;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
public class MirrorTamperedException extends Exception implements ContextExecutorTask {

View File

@ -2,15 +2,14 @@ package net.kdt.pojavlaunch.services;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.Process;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;
@ -20,10 +19,7 @@ import java.lang.ref.WeakReference;
public class GameService extends Service {
private static final WeakReference<Service> sGameService = new WeakReference<>(null);
public static void startService(Context context) {
Intent intent = new Intent(context, GameService.class);
ContextCompat.startForegroundService(context, intent);
}
private final LocalBinder mLocalBinder = new LocalBinder();
@Override
public void onCreate() {
@ -47,7 +43,7 @@ public class GameService extends Service {
.addAction(android.R.drawable.ic_menu_close_clear_cancel, getString(R.string.notification_terminate), pendingKillIntent)
.setSmallIcon(R.drawable.notif_icon)
.setNotificationSilent();
startForeground(NotificationUtils.NOTIFICATION_ID_GAME_SERVICE, notificationBuilder.build());
startForeground(NotificationUtils.NOTIFICATION_ID_GAME_SERVICE, notificationBuilder.build());
return START_NOT_STICKY; // non-sticky so android wont try restarting the game after the user uses the "Quit" button
}
@ -61,6 +57,10 @@ public class GameService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
return mLocalBinder;
}
public static class LocalBinder extends Binder {
public boolean isActive;
}
}

View File

@ -16,12 +16,15 @@ import android.system.*;
import android.util.*;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.oracle.dalvik.*;
import java.io.*;
import java.util.*;
import net.kdt.pojavlaunch.*;
import net.kdt.pojavlaunch.extra.ExtraConstants;
import net.kdt.pojavlaunch.extra.ExtraCore;
import net.kdt.pojavlaunch.lifecycle.LifecycleAwareAlertDialog;
import net.kdt.pojavlaunch.multirt.MultiRTUtils;
import net.kdt.pojavlaunch.multirt.Runtime;
import net.kdt.pojavlaunch.plugins.FFmpegPlugin;
@ -265,7 +268,7 @@ public class JREUtils {
// return ldLibraryPath;
}
public static int launchJavaVM(final Activity activity, final Runtime runtime, File gameDirectory, final List<String> JVMArgs, final String userArgsString) throws Throwable {
public static void launchJavaVM(final AppCompatActivity activity, final Runtime runtime, File gameDirectory, final List<String> JVMArgs, final String userArgsString) throws Throwable {
String runtimeHome = MultiRTUtils.getRuntimeHome(runtime.name).getAbsolutePath();
JREUtils.relocateLibPath(runtime, runtimeHome);
@ -303,18 +306,13 @@ public class JREUtils {
final int exitCode = VMLauncher.launchJVM(userArgs.toArray(new String[0]));
Logger.appendToLog("Java Exit code: " + exitCode);
if (exitCode != 0) {
activity.runOnUiThread(() -> {
AlertDialog.Builder dialog = new AlertDialog.Builder(activity);
dialog.setMessage(activity.getString(R.string.mcn_exit_title, exitCode));
LifecycleAwareAlertDialog.DialogCreator dialogCreator = (dialog, builder)->
builder.setMessage(activity.getString(R.string.mcn_exit_title, exitCode))
.setPositiveButton(R.string.main_share_logs, (dialogInterface, which)-> shareLog(activity));
dialog.setPositiveButton(R.string.main_share_logs, (p1, p2) -> {
shareLog(activity);
MainActivity.fullyExit();
});
dialog.show();
});
LifecycleAwareAlertDialog.haltOnDialog(activity.getLifecycle(), activity, dialogCreator);
}
return exitCode;
MainActivity.fullyExit();
}
/**