如何在onCallAdded回调中获取当前设备的电话号码和SIM卡槽信息?

3

背景

我正在开发一个应用程序,实现InCallService,以便处理电话通话。

问题

在具有多个SIM卡的设备上,我需要显示使用哪个SIM卡和相关电话号码(当前设备的)的信息。

问题是,我找不到获取此信息的位置。

我的发现

  1. 假设我到达诸如onCallAdded这样的函数,我会得到一个Call类的实例,因此我需要将从那里得到的内容与sim卡槽和电话号码相关联。

  2. 使用call.getDetails().getHandle(),我可以获得仅包含其他人拨打的电话号码的Uri(拨打当前用户的人或当前用户拨打的人)。

  3. 我可以遍历所有SIM卡,但找不到可用于映射它们与当前呼叫之间的内容:

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. 有一段旧代码,它使用了call.getDetails().getAccountHandle().getId()SubscriptionManager.from(context)getActiveSubscriptionInfoList().getActiveSubscriptionInfoList(),但已经不再起作用。

  2. 我认为我可以使用telephonyManager.getSubscriptionId(callDetails.getAccountHandle()),但需要API 30,这是相当新的...

问题

给定从此回调(和可能其他回调)获得的电话呼叫,如何获取其关联的SIM插槽和电话号码?

我希望尽可能了解在API 23之前的Android版本中如何完成,因为InCallService是从API 23开始的... 在API 30之前应该也是可能的,对吗?


1
TelecomManager.getPhoneAccount() 运行正常吗? - vlp
@vlp,它似乎真的很好用!可惜我无法在旧设备上测试(没有任何设备),模拟器也不能有多个SIM卡,但看起来很有前途。我使用了这个代码来获取电话号码:telecomManager.getPhoneAccount(call.details.accountHandle)?.address?.schemeSpecificPart,然后我从subscriptionManager.activeSubscriptionInfoList列表中的number字段中找到了它。为什么你没有在答案中写出来呢?你是怎么发现它的?我试着搜索了几个小时... - android developer
说实话 - 我在官方文档中读到过,但从未尝试过。我很高兴它对你有用。祝你的项目好运! - vlp
@vlp 但是你怎么想到要检查TelecomManager的? - android developer
我偶然在那里找到了它。非常感谢您在答案中提供了最终代码。祝您的项目好运! - vlp
显示剩余2条评论
1个回答

3
使用call.getDetails().getAccountHandle()获取PhoneAccountHandle
然后使用TelecomManager.getPhoneAccount()获取PhoneAccount实例。

应用程序针对API级别31+的需要权限Manifest.permission.READ_PHONE_NUMBERS

免责声明:我不是Android专家,请验证我的想法。

编辑:因此,在API 30之前和API 30及之后的解决方案可以如下所示:
@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
    }

你认为我们需要哪个函数来使用 READ_PHONE_NUMBERS 权限?即使我使用相对较新的 API telephonyManager.getSubscriptionId,并在 subscriptionManager.activeSubscriptionInfoList 列表中查找其 subscriptionId,是否仍然需要该权限? - android developer
1
文档说明:TelecomManager.getPhoneAccount() 在 API 31+ 上需要 READ_PHONE_NUMBERSTelephonyManager.getSubscriptionId 需要 READ_PHONE_STATESubscriptionManager.getActiveSubscriptionInfoList() 需要 READ_PHONE_STATE 或运营商特权。另外请注意,您计划使用的 SubscriptionInfo.getNumber() 需要一些权限(在 API 30 以下需要 READ_PHONE_STATE,在 API 30+ 上需要 READ_PRIVILEGED_PHONE_STATE/READ_PHONE_NUMBERS/READ_SMS 中的一个或运营商特权)。 - vlp
1
因此,对于telephonyManager.getSubscriptionId(callDetails.getAccountHandle())后跟在SubscriptionManager.getActiveSubscriptionInfoList()中查找的方法,理论上您需要READ_PHONE_STATETelephonyManager.getSubscriptionId+SubscriptionManager.getActiveSubscriptionInfoList()),然后需要相应的权限来读取SubsciptionInfo中的信息(READ_PRIVILEGED_PHONE_STATE/READ_PHONE_NUMBERS/READ_SMS以获取电话号码)。假设API级别为30+。请交叉验证我的想法。 - vlp
我已经编辑了你的答案,包括两种情况的完整代码。至于权限,谢谢。当目标 API 为 30 时,两种情况都需要 READ_PHONE_STATE。我认为当目标 API 为 31 时,只有 getPhoneAccount 会有问题,但是它仅用于 API 30 之前的情况,所以应该没问题。我现在尝试了一下,它只警告了 READ_PHONE_STATE 权限,不知道这段代码是否还存在其他可能的问题。在此报告:https://issuetracker.google.com/issues/205726880。如果您认为还有其他重要的事情,请告诉我。 - android developer
Google 给了我如何做的答案,但不确定他们写的是什么:https://issuetracker.google.com/issues/204791554#comment5 - android developer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接