summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorTyler Gunn <tgunn@google.com>2014-11-20 03:40:29 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2014-11-20 03:40:30 +0000
commit9c1749ebd4bef58a54490293780c214dab818a87 (patch)
tree685c5a6cd4b59ef88ce6c0c64860923eccb1a2e8 /src
parentc30aaabf2a498c4fe11243b25a1d0eb1c833f4d5 (diff)
parent2c248f38ac4c7fefc50fb9c595b6cbf2798c0632 (diff)
downloadandroid_packages_services_Telephony-9c1749ebd4bef58a54490293780c214dab818a87.tar.gz
android_packages_services_Telephony-9c1749ebd4bef58a54490293780c214dab818a87.tar.bz2
android_packages_services_Telephony-9c1749ebd4bef58a54490293780c214dab818a87.zip
Merge "Create IMSConference to represent IMS conference calls." into lmp-mr1-dev
Diffstat (limited to 'src')
-rw-r--r--src/com/android/services/telephony/ConferenceParticipantConnection.java41
-rw-r--r--src/com/android/services/telephony/ImsConference.java504
-rw-r--r--src/com/android/services/telephony/ImsConferenceController.java274
-rw-r--r--src/com/android/services/telephony/TelephonyConference.java65
-rw-r--r--src/com/android/services/telephony/TelephonyConferenceController.java174
-rw-r--r--src/com/android/services/telephony/TelephonyConnection.java62
-rw-r--r--src/com/android/services/telephony/TelephonyConnectionService.java4
7 files changed, 920 insertions, 204 deletions
diff --git a/src/com/android/services/telephony/ConferenceParticipantConnection.java b/src/com/android/services/telephony/ConferenceParticipantConnection.java
index e74863481..49307ba1e 100644
--- a/src/com/android/services/telephony/ConferenceParticipantConnection.java
+++ b/src/com/android/services/telephony/ConferenceParticipantConnection.java
@@ -19,8 +19,8 @@ package com.android.services.telephony;
import com.android.internal.telephony.PhoneConstants;
import android.net.Uri;
-import android.telecom.ConferenceParticipant;
import android.telecom.Connection;
+import android.telecom.ConferenceParticipant;
import android.telecom.DisconnectCause;
import android.telecom.PhoneCapabilities;
@@ -37,7 +37,7 @@ public class ConferenceParticipantConnection extends Connection {
/**
* The connection which owns this participant.
*/
- private final Connection mParentConnection;
+ private final com.android.internal.telephony.Connection mParentConnection;
/**
* Creates a new instance.
@@ -45,7 +45,8 @@ public class ConferenceParticipantConnection extends Connection {
* @param participant The conference participant to create the instance for.
*/
public ConferenceParticipantConnection(
- Connection parentConnection, ConferenceParticipant participant) {
+ com.android.internal.telephony.Connection parentConnection,
+ ConferenceParticipant participant) {
mParentConnection = parentConnection;
setAddress(participant.getHandle(), PhoneConstants.PRESENTATION_ALLOWED);
@@ -61,6 +62,8 @@ public class ConferenceParticipantConnection extends Connection {
* @param newState The new state.
*/
public void updateState(int newState) {
+ Log.v(this, "updateState endPoint: %s state: %s", Log.pii(mEndpoint),
+ Connection.stateToString(newState));
if (newState == getState()) {
return;
}
@@ -100,12 +103,19 @@ public class ConferenceParticipantConnection extends Connection {
*/
@Override
public void onDisconnect() {
- Log.v(this, "onDisconnect");
-
mParentConnection.onDisconnectConferenceParticipant(mEndpoint);
}
/**
+ * Retrieves the endpoint for this connection.
+ *
+ * @return The endpoint.
+ */
+ public Uri getEndpoint() {
+ return mEndpoint;
+ }
+
+ /**
* Configures the {@link android.telecom.PhoneCapabilities} applicable to this connection. A
* conference participant can only be disconnected from a conference since there is not
* actual connection to the participant which could be split from the conference.
@@ -114,4 +124,25 @@ public class ConferenceParticipantConnection extends Connection {
int capabilities = PhoneCapabilities.DISCONNECT_FROM_CONFERENCE;
setCallCapabilities(capabilities);
}
+
+ /**
+ * Builds a string representation of this conference participant connection.
+ *
+ * @return String representation of connection.
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[ConferenceParticipantConnection objId:");
+ sb.append(System.identityHashCode(this));
+ sb.append(" endPoint:");
+ sb.append(Log.pii(mEndpoint));
+ sb.append(" parentConnection:");
+ sb.append(Log.pii(mParentConnection.getAddress()));
+ sb.append(" state:");
+ sb.append(Connection.stateToString(getState()));
+ sb.append("]");
+
+ return sb.toString();
+ }
}
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
new file mode 100644
index 000000000..0d235c3dc
--- /dev/null
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -0,0 +1,504 @@
+/*
+ * 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 com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.Phone;
+
+import android.net.Uri;
+import android.telecom.Connection;
+import android.telecom.Conference;
+import android.telecom.ConferenceParticipant;
+import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.PhoneCapabilities;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Represents an IMS conference call.
+ * <P>
+ * An IMS conference call consists of a conference host connection and potentially a list of
+ * conference participants. The conference host connection represents the radio connection to the
+ * IMS conference server. Since it is not a connection to any one individual, it is not represented
+ * in Telecom/InCall as a call. The conference participant information is received via the host
+ * connection via a conference event package. Conference participant connections do not represent
+ * actual radio connections to the participants; they act as a virtual representation of the
+ * participant, keyed by a unique endpoint {@link android.net.Uri}.
+ * <p>
+ * The {@link ImsConference} listens for conference event package data received via the host
+ * connection and is responsible for managing the conference participant connections which represent
+ * the participants.
+ */
+public class ImsConference extends Conference {
+
+ /**
+ * TelephonyConnection class used to represent the connection to the conference server.
+ */
+ private class ConferenceHostConnection extends TelephonyConnection {
+ protected ConferenceHostConnection(
+ com.android.internal.telephony.Connection originalConnection) {
+ super(originalConnection);
+ }
+ }
+
+ /**
+ * Listener used to respond to changes to conference participants. At the conference level we
+ * are most concerned with handling destruction of a conference participant.
+ */
+ private final Connection.Listener mParticipantListener = new Connection.Listener() {
+ /**
+ * Participant has been destroyed. Remove it from the conference.
+ *
+ * @param connection The participant which was destroyed.
+ */
+ @Override
+ public void onDestroyed(Connection connection) {
+ ConferenceParticipantConnection participant =
+ (ConferenceParticipantConnection) connection;
+ removeConferenceParticipant(participant);
+ updateManageConference();
+ }
+
+ };
+
+ /**
+ * Listener used to respond to changes to the connection to the IMS conference server.
+ */
+ private final android.telecom.Connection.Listener mConferenceHostListener =
+ new android.telecom.Connection.Listener() {
+
+ /**
+ * Updates the state of the conference based on the new state of the host.
+ *
+ * @param c The host connection.
+ * @param state The new state
+ */
+ @Override
+ public void onStateChanged(android.telecom.Connection c, int state) {
+ setState(state);
+ }
+
+ /**
+ * Disconnects the conference when its host connection disconnects.
+ *
+ * @param c The host connection.
+ * @param disconnectCause The host connection disconnect cause.
+ */
+ @Override
+ public void onDisconnected(android.telecom.Connection c, DisconnectCause disconnectCause) {
+ setDisconnected(disconnectCause);
+ }
+
+ /**
+ * Handles destruction of the host connection; once the host connection has been
+ * destroyed, cleans up the conference participant connection.
+ *
+ * @param connection The host connection.
+ */
+ @Override
+ public void onDestroyed(android.telecom.Connection connection) {
+ disconnectConferenceParticipants();
+ }
+
+ /**
+ * Handles changes to conference participant data as reported by the conference host
+ * connection.
+ *
+ * @param c The connection.
+ * @param participants The participant information.
+ */
+ @Override
+ public void onConferenceParticipantsChanged(android.telecom.Connection c,
+ List<ConferenceParticipant> participants) {
+
+ if (c == null) {
+ return;
+ }
+ Log.v(this, "onConferenceParticipantsChanged: %d participants", participants.size());
+ TelephonyConnection telephonyConnection = (TelephonyConnection) c;
+ handleConferenceParticipantsUpdate(telephonyConnection, participants);
+ }
+ };
+
+ /**
+ * The telephony connection service; used to add new participant connections to Telecom.
+ */
+ private TelephonyConnectionService mTelephonyConnectionService;
+
+ /**
+ * The connection to the conference server which is hosting the conference.
+ */
+ private TelephonyConnection mConferenceHost;
+
+ /**
+ * The known conference participant connections. The HashMap is keyed by endpoint Uri.
+ * A {@link ConcurrentHashMap} is used as there is a possibility for radio events impacting the
+ * available participants to occur at the same time as an access via the connection service.
+ */
+ private final ConcurrentHashMap<Uri, ConferenceParticipantConnection>
+ mConferenceParticipantConnections =
+ new ConcurrentHashMap<Uri, ConferenceParticipantConnection>(8, 0.9f, 1);
+
+ /**
+ * Initializes a new {@link ImsConference}.
+ *
+ * @param telephonyConnectionService The connection service responsible for adding new
+ * conferene participants.
+ * @param conferenceHost The IMS radio connection hosting the conference.
+ */
+ public ImsConference(TelephonyConnectionService telephonyConnectionService,
+ com.android.internal.telephony.Connection conferenceHost) {
+
+ super(null);
+ mTelephonyConnectionService = telephonyConnectionService;
+ setConferenceHost(conferenceHost);
+ setCapabilities(
+ PhoneCapabilities.SUPPORT_HOLD |
+ PhoneCapabilities.HOLD |
+ PhoneCapabilities.MUTE
+ );
+ }
+
+ /**
+ * Not used by the IMS conference controller.
+ *
+ * @return {@code Null}.
+ */
+ @Override
+ public android.telecom.Connection getPrimaryConnection() {
+ return null;
+ }
+
+ /**
+ * Invoked when the Conference and all its {@link Connection}s should be disconnected.
+ * <p>
+ * Hangs up the call via the conference host connection. When the host connection has been
+ * successfully disconnected, the {@link #mConferenceHostListener} listener receives an
+ * {@code onDestroyed} event, which triggers the conference participant connections to be
+ * disconnected.
+ */
+ @Override
+ public void onDisconnect() {
+ Log.v(this, "onDisconnect: hanging up conference host.");
+
+ Call call = mConferenceHost.getCall();
+ if (call != null) {
+ try {
+ call.hangup();
+ } catch (CallStateException e) {
+ Log.e(this, e, "Exception thrown trying to hangup conference");
+ }
+ }
+ }
+
+ /**
+ * Invoked when the specified {@link android.telecom.Connection} should be separated from the
+ * conference call.
+ * <p>
+ * IMS does not support separating connections from the conference.
+ *
+ * @param connection The connection to separate.
+ */
+ @Override
+ public void onSeparate(android.telecom.Connection connection) {
+ Log.wtf(this, "Cannot separate connections from an IMS conference.");
+ }
+
+ /**
+ * Invoked when the specified {@link android.telecom.Connection} should be merged into the
+ * conference call.
+ *
+ * @param connection The {@code Connection} to merge.
+ */
+ @Override
+ public void onMerge(android.telecom.Connection connection) {
+ try {
+ Phone phone = ((TelephonyConnection) connection).getPhone();
+ if (phone != null) {
+ phone.conference();
+ }
+ } catch (CallStateException e) {
+ Log.e(this, e, "Exception thrown trying to merge call into a conference");
+ }
+ }
+
+ /**
+ * Invoked when the conference should be put on hold.
+ */
+ @Override
+ public void onHold() {
+ mConferenceHost.performHold();
+ }
+
+ /**
+ * Invoked when the conference should be moved from hold to active.
+ */
+ @Override
+ public void onUnhold() {
+ mConferenceHost.performUnhold();
+ }
+
+ /**
+ * Invoked to play a DTMF tone.
+ *
+ * @param c A DTMF character.
+ */
+ @Override
+ public void onPlayDtmfTone(char c) {
+ mConferenceHost.onPlayDtmfTone(c);
+ }
+
+ /**
+ * Invoked to stop playing a DTMF tone.
+ */
+ @Override
+ public void onStopDtmfTone() {
+ mConferenceHost.onStopDtmfTone();
+ }
+
+ /**
+ * Handles the addition of connections to the {@link ImsConference}. The
+ * {@link ImsConferenceController} does not add connections to the conference.
+ *
+ * @param connection The newly added connection.
+ */
+ @Override
+ public void onConnectionAdded(android.telecom.Connection connection) {
+ // No-op
+ }
+
+ /**
+ * Updates the manage conference capability of the conference. Where there are one or more
+ * conference event package participants, the conference management is permitted. Where there
+ * are no conference event package participants, conference management is not permitted.
+ */
+ private void updateManageConference() {
+ boolean couldManageConference = PhoneCapabilities.can(getCapabilities(),
+ PhoneCapabilities.MANAGE_CONFERENCE);
+ boolean canManageConference = !mConferenceParticipantConnections.isEmpty();
+ Log.v(this, "updateManageConference was:%s is:%s", couldManageConference ? "Y" : "N",
+ canManageConference ? "Y" : "N");
+
+ if (couldManageConference != canManageConference) {
+ int newCapabilities = getCapabilities();
+
+ if (canManageConference) {
+ newCapabilities |= PhoneCapabilities.MANAGE_CONFERENCE;
+ } else {
+ newCapabilities = PhoneCapabilities.remove(newCapabilities,
+ PhoneCapabilities.MANAGE_CONFERENCE);
+ }
+ setCapabilities(newCapabilities);
+ }
+ }
+
+ /**
+ * Sets the connection hosting the conference and registers for callbacks.
+ *
+ * @param conferenceHost The connection hosting the conference.
+ */
+ private void setConferenceHost(com.android.internal.telephony.Connection conferenceHost) {
+ if (Log.VERBOSE) {
+ Log.v(this, "setConferenceHost " + conferenceHost);
+ }
+
+ mConferenceHost = new ConferenceHostConnection(conferenceHost);
+ mConferenceHost.addConnectionListener(mConferenceHostListener);
+ }
+
+ /**
+ * Handles state changes for conference participant(s). The participants data passed in
+ *
+ * @param parent The connection which was notified of the conference participant.
+ * @param participants The conference participant information.
+ */
+ private void handleConferenceParticipantsUpdate(
+ TelephonyConnection parent, List<ConferenceParticipant> participants) {
+
+ boolean newParticipantsAdded = false;
+ boolean oldParticipantsRemoved = false;
+ ArrayList<ConferenceParticipant> newParticipants = new ArrayList<>(participants.size());
+ HashSet<Uri> participantEndpoints = new HashSet<>(participants.size());
+
+ // Add any new participants and update existing.
+ for (ConferenceParticipant participant : participants) {
+ Uri endpoint = participant.getEndpoint();
+ participantEndpoints.add(endpoint);
+ if (!mConferenceParticipantConnections.containsKey(endpoint)) {
+ createConferenceParticipantConnection(parent, participant);
+ newParticipants.add(participant);
+ newParticipantsAdded = true;
+ } else {
+ ConferenceParticipantConnection connection =
+ mConferenceParticipantConnections.get(endpoint);
+ connection.updateState(participant.getState());
+ }
+ }
+
+ // Set state of new participants.
+ if (newParticipantsAdded) {
+ // Set the state of the new participants at once and add to the conference
+ for (ConferenceParticipant newParticipant : newParticipants) {
+ ConferenceParticipantConnection connection =
+ mConferenceParticipantConnections.get(newParticipant.getEndpoint());
+ connection.updateState(newParticipant.getState());
+ }
+ }
+
+ // Finally, remove any participants from the conference that no longer exist in the
+ // conference event package data.
+ Iterator<Map.Entry<Uri, ConferenceParticipantConnection>> entryIterator =
+ mConferenceParticipantConnections.entrySet().iterator();
+ while (entryIterator.hasNext()) {
+ Map.Entry<Uri, ConferenceParticipantConnection> entry = entryIterator.next();
+
+ if (!participantEndpoints.contains(entry.getKey())) {
+ ConferenceParticipantConnection participant = entry.getValue();
+ participant.removeConnectionListener(mParticipantListener);
+ removeConnection(participant);
+ entryIterator.remove();
+ oldParticipantsRemoved = true;
+ }
+ }
+
+ // If new participants were added or old ones were removed, we need to ensure the state of
+ // the manage conference capability is updated.
+ if (newParticipantsAdded || oldParticipantsRemoved) {
+ updateManageConference();
+ }
+ }
+
+ /**
+ * Creates a new {@link ConferenceParticipantConnection} to represent a
+ * {@link ConferenceParticipant}.
+ * <p>
+ * The new connection is added to the conference controller and connection service.
+ *
+ * @param parent The connection which was notified of the participant change (e.g. the
+ * parent connection).
+ * @param participant The conference participant information.
+ */
+ private void createConferenceParticipantConnection(
+ TelephonyConnection parent, ConferenceParticipant participant) {
+
+ // Create and add the new connection in holding state so that it does not become the
+ // active call.
+ ConferenceParticipantConnection connection = new ConferenceParticipantConnection(
+ parent.getOriginalConnection(), participant);
+ connection.addConnectionListener(mParticipantListener);
+
+ if (Log.VERBOSE) {
+ Log.v(this, "createConferenceParticipantConnection: %s", connection);
+ }
+
+ mConferenceParticipantConnections.put(participant.getEndpoint(), connection);
+ PhoneAccountHandle phoneAccountHandle =
+ TelecomAccountRegistry.makePstnPhoneAccountHandle(parent.getPhone());
+ mTelephonyConnectionService.addExistingConnection(phoneAccountHandle, connection);
+ addConnection(connection);
+ }
+
+ /**
+ * Removes a conference participant from the conference.
+ *
+ * @param participant The participant to remove.
+ */
+ private void removeConferenceParticipant(ConferenceParticipantConnection participant) {
+ if (Log.VERBOSE) {
+ Log.v(this, "removeConferenceParticipant: %s", participant);
+ }
+
+ participant.removeConnectionListener(mParticipantListener);
+ participant.getEndpoint();
+ mConferenceParticipantConnections.remove(participant.getEndpoint());
+ }
+
+ /**
+ * Disconnects all conference participants from the conference.
+ */
+ private void disconnectConferenceParticipants() {
+ Log.v(this, "disconnectConferenceParticipants");
+
+ for (ConferenceParticipantConnection connection :
+ mConferenceParticipantConnections.values()) {
+
+ removeConferenceParticipant(connection);
+
+ // Mark disconnect cause as cancelled to ensure that the call is not logged in the
+ // call log.
+ connection.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
+ connection.destroy();
+ }
+ mConferenceParticipantConnections.clear();
+ }
+
+ /**
+ * Changes the state of the Ims conference.
+ *
+ * @param state the new state.
+ */
+ public void setState(int state) {
+ Log.v(this, "setState %s", Connection.stateToString(state));
+
+ switch (state) {
+ case Connection.STATE_INITIALIZING:
+ case Connection.STATE_NEW:
+ case Connection.STATE_RINGING:
+ case Connection.STATE_DIALING:
+ // No-op -- not applicable.
+ break;
+ case Connection.STATE_DISCONNECTED:
+ setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
+ mConferenceHost.getOriginalConnection().getDisconnectCause()));
+ destroy();
+ break;
+ case Connection.STATE_ACTIVE:
+ setActive();
+ break;
+ case Connection.STATE_HOLDING:
+ setOnHold();
+ break;
+ }
+ }
+
+ /**
+ * Builds a string representation of the {@link ImsConference}.
+ *
+ * @return String representing the conference.
+ */
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[ImsConference objId:");
+ sb.append(System.identityHashCode(this));
+ sb.append(" state:");
+ sb.append(Connection.stateToString(getState()));
+ sb.append(" hostConnection:");
+ sb.append(mConferenceHost);
+ sb.append(" participants:");
+ sb.append(mConferenceParticipantConnections.size());
+ sb.append("]");
+ return sb.toString();
+ }
+}
diff --git a/src/com/android/services/telephony/ImsConferenceController.java b/src/com/android/services/telephony/ImsConferenceController.java
new file mode 100644
index 000000000..6d92fbb9c
--- /dev/null
+++ b/src/com/android/services/telephony/ImsConferenceController.java
@@ -0,0 +1,274 @@
+/*
+ * 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 android.net.Uri;
+import android.telecom.Conference;
+import android.telecom.Connection;
+import android.telecom.ConnectionService;
+import android.telecom.DisconnectCause;
+import android.telecom.IConferenceable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Manages conferences for IMS connections.
+ */
+public class ImsConferenceController {
+
+ /**
+ * Conference listener; used to receive notification when a conference has been disconnected.
+ */
+ private final Conference.Listener mConferenceListener = new Conference.Listener() {
+ @Override
+ public void onDestroyed(Conference conference) {
+ if (Log.VERBOSE) {
+ Log.v(ImsConferenceController.class, "onDestroyed: %s", conference);
+ }
+
+ mImsConferences.remove(conference);
+ }
+ };
+
+ /**
+ * Ims conference controller connection listener. Used to respond to changes in state of the
+ * Telephony connections the controller is aware of.
+ */
+ private final Connection.Listener mConnectionListener = new Connection.Listener() {
+ @Override
+ public void onStateChanged(Connection c, int state) {
+ Log.v(this, "onStateChanged: %s", Log.pii(c.getAddress()));
+ recalculate();
+ }
+
+ @Override
+ public void onDisconnected(Connection c, DisconnectCause disconnectCause) {
+ Log.v(this, "onDisconnected: %s", Log.pii(c.getAddress()));
+ recalculate();
+ }
+
+ @Override
+ public void onDestroyed(Connection connection) {
+ remove(connection);
+ }
+ };
+
+ /**
+ * The current {@link ConnectionService}.
+ */
+ private final TelephonyConnectionService mConnectionService;
+
+ /**
+ * List of known {@link TelephonyConnection}s.
+ */
+ private final ArrayList<TelephonyConnection> mTelephonyConnections = new ArrayList<>();
+
+ /**
+ * List of known {@link ImsConference}s. Realistically there will only ever be a single
+ * concurrent IMS conference.
+ */
+ private final ArrayList<ImsConference> mImsConferences = new ArrayList<>(1);
+
+ /**
+ * Creates a new instance of the Ims conference controller.
+ *
+ * @param connectionService The current connection service.
+ */
+ public ImsConferenceController(TelephonyConnectionService connectionService) {
+ mConnectionService = connectionService;
+ }
+
+ /**
+ * Adds a new connection to the IMS conference controller.
+ *
+ * @param connection
+ */
+ void add(TelephonyConnection connection) {
+ // Note: Wrap in Log.VERBOSE to avoid calling connection.toString if we are not going to be
+ // outputting the value.
+ if (Log.VERBOSE) {
+ Log.v(this, "add connection %s", connection);
+ }
+
+ mTelephonyConnections.add(connection);
+ connection.addConnectionListener(mConnectionListener);
+ recalculateConference();
+ }
+
+ /**
+ * Removes a connection from the IMS conference controller.
+ *
+ * @param connection
+ */
+ void remove(Connection connection) {
+ if (Log.VERBOSE) {
+ Log.v(this, "remove connection: %s", connection);
+ }
+
+ mTelephonyConnections.remove(connection);
+ recalculateConferenceable();
+ }
+
+ /**
+ * Triggers both a re-check of conferenceable connections, as well as checking for new
+ * conferences.
+ */
+ private void recalculate() {
+ recalculateConferenceable();
+ recalculateConference();
+ }
+
+ /**
+ * Calculates the conference-capable state of all GSM connections in this connection service.
+ */
+ private void recalculateConferenceable() {
+ Log.v(this, "recalculateConferenceable : %d", mTelephonyConnections.size());
+ List<IConferenceable> activeConnections = new ArrayList<>(mTelephonyConnections.size());
+ List<IConferenceable> backgroundConnections = new ArrayList<>(mTelephonyConnections.size());
+
+ // Loop through and collect all calls which are active or holding
+ for (Connection connection : mTelephonyConnections) {
+ if (Log.DEBUG) {
+ Log.d(this, "recalc - %s %s", connection.getState(), 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());
+ }
+
+ for (Conference conference : mImsConferences) {
+ if (Log.DEBUG) {
+ Log.d(this, "recalc - %s %s", conference.getState(), conference);
+ }
+
+ switch (conference.getState()) {
+ case Connection.STATE_ACTIVE:
+ activeConnections.add(conference);
+ continue;
+ case Connection.STATE_HOLDING:
+ backgroundConnections.add(conference);
+ continue;
+ default:
+ break;
+ }
+ }
+
+ 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 (IConferenceable conferenceable : activeConnections) {
+ if (conferenceable instanceof Connection) {
+ Connection connection = (Connection) conferenceable;
+ connection.setConferenceables(backgroundConnections);
+ }
+ }
+
+ // Go through all the background connections and set the active connections as
+ // conferenceable.
+ for (IConferenceable conferenceable : backgroundConnections) {
+ if (conferenceable instanceof Connection) {
+ Connection connection = (Connection) conferenceable;
+ connection.setConferenceables(activeConnections);
+ }
+
+ }
+
+ // Set the conference as conferenceable with all the connections
+ for (ImsConference conference : mImsConferences) {
+ List<Connection> nonConferencedConnections =
+ new ArrayList<>(mTelephonyConnections.size());
+ for (Connection c : mTelephonyConnections) {
+ if (c.getConference() == null) {
+ nonConferencedConnections.add(c);
+ }
+ }
+ if (Log.VERBOSE) {
+ Log.v(this, "conference conferenceable: %s", nonConferencedConnections);
+ }
+ conference.setConferenceableConnections(nonConferencedConnections);
+ }
+ }
+
+ /**
+ * Starts a new ImsConference for a connection which just entered a multiparty state.
+ */
+ private void recalculateConference() {
+ Log.v(this, "recalculateConference");
+
+ Iterator<TelephonyConnection> it = mTelephonyConnections.iterator();
+ while (it.hasNext()) {
+ TelephonyConnection connection = it.next();
+
+ if (connection.isImsConnection() && connection.getOriginalConnection() != null &&
+ connection.getOriginalConnection().isMultiparty()) {
+
+ startConference(connection);
+ it.remove();
+ }
+ }
+ }
+
+ /**
+ * Starts a new {@link ImsConference} for the given IMS connection.
+ * <p>
+ * Creates a new IMS Conference to manage the conference represented by the connection.
+ * Internally the ImsConference wraps the radio connection with a new TelephonyConnection
+ * which is NOT reported to the connection service and Telecom.
+ * <p>
+ * Once the new IMS Conference has been created, the connection passed in is held and removed
+ * from the connection service (removing it from Telecom). The connection is put into a held
+ * state to ensure that telecom removes the connection without putting it into a disconnected
+ * state first.
+ *
+ * @param connection The connection to the Ims server.
+ */
+ private void startConference(TelephonyConnection connection) {
+ com.android.internal.telephony.Connection originalConnection =
+ connection.getOriginalConnection();
+ if (Log.VERBOSE) {
+ Log.v(this, "Start new ImsConference - connection: %s", connection);
+ }
+
+ // Create conference and add to telecom
+ ImsConference conference = new ImsConference(mConnectionService, originalConnection);
+ conference.setState(connection.getState());
+ mConnectionService.addConference(conference);
+ conference.addListener(mConferenceListener);
+
+ // Cleanup TelephonyConnection which backed the original connection and remove from telecom.
+ // Use the "Canceled" disconnect cause to ensure the call is not logged.
+ connection.removeConnectionListener(mConnectionListener);
+ connection.clearOriginalConnection();
+ connection.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
+ connection.destroy();
+ mImsConferences.add(conference);
+ }
+}
diff --git a/src/com/android/services/telephony/TelephonyConference.java b/src/com/android/services/telephony/TelephonyConference.java
index fc009bc4f..3a8009705 100644
--- a/src/com/android/services/telephony/TelephonyConference.java
+++ b/src/com/android/services/telephony/TelephonyConference.java
@@ -33,12 +33,6 @@ import java.util.List;
*/
public class TelephonyConference extends Conference {
- /**
- * When {@code true}, indicates that conference participant information from an IMS conference
- * event package has been received.
- */
- private boolean mParticipantsReceived = false;
-
public TelephonyConference(PhoneAccountHandle phoneAccount) {
super(phoneAccount);
setCapabilities(
@@ -55,17 +49,30 @@ public class TelephonyConference extends Conference {
@Override
public void onDisconnect() {
for (Connection connection : getConnections()) {
- Call call = getMultipartyCallForConnection(connection, "onDisconnect");
- if (call != null) {
- Log.d(this, "Found multiparty call to hangup for conference.");
- try {
- call.hangup();
- break;
- } catch (CallStateException e) {
- Log.e(this, e, "Exception thrown trying to hangup conference");
- }
+ if (disconnectCall(connection)) {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Disconnect the underlying Telephony Call for a connection.
+ *
+ * @param connection The connection.
+ * @return {@code True} if the call was disconnected.
+ */
+ private boolean disconnectCall(Connection connection) {
+ Call call = getMultipartyCallForConnection(connection, "onDisconnect");
+ if (call != null) {
+ Log.d(this, "Found multiparty call to hangup for conference.");
+ try {
+ call.hangup();
+ return true;
+ } catch (CallStateException e) {
+ Log.e(this, e, "Exception thrown trying to hangup conference");
}
}
+ return false;
}
/**
@@ -140,8 +147,7 @@ public class TelephonyConference extends Conference {
// as the default behavior. If there is a conference event package, this may be overridden.
// If a conference event package was received, do not attempt to remove manage conference.
if (connection instanceof TelephonyConnection &&
- ((TelephonyConnection) connection).wasImsConnection() &&
- !mParticipantsReceived) {
+ ((TelephonyConnection) connection).wasImsConnection()) {
int capabilities = getCapabilities();
if (PhoneCapabilities.can(capabilities, PhoneCapabilities.MANAGE_CONFERENCE)) {
int newCapabilities =
@@ -153,11 +159,17 @@ public class TelephonyConference extends Conference {
@Override
public Connection getPrimaryConnection() {
+
+ List<Connection> connections = getConnections();
+ if (connections == null || connections.isEmpty()) {
+ return null;
+ }
+
// Default to the first connection.
- Connection primaryConnection = getConnections().get(0);
+ Connection primaryConnection = connections.get(0);
// Otherwise look for a connection where the radio connection states it is multiparty.
- for (Connection connection : getConnections()) {
+ for (Connection connection : connections) {
com.android.internal.telephony.Connection radioConnection =
getOriginalConnection(connection);
@@ -182,7 +194,7 @@ public class TelephonyConference extends Conference {
return null;
}
- private com.android.internal.telephony.Connection getOriginalConnection(
+ protected com.android.internal.telephony.Connection getOriginalConnection(
Connection connection) {
if (connection instanceof TelephonyConnection) {
@@ -199,17 +211,4 @@ public class TelephonyConference extends Conference {
}
return (TelephonyConnection) connections.get(0);
}
-
- /**
- * Flags the conference to indicate that a conference event package has been received and there
- * is now participant data present which would permit conference management.
- */
- public void setParticipantsReceived() {
- if (!mParticipantsReceived) {
- int capabilities = getCapabilities();
- capabilities |= PhoneCapabilities.MANAGE_CONFERENCE;
- setCapabilities(capabilities);
- }
- mParticipantsReceived = true;
- }
}
diff --git a/src/com/android/services/telephony/TelephonyConferenceController.java b/src/com/android/services/telephony/TelephonyConferenceController.java
index 55c8338a6..2af10a62d 100644
--- a/src/com/android/services/telephony/TelephonyConferenceController.java
+++ b/src/com/android/services/telephony/TelephonyConferenceController.java
@@ -59,36 +59,11 @@ final class TelephonyConferenceController {
public void onDestroyed(Connection connection) {
remove(connection);
}
-
- /**
- * Handles notifications from an connection that participant(s) in a conference have changed
- * state.
- *
- * @param c The connection.
- * @param participants The participant information.
- */
- @Override
- public void onConferenceParticipantsChanged(Connection c,
- List<ConferenceParticipant> participants) {
-
- if (c == null) {
- return;
- }
- Log.v(this, "onConferenceParticipantsChanged: %d participants", participants.size());
- TelephonyConnection telephonyConnection = (TelephonyConnection) c;
- handleConferenceParticipantsUpdate(telephonyConnection, participants);
- }
};
/** The known connections. */
private final List<TelephonyConnection> mTelephonyConnections = new ArrayList<>();
- /**
- * The known conference participant connections. The HashMap is keyed by endpoint Uri.
- */
- private final HashMap<Uri, ConferenceParticipantConnection> mConferenceParticipantConnections =
- new HashMap<>();
-
private final TelephonyConnectionService mConnectionService;
public TelephonyConferenceController(TelephonyConnectionService connectionService) {
@@ -106,12 +81,7 @@ final class TelephonyConferenceController {
void remove(Connection connection) {
connection.removeConnectionListener(mConnectionListener);
-
- if (connection instanceof ConferenceParticipantConnection) {
- mConferenceParticipantConnections.remove(connection);
- } else {
- mTelephonyConnections.remove(connection);
- }
+ mTelephonyConnections.remove(connection);
recalculate();
}
@@ -192,9 +162,7 @@ final class TelephonyConferenceController {
private void recalculateConference() {
Set<Connection> conferencedConnections = new HashSet<>();
-
int numGsmConnections = 0;
- int numImsConnections = 0;
for (TelephonyConnection connection : mTelephonyConnections) {
com.android.internal.telephony.Connection radioConnection =
@@ -206,11 +174,7 @@ final class TelephonyConferenceController {
if ((state == Call.State.ACTIVE || state == Call.State.HOLDING) &&
(call != null && call.isMultiparty())) {
- if (radioConnection instanceof GsmConnection) {
- numGsmConnections++;
- } else if (radioConnection instanceof ImsPhoneConnection) {
- numImsConnections++;
- }
+ numGsmConnections++;
conferencedConnections.add(connection);
}
}
@@ -219,18 +183,11 @@ final class TelephonyConferenceController {
Log.d(this, "Recalculate conference calls %s %s.",
mTelephonyConference, conferencedConnections);
- boolean wasParticipantsAdded = false;
-
- // If the number of telephony connections drops below the limit, the conference can be
- // considered terminated.
- // We must have less than 2 GSM connections and less than 1 IMS connection.
- if (numGsmConnections < 2 && numImsConnections < 1) {
+ // If this is a GSM conference and the number of connections drops below 2, we will
+ // terminate the conference.
+ if (numGsmConnections < 2) {
Log.d(this, "not enough connections to be a conference!");
- // The underlying telephony connections have been disconnected -- disconnect the
- // conference participants now.
- disconnectConferenceParticipants();
-
// No more connections are conferenced, destroy any existing conference.
if (mTelephonyConference != null) {
Log.d(this, "with a conference to destroy!");
@@ -254,131 +211,30 @@ final class TelephonyConferenceController {
mTelephonyConference.addConnection(connection);
}
}
-
- // Add new conference participants
- for (Connection conferenceParticipant :
- mConferenceParticipantConnections.values()) {
-
- if (conferenceParticipant.getState() == Connection.STATE_NEW) {
- if (!existingConnections.contains(conferenceParticipant)) {
- wasParticipantsAdded = true;
- mTelephonyConference.addConnection(conferenceParticipant);
- }
- }
- }
} 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);
}
- // Add the conference participants
- for (Connection conferenceParticipant :
- mConferenceParticipantConnections.values()) {
- wasParticipantsAdded = true;
- mTelephonyConference.addConnection(conferenceParticipant);
- }
-
mConnectionService.addConference(mTelephonyConference);
}
- // If we added conference participants (e.g. via an IMS conference event package),
- // notify the conference so that the MANAGE_CONFERENCE capability can be added.
- if (wasParticipantsAdded) {
- mTelephonyConference.setParticipantsReceived();
- }
-
// Set the conference state to the same state as its child connections.
Connection conferencedConnection = mTelephonyConference.getPrimaryConnection();
- switch (conferencedConnection.getState()) {
- case Connection.STATE_ACTIVE:
- mTelephonyConference.setActive();
- break;
- case Connection.STATE_HOLDING:
- mTelephonyConference.setOnHold();
- break;
- }
- }
- }
-
- /**
- * Disconnects all conference participants from the conference.
- */
- private void disconnectConferenceParticipants() {
- for (Connection connection : mConferenceParticipantConnections.values()) {
- // Disconnect listener so that the connection doesn't fire events on the conference
- // controller, causing a recursive call.
- connection.removeConnectionListener(mConnectionListener);
- mConferenceParticipantConnections.remove(connection);
-
- // Mark disconnect cause as cancelled to ensure that the call is not logged in the
- // call log.
- connection.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
- connection.destroy();
- }
- }
-
- /**
- * Handles state changes for conference participant(s).
- *
- * @param parent The connection which was notified of the conference participant.
- * @param participants The conference participant information.
- */
- private void handleConferenceParticipantsUpdate(
- TelephonyConnection parent, List<ConferenceParticipant> participants) {
-
- boolean recalculateConference = false;
- ArrayList<ConferenceParticipant> newParticipants = new ArrayList<>(participants.size());
-
- for (ConferenceParticipant participant : participants) {
- Uri endpoint = participant.getEndpoint();
- if (!mConferenceParticipantConnections.containsKey(endpoint)) {
- createConferenceParticipantConnection(parent, participant);
- newParticipants.add(participant);
- recalculateConference = true;
- } else {
- ConferenceParticipantConnection connection =
- mConferenceParticipantConnections.get(endpoint);
- connection.updateState(participant.getState());
- }
- }
-
- if (recalculateConference) {
- // Recalculate to add new connections to the conference.
- recalculateConference();
-
- // Now that conference is established, set the state for all participants.
- for (ConferenceParticipant newParticipant : newParticipants) {
- ConferenceParticipantConnection connection =
- mConferenceParticipantConnections.get(newParticipant.getEndpoint());
- connection.updateState(newParticipant.getState());
+ if (conferencedConnection != null) {
+ switch (conferencedConnection.getState()) {
+ case Connection.STATE_ACTIVE:
+ mTelephonyConference.setActive();
+ break;
+ case Connection.STATE_HOLDING:
+ mTelephonyConference.setOnHold();
+ break;
+ }
}
}
}
-
- /**
- * Creates a new {@link ConferenceParticipantConnection} to represent a
- * {@link ConferenceParticipant}.
- * <p>
- * The new connection is added to the conference controller and connection service.
- *
- * @param parent The connection which was notified of the participant change (e.g. the
- * parent connection).
- * @param participant The conference participant information.
- */
- private void createConferenceParticipantConnection(
- TelephonyConnection parent, ConferenceParticipant participant) {
-
- // Create and add the new connection in holding state so that it does not become the
- // active call.
- ConferenceParticipantConnection connection = new ConferenceParticipantConnection(
- parent, participant);
- connection.addConnectionListener(mConnectionListener);
- mConferenceParticipantConnections.put(participant.getEndpoint(), connection);
- PhoneAccountHandle phoneAccountHandle =
- TelecomAccountRegistry.makePstnPhoneAccountHandle(parent.getPhone());
- mConnectionService.addExistingConnection(phoneAccountHandle, connection);
- }
}
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 056f62f82..77a76d09c 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -24,6 +24,7 @@ import android.telecom.AudioState;
import android.telecom.Conference;
import android.telecom.ConferenceParticipant;
import android.telecom.Connection;
+import android.telecom.DisconnectCause;
import android.telecom.PhoneAccount;
import android.telecom.PhoneCapabilities;
@@ -31,6 +32,9 @@ import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.Connection.PostDialListener;
import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.cdma.CdmaCall;
+import com.android.internal.telephony.gsm.*;
+import com.android.internal.telephony.gsm.GsmConnection;
import com.android.internal.telephony.imsphone.ImsPhoneConnection;
import java.lang.Override;
@@ -442,12 +446,8 @@ abstract class TelephonyConnection extends Connection {
void setOriginalConnection(com.android.internal.telephony.Connection originalConnection) {
Log.v(this, "new TelephonyConnection, originalConnection: " + originalConnection);
- if (mOriginalConnection != null) {
- getPhone().unregisterForPreciseCallStateChanged(mHandler);
- getPhone().unregisterForRingbackTone(mHandler);
- getPhone().unregisterForHandoverStateChanged(mHandler);
- getPhone().unregisterForDisconnect(mHandler);
- }
+ clearOriginalConnection();
+
mOriginalConnection = originalConnection;
getPhone().registerForPreciseCallStateChanged(
mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null);
@@ -473,6 +473,19 @@ abstract class TelephonyConnection extends Connection {
updateAddress();
}
+ /**
+ * Un-sets the underlying radio connection.
+ */
+ void clearOriginalConnection() {
+ if (mOriginalConnection != null) {
+ getPhone().unregisterForPreciseCallStateChanged(mHandler);
+ getPhone().unregisterForRingbackTone(mHandler);
+ getPhone().unregisterForHandoverStateChanged(mHandler);
+ getPhone().unregisterForDisconnect(mHandler);
+ mOriginalConnection = null;
+ }
+ }
+
protected void hangup(int telephonyDisconnectCode) {
if (mOriginalConnection != null) {
try {
@@ -871,4 +884,41 @@ abstract class TelephonyConnection extends Connection {
l.onOriginalConnectionConfigured(this);
}
}
+
+ /**
+ * Creates a string representation of this {@link TelephonyConnection}. Primarily intended for
+ * use in log statements.
+ *
+ * @return String representation of the connection.
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[TelephonyConnection objId:");
+ sb.append(System.identityHashCode(this));
+ sb.append(" type:");
+ if (isImsConnection()) {
+ sb.append("ims");
+ } else if (this instanceof com.android.services.telephony.GsmConnection) {
+ sb.append("gsm");
+ } else if (this instanceof CdmaConnection) {
+ sb.append("cdma");
+ }
+ sb.append(" state:");
+ sb.append(Connection.stateToString(getState()));
+ sb.append(" capabilities:");
+ sb.append(PhoneCapabilities.toString(getCallCapabilities()));
+ sb.append(" address:");
+ sb.append(Log.pii(getAddress()));
+ sb.append(" originalConnection:");
+ sb.append(mOriginalConnection);
+ sb.append(" partOfConf:");
+ if (getConference() == null) {
+ sb.append("N");
+ } else {
+ sb.append("Y");
+ }
+ sb.append("]");
+ return sb.toString();
+ }
}
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 8ef9ae886..7ce5005c0 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -52,6 +52,8 @@ public class TelephonyConnectionService extends ConnectionService {
new TelephonyConferenceController(this);
private final CdmaConferenceController mCdmaConferenceController =
new CdmaConferenceController(this);
+ private final ImsConferenceController mImsConferenceController =
+ new ImsConferenceController(this);
private ComponentName mExpectedComponentName = null;
private EmergencyCallHelper mEmergencyCallHelper;
private EmergencyTonePlayer mEmergencyTonePlayer;
@@ -448,7 +450,7 @@ public class TelephonyConnectionService extends ConnectionService {
// conference controllers first before re-adding it.
if (connection.isImsConnection()) {
Log.d(this, "Adding IMS connection to conference controller: " + connection);
- mTelephonyConferenceController.add(connection);
+ mImsConferenceController.add(connection);
} else {
int phoneType = connection.getCall().getPhone().getPhoneType();
if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {