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