Merge "Ims: Add support for Adhoc Conference calls" am: ba248894ac am: b54e70f7ae
Change-Id: Idf560b660a3d6d5f026ea240c421d380d4506c2d
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 8773ea2..e158230 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -41,6 +41,7 @@
import android.telecom.GatewayInfo;
import android.telecom.Log;
import android.telecom.Logging.EventManager;
+import android.telecom.ParcelableConference;
import android.telecom.ParcelableConnection;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
@@ -326,6 +327,8 @@
/** The handle with which to establish this call. */
private Uri mHandle;
+ /** The participants with which to establish adhoc conference call */
+ private List<Uri> mParticipants;
/**
* The presentation requirements for the handle. See {@link TelecomManager} for valid values.
*/
@@ -624,15 +627,43 @@
boolean isConference,
ClockProxy clockProxy,
ToastFactory toastFactory) {
+ this(callId, context, callsManager, lock, repository, phoneNumberUtilsAdapter,
+ handle, null, gatewayInfo, connectionManagerPhoneAccountHandle,
+ targetPhoneAccountHandle, callDirection, shouldAttachToExistingConnection,
+ isConference, clockProxy, toastFactory);
+
+ }
+
+ public Call(
+ String callId,
+ Context context,
+ CallsManager callsManager,
+ TelecomSystem.SyncRoot lock,
+ ConnectionServiceRepository repository,
+ PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
+ Uri handle,
+ List<Uri> participants,
+ GatewayInfo gatewayInfo,
+ PhoneAccountHandle connectionManagerPhoneAccountHandle,
+ PhoneAccountHandle targetPhoneAccountHandle,
+ int callDirection,
+ boolean shouldAttachToExistingConnection,
+ boolean isConference,
+ ClockProxy clockProxy,
+ ToastFactory toastFactory) {
+
mId = callId;
mConnectionId = callId;
- mState = isConference ? CallState.ACTIVE : CallState.NEW;
+ mState = (isConference && callDirection != CALL_DIRECTION_INCOMING &&
+ callDirection != CALL_DIRECTION_OUTGOING) ?
+ CallState.ACTIVE : CallState.NEW;
mContext = context;
mCallsManager = callsManager;
mLock = lock;
mRepository = repository;
mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
setHandle(handle);
+ mParticipants = participants;
mPostDialDigits = handle != null
? PhoneNumberUtils.extractPostDialPortion(handle.getSchemeSpecificPart()) : "";
mGatewayInfo = gatewayInfo;
@@ -655,14 +686,14 @@
* @param handle The handle to dial.
* @param gatewayInfo Gateway information to use for the call.
* @param connectionManagerPhoneAccountHandle Account to use for the service managing the call.
-* This account must be one that was registered with the
-* {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} flag.
+ * This account must be one that was registered with the
+ * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} flag.
* @param targetPhoneAccountHandle Account information to use for the call. This account must be
-* one that was registered with the {@link PhoneAccount#CAPABILITY_CALL_PROVIDER} flag.
+ * one that was registered with the {@link PhoneAccount#CAPABILITY_CALL_PROVIDER} flag.
* @param callDirection one of CALL_DIRECTION_INCOMING, CALL_DIRECTION_OUTGOING,
-* or CALL_DIRECTION_UNKNOWN
+ * or CALL_DIRECTION_UNKNOWN
* @param shouldAttachToExistingConnection Set to true to attach the call to an existing
-* connection, regardless of whether it's incoming or outgoing.
+ * connection, regardless of whether it's incoming or outgoing.
* @param connectTimeMillis The connection time of the call.
* @param clockProxy
*/
@@ -1080,6 +1111,16 @@
return mHandle;
}
+ public List<Uri> getParticipants() {
+ return mParticipants;
+ }
+
+ public boolean isAdhocConferenceCall() {
+ return mIsConference &&
+ (mCallDirection == CALL_DIRECTION_OUTGOING ||
+ mCallDirection == CALL_DIRECTION_INCOMING);
+ }
+
public String getPostDialDigits() {
return mPostDialDigits;
}
@@ -1828,6 +1869,39 @@
}
@Override
+ public void handleCreateConferenceSuccess(
+ CallIdMapper idMapper,
+ ParcelableConference conference) {
+ Log.v(this, "handleCreateConferenceSuccessful %s", conference);
+ setTargetPhoneAccount(conference.getPhoneAccount());
+ setHandle(conference.getHandle(), conference.getHandlePresentation());
+
+ setConnectionCapabilities(conference.getConnectionCapabilities());
+ setConnectionProperties(conference.getConnectionProperties());
+ setVideoProvider(conference.getVideoProvider());
+ setVideoState(conference.getVideoState());
+ setRingbackRequested(conference.isRingbackRequested());
+ setStatusHints(conference.getStatusHints());
+ putExtras(SOURCE_CONNECTION_SERVICE, conference.getExtras());
+
+ switch (mCallDirection) {
+ case CALL_DIRECTION_INCOMING:
+ // Listeners (just CallsManager for now) will be responsible for checking whether
+ // the call should be blocked.
+ for (Listener l : mListeners) {
+ l.onSuccessfulIncomingCall(this);
+ }
+ break;
+ case CALL_DIRECTION_OUTGOING:
+ for (Listener l : mListeners) {
+ l.onSuccessfulOutgoingCall(this,
+ getStateFromConnectionState(conference.getState()));
+ }
+ break;
+ }
+ }
+
+ @Override
public void handleCreateConnectionSuccess(
CallIdMapper idMapper,
ParcelableConnection connection) {
@@ -1878,6 +1952,26 @@
}
@Override
+ public void handleCreateConferenceFailure(DisconnectCause disconnectCause) {
+ clearConnectionService();
+ setDisconnectCause(disconnectCause);
+ mCallsManager.markCallAsDisconnected(this, disconnectCause);
+
+ switch (mCallDirection) {
+ case CALL_DIRECTION_INCOMING:
+ for (Listener listener : mListeners) {
+ listener.onFailedIncomingCall(this);
+ }
+ break;
+ case CALL_DIRECTION_OUTGOING:
+ for (Listener listener : mListeners) {
+ listener.onFailedOutgoingCall(this, disconnectCause);
+ }
+ break;
+ }
+ }
+
+ @Override
public void handleCreateConnectionFailure(DisconnectCause disconnectCause) {
clearConnectionService();
setDisconnectCause(disconnectCause);
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 82422db..024a863 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -109,6 +109,7 @@
import com.android.server.telecom.ui.ToastFactory;
import java.util.Arrays;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -1153,6 +1154,11 @@
mListeners.remove(listener);
}
+ void processIncomingConference(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
+ Log.d(this, "processIncomingCallConference");
+ processIncomingCallIntent(phoneAccountHandle, extras, true);
+ }
+
/**
* Starts the process to attach the call to a connection service.
*
@@ -1161,6 +1167,11 @@
* @param extras The optional extras Bundle passed with the intent used for the incoming call.
*/
void processIncomingCallIntent(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
+ processIncomingCallIntent(phoneAccountHandle, extras, false);
+ }
+
+ void processIncomingCallIntent(PhoneAccountHandle phoneAccountHandle, Bundle extras,
+ boolean isConference) {
Log.d(this, "processIncomingCallIntent");
boolean isHandover = extras.getBoolean(TelecomManager.EXTRA_IS_HANDOVER);
Uri handle = extras.getParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS);
@@ -1181,7 +1192,7 @@
phoneAccountHandle,
Call.CALL_DIRECTION_INCOMING /* callDirection */,
false /* forceAttachToExistingConnection */,
- false, /* isConference */
+ isConference, /* isConference */
mClockProxy,
mToastFactory);
@@ -1295,14 +1306,22 @@
if (!isHandoverAllowed || (call.isSelfManaged() && !isIncomingCallPermitted(call,
call.getTargetPhoneAccount()))) {
- notifyCreateConnectionFailed(phoneAccountHandle, call);
+ if (isConference) {
+ notifyCreateConferenceFailed(phoneAccountHandle, call);
+ } else {
+ notifyCreateConnectionFailed(phoneAccountHandle, call);
+ }
} else if (isInEmergencyCall()) {
// The incoming call is implicitly being rejected so the user does not get any incoming
// call UI during an emergency call. In this case, log the call as missed instead of
// rejected since the user did not explicitly reject.
mCallLogManager.logCall(call, Calls.MISSED_TYPE,
true /*showNotificationForMissedCall*/, null /*CallFilteringResult*/);
- notifyCreateConnectionFailed(phoneAccountHandle, call);
+ if (isConference) {
+ notifyCreateConferenceFailed(phoneAccountHandle, call);
+ } else {
+ notifyCreateConnectionFailed(phoneAccountHandle, call);
+ }
} else {
call.startCreateConnection(mPhoneAccountRegistrar);
}
@@ -1390,7 +1409,18 @@
PhoneAccountHandle requestedAccountHandle,
Bundle extras, UserHandle initiatingUser, Intent originalIntent,
String callingPackage) {
+ final List<Uri> callee = new ArrayList<>();
+ callee.add(handle);
+ return startOutgoingCall(callee, requestedAccountHandle, extras, initiatingUser,
+ originalIntent, callingPackage, false);
+ }
+
+ private CompletableFuture<Call> startOutgoingCall(List<Uri> participants,
+ PhoneAccountHandle requestedAccountHandle,
+ Bundle extras, UserHandle initiatingUser, Intent originalIntent,
+ String callingPackage, boolean isConference) {
boolean isReusedCall;
+ Uri handle = isConference ? Uri.parse("tel:conf-factory") : participants.get(0);
Call call = reuseOutgoingCall(handle);
PhoneAccount account =
@@ -1406,12 +1436,13 @@
mConnectionServiceRepository,
mPhoneNumberUtilsAdapter,
handle,
+ isConference ? participants : null,
null /* gatewayInfo */,
null /* connectionManagerPhoneAccount */,
null /* requestedAccountHandle */,
Call.CALL_DIRECTION_OUTGOING /* callDirection */,
false /* forceAttachToExistingConnection */,
- false, /* isConference */
+ isConference, /* isConference */
mClockProxy,
mToastFactory);
call.initAnalytics(callingPackage);
@@ -1475,7 +1506,8 @@
CompletableFuture.completedFuture((Void) null).thenComposeAsync((x) ->
findOutgoingCallPhoneAccount(requestedAccountHandle, handle,
VideoProfile.isVideo(finalVideoState),
- finalCall.isEmergencyCall(), initiatingUser),
+ finalCall.isEmergencyCall(), initiatingUser,
+ isConference),
new LoggedHandlerExecutor(outgoingCallHandler, "CM.fOCP", mLock));
// This is a block of code that executes after the list of potential phone accts has been
@@ -1543,8 +1575,13 @@
} else {
// If the ongoing call is a managed call, we will prevent the outgoing
// call from dialing.
- notifyCreateConnectionFailed(
- finalCall.getTargetPhoneAccount(), finalCall);
+ if (isConference) {
+ notifyCreateConferenceFailed(finalCall.getTargetPhoneAccount(),
+ finalCall);
+ } else {
+ notifyCreateConnectionFailed(
+ finalCall.getTargetPhoneAccount(), finalCall);
+ }
}
Log.i(CallsManager.this, "Aborting call since there's no room");
return CompletableFuture.completedFuture(null);
@@ -1706,6 +1743,38 @@
return mLatestPostSelectionProcessingFuture;
}
+ public void startConference(List<Uri> participants, Bundle clientExtras, String callingPackage,
+ UserHandle initiatingUser) {
+
+ if (clientExtras == null) {
+ clientExtras = new Bundle();
+ }
+
+ PhoneAccountHandle phoneAccountHandle = clientExtras.getParcelable(
+ TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
+ CompletableFuture<Call> callFuture = startOutgoingCall(participants, phoneAccountHandle,
+ clientExtras, initiatingUser, null/* originalIntent */, callingPackage,
+ true/* isconference*/);
+
+ final boolean speakerphoneOn = clientExtras.getBoolean(
+ TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE);
+ final int videoState = clientExtras.getInt(
+ TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE);
+
+ final Session logSubsession = Log.createSubsession();
+ callFuture.thenAccept((call) -> {
+ if (call != null) {
+ Log.continueSession(logSubsession, "CM.pOGC");
+ try {
+ placeOutgoingCall(call, call.getHandle(), null/* gatewayInfo */,
+ speakerphoneOn, videoState);
+ } finally {
+ Log.endSession();
+ }
+ }
+ });
+ }
+
/**
* Performs call identification for an outgoing phone call.
* @param theCall The outgoing call to perform identification.
@@ -1768,6 +1837,14 @@
public CompletableFuture<List<PhoneAccountHandle>> findOutgoingCallPhoneAccount(
PhoneAccountHandle targetPhoneAccountHandle, Uri handle, boolean isVideo,
boolean isEmergency, UserHandle initiatingUser) {
+ return findOutgoingCallPhoneAccount(targetPhoneAccountHandle, handle, isVideo,
+ isEmergency, initiatingUser, false/* isConference */);
+ }
+
+ public CompletableFuture<List<PhoneAccountHandle>> findOutgoingCallPhoneAccount(
+ PhoneAccountHandle targetPhoneAccountHandle, Uri handle, boolean isVideo,
+ boolean isEmergency, UserHandle initiatingUser, boolean isConference) {
+
if (isSelfManaged(targetPhoneAccountHandle, initiatingUser)) {
return CompletableFuture.completedFuture(Arrays.asList(targetPhoneAccountHandle));
}
@@ -1775,12 +1852,13 @@
List<PhoneAccountHandle> accounts;
// Try to find a potential phone account, taking into account whether this is a video
// call.
- accounts = constructPossiblePhoneAccounts(handle, initiatingUser, isVideo, isEmergency);
+ accounts = constructPossiblePhoneAccounts(handle, initiatingUser, isVideo, isEmergency,
+ isConference);
if (isVideo && accounts.size() == 0) {
// Placing a video call but no video capable accounts were found, so consider any
// call capable accounts (we can fallback to audio).
accounts = constructPossiblePhoneAccounts(handle, initiatingUser,
- false /* isVideo */, isEmergency /* isEmergency */);
+ false /* isVideo */, isEmergency /* isEmergency */, isConference);
}
Log.v(this, "findOutgoingCallPhoneAccount: accounts = " + accounts);
@@ -2057,7 +2135,11 @@
// If the account has been set, proceed to place the outgoing call.
// Otherwise the connection will be initiated when the account is set by the user.
if (call.isSelfManaged() && !isOutgoingCallPermitted) {
- notifyCreateConnectionFailed(call.getTargetPhoneAccount(), call);
+ if (call.isAdhocConferenceCall()) {
+ notifyCreateConferenceFailed(call.getTargetPhoneAccount(), call);
+ } else {
+ notifyCreateConnectionFailed(call.getTargetPhoneAccount(), call);
+ }
} else {
if (call.isEmergencyCall()) {
// Drop any ongoing self-managed calls to make way for an emergency call.
@@ -2458,15 +2540,23 @@
@VisibleForTesting
public List<PhoneAccountHandle> constructPossiblePhoneAccounts(Uri handle, UserHandle user,
boolean isVideo, boolean isEmergency) {
+ return constructPossiblePhoneAccounts(handle, user, isVideo, isEmergency, false);
+ }
+
+ public List<PhoneAccountHandle> constructPossiblePhoneAccounts(Uri handle, UserHandle user,
+ boolean isVideo, boolean isEmergency, boolean isConference) {
+
if (handle == null) {
return Collections.emptyList();
}
// If we're specifically looking for video capable accounts, then include that capability,
// otherwise specify no additional capability constraints. When handling the emergency call,
// it also needs to find the phone accounts excluded by CAPABILITY_EMERGENCY_CALLS_ONLY.
+ int capabilities = isVideo ? PhoneAccount.CAPABILITY_VIDEO_CALLING : 0;
+ capabilities |= isConference ? PhoneAccount.CAPABILITY_ADHOC_CONFERENCE_CALLING : 0;
List<PhoneAccountHandle> allAccounts =
mPhoneAccountRegistrar.getCallCapablePhoneAccounts(handle.getScheme(), false, user,
- isVideo ? PhoneAccount.CAPABILITY_VIDEO_CALLING : 0 /* any */,
+ capabilities,
isEmergency ? 0 : PhoneAccount.CAPABILITY_EMERGENCY_CALLS_ONLY);
if (mMaxNumberOfSimultaneouslyActiveSims < 0) {
mMaxNumberOfSimultaneouslyActiveSims =
@@ -4364,6 +4454,29 @@
/**
* Notifies the {@link android.telecom.ConnectionService} associated with a
+ * {@link PhoneAccountHandle} that the attempt to create a new connection has failed.
+ *
+ * @param phoneAccountHandle The {@link PhoneAccountHandle}.
+ * @param call The {@link Call} which could not be added.
+ */
+ private void notifyCreateConferenceFailed(PhoneAccountHandle phoneAccountHandle, Call call) {
+ if (phoneAccountHandle == null) {
+ return;
+ }
+ ConnectionServiceWrapper service = mConnectionServiceRepository.getService(
+ phoneAccountHandle.getComponentName(), phoneAccountHandle.getUserHandle());
+ if (service == null) {
+ Log.i(this, "Found no connection service.");
+ return;
+ } else {
+ call.setConnectionService(service);
+ service.createConferenceFailed(call);
+ }
+ }
+
+
+ /**
+ * Notifies the {@link android.telecom.ConnectionService} associated with a
* {@link PhoneAccountHandle} that the attempt to handover a call has failed.
*
* @param call The handover call
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index dd50318..ca513da 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -99,6 +99,36 @@
}
@Override
+ public void handleCreateConferenceComplete(String callId, ConnectionRequest request,
+ ParcelableConference conference, Session.Info sessionInfo) {
+ Log.startSession(sessionInfo, LogUtils.Sessions.CSW_HANDLE_CREATE_CONNECTION_COMPLETE);
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ logIncoming("handleCreateConferenceComplete %s", callId);
+ ConnectionServiceWrapper.this
+ .handleCreateConferenceComplete(callId, request, conference);
+
+ if (mServiceInterface != null) {
+ logOutgoing("createConferenceComplete %s", callId);
+ try {
+ mServiceInterface.createConferenceComplete(callId,
+ Log.getExternalSession());
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ } catch (Throwable t) {
+ Log.e(ConnectionServiceWrapper.this, t, "");
+ throw t;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ Log.endSession();
+ }
+ }
+
+
+ @Override
public void setActive(String callId, Session.Info sessionInfo) {
Log.startSession(sessionInfo, LogUtils.Sessions.CSW_SET_ACTIVE);
long token = Binder.clearCallingIdentity();
@@ -1093,6 +1123,66 @@
}
/**
+ * Creates a conference for a new outgoing call or attach to an existing incoming call.
+ */
+ public void createConference(final Call call, final CreateConnectionResponse response) {
+ Log.d(this, "createConference(%s) via %s.", call, getComponentName());
+ BindCallback callback = new BindCallback() {
+ @Override
+ public void onSuccess() {
+ String callId = mCallIdMapper.getCallId(call);
+ mPendingResponses.put(callId, response);
+
+ Bundle extras = call.getIntentExtras();
+
+ Log.addEvent(call, LogUtils.Events.START_CONFERENCE,
+ Log.piiHandle(call.getHandle()));
+
+ ConnectionRequest connectionRequest = new ConnectionRequest.Builder()
+ .setAccountHandle(call.getTargetPhoneAccount())
+ .setAddress(call.getHandle())
+ .setExtras(extras)
+ .setVideoState(call.getVideoState())
+ .setTelecomCallId(callId)
+ // For self-managed incoming calls, if there is another ongoing call Telecom
+ // is responsible for showing a UI to ask the user if they'd like to answer
+ // this new incoming call.
+ .setShouldShowIncomingCallUi(
+ !mCallsManager.shouldShowSystemIncomingCallUi(call))
+ .setRttPipeFromInCall(call.getInCallToCsRttPipeForCs())
+ .setRttPipeToInCall(call.getCsToInCallRttPipeForCs())
+ .setParticipants(call.getParticipants())
+ .setIsAdhocConferenceCall(call.isAdhocConferenceCall())
+ .build();
+
+ try {
+ mServiceInterface.createConference(
+ call.getConnectionManagerPhoneAccount(),
+ callId,
+ connectionRequest,
+ call.shouldAttachToExistingConnection(),
+ call.isUnknown(),
+ Log.getExternalSession());
+
+ } catch (RemoteException e) {
+ Log.e(this, e, "Failure to createConference -- %s", getComponentName());
+ mPendingResponses.remove(callId).handleCreateConferenceFailure(
+ new DisconnectCause(DisconnectCause.ERROR, e.toString()));
+ }
+ }
+
+ @Override
+ public void onFailure() {
+ Log.e(this, new Exception(), "Failure to conference %s", getComponentName());
+ response.handleCreateConferenceFailure(new DisconnectCause(DisconnectCause.ERROR));
+ }
+ };
+
+ mBinder.bind(callback, call);
+
+ }
+
+ /**
* Creates a new connection for a new outgoing call or to attach to an existing incoming call.
*/
@VisibleForTesting
@@ -1225,6 +1315,53 @@
mBinder.bind(callback, call);
}
+ /**
+ * Notifies the {@link ConnectionService} associated with a {@link Call} that the request to
+ * create a conference has been denied or failed.
+ * @param call The call.
+ */
+ void createConferenceFailed(final Call call) {
+ Log.d(this, "createConferenceFailed(%s) via %s.", call, getComponentName());
+ BindCallback callback = new BindCallback() {
+ @Override
+ public void onSuccess() {
+ final String callId = mCallIdMapper.getCallId(call);
+ // If still bound, tell the connection service create connection has failed.
+ if (callId != null && isServiceValid("createConferenceFailed")) {
+ Log.addEvent(call, LogUtils.Events.CREATE_CONFERENCE_FAILED,
+ Log.piiHandle(call.getHandle()));
+ try {
+ logOutgoing("createConferenceFailed %s", callId);
+ mServiceInterface.createConferenceFailed(
+ call.getConnectionManagerPhoneAccount(),
+ callId,
+ new ConnectionRequest(
+ call.getTargetPhoneAccount(),
+ call.getHandle(),
+ call.getIntentExtras(),
+ call.getVideoState(),
+ callId,
+ false),
+ call.isIncoming(),
+ Log.getExternalSession());
+ call.setDisconnectCause(new DisconnectCause(DisconnectCause.CANCELED));
+ call.disconnect();
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ @Override
+ public void onFailure() {
+ // Binding failed. Oh no.
+ Log.w(this, "onFailure - could not bind to CS for conf call %s", call.getId());
+ }
+ };
+
+ mBinder.bind(callback, call);
+ }
+
+
void handoverFailed(final Call call, final int reason) {
Log.d(this, "handoverFailed(%s) via %s.", call, getComponentName());
BindCallback callback = new BindCallback() {
@@ -1696,6 +1833,26 @@
}
}
+ private void handleCreateConferenceComplete(
+ String callId,
+ ConnectionRequest request,
+ ParcelableConference conference) {
+ // TODO: Note we are not using parameter "request", which is a side effect of our tacit
+ // assumption that we have at most one outgoing conference attempt per ConnectionService.
+ // This may not continue to be the case.
+ if (conference.getState() == Connection.STATE_DISCONNECTED) {
+ // A conference that begins in the DISCONNECTED state is an indication of
+ // failure to connect; we handle all failures uniformly
+ removeCall(callId, conference.getDisconnectCause());
+ } else {
+ // Successful connection
+ if (mPendingResponses.containsKey(callId)) {
+ mPendingResponses.remove(callId)
+ .handleCreateConferenceSuccess(mCallIdMapper, conference);
+ }
+ }
+ }
+
/**
* Called when the associated connection service dies.
*/
diff --git a/src/com/android/server/telecom/CreateConnectionProcessor.java b/src/com/android/server/telecom/CreateConnectionProcessor.java
index 72c06a3..bfd625f 100644
--- a/src/com/android/server/telecom/CreateConnectionProcessor.java
+++ b/src/com/android/server/telecom/CreateConnectionProcessor.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.telecom.DisconnectCause;
import android.telecom.Log;
+import android.telecom.ParcelableConference;
import android.telecom.ParcelableConnection;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
@@ -245,7 +246,11 @@
mCall.setConnectionService(mService);
setTimeoutIfNeeded(mService, attempt);
if (mCall.isIncoming()) {
- mService.createConnection(mCall, CreateConnectionProcessor.this);
+ if (mCall.isAdhocConferenceCall()) {
+ mService.createConference(mCall, CreateConnectionProcessor.this);
+ } else {
+ mService.createConnection(mCall, CreateConnectionProcessor.this);
+ }
} else {
// Start to create the connection for outgoing call after the ConnectionService
// of the call has gained the focus.
@@ -254,10 +259,16 @@
new CallsManager.RequestCallback(new CallsManager.PendingAction() {
@Override
public void performAction() {
- Log.d(this, "perform create connection");
- mService.createConnection(
- mCall,
- CreateConnectionProcessor.this);
+ if (mCall.isAdhocConferenceCall()) {
+ Log.d(this, "perform create conference");
+ mService.createConference(mCall,
+ CreateConnectionProcessor.this);
+ } else {
+ Log.d(this, "perform create connection");
+ mService.createConnection(
+ mCall,
+ CreateConnectionProcessor.this);
+ }
}
}));
@@ -267,7 +278,11 @@
Log.v(this, "attemptNextPhoneAccount, no more accounts, failing");
DisconnectCause disconnectCause = mLastErrorDisconnectCause != null ?
mLastErrorDisconnectCause : new DisconnectCause(DisconnectCause.ERROR);
- notifyCallConnectionFailure(disconnectCause);
+ if (mCall.isAdhocConferenceCall()) {
+ notifyConferenceCallFailure(disconnectCause);
+ } else {
+ notifyCallConnectionFailure(disconnectCause);
+ }
}
}
@@ -457,6 +472,16 @@
}
}
+ private void notifyConferenceCallFailure(DisconnectCause errorDisconnectCause) {
+ if (mCallResponse != null) {
+ clearTimeout();
+ mCallResponse.handleCreateConferenceFailure(errorDisconnectCause);
+ mCallResponse = null;
+ mCall.clearConnectionService();
+ }
+ }
+
+
@Override
public void handleCreateConnectionSuccess(
CallIdMapper idMapper,
@@ -475,6 +500,25 @@
}
}
+ @Override
+ public void handleCreateConferenceSuccess(
+ CallIdMapper idMapper,
+ ParcelableConference conference) {
+ if (mCallResponse == null) {
+ // Nobody is listening for this conference attempt any longer; ask the responsible
+ // ConnectionService to tear down any resources associated with the call
+ mService.abort(mCall);
+ } else {
+ // Success -- share the good news and remember that we are no longer interested
+ // in hearing about any more attempts
+ mCallResponse.handleCreateConferenceSuccess(idMapper, conference);
+ mCallResponse = null;
+ // If there's a timeout running then don't clear it. The timeout can be triggered
+ // after the call has successfully been created but before it has become active.
+ }
+ }
+
+
private boolean shouldFailCallIfConnectionManagerFails(DisconnectCause cause) {
// Connection Manager does not exist or does not match registered Connection Manager
// Since Connection manager is a proxy for SIM, fall back to SIM
@@ -522,6 +566,18 @@
attemptNextPhoneAccount();
}
+ @Override
+ public void handleCreateConferenceFailure(DisconnectCause errorDisconnectCause) {
+ // Failure of some sort; record the reasons for failure and try again if possible
+ Log.d(CreateConnectionProcessor.this, "Conference failed: (%s)", errorDisconnectCause);
+ if (shouldFailCallIfConnectionManagerFails(errorDisconnectCause)) {
+ notifyConferenceCallFailure(errorDisconnectCause);
+ return;
+ }
+ mLastErrorDisconnectCause = errorDisconnectCause;
+ attemptNextPhoneAccount();
+ }
+
public void sortSimPhoneAccountsForEmergency(List<PhoneAccount> accounts,
PhoneAccount userPreferredAccount) {
// Sort the accounts according to how we want to display them (ascending order).
diff --git a/src/com/android/server/telecom/CreateConnectionResponse.java b/src/com/android/server/telecom/CreateConnectionResponse.java
index 8e3d0cf..601a165 100644
--- a/src/com/android/server/telecom/CreateConnectionResponse.java
+++ b/src/com/android/server/telecom/CreateConnectionResponse.java
@@ -17,6 +17,7 @@
package com.android.server.telecom;
import android.telecom.DisconnectCause;
+import android.telecom.ParcelableConference;
import android.telecom.ParcelableConnection;
import com.android.internal.annotations.VisibleForTesting;
@@ -28,4 +29,7 @@
public interface CreateConnectionResponse {
void handleCreateConnectionSuccess(CallIdMapper idMapper, ParcelableConnection connection);
void handleCreateConnectionFailure(DisconnectCause disconnectCaused);
+
+ void handleCreateConferenceSuccess(CallIdMapper idMapper, ParcelableConference conference);
+ void handleCreateConferenceFailure(DisconnectCause disconnectCaused);
}
diff --git a/src/com/android/server/telecom/LogUtils.java b/src/com/android/server/telecom/LogUtils.java
index 9b089a2..f586a9d 100644
--- a/src/com/android/server/telecom/LogUtils.java
+++ b/src/com/android/server/telecom/LogUtils.java
@@ -120,6 +120,8 @@
public static final String STOP_CALL_WAITING_TONE = "STOP_CALL_WAITING_TONE";
public static final String START_CONNECTION = "START_CONNECTION";
public static final String CREATE_CONNECTION_FAILED = "CREATE_CONNECTION_FAILED";
+ public static final String START_CONFERENCE = "START_CONFERENCE";
+ public static final String CREATE_CONFERENCE_FAILED = "CREATE_CONFERENCE_FAILED";
public static final String BIND_CS = "BIND_CS";
public static final String CS_BOUND = "CS_BOUND";
public static final String CONFERENCE_WITH = "CONF_WITH";
diff --git a/src/com/android/server/telecom/ParcelableCallUtils.java b/src/com/android/server/telecom/ParcelableCallUtils.java
index 69d9c5e..d1d8f0d 100644
--- a/src/com/android/server/telecom/ParcelableCallUtils.java
+++ b/src/com/android/server/telecom/ParcelableCallUtils.java
@@ -558,7 +558,10 @@
android.telecom.Call.Details.PROPERTY_RTT,
Connection.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL,
- android.telecom.Call.Details.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL
+ android.telecom.Call.Details.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL,
+
+ Connection.PROPERTY_IS_ADHOC_CONFERENCE,
+ android.telecom.Call.Details.PROPERTY_IS_ADHOC_CONFERENCE
};
private static int convertConnectionToCallProperties(int connectionProperties) {
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 3daa452..b75c0a6 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -1172,6 +1172,54 @@
}
/**
+ * @see android.telecom.TelecomManager#addNewIncomingConference
+ */
+ @Override
+ public void addNewIncomingConference(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
+ try {
+ Log.startSession("TSI.aNIC");
+ synchronized (mLock) {
+ Log.i(this, "Adding new incoming conference with phoneAccountHandle %s",
+ phoneAccountHandle);
+ if (phoneAccountHandle != null &&
+ phoneAccountHandle.getComponentName() != null) {
+ if (isCallerSimCallManager(phoneAccountHandle)
+ && TelephonyUtil.isPstnComponentName(
+ phoneAccountHandle.getComponentName())) {
+ Log.v(this, "Allowing call manager to add incoming conference" +
+ " with PSTN handle");
+ } else {
+ mAppOpsManager.checkPackage(
+ Binder.getCallingUid(),
+ phoneAccountHandle.getComponentName().getPackageName());
+ // Make sure it doesn't cross the UserHandle boundary
+ enforceUserHandleMatchesCaller(phoneAccountHandle);
+ enforcePhoneAccountIsRegisteredEnabled(phoneAccountHandle,
+ Binder.getCallingUserHandle());
+ if (isSelfManagedConnectionService(phoneAccountHandle)) {
+ throw new SecurityException("Self-Managed ConnectionServices cannot add "
+ + "adhoc conference calls");
+ }
+ }
+ long token = Binder.clearCallingIdentity();
+ try {
+ mCallsManager.processIncomingConference(
+ phoneAccountHandle, extras);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ } else {
+ Log.w(this, "Null phoneAccountHandle. Ignoring request to add new" +
+ " incoming conference");
+ }
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+
+
+ /**
* @see android.telecom.TelecomManager#acceptHandover
*/
@Override
@@ -1274,6 +1322,25 @@
}
/**
+ * @see android.telecom.TelecomManager#startConference.
+ */
+ @Override
+ public void startConference(List<Uri> participants, Bundle extras,
+ String callingPackage) {
+ try {
+ Log.startSession("TSI.sC");
+ if (!canCallPhone(callingPackage, "startConference")) {
+ throw new SecurityException("Package " + callingPackage + " is not allowed"
+ + " to start conference call");
+ }
+ mCallsManager.startConference(participants, extras, callingPackage,
+ Binder.getCallingUserHandle());
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ /**
* @see android.telecom.TelecomManager#placeCall
*/
@Override
diff --git a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
index 131e591..bcac5f1 100644
--- a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
@@ -268,6 +268,26 @@
}
@Override
+ public void createConference(
+ PhoneAccountHandle connectionManagerPhoneAccount,
+ String id,
+ ConnectionRequest request,
+ boolean isIncoming,
+ boolean isUnknown,
+ Session.Info sessionInfo) { }
+
+ @Override
+ public void createConferenceComplete(String id, Session.Info sessionInfo) { }
+
+ @Override
+ public void createConferenceFailed(
+ PhoneAccountHandle connectionManagerPhoneAccount,
+ String callId,
+ ConnectionRequest request,
+ boolean isIncoming,
+ Session.Info sessionInfo) { }
+
+ @Override
public void abort(String callId, Session.Info info) throws RemoteException { }
@Override