java.lang.SecurityException with SubscriptionManager – But I declared the permissions!..

java.lang.SecurityException with SubscriptionManager – But I declared the permissions!..

Oh my! This is one of those times with Android where you just slap your forehead, like what!

This is the official documentation for using SubscriptionManager.addOnSubscriptionChangedListener

Register for changes to the list of active SubscriptionInfo records or to the individual records themselves. When a change occurs the onSubscriptionsChanged method of the listener will be invoked immediately if there has been a notification. The onSubscriptionChanged method will also be triggered once initially when calling this function.

https://developer.android.com/reference/android/telephony/SubscriptionManager#addOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener)

It does not claim that any Android permissions are needed. However if you are reading this, you have probably seen the same stacktrace as me in your Crashlytics or PlayStore reports.

java.lang.RuntimeException: 
  at android.app.ActivityThread.handleBindApplication (ActivityThread.java:6654)
  at android.app.ActivityThread.-wrap2 (Unknown Source)
  at android.app.ActivityThread$H.handleMessage (ActivityThread.java:2084)
  at android.os.Handler.dispatchMessage (Handler.java:109)
  at android.os.Looper.loop (Looper.java:166)
  at android.app.ActivityThread.main (ActivityThread.java:7555)
  at java.lang.reflect.Method.invoke (Method.java)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:469)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:963)
Caused by: java.lang.SecurityException: 
  at android.os.Parcel.readException (Parcel.java:2016)
  at android.os.Parcel.readException (Parcel.java:1962)
  at com.android.internal.telephony.ITelephonyRegistry$Stub$Proxy.addOnSubscriptionsChangedListener (ITelephonyRegistry.java:491)
  at android.telephony.SubscriptionManager.addOnSubscriptionsChangedListener (SubscriptionManager.java:518)

Hmmm what is going on! Well it turns out the callback is described as being used to:

A listener class for monitoring changes to SubscriptionInfo records.

But it doesn’t actually give you the SubscriptionInfo object. However another method does: getActiveSubscriptionInfo

https://developer.android.com/reference/android/telephony/SubscriptionManager#getActiveSubscriptionInfo(int)

And look this method requires an Android permission:

Get the active SubscriptionInfo with the input subId.

Requires Permission: READ_PHONE_STATE or that the calling app has carrier privileges (see TelephonyManager#hasCarrierPrivileges).
Requires Manifest.permission.READ_PHONE_STATE

https://developer.android.com/reference/android/telephony/SubscriptionManager#getActiveSubscriptionInfo(int)

So the first conclusion I jumped to was Huawei (and vendors it sells its AOSP version too) have slightly tweaked their implementation, and this listener comes under requiring the permission.

I’ve seen it happen before, it is totally possible, but come on in 2020? no way. So with a little more digging, and reading of the docs from the actual listener, I found it!

Permissions android.Manifest.permission.READ_PHONE_STATE is required for #onSubscriptionsChanged to be invoked.

https://developer.android.com/reference/android/telephony/SubscriptionManager.OnSubscriptionsChangedListener

You don’t need the Security Permission to add an on changed listener, but you do need the permission for your listener to be invoked. That’s the rub.

We came across this because we set the listener when the app starts but don’t ask for the permission until the feature is required (best UX practice). The practical solutions we had to choose from, was to either check for the permission when the app starts, or move the listener to only start listening when the user has given permission.

Happy Androiding! And always remember to RTFM.