如何在其他通话正在进行时以编程方式合并通话(会议通话)

39
我的要求是这样的:假设我在打电话的时候,我想通过编程方式拨打另一个号码。到目前为止,我已经做到了:在已经有通话的情况下,我能够拨打特定的号码。例如,假设我正在拨打号码123,然后在1分钟后(通过使用Alarm Manger触发一个事件)拨打另一个号码456,这样就完成了!
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:456"));
startActivity(intent);

我正在使用这样的意图进行呼叫,现在我能在手机上看到一个合并通话的按钮。

screenshot of phone

在这张图片中,你可以看到一个合并通话的按钮。现在,当用户点击合并按钮时,它将合并所有三个通话。我想以编程的方式实现它,而不是通过用户界面。

3
如果您找到了任何解决方案、文章或其他相关内容,请分享。 - mark
你们国家的运营商是否支持GSM电话会议? - user755
@user755 当然是的 - Aamirkhan
请查看此处的相关内容:https://www.twilio.com/docs/client/android - Ranjit
嘿,老兄...你是怎么触发第二个调用的?如果我尝试从AlarmManager启动startActivity,它会等待通话断开,然后才会拨号...你能和我们分享一下你的代码吗? - Gal Rom
@GalRom 你是在哪个 Android 版本上测试这个功能?我建议你把你的 Alarm Manager 类给我看一下。 - Aamirkhan
6个回答

16

你的问题看起来很有趣,所以我开始研究Android源代码。这是我找到的:

你贴出的图片中的活动被称为InCallUI

当你开始查看时,你会发现InCallPresenter,它在第463行中有:

final boolean canMerge = activeCall.can(Capabilities.MERGE_CALLS);

然后在472处:

CallCommandClient.getInstance().merge();

当你查看 CallCommandClient 中的 merge() 方法时,你会发现它使用了 ICallCommandService 接口,我认为这就是你要找的内容 :)

CallCommandClient 的初始化在 CallHandlerService 的大约 193 行左右。

希望这能有所帮助并祝好运。

PS. 我列出的 API 大多数都是 Android 的内部内容。你可能需要使用反射来调用它,或者完全不可能 - 它可能对你的应用程序来说是不可访问的,因为它没有标记为系统应用程序。


4
我按照你的代码来做,但是我得到了一个ClassNotFound错误:com.android.incallui.CallCommandClient - N Sharma
3
@MeTTeO .... 我制作了一个定制的 android.jar 文件,可以访问所有内部和隐藏的 API 方法,但无法访问 InCallUICallCommandClient。而且我也无法使用 反射 访问它们。请介绍一下你建议的方法。 - mark
3
由于CallCommandClient类位于InCallUI应用程序中,而该应用程序是一个完全独立的APK包,不是Android SDK的一部分,也不是一个内置应用程序,因此您无法直接调用CallCommandClient。相反,您应该查看CallHandlerService如何访问ICallCommandService。请注意保持原文意思并使翻译更加通俗易懂。 - MeTTeO
2
@MeTTeO,我已经理解了你的概念,但是按照你的方法,第三方应用程序无法访问这些方法。因此,我认为上述解释没有任何意义。那么你能告诉我如何继续进行吗? - N Sharma
我解释了内置的InCall应用程序是如何实现的。此外,我添加了免责声明,可能只有系统应用程序才能访问所提到的API。Mark已经给出了一些关于如何继续这个概念的提示 - 如何访问隐藏/内部API(通过基于framework.jar构建自定义android.jar)。目前我没有时间准备poc。 - MeTTeO
1
@MeTTeO,我知道你建议的API无法访问,同样的标记也说过 - “我已经制作了一个自定义的android.jar文件,在其中能够访问所有内部和隐藏的API方法,但无法访问InCallUI、CallCommandClient。同时,我也无法使用反射来访问它们。请详细说明你上面提出的方法。” - N Sharma

9

2
我能够调用第三方,问题是如何合并,在我们已经有两个人在通话的时候建立第三方通话时,手机屏幕上会出现合并选项,我不想让用户点击合并按钮并建立一个会议电话,我想通过编程实现合并。 - Aamirkhan
我不是很确定,但你可以通过performClick()方法实现对事件的实用调用,但你必须在其上搜索ID。 - Akhil Dad
这是我能够做到的事情,正如我在问题中所提到的。 - Aamirkhan
@Williams 感谢您的批评。我真诚地接受您的想法,但我只是说它在Android API中不可用,并非所有事情都如此。我也很想知道哪个应用程序正在执行相同的操作。这是系统应用程序吗? - Akhil Dad

5

使用智能手机无法管理会议。您需要一个中间服务来为您完成此操作。您可以使用CCXML编写会议管理器。

Voxeo拥有良好的托管平台,可用于CCXML实现,并且您可以查看他们的文档以了解如何设置会议。在“学习CCXML 1.0 \ CCXML 1.0中的多方会议”中有示例。

您可以在Voxeo上免费开发和测试,只有在投入生产后才会开始收费。另一个选择是Twillio。

这里是一个链接,介绍如何在其平台上编程电话会议。

检查链接,您将获得有用的信息。#礼貌-SO


酷,但是需要检查一下是否适用于安卓手机,我会尽快尝试并回复你。 - Aamirkhan

4
据我所知,SDK中没有可编程合并呼叫的API。您需要在RIL(无线电接口层)上进行工作,以实现电话会议,这是Android用于通信电话的。Android的无线电接口层(RIL)为Android电话服务(android.telephony)和无线电硬件之间提供抽象层。RIL是无线电不可知的,并包括对基于全球移动通信系统(GSM)的无线电的支持。请参见此处:http://www.kandroid.org/online-pdk/guide/telephony.html
更新:调制解调器代码如何与Android代码通信

http://fabiensanglard.net/cellphoneModem/index2.php

http://www.e-consystems.com/blog/android/?p=498

所以您必须在套接字中编写AT调制解调器命令,然后rild调用回调函数到供应商库,然后供应商库再代表无线电固件。


2

在经过大量搜索后,我成功合并了通话,现在我想与您分享我的发现。供参考,我使用了这个链接

  1. 将CallList.java文件引入到您的项目中
package com.example.confrencecalldemo;

import android.os.Handler;
import android.os.Message;
import android.os.Trace;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccount;

import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;

import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;


public class CallList {

    private static final int DISCONNECTED_CALL_SHORT_TIMEOUT_MS = 200;
    private static final int DISCONNECTED_CALL_MEDIUM_TIMEOUT_MS = 2000;
    private static final int DISCONNECTED_CALL_LONG_TIMEOUT_MS = 5000;

    private static final int EVENT_DISCONNECTED_TIMEOUT = 1;

    private static CallList sInstance = new CallList();

    private final HashMap<String, CallHelper> mCallById = new HashMap<>();
    private final HashMap<android.telecom.Call, CallHelper> mCallByTelecommCall = new HashMap<>();
    private final HashMap<String, List<String>> mCallTextReponsesMap = Maps.newHashMap();
    /**
     * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
     * load factor before resizing, 1 means we only expect a single thread to
     * access the map so make only a single shard
     */
    private final Set<Listener> mListeners = Collections.newSetFromMap(
            new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
    private final HashMap<String, List<CallUpdateListener>> mCallUpdateListenerMap = Maps
            .newHashMap();
    private final Set<CallHelper> mPendingDisconnectCalls = Collections.newSetFromMap(
            new ConcurrentHashMap<CallHelper, Boolean>(8, 0.9f, 1));

    /**
     * Static singleton accessor method.
     */
    public static CallList getInstance() {
        return sInstance;
    }

    /**
     * USED ONLY FOR TESTING
     * Testing-only constructor.  Instance should only be acquired through getInstance().
     */
    CallList() {
    }

    public void onCallAdded(android.telecom.Call telecommCall) {
        Trace.beginSection("onCallAdded");
        CallHelper call = new CallHelper(telecommCall);
//        Log.d(this, "onCallAdded: callState=" + call.getState());
        if (call.getState() == CallHelper.State.INCOMING ||
                call.getState() == CallHelper.State.CALL_WAITING) {
            onIncoming(call, call.getCannedSmsResponses());
        } else {
            onUpdate(call);
        }
        Trace.endSection();
    }

    public void onCallRemoved(android.telecom.Call telecommCall) {
        if (mCallByTelecommCall.containsKey(telecommCall)) {
            CallHelper call = mCallByTelecommCall.get(telecommCall);
            if (updateCallInMap(call)) {
//                Log.w(this, "Removing call not previously disconnected " + call.getId());
            }
            updateCallTextMap(call, null);
        }
    }

    /**
     * Called when a single call disconnects.
     */
    public void onDisconnect(CallHelper call) {
        if (updateCallInMap(call)) {
//            Log.i(this, "onDisconnect: " + call);
            // notify those listening for changes on this specific change
            notifyCallUpdateListeners(call);
            // notify those listening for all disconnects
            notifyListenersOfDisconnect(call);
        }
    }

    /**
     * Called when a single call has changed.
     */
    public void onIncoming(CallHelper call, List<String> textMessages) {
        if (updateCallInMap(call)) {
//            Log.i(this, "onIncoming - " + call);
        }
        updateCallTextMap(call, textMessages);

        for (Listener listener : mListeners) {
            listener.onIncomingCall(call);
        }
    }

    public void onUpgradeToVideo(CallHelper call){
//        Log.d(this, "onUpgradeToVideo call=" + call);
        for (Listener listener : mListeners) {
            listener.onUpgradeToVideo(call);
        }
    }
    /**
     * Called when a single call has changed.
     */
    public void onUpdate(CallHelper call) {
        Trace.beginSection("onUpdate");
        onUpdateCall(call);
        notifyGenericListeners();
        Trace.endSection();
    }

    /**
     * Called when a single call has changed session modification state.
     *
     * @param call The call.
     * @param sessionModificationState The new session modification state.
     */
    public void onSessionModificationStateChange(CallHelper call, int sessionModificationState) {
        final List<CallUpdateListener> listeners = mCallUpdateListenerMap.get(call.getId());
        if (listeners != null) {
            for (CallUpdateListener listener : listeners) {
                listener.onSessionModificationStateChange(sessionModificationState);
            }
        }
    }

    /**
     * Called when the last forwarded number changes for a call.  With IMS, the last forwarded
     * number changes due to a supplemental service notification, so it is not pressent at the
     * start of the call.
     *
     * @param call The call.
     */
    public void onLastForwardedNumberChange(CallHelper call) {
        final List<CallUpdateListener> listeners = mCallUpdateListenerMap.get(call.getId());
        if (listeners != null) {
            for (CallUpdateListener listener : listeners) {
                listener.onLastForwardedNumberChange();
            }
        }
    }

    /**
     * Called when the child number changes for a call.  The child number can be received after a
     * call is initially set up, so we need to be able to inform listeners of the change.
     *
     * @param call The call.
     */
    public void onChildNumberChange(CallHelper call) {
        final List<CallUpdateListener> listeners = mCallUpdateListenerMap.get(call.getId());
        if (listeners != null) {
            for (CallUpdateListener listener : listeners) {
                listener.onChildNumberChange();
            }
        }
    }

    public void notifyCallUpdateListeners(CallHelper call) {
        final List<CallUpdateListener> listeners = mCallUpdateListenerMap.get(call.getId());
        if (listeners != null) {
            for (CallUpdateListener listener : listeners) {
                listener.onCallChanged(call);
            }
        }
    }

    /**
     * Add a call update listener for a call id.
     *
     * @param callId The call id to get updates for.
     * @param listener The listener to add.
     */
    public void addCallUpdateListener(String callId, CallUpdateListener listener) {
        List<CallUpdateListener> listeners = mCallUpdateListenerMap.get(callId);
        if (listeners == null) {
            listeners = new CopyOnWriteArrayList<CallUpdateListener>();
            mCallUpdateListenerMap.put(callId, listeners);
        }
        listeners.add(listener);
    }

    /**
     * Remove a call update listener for a call id.
     *
     * @param callId The call id to remove the listener for.
     * @param listener The listener to remove.
     */
    public void removeCallUpdateListener(String callId, CallUpdateListener listener) {
        List<CallUpdateListener> listeners = mCallUpdateListenerMap.get(callId);
        if (listeners != null) {
            listeners.remove(listener);
        }
    }

    public void addListener(Listener listener) {
        Preconditions.checkNotNull(listener);

        mListeners.add(listener);

        // Let the listener know about the active calls immediately.
        listener.onCallListChange(this);
    }

    public void removeListener(Listener listener) {
        if (listener != null) {
            mListeners.remove(listener);
        }
    }

    /**
     * TODO: Change so that this function is not needed. Instead of assuming there is an active
     * call, the code should rely on the status of a specific CallHelper and allow the presenters to
     * update the CallHelper object when the active call changes.
     */
    public CallHelper getIncomingOrActive() {
        CallHelper retval = getIncomingCall();
        if (retval == null) {
            retval = getActiveCall();
        }
        return retval;
    }

    public CallHelper getOutgoingOrActive() {
        CallHelper retval = getOutgoingCall();
        if (retval == null) {
            retval = getActiveCall();
        }
        return retval;
    }

    /**
     * A call that is waiting for {@link PhoneAccount} selection
     */
    public CallHelper getWaitingForAccountCall() {
        return getFirstCallWithState(CallHelper.State.SELECT_PHONE_ACCOUNT);
    }

    public CallHelper getPendingOutgoingCall() {
        return getFirstCallWithState(CallHelper.State.CONNECTING);
    }

    public CallHelper getOutgoingCall() {
        CallHelper call = getFirstCallWithState(CallHelper.State.DIALING);
        if (call == null) {
            call = getFirstCallWithState(CallHelper.State.REDIALING);
        }
        return call;
    }

    public CallHelper getActiveCall() {
        return getFirstCallWithState(CallHelper.State.ACTIVE);
    }

    public CallHelper getBackgroundCall() {
        return getFirstCallWithState(CallHelper.State.ONHOLD);
    }

    public CallHelper getDisconnectedCall() {
        return getFirstCallWithState(CallHelper.State.DISCONNECTED);
    }

    public CallHelper getDisconnectingCall() {
        return getFirstCallWithState(CallHelper.State.DISCONNECTING);
    }

    public CallHelper getSecondBackgroundCall() {
        return getCallWithState(CallHelper.State.ONHOLD, 1);
    }

    public CallHelper getActiveOrBackgroundCall() {
        CallHelper call = getActiveCall();
        if (call == null) {
            call = getBackgroundCall();
        }
        return call;
    }

    public CallHelper getIncomingCall() {
        CallHelper call = getFirstCallWithState(CallHelper.State.INCOMING);
        if (call == null) {
            call = getFirstCallWithState(CallHelper.State.CALL_WAITING);
        }

        return call;
    }

    public CallHelper getFirstCall() {
        CallHelper result = getIncomingCall();
        if (result == null) {
            result = getPendingOutgoingCall();
        }
        if (result == null) {
            result = getOutgoingCall();
        }
        if (result == null) {
            result = getFirstCallWithState(CallHelper.State.ACTIVE);
        }
        if (result == null) {
            result = getDisconnectingCall();
        }
        if (result == null) {
            result = getDisconnectedCall();
        }
        return result;
    }

    public boolean hasLiveCall() {
        CallHelper call = getFirstCall();
        if (call == null) {
            return false;
        }
        return call != getDisconnectingCall() && call != getDisconnectedCall();
    }


    public CallHelper getVideoUpgradeRequestCall() {
        for(CallHelper call : mCallById.values()) {
            if (call.getSessionModificationState() ==
                    CallHelper.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
                return call;
            }
        }
        return null;
    }

    public CallHelper getCallById(String callId) {
        return mCallById.get(callId);
    }

    public CallHelper getCallByTelecommCall(android.telecom.Call telecommCall) {
        return mCallByTelecommCall.get(telecommCall);
    }

    public List<String> getTextResponses(String callId) {
        return mCallTextReponsesMap.get(callId);
    }

    /**
     * Returns first call found in the call map with the specified state.
     */
    public CallHelper getFirstCallWithState(int state) {
        return getCallWithState(state, 0);
    }

    /**
     * Returns the [position]th call found in the call map with the specified state.
     * TODO: Improve this logic to sort by call time.
     */
    public CallHelper getCallWithState(int state, int positionToFind) {
        CallHelper retval = null;
        int position = 0;
        for (CallHelper call : mCallById.values()) {
            if (call.getState() == state) {
                if (position >= positionToFind) {
                    retval = call;
                    break;
                } else {
                    position++;
                }
            }
        }

        return retval;
    }

    /**
     * This is called when the service disconnects, either expectedly or unexpectedly.
     * For the expected case, it's because we have no calls left.  For the unexpected case,
     * it is likely a crash of phone and we need to clean up our calls manually.  Without phone,
     * there can be no active calls, so this is relatively safe thing to do.
     */
    public void clearOnDisconnect() {
        for (CallHelper call : mCallById.values()) {
            final int state = call.getState();
            if (state != CallHelper.State.IDLE &&
                    state != CallHelper.State.INVALID &&
                    state != CallHelper.State.DISCONNECTED) {

                call.setState(CallHelper.State.DISCONNECTED);
                call.setDisconnectCause(new DisconnectCause(DisconnectCause.UNKNOWN));
                updateCallInMap(call);
            }
        }
        notifyGenericListeners();
    }

    /**
     * Called when the user has dismissed an error dialog. This indicates acknowledgement of
     * the disconnect cause, and that any pending disconnects should immediately occur.
     */
    public void onErrorDialogDismissed() {
        final Iterator<CallHelper> iterator = mPendingDisconnectCalls.iterator();
        while (iterator.hasNext()) {
            CallHelper call = iterator.next();
            iterator.remove();
            finishDisconnectedCall(call);
        }
    }

    /**
     * Processes an update for a single call.
     *
     * @param call The call to update.
     */
    private void onUpdateCall(CallHelper call) {
//        Log.d(this, "\t" + call);
        if (updateCallInMap(call)) {
//            Log.i(this, "onUpdate - " + call);
        }
        updateCallTextMap(call, call.getCannedSmsResponses());
        notifyCallUpdateListeners(call);
    }

    /**
     * Sends a generic notification to all listeners that something has changed.
     * It is up to the listeners to call back to determine what changed.
     */
    private void notifyGenericListeners() {
        for (Listener listener : mListeners) {
            listener.onCallListChange(this);
        }
    }

    private void notifyListenersOfDisconnect(CallHelper call) {
        for (Listener listener : mListeners) {
            listener.onDisconnect(call);
        }
    }

    /**
     * Updates the call entry in the local map.
     * @return false if no call previously existed and no call was added, otherwise true.
     */
    private boolean updateCallInMap(CallHelper call) {
        Preconditions.checkNotNull(call);

        boolean updated = false;

        if (call.getState() == CallHelper.State.DISCONNECTED) {
            // update existing (but do not add!!) disconnected calls
            if (mCallById.containsKey(call.getId())) {
                // For disconnected calls, we want to keep them alive for a few seconds so that the
                // UI has a chance to display anything it needs when a call is disconnected.

                // Set up a timer to destroy the call after X seconds.
                final Message msg = mHandler.obtainMessage(EVENT_DISCONNECTED_TIMEOUT, call);
                mHandler.sendMessageDelayed(msg, getDelayForDisconnect(call));
                mPendingDisconnectCalls.add(call);

                mCallById.put(call.getId(), call);
                mCallByTelecommCall.put(call.getTelecommCall(), call);
                updated = true;
            }
        } else if (!isCallDead(call)) {
            mCallById.put(call.getId(), call);
            mCallByTelecommCall.put(call.getTelecommCall(), call);
            updated = true;
        } else if (mCallById.containsKey(call.getId())) {
            mCallById.remove(call.getId());
            mCallByTelecommCall.remove(call.getTelecommCall());
            updated = true;
        }

        return updated;
    }

    private int getDelayForDisconnect(CallHelper call) {
        Preconditions.checkState(call.getState() == CallHelper.State.DISCONNECTED);


        final int cause = call.getDisconnectCause().getCode();
        final int delay;
        switch (cause) {
            case DisconnectCause.LOCAL:
                delay = DISCONNECTED_CALL_SHORT_TIMEOUT_MS;
                break;
            case DisconnectCause.REMOTE:
            case DisconnectCause.ERROR:
                delay = DISCONNECTED_CALL_MEDIUM_TIMEOUT_MS;
                break;
            case DisconnectCause.REJECTED:
            case DisconnectCause.MISSED:
            case DisconnectCause.CANCELED:
                // no delay for missed/rejected incoming calls and canceled outgoing calls.
                delay = 0;
                break;
            default:
                delay = DISCONNECTED_CALL_LONG_TIMEOUT_MS;
                break;
        }

        return delay;
    }

    private void updateCallTextMap(CallHelper call, List<String> textResponses) {
        Preconditions.checkNotNull(call);

        if (!isCallDead(call)) {
            if (textResponses != null) {
                mCallTextReponsesMap.put(call.getId(), textResponses);
            }
        } else if (mCallById.containsKey(call.getId())) {
            mCallTextReponsesMap.remove(call.getId());
        }
    }

    private boolean isCallDead(CallHelper call) {
        final int state = call.getState();
        return CallHelper.State.IDLE == state || CallHelper.State.INVALID == state;
    }

    /**
     * Sets up a call for deletion and notifies listeners of change.
     */
    private void finishDisconnectedCall(CallHelper call) {
        if (mPendingDisconnectCalls.contains(call)) {
            mPendingDisconnectCalls.remove(call);
        }
        call.setState(CallHelper.State.IDLE);
        updateCallInMap(call);
        notifyGenericListeners();
    }

    /**
     * Notifies all video calls of a change in device orientation.
     *
     * @param rotation The new rotation angle (in degrees).
     */
    public void notifyCallsOfDeviceRotation(int rotation) {
        for (CallHelper call : mCallById.values()) {
            // First, ensure a VideoCall is set on the call so that the change can be sent to the
            // provider (a VideoCall can be present for a call that does not currently have video,
            // but can be upgraded to video).
            // Second, ensure that the call videoState has video enabled (there is no need to set
            // device orientation on a voice call which has not yet been upgraded to video).
            if (call.getVideoCall() != null && CallUtils.isVideoCall(call)) {
                call.getVideoCall().setDeviceOrientation(rotation);
            }
        }
    }

    /**
     * Handles the timeout for destroying disconnected calls.
     */
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case EVENT_DISCONNECTED_TIMEOUT:
//                    Log.d(this, "EVENT_DISCONNECTED_TIMEOUT ", msg.obj);
                    finishDisconnectedCall((CallHelper) msg.obj);
                    break;
                default:
//                    Log.wtf(this, "Message not expected: " + msg.what);
                    break;
            }
        }
    };

    /**
     * Listener interface for any class that wants to be notified of changes
     * to the call list.
     */
    public interface Listener {
        /**
         * Called when a new incoming call comes in.
         * This is the only method that gets called for incoming calls. Listeners
         * that want to perform an action on incoming call should respond in this method
         * because {@link #onCallListChange} does not automatically get called for
         * incoming calls.
         */
        public void onIncomingCall(CallHelper call);
        /**
         * Called when a new modify call request comes in
         * This is the only method that gets called for modify requests.
         */
        public void onUpgradeToVideo(CallHelper call);
        /**
         * Called anytime there are changes to the call list.  The change can be switching call
         * states, updating information, etc. This method will NOT be called for new incoming
         * calls and for calls that switch to disconnected state. Listeners must add actions
         * to those method implementations if they want to deal with those actions.
         */
        public void onCallListChange(CallList callList);

        /**
         * Called when a call switches to the disconnected state.  This is the only method
         * that will get called upon disconnection.
         */
        public void onDisconnect(CallHelper call);


    }

    public interface CallUpdateListener {
        // TODO: refactor and limit arg to be call state.  Caller info is not needed.
        public void onCallChanged(CallHelper call);

        /**
         * Notifies of a change to the session modification state for a call.
         *
         * @param sessionModificationState The new session modification state.
         */
        public void onSessionModificationStateChange(int sessionModificationState);

        /**
         * Notifies of a change to the last forwarded number for a call.
         */
        public void onLastForwardedNumberChange();

        /**
         * Notifies of a change to the child number for a call.
         */
        public void onChildNumberChange();
    }
}

2.从InCallService类中调用CallList.java的方法。

@Override
    public void onCallAdded(Call call) {
        super.onCallAdded(call);
        Log.d("MyConnectionService","onCallAdded");

        CallList.getInstance().onCallAdded(call);
        
    }

    @Override
    public void onCallRemoved(Call call) {
        super.onCallRemoved(call);
        Log.d("MyConnectionService","onCallRemoved"); 

        CallList.getInstance().onCallRemoved(call);
    }
  1. 最后调用函数合并呼叫
public void mergeCall() {

        final CallList calls = CallList.getInstance();
        CallHelper activeCall = calls.getActiveCall();

        if (activeCall != null) {

            final boolean canMerge = activeCall.can(
                    android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE);
            final boolean canSwap = activeCall.can(
                    android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE);
            // (2) Attempt actions on conference calls
            if (canMerge) {
                TelecomAdapter.getInstance().merge(activeCall.getId());

            } else if (canSwap) {
                TelecomAdapter.getInstance().swap(activeCall.getId());
            }
        }
    }
我正在编辑我的答案,我忘记放置 CallHelper.java 类。请访问下面的链接获取 CallHelper.java 文件。 https://gist.github.com/amitsemwal1/4e9ca712adc8daaf070a0cc0e0d58c26

1
CallHelper类。我在哪里可以找到它?如果可能的话,请为这个呼叫合并实现提供完整的解决方案。 - Narendrakumar
CallHelper是什么?请指教。 - Sanidhya Kumar
@SanidhyaKumar 我已经更新了我的答案,请访问给定链接以获取CallHelper类。 - amit semwal

1

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