Support conference calling. (3/4) [DO NOT MERGE]
Bug: 15006702
Change-Id: I2764ea242f783ba478c9eae86618dd33e9fc792a
diff --git a/src/com/android/services/telephony/ConferenceConnection.java b/src/com/android/services/telephony/ConferenceConnection.java
new file mode 100644
index 0000000..43095b1
--- /dev/null
+++ b/src/com/android/services/telephony/ConferenceConnection.java
@@ -0,0 +1,73 @@
+/*
+ * 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.telecomm.Connection;
+import android.telephony.DisconnectCause;
+
+import com.android.internal.telephony.CallStateException;
+
+import java.util.List;
+
+/**
+ * Manages state for a conference call.
+ */
+class ConferenceConnection extends Connection {
+ @Override
+ protected void onChildrenChanged(List<Connection> children) {
+ if (children.isEmpty()) {
+ setDisconnected(DisconnectCause.LOCAL, "conference call disconnected.");
+ setDestroyed();
+ }
+ }
+
+ /** ${inheritDoc} */
+ @Override
+ protected void onDisconnect() {
+ // For conference-level disconnects, we need to make sure we disconnect the entire call,
+ // not just one of the connections. To do this, we go through the children and get a
+ // reference to the telephony-Call object and call hangup().
+ for (Connection connection : getChildConnections()) {
+ if (connection instanceof TelephonyConnection) {
+ com.android.internal.telephony.Connection origConnection =
+ ((TelephonyConnection) connection).getOriginalConnection();
+ if (origConnection != null && origConnection.getCall() != null) {
+ try {
+ // getCall() returns what is the parent call of all conferenced conections
+ // so we only need ot call hangup on the main call object. Break once we've
+ // done that.
+ origConnection.getCall().hangup();
+ break;
+ } catch (CallStateException e) {
+ Log.e(this, e, "Call state exception in conference hangup.");
+ }
+ }
+ }
+ }
+ }
+
+ /** ${inheritDoc} */
+ @Override
+ protected void onHold() {
+ List<Connection> children = getChildConnections();
+ if (!children.isEmpty()) {
+ // Hold only needs to be called on one of the children.
+ children.get(0).hold();
+ }
+ }
+
+}
diff --git a/src/com/android/services/telephony/GsmConnection.java b/src/com/android/services/telephony/GsmConnection.java
index 50ef154..d37accd 100644
--- a/src/com/android/services/telephony/GsmConnection.java
+++ b/src/com/android/services/telephony/GsmConnection.java
@@ -16,6 +16,7 @@
package com.android.services.telephony;
+import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.Connection;
import com.android.internal.telephony.Phone;
@@ -41,4 +42,21 @@
getPhone().stopDtmf();
super.onStopDtmfTone();
}
+
+ /** {@inheritDoc} */
+ @Override
+ public void onConference() {
+ try {
+ Log.d(this, "conference - %s", this);
+ getPhone().conference();
+ } catch (CallStateException e) {
+ Log.e(this, e, "Failed to conference call.");
+ }
+ }
+
+ @Override
+ public void setIsConferenceCapable(boolean isConferenceCapable) {
+ // This method increases access modifier.
+ super.setIsConferenceCapable(isConferenceCapable);
+ }
}
diff --git a/src/com/android/services/telephony/GsmConnectionService.java b/src/com/android/services/telephony/GsmConnectionService.java
index 3fa59db..ae5dc22 100644
--- a/src/com/android/services/telephony/GsmConnectionService.java
+++ b/src/com/android/services/telephony/GsmConnectionService.java
@@ -20,16 +20,48 @@
import android.net.Uri;
import android.telephony.TelephonyManager;
+import com.android.internal.telephony.Call;
import com.android.internal.telephony.Connection;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneFactory;
import com.android.phone.Constants;
+
+import java.util.Collection;
+import java.util.HashSet;
+
+import android.telecomm.CallState;
import android.telecomm.ConnectionRequest;
+import android.telecomm.Response;
/**
* Connnection service that uses GSM.
*/
public class GsmConnectionService extends PstnConnectionService {
+
+ private final android.telecomm.Connection.Listener mConnectionListener =
+ new android.telecomm.Connection.ListenerBase() {
+ @Override
+ public void onStateChanged(android.telecomm.Connection c, int state) {
+ // No need to recalculate for conference calls, just traditional calls.
+ if (c != mConferenceConnection) {
+ recalculateConferenceState();
+ }
+ }
+
+ /** ${inheritDoc} */
+ @Override
+ public void onDisconnected(
+ android.telecomm.Connection c, int cause, String message) {
+ // When a connection disconnects, make sure to release its parent reference
+ // so that the parent can move to disconnected as well.
+ c.setParentConnection(null);
+ }
+
+ };
+
+ /** The conferenc connection object. */
+ private ConferenceConnection mConferenceConnection;
+
/** {@inheritDoc} */
@Override
protected Phone getPhone() {
@@ -56,4 +88,73 @@
ConnectionRequest request, Connection connection) {
return new GsmConnection(getPhone(), connection);
}
+
+ /** {@inheritDoc} */
+ @Override
+ public void onConnectionAdded(android.telecomm.Connection connection) {
+ connection.addConnectionListener(mConnectionListener);
+ recalculateConferenceState();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void onConnectionRemoved(android.telecomm.Connection connection) {
+ connection.removeConnectionListener(mConnectionListener);
+ recalculateConferenceState();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void onCreateConferenceConnection(
+ String token,
+ android.telecomm.Connection telecommConnection,
+ Response<String, android.telecomm.Connection> callback) {
+ if (mConferenceConnection == null) {
+ mConferenceConnection = new ConferenceConnection();
+ Log.d(this, "creating the conference connection: %s", mConferenceConnection);
+ }
+ callback.onResult(token, mConferenceConnection);
+ telecommConnection.conference();
+ }
+
+ /**
+ * Calculates the conference-capable state of all connections in this connection service.
+ */
+ private void recalculateConferenceState() {
+ Log.v(this, "recalculateConferenceState");
+ Collection<android.telecomm.Connection> allConnections = this.getAllConnections();
+ for (android.telecomm.Connection connection : new HashSet<>(allConnections)) {
+ Log.d(this, "recalc - %s", connection);
+ if (connection instanceof GsmConnection) {
+ boolean isConferenceCapable = false;
+ Connection radioConnection = ((GsmConnection) connection).getOriginalConnection();
+ if (radioConnection != null) {
+
+ // First calculate to see if we are in the conference call. We only support a
+ // single active conference call on PSTN, which makes things a little easier.
+ if (mConferenceConnection != null) {
+ if (radioConnection.getCall().isMultiparty()) {
+ connection.setParentConnection(mConferenceConnection);
+ } else {
+ connection.setParentConnection(null);
+ }
+ }
+
+ boolean callIsActive = radioConnection.getState() == Call.State.ACTIVE;
+ boolean isConferenced =
+ callIsActive && radioConnection.getCall().isMultiparty();
+ boolean hasBackgroundCall = getPhone().getBackgroundCall().hasConnections();
+ Log.d(this, "recalc: active: %b, is_conf: %b, has_bkgd: %b",
+ callIsActive, isConferenced, hasBackgroundCall);
+ // We only set conference capable on:
+ // 1) Active calls,
+ // 2) which are not already part of a conference call
+ // 3) and there exists a call on HOLD
+ isConferenceCapable = callIsActive && !isConferenced && hasBackgroundCall;
+ }
+
+ ((GsmConnection) connection).setIsConferenceCapable(isConferenceCapable);
+ }
+ }
+ }
}
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 0d84134..43e72c8 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -61,6 +61,18 @@
}
@Override
+ protected void onSeparate() {
+ if (mOriginalConnection != null) {
+ try {
+ mOriginalConnection.separate();
+ } catch (CallStateException e) {
+ Log.e(this, e, "Call to Connection.separate failed with exception");
+ }
+ }
+ super.onSeparate();
+ }
+
+ @Override
protected void onHold() {
Log.d(this, "Attempting to put call on hold");
// TODO(santoscordon): Can dialing calls be put on hold as well since they take up the
@@ -127,8 +139,10 @@
if (mOriginalConnection != null) {
try {
Call call = mOriginalConnection.getCall();
- if (call != null) {
+ if (call != null && !call.isMultiparty()) {
call.hangup();
+ } else {
+ mOriginalConnection.hangup();
}
// Set state deliberately since we are going to close() and will no longer be
// listening to state updates from mOriginalConnection
@@ -146,35 +160,34 @@
}
Call.State newState = mOriginalConnection.getState();
- if (mState == newState) {
- return;
- }
+ Log.v(this, "Update state from %s to %s for %s", mState, newState, this);
+ if (mState != newState) {
+ Log.d(this, "mOriginalConnection new state = %s", newState);
- Log.d(this, "mOriginalConnection new state = %s", newState);
-
- mState = newState;
- switch (newState) {
- case IDLE:
- break;
- case ACTIVE:
- setActive();
- break;
- case HOLDING:
- setOnHold();
- break;
- case DIALING:
- case ALERTING:
- setDialing();
- break;
- case INCOMING:
- case WAITING:
- setRinging();
- break;
- case DISCONNECTED:
- setDisconnected(mOriginalConnection.getDisconnectCause(), null);
- break;
- case DISCONNECTING:
- break;
+ mState = newState;
+ switch (newState) {
+ case IDLE:
+ break;
+ case ACTIVE:
+ setActive();
+ break;
+ case HOLDING:
+ setOnHold();
+ break;
+ case DIALING:
+ case ALERTING:
+ setDialing();
+ break;
+ case INCOMING:
+ case WAITING:
+ setRinging();
+ break;
+ case DISCONNECTED:
+ setDisconnected(mOriginalConnection.getDisconnectCause(), null);
+ break;
+ case DISCONNECTING:
+ break;
+ }
}
}
@@ -185,6 +198,7 @@
call.getPhone().unregisterForPreciseCallStateChanged(mHandler);
}
mOriginalConnection = null;
+ setDestroyed();
}
}