You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何基于Connection Service实现拨号应用的运营商通话功能?

实现基于Connection Service的运营商通话功能(绕过内部API)

嘿,你遇到的这个问题其实挺典型的——Android系统的com.android.internal.telephony下的Call、Phone等类都是系统内部API,第三方应用根本没法直接访问(就算用反射也不推荐,兼容性和合规性都有大坑)。咱们得换个思路,用Android提供的公开Telephony API结合Connection Service来实现自有拨号应用的运营商通话功能,具体步骤如下:

核心思路

放弃依赖系统内部Telephony组件,转而通过公开的TelephonyManager控制通话状态,同时配合Connection Service的规范实现通话生命周期管理,让系统识别你的应用为合法的通话提供者。

具体实现步骤

1. 补全权限配置

除了你已经配置的权限,还需要补充这些关键权限(根据目标Android版本调整):

  • CALL_PHONE:发起通话的基础权限
  • READ_PHONE_STATE:获取设备通话状态
  • ANSWER_PHONE_CALLS(Android 9+):接听来电的权限
  • READ_CALL_LOG(可选):读取通话记录用于展示
  • ACCESS_FINE_LOCATION(Android 10+):部分场景下获取蜂窝网络信息需要

AndroidManifest.xml中声明:

<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

2. 正确配置PhoneAccount能力

你的ConnectionService组件已经配置了基础intent filter,还要确保注册PhoneAccount时设置正确的能力,告诉系统你的应用可以提供通话服务:

PhoneAccountHandle handle = new PhoneAccountHandle(
        new ComponentName(this, MyConnectionService.class),
        "my_unique_account_id"
);
PhoneAccount account = PhoneAccount.builder(handle, "我的拨号应用")
        .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER | PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
        .setHighlightColor(Color.GREEN)
        .build();

TelecomManager telecomManager = getSystemService(TelecomManager.class);
telecomManager.registerPhoneAccount(account);

3. 实现真实通话的Connection逻辑

在你的Connection子类中,不要依赖内部API,而是通过TelephonyManager处理通话发起、状态同步:

public class MyConnection extends Connection {
    private TelephonyManager mTelephonyManager;
    private PhoneStateListener mPhoneStateListener;

    @Override
    public void onCreate() {
        super.onCreate();
        // 标记为真实运营商通话,区别于虚拟通话
        setConnectionProperties(PROPERTY_IS_REAL_CALL);
        // 设置初始状态为拨号中
        setState(STATE_DIALING);

        mTelephonyManager = getSystemService(TelephonyManager.class);
        // 发起真实通话(确保已申请CALL_PHONE权限)
        if (ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_GRANTED) {
            String phoneNumber = getAddress().toString().replace("tel:", "");
            mTelephonyManager.call(phoneNumber);
        }

        // 监听系统通话状态,同步到Connection对象
        mPhoneStateListener = new PhoneStateListener() {
            @Override
            public void onCallStateChanged(int state, String phoneNumber) {
                super.onCallStateChanged(state, phoneNumber);
                switch (state) {
                    case TelephonyManager.CALL_STATE_OFFHOOK:
                        // 通话接通,更新Connection状态
                        setState(STATE_ACTIVE);
                        break;
                    case TelephonyManager.CALL_STATE_IDLE:
                        // 通话结束,标记断开并销毁Connection
                        setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
                        destroy();
                        break;
                }
            }
        };
        mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // 取消监听,避免内存泄漏
        if (mTelephonyManager != null && mPhoneStateListener != null) {
            mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
        }
    }

    // 实现挂断通话的逻辑
    @Override
    public void onDisconnect() {
        super.onDisconnect();
        if (ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ANSWER_PHONE_CALLS) == PackageManager.PERMISSION_GRANTED) {
            mTelephonyManager.endCall();
        }
        setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
        destroy();
    }
}

4. 处理通话请求路由

当你的应用被设为默认拨号应用后,系统会将通话请求路由到你的ConnectionService,需要在对应方法中返回自定义的MyConnection实例:

public class MyConnectionService extends ConnectionService {
    @Override
    public Connection onCreateOutgoingConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {
        MyConnection connection = new MyConnection();
        connection.setAddress(request.getAddress(), TelecomManager.PRESENTATION_ALLOWED);
        return connection;
    }

    // 可选:处理来电逻辑
    @Override
    public Connection onCreateIncomingConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {
        MyConnection connection = new MyConnection();
        connection.setAddress(request.getAddress(), TelecomManager.PRESENTATION_ALLOWED);
        connection.setIncoming(true);
        connection.setState(STATE_RINGING);
        return connection;
    }
}

关键注意事项

  • 绝对不要用反射调用内部API:不同厂商的系统可能修改com.android.internal.telephony下的类,导致兼容性问题,而且Google Play审核也会拒绝这类应用。
  • 权限动态申请:Android 6及以上需要动态申请危险权限(比如CALL_PHONEREAD_PHONE_STATE),记得在代码中处理权限申请流程。
  • 测试默认拨号设置:在系统设置中把你的应用设为默认拨号后,测试发起/接听通话的完整流程,确保状态同步正确。

内容的提问来源于stack exchange,提问作者Vatish Sharma

火山引擎 最新活动