diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index e92b2a4..04e6d7b 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -3,12 +3,14 @@ > + + + + 25) { + startStopIntent.putExtra(EXTRA_NOTIFICATION_ID, NOTIFICATION_ID); + } + if (canStart) { + startStopIntent.setAction(ACTION_START_NODE); + PendingIntent i = PendingIntent + .getBroadcast(BackgroundService.this, 1, startStopIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT); + mNotificationBuilder.addAction(R.drawable.ic_start, getStartText(), i); + } else if (canStop) { + startStopIntent.setAction(ACTION_STOP_NODE); + PendingIntent i = PendingIntent + .getBroadcast(BackgroundService.this, 1, startStopIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT); + mNotificationBuilder.addAction(R.drawable.ic_stop, getStopText(), i); + } + + // Set up a button to exit from the app. + if (canStart || canStop) { + Intent exitIntent = new Intent(BackgroundService.this, NotificationActionsReceiver.class); + if (Build.VERSION.SDK_INT > 25) { + exitIntent.putExtra(EXTRA_NOTIFICATION_ID, NOTIFICATION_ID); + } + exitIntent.setAction(ACTION_EXIT); + PendingIntent i = PendingIntent + .getBroadcast(BackgroundService.this, 1, exitIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT); + mNotificationBuilder.addAction(R.drawable.ic_close, getExitText(), i); + } + } + + // Update notification. + if (textChanged || buttonsChanged) { + NotificationManager manager = getSystemService(NotificationManager.class); + manager.notify(NOTIFICATION_ID, mNotificationBuilder.build()); + } + + // Repeat notification update. + mHandler.postDelayed(this, 1000); } } }; + @SuppressLint({"WakelockTimeout", "UnspecifiedRegisterReceiverFlag"}) @Override public void onCreate() { if (mStopped) { @@ -78,15 +155,20 @@ public class BackgroundService extends Service { mNotificationBuilder = new NotificationCompat.Builder(this, TAG) .setContentTitle(this.getSyncTitle()) .setContentText(this.getSyncStatusText()) + .setStyle(new NotificationCompat.BigTextStyle().bigText(this.getSyncStatusText())) .setSmallIcon(R.drawable.ic_stat_name) + .setPriority(NotificationCompat.PRIORITY_MAX) .setContentIntent(pendingIntent); Notification notification = mNotificationBuilder.build(); // Start service at foreground state to prevent killing by system. - startForeground(SYNC_STATUS_NOTIFICATION_ID, notification); + startForeground(NOTIFICATION_ID, notification); // Update sync status at notification. mHandler.post(mUpdateSyncStatus); + + // Register receiver to refresh notifications by intent. + registerReceiver(mReceiver, new IntentFilter(ACTION_REFRESH)); } @Override @@ -117,22 +199,26 @@ public class BackgroundService extends Service { // Stop updating the notification. mHandler.removeCallbacks(mUpdateSyncStatus); + unregisterReceiver(mReceiver); + clearNotification(); // Remove service from foreground state. stopForeground(Service.STOP_FOREGROUND_REMOVE); - // Remove notification. + // Release wake lock to allow CPU to sleep at background. + if (mWakeLock != null && mWakeLock.isHeld()) { + mWakeLock.release(); + mWakeLock = null; + } + } + + // Remove notification. + private void clearNotification() { NotificationManager notificationManager = getSystemService(NotificationManager.class); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { notificationManager.deleteNotificationChannel(TAG); } - notificationManager.cancel(SYNC_STATUS_NOTIFICATION_ID); - - // Release wake lock to allow CPU to sleep at background. - if (mWakeLock.isHeld()) { - mWakeLock.release(); - mWakeLock = null; - } + notificationManager.cancel(NOTIFICATION_ID); } // Start the service. @@ -165,10 +251,24 @@ public class BackgroundService extends Service { return false; } - // Get sync status text for notification from native code. + // Get sync status text for notification. private native String getSyncStatusText(); - // Get sync title text for notification from native code. + // Get sync title text for notification. private native String getSyncTitle(); - // Check if app from the app is needed after node stop from native code. + + // Get start text for notification. + private native String getStartText(); + // Get stop text for notification. + private native String getStopText(); + + // Check if start node is possible. + private native boolean canStartNode(); + // Check if stop node is possible. + private native boolean canStopNode(); + + // Get exit text for notification. + private native String getExitText(); + + // Check if app from the app is needed after node stop. private native boolean exitAppAfterNodeStop(); } diff --git a/android/app/src/main/java/mw/gri/android/NotificationActionsReceiver.java b/android/app/src/main/java/mw/gri/android/NotificationActionsReceiver.java new file mode 100644 index 0000000..2336591 --- /dev/null +++ b/android/app/src/main/java/mw/gri/android/NotificationActionsReceiver.java @@ -0,0 +1,35 @@ +package mw.gri.android; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +public class NotificationActionsReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent i) { + String a = i.getAction(); + if (a.equals(BackgroundService.ACTION_START_NODE)) { + startNode(); + context.sendBroadcast(new Intent(BackgroundService.ACTION_REFRESH)); + } else if (a.equals(BackgroundService.ACTION_STOP_NODE)) { + stopNode(); + context.sendBroadcast(new Intent(BackgroundService.ACTION_REFRESH)); + } else { + if (isNodeRunning()) { + stopNodeToExit(); + context.sendBroadcast(new Intent(BackgroundService.ACTION_REFRESH)); + } else { + context.sendBroadcast(new Intent(MainActivity.STOP_APP_ACTION)); + } + } + } + + // Start integrated node. + native void startNode(); + // Stop integrated node. + native void stopNode(); + // Stop node and exit from the app. + native void stopNodeToExit(); + // Check if node is running. + native boolean isNodeRunning(); +} diff --git a/android/app/src/main/res/drawable-anydpi/ic_close.xml b/android/app/src/main/res/drawable-anydpi/ic_close.xml new file mode 100644 index 0000000..b333f53 --- /dev/null +++ b/android/app/src/main/res/drawable-anydpi/ic_close.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/android/app/src/main/res/drawable-anydpi/ic_start.xml b/android/app/src/main/res/drawable-anydpi/ic_start.xml new file mode 100644 index 0000000..a00c0ed --- /dev/null +++ b/android/app/src/main/res/drawable-anydpi/ic_start.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/android/app/src/main/res/drawable-anydpi/ic_stop.xml b/android/app/src/main/res/drawable-anydpi/ic_stop.xml new file mode 100644 index 0000000..957c847 --- /dev/null +++ b/android/app/src/main/res/drawable-anydpi/ic_stop.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/android/app/src/main/res/drawable-hdpi/ic_close.png b/android/app/src/main/res/drawable-hdpi/ic_close.png new file mode 100644 index 0000000..136ac44 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/ic_close.png differ diff --git a/android/app/src/main/res/drawable-hdpi/ic_start.png b/android/app/src/main/res/drawable-hdpi/ic_start.png new file mode 100644 index 0000000..e61e66a Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/ic_start.png differ diff --git a/android/app/src/main/res/drawable-hdpi/ic_stop.png b/android/app/src/main/res/drawable-hdpi/ic_stop.png new file mode 100644 index 0000000..44c823e Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/ic_stop.png differ diff --git a/android/app/src/main/res/drawable-mdpi/ic_close.png b/android/app/src/main/res/drawable-mdpi/ic_close.png new file mode 100644 index 0000000..a6551fb Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_close.png differ diff --git a/android/app/src/main/res/drawable-mdpi/ic_start.png b/android/app/src/main/res/drawable-mdpi/ic_start.png new file mode 100644 index 0000000..9611b5f Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_start.png differ diff --git a/android/app/src/main/res/drawable-mdpi/ic_stop.png b/android/app/src/main/res/drawable-mdpi/ic_stop.png new file mode 100644 index 0000000..f744450 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_stop.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/ic_close.png b/android/app/src/main/res/drawable-xhdpi/ic_close.png new file mode 100644 index 0000000..433762c Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/ic_close.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/ic_start.png b/android/app/src/main/res/drawable-xhdpi/ic_start.png new file mode 100644 index 0000000..6cf694b Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/ic_start.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/ic_stop.png b/android/app/src/main/res/drawable-xhdpi/ic_stop.png new file mode 100644 index 0000000..b1de0c5 Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/ic_stop.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_close.png b/android/app/src/main/res/drawable-xxhdpi/ic_close.png new file mode 100644 index 0000000..a403ca3 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/ic_close.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_start.png b/android/app/src/main/res/drawable-xxhdpi/ic_start.png new file mode 100644 index 0000000..57227f5 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/ic_start.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_stop.png b/android/app/src/main/res/drawable-xxhdpi/ic_stop.png new file mode 100644 index 0000000..56a8d47 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/ic_stop.png differ diff --git a/src/node/node.rs b/src/node/node.rs index e7cfaa8..04d85cf 100644 --- a/src/node/node.rs +++ b/src/node/node.rs @@ -658,6 +658,128 @@ pub extern "C" fn Java_mw_gri_android_BackgroundService_getSyncTitle( return j_text.unwrap().into_raw(); } +#[allow(dead_code)] +#[cfg(target_os = "android")] +#[allow(non_snake_case)] +#[no_mangle] +/// Get start text for Android notification in Java string format. +pub extern "C" fn Java_mw_gri_android_BackgroundService_getStartText( + _env: jni::JNIEnv, + _class: jni::objects::JObject, + _activity: jni::objects::JObject, +) -> jni::sys::jstring { + let j_text = _env.new_string(t!("network_settings.enable")); + return j_text.unwrap().into_raw(); +} + +#[allow(dead_code)] +#[cfg(target_os = "android")] +#[allow(non_snake_case)] +#[no_mangle] +/// Get stop text for Android notification in Java string format. +pub extern "C" fn Java_mw_gri_android_BackgroundService_getStopText( + _env: jni::JNIEnv, + _class: jni::objects::JObject, + _activity: jni::objects::JObject, +) -> jni::sys::jstring { + let j_text = _env.new_string(t!("network_settings.disable")); + return j_text.unwrap().into_raw(); +} + +#[allow(dead_code)] +#[cfg(target_os = "android")] +#[allow(non_snake_case)] +#[no_mangle] +/// Get exit text for Android notification in Java string format. +pub extern "C" fn Java_mw_gri_android_BackgroundService_getExitText( + _env: jni::JNIEnv, + _class: jni::objects::JObject, + _activity: jni::objects::JObject, +) -> jni::sys::jstring { + let j_text = _env.new_string(t!("modal_exit.exit")); + return j_text.unwrap().into_raw(); +} + +#[allow(dead_code)] +#[cfg(target_os = "android")] +#[allow(non_snake_case)] +#[no_mangle] +/// Check if node launch is possible. +pub extern "C" fn Java_mw_gri_android_BackgroundService_canStartNode( + _env: jni::JNIEnv, + _class: jni::objects::JObject, + _activity: jni::objects::JObject, +) -> jni::sys::jboolean { + let loading = Node::is_stopping() || Node::is_restarting() || Node::is_starting(); + return (!loading && !Node::is_running()) as jni::sys::jboolean; +} + +#[allow(dead_code)] +#[cfg(target_os = "android")] +#[allow(non_snake_case)] +#[no_mangle] +/// Check if node stop is possible. +pub extern "C" fn Java_mw_gri_android_BackgroundService_canStopNode( + _env: jni::JNIEnv, + _class: jni::objects::JObject, + _activity: jni::objects::JObject, +) -> jni::sys::jboolean { + let loading = Node::is_stopping() || Node::is_restarting() || Node::is_starting(); + return (!loading && Node::is_running()) as jni::sys::jboolean; +} + +#[allow(dead_code)] +#[cfg(target_os = "android")] +#[allow(non_snake_case)] +#[no_mangle] +/// Check if node stop is possible. +pub extern "C" fn Java_mw_gri_android_NotificationActionsReceiver_isNodeRunning( + _env: jni::JNIEnv, + _class: jni::objects::JObject, + _activity: jni::objects::JObject, +) -> jni::sys::jboolean { + return Node::is_running() as jni::sys::jboolean; +} + +#[allow(dead_code)] +#[cfg(target_os = "android")] +#[allow(non_snake_case)] +#[no_mangle] +/// Start node from Android Java code. +pub extern "C" fn Java_mw_gri_android_NotificationActionsReceiver_startNode( + _env: jni::JNIEnv, + _class: jni::objects::JObject, + _activity: jni::objects::JObject, +) { + Node::start(); +} + +#[allow(dead_code)] +#[cfg(target_os = "android")] +#[allow(non_snake_case)] +#[no_mangle] +/// Stop node from Android Java code. +pub extern "C" fn Java_mw_gri_android_NotificationActionsReceiver_stopNode( + _env: jni::JNIEnv, + _class: jni::objects::JObject, + _activity: jni::objects::JObject, +) { + Node::stop(false); +} + +#[allow(dead_code)] +#[cfg(target_os = "android")] +#[allow(non_snake_case)] +#[no_mangle] +/// Stop node from Android Java code. +pub extern "C" fn Java_mw_gri_android_NotificationActionsReceiver_stopNodeToExit( + _env: jni::JNIEnv, + _class: jni::objects::JObject, + _activity: jni::objects::JObject, +) { + Node::stop(true); +} + #[allow(dead_code)] #[cfg(target_os = "android")] #[allow(non_snake_case)]