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

2.6k Views Asked by At

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?

1

There are 1 best solutions below

5
vlp On BEST ANSWER

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
    }