From 823fd3c79dd4f762bbc778e0ce9e2204b6d3d454 Mon Sep 17 00:00:00 2001 From: Santos Cordon Date: Thu, 7 Aug 2014 18:35:18 -0700 Subject: [PATCH] Update conference call APIs. Clean up conference call APIs to use a distinct type separate from Connection. Also allow the addition of Conference calls at any point using addConference() API method. Bug:16844332 Bug:16449372 Change-Id: I34e45fde1aa43559f5f4e29b990929c188b16875 --- api/current.txt | 29 ++- telecomm/java/android/telecomm/Call.java | 48 +++- .../java/android/telecomm/Conference.java | 238 ++++++++++++++++++ .../java/android/telecomm/Connection.java | 128 +++++----- .../android/telecomm/ConnectionService.java | 217 +++++++++++----- .../telecomm/ConnectionServiceAdapter.java | 4 +- .../ConnectionServiceAdapterServant.java | 18 +- .../telecomm/ParcelableConference.aidl | 19 ++ .../telecomm/ParcelableConference.java | 111 ++++++++ .../telecomm/RemoteConnectionService.java | 2 +- .../telecomm/IConnectionServiceAdapter.aidl | 3 +- 11 files changed, 680 insertions(+), 137 deletions(-) create mode 100644 telecomm/java/android/telecomm/Conference.java create mode 100644 telecomm/java/android/telecomm/ParcelableConference.aidl create mode 100644 telecomm/java/android/telecomm/ParcelableConference.java diff --git a/api/current.txt b/api/current.txt index bdeae78665915..b5507abbb26b7 100644 --- a/api/current.txt +++ b/api/current.txt @@ -28838,6 +28838,25 @@ package android.telecomm { field public static final android.os.Parcelable.Creator CREATOR; } + public abstract class Conference { + ctor public Conference(android.telecomm.PhoneAccountHandle); + method public boolean addConnection(android.telecomm.Connection); + method public void destroy(); + method public final int getCapabilities(); + method public final java.util.List getConnections(); + method public final android.telecomm.PhoneAccountHandle getPhoneAccount(); + method public final int getState(); + method public void onDisconnect(); + method public void onHold(); + method public void onSeparate(android.telecomm.Connection); + method public void onUnhold(); + method public void removeConnection(android.telecomm.Connection); + method public final void setActive(); + method public final void setCapabilities(int); + method public final void setDisconnected(int, java.lang.String); + method public final void setOnHold(); + } + public abstract class Connection { ctor public Connection(); method public static android.telecomm.Connection createCanceledConnection(); @@ -28848,22 +28867,19 @@ package android.telecomm { method public final int getCallCapabilities(); method public final java.lang.String getCallerDisplayName(); method public final int getCallerDisplayNamePresentation(); - method public final java.util.List getChildConnections(); + method public final android.telecomm.Conference getConference(); method public final java.util.List getConferenceableConnections(); method public final int getFailureCode(); method public final java.lang.String getFailureMessage(); method public final android.net.Uri getHandle(); method public final int getHandlePresentation(); - method public final android.telecomm.Connection getParentConnection(); method public final int getState(); method public final android.telecomm.StatusHints getStatusHints(); method public final android.telecomm.Connection.VideoProvider getVideoProvider(); method public final int getVideoState(); - method public final boolean isConferenceConnection(); method public final boolean isRequestingRingback(); method public void onAbort(); method public void onAnswer(int); - method public void onChildrenChanged(java.util.List); method public void onConferenceWith(android.telecomm.Connection); method public void onDisconnect(); method public void onHold(); @@ -28882,6 +28898,7 @@ package android.telecomm { method public final void setCallerDisplayName(java.lang.String, int); method public final void setCanceled(); method public final void setConferenceableConnections(java.util.List); + method public final void setConnectionService(android.telecomm.ConnectionService); method public final void setDialing(); method public final void setDisconnected(int, java.lang.String); method public final void setFailed(int, java.lang.String); @@ -28889,7 +28906,6 @@ package android.telecomm { method public final void setInitialized(); method public final void setInitializing(); method public final void setOnHold(); - method public final void setParentConnection(android.telecomm.Connection); method public final void setPostDialWait(java.lang.String); method public final void setRequestingRingback(boolean); method public final void setRinging(); @@ -28952,13 +28968,14 @@ package android.telecomm { public abstract class ConnectionService extends android.app.Service { ctor public ConnectionService(); + method public final void addConference(android.telecomm.Conference); method public final android.telecomm.RemoteConnection createRemoteIncomingConnection(android.telecomm.PhoneAccountHandle, android.telecomm.ConnectionRequest); method public final android.telecomm.RemoteConnection createRemoteOutgoingConnection(android.telecomm.PhoneAccountHandle, android.telecomm.ConnectionRequest); method public final java.util.Collection getAllConnections(); method public final android.os.IBinder onBind(android.content.Intent); + method public void onConference(android.telecomm.Connection, android.telecomm.Connection); method public void onConnectionAdded(android.telecomm.Connection); method public void onConnectionRemoved(android.telecomm.Connection); - method public void onCreateConferenceConnection(java.lang.String, android.telecomm.Connection, android.telecomm.Response); method public android.telecomm.Connection onCreateIncomingConnection(android.telecomm.PhoneAccountHandle, android.telecomm.ConnectionRequest); method public android.telecomm.Connection onCreateOutgoingConnection(android.telecomm.PhoneAccountHandle, android.telecomm.ConnectionRequest); field public static final java.lang.String SERVICE_INTERFACE = "android.telecomm.ConnectionService"; diff --git a/telecomm/java/android/telecomm/Call.java b/telecomm/java/android/telecomm/Call.java index 7223574b28897..d90ec133dd270 100644 --- a/telecomm/java/android/telecomm/Call.java +++ b/telecomm/java/android/telecomm/Call.java @@ -363,6 +363,7 @@ public final class Call { private final Phone mPhone; private final String mTelecommCallId; private final InCallAdapter mInCallAdapter; + private final List mChildrenIds = new ArrayList<>(); private final List mChildren = new ArrayList<>(); private final List mUnmodifiableChildren = Collections.unmodifiableList(mChildren); private final List mListeners = new CopyOnWriteArrayList<>(); @@ -370,7 +371,8 @@ public final class Call { private final List mUnmodifiableConferenceableCalls = Collections.unmodifiableList(mConferenceableCalls); - private Call mParent = null; + private boolean mChildrenCached; + private String mParentId = null; private int mState; private List mCannedTextResponses = null; private String mRemainingPostDialSequence; @@ -513,7 +515,10 @@ public final class Call { * child of any conference {@code Call}s. */ public Call getParent() { - return mParent; + if (mParentId != null) { + return mPhone.internalGetCallByTelecommId(mParentId); + } + return null; } /** @@ -523,6 +528,21 @@ public final class Call { * {@code List} otherwise. */ public List getChildren() { + if (!mChildrenCached) { + mChildrenCached = true; + mChildren.clear(); + + for(String id : mChildrenIds) { + Call call = mPhone.internalGetCallByTelecommId(id); + if (call == null) { + // At least one child was still not found, so do not save true for "cached" + mChildrenCached = false; + } else { + mChildren.add(call); + } + } + } + return mUnmodifiableChildren; } @@ -648,16 +668,18 @@ public final class Call { mState = state; } - if (parcelableCall.getParentCallId() != null) { - mParent = mPhone.internalGetCallByTelecommId(parcelableCall.getParentCallId()); + String parentId = parcelableCall.getParentCallId(); + boolean parentChanged = !Objects.equals(mParentId, parentId); + if (parentChanged) { + mParentId = parentId; } - mChildren.clear(); - if (parcelableCall.getChildCallIds() != null) { - for (int i = 0; i < parcelableCall.getChildCallIds().size(); i++) { - mChildren.add(mPhone.internalGetCallByTelecommId( - parcelableCall.getChildCallIds().get(i))); - } + List childCallIds = parcelableCall.getChildCallIds(); + boolean childrenChanged = !Objects.equals(childCallIds, mChildrenIds); + if (childrenChanged) { + mChildrenIds.clear(); + mChildrenIds.addAll(parcelableCall.getChildCallIds()); + mChildrenCached = false; } List conferenceableCallIds = parcelableCall.getConferenceableCallIds(); @@ -689,6 +711,12 @@ public final class Call { if (videoCallChanged) { fireVideoCallChanged(mVideoCall); } + if (parentChanged) { + fireParentChanged(getParent()); + } + if (childrenChanged) { + fireChildrenChanged(getChildren()); + } // If we have transitioned to DISCONNECTED, that means we need to notify clients and // remove ourselves from the Phone. Note that we do this after completing all state updates diff --git a/telecomm/java/android/telecomm/Conference.java b/telecomm/java/android/telecomm/Conference.java new file mode 100644 index 0000000000000..34b9dae7d83ad --- /dev/null +++ b/telecomm/java/android/telecomm/Conference.java @@ -0,0 +1,238 @@ +/* + * 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 android.telecomm; + +import android.telephony.DisconnectCause; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * Represents a conference call which can contain any number of {@link Connection} objects. + */ +public abstract class Conference { + + /** @hide */ + public abstract static class Listener { + public void onStateChanged(Conference conference, int oldState, int newState) {} + public void onDisconnected(Conference conference, int cause, String message) {} + public void onConnectionAdded(Conference conference, Connection connection) {} + public void onConnectionRemoved(Conference conference, Connection connection) {} + public void onDestroyed(Conference conference) {} + public void onCapabilitiesChanged(Conference conference, int capabilities) {} + } + + private final Set mListeners = new CopyOnWriteArraySet<>(); + private final List mChildConnections = new CopyOnWriteArrayList<>(); + private final List mUnmodifiableChildConnection = + Collections.unmodifiableList(mChildConnections); + + private PhoneAccountHandle mPhoneAccount; + private int mState = Connection.STATE_NEW; + private int mDisconnectCause = DisconnectCause.NOT_VALID; + private int mCapabilities; + private String mDisconnectMessage; + + public Conference(PhoneAccountHandle phoneAccount) { + mPhoneAccount = phoneAccount; + } + + public final PhoneAccountHandle getPhoneAccount() { + return mPhoneAccount; + } + + public final List getConnections() { + return mUnmodifiableChildConnection; + } + + public final int getState() { + return mState; + } + + public final int getCapabilities() { + return mCapabilities; + } + + /** + * Invoked when the Conference and all it's {@link Connection}s should be disconnected. + */ + public void onDisconnect() {} + + /** + * Invoked when the specified {@link Connection} should be separated from the conference call. + * + * @param connection The connection to separate. + */ + public void onSeparate(Connection connection) {} + + /** + * Invoked when the conference should be put on hold. + */ + public void onHold() {} + + /** + * Invoked when the conference should be moved from hold to active. + */ + public void onUnhold() {} + + /** + * Sets state to be on hold. + */ + public final void setOnHold() { + setState(Connection.STATE_HOLDING); + } + + /** + * Sets state to be active. + */ + public final void setActive() { + setState(Connection.STATE_ACTIVE); + } + + /** + * Sets state to disconnected. + * + * @param cause The reason for the disconnection, any of + * {@link android.telephony.DisconnectCause}. + * @param message Optional call-service-provided message about the disconnect. + */ + public final void setDisconnected(int cause, String message) { + mDisconnectCause = cause; + mDisconnectMessage = message; + setState(Connection.STATE_DISCONNECTED); + for (Listener l : mListeners) { + l.onDisconnected(this, mDisconnectCause, mDisconnectMessage); + } + } + + /** + * Sets the cabilities of a conference. + */ + public final void setCapabilities(int capabilities) { + if (capabilities != mCapabilities) { + mCapabilities = capabilities; + + for (Listener l : mListeners) { + l.onCapabilitiesChanged(this, mCapabilities); + } + } + } + + /** + * Adds the specified connection as a child of this conference. + * + * @param connection The connection to add. + * @return True if the connection was successfully added. + */ + public boolean addConnection(Connection connection) { + if (connection != null && !mChildConnections.contains(connection)) { + if (connection.setConference(this)) { + mChildConnections.add(connection); + for (Listener l : mListeners) { + l.onConnectionAdded(this, connection); + } + return true; + } + } + return false; + } + + /** + * Removes the specified connection as a child of this conference. + * + * @param connection The connection to remove. + * @return True if the connection was successfully removed. + */ + public void removeConnection(Connection connection) { + if (connection != null && mChildConnections.remove(connection)) { + connection.resetConference(); + for (Listener l : mListeners) { + l.onConnectionRemoved(this, connection); + } + } + } + + /** + * Tears down the conference object and any of it's current connections. + */ + public void destroy() { + Log.d(this, "destroying conference : %s", this); + // Tear down the children. + for (Connection connection : new ArrayList<>(mChildConnections)) { + Log.d(this, "removing connection %s", connection); + removeConnection(connection); + } + + // If not yet disconnected, set the conference call as disconnected first. + if (mState != Connection.STATE_DISCONNECTED) { + Log.d(this, "setting to disconnected"); + setDisconnected(DisconnectCause.LOCAL, null); + } + + // ...and notify. + for (Listener l : mListeners) { + l.onDestroyed(this); + } + } + + /** + * Add a listener to be notified of a state change. + * + * @param listener The new listener. + * @return This conference. + * @hide + */ + public final Conference addListener(Listener listener) { + mListeners.add(listener); + return this; + } + + /** + * Removes the specified listener. + * + * @param listener The listener to remove. + * @return This conference. + * @hide + */ + public final Conference removeListener(Listener listener) { + mListeners.remove(listener); + return this; + } + + private void setState(int newState) { + if (newState != Connection.STATE_ACTIVE && + newState != Connection.STATE_HOLDING && + newState != Connection.STATE_DISCONNECTED) { + Log.w(this, "Unsupported state transition for Conference call.", + Connection.stateToString(newState)); + return; + } + + if (mState != newState) { + int oldState = mState; + mState = newState; + for (Listener l : mListeners) { + l.onStateChanged(this, oldState, newState); + } + } + } +} diff --git a/telecomm/java/android/telecomm/Connection.java b/telecomm/java/android/telecomm/Connection.java index 78c34a117500e..27debde1ce072 100644 --- a/telecomm/java/android/telecomm/Connection.java +++ b/telecomm/java/android/telecomm/Connection.java @@ -74,7 +74,6 @@ public abstract class Connection { public void onRequestingRingback(Connection c, boolean ringback) {} public void onDestroyed(Connection c) {} public void onCallCapabilitiesChanged(Connection c, int callCapabilities) {} - public void onParentConnectionChanged(Connection c, Connection parent) {} public void onVideoProviderChanged( Connection c, VideoProvider videoProvider) {} public void onAudioModeIsVoipChanged(Connection c, boolean isVoip) {} @@ -82,6 +81,7 @@ public abstract class Connection { public void onStartActivityFromInCall(Connection c, PendingIntent intent) {} public void onConferenceableConnectionsChanged( Connection c, List conferenceableConnections) {} + public void onConferenceChanged(Connection c, Conference conference) {} } public static abstract class VideoProvider { @@ -455,9 +455,6 @@ public abstract class Connection { */ private final Set mListeners = Collections.newSetFromMap( new ConcurrentHashMap(8, 0.9f, 1)); - private final List mChildConnections = new ArrayList<>(); - private final List mUnmodifiableChildConnections = - Collections.unmodifiableList(mChildConnections); private final List mConferenceableConnections = new ArrayList<>(); private final List mUnmodifiableConferenceableConnections = Collections.unmodifiableList(mConferenceableConnections); @@ -470,7 +467,6 @@ public abstract class Connection { private int mCallerDisplayNamePresentation; private boolean mRequestingRingback = false; private int mCallCapabilities; - private Connection mParentConnection; private VideoProvider mVideoProvider; private boolean mAudioModeIsVoip; private StatusHints mStatusHints; @@ -478,6 +474,8 @@ public abstract class Connection { private int mFailureCode; private String mFailureMessage; private boolean mIsCanceled; + private Conference mConference; + private ConnectionService mConnectionService; /** * Create a new Connection. @@ -542,6 +540,14 @@ public abstract class Connection { return mAudioState; } + /** + * @return The conference that this connection is a part of. Null if it is not part of any + * conference. + */ + public final Conference getConference() { + return mConference; + } + /** * Returns whether this connection is requesting that the system play a ringback tone * on its behalf. @@ -550,13 +556,6 @@ public abstract class Connection { return mRequestingRingback; } - /** - * Returns whether this connection is a conference connection (has child connections). - */ - public final boolean isConferenceConnection() { - return !mChildConnections.isEmpty(); - } - /** * @return True if the connection's audio mode is VOIP. */ @@ -655,34 +654,6 @@ public abstract class Connection { } } - /** - * TODO: Needs documentation. - */ - public final void setParentConnection(Connection parentConnection) { - Log.d(this, "parenting %s to %s", this, parentConnection); - if (mParentConnection != parentConnection) { - if (mParentConnection != null) { - mParentConnection.removeChild(this); - } - mParentConnection = parentConnection; - if (mParentConnection != null) { - mParentConnection.addChild(this); - // do something if the child connections goes down to ZERO. - } - for (Listener l : mListeners) { - l.onParentConnectionChanged(this, mParentConnection); - } - } - } - - public final Connection getParentConnection() { - return mParentConnection; - } - - public final List getChildConnections() { - return mUnmodifiableChildConnections; - } - /** * Returns the connection's {@link PhoneCapabilities} */ @@ -936,6 +907,60 @@ public abstract class Connection { return mUnmodifiableConferenceableConnections; } + /* + * @hide + */ + public final void setConnectionService(ConnectionService connectionService) { + if (mConnectionService != null) { + Log.e(this, new Exception(), "Trying to set ConnectionService on a connection " + + "which is already associated with another ConnectionService."); + } else { + mConnectionService = connectionService; + } + } + + /** + * @hide + */ + public final void unsetConnectionService(ConnectionService connectionService) { + if (mConnectionService != connectionService) { + Log.e(this, new Exception(), "Trying to remove ConnectionService from a Connection " + + "that does not belong to the ConnectionService."); + } else { + mConnectionService = null; + } + } + + /** + * Sets the conference that this connection is a part of. This will fail if the connection is + * already part of a conference call. {@link #resetConference} to un-set the conference first. + * + * @param conference The conference. + * @return {@code true} if the conference was successfully set. + * @hide + */ + public final boolean setConference(Conference conference) { + // We check to see if it is already part of another conference. + if (mConference == null && mConnectionService != null && + mConnectionService.containsConference(conference)) { + mConference = conference; + fireConferenceChanged(); + return true; + } + return false; + } + + /** + * Resets the conference that this connection is a part of. + * @hide + */ + public final void resetConference() { + if (mConference != null) { + mConference = null; + fireConferenceChanged(); + } + } + /** * Launches an activity for this connection on top of the in-call UI. * @@ -1021,11 +1046,6 @@ public abstract class Connection { */ public void onPostDialContinue(boolean proceed) {} - /** - * TODO: Needs documentation. - */ - public void onChildrenChanged(List children) {} - /** * Called when the phone account UI was clicked. */ @@ -1073,18 +1093,6 @@ public abstract class Connection { return sNullConnection; } - private void addChild(Connection connection) { - Log.d(this, "adding child %s", connection); - mChildConnections.add(connection); - onChildrenChanged(mChildConnections); - } - - private void removeChild(Connection connection) { - Log.d(this, "removing child %s", connection); - mChildConnections.remove(connection); - onChildrenChanged(mChildConnections); - } - private void setState(int state) { if (mState == STATE_FAILED || mState == STATE_CANCELED) { Log.d(this, "Connection already %s; cannot transition out of this state.", @@ -1139,6 +1147,12 @@ public abstract class Connection { } } + private final void fireConferenceChanged() { + for (Listener l : mListeners) { + l.onConferenceChanged(this, mConference); + } + } + private final void clearConferenceableList() { for (Connection c : mConferenceableConnections) { c.removeConnectionListener(mConnectionDeathListener); diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java index d2d4828054a6f..97a31024ba292 100644 --- a/telecomm/java/android/telecomm/ConnectionService.java +++ b/telecomm/java/android/telecomm/ConnectionService.java @@ -39,6 +39,8 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.UUID; /** * A {@link android.app.Service} that provides telephone connections to processes running on an @@ -73,12 +75,14 @@ public abstract class ConnectionService extends Service { private final Map mConnectionById = new HashMap<>(); private final Map mIdByConnection = new HashMap<>(); + private final Map mConferenceById = new HashMap<>(); + private final Map mIdByConference = new HashMap<>(); private final RemoteConnectionManager mRemoteConnectionManager = new RemoteConnectionManager(); - - private boolean mAreAccountsInitialized = false; private final List mPreInitializationConnectionRequests = new ArrayList<>(); private final ConnectionServiceAdapter mAdapter = new ConnectionServiceAdapter(); + private boolean mAreAccountsInitialized = false; + private final IBinder mBinder = new IConnectionService.Stub() { @Override public void addConnectionServiceAdapter(IConnectionServiceAdapter adapter) { @@ -155,10 +159,10 @@ public abstract class ConnectionService extends Service { } @Override - public void conference(String conferenceCallId, String callId) { + public void conference(String callId1, String callId2) { SomeArgs args = SomeArgs.obtain(); - args.arg1 = conferenceCallId; - args.arg2 = callId; + args.arg1 = callId1; + args.arg2 = callId2; mHandler.obtainMessage(MSG_CONFERENCE, args).sendToTarget(); } @@ -270,9 +274,9 @@ public abstract class ConnectionService extends Service { case MSG_CONFERENCE: { SomeArgs args = (SomeArgs) msg.obj; try { - String conferenceCallId = (String) args.arg1; - String callId = (String) args.arg2; - conference(conferenceCallId, callId); + String callId1 = (String) args.arg1; + String callId2 = (String) args.arg2; + conference(callId1, callId2); } finally { args.recycle(); } @@ -301,6 +305,51 @@ public abstract class ConnectionService extends Service { } }; + private final Conference.Listener mConferenceListener = new Conference.Listener() { + @Override + public void onStateChanged(Conference conference, int oldState, int newState) { + String id = mIdByConference.get(conference); + switch (newState) { + case Connection.STATE_ACTIVE: + mAdapter.setActive(id); + break; + case Connection.STATE_HOLDING: + mAdapter.setOnHold(id); + break; + case Connection.STATE_DISCONNECTED: + // handled by onDisconnected + break; + } + } + + @Override + public void onDisconnected(Conference conference, int cause, String message) { + String id = mIdByConference.get(conference); + mAdapter.setDisconnected(id, cause, message); + } + + @Override + public void onConnectionAdded(Conference conference, Connection connection) { + } + + @Override + public void onConnectionRemoved(Conference conference, Connection connection) { + } + + @Override + public void onDestroyed(Conference conference) { + removeConference(conference); + } + + @Override + public void onCapabilitiesChanged(Conference conference, int capabilities) { + String id = mIdByConference.get(conference); + Log.d(this, "call capabilities: conference: %s", + PhoneCapabilities.toString(capabilities)); + mAdapter.setCallCapabilities(id, capabilities); + } + }; + private final Connection.Listener mConnectionListener = new Connection.Listener() { @Override public void onStateChanged(Connection c, int state) { @@ -382,13 +431,6 @@ public abstract class ConnectionService extends Service { mAdapter.setCallCapabilities(id, capabilities); } - @Override - public void onParentConnectionChanged(Connection c, Connection parent) { - String id = mIdByConnection.get(c); - String parentId = parent == null ? null : mIdByConnection.get(parent); - mAdapter.setIsConferenced(id, parentId); - } - @Override public void onVideoProviderChanged(Connection c, Connection.VideoProvider videoProvider) { String id = mIdByConnection.get(c); @@ -426,6 +468,18 @@ public abstract class ConnectionService extends Service { Collections.sort(conferenceableCallIds); mAdapter.setConferenceableConnections(id, conferenceableCallIds); } + + @Override + public void onConferenceChanged(Connection connection, Conference conference) { + String id = mIdByConnection.get(connection); + if (id != null) { + String conferenceId = null; + if (conference != null) { + conferenceId = mIdByConference.get(conference); + } + mAdapter.setIsConferenced(id, conferenceId); + } + } }; /** {@inheritDoc} */ @@ -483,6 +537,13 @@ public abstract class ConnectionService extends Service { } c.removeConnectionListener(this); } + + @Override + public void onDestroyed(Connection c) { + // Listen to onDestroy in case the connection is destroyed before + // transitioning to another state. + c.removeConnectionListener(this); + } }); Log.d(this, "Connection created in state INITIALIZING"); connectionCreated(callId, request, createdConnection); @@ -586,38 +647,22 @@ public abstract class ConnectionService extends Service { findConnectionForAction(callId, "stopDtmfTone").onStopDtmfTone(); } - private void conference(final String conferenceCallId, String callId) { - Log.d(this, "conference %s, %s", conferenceCallId, callId); + private void conference(String callId1, String callId2) { + Log.d(this, "conference %s, %s", callId1, callId2); - Connection connection = findConnectionForAction(callId, "conference"); - if (connection == Connection.getNullConnection()) { - Log.w(this, "Connection missing in conference request %s.", callId); + Connection connection1 = findConnectionForAction(callId1, "conference"); + if (connection1 == Connection.getNullConnection()) { + Log.w(this, "Connection1 missing in conference request %s.", callId1); return; } - onCreateConferenceConnection(conferenceCallId, connection, - new Response() { - /** ${inheritDoc} */ - @Override - public void onResult(String ignored, Connection... result) { - Log.d(this, "onCreateConference.Response %s", (Object[]) result); - if (result != null && result.length == 1) { - Connection conferenceConnection = result[0]; - if (!mIdByConnection.containsKey(conferenceConnection)) { - Log.v(this, "sending new conference call %s", conferenceCallId); - mAdapter.addConferenceCall(conferenceCallId); - addConnection(conferenceCallId, conferenceConnection); - } - } - } + Connection connection2 = findConnectionForAction(callId2, "conference"); + if (connection2 == Connection.getNullConnection()) { + Log.w(this, "Connection2 missing in conference request %s.", callId2); + return; + } - /** ${inheritDoc} */ - @Override - public void onError(String request, int code, String reason) { - // no-op - } - } - ); + onConference(connection1, connection2); } private void splitFromConference(String callId) { @@ -711,6 +756,40 @@ public abstract class ConnectionService extends Service { connectionManagerPhoneAccount, request, false); } + /** + * Adds a new conference call. When a conference call is created either as a result of an + * explicit request via {@link #onConference} or otherwise, the connection service should supply + * an instance of {@link Conference} by invoking this method. A conference call provided by this + * method will persist until {@link Conference#destroy} is invoked on the conference instance. + * + * @param conference The new conference object. + */ + public final void addConference(Conference conference) { + String id = addConferenceInternal(conference); + if (id != null) { + List connectionIds = new ArrayList<>(2); + for (Connection connection : conference.getConnections()) { + if (mIdByConnection.containsKey(connection)) { + connectionIds.add(mIdByConnection.get(connection)); + } + } + ParcelableConference parcelableConference = new ParcelableConference( + conference.getPhoneAccount(), + conference.getState(), + conference.getCapabilities(), + connectionIds); + mAdapter.addConferenceCall(id, parcelableConference); + + // Go through any child calls and set the parent. + for (Connection connection : conference.getConnections()) { + String connectionId = mIdByConnection.get(connection); + if (connectionId != null) { + mAdapter.setIsConferenced(connectionId, id); + } + } + } + } + /** * Returns all the active {@code Connection}s for which this {@code ConnectionService} * has taken responsibility. @@ -767,22 +846,14 @@ public abstract class ConnectionService extends Service { } /** - * Returns a new or existing conference connection when the the user elects to convert the - * specified connection into a conference call. The specified connection can be any connection - * which had previously specified itself as conference-capable including both simple connections - * and connections previously returned from this method. - *

- * TODO: To be refactored out with conference call re-engineering
- * TODO: Also remove class {@link Response} once this method is removed + * Conference two specified connections. Invoked when the user has made a request to merge the + * specified connections into a conference call. In response, the connection service should + * create an instance of {@link Conference} and pass it into {@link #addConference}. * - * @param connection The connection from which the user opted to start a conference call. - * @param token The token to be passed into the response callback. - * @param callback The callback for providing the potentially-new conference connection. + * @param connection1 A connection to merge into a conference call. + * @param connection2 A connection to merge into a conference call. */ - public void onCreateConferenceConnection( - String token, - Connection connection, - Response callback) {} + public void onConference(Connection connection1, Connection connection2) {} /** * Notifies that a connection has been added to this connection service and sent to Telecomm. @@ -798,6 +869,13 @@ public abstract class ConnectionService extends Service { */ public void onConnectionRemoved(Connection connection) {} + /** + * @hide + */ + public boolean containsConference(Conference conference) { + return mIdByConference.containsKey(conference); + } + private void onAccountsInitialized() { mAreAccountsInitialized = true; for (Runnable r : mPreInitializationConnectionRequests) { @@ -810,11 +888,13 @@ public abstract class ConnectionService extends Service { mConnectionById.put(callId, connection); mIdByConnection.put(connection, callId); connection.addConnectionListener(mConnectionListener); + connection.setConnectionService(this); onConnectionAdded(connection); } private void removeConnection(Connection connection) { String id = mIdByConnection.get(connection); + connection.unsetConnectionService(this); connection.removeConnectionListener(mConnectionListener); mConnectionById.remove(mIdByConnection.get(connection)); mIdByConnection.remove(connection); @@ -822,6 +902,31 @@ public abstract class ConnectionService extends Service { mAdapter.removeCall(id); } + private String addConferenceInternal(Conference conference) { + if (mIdByConference.containsKey(conference)) { + Log.w(this, "Re-adding an existing conference: %s.", conference); + } else if (conference != null) { + String id = UUID.randomUUID().toString(); + mConferenceById.put(id, conference); + mIdByConference.put(conference, id); + conference.addListener(mConferenceListener); + return id; + } + + return null; + } + + private void removeConference(Conference conference) { + if (mIdByConference.containsKey(conference)) { + conference.removeListener(mConferenceListener); + + String id = mIdByConference.get(conference); + mConferenceById.remove(id); + mIdByConference.remove(conference); + mAdapter.removeCall(id); + } + } + private Connection findConnectionForAction(String callId, String action) { if (mConnectionById.containsKey(callId)) { return mConnectionById.get(callId); diff --git a/telecomm/java/android/telecomm/ConnectionServiceAdapter.java b/telecomm/java/android/telecomm/ConnectionServiceAdapter.java index 4144b81e5c844..0188e62bc4b96 100644 --- a/telecomm/java/android/telecomm/ConnectionServiceAdapter.java +++ b/telecomm/java/android/telecomm/ConnectionServiceAdapter.java @@ -256,10 +256,10 @@ final class ConnectionServiceAdapter implements DeathRecipient { * * @param callId The unique ID of the conference call. */ - void addConferenceCall(String callId) { + void addConferenceCall(String callId, ParcelableConference parcelableConference) { for (IConnectionServiceAdapter adapter : mAdapters) { try { - adapter.addConferenceCall(callId); + adapter.addConferenceCall(callId, parcelableConference); } catch (RemoteException ignored) { } } diff --git a/telecomm/java/android/telecomm/ConnectionServiceAdapterServant.java b/telecomm/java/android/telecomm/ConnectionServiceAdapterServant.java index 2632924112760..2654acea9ca0d 100644 --- a/telecomm/java/android/telecomm/ConnectionServiceAdapterServant.java +++ b/telecomm/java/android/telecomm/ConnectionServiceAdapterServant.java @@ -149,9 +149,16 @@ final class ConnectionServiceAdapterServant { } break; } - case MSG_ADD_CONFERENCE_CALL: - mDelegate.addConferenceCall((String) msg.obj); + case MSG_ADD_CONFERENCE_CALL: { + SomeArgs args = (SomeArgs) msg.obj; + try { + mDelegate.addConferenceCall( + (String) args.arg1, (ParcelableConference) args.arg2); + } finally { + args.recycle(); + } break; + } case MSG_REMOVE_CALL: mDelegate.removeCall((String) msg.obj); break; @@ -323,8 +330,11 @@ final class ConnectionServiceAdapterServant { } @Override - public void addConferenceCall(String callId) { - mHandler.obtainMessage(MSG_ADD_CONFERENCE_CALL, callId).sendToTarget(); + public void addConferenceCall(String callId, ParcelableConference parcelableConference) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + args.arg2 = parcelableConference; + mHandler.obtainMessage(MSG_ADD_CONFERENCE_CALL, args).sendToTarget(); } @Override diff --git a/telecomm/java/android/telecomm/ParcelableConference.aidl b/telecomm/java/android/telecomm/ParcelableConference.aidl new file mode 100644 index 0000000000000..a260085877922 --- /dev/null +++ b/telecomm/java/android/telecomm/ParcelableConference.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 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 android.telecomm; + +parcelable ParcelableConference; diff --git a/telecomm/java/android/telecomm/ParcelableConference.java b/telecomm/java/android/telecomm/ParcelableConference.java new file mode 100644 index 0000000000000..b2798613935d1 --- /dev/null +++ b/telecomm/java/android/telecomm/ParcelableConference.java @@ -0,0 +1,111 @@ +/* + * Copyright 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 android.telecomm; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.List; + +/** + * A parcelable representation of a conference connection. + * @hide + */ +public final class ParcelableConference implements Parcelable { + + private PhoneAccountHandle mPhoneAccount; + private int mState; + private int mCapabilities; + private List mConnectionIds; + + public ParcelableConference( + PhoneAccountHandle phoneAccount, + int state, + int capabilities, + List connectionIds) { + mPhoneAccount = phoneAccount; + mState = state; + mCapabilities = capabilities; + mConnectionIds = connectionIds; + } + + @Override + public String toString() { + return (new StringBuffer()) + .append("account: ") + .append(mPhoneAccount) + .append(", state: ") + .append(Connection.stateToString(mState)) + .append(", capabilities: ") + .append(PhoneCapabilities.toString(mCapabilities)) + .append(", children: ") + .append(mConnectionIds) + .toString(); + } + + public PhoneAccountHandle getPhoneAccount() { + return mPhoneAccount; + } + + public int getState() { + return mState; + } + + public int getCapabilities() { + return mCapabilities; + } + + public List getConnectionIds() { + return mConnectionIds; + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator () { + @Override + public ParcelableConference createFromParcel(Parcel source) { + ClassLoader classLoader = ParcelableConference.class.getClassLoader(); + PhoneAccountHandle phoneAccount = source.readParcelable(classLoader); + int state = source.readInt(); + int capabilities = source.readInt(); + List connectionIds = new ArrayList<>(2); + source.readList(connectionIds, classLoader); + + return new ParcelableConference(phoneAccount, state, capabilities, connectionIds); + } + + @Override + public ParcelableConference[] newArray(int size) { + return new ParcelableConference[size]; + } + }; + + /** {@inheritDoc} */ + @Override + public int describeContents() { + return 0; + } + + /** Writes ParcelableConference object into a Parcel. */ + @Override + public void writeToParcel(Parcel destination, int flags) { + destination.writeParcelable(mPhoneAccount, 0); + destination.writeInt(mState); + destination.writeInt(mCapabilities); + destination.writeList(mConnectionIds); + } +} diff --git a/telecomm/java/android/telecomm/RemoteConnectionService.java b/telecomm/java/android/telecomm/RemoteConnectionService.java index 9a1729f20bef8..dedb10e4696ac 100644 --- a/telecomm/java/android/telecomm/RemoteConnectionService.java +++ b/telecomm/java/android/telecomm/RemoteConnectionService.java @@ -135,7 +135,7 @@ final class RemoteConnectionService { } @Override - public void addConferenceCall(String callId) { + public void addConferenceCall(String callId, ParcelableConference parcelableConference) { // not supported for remote connections. } diff --git a/telecomm/java/com/android/internal/telecomm/IConnectionServiceAdapter.aidl b/telecomm/java/com/android/internal/telecomm/IConnectionServiceAdapter.aidl index fd4e9314c8e53..e6ebae5d03277 100644 --- a/telecomm/java/com/android/internal/telecomm/IConnectionServiceAdapter.aidl +++ b/telecomm/java/com/android/internal/telecomm/IConnectionServiceAdapter.aidl @@ -20,6 +20,7 @@ import android.app.PendingIntent; import android.net.Uri; import android.telecomm.ConnectionRequest; import android.telecomm.ParcelableConnection; +import android.telecomm.ParcelableConference; import android.telecomm.StatusHints; import com.android.internal.telecomm.IVideoProvider; @@ -63,7 +64,7 @@ oneway interface IConnectionServiceAdapter { void setIsConferenced(String callId, String conferenceCallId); - void addConferenceCall(String callId); + void addConferenceCall(String callId, in ParcelableConference conference); void removeCall(String callId);