安卓设备电话通话功能

36

如何判断给定的设备是否具有打电话的功能?

例如,我的Galaxy平板电脑不能发起呼叫,它不是一部电话。在调用isIntentAvailable(context, Intent.ACTION_DIAL)之前,我想检测到这一点。我尝试检查isIntentAvailable,但似乎不是正确的方法。

7个回答

50
if (((TelephonyManager)getContext().getSystemService(Context.TELEPHONY_SERVICE)).getPhoneType()
    == TelephonyManager.PHONE_TYPE_NONE)
{
    // no phone
}

编辑 我很惊讶它返回PHONE_TYPE_CDMA,这里还有另一种可能:

if (((TelephonyManager)getContext().getSystemService(Context.TELEPHONY_SERVICE)).getLine1Number()
    == null)
{
    // no phone
}

这将需要READ_PHONE_STATE权限。


谢谢,我在测试不同的设备时,第一种解决方案适用于没有内置电话的Xoom,但显然在欧洲可以带有电话功能的Galaxy-Tab,因此需要CDMA响应。 - steve y
getLine1Number()不是答案。在Nexus 10模拟器上尝试一下,你会发现它返回的不是null,而应该是null。Nexus 10无法打电话!此外,Nexus 10返回的是PHONE_TYPE_GSM。这太荒谬了。 - AutoM8R
@AutoM8R - 在 Nexus 10 上,getLine1Number() 方法返回一个非空字符串吗?这个字符串里面是什么?(同样的,当你在 Nexus 10 模拟器上尝试时,这个字符串里面又是什么?) - Ted Hopp
@AutoM8R - 奇怪。看起来目前还没有适用于所有平台的万无一失的技术。如果你打电话给那个号码,我想知道会有谁回答? :) - Ted Hopp
仅供参考,即使在Nexus4上有SIM卡并且电信功能可用,getLine1Number()也会返回null。因此,您不应该依赖该信息。 - Mathias Seguy Android2ee
显示剩余6条评论

31

设备不一定需要Context.TELEPHONY_SERVICE来进行电话通话。考虑一下如果您安装了Skype会发生什么:

  • 在原生的拨号/电话应用程序中输入电话号码并按“呼叫”按钮。
  • 一个弹出窗口出现,标题为“使用以下方式完成操作”,并提供“拨号器”或“Skype”应用程序(也可能列出其他应用程序)。

因此,我相信即使没有手机功能(根据Context.TELEPHONY_SERVICE),Skype也可以在仅限WiFi的设备上工作。

我认为您最初的想法是正确的,但您需要检查哪些应用程序已注册处理Intent.ACTION_CALL而不是Intent.ACTION_DIAL

Intent callIntent = new Intent(Intent.ACTION_CALL);
callIntent.setData(Uri.parse("tel:5551231234"));
List<ResolveInfo> callAppsList = 
  context.getPackageManager().queryIntentActivities(callIntent, 0);
然而,我不知道有任何可靠的、具备前瞻性的方法来确定那些应用程序是否能够处理电话呼叫。考虑以下情况:
1)仅支持Wifi的Xoom平板电脑上安装了Skype。它需要有效的Wifi连接,并且用户必须配置Skype以使用他们的账户,否则呼叫将无法成功。
2)一台启用了电话功能的设备没有插入SIM卡或插入的SIM卡被锁定或已失效。设备认为它可以处理电话,但是呼叫会导致“未注册网络”错误。
3)一台启用了电话功能的设备没有Wifi或移动连接(或处于飞行/飞行模式)。设备认为它可以处理电话,但呼叫将失败。
虽然可以检测出其中一些情况(例如检查getSystemService(Context.TELEPHONY_SERVICE).getSimState()),但我认为这可能会导致脆弱的代码,在未来发生变化时可能会出现问题。例如,您能始终可靠地检测到列表中哪个应用程序是默认的拨号器/电话应用程序吗?如果Android在随后的发布中更改了其软件包名称,该怎么办?
希望这给了您一些有用的信息-我想表明这比乍一看要棘手得多!

1
感谢提供详细的信息和示例案例;它阐明了这个看似简单的问题“它能打电话吗?”实际上却被琐碎的细节所困扰。您的建议对我的实现非常有效。 - Goffredo
2
原来 ACTION_CALL 并不是百分之百可靠的。我尝试在一款市场上最受欢迎的 Android 平板电脑 ASUS Eee Pad Transformer TF101 16GB 上运行 Android 3.2.1,该设备没有电话功能。出乎意料的是,它返回了一个由类 com.android.phone.PhoneApp 标识的单个意图。我本以为会返回0个意图。 我推测,PhoneApp 是通过显示“电话号码:[XXX]XXX-XXXX” [添加到联系人] [关闭] 对话框来处理拨号尝试的设备(类似于 iOS 触摸设备)。 - Goffredo

25

我认为更好的方法是查询PackageManager以检测设备上是否有可用的电话功能。只有具备电话功能的设备才应该拥有电话功能。我在Nexus 7上进行了测试,它没有电话功能,而且它可以正常工作。我没有带有电话的设备来测试相反的情况。

PackageManager pm = getPackageManager();

if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)){
    //has Telephony features.
}

2
这个很棒!(在平板电脑上禁用电话应用程序进行了测试)。在智能手机上像预期的那样工作。您甚至不需要READ_PHONE_STATE权限。 - Sohel Mansuri

4

4

您可以仅检查电话功能或单独检查GSMCDMA

private boolean hasTelephony() {
    return getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
}

或者

private boolean hasGsmOrCdma() {
    PackageManager pm = getPackageManager();

    boolean gsmSupported = pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_GSM);
    boolean cdmaSupported = pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CDMA);
//if necessary write somehow what exactly the devise supports
return gsmSupported || cdmaSupported;
    }

运行良好!


2

这里提供的大部分/全部解决方案在某些情况下有效,但不是所有情况都适用。

正如@DanJ所指出的那样。没有简单的方法可以实现这一点。

我在检查PackageManager.FEATURE_TELEPHONYgetPhoneType时遇到了问题,因为两者似乎都检查设备是否理论上具备能力,并假设如果是这种情况,则可以进行通话。如果您的手机没有SIM卡,可能会出现错误,如果没有覆盖范围/超出范围也可能会出现错误。检查哪些应用程序可以处理呼叫的方法也是如此,因为电话应用程序仍然安装在手机上,但如果没有SIM卡,则会出现错误(至少在我的Galaxy S3上)。

然而,有一种检查方法对于大多数情况来说似乎可行,那就是检查您的电话的subscriberId,基本上是“您的网络提供商的名称/ID是什么”。

您可以执行以下操作:

if(((TelephonyManager)getContext()
   .getSystemService(Context.TELEPHONY_SERVICE))
   .getSubscriberId() == null) {
  //No network id ~= can't make calls
}

在这里,我们检查是否没有网络,因此可以“安全”地假设设备无法拨打电话。这种方法在您拥有VoIP电话或安装了Skype等应用程序的情况下会产生错误的负面影响。

这种方法需要您添加权限:

<uses-permission android:name="android.permission.READ_PHONE_STATE" />

@TedHopp的解决方案需要READ_SMS权限,这对我来说似乎更具侵入性。


哦,我多么希望在Android API中有phone.canMakeCallsWithoutCrashing()这样的东西。


0

我还没有检查过这个,但是这里有一个解决方案,看起来应该可以很好地工作。我认为这不需要任何特殊权限,因为它只是在查找制造商设置的布尔值。

static boolean isRunningOnPhone(Context context){
    UiModeManager uiModeManager = (UiModeManager) context.getSystemService(UI_MODE_SERVICE);
    PackageManager packageManager = context.getPackageManager();
    boolean a = packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
    boolean b = packageManager.hasSystemFeature(PackageManager.FEATURE_SIP_VOIP) || context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CDMA) || context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CDMA);
    boolean c = packageManager.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN);
    boolean d;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        d = packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
    }else {
        d = !(uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR);
    }
    boolean e = !(uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION);
    boolean f = uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_APPLIANCE;
    boolean g = true;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
        g = !(uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_WATCH);
    }

    return a && b && c && d && e && f && g;
}

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