• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.services.telephony;
18 
19 import android.telecom.Conference;
20 import android.telecom.Connection;
21 import android.telecom.DisconnectCause;
22 import android.telecom.PhoneAccountHandle;
23 
24 import com.android.internal.telephony.Call;
25 import com.android.phone.PhoneUtils;
26 
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.HashSet;
31 import java.util.List;
32 import java.util.Set;
33 import java.util.stream.Collectors;
34 
35 /**
36  * Maintains a list of all the known TelephonyConnections connections and controls GSM and
37  * default IMS conference call behavior. This functionality is characterized by the support of
38  * two top-level calls, in contrast to a CDMA conference call which automatically starts a
39  * conference when there are two calls.
40  */
41 final class TelephonyConferenceController {
42     private static final int TELEPHONY_CONFERENCE_MAX_SIZE = 5;
43 
44     private final Connection.Listener mConnectionListener = new Connection.Listener() {
45         @Override
46         public void onStateChanged(Connection c, int state) {
47             Log.v(this, "onStateChange triggered in Conf Controller : connection = "+ c
48                  + " state = " + state);
49             recalculate();
50         }
51 
52         /** ${inheritDoc} */
53         @Override
54         public void onDisconnected(Connection c, DisconnectCause disconnectCause) {
55             recalculate();
56         }
57 
58         @Override
59         public void onDestroyed(Connection connection) {
60             remove(connection);
61         }
62     };
63 
64     /** The known connections. */
65     private final List<TelephonyConnection> mTelephonyConnections = new ArrayList<>();
66 
67     private final TelephonyConnectionServiceProxy mConnectionService;
68     private boolean mTriggerRecalculate = false;
69 
TelephonyConferenceController(TelephonyConnectionServiceProxy connectionService)70     public TelephonyConferenceController(TelephonyConnectionServiceProxy connectionService) {
71         mConnectionService = connectionService;
72     }
73     /** The TelephonyConference connection object. */
74     private TelephonyConference mTelephonyConference;
75 
shouldRecalculate()76     boolean shouldRecalculate() {
77         Log.d(this, "shouldRecalculate is " + mTriggerRecalculate);
78         return mTriggerRecalculate;
79     }
80 
add(TelephonyConnection connection)81     void add(TelephonyConnection connection) {
82         if (mTelephonyConnections.contains(connection)) {
83             // Adding a duplicate realistically shouldn't happen.
84             Log.w(this, "add - connection already tracked; connection=%s", connection);
85             return;
86         }
87         mTelephonyConnections.add(connection);
88         connection.addConnectionListener(mConnectionListener);
89         recalculate();
90     }
91 
remove(Connection connection)92     void remove(Connection connection) {
93         if (!mTelephonyConnections.contains(connection)) {
94             // Debug only since TelephonyConnectionService tries to clean up the connections tracked
95             // when the original connection changes.  It does this proactively.
96             Log.d(this, "remove - connection not tracked; connection=%s", connection);
97             return;
98         }
99         connection.removeConnectionListener(mConnectionListener);
100         mTelephonyConnections.remove(connection);
101         recalculate();
102     }
103 
recalculate()104     void recalculate() {
105         recalculateConference();
106         recalculateConferenceable();
107     }
108 
isFullConference(Conference conference)109     private boolean isFullConference(Conference conference) {
110         return conference.getConnections().size() >= TELEPHONY_CONFERENCE_MAX_SIZE;
111     }
112 
participatesInFullConference(Connection connection)113     private boolean participatesInFullConference(Connection connection) {
114         return connection.getConference() != null &&
115                 isFullConference(connection.getConference());
116     }
117 
118     /**
119      * Calculates the conference-capable state of all GSM connections in this connection service.
120      */
recalculateConferenceable()121     private void recalculateConferenceable() {
122         Log.v(this, "recalculateConferenceable : %d", mTelephonyConnections.size());
123         HashSet<Connection> conferenceableConnections = new HashSet<>(mTelephonyConnections.size());
124 
125         // Loop through and collect all calls which are active or holding
126         for (TelephonyConnection connection : mTelephonyConnections) {
127             Log.d(this, "recalc - %s %s supportsConf? %s", connection.getState(), connection,
128                     connection.isConferenceSupported());
129 
130             if (connection.isConferenceSupported() && !participatesInFullConference(connection)) {
131                 switch (connection.getState()) {
132                     case Connection.STATE_ACTIVE:
133                         //fall through
134                     case Connection.STATE_HOLDING:
135                         conferenceableConnections.add(connection);
136                         continue;
137                     default:
138                         break;
139                 }
140             }
141 
142             connection.setConferenceableConnections(Collections.<Connection>emptyList());
143         }
144 
145         Log.v(this, "conferenceable: " + conferenceableConnections.size());
146 
147         // Go through all the conferenceable connections and add all other conferenceable
148         // connections that is not the connection itself
149         for (Connection c : conferenceableConnections) {
150             List<Connection> connections = conferenceableConnections
151                     .stream()
152                     // Filter out this connection from the list of connections
153                     .filter(connection -> c != connection)
154                     .collect(Collectors.toList());
155             c.setConferenceableConnections(connections);
156         }
157 
158         // Set the conference as conferenceable with all of the connections that are not in the
159         // conference.
160         if (mTelephonyConference != null) {
161             if (!isFullConference(mTelephonyConference)) {
162                 List<Connection> nonConferencedConnections = mTelephonyConnections
163                         .stream()
164                         // Only retrieve Connections that are not in a conference (but support
165                         // conferences).
166                         .filter(c -> c.isConferenceSupported() && c.getConference() == null)
167                         .collect(Collectors.toList());
168                 mTelephonyConference.setConferenceableConnections(nonConferencedConnections);
169             } else {
170                 Log.d(this, "cannot merge anymore due it is full");
171                 mTelephonyConference
172                         .setConferenceableConnections(Collections.<Connection>emptyList());
173             }
174         }
175         // TODO: Do not allow conferencing of already conferenced connections.
176     }
177 
recalculateConference()178     private void recalculateConference() {
179         Set<Connection> conferencedConnections = new HashSet<>();
180         int numGsmConnections = 0;
181 
182         for (TelephonyConnection connection : mTelephonyConnections) {
183             com.android.internal.telephony.Connection radioConnection =
184                 connection.getOriginalConnection();
185             if (radioConnection != null) {
186                 Call.State state = radioConnection.getState();
187                 Call call = radioConnection.getCall();
188                 if ((state == Call.State.ACTIVE || state == Call.State.HOLDING) &&
189                         (call != null && call.isMultiparty())) {
190                     numGsmConnections++;
191                     conferencedConnections.add(connection);
192                 }
193             }
194         }
195 
196         Log.d(this, "Recalculate conference calls %s %s.",
197                 mTelephonyConference, conferencedConnections);
198 
199         // Check if all conferenced connections are in Connection Service
200         boolean allConnInService = true;
201         Collection<Connection> allConnections = mConnectionService.getAllConnections();
202         for (Connection connection : conferencedConnections) {
203             Log.v (this, "Finding connection in Connection Service for " + connection);
204             if (!allConnections.contains(connection)) {
205                 allConnInService = false;
206                 Log.v(this, "Finding connection in Connection Service Failed");
207                 break;
208             }
209         }
210 
211         Log.d(this, "Is there a match for all connections in connection service " +
212             allConnInService);
213 
214         // If this is a GSM conference and the number of connections drops below 2, we will
215         // terminate the conference.
216         if (numGsmConnections < 2) {
217             Log.d(this, "not enough connections to be a conference!");
218 
219             // No more connections are conferenced, destroy any existing conference.
220             if (mTelephonyConference != null) {
221                 Log.d(this, "with a conference to destroy!");
222                 mTelephonyConference.destroy();
223                 mTelephonyConference = null;
224             }
225         } else {
226             if (mTelephonyConference != null) {
227                 List<Connection> existingConnections = mTelephonyConference.getConnections();
228                 // Remove any that no longer exist
229                 for (Connection connection : existingConnections) {
230                     if (connection instanceof TelephonyConnection &&
231                             !conferencedConnections.contains(connection)) {
232                         mTelephonyConference.removeConnection(connection);
233                     }
234                 }
235                 if (allConnInService) {
236                     mTriggerRecalculate = false;
237                     // Add any new ones
238                     for (Connection connection : conferencedConnections) {
239                         if (!existingConnections.contains(connection)) {
240                             mTelephonyConference.addConnection(connection);
241                         }
242                     }
243                 } else {
244                     Log.d(this, "Trigger recalculate later");
245                     mTriggerRecalculate = true;
246                 }
247             } else {
248                 if (allConnInService) {
249                     mTriggerRecalculate = false;
250 
251                     // Get PhoneAccount from one of the conferenced connections and use it to set
252                     // the phone account on the conference.
253                     PhoneAccountHandle phoneAccountHandle = null;
254                     if (!conferencedConnections.isEmpty()) {
255                         TelephonyConnection telephonyConnection =
256                                 (TelephonyConnection) conferencedConnections.iterator().next();
257                         phoneAccountHandle = PhoneUtils.makePstnPhoneAccountHandle(
258                                 telephonyConnection.getPhone());
259                     }
260 
261                     mTelephonyConference = new TelephonyConference(phoneAccountHandle);
262                     for (Connection connection : conferencedConnections) {
263                         Log.d(this, "Adding a connection to a conference call: %s %s",
264                                 mTelephonyConference, connection);
265                         mTelephonyConference.addConnection(connection);
266                     }
267                     mTelephonyConference.updateCallRadioTechAfterCreation();
268                     mConnectionService.addConference(mTelephonyConference);
269                 } else {
270                     Log.d(this, "Trigger recalculate later");
271                     mTriggerRecalculate = true;
272                 }
273             }
274             if (mTelephonyConference != null) {
275                 Connection conferencedConnection = mTelephonyConference.getPrimaryConnection();
276                 Log.v(this, "Primary Conferenced connection is " + conferencedConnection);
277                 if (conferencedConnection != null) {
278                     switch (conferencedConnection.getState()) {
279                         case Connection.STATE_ACTIVE:
280                             Log.v(this, "Setting conference to active");
281                             mTelephonyConference.setActive();
282                             break;
283                         case Connection.STATE_HOLDING:
284                             Log.v(this, "Setting conference to hold");
285                             mTelephonyConference.setOnHold();
286                             break;
287                     }
288                 }
289             }
290         }
291     }
292 }
293