| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.services.telephony; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| import android.telecom.Conference; |
| import android.telecom.Connection; |
| import android.telecom.DisconnectCause; |
| |
| import com.android.internal.telephony.Call; |
| |
| /** |
| * Maintains a list of all the known TelephonyConnections connections and controls GSM and |
| * default IMS conference call behavior. This functionality is characterized by the support of |
| * two top-level calls, in contrast to a CDMA conference call which automatically starts a |
| * conference when there are two calls. |
| */ |
| final class TelephonyConferenceController { |
| private static final int TELEPHONY_CONFERENCE_MAX_SIZE = 5; |
| |
| private final Connection.Listener mConnectionListener = new Connection.Listener() { |
| @Override |
| public void onStateChanged(Connection c, int state) { |
| recalculate(); |
| } |
| |
| /** ${inheritDoc} */ |
| @Override |
| public void onDisconnected(Connection c, DisconnectCause disconnectCause) { |
| recalculate(); |
| } |
| |
| @Override |
| public void onDestroyed(Connection connection) { |
| remove((TelephonyConnection) connection); |
| } |
| }; |
| |
| /** The known connections. */ |
| private final List<TelephonyConnection> mTelephonyConnections = new ArrayList<>(); |
| |
| private final TelephonyConnectionService mConnectionService; |
| |
| public TelephonyConferenceController(TelephonyConnectionService connectionService) { |
| mConnectionService = connectionService; |
| } |
| |
| /** The TelephonyConference connection object. */ |
| private TelephonyConference mTelephonyConference; |
| |
| void add(TelephonyConnection connection) { |
| mTelephonyConnections.add(connection); |
| connection.addConnectionListener(mConnectionListener); |
| recalculate(); |
| } |
| |
| void remove(TelephonyConnection connection) { |
| connection.removeConnectionListener(mConnectionListener); |
| mTelephonyConnections.remove(connection); |
| recalculate(); |
| } |
| |
| private void recalculate() { |
| recalculateConferenceable(); |
| recalculateConference(); |
| } |
| |
| private boolean isFullConference(Conference conference) { |
| return conference.getConnections().size() >= TELEPHONY_CONFERENCE_MAX_SIZE; |
| } |
| |
| private boolean participatesInFullConference(Connection connection) { |
| return connection.getConference() != null && |
| isFullConference(connection.getConference()); |
| } |
| |
| /** |
| * Calculates the conference-capable state of all GSM connections in this connection service. |
| */ |
| private void recalculateConferenceable() { |
| Log.v(this, "recalculateConferenceable : %d", mTelephonyConnections.size()); |
| |
| List<Connection> activeConnections = new ArrayList<>(mTelephonyConnections.size()); |
| List<Connection> backgroundConnections = new ArrayList<>(mTelephonyConnections.size()); |
| |
| // Loop through and collect all calls which are active or holding |
| for (Connection connection : mTelephonyConnections) { |
| Log.d(this, "recalc - %s %s", connection.getState(), connection); |
| |
| if (!participatesInFullConference(connection)) { |
| switch (connection.getState()) { |
| case Connection.STATE_ACTIVE: |
| activeConnections.add(connection); |
| continue; |
| case Connection.STATE_HOLDING: |
| backgroundConnections.add(connection); |
| continue; |
| default: |
| break; |
| } |
| } |
| |
| connection.setConferenceableConnections(Collections.<Connection>emptyList()); |
| } |
| |
| Log.v(this, "active: %d, holding: %d", |
| activeConnections.size(), backgroundConnections.size()); |
| |
| // Go through all the active connections and set the background connections as |
| // conferenceable. |
| for (Connection connection : activeConnections) { |
| connection.setConferenceableConnections(backgroundConnections); |
| } |
| |
| // Go through all the background connections and set the active connections as |
| // conferenceable. |
| for (Connection connection : backgroundConnections) { |
| connection.setConferenceableConnections(activeConnections); |
| } |
| |
| // Set the conference as conferenceable with all the connections |
| if (mTelephonyConference != null && !isFullConference(mTelephonyConference)) { |
| List<Connection> nonConferencedConnections = |
| new ArrayList<>(mTelephonyConnections.size()); |
| for (TelephonyConnection c : mTelephonyConnections) { |
| if (c.getConference() == null) { |
| nonConferencedConnections.add(c); |
| } |
| } |
| mTelephonyConference.setConferenceableConnections(nonConferencedConnections); |
| } |
| |
| // TODO: Do not allow conferencing of already conferenced connections. |
| } |
| |
| private void recalculateConference() { |
| Set<TelephonyConnection> conferencedConnections = new HashSet<>(); |
| |
| for (TelephonyConnection connection : mTelephonyConnections) { |
| com.android.internal.telephony.Connection radioConnection = |
| connection.getOriginalConnection(); |
| |
| if (radioConnection != null) { |
| Call.State state = radioConnection.getState(); |
| Call call = radioConnection.getCall(); |
| if ((state == Call.State.ACTIVE || state == Call.State.HOLDING) && |
| (call != null && call.isMultiparty())) { |
| conferencedConnections.add(connection); |
| } |
| } |
| } |
| |
| Log.d(this, "Recalculate conference calls %s %s.", |
| mTelephonyConference, conferencedConnections); |
| |
| if (conferencedConnections.size() < 2) { |
| Log.d(this, "less than two conference calls!"); |
| // No more connections are conferenced, destroy any existing conference. |
| if (mTelephonyConference != null) { |
| Log.d(this, "with a conference to destroy!"); |
| mTelephonyConference.destroy(); |
| mTelephonyConference = null; |
| } |
| } else { |
| if (mTelephonyConference != null) { |
| List<Connection> existingConnections = mTelephonyConference.getConnections(); |
| // Remove any that no longer exist |
| for (Connection connection : existingConnections) { |
| if (!conferencedConnections.contains(connection)) { |
| mTelephonyConference.removeConnection(connection); |
| } |
| } |
| |
| // Add any new ones |
| for (Connection connection : conferencedConnections) { |
| if (!existingConnections.contains(connection)) { |
| mTelephonyConference.addConnection(connection); |
| } |
| } |
| } else { |
| mTelephonyConference = new TelephonyConference(null); |
| for (Connection connection : conferencedConnections) { |
| Log.d(this, "Adding a connection to a conference call: %s %s", |
| mTelephonyConference, connection); |
| mTelephonyConference.addConnection(connection); |
| } |
| mConnectionService.addConference(mTelephonyConference); |
| } |
| |
| // Set the conference state to the same state as its child connections. |
| Connection conferencedConnection = mTelephonyConference.getConnections().get(0); |
| switch (conferencedConnection.getState()) { |
| case Connection.STATE_ACTIVE: |
| mTelephonyConference.setActive(); |
| break; |
| case Connection.STATE_HOLDING: |
| mTelephonyConference.setOnHold(); |
| break; |
| } |
| } |
| } |
| } |