Merge "[TelephonyService] Improve hold capability signal"
diff --git a/src/com/android/services/telephony/CdmaConference.java b/src/com/android/services/telephony/CdmaConference.java
index 19572e9..69ff2a4 100755
--- a/src/com/android/services/telephony/CdmaConference.java
+++ b/src/com/android/services/telephony/CdmaConference.java
@@ -26,16 +26,16 @@
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
import com.android.phone.PhoneGlobals;
-import com.android.phone.common.R;
import java.util.List;
/**
* CDMA-based conference call.
*/
-public class CdmaConference extends Conference {
+public class CdmaConference extends Conference implements Holdable {
private int mCapabilities;
private int mProperties;
+ private boolean mIsHoldable;
public CdmaConference(PhoneAccountHandle phoneAccount) {
super(phoneAccount);
@@ -43,6 +43,8 @@
mProperties = Connection.PROPERTY_GENERIC_CONFERENCE;
setConnectionProperties(mProperties);
+
+ mIsHoldable = false;
}
public void updateCapabilities(int capabilities) {
@@ -199,4 +201,17 @@
}
return (CdmaConnection) connections.get(0);
}
+
+ @Override
+ public void setHoldable(boolean isHoldable) {
+ // Since the CDMA-based conference can't not be held, dont update the capability when this
+ // method called.
+ mIsHoldable = isHoldable;
+ }
+
+ @Override
+ public boolean isChildHoldable() {
+ // The conference can not be a child of other conference.
+ return false;
+ }
}
diff --git a/src/com/android/services/telephony/GsmConnection.java b/src/com/android/services/telephony/GsmConnection.java
index ca547fa..0a58fba 100644
--- a/src/com/android/services/telephony/GsmConnection.java
+++ b/src/com/android/services/telephony/GsmConnection.java
@@ -76,7 +76,7 @@
// hold for IMS calls.
if (!shouldTreatAsEmergencyCall()) {
capabilities |= CAPABILITY_SUPPORT_HOLD;
- if (getState() == STATE_ACTIVE || getState() == STATE_HOLDING) {
+ if (isHoldable() && (getState() == STATE_ACTIVE || getState() == STATE_HOLDING)) {
capabilities |= CAPABILITY_HOLD;
}
}
diff --git a/src/com/android/services/telephony/HoldTracker.java b/src/com/android/services/telephony/HoldTracker.java
new file mode 100644
index 0000000..805802f
--- /dev/null
+++ b/src/com/android/services/telephony/HoldTracker.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2017 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.telecom.Log;
+import android.telecom.PhoneAccountHandle;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @hide
+ */
+public class HoldTracker {
+ private final Map<PhoneAccountHandle, List<Holdable>> mHoldables;
+
+ public HoldTracker() {
+ mHoldables = new HashMap<>();
+ }
+
+ /**
+ * Adds the holdable associated with the {@code phoneAccountHandle}, this method may update
+ * the hold state for all holdable associated with the {@code phoneAccountHandle}.
+ */
+ public void addHoldable(PhoneAccountHandle phoneAccountHandle, Holdable holdable) {
+ if (!mHoldables.containsKey(phoneAccountHandle)) {
+ mHoldables.put(phoneAccountHandle, new ArrayList<>(1));
+ }
+ List<Holdable> holdables = mHoldables.get(phoneAccountHandle);
+ if (!holdables.contains(holdable)) {
+ holdables.add(holdable);
+ updateHoldCapability(phoneAccountHandle);
+ }
+ }
+
+ /**
+ * Removes the holdable associated with the {@code phoneAccountHandle}, this method may update
+ * the hold state for all holdable associated with the {@code phoneAccountHandle}.
+ */
+ public void removeHoldable(PhoneAccountHandle phoneAccountHandle, Holdable holdable) {
+ if (!mHoldables.containsKey(phoneAccountHandle)) {
+ return;
+ }
+
+ if (mHoldables.get(phoneAccountHandle).remove(holdable)) {
+ updateHoldCapability(phoneAccountHandle);
+ }
+ }
+
+ /**
+ * Updates the hold capability for all holdables associated with the {@code phoneAccountHandle}.
+ */
+ public void updateHoldCapability(PhoneAccountHandle phoneAccountHandle) {
+ if (!mHoldables.containsKey(phoneAccountHandle)) {
+ return;
+ }
+
+ List<Holdable> holdables = mHoldables.get(phoneAccountHandle);
+ int topHoldableCount = 0;
+ for (Holdable holdable : holdables) {
+ if (!holdable.isChildHoldable()) {
+ ++topHoldableCount;
+ }
+ }
+
+ Log.d(this, "topHoldableCount = " + topHoldableCount);
+ boolean isHoldable = topHoldableCount < 2;
+ for (Holdable holdable : holdables) {
+ holdable.setHoldable(holdable.isChildHoldable() ? false : isHoldable);
+ }
+ }
+}
diff --git a/src/com/android/services/telephony/Holdable.java b/src/com/android/services/telephony/Holdable.java
new file mode 100644
index 0000000..4002d30
--- /dev/null
+++ b/src/com/android/services/telephony/Holdable.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 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;
+
+/** The inference used to track the hold state of a holdable object. */
+public interface Holdable {
+
+ /** Returns true if this holdable is a child node of other holdable. */
+ boolean isChildHoldable();
+
+ /**
+ * Sets the holdable property for a holdable object.
+ *
+ * @param isHoldable true means this holdable object can be held.
+ */
+ void setHoldable(boolean isHoldable);
+}
+
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index 06bc06f..61c7a72 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -65,7 +65,7 @@
* connection and is responsible for managing the conference participant connections which represent
* the participants.
*/
-public class ImsConference extends Conference {
+public class ImsConference extends Conference implements Holdable {
/**
* Listener used to respond to changes to conference participants. At the conference level we
@@ -240,6 +240,8 @@
*/
private final Object mUpdateSyncRoot = new Object();
+ private boolean mIsHoldable;
+
public void updateConferenceParticipantsAfterCreation() {
if (mConferenceHost != null) {
Log.v(this, "updateConferenceStateAfterCreation :: process participant update");
@@ -283,6 +285,7 @@
Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN;
if (canHoldImsCalls()) {
capabilities |= Connection.CAPABILITY_SUPPORT_HOLD | Connection.CAPABILITY_HOLD;
+ mIsHoldable = true;
}
capabilities = applyHostCapabilities(capabilities,
mConferenceHost.getConnectionCapabilities(),
@@ -508,6 +511,22 @@
// No-op
}
+ @Override
+ public void setHoldable(boolean isHoldable) {
+ mIsHoldable = isHoldable;
+ if (!mIsHoldable) {
+ removeCapability(Connection.CAPABILITY_HOLD);
+ } else {
+ addCapability(Connection.CAPABILITY_HOLD);
+ }
+ }
+
+ @Override
+ public boolean isChildHoldable() {
+ // The conference should not be a child of other conference.
+ return false;
+ }
+
/**
* Changes a bit-mask to add or remove a bit-field.
*
diff --git a/src/com/android/services/telephony/TelephonyConference.java b/src/com/android/services/telephony/TelephonyConference.java
index e379f38..c66d6f2 100644
--- a/src/com/android/services/telephony/TelephonyConference.java
+++ b/src/com/android/services/telephony/TelephonyConference.java
@@ -30,7 +30,9 @@
* TelephonyConnection-based conference call for GSM conferences and IMS conferences (which may
* be either GSM-based or CDMA-based).
*/
-public class TelephonyConference extends Conference {
+public class TelephonyConference extends Conference implements Holdable {
+
+ private boolean mIsHoldable;
public TelephonyConference(PhoneAccountHandle phoneAccount) {
super(phoneAccount);
@@ -40,6 +42,7 @@
Connection.CAPABILITY_MUTE |
Connection.CAPABILITY_MANAGE_CONFERENCE);
setActive();
+ mIsHoldable = true;
}
/**
@@ -176,6 +179,22 @@
return primaryConnection;
}
+ @Override
+ public void setHoldable(boolean isHoldable) {
+ mIsHoldable = isHoldable;
+ if (!mIsHoldable) {
+ removeCapability(Connection.CAPABILITY_HOLD);
+ } else {
+ addCapability(Connection.CAPABILITY_HOLD);
+ }
+ }
+
+ @Override
+ public boolean isChildHoldable() {
+ // The conference should not be a child of other conference.
+ return false;
+ }
+
private Call getMultipartyCallForConnection(Connection connection, String tag) {
com.android.internal.telephony.Connection radioConnection =
getOriginalConnection(connection);
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index d5ff043..c1f65dd 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -33,7 +33,6 @@
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import android.telephony.CarrierConfigManager;
-import android.telephony.DisconnectCause;
import android.telephony.PhoneNumberUtils;
import android.telephony.TelephonyManager;
import android.util.Pair;
@@ -68,7 +67,7 @@
/**
* Base class for CDMA and GSM connections.
*/
-abstract class TelephonyConnection extends Connection {
+abstract class TelephonyConnection extends Connection implements Holdable {
private static final int MSG_PRECISE_CALL_STATE_CHANGED = 1;
private static final int MSG_RINGBACK_TONE = 2;
private static final int MSG_HANDOVER_STATE_CHANGED = 3;
@@ -515,6 +514,13 @@
protected final boolean mIsOutgoing;
/**
+ * Indicates whether the connection can be held. This filed combined with the state of the
+ * connection can determine whether {@link Connection#CAPABILITY_HOLD} should be added to the
+ * connection.
+ */
+ private boolean mIsHoldable;
+
+ /**
* Listeners to our TelephonyConnection specific callbacks
*/
private final Set<TelephonyConnectionListener> mTelephonyListeners = Collections.newSetFromMap(
@@ -768,11 +774,14 @@
}
if (!shouldTreatAsEmergencyCall() && isImsConnection() && canHoldImsCalls()) {
callCapabilities |= CAPABILITY_SUPPORT_HOLD;
- if (getState() == STATE_ACTIVE || getState() == STATE_HOLDING) {
+ if (mIsHoldable && (getState() == STATE_ACTIVE || getState() == STATE_HOLDING)) {
callCapabilities |= CAPABILITY_HOLD;
}
}
+ Log.d(this, "buildConnectionCapabilities: isHoldable = "
+ + mIsHoldable + " State = " + getState() + " capabilities = " + callCapabilities);
+
return callCapabilities;
}
@@ -1473,9 +1482,7 @@
* @return {@code true} if the connection is external, {@code false} otherwise.
*/
private boolean isExternalConnection() {
- return can(mOriginalConnectionCapabilities, Capability.IS_EXTERNAL_CONNECTION)
- && can(mOriginalConnectionCapabilities,
- Capability.IS_EXTERNAL_CONNECTION);
+ return can(mOriginalConnectionCapabilities, Capability.IS_EXTERNAL_CONNECTION);
}
/**
@@ -1737,6 +1744,21 @@
return this;
}
+ @Override
+ public void setHoldable(boolean isHoldable) {
+ mIsHoldable = isHoldable;
+ buildConnectionCapabilities();
+ }
+
+ @Override
+ public boolean isChildHoldable() {
+ return getConference() != null;
+ }
+
+ public boolean isHoldable() {
+ return mIsHoldable;
+ }
+
/**
* Fire a callback to the various listeners for when the original connection is
* set in this {@link TelephonyConnection}
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index ded2468..6b3fe65 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -111,6 +111,13 @@
}
};
+ private final Connection.Listener mConnectionListener = new Connection.Listener() {
+ @Override
+ public void onConferenceChanged(Connection connection, Conference conference) {
+ mHoldTracker.updateHoldCapability(connection.getPhoneAccountHandle());
+ }
+ };
+
private final TelephonyConferenceController mTelephonyConferenceController =
new TelephonyConferenceController(mTelephonyConnectionServiceProxy);
private final CdmaConferenceController mCdmaConferenceController =
@@ -122,6 +129,7 @@
private ComponentName mExpectedComponentName = null;
private RadioOnHelper mRadioOnHelper;
private EmergencyTonePlayer mEmergencyTonePlayer;
+ private HoldTracker mHoldTracker;
// Contains one TelephonyConnection that has placed a call and a memory of which Phones it has
// already tried to connect with. There should be only one TelephonyConnection trying to place a
@@ -253,6 +261,7 @@
mExpectedComponentName = new ComponentName(this, this.getClass());
mEmergencyTonePlayer = new EmergencyTonePlayer(this);
TelecomAccountRegistry.getInstance(this).setTelephonyConnectionService(this);
+ mHoldTracker = new HoldTracker();
}
@Override
@@ -860,6 +869,41 @@
}
}
+ @Override
+ public void onConnectionAdded(Connection connection) {
+ if (connection instanceof Holdable && !isExternalConnection(connection)) {
+ connection.addConnectionListener(mConnectionListener);
+ mHoldTracker.addHoldable(
+ connection.getPhoneAccountHandle(), (Holdable) connection);
+ }
+ }
+
+ @Override
+ public void onConnectionRemoved(Connection connection) {
+ if (connection instanceof Holdable && !isExternalConnection(connection)) {
+ mHoldTracker.removeHoldable(connection.getPhoneAccountHandle(), (Holdable) connection);
+ }
+ }
+
+ @Override
+ public void onConferenceAdded(Conference conference) {
+ if (conference instanceof Holdable) {
+ mHoldTracker.addHoldable(conference.getPhoneAccountHandle(), (Holdable) conference);
+ }
+ }
+
+ @Override
+ public void onConferenceRemoved(Conference conference) {
+ if (conference instanceof Holdable) {
+ mHoldTracker.removeHoldable(conference.getPhoneAccountHandle(), (Holdable) conference);
+ }
+ }
+
+ private boolean isExternalConnection(Connection connection) {
+ return (connection.getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL)
+ == Connection.PROPERTY_IS_EXTERNAL_CALL;
+ }
+
private boolean blockCallForwardingNumberWhileRoaming(Phone phone, String number) {
if (phone == null || TextUtils.isEmpty(number) || !phone.getServiceState().getRoaming()) {
return false;
@@ -969,7 +1013,7 @@
// on which phone account ECall can be placed. After deciding, we should notify Telecom of
// the change so that the proper PhoneAccount can be displayed.
Log.i(this, "updatePhoneAccount setPhoneAccountHandle, account = " + pHandle);
- connection.notifyPhoneAccountChanged(pHandle);
+ connection.setPhoneAccountHandle(pHandle);
}
private void placeOutgoingConnection(
diff --git a/tests/src/com/android/services/telephony/HoldTrackerTest.java b/tests/src/com/android/services/telephony/HoldTrackerTest.java
new file mode 100644
index 0000000..0db10e4
--- /dev/null
+++ b/tests/src/com/android/services/telephony/HoldTrackerTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2017 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 static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.content.ComponentName;
+import android.support.test.runner.AndroidJUnit4;
+import android.telecom.PhoneAccountHandle;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class HoldTrackerTest {
+
+ private HoldTracker mHoldTrackerUT;
+ private PhoneAccountHandle mPhoneAccountHandle1;
+ private PhoneAccountHandle mPhoneAccountHandle2;
+
+ @Before
+ public void setUp() throws Exception {
+ mHoldTrackerUT = new HoldTracker();
+ mPhoneAccountHandle1 =
+ new PhoneAccountHandle(new ComponentName("pkg1", "cls1"), "0");
+ mPhoneAccountHandle2 =
+ new PhoneAccountHandle(new ComponentName("pkg2", "cls2"), "1");
+ }
+
+ @Test
+ public void oneTopHoldableCanBeHeld() {
+ FakeHoldable topHoldable = createHoldable(false);
+ mHoldTrackerUT.addHoldable(mPhoneAccountHandle1, topHoldable);
+
+ assertTrue(topHoldable.canBeHeld());
+ }
+
+ @Test
+ public void childHoldableCanNotBeHeld() {
+ FakeHoldable topHoldable = createHoldable(false);
+ FakeHoldable childHoldable = createHoldable(true);
+ mHoldTrackerUT.addHoldable(mPhoneAccountHandle1, topHoldable);
+ mHoldTrackerUT.addHoldable(mPhoneAccountHandle1, childHoldable);
+
+ assertTrue(topHoldable.canBeHeld());
+ assertFalse(childHoldable.canBeHeld());
+ }
+
+ @Test
+ public void twoTopHoldableWithTheSamePhoneAccountCanNotBeHeld() {
+ FakeHoldable topHoldable1 = createHoldable(false);
+ FakeHoldable topHoldable2 = createHoldable(false);
+ mHoldTrackerUT.addHoldable(mPhoneAccountHandle1, topHoldable1);
+ mHoldTrackerUT.addHoldable(mPhoneAccountHandle1, topHoldable2);
+
+ mHoldTrackerUT.updateHoldCapability(mPhoneAccountHandle1);
+ assertFalse(topHoldable1.canBeHeld());
+ assertFalse(topHoldable2.canBeHeld());
+ }
+
+ @Test
+ public void holdableWithDifferentPhoneAccountDoesNotAffectEachOther() {
+ FakeHoldable topHoldable1 = createHoldable(false);
+ FakeHoldable topHoldable2 = createHoldable(false);
+ mHoldTrackerUT.addHoldable(mPhoneAccountHandle1, topHoldable1);
+ mHoldTrackerUT.addHoldable(mPhoneAccountHandle2, topHoldable2);
+
+ // Both phones account have only one top holdable, so the holdable of each phone account can
+ // be held.
+ assertTrue(topHoldable1.canBeHeld());
+ assertTrue(topHoldable2.canBeHeld());
+ }
+
+ @Test
+ public void removeOneTopHoldableAndUpdateHoldCapabilityCorrectly() {
+ FakeHoldable topHoldable1 = createHoldable(false);
+ FakeHoldable topHoldable2 = createHoldable(false);
+ mHoldTrackerUT.addHoldable(mPhoneAccountHandle1, topHoldable1);
+ mHoldTrackerUT.addHoldable(mPhoneAccountHandle1, topHoldable2);
+ assertFalse(topHoldable1.canBeHeld());
+ assertFalse(topHoldable2.canBeHeld());
+
+ mHoldTrackerUT.removeHoldable(mPhoneAccountHandle1, topHoldable1);
+ assertTrue(topHoldable2.canBeHeld());
+ }
+
+ public FakeHoldable createHoldable(boolean isChildHoldable) {
+ return new FakeHoldable(isChildHoldable);
+ }
+
+ private class FakeHoldable implements Holdable {
+ private boolean mIsChildHoldable;
+ private boolean mIsHoldable;
+
+ FakeHoldable(boolean isChildHoldable) {
+ mIsChildHoldable = isChildHoldable;
+ }
+
+ @Override
+ public boolean isChildHoldable() {
+ return mIsChildHoldable;
+ }
+
+ @Override
+ public void setHoldable(boolean isHoldable) {
+ mIsHoldable = isHoldable;
+ }
+
+ public boolean canBeHeld() {
+ return mIsHoldable;
+ }
+ }
+}