blob: 8ccc8f995ddc416ae4a9bde8c398875a018d8c6e [file] [log] [blame]
/*
* 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.content.Context;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.telecom.Conference;
import android.telecom.ConferenceParticipant;
import android.telecom.Connection.VideoProvider;
import android.telecom.Connection;
import android.telecom.DisconnectCause;
import android.telecom.Log;
import android.telecom.PhoneAccountHandle;
import android.telecom.StatusHints;
import android.telecom.VideoProfile;
import android.telephony.PhoneNumberUtils;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.internal.telephony.imsphone.ImsPhoneConnection;
import com.android.phone.PhoneUtils;
import com.android.phone.R;
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.Objects;
/**
* 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 {
/**
* 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 underlying radio connection for the conference
* host connection. Used to respond to SRVCC changes.
*/
private final TelephonyConnection.TelephonyConnectionListener mTelephonyConnectionListener =
new TelephonyConnection.TelephonyConnectionListener() {
@Override
public void onOriginalConnectionConfigured(TelephonyConnection c) {
if (c == mConferenceHost) {
handleOriginalConnectionChange();
}
}
};
/**
* 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 || participants == null) {
return;
}
Log.v(this, "onConferenceParticipantsChanged: %d participants", participants.size());
TelephonyConnection telephonyConnection = (TelephonyConnection) c;
handleConferenceParticipantsUpdate(telephonyConnection, participants);
}
@Override
public void onVideoStateChanged(android.telecom.Connection c, int videoState) {
Log.d(this, "onVideoStateChanged video state %d", videoState);
setVideoState(c, videoState);
}
@Override
public void onVideoProviderChanged(android.telecom.Connection c,
Connection.VideoProvider videoProvider) {
Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c,
videoProvider);
setVideoProvider(c, videoProvider);
}
@Override
public void onConnectionCapabilitiesChanged(Connection c, int connectionCapabilities) {
Log.d(this, "onCallCapabilitiesChanged: Connection: %s, callCapabilities: %s", c,
connectionCapabilities);
int capabilites = ImsConference.this.getConnectionCapabilities();
setConnectionCapabilities(applyHostCapabilities(capabilites, connectionCapabilities));
}
@Override
public void onStatusHintsChanged(Connection c, StatusHints statusHints) {
Log.v(this, "onStatusHintsChanged");
updateStatusHints();
}
};
/**
* 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 PhoneAccountHandle of the conference host.
*/
private PhoneAccountHandle mConferenceHostPhoneAccountHandle;
/**
* The address of the conference host.
*/
private Uri mConferenceHostAddress;
/**
* The known conference participant connections. The HashMap is keyed by endpoint Uri.
* Access to the hashmap is protected by the {@link #mUpdateSyncRoot}.
*/
private final HashMap<Uri, ConferenceParticipantConnection>
mConferenceParticipantConnections = new HashMap<Uri, ConferenceParticipantConnection>();
/**
* Sychronization root used to ensure that updates to the
* {@link #mConferenceParticipantConnections} happen atomically are are not interleaved across
* threads. There are some instances where the network will send conference event package
* data closely spaced. If that happens, it is possible that the interleaving of the update
* will cause duplicate participant info to be added.
*/
private final Object mUpdateSyncRoot = new Object();
public void updateConferenceParticipantsAfterCreation() {
if (mConferenceHost != null) {
Log.v(this, "updateConferenceStateAfterCreation :: process participant update");
handleConferenceParticipantsUpdate(mConferenceHost,
mConferenceHost.getConferenceParticipants());
} else {
Log.v(this, "updateConferenceStateAfterCreation :: null mConferenceHost");
}
}
/**
* Initializes a new {@link ImsConference}.
*
* @param telephonyConnectionService The connection service responsible for adding new
* conferene participants.
* @param conferenceHost The telephony connection hosting the conference.
*/
public ImsConference(TelephonyConnectionService telephonyConnectionService,
TelephonyConnection conferenceHost) {
super((conferenceHost != null && conferenceHost.getCall() != null &&
conferenceHost.getCall().getPhone() != null) ?
PhoneUtils.makePstnPhoneAccountHandle(
conferenceHost.getCall().getPhone()) : null);
// Specify the connection time of the conference to be the connection time of the original
// connection.
long connectTime = conferenceHost.getOriginalConnection().getConnectTime();
setConnectTimeMillis(connectTime);
// Set the connectTime in the connection as well.
conferenceHost.setConnectTimeMillis(connectTime);
mTelephonyConnectionService = telephonyConnectionService;
setConferenceHost(conferenceHost);
int capabilities = Connection.CAPABILITY_SUPPORT_HOLD | Connection.CAPABILITY_HOLD |
Connection.CAPABILITY_MUTE | Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN;
capabilities = applyHostCapabilities(capabilities,
mConferenceHost.getConnectionCapabilities());
setConnectionCapabilities(capabilities);
}
/**
* Transfers capabilities from the conference host to the conference itself.
*
* @param conferenceCapabilities The current conference capabilities.
* @param capabilities The new conference host capabilities.
* @return The merged capabilities to be applied to the conference.
*/
private int applyHostCapabilities(int conferenceCapabilities, int capabilities) {
if (can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL)) {
conferenceCapabilities = applyCapability(conferenceCapabilities,
Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
} else {
conferenceCapabilities = removeCapability(conferenceCapabilities,
Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
}
if (can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL)) {
conferenceCapabilities = applyCapability(conferenceCapabilities,
Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
} else {
conferenceCapabilities = removeCapability(conferenceCapabilities,
Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
}
if (can(capabilities, Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO)) {
conferenceCapabilities = applyCapability(conferenceCapabilities,
Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO);
} else {
conferenceCapabilities = removeCapability(conferenceCapabilities,
Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO);
}
if (can(capabilities, Connection.CAPABILITY_HIGH_DEF_AUDIO)) {
conferenceCapabilities = applyCapability(conferenceCapabilities,
Connection.CAPABILITY_HIGH_DEF_AUDIO);
} else {
conferenceCapabilities = removeCapability(conferenceCapabilities,
Connection.CAPABILITY_HIGH_DEF_AUDIO);
}
return conferenceCapabilities;
}
/**
* Not used by the IMS conference controller.
*
* @return {@code Null}.
*/
@Override
public android.telecom.Connection getPrimaryConnection() {
return null;
}
/**
* Returns VideoProvider of the conference. This can be null.
*
* @hide
*/
@Override
public VideoProvider getVideoProvider() {
if (mConferenceHost != null) {
return mConferenceHost.getVideoProvider();
}
return null;
}
/**
* Returns video state of conference
*
* @hide
*/
@Override
public int getVideoState() {
if (mConferenceHost != null) {
return mConferenceHost.getVideoState();
}
return VideoProfile.STATE_AUDIO_ONLY;
}
/**
* 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.");
if (mConferenceHost == null) {
return;
}
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() {
if (mConferenceHost == null) {
return;
}
mConferenceHost.performHold();
}
/**
* Invoked when the conference should be moved from hold to active.
*/
@Override
public void onUnhold() {
if (mConferenceHost == null) {
return;
}
mConferenceHost.performUnhold();
}
/**
* Invoked to play a DTMF tone.
*
* @param c A DTMF character.
*/
@Override
public void onPlayDtmfTone(char c) {
if (mConferenceHost == null) {
return;
}
mConferenceHost.onPlayDtmfTone(c);
}
/**
* Invoked to stop playing a DTMF tone.
*/
@Override
public void onStopDtmfTone() {
if (mConferenceHost == null) {
return;
}
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
}
private int applyCapability(int capabilities, int capability) {
int newCapabilities = capabilities | capability;
return newCapabilities;
}
private int removeCapability(int capabilities, int capability) {
int newCapabilities = capabilities & ~capability;
return newCapabilities;
}
/**
* Determines if this conference is hosted on the current device or the peer device.
*
* @return {@code true} if this conference is hosted on the current device, {@code false} if it
* is hosted on the peer device.
*/
public boolean isConferenceHost() {
if (mConferenceHost == null) {
return false;
}
com.android.internal.telephony.Connection originalConnection =
mConferenceHost.getOriginalConnection();
if (!(originalConnection instanceof ImsPhoneConnection)) {
return false;
}
ImsPhoneConnection imsPhoneConnection = (ImsPhoneConnection) originalConnection;
return imsPhoneConnection.isMultiparty() && imsPhoneConnection.isConferenceHost();
}
/**
* 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.
* <p>
* Note: We add and remove {@link Connection#CAPABILITY_CONFERENCE_HAS_NO_CHILDREN} to ensure
* that the conference is represented appropriately on Bluetooth devices.
*/
private void updateManageConference() {
boolean couldManageConference = can(Connection.CAPABILITY_MANAGE_CONFERENCE);
boolean canManageConference = !mConferenceParticipantConnections.isEmpty();
Log.v(this, "updateManageConference was :%s is:%s", couldManageConference ? "Y" : "N",
canManageConference ? "Y" : "N");
if (couldManageConference != canManageConference) {
int capabilities = getConnectionCapabilities();
if (canManageConference) {
capabilities |= Connection.CAPABILITY_MANAGE_CONFERENCE;
capabilities &= ~Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN;
} else {
capabilities &= ~Connection.CAPABILITY_MANAGE_CONFERENCE;
capabilities |= Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN;
}
setConnectionCapabilities(capabilities);
}
}
/**
* Sets the connection hosting the conference and registers for callbacks.
*
* @param conferenceHost The connection hosting the conference.
*/
private void setConferenceHost(TelephonyConnection conferenceHost) {
if (Log.VERBOSE) {
Log.v(this, "setConferenceHost " + conferenceHost);
}
mConferenceHost = conferenceHost;
// Attempt to get the conference host's address (e.g. the host's own phone number).
// We need to look at the default phone for the ImsPhone when creating the phone account
// for the
if (mConferenceHost.getPhone() != null && mConferenceHost.getPhone() instanceof ImsPhone) {
// Look up the conference host's address; we need this later for filtering out the
// conference host in conference event package data.
ImsPhone imsPhone = (ImsPhone) mConferenceHost.getPhone();
mConferenceHostPhoneAccountHandle =
PhoneUtils.makePstnPhoneAccountHandle(imsPhone.getDefaultPhone());
mConferenceHostAddress = TelecomAccountRegistry.getInstance(mTelephonyConnectionService)
.getAddress(mConferenceHostPhoneAccountHandle);
}
mConferenceHost.addConnectionListener(mConferenceHostListener);
mConferenceHost.addTelephonyConnectionListener(mTelephonyConnectionListener);
setState(mConferenceHost.getState());
updateStatusHints();
}
/**
* 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) {
if (participants == null) {
return;
}
// Perform the update in a synchronized manner. It is possible for the IMS framework to
// trigger two onConferenceParticipantsChanged callbacks in quick succession. If the first
// update adds new participants, and the second does something like update the status of one
// of the participants, we can get into a situation where the participant is added twice.
synchronized (mUpdateSyncRoot) {
boolean newParticipantsAdded = false;
boolean oldParticipantsRemoved = false;
ArrayList<ConferenceParticipant> newParticipants = new ArrayList<>(participants.size());
HashSet<Uri> participantUserEntities = new HashSet<>(participants.size());
// Add any new participants and update existing.
for (ConferenceParticipant participant : participants) {
Uri userEntity = participant.getHandle();
participantUserEntities.add(userEntity);
if (!mConferenceParticipantConnections.containsKey(userEntity)) {
// Some carriers will also include the conference host in the CEP. We will
// filter that out here.
if (!isParticipantHost(mConferenceHostAddress, userEntity)) {
createConferenceParticipantConnection(parent, participant);
newParticipants.add(participant);
newParticipantsAdded = true;
}
} else {
ConferenceParticipantConnection connection =
mConferenceParticipantConnections.get(userEntity);
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.getHandle());
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 (!participantUserEntities.contains(entry.getKey())) {
ConferenceParticipantConnection participant = entry.getValue();
participant.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
participant.removeConnectionListener(mParticipantListener);
mTelephonyConnectionService.removeConnection(participant);
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);
connection.setConnectTimeMillis(parent.getConnectTimeMillis());
if (Log.VERBOSE) {
Log.v(this, "createConferenceParticipantConnection: %s", connection);
}
synchronized(mUpdateSyncRoot) {
mConferenceParticipantConnections.put(participant.getHandle(), connection);
}
mTelephonyConnectionService.addExistingConnection(mConferenceHostPhoneAccountHandle,
connection);
addConnection(connection);
}
/**
* Removes a conference participant from the conference.
*
* @param participant The participant to remove.
*/
private void removeConferenceParticipant(ConferenceParticipantConnection participant) {
Log.d(this, "removeConferenceParticipant: %s", participant);
participant.removeConnectionListener(mParticipantListener);
synchronized(mUpdateSyncRoot) {
mConferenceParticipantConnections.remove(participant.getUserEntity());
}
mTelephonyConnectionService.removeConnection(participant);
}
/**
* Disconnects all conference participants from the conference.
*/
private void disconnectConferenceParticipants() {
Log.v(this, "disconnectConferenceParticipants");
synchronized(mUpdateSyncRoot) {
for (ConferenceParticipantConnection connection :
mConferenceParticipantConnections.values()) {
connection.removeConnectionListener(mParticipantListener);
// Mark disconnect cause as cancelled to ensure that the call is not logged in the
// call log.
connection.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
mTelephonyConnectionService.removeConnection(connection);
connection.destroy();
}
mConferenceParticipantConnections.clear();
}
}
/**
* Determines if the passed in participant handle is the same as the conference host's handle.
* Starts with a simple equality check. However, the handles from a conference event package
* will be a SIP uri, so we need to pull that apart to look for the participant's phone number.
*
* @param hostHandle The handle of the connection hosting the conference.
* @param handle The handle of the conference participant.
* @return {@code true} if the host's handle matches the participant's handle, {@code false}
* otherwise.
*/
private boolean isParticipantHost(Uri hostHandle, Uri handle) {
// If host and participant handles are the same, bail early.
if (Objects.equals(hostHandle, handle)) {
Log.v(this, "isParticipantHost(Y) : uris equal");
return true;
}
// If there is no host handle or not participant handle, bail early.
if (hostHandle == null || handle == null) {
Log.v(this, "isParticipantHost(N) : host or participant uri null");
return false;
}
// Conference event package participants are identified using SIP URIs (see RFC3261).
// A valid SIP uri has the format: sip:user:password@host:port;uri-parameters?headers
// Per RFC3261, the "user" can be a telephone number.
// For example: sip:1650555121;phone-context=blah.com@host.com
// In this case, the phone number is in the user field of the URI, and the parameters can be
// ignored.
//
// A SIP URI can also specify a phone number in a format similar to:
// sip:+1-212-555-1212@something.com;user=phone
// In this case, the phone number is again in user field and the parameters can be ignored.
// We can get the user field in these instances by splitting the string on the @, ;, or :
// and looking at the first found item.
String number = handle.getSchemeSpecificPart();
String numberParts[] = number.split("[@;:]");
if (numberParts.length == 0) {
Log.v(this, "isParticipantHost(N) : no number in participant handle");
return false;
}
number = numberParts[0];
// The host number will be a tel: uri. Per RFC3966, the part after tel: is the phone
// number.
String hostNumber = hostHandle.getSchemeSpecificPart();
// Use a loose comparison of the phone numbers. This ensures that numbers that differ by
// special characters are counted as equal.
// E.g. +16505551212 would be the same as 16505551212
boolean isHost = PhoneNumberUtils.compare(hostNumber, number);
Log.v(this, "isParticipantHost(%s) : host: %s, participant %s", (isHost ? "Y" : "N"),
Log.pii(hostNumber), Log.pii(number));
return isHost;
}
/**
* Handles a change in the original connection backing the conference host connection. This can
* happen if an SRVCC event occurs on the original IMS connection, requiring a fallback to
* GSM or CDMA.
* <p>
* If this happens, we will add the conference host connection to telecom and tear down the
* conference.
*/
private void handleOriginalConnectionChange() {
if (mConferenceHost == null) {
Log.w(this, "handleOriginalConnectionChange; conference host missing.");
return;
}
com.android.internal.telephony.Connection originalConnection =
mConferenceHost.getOriginalConnection();
if (!(originalConnection instanceof ImsPhoneConnection)) {
if (Log.VERBOSE) {
Log.v(this,
"Original connection for conference host is no longer an IMS connection; " +
"new connection: %s", originalConnection);
}
PhoneAccountHandle phoneAccountHandle =
PhoneUtils.makePstnPhoneAccountHandle(mConferenceHost.getPhone());
if (mConferenceHost.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) {
GsmConnection c = new GsmConnection(originalConnection);
c.updateState();
// Copy the connect time from the conferenceHost
c.setConnectTimeMillis(mConferenceHost.getConnectTimeMillis());
mTelephonyConnectionService.addExistingConnection(phoneAccountHandle, c);
mTelephonyConnectionService.addConnectionToConferenceController(c);
} // CDMA case not applicable for SRVCC
mConferenceHost.removeConnectionListener(mConferenceHostListener);
mConferenceHost.removeTelephonyConnectionListener(mTelephonyConnectionListener);
mConferenceHost = null;
setDisconnected(new DisconnectCause(DisconnectCause.OTHER));
disconnectConferenceParticipants();
destroy();
}
updateStatusHints();
}
/**
* 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:
// No-op -- not applicable.
break;
case Connection.STATE_DIALING:
setDialing();
break;
case Connection.STATE_DISCONNECTED:
DisconnectCause disconnectCause;
if (mConferenceHost == null) {
disconnectCause = new DisconnectCause(DisconnectCause.CANCELED);
} else {
disconnectCause = DisconnectCauseUtil.toTelecomDisconnectCause(
mConferenceHost.getOriginalConnection().getDisconnectCause());
}
setDisconnected(disconnectCause);
disconnectConferenceParticipants();
destroy();
break;
case Connection.STATE_ACTIVE:
setActive();
break;
case Connection.STATE_HOLDING:
setOnHold();
break;
}
}
private void updateStatusHints() {
if (mConferenceHost == null) {
setStatusHints(null);
return;
}
if (mConferenceHost.isWifi()) {
Phone phone = mConferenceHost.getPhone();
if (phone != null) {
Context context = phone.getContext();
setStatusHints(new StatusHints(
context.getString(R.string.status_hint_label_wifi_call),
Icon.createWithResource(
context.getResources(),
R.drawable.ic_signal_wifi_4_bar_24dp),
null /* extras */));
}
} else {
setStatusHints(null);
}
}
/**
* 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();
}
}