DEV Community

Cover image for Android 13 Killed My SMS Permissions — Here's How I Adapted
DEVANSHU PATIL
DEVANSHU PATIL

Posted on

Android 13 Killed My SMS Permissions — Here's How I Adapted

Android 13 Killed My SMS Permissions — Here's How I Adapted

On Android, there are few permissions as heavily guarded as SMS access.

Because malicious applications historically abused SMS access to intercept two-factor authentication codes and secretly subscribe users to premium carrier services, Google Play introduced strict sensitive permissions policies. If your app requests RECEIVE_SMS or READ_SMS, it faces automated scrutiny, manual policy review, and immediate rejection if your declared use case does not fit narrow exemptions.

Furthermore, starting in Android 13 (API level 33), Google overhauled BroadcastReceiver security, requiring explicit receiver export flags (RECEIVER_EXPORTED vs RECEIVER_NOT_EXPORTED) and introducing granular runtime permissions for notifications.

When building FinLedger, engineering the SMS ingestion pipeline required navigating both platform security constraints and Google Play compliance without compromising user trust.

Here is how FinLedger solved SMS permissions on modern Android devices through a consent-first architecture and defensive security boundaries.

The Google Play Permission Barrier

Google Play enforces a strict principle for sensitive permissions: core functionality only.

If an application requests SMS permissions simply as a convenience feature, Google's policy team will reject the update. To qualify for financial SMS automation:

  1. The app must clearly explain why SMS parsing is a primary user journey.
  2. The app must document that parsed data stays strictly on the user's device and is never transmitted to cloud servers for advertising, analytics, or behavioral profiling.
  3. The app must provide a prominent in-app disclosure before the Android system permission dialogue is triggered.
flowchart TD
    subgraph HostilePattern["The Hostile Permission Flow (Rejected by Users & Policy)"]
        ColdLaunch["App Launched for the First Time"] --> SystemPopup["Instant System Dialog:
        'Allow FinLedger to view and send SMS?'"]
        SystemPopup --> UserRejection["User Declines / Reports App:
        Zero Context Provided. Immediate Trust Deficit."]
    end

    subgraph ConsentFirstPattern["FinLedger Consent-First Architecture"]
        ColdLaunch2["App Launched for the First Time"] --> CleanBoarding["Clean Onboarding:
        Offline-First Value Pitch"]
        CleanBoarding --> ExplainerPage["SmsPage Explainer:
        Explains on-device parsing, privacy guarantees,
        and opt-in nature."]
        ExplainerPage --> UserChoice{User Chooses}
        UserChoice -->|Declines| ManualMode["Graceful Degradation:
        App functions 100% offline via manual entry.
        Never prompts again."]
        UserChoice -->|Accepts| RuntimePermission["Android System Permission Dialog"]
    end

The Consent-First Architecture (ConsentFirstSmsAutomationTest.kt)

One of the worst patterns in mobile development is bombarding users with system permission prompts the second an app launches. When a user sees a finance app demanding access to their personal messages before they have even seen the home screen, they uninstall it.

In FinLedger, SMS automation is strictly opt-in and consent-first:

  • During onboarding (SmsPage.kt), the app introduces SMS detection as an optional accelerator for busy users.
  • The screen explicitly states our cryptographic boundaries: messages are parsed locally using deterministic rules; raw SMS texts are never transmitted off the device; and OTPs or personal conversations are discarded instantly.
  • If the user skips or declines the feature, the app never asks again. FinLedger operates with 100% feature completeness as a manual ledger.
  • If the user decides later to enable SMS detection, they can toggle it in Settings, triggering the prominent disclosure only when they explicitly seek it.

This workflow is verified by automated integration tests (ConsentFirstSmsAutomationTest.kt) ensuring that the app never attempts to query the Android SMS content provider without verified user consent flags stored in encrypted preferences.

Surviving Android 13+ BroadcastReceiver Rules

Starting in Android 13 (API 33), Android requires developers to explicitly specify whether dynamically registered broadcast receivers are exported to other apps or not:

  • RECEIVER_NOT_EXPORTED: The receiver can only receive broadcasts sent by the system or apps sharing the same UID.
  • RECEIVER_EXPORTED: The receiver can receive broadcasts from any external app on the device.

For receiving SMS messages, the situation is nuanced:

  • The system broadcast android.provider.Telephony.SMS_RECEIVED originates from the operating system's telephony subsystem.
  • However, if an app declares a receiver statically in AndroidManifest.xml without proper permission protections, malicious applications on the device could construct fake SMS intents and broadcast them directly to the receiver, injecting fraudulent transactions into the user's ledger!
flowchart LR
    ExternalAttacker["Malicious Third-Party App"] -.->|Attempts Fake SMS Intent| ManifestFilter["AndroidManifest.xml
    Signature & System-Only Protection:
    android.permission.BROADCAST_SMS"]
    ManifestFilter -->|Blocked & Dropped| Blocked["Attacker Rejected"]

    AndroidTelephony["Android Telephony Subsystem"] -->|Genuine SMS Broadcast| ManifestFilter
    ManifestFilter -->|Allowed| SmsReceiver["FinLedger SmsReceiver"]

FinLedger safeguards incoming SMS broadcasts through multi-layered defenses:

1. System Permission Binding in Manifest

In AndroidManifest.xml, the receiver is bound with android:permission="android.permission.BROADCAST_SMS".

Because ordinary third-party applications cannot obtain BROADCAST_SMS (it is restricted to the platform signature and system processes), rogue applications cannot trigger FinLedger's transaction parser with synthetic broadcast intents.

2. Runtime Sender ID Verification

Even after receiving an intent, SmsReceiver.kt inspects the sender address. It checks against known TRAI header prefixes (e.g., AD-HDFCBK) and rejects messages originating from arbitrary 10-digit phone numbers.

Graceful Degradation: When Permissions Are Revoked

Android users can revoke permissions at any time through the system settings. A resilient Android app must handle permission revocation without crashing or displaying orphaned UI states.

FinLedger implements graceful degradation across two dimensions:

  1. Permission Check Before Work Execution: Before SmsInboxScanner.kt initiates a background scan, it validates that READ_SMS is still granted. If revoked, it cancels the job cleanly and updates local state without throwing SecurityException.
  2. Manual Clipboard Paste Fallback (ManualSmsFetchDialog.kt): For users who prefer not to grant system-wide SMS permissions, FinLedger provides a manual clipboard dialog. Users can copy a bank SMS notification from their messages app and paste it directly into FinLedger's dialog. The same on-device deterministic parser processes the text and stages the transaction in under a millisecond, granting the benefits of automated parsing with zero system permissions required.

Key Takeaway

Permissions are not an entitlement; they are a contract of trust between your application and your user.

By combining prominent consent disclosures, strict manifest receiver protections, graceful permission degradation, and zero-telemetry guarantees, you can satisfy Google Play's rigorous compliance audits while delivering a secure, privacy-first user experience.


FinLedger is a privacy-first, offline-first personal finance application for Android that models bank accounts, credit cards, and cash without double-counting.

Explore FinLedger on Google Play: https://play.google.com/store/apps/details?id=com.devanshufinance.app

Top comments (0)