fix: require Android contact and calendar write permissions in onboarding (#99158)

* fix: require Android contact and calendar write permissions in onboarding

* refactor(android): share required onboarding permission groups

---------

Co-authored-by: NianJiuZst <180004567+users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
NianJiu
2026-07-03 02:36:56 +08:00
committed by GitHub
parent 97ad0b7c21
commit db7bfc618d
2 changed files with 51 additions and 6 deletions

View File

@@ -1370,6 +1370,9 @@ internal fun canFinishOnboarding(
-> true
}
private val requiredContactPermissions = listOf(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)
private val requiredCalendarPermissions = listOf(Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR)
/** Builds permission rows and applies granted feature toggles after onboarding. */
@Composable
private fun rememberPermissionState(
@@ -1383,8 +1386,12 @@ private fun rememberPermissionState(
}
val photosPermissions = photoReadPermissionsForRequest()
var photosGranted by rememberSaveable { mutableStateOf(hasPhotoReadPermission(context)) }
var contactsGranted by rememberSaveable { mutableStateOf(hasPermission(context, Manifest.permission.READ_CONTACTS)) }
var calendarGranted by rememberSaveable { mutableStateOf(hasPermission(context, Manifest.permission.READ_CALENDAR)) }
var contactsGranted by rememberSaveable {
mutableStateOf(requiredContactPermissions.all { permission -> hasPermission(context, permission) })
}
var calendarGranted by rememberSaveable {
mutableStateOf(requiredCalendarPermissions.all { permission -> hasPermission(context, permission) })
}
var notificationsGranted by rememberSaveable {
mutableStateOf(Build.VERSION.SDK_INT < 33 || hasPermission(context, Manifest.permission.POST_NOTIFICATIONS))
}
@@ -1430,8 +1437,18 @@ private fun rememberPermissionState(
permissions[Manifest.permission.ACCESS_COARSE_LOCATION] == true ||
locationGranted
photosGranted = hasPhotoReadPermission(context) || photosPermissions.any { permissions[it] == true }
contactsGranted = permissions[Manifest.permission.READ_CONTACTS] ?: contactsGranted
calendarGranted = permissions[Manifest.permission.READ_CALENDAR] ?: calendarGranted
contactsGranted =
mergedRequiredPermissionGrantState(
permissions = permissions,
requiredPermissions = requiredContactPermissions,
currentlyGranted = { permission -> hasPermission(context, permission) },
)
calendarGranted =
mergedRequiredPermissionGrantState(
permissions = permissions,
requiredPermissions = requiredCalendarPermissions,
currentlyGranted = { permission -> hasPermission(context, permission) },
)
notificationsGranted =
if (Build.VERSION.SDK_INT >= 33) {
permissions[Manifest.permission.POST_NOTIFICATIONS] ?: notificationsGranted
@@ -1471,10 +1488,10 @@ private fun rememberPermissionState(
null
},
PermissionRowModel("Contacts", "Read contacts securely", Icons.Default.Person, contactsGranted) {
request(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)
request(*requiredContactPermissions.toTypedArray())
},
PermissionRowModel("Calendar", "Read events and schedules", Icons.Default.CalendarMonth, calendarGranted) {
request(Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR)
request(*requiredCalendarPermissions.toTypedArray())
},
PermissionRowModel("Notifications", "Send important alerts", Icons.Default.Notifications, notificationsGranted) {
if (Build.VERSION.SDK_INT >= 33) request(Manifest.permission.POST_NOTIFICATIONS)

View File

@@ -88,6 +88,34 @@ class OnboardingFlowLogicTest {
assertFalse(deniedRead)
}
@Test
fun contactAndCalendarPermissionGroupsRequireBothGrants() {
val permissionGroups =
listOf(
listOf(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS),
listOf(Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR),
)
for (requiredPermissions in permissionGroups) {
val readPermission = requiredPermissions.first()
val writePermission = requiredPermissions.last()
assertFalse(
mergedRequiredPermissionGrantState(
permissions = mapOf(readPermission to true),
requiredPermissions = requiredPermissions,
currentlyGranted = { false },
),
)
assertTrue(
mergedRequiredPermissionGrantState(
permissions = mapOf(writePermission to true),
requiredPermissions = requiredPermissions,
currentlyGranted = { permission -> permission == readPermission },
),
)
}
}
@Test
fun nearbyGatewayFoundStateIsConnectable() {
assertEquals(