From 6f63fc288af9960d08bd45d093d90e1f643ef343 Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Sun, 1 Mar 2026 20:28:32 +0530 Subject: [PATCH] fix(android): return NOT_AUTHORIZED when notify permission is lost --- .../ai/openclaw/android/node/SystemHandler.kt | 7 ++++- .../android/node/SystemHandlerTest.kt | 31 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/apps/android/app/src/main/java/ai/openclaw/android/node/SystemHandler.kt b/apps/android/app/src/main/java/ai/openclaw/android/node/SystemHandler.kt index ed6449f9434..6d8fd5b86aa 100644 --- a/apps/android/app/src/main/java/ai/openclaw/android/node/SystemHandler.kt +++ b/apps/android/app/src/main/java/ai/openclaw/android/node/SystemHandler.kt @@ -61,7 +61,7 @@ private class AndroidSystemNotificationPoster( ContextCompat.checkSelfPermission(appContext, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED ) { - return + throw SecurityException("notifications permission missing") } NotificationManagerCompat.from(appContext).notify((System.currentTimeMillis() and 0x7FFFFFFF).toInt(), notification) } @@ -127,6 +127,11 @@ class SystemHandler private constructor( return try { poster.post(params) GatewaySession.InvokeResult.ok(null) + } catch (_: SecurityException) { + GatewaySession.InvokeResult.error( + code = "NOT_AUTHORIZED", + message = "NOT_AUTHORIZED: notifications", + ) } catch (err: Throwable) { GatewaySession.InvokeResult.error( code = "UNAVAILABLE", diff --git a/apps/android/app/src/test/java/ai/openclaw/android/node/SystemHandlerTest.kt b/apps/android/app/src/test/java/ai/openclaw/android/node/SystemHandlerTest.kt index 7b49a3641e0..770d1920c76 100644 --- a/apps/android/app/src/test/java/ai/openclaw/android/node/SystemHandlerTest.kt +++ b/apps/android/app/src/test/java/ai/openclaw/android/node/SystemHandlerTest.kt @@ -36,6 +36,26 @@ class SystemHandlerTest { assertTrue(result.ok) assertEquals(1, poster.posts) } + + @Test + fun handleSystemNotify_returnsUnauthorizedWhenPostFailsPermission() { + val handler = SystemHandler.forTesting(poster = ThrowingPoster(authorized = true, error = SecurityException("denied"))) + + val result = handler.handleSystemNotify("""{"title":"OpenClaw","body":"done"}""") + + assertFalse(result.ok) + assertEquals("NOT_AUTHORIZED", result.error?.code) + } + + @Test + fun handleSystemNotify_returnsUnavailableWhenPostFailsUnexpectedly() { + val handler = SystemHandler.forTesting(poster = ThrowingPoster(authorized = true, error = IllegalStateException("boom"))) + + val result = handler.handleSystemNotify("""{"title":"OpenClaw","body":"done"}""") + + assertFalse(result.ok) + assertEquals("UNAVAILABLE", result.error?.code) + } } private class FakePoster( @@ -50,3 +70,14 @@ private class FakePoster( posts += 1 } } + +private class ThrowingPoster( + private val authorized: Boolean, + private val error: Throwable, +) : SystemNotificationPoster { + override fun isAuthorized(): Boolean = authorized + + override fun post(request: SystemNotifyRequest) { + throw error + } +}