diff --git a/app/src/main/java/mw/gri/android/BackgroundService.java b/app/src/main/java/mw/gri/android/BackgroundService.java index 3b6b013..19ec258 100644 --- a/app/src/main/java/mw/gri/android/BackgroundService.java +++ b/app/src/main/java/mw/gri/android/BackgroundService.java @@ -28,8 +28,13 @@ public class BackgroundService extends Service { NotificationManager manager = getSystemService(NotificationManager.class); manager.notify(SYNC_STATUS_NOTIFICATION_ID, mNotificationBuilder.build()); + if (exitAppAfterNodeStop()) { + sendBroadcast(new Intent(MainActivity.FINISH_ACTIVITY_ACTION)); + mStopped = true; + } + if (!mStopped) { - mHandler.postDelayed(this, 500); + mHandler.postDelayed(this, 300); } } }; @@ -127,4 +132,5 @@ public class BackgroundService extends Service { private native String getSyncStatusText(); private native String getSyncTitle(); + private native boolean exitAppAfterNodeStop(); } diff --git a/app/src/main/java/mw/gri/android/MainActivity.java b/app/src/main/java/mw/gri/android/MainActivity.java index 8fb8c4b..aa2e1ff 100644 --- a/app/src/main/java/mw/gri/android/MainActivity.java +++ b/app/src/main/java/mw/gri/android/MainActivity.java @@ -1,5 +1,9 @@ package mw.gri.android; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.hardware.SensorManager; import android.os.Bundle; import android.os.Process; @@ -13,10 +17,21 @@ import java.util.concurrent.atomic.AtomicBoolean; public class MainActivity extends GameActivity { + public static String FINISH_ACTIVITY_ACTION = "MainActivity.finish"; + static { System.loadLibrary("grim"); } + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context ctx, Intent i) { + if (i.getAction().equals(FINISH_ACTIVITY_ACTION)) { + onExit(); + } + } + }; + @Override protected void onCreate(Bundle savedInstanceState) { // Setup HOME environment variable for native code configurations. @@ -40,6 +55,9 @@ public class MainActivity extends GameActivity { } onDisplayCutoutsChanged(Utils.getDisplayCutouts(this)); + // Register receiver to finish activity from the BackgroundService. + registerReceiver(mBroadcastReceiver, new IntentFilter(FINISH_ACTIVITY_ACTION)); + // Start notification service. BackgroundService.start(this); } @@ -62,10 +80,12 @@ public class MainActivity extends GameActivity { @Override protected void onDestroy() { + unregisterReceiver(mBroadcastReceiver); + if (!mManualExit) { onTermination(); } - // Temp fix: kill process after 3 seconds to prevent app hanging at next launch + // Temp fix: kill process after 3 seconds to prevent app hanging at next launch. new Thread(() -> { try { Thread.sleep(3000); @@ -80,8 +100,12 @@ public class MainActivity extends GameActivity { mActivityDestroyed.set(true); } - // Called from native code + // Called from native code. public void onExit() { + // Return if exit was already requested. + if (mManualExit) { + return; + } mManualExit = true; BackgroundService.stop(this); finish(); diff --git a/src/gui/app.rs b/src/gui/app.rs index ae4c831..a229169 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -15,9 +15,10 @@ use egui::{Context, Stroke}; use egui::os::OperatingSystem; -use crate::gui::Colors; +use crate::gui::{Colors, Navigator}; use crate::gui::platform::PlatformCallbacks; use crate::gui::screens::Root; +use crate::node::Node; pub struct PlatformApp { pub(crate) app: App, @@ -132,3 +133,28 @@ impl App { } } +#[allow(dead_code)] +#[cfg(target_os = "android")] +#[allow(non_snake_case)] +#[no_mangle] +pub extern "C" fn Java_mw_gri_android_MainActivity_onBackButtonPress( + _env: jni::JNIEnv, + _class: jni::objects::JObject, + _activity: jni::objects::JObject, +) { + Navigator::back(); +} + +#[allow(dead_code)] +#[cfg(target_os = "android")] +#[allow(non_snake_case)] +#[no_mangle] +/// Calling on unexpected Android application termination (removal from recent apps). +pub extern "C" fn Java_mw_gri_android_MainActivity_onTermination( + _env: jni::JNIEnv, + _class: jni::objects::JObject, + _activity: jni::objects::JObject, +) { + Node::stop(false); +} + diff --git a/src/gui/screens/root.rs b/src/gui/screens/root.rs index 412feb2..762ba24 100644 --- a/src/gui/screens/root.rs +++ b/src/gui/screens/root.rs @@ -80,12 +80,9 @@ impl Root { } ui.add_space(16.0); ui.vertical_centered(|ui| { - Spinner::new().size(48.0).color(Colors::GRAY).ui(ui); + Spinner::new().size(48.0).color(Colors::GOLD).ui(ui); ui.add_space(12.0); - ui.label(RichText::new(t!("sync_status.shutdown")) - .size(17.0) - .color(Colors::TEXT) - ); + ui.label(t!("sync_status.shutdown")); }); ui.add_space(10.0); } else { @@ -103,7 +100,7 @@ impl Root { App::exit(frame, cb); modal.close(); } else { - Node::stop(); + Node::stop(true); modal.disable_closing(); self.show_exit_progress = true; } @@ -123,9 +120,9 @@ impl Root { } fn show_current_screen(&mut self, - ui: &mut egui::Ui, - frame: &mut eframe::Frame, - cb: &dyn PlatformCallbacks) { + ui: &mut egui::Ui, + frame: &mut eframe::Frame, + cb: &dyn PlatformCallbacks) { let Self { screens, .. } = self; for screen in screens.iter_mut() { if Navigator::is_current(&screen.id()) { @@ -146,16 +143,4 @@ impl Root { }; (is_panel_open, panel_width) } -} - -#[allow(dead_code)] -#[cfg(target_os = "android")] -#[allow(non_snake_case)] -#[no_mangle] -pub extern "C" fn Java_mw_gri_android_MainActivity_onBackButtonPress( - _env: jni::JNIEnv, - _class: jni::objects::JObject, - _activity: jni::objects::JObject, -) { - Navigator::back(); } \ No newline at end of file diff --git a/src/node/node.rs b/src/node/node.rs index cd63cc8..bbd6ab1 100644 --- a/src/node/node.rs +++ b/src/node/node.rs @@ -25,7 +25,7 @@ use grin_config::config; use grin_core::global; use grin_core::global::ChainTypes; use grin_servers::{Server, ServerStats}; -use jni::sys::jstring; +use jni::sys::{jboolean, jstring}; use lazy_static::lazy_static; use log::info; @@ -46,6 +46,8 @@ pub struct Node { restart_needed: AtomicBool, /// Thread flag to stop the server. stop_needed: AtomicBool, + /// Flag to check if app exit is needed after server stop. + exit_after_stop: AtomicBool } impl Default for Node { @@ -56,14 +58,16 @@ impl Default for Node { starting: AtomicBool::new(false), restart_needed: AtomicBool::new(false), stop_needed: AtomicBool::new(false), + exit_after_stop: AtomicBool::new(false) } } } impl Node { - /// Stop the [`Server`]. - pub fn stop() { + /// Stop the [`Server`] and setup exit flag after if needed. + pub fn stop(exit_after_stop: bool) { NODE_STATE.stop_needed.store(true, Ordering::Relaxed); + NODE_STATE.exit_after_stop.store(exit_after_stop, Ordering::Relaxed); } /// Start [`Server`] with provided chain type. @@ -190,14 +194,14 @@ impl Node { /// Get synchronization status i18n text. pub fn get_sync_status_text() -> String { - if Node::is_starting() { - return t!("sync_status.initial") - }; - if Node::is_stopping() { return t!("sync_status.shutdown") }; + if Node::is_starting() { + return t!("sync_status.initial") + }; + if Node::is_restarting() { return t!("sync_status.server_restarting") } @@ -440,11 +444,13 @@ pub extern "C" fn Java_mw_gri_android_BackgroundService_getSyncTitle( #[cfg(target_os = "android")] #[allow(non_snake_case)] #[no_mangle] -/// Calling on unexpected Android application termination (removal from recent apps). -pub extern "C" fn Java_mw_gri_android_MainActivity_onTermination( +/// Check if app exit is needed after node stop. +pub extern "C" fn Java_mw_gri_android_BackgroundService_exitAppAfterNodeStop( _env: jni::JNIEnv, _class: jni::objects::JObject, _activity: jni::objects::JObject, -) { - Node::stop(); +) -> jboolean { + let exit_after_stop = NODE_STATE.exit_after_stop.load(Ordering::Relaxed); + let is_app_exit_needed = !Node::is_running() && exit_after_stop; + return is_app_exit_needed as jboolean; } \ No newline at end of file