Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ece0270d authored by Santos Cordon's avatar Santos Cordon Committed by Android (Google) Code Review
Browse files

Merge "Support conference calling. (1/4) [DO NOT MERGE]" into lmp-preview-dev

parents 342c0d0a 1a79c70e
Loading
Loading
Loading
Loading
+43 −7
Original line number Diff line number Diff line
@@ -17,7 +17,7 @@
package android.telecomm;

/** Defines actions a call currently supports. */
public class CallCapabilities {
public final class CallCapabilities {
    /** Call can currently be put on hold or unheld. */
    public static final int HOLD               = 0x00000001;

@@ -27,24 +27,60 @@ public class CallCapabilities {
    /** Call can currently be merged. */
    public static final int MERGE_CALLS        = 0x00000004;

     /* Call can currently be swapped with another call. */
    /** Call can currently be swapped with another call. */
    public static final int SWAP_CALLS         = 0x00000008;

     /* Call currently supports adding another call to this one. */
    /** Call currently supports adding another call to this one. */
    public static final int ADD_CALL           = 0x00000010;

     /* Call supports responding via text option. */
    /** Call supports responding via text option. */
    public static final int RESPOND_VIA_TEXT   = 0x00000020;

     /* Call can be muted. */
    /** Call can be muted. */
    public static final int MUTE               = 0x00000040;

     /* Call supports generic conference mode. */
    /** Call supports generic conference mode. */
    public static final int GENERIC_CONFERENCE = 0x00000080;

     /* Call currently supports switch between connections. */
    /** Call currently supports switch between connections. */
    public static final int CONNECTION_HANDOFF = 0x00000100;

    public static final int ALL = HOLD | SUPPORT_HOLD | MERGE_CALLS | SWAP_CALLS | ADD_CALL
            | RESPOND_VIA_TEXT | MUTE | GENERIC_CONFERENCE | CONNECTION_HANDOFF;

    public static String toString(int capabilities) {
        StringBuilder builder = new StringBuilder();
        builder.append("[Capabilities:");
        if ((capabilities & HOLD) != 0) {
            builder.append(" HOLD");
        }
        if ((capabilities & SUPPORT_HOLD) != 0) {
            builder.append(" SUPPORT_HOLD");
        }
        if ((capabilities & MERGE_CALLS) != 0) {
            builder.append(" MERGE_CALLS");
        }
        if ((capabilities & SWAP_CALLS) != 0) {
            builder.append(" SWAP_CALLS");
        }
        if ((capabilities & ADD_CALL) != 0) {
            builder.append(" ADD_CALL");
        }
        if ((capabilities & RESPOND_VIA_TEXT) != 0) {
            builder.append(" RESPOND_VIA_TEXT");
        }
        if ((capabilities & MUTE) != 0) {
            builder.append(" MUTE");
        }
        if ((capabilities & GENERIC_CONFERENCE) != 0) {
            builder.append(" GENERIC_CONFERENCE");
        }
        if ((capabilities & CONNECTION_HANDOFF) != 0) {
            builder.append(" HANDOFF");
        }
        builder.append("]");
        return builder.toString();
    }

    private CallCapabilities() {}
}
+16 −30
Original line number Diff line number Diff line
@@ -61,7 +61,7 @@ public abstract class CallService extends Service {
    private static final int MSG_ON_AUDIO_STATE_CHANGED = 11;
    private static final int MSG_PLAY_DTMF_TONE = 12;
    private static final int MSG_STOP_DTMF_TONE = 13;
    private static final int MSG_ADD_TO_CONFERENCE = 14;
    private static final int MSG_CONFERENCE = 14;
    private static final int MSG_SPLIT_FROM_CONFERENCE = 15;
    private static final int MSG_ON_POST_DIAL_CONTINUE = 16;

@@ -128,24 +128,12 @@ public abstract class CallService extends Service {
                case MSG_STOP_DTMF_TONE:
                    stopDtmfTone((String) msg.obj);
                    break;
                case MSG_ADD_TO_CONFERENCE: {
                    SomeArgs args = (SomeArgs) msg.obj;
                    try {
                        @SuppressWarnings("unchecked")
                        List<String> callIds = (List<String>) args.arg2;
                        String conferenceCallId = (String) args.arg1;
                        addToConference(conferenceCallId, callIds);
                    } finally {
                        args.recycle();
                    }
                    break;
                }
                case MSG_SPLIT_FROM_CONFERENCE: {
                case MSG_CONFERENCE: {
                    SomeArgs args = (SomeArgs) msg.obj;
                    try {
                        String conferenceCallId = (String) args.arg1;
                        String callId = (String) args.arg2;
                        splitFromConference(conferenceCallId, callId);
                        conference(conferenceCallId, callId);
                    } finally {
                        args.recycle();
                    }
@@ -162,6 +150,9 @@ public abstract class CallService extends Service {
                    }
                    break;
                }
                case MSG_SPLIT_FROM_CONFERENCE:
                    splitFromConference((String) msg.obj);
                    break;
                default:
                    break;
            }
@@ -245,19 +236,16 @@ public abstract class CallService extends Service {
        }

        @Override
        public void addToConference(String conferenceCallId, List<String> callsToConference) {
        public void conference(String conferenceCallId, String callId) {
            SomeArgs args = SomeArgs.obtain();
            args.arg1 = conferenceCallId;
            args.arg2 = callsToConference;
            mMessageHandler.obtainMessage(MSG_ADD_TO_CONFERENCE, args).sendToTarget();
            args.arg2 = callId;
            mMessageHandler.obtainMessage(MSG_CONFERENCE, args).sendToTarget();
        }

        @Override
        public void splitFromConference(String conferenceCallId, String callId) {
            SomeArgs args = SomeArgs.obtain();
            args.arg1 = conferenceCallId;
            args.arg2 = callId;
            mMessageHandler.obtainMessage(MSG_SPLIT_FROM_CONFERENCE, args).sendToTarget();
        public void splitFromConference(String callId) {
            mMessageHandler.obtainMessage(MSG_SPLIT_FROM_CONFERENCE, callId).sendToTarget();
        }

        @Override
@@ -424,24 +412,22 @@ public abstract class CallService extends Service {
    public abstract void onAudioStateChanged(String activeCallId, CallAudioState audioState);

    /**
     * Adds the specified calls to the specified conference call.
     * Conferences the specified call.
     *
     * @param conferenceCallId The unique ID of the conference call onto which the specified calls
     *         should be added.
     * @param callIds The calls to add to the conference call.
     * @param callId The call to conference.
     * @hide
     */
    public abstract void addToConference(String conferenceCallId, List<String> callIds);
    public abstract void conference(String conferenceCallId, String callId);

    /**
     * Removes the specified call from the specified conference call. This is a no-op if the call
     * is not already part of the conference call.
     * Removes the specified call from a conference call.
     *
     * @param conferenceCallId The conference call.
     * @param callId The call to remove from the conference call
     * @hide
     */
    public abstract void splitFromConference(String conferenceCallId, String callId);
    public abstract void splitFromConference(String callId);

    public void onPostDialContinue(String callId, boolean proceed) {}
    public void onPostDialWait(Connection conn, String remaining) {}
+19 −6
Original line number Diff line number Diff line
@@ -179,12 +179,12 @@ public final class CallServiceAdapter {
     * Indicates that the specified call can conference with any of the specified list of calls.
     *
     * @param callId The unique ID of the call.
     * @param conferenceCapableCallIds The unique IDs of the calls which can be conferenced.
     * @param canConference Specified whether or not the call can be conferenced.
     * @hide
     */
    public void setCanConferenceWith(String callId, List<String> conferenceCapableCallIds) {
    public void setCanConference(String callId, boolean canConference) {
        try {
            mAdapter.setCanConferenceWith(callId, conferenceCapableCallIds);
            mAdapter.setCanConference(callId, canConference);
        } catch (RemoteException ignored) {
        }
    }
@@ -193,13 +193,14 @@ public final class CallServiceAdapter {
     * Indicates whether or not the specified call is currently conferenced into the specified
     * conference call.
     *
     * @param conferenceCallId The unique ID of the conference call.
     * @param callId The unique ID of the call being conferenced.
     * @param conferenceCallId The unique ID of the conference call. Null if call is not
     *         conferenced.
     * @hide
     */
    public void setIsConferenced(String conferenceCallId, String callId, boolean isConferenced) {
    public void setIsConferenced(String callId, String conferenceCallId) {
        try {
            mAdapter.setIsConferenced(conferenceCallId, callId, isConferenced);
            mAdapter.setIsConferenced(callId, conferenceCallId);
        } catch (RemoteException ignored) {
        }
    }
@@ -224,4 +225,16 @@ public final class CallServiceAdapter {
        } catch (RemoteException ignored) {
        }
    }

    /**
     * Indicates that a new conference call has been created.
     *
     * @param callId The unique ID of the conference call.
     */
    public void addConferenceCall(String callId) {
        try {
            mAdapter.addConferenceCall(callId, null);
        } catch (RemoteException ignored) {
        }
    }
}
+121 −1
Original line number Diff line number Diff line
@@ -19,7 +19,10 @@ package android.telecomm;
import android.net.Uri;
import android.os.Bundle;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
@@ -35,6 +38,8 @@ public abstract class Connection {
        void onDisconnected(Connection c, int cause, String message);
        void onRequestingRingback(Connection c, boolean ringback);
        void onDestroyed(Connection c);
        void onConferenceCapableChanged(Connection c, boolean isConferenceCapable);
        void onParentConnectionChanged(Connection c, Connection parent);
    }

    public static class ListenerBase implements Listener {
@@ -65,6 +70,14 @@ public abstract class Connection {
        /** {@inheritDoc} */
        @Override
        public void onRequestingRingback(Connection c, boolean ringback) {}

        /** ${inheritDoc} */
        @Override
        public void onConferenceCapableChanged(Connection c, boolean isConferenceCapable) {}

        /** ${inheritDoc} */
        @Override
        public void onParentConnectionChanged(Connection c, Connection parent) {}
    }

    public final class State {
@@ -79,10 +92,14 @@ public abstract class Connection {
    }

    private final Set<Listener> mListeners = new HashSet<>();
    private final List<Connection> mChildConnections = new ArrayList<>();

    private int mState = State.NEW;
    private CallAudioState mCallAudioState;
    private Uri mHandle;
    private boolean mRequestingRingback = false;
    private boolean mIsConferenceCapable = false;
    private Connection mParentConnection;

    /**
     * Create a new Connection.
@@ -175,6 +192,16 @@ public abstract class Connection {
        onDisconnect();
    }

    /**
     * Separates this Connection from a parent connection.
     *
     * @hide
     */
    public final void separate() {
        Log.d(this, "separate");
        onSeparate();
    }

    /**
     * Abort this Connection. The Connection will immediately transition to
     * the {@link State#DISCONNECTED} state, and send no notifications of this
@@ -239,6 +266,14 @@ public abstract class Connection {
        }
    }

    /**
     * TODO(santoscordon): Needs updated documentation.
     */
    public final void conference() {
        Log.d(this, "conference");
        onConference();
    }

    /**
     * Inform this Connection that the state of its audio output has been changed externally.
     *
@@ -274,13 +309,45 @@ public abstract class Connection {
    }

    /**
     * @return Whether this connection is requesting that the system play a ringback tone
     * Returns whether this connection is requesting that the system play a ringback tone
     * on its behalf.
     */
    public boolean isRequestingRingback() {
        return mRequestingRingback;
    }

    /**
     * Returns whether this connection is a conference connection (has child connections).
     */
    public boolean isConferenceConnection() {
        return !mChildConnections.isEmpty();
    }

    public void setParentConnection(Connection parentConnection) {
        Log.d(this, "parenting %s to %s", this, parentConnection);
        if (mParentConnection != parentConnection) {
            if (mParentConnection != null) {
                mParentConnection.removeChild(this);
            }
            mParentConnection = parentConnection;
            if (mParentConnection != null) {
                mParentConnection.addChild(this);
                // do something if the child connections goes down to ZERO.
            }
            for (Listener l : mListeners) {
                l.onParentConnectionChanged(this, mParentConnection);
            }
        }
    }

    public Connection getParentConnection() {
        return mParentConnection;
    }

    public List<Connection> getChildConnections() {
        return mChildConnections;
    }

    /**
     * Sets the value of the {@link #getHandle()} property and notifies listeners.
     *
@@ -358,6 +425,32 @@ public abstract class Connection {
        }
    }

    /**
     * TODO(santoscordon): Needs documentation.
     */
    protected void setIsConferenceCapable(boolean isConferenceCapable) {
        if (mIsConferenceCapable != isConferenceCapable) {
            mIsConferenceCapable = isConferenceCapable;
            for (Listener l : mListeners) {
                l.onConferenceCapableChanged(this, mIsConferenceCapable);
            }
        }
    }

    /**
     * TODO(santoscordon): Needs documentation.
     */
    protected void setDestroyed() {
        // It is possible that onDestroy() will trigger the listener to remove itself which will
        // result in a concurrent modification exception. To counteract this we make a copy of the
        // listeners and iterate on that.
        for (Listener l : new ArrayList<>(mListeners)) {
            if (mListeners.contains(l)) {
                l.onDestroyed(this);
            }
        }
    }

    /**
     * Notifies this Connection and listeners that the {@link #getCallAudioState()} property
     * has a new value.
@@ -417,6 +510,11 @@ public abstract class Connection {
     */
    protected void onDisconnect() {}

    /**
     * Notifies this Connection of a request to disconnect.
     */
    protected void onSeparate() {}

    /**
     * Notifies this Connection of a request to abort.
     */
@@ -449,6 +547,28 @@ public abstract class Connection {
     */
    protected void onPostDialContinue(boolean proceed) {}

    /**
     * TODO(santoscordon): Needs documentation.
     */
    protected void onConference() {}

    /**
     * TODO(santoscordon): Needs documentation.
     */
    protected void onChildrenChanged(List<Connection> children) {}

    private void addChild(Connection connection) {
        Log.d(this, "adding child %s", connection);
        mChildConnections.add(connection);
        onChildrenChanged(mChildConnections);
    }

    private void removeChild(Connection connection) {
        Log.d(this, "removing child %s", connection);
        mChildConnections.remove(connection);
        onChildrenChanged(mChildConnections);
    }

    private void setState(int state) {
        Log.d(this, "setState: %s", stateToString(state));
        onSetState(state);
+92 −21
Original line number Diff line number Diff line
@@ -20,10 +20,15 @@ import android.net.Uri;
import android.os.Bundle;
import android.telephony.DisconnectCause;

import android.os.SystemClock;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * A {@link android.app.Service} that provides telephone connections to
@@ -32,7 +37,6 @@ import java.util.Map;
public abstract class ConnectionService extends CallService {
    // Flag controlling whether PII is emitted into the logs
    private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG);

    private static final Connection NULL_CONNECTION = new Connection() {};

    // Mappings from Connections to IDs as understood by the current CallService implementation
@@ -99,6 +103,20 @@ public abstract class ConnectionService extends CallService {
            Log.d(this, "Adapter onRingback %b", ringback);
            getAdapter().setRequestingRingback(id, ringback);
        }

        @Override
        public void onConferenceCapableChanged(Connection c, boolean isConferenceCapable) {
            String id = mIdByConnection.get(c);
            getAdapter().setCanConference(id, isConferenceCapable);
        }

        /** ${inheritDoc} */
        @Override
        public void onParentConnectionChanged(Connection c, Connection parent) {
            String id = mIdByConnection.get(c);
            String parentId = parent == null ? null : mIdByConnection.get(parent);
            getAdapter().setIsConferenced(id, parentId);
        }
    };

    @Override
@@ -110,8 +128,7 @@ public abstract class ConnectionService extends CallService {
                    @Override
                    public void onResult(Uri handle, Subscription... result) {
                        boolean isCompatible = result.length > 0;
                        Log.d(this, "adapter setIsCompatibleWith "
                                + callInfo.getId() + " " + isCompatible);
                        Log.d(this, "adapter setIsCompatibleWith ");
                        getAdapter().setIsCompatibleWith(callInfo.getId(), isCompatible);
                    }

@@ -135,7 +152,7 @@ public abstract class ConnectionService extends CallService {
                new Response<ConnectionRequest, Connection>() {
                    @Override
                    public void onResult(ConnectionRequest request, Connection... result) {
                        if (result.length != 1) {
                        if (result != null && result.length != 1) {
                            Log.d(this, "adapter handleFailedOutgoingCall %s", callInfo);
                            getAdapter().handleFailedOutgoingCall(
                                    request,
@@ -145,10 +162,10 @@ public abstract class ConnectionService extends CallService {
                                c.abort();
                            }
                        } else {
                            addConnection(callInfo.getId(), result[0]);
                            Log.d(this, "adapter handleSuccessfulOutgoingCall %s",
                                    callInfo.getId());
                            getAdapter().handleSuccessfulOutgoingCall(callInfo.getId());
                            addConnection(callInfo.getId(), result[0]);
                        }
                    }

@@ -177,7 +194,7 @@ public abstract class ConnectionService extends CallService {
                new Response<ConnectionRequest, Connection>() {
                    @Override
                    public void onResult(ConnectionRequest request, Connection... result) {
                        if (result.length != 1) {
                        if (result != null && result.length != 1) {
                            Log.d(this, "adapter handleFailedOutgoingCall %s", callId);
                            getAdapter().handleFailedOutgoingCall(
                                    request,
@@ -258,27 +275,43 @@ public abstract class ConnectionService extends CallService {

    /** @hide */
    @Override
    public final void addToConference(String conferenceCallId, List<String> callIds) {
        Log.d(this, "addToConference %s, %s", conferenceCallId, callIds);
    public final void conference(final String conferenceCallId, String callId) {
        Log.d(this, "conference %s, %s", conferenceCallId, callId);

        List<Connection> connections = new LinkedList<>();
        for (String id : callIds) {
            Connection connection = findConnectionForAction(id, "addToConference");
        Connection connection = findConnectionForAction(callId, "conference");
        if (connection == NULL_CONNECTION) {
                Log.w(this, "Connection missing in conference request %s.", id);
            Log.w(this, "Connection missing in conference request %s.", callId);
            return;
        }
            connections.add(connection);

        onCreateConferenceConnection(conferenceCallId, connection,
                new Response<String, Connection>() {
                    /** ${inheritDoc} */
                    @Override
                    public void onResult(String ignored, Connection... result) {
                        Log.d(this, "onCreateConference.Response %s", (Object[]) result);
                        if (result != null && result.length == 1) {
                            Connection conferenceConnection = result[0];
                            if (!mIdByConnection.containsKey(conferenceConnection)) {
                                Log.v(this, "sending new conference call %s", conferenceCallId);
                                getAdapter().addConferenceCall(conferenceCallId);
                                addConnection(conferenceCallId, conferenceConnection);
                            }
                        }
                    }

        // TODO(santoscordon): Find an existing conference call or create a new one. Then call
        // conferenceWith on it.
                    /** ${inheritDoc} */
                    @Override
                    public void onError(String request, int code, String reason) {
                        // no-op
                    }
                });
    }

    /** @hide */
    @Override
    public final void splitFromConference(String conferenceCallId, String callId) {
        Log.d(this, "splitFromConference(%s, %s)", conferenceCallId, callId);
    public final void splitFromConference(String callId) {
        Log.d(this, "splitFromConference(%s)", callId);

        Connection connection = findConnectionForAction(callId, "splitFromConference");
        if (connection == NULL_CONNECTION) {
@@ -308,6 +341,13 @@ public abstract class ConnectionService extends CallService {
        getAdapter().onPostDialWait(mIdByConnection.get(conn), remaining);
    }

    /**
     * Returns all connections currently associated with this connection service.
     */
    public Collection<Connection> getAllConnections() {
        return mConnectionById.values();
    }

    /**
     * Find a set of Subscriptions matching a given handle (e.g. phone number).
     *
@@ -328,6 +368,21 @@ public abstract class ConnectionService extends CallService {
            ConnectionRequest request,
            Response<ConnectionRequest, Connection> callback) {}

    /**
     * Returns a new or existing conference connection when the the user elects to convert the
     * specified connection into a conference call. The specified connection can be any connection
     * which had previously specified itself as conference-capable including both simple connections
     * and connections previously returned from this method.
     *
     * @param connection The connection from which the user opted to start a conference call.
     * @param token The token to be passed into the response callback.
     * @param callback The callback for providing the potentially-new conference connection.
     */
    public void onCreateConferenceConnection(
            String token,
            Connection connection,
            Response<String, Connection> callback) {}

    /**
     * Create a Connection to match an incoming connection notification.
     *
@@ -338,6 +393,20 @@ public abstract class ConnectionService extends CallService {
            ConnectionRequest request,
            Response<ConnectionRequest, Connection> callback) {}

    /**
     * Notifies that a connection has been added to this connection service and sent to Telecomm.
     *
     * @param connection The connection which was added.
     */
    public void onConnectionAdded(Connection connection) {}

    /**
     * Notified that a connection has been removed from this connection service.
     *
     * @param connection The connection which was removed.
     */
    public void onConnectionRemoved(Connection connection) {}

    static String toLogSafePhoneNumber(String number) {
        // For unknown number, log empty string.
        if (number == null) {
@@ -387,12 +456,14 @@ public abstract class ConnectionService extends CallService {
        mConnectionById.put(callId, connection);
        mIdByConnection.put(connection, callId);
        connection.addConnectionListener(mConnectionListener);
        onConnectionAdded(connection);
    }

    private void removeConnection(Connection connection) {
        connection.removeConnectionListener(mConnectionListener);
        mConnectionById.remove(mIdByConnection.get(connection));
        mIdByConnection.remove(connection);
        onConnectionRemoved(connection);
    }

    private Connection findConnectionForAction(String callId, String action) {
Loading