CdmaConferenceController.java revision d9d19ecdfb9018eef212b82320a25216543438de
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.services.telephony;
18
19import android.os.Handler;
20import android.telecom.Connection;
21import android.telecom.DisconnectCause;
22import android.telecom.PhoneCapabilities;
23
24import java.util.ArrayList;
25import java.util.List;
26
27/**
28 * Manages CDMA conference calls. CDMA conference calls are much more limited than GSM conference
29 * calls. Two main points of difference:
30 * 1) Users cannot manage individual calls within a conference
31 * 2) Whether a conference call starts off as a conference or as two distinct calls is a matter of
32 *    physical location (some antennas are different than others). Worst still, there's no
33 *    indication given to us as to what state they are in.
34 *
35 * To make life easier on the user we do the following: Whenever there exist 2 or more calls, we
36 * say that we are in a conference call with {@link Connection#CAPABILITY_GENERIC_CONFERENCE}.
37 * Generic indicates that this is a simple conference that doesn't support conference management.
38 * The conference call will also support "MERGE" to begin with and stop supporting it the first time
39 * we are asked to actually execute a merge. I emphasize when "we are asked" because we get no
40 * indication whether the merge succeeds from CDMA, we just assume it does. Thats the best we
41 * can do. Also, we do not kill a conference call once it is created unless all underlying
42 * connections also go away.
43 *
44 * Outgoing CDMA calls made while another call exists would normally trigger a conference to be
45 * created. To avoid this and make it seem like there is a "dialing" state, we fake it and prevent
46 * the conference from being created for 3 seconds. This is a more pleasant experience for the user.
47 */
48final class CdmaConferenceController {
49    private final Connection.Listener mConnectionListener = new Connection.Listener() {
50                @Override
51                public void onStateChanged(Connection c, int state) {
52                    recalculateConference();
53                }
54
55                @Override
56                public void onDisconnected(Connection c, DisconnectCause disconnectCause) {
57                    recalculateConference();
58                }
59
60                @Override
61                public void onDestroyed(Connection c) {
62                    remove((CdmaConnection) c);
63                }
64            };
65
66    private static final int ADD_OUTGOING_CONNECTION_DELAY_MILLIS = 6000;
67
68    /** The known CDMA connections. */
69    private final List<CdmaConnection> mCdmaConnections = new ArrayList<>();
70
71    /**
72     * Newly added connections.  We keep track of newly added outgoing connections because we do not
73     * create a conference until a second outgoing call has existing for
74     * {@link #ADD_OUTGOING_CONNECTION_DELAY_MILLIS} milliseconds.  This allows the UI to show the
75     * call as "Dialing" for a certain amount of seconds.
76     */
77    private final List<CdmaConnection> mPendingOutgoingConnections = new ArrayList<>();
78
79    private final TelephonyConnectionService mConnectionService;
80
81    private final Handler mHandler = new Handler();
82
83    public CdmaConferenceController(TelephonyConnectionService connectionService) {
84        mConnectionService = connectionService;
85    }
86
87    /** The CDMA conference connection object. */
88    private CdmaConference mConference;
89
90    void add(final CdmaConnection connection) {
91        if (!mCdmaConnections.isEmpty() && connection.isOutgoing()) {
92            // There already exists a connection, so this will probably result in a conference once
93            // it is added. For outgoing connections which are added while another connection
94            // exists, we mark them as "dialing" for a set amount of time to give the user time to
95            // see their new call as "Dialing" before it turns into a conference call.
96            // During that time, we also mark the other calls as "held" or else it can cause issues
97            // due to having an ACTIVE and a DIALING call simultaneously.
98            connection.forceAsDialing(true);
99            final List<CdmaConnection> connectionsToReset =
100                    new ArrayList<>(mCdmaConnections.size());
101            for (CdmaConnection current : mCdmaConnections) {
102                if (current.setHoldingForConference()) {
103                    connectionsToReset.add(current);
104                }
105            }
106            mHandler.postDelayed(new Runnable() {
107                @Override
108                public void run() {
109                    connection.forceAsDialing(false);
110                    for (CdmaConnection current : connectionsToReset) {
111                        current.resetStateForConference();
112                    }
113                    addInternal(connection);
114                }
115            }, ADD_OUTGOING_CONNECTION_DELAY_MILLIS);
116        } else {
117            // This is the first connection, or it is incoming, so let it flow through.
118            addInternal(connection);
119        }
120    }
121
122    private void addInternal(CdmaConnection connection) {
123        mCdmaConnections.add(connection);
124        connection.addConnectionListener(mConnectionListener);
125        recalculateConference();
126    }
127
128    private void remove(CdmaConnection connection) {
129        connection.removeConnectionListener(mConnectionListener);
130        mCdmaConnections.remove(connection);
131        recalculateConference();
132    }
133
134    private void recalculateConference() {
135        List<CdmaConnection> conferenceConnections = new ArrayList<>(mCdmaConnections.size());
136        for (CdmaConnection connection : mCdmaConnections) {
137            // We do not include call-waiting calls in conferences.
138            if (!connection.isCallWaiting() &&
139                    connection.getState() != Connection.STATE_DISCONNECTED) {
140                conferenceConnections.add(connection);
141            }
142        }
143
144        Log.d(this, "recalculating conference calls %d", conferenceConnections.size());
145        if (conferenceConnections.size() >= 2) {
146            boolean isNewlyCreated = false;
147
148            // There are two or more CDMA connections. Do the following:
149            // 1) Create a new conference connection if it doesn't exist.
150            if (mConference == null) {
151                Log.i(this, "Creating new Cdma conference call");
152                CdmaConnection newConnection = mCdmaConnections.get(mCdmaConnections.size() - 1);
153                if (newConnection.isOutgoing()) {
154                    // Only an outgoing call can be merged with an ongoing call.
155                    mConference = new CdmaConference(null, PhoneCapabilities.MERGE_CONFERENCE);
156                } else {
157                    // If the most recently added connection was an incoming call, enable
158                    // swap instead of merge.
159                    mConference = new CdmaConference(null, PhoneCapabilities.SWAP_CONFERENCE);
160                }
161                isNewlyCreated = true;
162            }
163
164            // 2) Add any new connections to the conference
165            List<Connection> existingChildConnections =
166                    new ArrayList<>(mConference.getConnections());
167            for (CdmaConnection connection : conferenceConnections) {
168                if (!existingChildConnections.contains(connection)) {
169                    Log.i(this, "Adding connection to conference call: %s", connection);
170                    mConference.addConnection(connection);
171                }
172                existingChildConnections.remove(connection);
173            }
174
175            // 3) Remove any lingering old/disconnected/destroyed connections
176            for (Connection oldConnection : existingChildConnections) {
177                mConference.removeConnection(oldConnection);
178                Log.i(this, "Removing connection from conference call: %s", oldConnection);
179            }
180
181            // 4) Add the conference to the connection service if it is new.
182            if (isNewlyCreated) {
183                Log.d(this, "Adding the conference call");
184                mConnectionService.addConference(mConference);
185            }
186        } else if (conferenceConnections.isEmpty()) {
187            // There are no more connection so if we still have a conference, lets remove it.
188            if (mConference != null) {
189                Log.i(this, "Destroying the CDMA conference connection.");
190                mConference.destroy();
191            }
192        }
193    }
194}
195