Skip to Content

Permissions

Luno declares the minimum set that a working gateway requires, and nothing dangerous is declared before the milestone that uses it.

What is declared

PermissionFlavorWhy
SEND_SMSbothOutbound messages via SmsManager
RECEIVE_SMSfull onlyInbound capture via the SMS_RECEIVED broadcast
READ_PHONE_STATEbothSIM identity and per-subscription signal
POST_NOTIFICATIONSbothThe ongoing foreground-service notification (runtime from API 33)
RECEIVE_BOOT_COMPLETEDbothRestart the service after a reboot
FOREGROUND_SERVICE + FOREGROUND_SERVICE_SPECIAL_USEbothThe 24/7 agent process
REQUEST_IGNORE_BATTERY_OPTIMIZATIONSbothPrompt for the Doze exemption
INTERNET / ACCESS_NETWORK_STATEbothBackend connection and network monitoring

Luno does not declare READ_SMS, does not request the default-SMS-handler role, and uses no accessibility or notification-listener APIs. This matters for both privacy and Play Protect.

Request order

Phone state first, then SMS. Asking for SMS access cold — before the app has shown what it is and what it does — is the fastest route to a permanent denial, and a permanently denied SMS permission cannot be re-prompted from within the app.

The battery-optimisation exemption is requested after permissions, because it opens a system settings screen rather than a dialog and is easier to explain once the rest is in place.

Revocation happens, months later

Two Android behaviours mean a granted permission is not permanently granted:

  • Permission auto-reset (Android 11+) revokes runtime permissions for apps the user has not opened in a few months. A gateway is precisely the kind of app nobody opens.
  • App hibernation additionally forces the app to stop.

The consequence is a SecurityException thrown from a send that has worked for months. Every telephony call is wrapped in taxonomy-aware error handling, so this surfaces as an AUTH/TERMINAL error with a re-grant prompt rather than a crash, and the app requests exemption from auto-reset where the OS allows it.

A node whose SEND_SMS has been silently revoked will still accept commands and still report — it just fails every send with a terminal error. Watch for this pattern in your dashboard: healthy heartbeats plus a rising terminal failure rate.

Blocked is a hint, never a verdict

On Android 15, a sideloaded app’s SMS permission toggle is greyed out and the runtime request is auto-denied with no dialog. Native reports PermissionStatus.BLOCKED and the UI explains that Android refused the last prompt.

That status is treated as advisory. Allowing restricted settings makes the permission grantable again without changing anything the app can observeshouldShowRequestPermissionRationale still returns false. So a cached blocked status must never suppress the Grant action, or the user is stranded with no way to trigger the now-working prompt.

The implementation follows from that: MainActivity.request always calls requestPermissions, and the UI escalates to the recovery sheet only when a live attempt comes back blocked.

See Play Protect for how to lift the restriction.