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:

RequirementPractical Evidence
Core functionalityStore listing and onboarding say SMS import is a core budget-tracking feature
Permission scopeManifest requests only the specific SMS permissions needed
Data minimizationParser ignores or discards unrelated SMS data
User controlUser can trigger scans and delete imported data
Denied modeManual entry or CSV import works without SMS access
Privacy policyStates what SMS data is read, stored, shared, and deleted
Play declarationMatches implemented behavior and demo video

Permissions To Avoid By Default

Do not add these casually:

  • READ_SMS
  • RECEIVE_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.