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 java.util.ArrayList; 20import java.util.Collections; 21import java.util.HashSet; 22import java.util.List; 23import java.util.Set; 24 25import android.telecom.Conference; 26import android.telecom.Connection; 27import android.telecom.DisconnectCause; 28 29import com.android.internal.telephony.Call; 30 31/** 32 * Maintains a list of all the known GSM connections and implements GSM-specific conference 33 * call functionality. 34 */ 35final class GsmConferenceController { 36 private static final int GSM_CONFERENCE_MAX_SIZE = 5; 37 38 private final Connection.Listener mConnectionListener = new Connection.Listener() { 39 @Override 40 public void onStateChanged(Connection c, int state) { 41 recalculate(); 42 } 43 44 /** ${inheritDoc} */ 45 @Override 46 public void onDisconnected(Connection c, DisconnectCause disconnectCause) { 47 recalculate(); 48 } 49 50 @Override 51 public void onDestroyed(Connection connection) { 52 remove((GsmConnection) connection); 53 } 54 }; 55 56 /** The known GSM connections. */ 57 private final List<GsmConnection> mGsmConnections = new ArrayList<>(); 58 59 private final TelephonyConnectionService mConnectionService; 60 61 public GsmConferenceController(TelephonyConnectionService connectionService) { 62 mConnectionService = connectionService; 63 } 64 65 /** The GSM conference connection object. */ 66 private Conference mGsmConference; 67 68 void add(GsmConnection connection) { 69 mGsmConnections.add(connection); 70 connection.addConnectionListener(mConnectionListener); 71 recalculate(); 72 } 73 74 void remove(GsmConnection connection) { 75 connection.removeConnectionListener(mConnectionListener); 76 mGsmConnections.remove(connection); 77 recalculate(); 78 } 79 80 private void recalculate() { 81 recalculateConferenceable(); 82 recalculateConference(); 83 } 84 85 private boolean isFullConference(Conference conference) { 86 return conference.getConnections().size() >= GSM_CONFERENCE_MAX_SIZE; 87 } 88 89 private boolean participatesInFullConference(Connection connection) { 90 return connection.getConference() != null && 91 isFullConference(connection.getConference()); 92 } 93 94 /** 95 * Calculates the conference-capable state of all GSM connections in this connection service. 96 */ 97 private void recalculateConferenceable() { 98 Log.v(this, "recalculateConferenceable : %d", mGsmConnections.size()); 99 100 List<Connection> activeConnections = new ArrayList<>(mGsmConnections.size()); 101 List<Connection> backgroundConnections = new ArrayList<>(mGsmConnections.size()); 102 103 // Loop through and collect all calls which are active or holding 104 for (GsmConnection connection : mGsmConnections) { 105 Log.d(this, "recalc - %s %s", connection.getState(), connection); 106 107 if (!participatesInFullConference(connection)) { 108 switch (connection.getState()) { 109 case Connection.STATE_ACTIVE: 110 activeConnections.add(connection); 111 continue; 112 case Connection.STATE_HOLDING: 113 backgroundConnections.add(connection); 114 continue; 115 default: 116 break; 117 } 118 } 119 120 connection.setConferenceableConnections(Collections.<Connection>emptyList()); 121 } 122 123 Log.v(this, "active: %d, holding: %d", 124 activeConnections.size(), backgroundConnections.size()); 125 126 // Go through all the active connections and set the background connections as 127 // conferenceable. 128 for (Connection connection : activeConnections) { 129 connection.setConferenceableConnections(backgroundConnections); 130 } 131 132 // Go through all the background connections and set the active connections as 133 // conferenceable. 134 for (Connection connection : backgroundConnections) { 135 connection.setConferenceableConnections(activeConnections); 136 } 137 138 // Set the conference as conferenceable with all the connections 139 if (mGsmConference != null && !isFullConference(mGsmConference)) { 140 List<Connection> nonConferencedConnections = new ArrayList<>(mGsmConnections.size()); 141 for (GsmConnection c : mGsmConnections) { 142 if (c.getConference() == null) { 143 nonConferencedConnections.add(c); 144 } 145 } 146 mGsmConference.setConferenceableConnections(nonConferencedConnections); 147 } 148 149 // TODO: Do not allow conferencing of already conferenced connections. 150 } 151 152 private void recalculateConference() { 153 Set<GsmConnection> conferencedConnections = new HashSet<>(); 154 155 for (GsmConnection connection : mGsmConnections) { 156 com.android.internal.telephony.Connection radioConnection = 157 connection.getOriginalConnection(); 158 159 if (radioConnection != null) { 160 Call.State state = radioConnection.getState(); 161 Call call = radioConnection.getCall(); 162 if ((state == Call.State.ACTIVE || state == Call.State.HOLDING) && 163 (call != null && call.isMultiparty())) { 164 conferencedConnections.add(connection); 165 } 166 } 167 } 168 169 Log.d(this, "Recalculate conference calls %s %s.", 170 mGsmConference, conferencedConnections); 171 172 if (conferencedConnections.size() < 2) { 173 Log.d(this, "less than two conference calls!"); 174 // No more connections are conferenced, destroy any existing conference. 175 if (mGsmConference != null) { 176 Log.d(this, "with a conference to destroy!"); 177 mGsmConference.destroy(); 178 mGsmConference = null; 179 } 180 } else { 181 if (mGsmConference != null) { 182 List<Connection> existingConnections = mGsmConference.getConnections(); 183 // Remove any that no longer exist 184 for (Connection connection : existingConnections) { 185 if (!conferencedConnections.contains(connection)) { 186 mGsmConference.removeConnection(connection); 187 } 188 } 189 190 // Add any new ones 191 for (Connection connection : conferencedConnections) { 192 if (!existingConnections.contains(connection)) { 193 mGsmConference.addConnection(connection); 194 } 195 } 196 } else { 197 mGsmConference = new GsmConference(null); 198 for (Connection connection : conferencedConnections) { 199 Log.d(this, "Adding a connection to a conference call: %s %s", 200 mGsmConference, connection); 201 mGsmConference.addConnection(connection); 202 } 203 mConnectionService.addConference(mGsmConference); 204 } 205 206 // Set the conference state to the same state as its child connections. 207 Connection conferencedConnection = mGsmConference.getConnections().get(0); 208 switch (conferencedConnection.getState()) { 209 case Connection.STATE_ACTIVE: 210 mGsmConference.setActive(); 211 break; 212 case Connection.STATE_HOLDING: 213 mGsmConference.setOnHold(); 214 break; 215 } 216 } 217 } 218} 219