Skip to content

Commit c20171d

Browse files
NianJiuZststeipete
andauthored
fix: require Android contact and calendar write permissions (#99204)
* fix: require Android contact and calendar write permissions * test(android): cover partial permission grants --------- Co-authored-by: NianJiuZst <180004567+users.noreply.github.com> Co-authored-by: Peter Steinberger <[email protected]>
1 parent 4df175b commit c20171d

2 files changed

Lines changed: 48 additions & 4 deletions

File tree

apps/android/app/src/main/java/ai/openclaw/app/node/DeviceHandler.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,14 +426,18 @@ class DeviceHandler private constructor(
426426
put(
427427
"contacts",
428428
permissionStateJson(
429-
granted = hasPermission(Manifest.permission.READ_CONTACTS),
429+
granted =
430+
hasPermission(Manifest.permission.READ_CONTACTS) &&
431+
hasPermission(Manifest.permission.WRITE_CONTACTS),
430432
promptableWhenDenied = true,
431433
),
432434
)
433435
put(
434436
"calendar",
435437
permissionStateJson(
436-
granted = hasPermission(Manifest.permission.READ_CALENDAR),
438+
granted =
439+
hasPermission(Manifest.permission.READ_CALENDAR) &&
440+
hasPermission(Manifest.permission.WRITE_CALENDAR),
437441
promptableWhenDenied = true,
438442
),
439443
)

apps/android/app/src/test/java/ai/openclaw/app/node/DeviceHandlerTest.kt

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package ai.openclaw.app.node
22

3-
import android.content.Context
3+
import android.Manifest
4+
import android.app.Application
45
import android.content.pm.ApplicationInfo
56
import kotlinx.serialization.json.Json
67
import kotlinx.serialization.json.JsonObject
@@ -15,6 +16,7 @@ import org.junit.Test
1516
import org.junit.runner.RunWith
1617
import org.robolectric.RobolectricTestRunner
1718
import org.robolectric.RuntimeEnvironment
19+
import org.robolectric.Shadows.shadowOf
1820

1921
@RunWith(RobolectricTestRunner::class)
2022
class 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

434474
private class FakeDeviceAppSource(

0 commit comments

Comments
 (0)