Merge "Modify CallAudioManager\'s audio speedup functionality" into nyc-dev
am: 0125eab
* commit '0125eab617f07007c327811cf3b55e16419739f2':
Modify CallAudioManager's audio speedup functionality
Change-Id: I8cccbe5ec32220e150dad4fc6333254b43d26b82
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 1280950..7bc727c 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -97,8 +97,8 @@
Log.d(LOG_TAG, "Call state changed for TC@%s: %s -> %s", call.getId(),
CallState.toString(oldState), CallState.toString(newState));
- if (mCallStateToCalls.get(oldState) != null) {
- mCallStateToCalls.get(oldState).remove(call);
+ for (int i = 0; i < mCallStateToCalls.size(); i++) {
+ mCallStateToCalls.valueAt(i).remove(call);
}
if (mCallStateToCalls.get(newState) != null) {
mCallStateToCalls.get(newState).add(call);
@@ -157,8 +157,8 @@
Log.d(LOG_TAG, "Call removed with id TC@%s in state %s", call.getId(),
CallState.toString(call.getState()));
- if (mCallStateToCalls.get(call.getState()) != null) {
- mCallStateToCalls.get(call.getState()).remove(call);
+ for (int i = 0; i < mCallStateToCalls.size(); i++) {
+ mCallStateToCalls.valueAt(i).remove(call);
}
updateForegroundCall();
@@ -216,7 +216,7 @@
mCallStateToCalls.get(call.getState()).remove(call);
}
mActiveDialingOrConnectingCalls.add(call);
- mCallAudioModeStateMachine.sendMessage(
+ mCallAudioModeStateMachine.sendMessageWithArgs(
CallAudioModeStateMachine.MT_AUDIO_SPEEDUP_FOR_RINGING_CALL,
makeArgsForModeStateMachine());
}
@@ -267,7 +267,7 @@
if (call != mForegroundCall) {
return;
}
- mCallAudioModeStateMachine.sendMessage(
+ mCallAudioModeStateMachine.sendMessageWithArgs(
CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE,
makeArgsForModeStateMachine());
}
@@ -388,7 +388,8 @@
mRingingCalls.clear();
mRinger.stopRinging();
mRinger.stopCallWaiting();
- mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NO_MORE_RINGING_CALLS,
+ mCallAudioModeStateMachine.sendMessageWithArgs(
+ CallAudioModeStateMachine.NO_MORE_RINGING_CALLS,
makeArgsForModeStateMachine());
}
@@ -451,7 +452,7 @@
@VisibleForTesting
public void setIsTonePlaying(boolean isTonePlaying) {
mIsTonePlaying = isTonePlaying;
- mCallAudioModeStateMachine.sendMessage(
+ mCallAudioModeStateMachine.sendMessageWithArgs(
isTonePlaying ? CallAudioModeStateMachine.TONE_STARTED_PLAYING
: CallAudioModeStateMachine.TONE_STOPPED_PLAYING,
makeArgsForModeStateMachine());
@@ -496,7 +497,7 @@
private void onCallLeavingActiveDialingOrConnecting() {
if (mActiveDialingOrConnectingCalls.size() == 0) {
- mCallAudioModeStateMachine.sendMessage(
+ mCallAudioModeStateMachine.sendMessageWithArgs(
CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS,
makeArgsForModeStateMachine());
}
@@ -504,21 +505,23 @@
private void onCallLeavingRinging() {
if (mRingingCalls.size() == 0) {
- mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NO_MORE_RINGING_CALLS,
+ mCallAudioModeStateMachine.sendMessageWithArgs(
+ CallAudioModeStateMachine.NO_MORE_RINGING_CALLS,
makeArgsForModeStateMachine());
}
}
private void onCallLeavingHold() {
if (mHoldingCalls.size() == 0) {
- mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NO_MORE_HOLDING_CALLS,
+ mCallAudioModeStateMachine.sendMessageWithArgs(
+ CallAudioModeStateMachine.NO_MORE_HOLDING_CALLS,
makeArgsForModeStateMachine());
}
}
private void onCallEnteringActiveDialingOrConnecting() {
if (mActiveDialingOrConnectingCalls.size() == 1) {
- mCallAudioModeStateMachine.sendMessage(
+ mCallAudioModeStateMachine.sendMessageWithArgs(
CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL,
makeArgsForModeStateMachine());
}
@@ -526,14 +529,16 @@
private void onCallEnteringRinging() {
if (mRingingCalls.size() == 1) {
- mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NEW_RINGING_CALL,
+ mCallAudioModeStateMachine.sendMessageWithArgs(
+ CallAudioModeStateMachine.NEW_RINGING_CALL,
makeArgsForModeStateMachine());
}
}
private void onCallEnteringHold() {
if (mHoldingCalls.size() == 1) {
- mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NEW_HOLDING_CALL,
+ mCallAudioModeStateMachine.sendMessageWithArgs(
+ CallAudioModeStateMachine.NEW_HOLDING_CALL,
makeArgsForModeStateMachine());
}
}
@@ -691,4 +696,14 @@
mRinger.stopCallWaiting();
}
}
+
+ @VisibleForTesting
+ public Set<Call> getTrackedCalls() {
+ return mCalls;
+ }
+
+ @VisibleForTesting
+ public SparseArray<LinkedHashSet<Call>> getCallStateToCalls() {
+ return mCallStateToCalls;
+ }
}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/CallAudioModeStateMachine.java b/src/com/android/server/telecom/CallAudioModeStateMachine.java
index 76ddbea..92adca6 100644
--- a/src/com/android/server/telecom/CallAudioModeStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -308,6 +308,13 @@
if (args.isTonePlaying) {
mCallAudioManager.stopCallWaiting();
}
+ // If a MT-audio-speedup call gets disconnected by the connection service
+ // concurrently with the user answering it, we may get this message
+ // indicating that a ringing call has disconnected while this state machine
+ // is in the SimCallFocusState.
+ if (!args.hasActiveOrDialingCalls) {
+ transitionTo(destinationStateAfterNoMoreActiveCalls(args));
+ }
return HANDLED;
case NO_MORE_HOLDING_CALLS:
// Do nothing.
@@ -483,6 +490,10 @@
return currentState == null ? "no state" : currentState.getName();
}
+ public void sendMessageWithArgs(int messageCode, MessageArgs args) {
+ sendMessage(messageCode, args);
+ }
+
@Override
protected void onPreHandleMessage(Message msg) {
if (msg.obj != null && msg.obj instanceof MessageArgs) {
diff --git a/src/com/android/server/telecom/DtmfLocalTonePlayer.java b/src/com/android/server/telecom/DtmfLocalTonePlayer.java
index 20c9dd8..64c0c55 100644
--- a/src/com/android/server/telecom/DtmfLocalTonePlayer.java
+++ b/src/com/android/server/telecom/DtmfLocalTonePlayer.java
@@ -34,7 +34,7 @@
* class employs a concept of a call "session" that starts and stops when the foreground call
* changes.
*/
-class DtmfLocalTonePlayer {
+public class DtmfLocalTonePlayer {
/** Generator used to actually play the tone. */
private ToneGenerator mToneGenerator;
diff --git a/src/com/android/server/telecom/RingbackPlayer.java b/src/com/android/server/telecom/RingbackPlayer.java
index af60b68..bfaf8a2 100644
--- a/src/com/android/server/telecom/RingbackPlayer.java
+++ b/src/com/android/server/telecom/RingbackPlayer.java
@@ -24,7 +24,7 @@
* able to turn off and on as the user switches between calls. This is why it is implemented as its
* own class.
*/
-class RingbackPlayer {
+public class RingbackPlayer {
private final InCallTonePlayer.Factory mPlayerFactory;
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index 8a2b41c..77b1590 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -31,7 +31,7 @@
* Controls the ringtone player.
*/
@VisibleForTesting
-public final class Ringer {
+public class Ringer {
private static final long[] VIBRATION_PATTERN = new long[] {
0, // No delay before starting
1000, // How long to vibrate
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
new file mode 100644
index 0000000..34325f0
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2016 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.server.telecom.tests;
+
+import android.media.ToneGenerator;
+import android.telecom.DisconnectCause;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.SparseArray;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallAudioModeStateMachine;
+import com.android.server.telecom.CallAudioRouteStateMachine;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.CallAudioManager;
+import com.android.server.telecom.DtmfLocalTonePlayer;
+import com.android.server.telecom.InCallTonePlayer;
+import com.android.server.telecom.RingbackPlayer;
+import com.android.server.telecom.Ringer;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.util.LinkedHashSet;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class CallAudioManagerTest extends TelecomTestCase {
+ @Mock private CallAudioRouteStateMachine mCallAudioRouteStateMachine;
+ @Mock private CallsManager mCallsManager;
+ @Mock private CallAudioModeStateMachine mCallAudioModeStateMachine;
+ @Mock private InCallTonePlayer.Factory mPlayerFactory;
+ @Mock private Ringer mRinger;
+ @Mock private RingbackPlayer mRingbackPlayer;
+ @Mock private DtmfLocalTonePlayer mDtmfLocalTonePlayer;
+
+ private CallAudioManager mCallAudioManager;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ doAnswer((invocation) -> {
+ InCallTonePlayer mockInCallTonePlayer = mock(InCallTonePlayer.class);
+ doAnswer((invocation2) -> {
+ mCallAudioManager.setIsTonePlaying(true);
+ return null;
+ }).when(mockInCallTonePlayer).startTone();
+ return mockInCallTonePlayer;
+ }).when(mPlayerFactory).createPlayer(anyInt());
+ mCallAudioManager = new CallAudioManager(
+ mCallAudioRouteStateMachine,
+ mCallsManager,
+ mCallAudioModeStateMachine,
+ mPlayerFactory,
+ mRinger,
+ mRingbackPlayer,
+ mDtmfLocalTonePlayer);
+ }
+
+ @MediumTest
+ public void testSingleIncomingCallFlowWithoutMTSpeedUp() {
+ Call call = createIncomingCall();
+ when(call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO))
+ .thenReturn(false);
+
+ ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
+ ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
+ // Answer the incoming call
+ mCallAudioManager.onIncomingCallAnswered(call);
+ when(call.getState()).thenReturn(CallState.ACTIVE);
+ mCallAudioManager.onCallStateChanged(call, CallState.RINGING, CallState.ACTIVE);
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NO_MORE_RINGING_CALLS), captor.capture());
+ CallAudioModeStateMachine.MessageArgs correctArgs =
+ new CallAudioModeStateMachine.MessageArgs(
+ true, // hasActiveOrDialingCalls
+ false, // hasRingingCalls
+ false, // hasHoldingCalls
+ false, // isTonePlaying
+ false, // foregroundCallIsVoip
+ null // session
+ );
+ assertMessageArgEquality(correctArgs, captor.getValue());
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
+ assertMessageArgEquality(correctArgs, captor.getValue());
+
+ disconnectCall(call);
+ stopTone();
+
+ mCallAudioManager.onCallRemoved(call);
+ verifyProperCleanup();
+ }
+
+ @MediumTest
+ public void testSingleIncomingCallFlowWithMTSpeedUp() {
+ Call call = createIncomingCall();
+ when(call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO))
+ .thenReturn(true);
+
+ ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
+ ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
+ // Answer the incoming call
+ mCallAudioManager.onIncomingCallAnswered(call);
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.MT_AUDIO_SPEEDUP_FOR_RINGING_CALL), captor.capture());
+ CallAudioModeStateMachine.MessageArgs correctArgs =
+ new CallAudioModeStateMachine.MessageArgs(
+ true, // hasActiveOrDialingCalls
+ false, // hasRingingCalls
+ false, // hasHoldingCalls
+ false, // isTonePlaying
+ false, // foregroundCallIsVoip
+ null // session
+ );
+ assertMessageArgEquality(correctArgs, captor.getValue());
+ assertMessageArgEquality(correctArgs, captor.getValue());
+ when(call.getState()).thenReturn(CallState.ACTIVE);
+ mCallAudioManager.onCallStateChanged(call, CallState.RINGING, CallState.ACTIVE);
+
+ disconnectCall(call);
+ stopTone();
+
+ mCallAudioManager.onCallRemoved(call);
+ verifyProperCleanup();
+ }
+
+ @MediumTest
+ public void testSingleOutgoingCall() {
+ Call call = mock(Call.class);
+ when(call.getState()).thenReturn(CallState.CONNECTING);
+
+ mCallAudioManager.onCallAdded(call);
+ assertEquals(call, mCallAudioManager.getForegroundCall());
+ ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
+ ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
+ verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
+ CallAudioModeStateMachine.MessageArgs expectedArgs =
+ new CallAudioModeStateMachine.MessageArgs(
+ true, // hasActiveOrDialingCalls
+ false, // hasRingingCalls
+ false, // hasHoldingCalls
+ false, // isTonePlaying
+ false, // foregroundCallIsVoip
+ null // session
+ );
+ assertMessageArgEquality(expectedArgs, captor.getValue());
+
+ when(call.getState()).thenReturn(CallState.DIALING);
+ mCallAudioManager.onCallStateChanged(call, CallState.CONNECTING, CallState.DIALING);
+ verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
+ assertMessageArgEquality(expectedArgs, captor.getValue());
+ verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
+ anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));
+
+
+ when(call.getState()).thenReturn(CallState.ACTIVE);
+ mCallAudioManager.onCallStateChanged(call, CallState.DIALING, CallState.ACTIVE);
+ verify(mCallAudioModeStateMachine, times(3)).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
+ assertMessageArgEquality(expectedArgs, captor.getValue());
+ verify(mCallAudioModeStateMachine, times(3)).sendMessageWithArgs(
+ anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));
+
+ disconnectCall(call);
+ stopTone();
+
+ mCallAudioManager.onCallRemoved(call);
+ verifyProperCleanup();
+ }
+
+ private Call createIncomingCall() {
+ Call call = mock(Call.class);
+ when(call.getState()).thenReturn(CallState.RINGING);
+
+ mCallAudioManager.onCallAdded(call);
+ assertEquals(call, mCallAudioManager.getForegroundCall());
+ ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
+ ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
+ verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NEW_RINGING_CALL), captor.capture());
+ assertMessageArgEquality(new CallAudioModeStateMachine.MessageArgs(
+ false, // hasActiveOrDialingCalls
+ true, // hasRingingCalls
+ false, // hasHoldingCalls
+ false, // isTonePlaying
+ false, // foregroundCallIsVoip
+ null // session
+ ), captor.getValue());
+
+ return call;
+ }
+
+ private void disconnectCall(Call call) {
+ ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
+ ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
+ CallAudioModeStateMachine.MessageArgs correctArgs;
+
+ when(call.getState()).thenReturn(CallState.DISCONNECTED);
+ when(call.getDisconnectCause()).thenReturn(new DisconnectCause(DisconnectCause.LOCAL,
+ "", "", "", ToneGenerator.TONE_PROP_PROMPT));
+
+ mCallAudioManager.onCallStateChanged(call, CallState.ACTIVE, CallState.DISCONNECTED);
+ verify(mPlayerFactory).createPlayer(InCallTonePlayer.TONE_CALL_ENDED);
+ correctArgs = new CallAudioModeStateMachine.MessageArgs(
+ false, // hasActiveOrDialingCalls
+ false, // hasRingingCalls
+ false, // hasHoldingCalls
+ true, // isTonePlaying
+ false, // foregroundCallIsVoip
+ null // session
+ );
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS), captor.capture());
+ assertMessageArgEquality(correctArgs, captor.getValue());
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.TONE_STARTED_PLAYING), captor.capture());
+ assertMessageArgEquality(correctArgs, captor.getValue());
+ }
+
+ private void stopTone() {
+ ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
+ ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
+ mCallAudioManager.setIsTonePlaying(false);
+ CallAudioModeStateMachine.MessageArgs correctArgs =
+ new CallAudioModeStateMachine.MessageArgs(
+ false, // hasActiveOrDialingCalls
+ false, // hasRingingCalls
+ false, // hasHoldingCalls
+ false, // isTonePlaying
+ false, // foregroundCallIsVoip
+ null // session
+ );
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.TONE_STOPPED_PLAYING), captor.capture());
+ assertMessageArgEquality(correctArgs, captor.getValue());
+ }
+
+ private void verifyProperCleanup() {
+ assertEquals(0, mCallAudioManager.getTrackedCalls().size());
+ SparseArray<LinkedHashSet<Call>> callStateToCalls = mCallAudioManager.getCallStateToCalls();
+ for (int i = 0; i < callStateToCalls.size(); i++) {
+ assertEquals(0, callStateToCalls.valueAt(i).size());
+ }
+ }
+
+ private void assertMessageArgEquality(CallAudioModeStateMachine.MessageArgs expected,
+ CallAudioModeStateMachine.MessageArgs actual) {
+ assertEquals(expected.hasActiveOrDialingCalls, actual.hasActiveOrDialingCalls);
+ assertEquals(expected.hasHoldingCalls, actual.hasHoldingCalls);
+ assertEquals(expected.hasRingingCalls, actual.hasRingingCalls);
+ assertEquals(expected.isTonePlaying, actual.isTonePlaying);
+ assertEquals(expected.foregroundCallIsVoip, actual.foregroundCallIsVoip);
+ }
+}