Detecting Sim Change on Android

Detecting Sim Change on Android

Ever wondered how online payment apps like Google Pay, Phone Pe and numerous other UPI apps detect when you change the SIM on your device?

Want to implement a similar feature for your android app to prevent fraud? Do you want to ensure the SIM used for logging in to your app is always present on the device?

In this article, let's look at different ways this can be done.

There are three primary ways to detect a SIM change in your Android app -

  1. Broadcast Receivers
  2. Subscriber ID - Recommended by Google
  3. Combine Both 😉

Note that all these methods need the permission READ_PHONE_STATE without which you will not be able to track the SIMs on the device.

Broadcast Receivers

The simplest way to detect SIM change is through broadcast receivers. The intent android.intent.action.SIM_STATE_CHANGED would be broadcast when any change is made to the SIM state or the SIMs themselves. You can use these intents to track SIM removal and insertion.

These intents would be broadcast multiple times during the process of removing and inserting a SIM and (on a few devices) when the device goes to and out of airplane/flight mode.

On removing a SIM, an intent with the state ABSENT is fired. On inserting a SIM, the states - READY and LOADED are fired one after the other.

In case the user switches off his device and then changes the SIM, these intents would be received by your app the first time the user opens your app after starting his device.

Please note that your app would not be receiving these intents if your app has been force stopped by the user. For this reason, using broadcasts is not a very reliable method to detect SIM change.

The code below would help you implement SIM change detection for your Android app using Broadcast Receivers.

Create a new file called SimChangeReceiver that extends BroadcastReceiver and override the method onReceive().

Now, in your manifest, register the receiver created and add an intent filter to the same. Make sure you also add the permission READ_PHONE_STATE.  

Now, run the code and start up the app. In the Logcat, filter for SimChangeReceivers. Now remove your SIM, you can see that an intent has been fired for SIM ABSENT. When you insert a SIM again, you can see that 2 intents READY and LOADED are broadcast one after the other.

SimChangeReceivers - SIM State Change Detected ABSENT
SimChangeReceivers - SIM State Change Detected READY
SimChangeReceivers - SIM State Change Detected LOADED

Now, turn off your phone and try changing the SIM. When you reopen the app, you can see that these intents have been fired.

Force stop the app and try the same, you'll then notice that the app has not received these broadcast intents. For this reason, we (and Google apparently 😆) recommend that you use Subscriber IDs.  

Using Subscriber ID to Detect Sim Change

The Subscriber ID API provides a method of uniquely identifying installed SIMs on a specific device.

A specific SIM would always have the same subscription ID on the same device, but it could (will) have a different subscription ID on a different device. While the subscription ID is not globally unique to the SIM, it is unique within the device. For our need of logging out the user on SIM change, this should be sufficient.  

When the user logs in to your app, store the current subscriber ID information in your app. Note that if the device has multiple SIMs, we will need to store info about them all because we cannot uniquely identify which SIM the user has used while logging in.

You may use SharedPreferences or a local database or even a backend server to store the current subscriber ID (or IDs in case of multi-SIM devices) information. For the example below, we have made use of Shared Preferences.

According to the requirements of your project, you should periodically check if the current subscriber ID/IDs are the same as the ones stored in your sharedPrefs, local DB or backend and accordingly either logout or ask the user to reinsert the same SIM. For our example, we'll show toasts indicating the current state.

We can call this function from anywhere we want based on the product requirements - base activity, app lifecycle listeners, on each startup, before doing a transaction, before allowing paywalled access and so on.

Ensure that the user has granted you READ_PHONE_STATE permission before you access SubscriptionManager. Without this, you would either get a null value or an empty list when you access the subscription information.

Check out this GitHub repository for a sample implementation of the above method.  

TIP: Make use of libraries like Permissions Dispatcher to make your job simpler when dealing with permissions.

Broadcasts + Subscriber ID

You can also combine both of the above methods if you would like to receive live broadcasts for removing and inserting SIM cards, and on app startup use subscriber ID to handle force stopped and any other cases that might be missed by the broadcast receivers based on the requirements of your project.

For some, using both might be overkill, so just stick with Subscriber IDs and you can make it work. In our GitHub project over here, we have used both the above methods (separately) and implemented SIM change detection. If you have any questions, feedback, or improvements, kindly raise an issue/PR on GitHub.

I hope you enjoyed this article and if it helped you, consider supporting me on Ko-Fi.