Fixes for IMS Conferences via the RemoteConnectionService API.
IMS Conferences are problematic to Telecom when a connection manager is
used. Both addExistingConnection and addConference will result in the
addition of duplicate conference participants and conferences to Telecom.
This is because Telecom does not have a means of de-duping the two, which
have new IDs created for them when they're added via a ConnectionService.
To fix this, added a workaround which packages the original existing
connection or conference id into the connection/conference extras which
are sent to the connection manager. This way, the connection mgr can use
the same connection/conference ID was was used by the original
ConnectionService, and also perform some de-duping.
Its not optimal, and this should be fixed better in the future.
Bug: 31464792
Change-Id: I7b63f145c741c29e1735f50a473585f26ef70fc7
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 98310bd..1227fce 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -26,6 +26,7 @@
import android.os.RemoteException;
import android.os.Trace;
import android.provider.ContactsContract.Contacts;
+import android.telecom.Conference;
import android.telecom.DisconnectCause;
import android.telecom.Connection;
import android.telecom.GatewayInfo;
@@ -383,6 +384,17 @@
private PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
/**
+ * For {@link Connection}s or {@link android.telecom.Conference}s added via a ConnectionManager
+ * using the {@link android.telecom.ConnectionService#addExistingConnection(PhoneAccountHandle,
+ * Connection)} or {@link android.telecom.ConnectionService#addConference(Conference)},
+ * indicates the ID of this call as it was referred to by the {@code ConnectionService} which
+ * originally created it.
+ *
+ * See {@link Connection#EXTRA_ORIGINAL_CONNECTION_ID} for more information.
+ */
+ private String mOriginalConnectionId;
+
+ /**
* Persists the specified parameters and initializes the new instance.
*
* @param context The context.
@@ -1110,6 +1122,34 @@
}
/**
+ * Perform an in-place replacement of the {@link ConnectionServiceWrapper} for this Call.
+ * Removes the call from its former {@link ConnectionServiceWrapper}, ensuring that the
+ * ConnectionService is NOT unbound if the call count hits zero.
+ * This is used by the {@link ConnectionServiceWrapper} when handling {@link Connection} and
+ * {@link Conference} additions via a ConnectionManager.
+ * The original {@link android.telecom.ConnectionService} will directly add external calls and
+ * conferences to Telecom as well as the ConnectionManager, which will add to Telecom. In these
+ * cases since its first added to via the original CS, we want to change the CS responsible for
+ * the call to the ConnectionManager rather than adding it again as another call/conference.
+ *
+ * @param service The new {@link ConnectionServiceWrapper}.
+ */
+ public void replaceConnectionService(ConnectionServiceWrapper service) {
+ Preconditions.checkNotNull(service);
+
+ if (mConnectionService != null) {
+ ConnectionServiceWrapper serviceTemp = mConnectionService;
+ mConnectionService = null;
+ serviceTemp.removeCall(this);
+ serviceTemp.decrementAssociatedCallCount(true /*isSuppressingUnbind*/);
+ }
+
+ service.incrementAssociatedCallCount();
+ mConnectionService = service;
+ mAnalytics.setCallConnectionService(service.getComponentName().flattenToShortString());
+ }
+
+ /**
* Clears the associated connection service.
*/
void clearConnectionService() {
@@ -2173,6 +2213,24 @@
}
}
+ public void setOriginalConnectionId(String originalConnectionId) {
+ mOriginalConnectionId = originalConnectionId;
+ }
+
+ /**
+ * For calls added via a ConnectionManager using the
+ * {@link android.telecom.ConnectionService#addExistingConnection(PhoneAccountHandle,
+ * Connection)}, or {@link android.telecom.ConnectionService#addConference(Conference)} APIS,
+ * indicates the ID of this call as it was referred to by the {@code ConnectionService} which
+ * originally created it.
+ *
+ * See {@link Connection#EXTRA_ORIGINAL_CONNECTION_ID}.
+ * @return The original connection ID.
+ */
+ public String getOriginalConnectionId() {
+ return mOriginalConnectionId;
+ }
+
/**
* Determines if a {@link Call}'s capabilities bitmask indicates that video is supported either
* remotely or locally.
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index fefca78..d0f062a 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -74,6 +74,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -1664,6 +1665,12 @@
call.setVideoProvider(parcelableConference.getVideoProvider());
call.setStatusHints(parcelableConference.getStatusHints());
call.putExtras(Call.SOURCE_CONNECTION_SERVICE, parcelableConference.getExtras());
+ // In case this Conference was added via a ConnectionManager, keep track of the original
+ // Connection ID as created by the originating ConnectionService.
+ Bundle extras = parcelableConference.getExtras();
+ if (extras != null && extras.containsKey(Connection.EXTRA_ORIGINAL_CONNECTION_ID)) {
+ call.setOriginalConnectionId(extras.getString(Connection.EXTRA_ORIGINAL_CONNECTION_ID));
+ }
// TODO: Move this to be a part of addCall()
call.addListener(this);
@@ -2067,14 +2074,44 @@
call.setConnectionProperties(connection.getConnectionProperties());
call.setCallerDisplayName(connection.getCallerDisplayName(),
connection.getCallerDisplayNamePresentation());
-
call.addListener(this);
+
+ // In case this connection was added via a ConnectionManager, keep track of the original
+ // Connection ID as created by the originating ConnectionService.
+ Bundle extras = connection.getExtras();
+ if (extras != null && extras.containsKey(Connection.EXTRA_ORIGINAL_CONNECTION_ID)) {
+ call.setOriginalConnectionId(extras.getString(Connection.EXTRA_ORIGINAL_CONNECTION_ID));
+ }
addCall(call);
return call;
}
/**
+ * Determines whether Telecom already knows about a Connection added via the
+ * {@link android.telecom.ConnectionService#addExistingConnection(PhoneAccountHandle,
+ * Connection)} API via a ConnectionManager.
+ *
+ * See {@link Connection#EXTRA_ORIGINAL_CONNECTION_ID}.
+ * @param originalConnectionId The new connection ID to check.
+ * @return {@code true} if this connection is already known by Telecom.
+ */
+ Call getAlreadyAddedConnection(String originalConnectionId) {
+ Optional<Call> existingCall = mCalls.stream()
+ .filter(call -> originalConnectionId.equals(call.getOriginalConnectionId()) ||
+ originalConnectionId.equals(call.getId()))
+ .findFirst();
+
+ if (existingCall.isPresent()) {
+ Log.i(this, "isExistingConnectionAlreadyAdded - call %s already added with id %s",
+ originalConnectionId, existingCall.get().getId());
+ return existingCall.get();
+ }
+
+ return null;
+ }
+
+ /**
* @return A new unique telecom call Id.
*/
private String getNextCallId() {
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index bf82a99..6ec8945 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -33,7 +33,6 @@
import android.telecom.GatewayInfo;
import android.telecom.ParcelableConference;
import android.telecom.ParcelableConnection;
-import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.StatusHints;
import android.telecom.TelecomManager;
@@ -361,6 +360,8 @@
"call id %s", callId);
return;
}
+ logIncoming("addConferenceCall %s %s [%s]", callId, parcelableConference,
+ parcelableConference.getConnectionIds());
// Make sure that there's at least one valid call. For remote connections
// we'll get a add conference msg from both the remote connection service
@@ -378,16 +379,46 @@
return;
}
- // need to create a new Call
PhoneAccountHandle phAcc = null;
if (parcelableConference != null &&
parcelableConference.getPhoneAccount() != null) {
phAcc = parcelableConference.getPhoneAccount();
}
- Call conferenceCall = mCallsManager.createConferenceCall(callId,
- phAcc, parcelableConference);
- mCallIdMapper.addCall(conferenceCall, callId);
- conferenceCall.setConnectionService(ConnectionServiceWrapper.this);
+
+ Bundle connectionExtras = parcelableConference.getExtras();
+
+ String connectIdToCheck = null;
+ if (connectionExtras != null && connectionExtras
+ .containsKey(Connection.EXTRA_ORIGINAL_CONNECTION_ID)) {
+ // Conference was added via a connection manager, see if its original id is
+ // known.
+ connectIdToCheck = connectionExtras
+ .getString(Connection.EXTRA_ORIGINAL_CONNECTION_ID);
+ } else {
+ connectIdToCheck = callId;
+ }
+
+ Call conferenceCall;
+ // Check to see if this conference has already been added.
+ Call alreadyAddedConnection = mCallsManager
+ .getAlreadyAddedConnection(connectIdToCheck);
+ if (alreadyAddedConnection != null && mCallIdMapper.getCall(callId) == null) {
+ // We are currently attempting to add the conference via a connection mgr,
+ // and the originating ConnectionService has already added it. Instead of
+ // making a new Telecom call, we will simply add it to the ID mapper here,
+ // and replace the ConnectionService on the call.
+ mCallIdMapper.addCall(alreadyAddedConnection, callId);
+ alreadyAddedConnection.replaceConnectionService(
+ ConnectionServiceWrapper.this);
+ conferenceCall = alreadyAddedConnection;
+ } else {
+ // need to create a new Call
+ Call newConferenceCall = mCallsManager.createConferenceCall(callId,
+ phAcc, parcelableConference);
+ mCallIdMapper.addCall(newConferenceCall, callId);
+ newConferenceCall.setConnectionService(ConnectionServiceWrapper.this);
+ conferenceCall = newConferenceCall;
+ }
Log.d(this, "adding children to conference %s phAcc %s",
parcelableConference.getConnectionIds(), phAcc);
@@ -597,10 +628,11 @@
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- logIncoming("setConferenceableConnections %s %s", callId,
- conferenceableCallIds);
+
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
+ logIncoming("setConferenceableConnections %s %s", callId,
+ conferenceableCallIds);
List<Call> conferenceableCalls =
new ArrayList<>(conferenceableCallIds.size());
for (String otherId : conferenceableCallIds) {
@@ -643,8 +675,37 @@
phoneAccountHandle = accountHandle;
}
}
+ // Allow the Sim call manager account as well, even if its disabled.
+ if (phoneAccountHandle == null && callingPhoneAccountHandle != null) {
+ if (callingPhoneAccountHandle.equals(
+ mPhoneAccountRegistrar.getSimCallManager(userHandle))) {
+ phoneAccountHandle = callingPhoneAccountHandle;
+ }
+ }
if (phoneAccountHandle != null) {
- logIncoming("addExistingConnection %s %s", callId, connection);
+ logIncoming("addExistingConnection %s %s", callId, connection);
+
+ Bundle connectionExtras = connection.getExtras();
+ String connectIdToCheck = null;
+ if (connectionExtras != null && connectionExtras
+ .containsKey(Connection.EXTRA_ORIGINAL_CONNECTION_ID)) {
+ connectIdToCheck = connectionExtras
+ .getString(Connection.EXTRA_ORIGINAL_CONNECTION_ID);
+ } else {
+ connectIdToCheck = callId;
+ }
+ // Check to see if this Connection has already been added.
+ Call alreadyAddedConnection = mCallsManager
+ .getAlreadyAddedConnection(connectIdToCheck);
+
+ if (alreadyAddedConnection != null
+ && mCallIdMapper.getCall(callId) == null) {
+ mCallIdMapper.addCall(alreadyAddedConnection, callId);
+ alreadyAddedConnection
+ .replaceConnectionService(ConnectionServiceWrapper.this);
+ return;
+ }
+
Call existingCall = mCallsManager
.createCallForExistingConnection(callId, connection);
mCallIdMapper.addCall(existingCall, callId);
@@ -1117,11 +1178,13 @@
}
private void logIncoming(String msg, Object... params) {
- Log.d(this, "ConnectionService -> Telecom: " + msg, params);
+ Log.d(this, "ConnectionService -> Telecom[" + mComponentName.flattenToShortString() + "]: "
+ + msg, params);
}
private void logOutgoing(String msg, Object... params) {
- Log.d(this, "Telecom -> ConnectionService: " + msg, params);
+ Log.d(this, "Telecom -> ConnectionService[" + mComponentName.flattenToShortString() + "]: "
+ + msg, params);
}
private void queryRemoteConnectionServices(final UserHandle userHandle,
diff --git a/src/com/android/server/telecom/ServiceBinder.java b/src/com/android/server/telecom/ServiceBinder.java
index 9a0f7b4..05f0bcb 100644
--- a/src/com/android/server/telecom/ServiceBinder.java
+++ b/src/com/android/server/telecom/ServiceBinder.java
@@ -169,7 +169,7 @@
private final String mServiceAction;
/** The component name of the service to bind to. */
- private final ComponentName mComponentName;
+ protected final ComponentName mComponentName;
/** The set of callbacks waiting for notification of the binding's success or failure. */
private final Set<BindCallback> mCallbacks = new ArraySet<>();
@@ -227,12 +227,16 @@
}
final void decrementAssociatedCallCount() {
+ decrementAssociatedCallCount(false /*isSuppressingUnbind*/);
+ }
+
+ final void decrementAssociatedCallCount(boolean isSuppressingUnbind) {
if (mAssociatedCallCount > 0) {
mAssociatedCallCount--;
Log.v(this, "Call count decrement %d, %s", mAssociatedCallCount,
mComponentName.flattenToShortString());
- if (mAssociatedCallCount == 0) {
+ if (!isSuppressingUnbind && mAssociatedCallCount == 0) {
unbind();
}
} else {