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 + } +}