Handle Permissions And Play Policy
Permissions are product design, not just manifest lines.
Runtime Permission Flow
Use this order:
flowchart TD
feature["User starts feature"] --> needed{"Permission needed?"}
needed -->|No| run["Run feature"]
needed -->|Yes| granted{"Already granted?"}
granted -->|Yes| run
granted -->|No| rationale{"Show rationale?"}
rationale -->|Yes| explain["Explain data and benefit"]
rationale -->|No| request["Request permission"]
explain --> request
request --> decision{"User grants?"}
decision -->|Yes| run
decision -->|No| fallback["Graceful fallback"]
Ask in context. Explain what data is accessed and what feature breaks without it. Let the user continue when they deny.
SMS Permission For Money Management
Google Play treats SMS and Call Log permissions as high-risk or sensitive. SMS-based money management appears in the policy as an exception category, but it is subject to Play review and permission declaration.
For an SMS expense tracker, design this before code:
| Requirement | Practical Evidence |
|---|---|
| Core functionality | Store listing and onboarding say SMS import is a core budget-tracking feature |
| Permission scope | Manifest requests only the specific SMS permissions needed |
| Data minimization | Parser ignores or discards unrelated SMS data |
| User control | User can trigger scans and delete imported data |
| Denied mode | Manual entry or CSV import works without SMS access |
| Privacy policy | States what SMS data is read, stored, shared, and deleted |
| Play declaration | Matches implemented behavior and demo video |
Permissions To Avoid By Default
Do not add these casually:
READ_SMSRECEIVE_SMS- Call Log permissions
- Accessibility service permissions
- background location
- broad file access
- notification listener access
Each one changes the release and trust posture.
Compose Onboarding Pattern
Use an educational screen before the system dialog:
@Composable
fun SmsPermissionEducation(
onContinue: () -> Unit,
onSkip: () -> Unit,
) {
Column {
Text("Import M-Pesa messages")
Text("The app reads transaction SMS locally to build your expense list. Manual entry works if you skip.")
Button(onClick = onContinue) { Text("Allow SMS import") }
TextButton(onClick = onSkip) { Text("Use manual entry") }
}
}
Test The Denial Path
Use adb to reset permission flags when testing repeated denial:
adb shell pm clear-permission-flags com.example.app android.permission.READ_SMS user-set user-fixed
adb shell pm revoke com.example.app android.permission.READ_SMS
Then verify:
- first ask shows rationale or education
- denial leaves the app usable
- second denial does not create nag loops
- permanent denial has a stable manual path
- settings link, if present, is not coercive
Warning: Do not use NotificationListener or AccessibilityService as an SMS workaround for a Play-distributed finance app. That is a policy and trust risk, not a clever architecture choice.