From ab9117ccebf25741b19aba22e48529a0a6f201d9 Mon Sep 17 00:00:00 2001 From: ardocrat Date: Tue, 9 Jul 2024 00:36:44 +0300 Subject: [PATCH] android: node control and exit from the notification --- android/app/src/main/AndroidManifest.xml | 4 + .../mw/gri/android/BackgroundService.java | 138 +++++++++++++++--- .../android/NotificationActionsReceiver.java | 35 +++++ .../src/main/res/drawable-anydpi/ic_close.xml | 15 ++ .../src/main/res/drawable-anydpi/ic_start.xml | 15 ++ .../src/main/res/drawable-anydpi/ic_stop.xml | 15 ++ .../src/main/res/drawable-hdpi/ic_close.png | Bin 0 -> 345 bytes .../src/main/res/drawable-hdpi/ic_start.png | Bin 0 -> 272 bytes .../src/main/res/drawable-hdpi/ic_stop.png | Bin 0 -> 140 bytes .../src/main/res/drawable-mdpi/ic_close.png | Bin 0 -> 239 bytes .../src/main/res/drawable-mdpi/ic_start.png | Bin 0 -> 171 bytes .../src/main/res/drawable-mdpi/ic_stop.png | Bin 0 -> 119 bytes .../src/main/res/drawable-xhdpi/ic_close.png | Bin 0 -> 383 bytes .../src/main/res/drawable-xhdpi/ic_start.png | Bin 0 -> 282 bytes .../src/main/res/drawable-xhdpi/ic_stop.png | Bin 0 -> 168 bytes .../src/main/res/drawable-xxhdpi/ic_close.png | Bin 0 -> 481 bytes .../src/main/res/drawable-xxhdpi/ic_start.png | Bin 0 -> 374 bytes .../src/main/res/drawable-xxhdpi/ic_stop.png | Bin 0 -> 197 bytes src/node/node.rs | 122 ++++++++++++++++ 19 files changed, 325 insertions(+), 19 deletions(-) create mode 100644 android/app/src/main/java/mw/gri/android/NotificationActionsReceiver.java create mode 100644 android/app/src/main/res/drawable-anydpi/ic_close.xml create mode 100644 android/app/src/main/res/drawable-anydpi/ic_start.xml create mode 100644 android/app/src/main/res/drawable-anydpi/ic_stop.xml create mode 100644 android/app/src/main/res/drawable-hdpi/ic_close.png create mode 100644 android/app/src/main/res/drawable-hdpi/ic_start.png create mode 100644 android/app/src/main/res/drawable-hdpi/ic_stop.png create mode 100644 android/app/src/main/res/drawable-mdpi/ic_close.png create mode 100644 android/app/src/main/res/drawable-mdpi/ic_start.png create mode 100644 android/app/src/main/res/drawable-mdpi/ic_stop.png create mode 100644 android/app/src/main/res/drawable-xhdpi/ic_close.png create mode 100644 android/app/src/main/res/drawable-xhdpi/ic_start.png create mode 100644 android/app/src/main/res/drawable-xhdpi/ic_stop.png create mode 100644 android/app/src/main/res/drawable-xxhdpi/ic_close.png create mode 100644 android/app/src/main/res/drawable-xxhdpi/ic_start.png create mode 100644 android/app/src/main/res/drawable-xxhdpi/ic_stop.png 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 0000000000000000000000000000000000000000..136ac44ea693d4c1541cd1eef102bece1be33725 GIT binary patch literal 345 zcmV-f0jB4+0@1%Y0lG$-@6AuQ-FvDaD?iRuuXyyY9U;)byj%DHIBY z8^O#5T#H5C2d=e3fxot6#M&w3=~do8){;=rtO~4&o|LqY@Xo18LP2i`UQt7&8N%~U zO)C^QMS~tSHLBf1yQC%y<|p-fK=TaE2Q^u-{U)vX3~CG1hN1UtsL>M|Z+|D(@@(K( z%ugdE?*@)Vjr;r(@j~6jFn^l1^r^cDYCPbV1h~_8R?JUkW&TEQ{LCbwMt5lZ6V$#? z>xSO!SlY#S@|j7({5>RBVwLf(bx>r7Nq$Xn}NG{`RGT$!QH2ZzsvydBfk zd%+;TkaK6odN&9ZE9C7F`fZ_HA#bXCYJ+NpoFmiGeP2WMLMODS&`kH#2IUGJn4a#a z6^a#lFcZBS1o8{D=EHbnMmirHvI}i>E*iWSIx_>Ei7GARe$ZBW)(93^=rd!C`6n;! W4VeIvhvRYp0000G&+ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..44c823e79e8fafb17cfed3af8e611c13594c7fe1 GIT binary patch literal 140 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBf<0XvLn`9lo>LTRFc4wBsCCLw zAcN<-{mr0Q;TPr|wZLO_EfBO5D}VbAj_ iIlb`rA8pU7hm5IGm+tO5&3X`MJcFmJpUXO@geCxtk1%Bb literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a6551fb94c1ee72071283d7c524d11f8a1326d87 GIT binary patch literal 239 zcmV0l3=2Lh$JxhYL)*PZ+?B69V6?}%t$P>|xD zVn}iEd4rs4(1x2(2y?E9nv;XU=ZlmK*wY-E-2tl5CT29I=rvfw;T2YsOeQtFX~5)l z&B$_UGX~bZ*EREv26pG`D;m8njBJO@9BjxollCF2`D{j+{AbU!WWb*0V0VB+Wcex{ p6z0qgZGr}E_^YTch-goKkSFlzSvEk_z)JuC002ovPDHLkV1nRTYk2?w literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9611b5fabafc727903533da313bde40010cd6c09 GIT binary patch literal 171 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjMV>B>Ar*1S2@FS|I&-3 z5LO57Jcf^)3TbZB7jrd4F);mau#k$Km(-BL$YaBn6joB2!mJ~9z=3;L)TRUVcf=YF z_$!FHUw0Pz(SM&wBBFI&f5wOU2Hgdj0vB~AWlrgTe~DWM4f(waJn literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f7444508b81d61cc942692617daca61543e3eb64 GIT binary patch literal 119 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjcAhSdAr*1S2@H3kWM;VY($Ix;Q~ZfR~|OR&()>zyyDw ONerH@elF{r5}E*8T_T$R literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..433762c6bcc1e9cb92d73d09fcc3a28202080a3d GIT binary patch literal 383 zcmV-_0f7FAP)qAnxpC# z!;J5(eThS6??JWa;F_|whPEG7yC@j0S9Q$(?318&!p5Cyn_!M^Fg}S{_XIUXTSHsd zs%?rnd@7D>7}#WW;vB}Ar;WX8yMW;&G$8i3j&_UjhNhB@p>#4v}A{;b)Qk^?%U7uQqfXaCmL`3odFtyD1aX0ME&v8{-2K;>SZVCiv%aX4t8Wo84mLXqTB-}3$tt=g0QxrTd z5F9KcURMk}FA!}lU0$CZUM~<$ct%!uzd*3F^m$em_^d#*;CacRU4h_e>G9lT(7r&l z=GjT$dj*1%Wz4VfQ&541+mDdzFR|O7QP+QDZnqK}INi*--cGwc(6~KPx<1s`aC%$C g_l+$P5fS-k7sKj??3-2dhyVZp07*qoM6N<$f|1U55dZ)H literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b1de0c55cd6d4e3d167cd2dfb3cef50ff1aa05cd GIT binary patch literal 168 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpUtd`}n0kczms=QnaP7>XRe@W#I6 z-@|KDxu0{mO5GCC7 dA7p1iJOBEL9qZ)&l&1oD44$rjF6*2UngEV-J`Vr@ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a403ca3fa3ac70cf5fa29f427897eac77a66ca95 GIT binary patch literal 481 zcmV<70UrK|P)C4>33BKg>6p-MvH~SIh<&#$J`X>Ge>>u zv2zQ1eCehqb(~3|kd0O*_PS;+;kRFOGf~G(3We;oGV!i!rV79Lp__ zCt`+nV=Zl!!i2$1>jSwIBW?j`^-?|-145#=a3TuYjeJ{OoNcQ@esKS#?SWj1{4KyW zZC=X9VpvFhg%eTMZhf@XH8`L&++t}A3n%I$B+A+$DZxqw;UyU>dg!5}nA0biD zR=Or4-)?O`?jS#q`+Z9Q z;Y73&QYhqJD-)C3DusNzRrC6bU!cmtui)k3mpJo6fe-rGQmAF-n3}M$=eRINs=T 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)]