11package ai.openclaw.app.node
22
3- import android.content.Context
3+ import android.Manifest
4+ import android.app.Application
45import android.content.pm.ApplicationInfo
56import kotlinx.serialization.json.Json
67import kotlinx.serialization.json.JsonObject
@@ -15,6 +16,7 @@ import org.junit.Test
1516import org.junit.runner.RunWith
1617import org.robolectric.RobolectricTestRunner
1718import org.robolectric.RuntimeEnvironment
19+ import org.robolectric.Shadows.shadowOf
1820
1921@RunWith(RobolectricTestRunner ::class )
2022class DeviceHandlerTest {
@@ -274,6 +276,31 @@ class DeviceHandlerTest {
274276 assertTrue(! callLog.getValue(" promptable" ).jsonPrimitive.boolean)
275277 }
276278
279+ @Test
280+ fun handleDevicePermissions_requiresReadAndWritePermissionPairs () {
281+ val app = appContext()
282+ val handler = DeviceHandler (app)
283+ val permissionPairs =
284+ listOf (
285+ Triple (" contacts" , Manifest .permission.READ_CONTACTS , Manifest .permission.WRITE_CONTACTS ),
286+ Triple (" calendar" , Manifest .permission.READ_CALENDAR , Manifest .permission.WRITE_CALENDAR ),
287+ )
288+
289+ for ((key, readPermission, writePermission) in permissionPairs) {
290+ shadowOf(app).denyPermissions(readPermission, writePermission)
291+
292+ shadowOf(app).grantPermissions(readPermission)
293+ assertEquals(" $key read-only" , " denied" , permissionStatus(handler.handleDevicePermissions(null ).payloadJson, key))
294+
295+ shadowOf(app).denyPermissions(readPermission)
296+ shadowOf(app).grantPermissions(writePermission)
297+ assertEquals(" $key write-only" , " denied" , permissionStatus(handler.handleDevicePermissions(null ).payloadJson, key))
298+
299+ shadowOf(app).grantPermissions(readPermission)
300+ assertEquals(" $key read-write" , " granted" , permissionStatus(handler.handleDevicePermissions(null ).payloadJson, key))
301+ }
302+ }
303+
277304 @Test
278305 fun handleDeviceHealth_returnsExpectedShape () {
279306 val handler = DeviceHandler (appContext())
@@ -423,12 +450,25 @@ class DeviceHandlerTest {
423450 assertTrue(isSystemDeviceApp(appInfo))
424451 }
425452
426- private fun appContext (): Context = RuntimeEnvironment .getApplication()
453+ private fun appContext (): Application = RuntimeEnvironment .getApplication()
427454
428455 private fun parsePayload (payloadJson : String? ): JsonObject {
429456 val jsonString = payloadJson ? : error(" expected payload" )
430457 return Json .parseToJsonElement(jsonString).jsonObject
431458 }
459+
460+ private fun permissionStatus (
461+ payloadJson : String? ,
462+ key : String ,
463+ ): String =
464+ parsePayload(payloadJson)
465+ .getValue(" permissions" )
466+ .jsonObject
467+ .getValue(key)
468+ .jsonObject
469+ .getValue(" status" )
470+ .jsonPrimitive
471+ .content
432472}
433473
434474private class FakeDeviceAppSource (
0 commit comments