Search code examples
androidphone-callsim-cardincallservice

How to get the phone number and SIM card slot for the current device, on the callback of onCallAdded?


Background

I'm working on an app that implements InCallService, so it can handle phone calls

The problem

On devices with multi-SIM, I need to show information of which SIM and associated phone number is used (of the current device).

Thing is, I can't find where to get this information.

What I've found

  1. Given that I reach a function like onCallAdded, I get an instance of Call class, so I need to associate something I get from there, to a sim slot and phone number.

  2. Using call.getDetails().getHandle() , I can get a Uri consisting only the phone number of the other person that called (who called the current user, or who the current user has called).

  3. I can iterate over all SIM cards, but can't find what I can use to map between them and the current call:

final TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
final SubscriptionManager subscriptionManager = SubscriptionManager.from(context);
for (final SubscriptionInfo subscriptionInfo : subscriptionManager.getActiveSubscriptionInfoList()) {
    final TelephonyManager subscriptionId = telephonyManager.createForSubscriptionId(subscriptionInfo.getSubscriptionId());
}
  1. There was an old code that doesn't work anymore that uses call.getDetails().getAccountHandle().getId() and SubscriptionManager.from(context)getActiveSubscriptionInfoList().getActiveSubscriptionInfoList() .

  2. I think I can use telephonyManager.getSubscriptionId(callDetails.getAccountHandle()) , but it requires API 30, which is quite new...

The questions

Given a phone call that I get from this callback (and probably others), how can I get the associated SIM slot and phone number of it?

I prefer to know how to do it for as wide range of Android versions as possible, because InCallService is from API 23... It should be possible before API 30, right?


Solution

  • Use call.getDetails().getAccountHandle() to get PhoneAccountHandle.

    Then use TelecomManager.getPhoneAccount() to get PhoneAccount instance.


    Permission Manifest.permission.READ_PHONE_NUMBERS is needed for applications targeting API level 31+.


    Disclaimer: I am no Android expert, so please do validate my thoughts.


    EDIT: So solution for both before API 30 and from API 30 could be as such:

    @RequiresApi(Build.VERSION_CODES.M)
    fun handleCall(context: Context, call: Call) {
        var foundAndSetSimDetails = false
        val callDetailsAccountHandle = callDetails.accountHandle
        val subscriptionManager = context
            .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager
        val telephonyManager =
            context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
        val telecomManager =
            context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
        val hasReadPhoneStatePermission =
            ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) == android.content.pm.PackageManager.PERMISSION_GRANTED
        val phoneAccount = telecomManager.getPhoneAccount(callDetailsAccountHandle)
        //TODO when targeting API 31, we might need to check for a new permission here, of READ_PHONE_NUMBERS
        //find SIM by phone account
        if (!foundAndSetSimDetails && phoneAccount != null && hasReadPhoneStatePermission) {
            val callCapablePhoneAccounts = telecomManager.callCapablePhoneAccounts
            run {
                callCapablePhoneAccounts?.forEachIndexed { index, phoneAccountHandle ->
                    if (phoneAccountHandle != callDetailsAccountHandle)
                        return@forEachIndexed
                    if (!phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION))
                        return@run
                    //found the sim card index
                    simName = phoneAccount.label?.toString().orEmpty()
                    simIndex = index + 1
                    foundAndSetSimDetails = true
                    return@run
                }
            }
        }
        //find SIM by subscription ID
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && hasReadPhoneStatePermission) {
            try {
                val callSubscriptionId: Int =
                    telephonyManager.getSubscriptionId(callDetailsAccountHandle!!)
                for (subscriptionInfo: SubscriptionInfo in subscriptionManager.activeSubscriptionInfoList) {
                    val activeSubscriptionId: Int = subscriptionInfo.subscriptionId
                    if (activeSubscriptionId == callSubscriptionId) {
                        setSimDetails(telephonyManager, subscriptionInfo)
                        foundAndSetSimDetails = true
                        break
                    }
                }
            } catch (e: Throwable) {
                e.printStackTrace()
            }
        }
        //find SIM by phone number
        if (!foundAndSetSimDetails && hasReadPhoneStatePermission) {
            try {
                val simPhoneNumber: String? = phoneAccount?.address?.schemeSpecificPart
                if (!simPhoneNumber.isNullOrBlank()) {
                    for (subscriptionInfo in subscriptionManager.activeSubscriptionInfoList) {
                        if (simPhoneNumber == subscriptionInfo.number) {
                            setSimDetails(telephonyManager, subscriptionInfo)
                            foundAndSetSimDetails = true
                            break
                        }
                    }
                    if (!foundAndSetSimDetails)
                }
            } catch (e: Throwable) {
                e.printStackTrace()
            }
        }
    
        private fun setSimDetails(telephonyManager: TelephonyManager, subscriptionInfo: SubscriptionInfo) {
            var foundSimName: String? = null
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                val telephonyManagerForSubscriptionId =
                    telephonyManager.createForSubscriptionId(subscriptionInfo.subscriptionId)
                foundSimName = telephonyManagerForSubscriptionId.simOperatorName
            }
            if (foundSimName.isNullOrBlank())
                foundSimName = subscriptionInfo.carrierName?.toString()
            simName = if (foundSimName.isNullOrBlank())
                ""
            else foundSimName
            simIndex = subscriptionInfo.simSlotIndex + 1
        }