>()
override fun onMessage(webSocket: WebSocket, text: String) {
@@ -243,6 +254,8 @@ class EmbeddedPushService : Service() {
return@ready
}
Log.i(TAG, "Push gateway subscription ready")
+ releaseWakeLock()
+ EmbeddedPushAlarm.schedule(this@EmbeddedPushService, HEARTBEAT_MS)
PushDiagnostics.record(this@EmbeddedPushService, PushOutcome.EMBEDDED_READY)
ready = true
retryDelay = BASE_BACKOFF_MS
@@ -278,7 +291,7 @@ class EmbeddedPushService : Service() {
PushDiagnostics.record(this@EmbeddedPushService, outcome)
socket = null
ready = false
- scheduleReconnect(url)
+ scheduleReconnect()
}
}
}
@@ -333,6 +346,9 @@ class EmbeddedPushService : Service() {
private const val FOREGROUND_ID = 0x5AB1E
private const val NORMAL_CLOSURE = 1000
private const val PING_SECONDS = 30L
+ private const val WAKELOCK_TAG = "SableEmbeddedPush:WakeLock"
+ private const val CONNECT_WAKELOCK_MS = 30_000L
+ private const val HEARTBEAT_MS = 900_000L
private const val BASE_BACKOFF_MS = 1_000L
private const val MAX_BACKOFF_MS = 120_000L
diff --git a/android/src/main/java/app/tauri/notification/NotificationPlugin.kt b/android/src/main/java/app/tauri/notification/NotificationPlugin.kt
index efc2c328..29e1607a 100644
--- a/android/src/main/java/app/tauri/notification/NotificationPlugin.kt
+++ b/android/src/main/java/app/tauri/notification/NotificationPlugin.kt
@@ -6,9 +6,12 @@ import android.app.Activity
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
+import android.net.Uri
import android.os.Build
import android.os.Handler
import android.os.Looper
+import android.os.PowerManager
+import android.provider.Settings
import android.webkit.WebView
import app.tauri.PermissionState
import app.tauri.annotation.Command
@@ -1110,6 +1113,27 @@ class NotificationPlugin(private val activity: Activity): Plugin(activity) {
invoke.resolve()
}
+ @Command
+ fun isIgnoringBatteryOptimizations(invoke: Invoke) {
+ val manager = activity.getSystemService(Context.POWER_SERVICE) as PowerManager
+ val result = JSObject()
+ result.put("ignoring", manager.isIgnoringBatteryOptimizations(activity.packageName))
+ invoke.resolve(result)
+ }
+
+ @Command
+ fun requestIgnoreBatteryOptimizations(invoke: Invoke) {
+ try {
+ activity.startActivity(
+ Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
+ .setData(Uri.parse("package:${activity.packageName}")),
+ )
+ invoke.resolve()
+ } catch (error: Exception) {
+ invoke.reject(error.message ?: "Could not open the battery optimization prompt")
+ }
+ }
+
@Command
fun takePushDiagnostics(invoke: Invoke) {
val snapshot = PushDiagnostics.drain(activity.applicationContext)
diff --git a/android/src/test/java/app/tauri/notification/EmbeddedPushAlarmTest.kt b/android/src/test/java/app/tauri/notification/EmbeddedPushAlarmTest.kt
new file mode 100644
index 00000000..002f586c
--- /dev/null
+++ b/android/src/test/java/app/tauri/notification/EmbeddedPushAlarmTest.kt
@@ -0,0 +1,83 @@
+package app.tauri.notification
+
+import android.app.AlarmManager
+import android.content.Context
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.RuntimeEnvironment
+import org.robolectric.Shadows.shadowOf
+
+@RunWith(RobolectricTestRunner::class)
+class EmbeddedPushAlarmTest {
+
+ private val context get() = RuntimeEnvironment.getApplication()
+
+ private val alarms
+ get() = shadowOf(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager)
+
+ @Test
+ fun schedulesAWakeupAlarmThatSurvivesDoze() {
+ EmbeddedPushAlarm.schedule(context, 60_000L)
+
+ val alarm = alarms.nextScheduledAlarm
+ assertNotNull(alarm)
+ assertEquals(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarm!!.type)
+ }
+
+ @Test
+ fun cancelDropsThePendingAlarm() {
+ EmbeddedPushAlarm.schedule(context, 60_000L)
+ EmbeddedPushAlarm.cancel(context)
+
+ assertNull(alarms.nextScheduledAlarm)
+ }
+
+ @Test
+ fun rescheduleReplacesTheAlarmInsteadOfStacking() {
+ EmbeddedPushAlarm.schedule(context, 60_000L)
+ EmbeddedPushAlarm.schedule(context, 120_000L)
+
+ assertEquals(1, alarms.scheduledAlarms.size)
+ }
+
+ @Test
+ fun anAlarmForAnotherDistributorDoesNotStartTheService() {
+ val state = UnifiedPushStateStore(context)
+ state.activeProvider = "unifiedpush"
+ state.endpoint = ENDPOINT
+
+ EmbeddedPushAlarm.onAlarm(context)
+
+ assertNull(shadowOf(context).nextStartedService)
+ }
+
+ @Test
+ fun anAlarmWithoutAnEndpointDoesNotStartTheService() {
+ UnifiedPushStateStore(context).activeProvider = "embedded"
+
+ EmbeddedPushAlarm.onAlarm(context)
+
+ assertNull(shadowOf(context).nextStartedService)
+ }
+
+ @Test
+ fun anAlarmForTheEmbeddedDistributorRestartsTheService() {
+ val state = UnifiedPushStateStore(context)
+ state.activeProvider = "embedded"
+ state.endpoint = ENDPOINT
+
+ EmbeddedPushAlarm.onAlarm(context)
+
+ val started = shadowOf(context).nextStartedService
+ assertNotNull(started)
+ assertEquals(EmbeddedPushService::class.java.name, started.component?.className)
+ }
+
+ private companion object {
+ const val ENDPOINT = "https://ntfy.sh/up0123456789ab?up=1"
+ }
+}
diff --git a/android/src/test/java/app/tauri/notification/EmbeddedPushServiceTest.kt b/android/src/test/java/app/tauri/notification/EmbeddedPushServiceTest.kt
index 44797acb..0120ad67 100644
--- a/android/src/test/java/app/tauri/notification/EmbeddedPushServiceTest.kt
+++ b/android/src/test/java/app/tauri/notification/EmbeddedPushServiceTest.kt
@@ -1,8 +1,11 @@
package app.tauri.notification
+import android.app.AlarmManager
+import android.content.Context
import android.net.ConnectivityManager
import android.app.NotificationManager
import android.os.Looper
+import android.os.SystemClock
import android.util.Base64
import com.google.crypto.tink.apps.fixed_webpush.WebPushHybridEncrypt
import io.mockk.every
@@ -77,6 +80,16 @@ class EmbeddedPushServiceTest {
service.onStartCommand(null, 0, 1)
}
+ private fun nextAlarmDelayMs(): Long {
+ val alarms = shadowOf(service.getSystemService(Context.ALARM_SERVICE) as AlarmManager)
+ val alarm = alarms.nextScheduledAlarm
+ assertNotNull(alarm)
+ return alarm.triggerAtTime - SystemClock.elapsedRealtime()
+ }
+
+ private fun scheduledAlarmCount(): Int =
+ shadowOf(service.getSystemService(Context.ALARM_SERVICE) as AlarmManager).scheduledAlarms.size
+
private fun frame(index: Int, text: String) {
val (socket, listener) = connections[index]
listener.onMessage(socket, text)
@@ -201,7 +214,8 @@ class EmbeddedPushServiceTest {
finishWork()
val (socket, listener) = connections[0]
listener.onFailure(socket, IOException("offline"), null)
- shadowOf(Looper.getMainLooper()).idleFor(Duration.ofSeconds(1))
+ shadowOf(Looper.getMainLooper()).idle()
+ start()
frame(1, """{"event":"open","time":300}""")
finishWork()
frame(1, message("A"))
@@ -524,7 +538,10 @@ class EmbeddedPushServiceTest {
val (socket, listener) = connections.single()
listener.onFailure(socket, IOException("disconnected"), null)
listener.onClosed(socket, 1000, "closed")
- shadowOf(Looper.getMainLooper()).idleFor(Duration.ofSeconds(1))
+ shadowOf(Looper.getMainLooper()).idle()
+
+ assertEquals(1, scheduledAlarmCount())
+ start()
assertEquals(2, connections.size)
assertEquals(1, PushDiagnostics.drain(service).counts["EMBEDDED_SOCKET_FAILED"])
@@ -537,9 +554,11 @@ class EmbeddedPushServiceTest {
val (socket, listener) = connections.single()
listener.onFailure(socket, IOException("disconnected"), null)
shadowOf(Looper.getMainLooper()).idle()
+ assertEquals(1, scheduledAlarmCount())
+
service.onDestroy()
- shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMinutes(5))
+ assertEquals(0, scheduledAlarmCount())
assertEquals(1, connections.size)
}
@@ -550,10 +569,12 @@ class EmbeddedPushServiceTest {
val (socket, listener) = connections.last()
listener.onFailure(socket, IOException("offline"), null)
shadowOf(Looper.getMainLooper()).idle()
+
+ val delay = nextAlarmDelayMs()
+ assertTrue(delay in 1..120_000)
+
val beforeRetry = connections.size
- shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(999))
- assertEquals(beforeRetry, connections.size)
- shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(119_001))
+ start()
assertEquals(beforeRetry + 1, connections.size)
}
}
diff --git a/build.rs b/build.rs
index 97769319..e09c9575 100644
--- a/build.rs
+++ b/build.rs
@@ -26,6 +26,10 @@ const COMMANDS: &[&str] = &[
"set_click_listener_active",
"set_action_listener_active",
"set_push_message_listener_active",
+ "set_encrypted_content_allowed",
+ "take_push_diagnostics",
+ "is_ignoring_battery_optimizations",
+ "request_ignore_battery_optimizations",
"list_distributors",
"set_distributor",
"set_token",
diff --git a/permissions/autogenerated/commands/is_ignoring_battery_optimizations.toml b/permissions/autogenerated/commands/is_ignoring_battery_optimizations.toml
new file mode 100644
index 00000000..a5e08bd7
--- /dev/null
+++ b/permissions/autogenerated/commands/is_ignoring_battery_optimizations.toml
@@ -0,0 +1,13 @@
+# Automatically generated - DO NOT EDIT!
+
+"$schema" = "../../schemas/schema.json"
+
+[[permission]]
+identifier = "allow-is-ignoring-battery-optimizations"
+description = "Enables the is_ignoring_battery_optimizations command without any pre-configured scope."
+commands.allow = ["is_ignoring_battery_optimizations"]
+
+[[permission]]
+identifier = "deny-is-ignoring-battery-optimizations"
+description = "Denies the is_ignoring_battery_optimizations command without any pre-configured scope."
+commands.deny = ["is_ignoring_battery_optimizations"]
diff --git a/permissions/autogenerated/commands/request_ignore_battery_optimizations.toml b/permissions/autogenerated/commands/request_ignore_battery_optimizations.toml
new file mode 100644
index 00000000..9145ebe6
--- /dev/null
+++ b/permissions/autogenerated/commands/request_ignore_battery_optimizations.toml
@@ -0,0 +1,13 @@
+# Automatically generated - DO NOT EDIT!
+
+"$schema" = "../../schemas/schema.json"
+
+[[permission]]
+identifier = "allow-request-ignore-battery-optimizations"
+description = "Enables the request_ignore_battery_optimizations command without any pre-configured scope."
+commands.allow = ["request_ignore_battery_optimizations"]
+
+[[permission]]
+identifier = "deny-request-ignore-battery-optimizations"
+description = "Denies the request_ignore_battery_optimizations command without any pre-configured scope."
+commands.deny = ["request_ignore_battery_optimizations"]
diff --git a/permissions/autogenerated/reference.md b/permissions/autogenerated/reference.md
index cce79c7b..e4a6d948 100644
--- a/permissions/autogenerated/reference.md
+++ b/permissions/autogenerated/reference.md
@@ -35,6 +35,8 @@ It allows all notification related features.
- `allow-set-push-message-listener-active`
- `allow-set-encrypted-content-allowed`
- `allow-take-push-diagnostics`
+- `allow-is-ignoring-battery-optimizations`
+- `allow-request-ignore-battery-optimizations`
- `allow-list-distributors`
- `allow-set-distributor`
- `allow-set-token`
@@ -259,6 +261,32 @@ Denies the get_pending command without any pre-configured scope.
|
+`notifications:allow-is-ignoring-battery-optimizations`
+
+ |
+
+
+Enables the is_ignoring_battery_optimizations command without any pre-configured scope.
+
+ |
+
+
+
+|
+
+`notifications:deny-is-ignoring-battery-optimizations`
+
+ |
+
+
+Denies the is_ignoring_battery_optimizations command without any pre-configured scope.
+
+ |
+
+
+
+|
+
`notifications:allow-is-permission-granted`
|
@@ -545,6 +573,32 @@ Denies the remove_listener command without any pre-configured scope.
|
+`notifications:allow-request-ignore-battery-optimizations`
+
+ |
+
+
+Enables the request_ignore_battery_optimizations command without any pre-configured scope.
+
+ |
+
+
+
+|
+
+`notifications:deny-request-ignore-battery-optimizations`
+
+ |
+
+
+Denies the request_ignore_battery_optimizations command without any pre-configured scope.
+
+ |
+
+
+
+|
+
`notifications:allow-request-permission`
|
diff --git a/permissions/default.toml b/permissions/default.toml
index b31883ce..ee3826b6 100644
--- a/permissions/default.toml
+++ b/permissions/default.toml
@@ -37,6 +37,8 @@ permissions = [
"allow-set-push-message-listener-active",
"allow-set-encrypted-content-allowed",
"allow-take-push-diagnostics",
+ "allow-is-ignoring-battery-optimizations",
+ "allow-request-ignore-battery-optimizations",
"allow-list-distributors",
"allow-set-distributor",
"allow-set-token",
diff --git a/permissions/schemas/schema.json b/permissions/schemas/schema.json
index 021e46a1..4c5480d9 100644
--- a/permissions/schemas/schema.json
+++ b/permissions/schemas/schema.json
@@ -390,6 +390,18 @@
"const": "deny-get-pending",
"markdownDescription": "Denies the get_pending command without any pre-configured scope."
},
+ {
+ "description": "Enables the is_ignoring_battery_optimizations command without any pre-configured scope.",
+ "type": "string",
+ "const": "allow-is-ignoring-battery-optimizations",
+ "markdownDescription": "Enables the is_ignoring_battery_optimizations command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the is_ignoring_battery_optimizations command without any pre-configured scope.",
+ "type": "string",
+ "const": "deny-is-ignoring-battery-optimizations",
+ "markdownDescription": "Denies the is_ignoring_battery_optimizations command without any pre-configured scope."
+ },
{
"description": "Enables the is_permission_granted command without any pre-configured scope.",
"type": "string",
@@ -522,6 +534,18 @@
"const": "deny-remove-listener",
"markdownDescription": "Denies the remove_listener command without any pre-configured scope."
},
+ {
+ "description": "Enables the request_ignore_battery_optimizations command without any pre-configured scope.",
+ "type": "string",
+ "const": "allow-request-ignore-battery-optimizations",
+ "markdownDescription": "Enables the request_ignore_battery_optimizations command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the request_ignore_battery_optimizations command without any pre-configured scope.",
+ "type": "string",
+ "const": "deny-request-ignore-battery-optimizations",
+ "markdownDescription": "Denies the request_ignore_battery_optimizations command without any pre-configured scope."
+ },
{
"description": "Enables the request_permission command without any pre-configured scope.",
"type": "string",
@@ -643,10 +667,10 @@
"markdownDescription": "Denies the unregister_for_push_notifications command without any pre-configured scope."
},
{
- "description": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-register-for-push-notifications`\n- `allow-unregister-for-push-notifications`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-cancel`\n- `allow-cancel-all`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-remove-all`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`\n- `allow-set-click-listener-active`\n- `allow-set-action-listener-active`\n- `allow-set-push-message-listener-active`\n- `allow-set-encrypted-content-allowed`\n- `allow-take-push-diagnostics`\n- `allow-list-distributors`\n- `allow-set-distributor`\n- `allow-set-token`",
+ "description": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-register-for-push-notifications`\n- `allow-unregister-for-push-notifications`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-cancel`\n- `allow-cancel-all`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-remove-all`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`\n- `allow-set-click-listener-active`\n- `allow-set-action-listener-active`\n- `allow-set-push-message-listener-active`\n- `allow-set-encrypted-content-allowed`\n- `allow-take-push-diagnostics`\n- `allow-is-ignoring-battery-optimizations`\n- `allow-request-ignore-battery-optimizations`\n- `allow-list-distributors`\n- `allow-set-distributor`\n- `allow-set-token`",
"type": "string",
"const": "default",
- "markdownDescription": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-register-for-push-notifications`\n- `allow-unregister-for-push-notifications`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-cancel`\n- `allow-cancel-all`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-remove-all`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`\n- `allow-set-click-listener-active`\n- `allow-set-action-listener-active`\n- `allow-set-push-message-listener-active`\n- `allow-set-encrypted-content-allowed`\n- `allow-take-push-diagnostics`\n- `allow-list-distributors`\n- `allow-set-distributor`\n- `allow-set-token`"
+ "markdownDescription": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-register-for-push-notifications`\n- `allow-unregister-for-push-notifications`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-cancel`\n- `allow-cancel-all`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-remove-all`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`\n- `allow-set-click-listener-active`\n- `allow-set-action-listener-active`\n- `allow-set-push-message-listener-active`\n- `allow-set-encrypted-content-allowed`\n- `allow-take-push-diagnostics`\n- `allow-is-ignoring-battery-optimizations`\n- `allow-request-ignore-battery-optimizations`\n- `allow-list-distributors`\n- `allow-set-distributor`\n- `allow-set-token`"
}
]
}
diff --git a/src/commands.rs b/src/commands.rs
index 54ff6ba8..b66977bd 100644
--- a/src/commands.rs
+++ b/src/commands.rs
@@ -203,6 +203,28 @@ pub async fn set_encrypted_content_allowed(
return notification.set_encrypted_content_allowed(allowed);
}
+#[command]
+pub async fn is_ignoring_battery_optimizations(
+ _app: AppHandle,
+ notification: State<'_, Notifications>,
+) -> Result {
+ #[cfg(mobile)]
+ return notification.is_ignoring_battery_optimizations().await;
+ #[cfg(desktop)]
+ return notification.is_ignoring_battery_optimizations();
+}
+
+#[command]
+pub async fn request_ignore_battery_optimizations(
+ _app: AppHandle,
+ notification: State<'_, Notifications>,
+) -> Result<()> {
+ #[cfg(mobile)]
+ return notification.request_ignore_battery_optimizations().await;
+ #[cfg(desktop)]
+ return notification.request_ignore_battery_optimizations();
+}
+
#[command]
pub async fn take_push_diagnostics(
_app: AppHandle,
diff --git a/src/desktop.rs b/src/desktop.rs
index 5295be3e..50084476 100644
--- a/src/desktop.rs
+++ b/src/desktop.rs
@@ -402,6 +402,14 @@ impl Notifications {
}
#[allow(clippy::unused_self)]
+ pub fn is_ignoring_battery_optimizations(&self) -> crate::Result {
+ Ok(true)
+ }
+
+ pub fn request_ignore_battery_optimizations(&self) -> crate::Result<()> {
+ Ok(())
+ }
+
pub fn take_push_diagnostics(&self) -> crate::Result {
Ok(crate::models::PushDiagnostics::default())
}
diff --git a/src/lib.rs b/src/lib.rs
index 80948df4..31ee1d37 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -331,6 +331,8 @@ pub fn init() -> TauriPlugin> {
commands::set_push_message_listener_active,
commands::set_encrypted_content_allowed,
commands::take_push_diagnostics,
+ commands::is_ignoring_battery_optimizations,
+ commands::request_ignore_battery_optimizations,
commands::remove_active,
commands::remove_all,
commands::cancel,
diff --git a/src/macos.rs b/src/macos.rs
index 19959ac9..f13484ed 100644
--- a/src/macos.rs
+++ b/src/macos.rs
@@ -337,6 +337,14 @@ impl Notifications {
}
#[allow(clippy::unused_self)]
+ pub fn is_ignoring_battery_optimizations(&self) -> crate::Result {
+ Ok(true)
+ }
+
+ pub fn request_ignore_battery_optimizations(&self) -> crate::Result<()> {
+ Ok(())
+ }
+
pub fn take_push_diagnostics(&self) -> crate::Result {
Ok(crate::models::PushDiagnostics::default())
}
diff --git a/src/mobile.rs b/src/mobile.rs
index c75a3521..5f48513a 100644
--- a/src/mobile.rs
+++ b/src/mobile.rs
@@ -321,6 +321,38 @@ impl Notifications {
}
}
+ pub async fn is_ignoring_battery_optimizations(&self) -> crate::Result {
+ #[cfg(all(target_os = "android", feature = "push-notifications"))]
+ {
+ self.0
+ .run_mobile_plugin_async::(
+ "isIgnoringBatteryOptimizations",
+ (),
+ )
+ .await
+ .map(|r| r.ignoring)
+ .map_err(Into::into)
+ }
+ #[cfg(not(all(target_os = "android", feature = "push-notifications")))]
+ {
+ Ok(true)
+ }
+ }
+
+ pub async fn request_ignore_battery_optimizations(&self) -> crate::Result<()> {
+ #[cfg(all(target_os = "android", feature = "push-notifications"))]
+ {
+ self.0
+ .run_mobile_plugin_async::<()>("requestIgnoreBatteryOptimizations", ())
+ .await
+ .map_err(Into::into)
+ }
+ #[cfg(not(all(target_os = "android", feature = "push-notifications")))]
+ {
+ Ok(())
+ }
+ }
+
pub async fn take_push_diagnostics(&self) -> crate::Result {
#[cfg(target_os = "android")]
{
diff --git a/src/models.rs b/src/models.rs
index 46d5b762..37783617 100644
--- a/src/models.rs
+++ b/src/models.rs
@@ -41,6 +41,12 @@ pub struct DistributorsResponse {
pub distributors: Vec,
}
+#[cfg(all(target_os = "android", feature = "push-notifications"))]
+#[derive(Debug, Deserialize)]
+pub struct BatteryOptimizationResponse {
+ pub ignoring: bool,
+}
+
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PushDiagnostics {
diff --git a/src/windows.rs b/src/windows.rs
index 3f81a857..56cf573e 100644
--- a/src/windows.rs
+++ b/src/windows.rs
@@ -1071,6 +1071,14 @@ impl Notifications {
}
#[allow(clippy::unused_self)]
+ pub fn is_ignoring_battery_optimizations(&self) -> crate::Result {
+ Ok(true)
+ }
+
+ pub fn request_ignore_battery_optimizations(&self) -> crate::Result<()> {
+ Ok(())
+ }
+
pub fn take_push_diagnostics(&self) -> crate::Result {
Ok(crate::models::PushDiagnostics::default())
}