Cache call events to ensure that cap exchange events are not missed
At the beginning of the call, if a call event is sent too soon, it
can result in the voip app missing a call event. Cache the pending call
events in a CachedCallback and deliver them when the ServiceWrapper
is set.
Flag: com.android.server.telecom.flags.cache_call_events
Bug: 364311190
Test: atest TelecomUnitTests:CallTest and manual ICS app testing
Change-Id: Ib577a652736634ca9be81adaed254f74b4d0fc4e
diff --git a/flags/telecom_call_flags.aconfig b/flags/telecom_call_flags.aconfig
index ed75f14..331c328 100644
--- a/flags/telecom_call_flags.aconfig
+++ b/flags/telecom_call_flags.aconfig
@@ -16,6 +16,17 @@
bug: "321369729"
}
+# OWNER=breadley TARGET=24Q4
+flag {
+ name: "cache_call_events"
+ namespace: "telecom"
+ description: "Cache call events to wait for the ServiceWrapper to be set"
+ bug: "364311190"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
# OWNER = breadley TARGET=24Q3
flag {
name: "cancel_removal_on_emergency_redial"
diff --git a/src/com/android/server/telecom/CachedAvailableEndpointsChange.java b/src/com/android/server/telecom/CachedAvailableEndpointsChange.java
index 232f00d..fc98991 100644
--- a/src/com/android/server/telecom/CachedAvailableEndpointsChange.java
+++ b/src/com/android/server/telecom/CachedAvailableEndpointsChange.java
@@ -34,6 +34,11 @@
}
@Override
+ public int getCacheType() {
+ return TYPE_STATE;
+ }
+
+ @Override
public void executeCallback(CallSourceService service, Call call) {
service.onAvailableCallEndpointsChanged(call, mAvailableEndpoints);
}
diff --git a/src/com/android/server/telecom/CachedCallEventQueue.java b/src/com/android/server/telecom/CachedCallEventQueue.java
new file mode 100644
index 0000000..9ce51bf
--- /dev/null
+++ b/src/com/android/server/telecom/CachedCallEventQueue.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 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;
+
+import android.os.Bundle;
+import android.telecom.Log;
+
+public class CachedCallEventQueue implements CachedCallback {
+ public static final String ID = CachedCallEventQueue.class.getSimpleName();
+
+ private final String mEvent;
+ private final Bundle mExtras;
+
+ public CachedCallEventQueue(String event, Bundle extras) {
+ mEvent = event;
+ mExtras = extras;
+ }
+
+ @Override
+ public int getCacheType() {
+ return TYPE_QUEUE;
+ }
+
+ @Override
+ public void executeCallback(CallSourceService service, Call call) {
+ Log.addEvent(call, LogUtils.Events.CALL_EVENT, mEvent);
+ service.sendCallEvent(call, mEvent, mExtras);
+ }
+
+ @Override
+ public String getCallbackId() {
+ return ID;
+ }
+}
diff --git a/src/com/android/server/telecom/CachedCallback.java b/src/com/android/server/telecom/CachedCallback.java
index 88dad07..c354beb 100644
--- a/src/com/android/server/telecom/CachedCallback.java
+++ b/src/com/android/server/telecom/CachedCallback.java
@@ -22,6 +22,27 @@
* The callback will be executed once the service is set.
*/
public interface CachedCallback {
+
+ /**
+ * This callback is caching a state, meaning any new CachedCallbacks with the same
+ * {@link #getCallbackId()} will REPLACE any existing CachedCallback.
+ */
+ int TYPE_STATE = 0;
+ /**
+ * This callback is caching a Queue, meaning that any new CachedCallbacks with the same
+ * {@link #getCallbackId()} will enqueue as a FIFO queue and each instance of this
+ * CachedCallback will run {@link #executeCallback(CallSourceService, Call)}.
+ */
+ int TYPE_QUEUE = 1;
+
+ /**
+ * This method allows the callback to determine whether it is caching a {@link #TYPE_STATE} or
+ * a {@link #TYPE_QUEUE}.
+ *
+ * @return Either {@link #TYPE_STATE} or {@link #TYPE_QUEUE} based on the callback type.
+ */
+ int getCacheType();
+
/**
* This method executes the callback that was cached because the service was not available
* at the time the callback was ready.
@@ -33,11 +54,19 @@
void executeCallback(CallSourceService service, Call call);
/**
- * This method is helpful for caching the callbacks. If the callback is called multiple times
- * while the service is not set, ONLY the last callback should be sent to the client since the
- * last callback is the most relevant
+ * The ID that this CachedCallback should use to identify itself as a distinct operation.
+ * <p>
+ * If {@link #TYPE_STATE} is set for {@link #getCacheType()}, and a CachedCallback with the
+ * same ID is called multiple times while the service is not set, ONLY the last callback will be
+ * sent to the client since the last callback is the most relevant.
+ * <p>
+ * If {@link #TYPE_QUEUE} is set for {@link #getCacheType()} and the CachedCallback with the
+ * same ID is called multiple times while the service is not set, each CachedCallback will be
+ * enqueued in FIFO order. Once the service is set, {@link #executeCallback} will be called
+ * for each CachedCallback with the same ID.
*
- * @return the callback id that is used in a map to only store the last callback value
+ * @return A unique callback id that will be used differentiate this CachedCallback type with
+ * other CachedCallback types.
*/
String getCallbackId();
}
diff --git a/src/com/android/server/telecom/CachedCurrentEndpointChange.java b/src/com/android/server/telecom/CachedCurrentEndpointChange.java
index 0d5bac9..1d838f0 100644
--- a/src/com/android/server/telecom/CachedCurrentEndpointChange.java
+++ b/src/com/android/server/telecom/CachedCurrentEndpointChange.java
@@ -33,6 +33,11 @@
}
@Override
+ public int getCacheType() {
+ return TYPE_STATE;
+ }
+
+ @Override
public void executeCallback(CallSourceService service, Call call) {
service.onCallEndpointChanged(call, mCurrentCallEndpoint);
}
diff --git a/src/com/android/server/telecom/CachedMuteStateChange.java b/src/com/android/server/telecom/CachedMuteStateChange.java
index 45cbfaa..ee1227b 100644
--- a/src/com/android/server/telecom/CachedMuteStateChange.java
+++ b/src/com/android/server/telecom/CachedMuteStateChange.java
@@ -29,6 +29,11 @@
}
@Override
+ public int getCacheType() {
+ return TYPE_STATE;
+ }
+
+ @Override
public void executeCallback(CallSourceService service, Call call) {
service.onMuteStateChanged(call, mIsMuted);
}
diff --git a/src/com/android/server/telecom/CachedVideoStateChange.java b/src/com/android/server/telecom/CachedVideoStateChange.java
index 0892c33..cefb92b 100644
--- a/src/com/android/server/telecom/CachedVideoStateChange.java
+++ b/src/com/android/server/telecom/CachedVideoStateChange.java
@@ -33,6 +33,11 @@
}
@Override
+ public int getCacheType() {
+ return TYPE_STATE;
+ }
+
+ @Override
public void executeCallback(CallSourceService service, Call call) {
service.onVideoStateChanged(call, mCurrentVideoState);
Log.addEvent(call, LogUtils.Events.VIDEO_STATE_CHANGED,
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 59cbdae..9845f1c 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -19,6 +19,8 @@
import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
import static android.telephony.TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE;
+import static com.android.server.telecom.CachedCallback.TYPE_QUEUE;
+import static com.android.server.telecom.CachedCallback.TYPE_STATE;
import static com.android.server.telecom.voip.VideoStateTranslation.TransactionalVideoStateToString;
import static com.android.server.telecom.voip.VideoStateTranslation.VideoProfileStateToTransactionalVideoState;
@@ -850,14 +852,51 @@
*/
private CompletableFuture<Boolean> mBtIcsFuture;
- Map<String, CachedCallback> mCachedServiceCallbacks = new HashMap<>();
+ /**
+ * Map of CachedCallbacks that are pending to be executed when the *ServiceWrapper connects
+ */
+ private final Map<String, List<CachedCallback>> mCachedServiceCallbacks = new HashMap<>();
public void cacheServiceCallback(CachedCallback callback) {
- mCachedServiceCallbacks.put(callback.getCallbackId(), callback);
+ synchronized (mCachedServiceCallbacks) {
+ if (mFlags.cacheCallEvents()) {
+ // If there are multiple threads caching + calling processCachedCallbacks at the
+ // same time, there is a race - double check here to ensure that we do not lose an
+ // operation due to a a cache happening after processCachedCallbacks.
+ // Either service will be non-null in this case, but both will not be non-null
+ if (mConnectionService != null) {
+ callback.executeCallback(mConnectionService, this);
+ return;
+ }
+ if (mTransactionalService != null) {
+ callback.executeCallback(mTransactionalService, this);
+ return;
+ }
+ }
+ List<CachedCallback> cbs = mCachedServiceCallbacks.computeIfAbsent(
+ callback.getCallbackId(), k -> new ArrayList<>());
+ switch (callback.getCacheType()) {
+ case TYPE_STATE: {
+ cbs.clear();
+ cbs.add(callback);
+ break;
+ }
+ case TYPE_QUEUE: {
+ cbs.add(callback);
+ }
+ }
+ }
}
- public Map<String, CachedCallback> getCachedServiceCallbacks() {
- return mCachedServiceCallbacks;
+ @VisibleForTesting
+ public Map<String, List<CachedCallback>> getCachedServiceCallbacksCopy() {
+ synchronized (mCachedServiceCallbacks) {
+ // This should only be used during testing, but to be safe, since there is internally a
+ // List value, we need to do a deep copy to ensure someone with a ref to the Map doesn't
+ // mutate the underlying list while we are modifying it in cacheServiceCallback.
+ return mCachedServiceCallbacks.entrySet().stream().collect(
+ Collectors.toUnmodifiableMap(Map.Entry::getKey, e-> List.copyOf(e.getValue())));
+ }
}
private FeatureFlags mFlags;
@@ -2053,11 +2092,13 @@
private void processCachedCallbacks(CallSourceService service) {
if(mFlags.cacheCallAudioCallbacks()) {
- for (CachedCallback callback : mCachedServiceCallbacks.values()) {
- callback.executeCallback(service, this);
+ synchronized (mCachedServiceCallbacks) {
+ for (List<CachedCallback> callbacks : mCachedServiceCallbacks.values()) {
+ callbacks.forEach( callback -> callback.executeCallback(service, this));
+ }
+ // clear list for memory cleanup purposes. The Service should never be reset
+ mCachedServiceCallbacks.clear();
}
- // clear list for memory cleanup purposes. The Service should never be reset
- mCachedServiceCallbacks.clear();
}
}
@@ -3516,26 +3557,12 @@
}
/**
- * Sends a call event to the {@link ConnectionService} for this call. This function is
- * called for event other than {@link Call#EVENT_REQUEST_HANDOVER}
+ * Sends a call event to the {@link ConnectionService} for this call.
*
* @param event The call event.
* @param extras Associated extras.
*/
public void sendCallEvent(String event, Bundle extras) {
- sendCallEvent(event, 0/*For Event != EVENT_REQUEST_HANDOVER*/, extras);
- }
-
- /**
- * Sends a call event to the {@link ConnectionService} for this call.
- *
- * See {@link Call#sendCallEvent(String, Bundle)}.
- *
- * @param event The call event.
- * @param targetSdkVer SDK version of the app calling this api
- * @param extras Associated extras.
- */
- public void sendCallEvent(String event, int targetSdkVer, Bundle extras) {
if (mConnectionService != null || mTransactionalService != null) {
// Relay bluetooth call quality reports to the call diagnostic service.
if (BluetoothCallQualityReport.EVENT_BLUETOOTH_CALL_QUALITY_REPORT.equals(event)
@@ -3548,19 +3575,25 @@
Log.addEvent(this, LogUtils.Events.CALL_EVENT, event);
sendEventToService(this, event, extras);
} else {
- Log.e(this, new NullPointerException(),
- "sendCallEvent failed due to null CS callId=%s", getId());
+ if (mFlags.cacheCallEvents()) {
+ Log.i(this, "sendCallEvent: caching call event for callId=%s, event=%s",
+ getId(), event);
+ cacheServiceCallback(new CachedCallEventQueue(event, extras));
+ } else {
+ Log.e(this, new NullPointerException(),
+ "sendCallEvent failed due to null CS callId=%s", getId());
+ }
}
}
/**
- * This method should only be called from sendCallEvent(String, int, Bundle).
+ * This method should only be called from sendCallEvent(String, Bundle).
*/
private void sendEventToService(Call call, String event, Bundle extras) {
if (mConnectionService != null) {
mConnectionService.sendCallEvent(call, event, extras);
} else if (mTransactionalService != null) {
- mTransactionalService.onEvent(call, event, extras);
+ mTransactionalService.sendCallEvent(call, event, extras);
}
}
diff --git a/src/com/android/server/telecom/CallSourceService.java b/src/com/android/server/telecom/CallSourceService.java
index d579542..6f16129 100644
--- a/src/com/android/server/telecom/CallSourceService.java
+++ b/src/com/android/server/telecom/CallSourceService.java
@@ -16,6 +16,7 @@
package com.android.server.telecom;
+import android.os.Bundle;
import android.telecom.CallEndpoint;
import java.util.Set;
@@ -37,4 +38,6 @@
void onAvailableCallEndpointsChanged(Call activeCall, Set<CallEndpoint> availableCallEndpoints);
void onVideoStateChanged(Call activeCall, int videoState);
+
+ void sendCallEvent(Call activeCall, String event, Bundle extras);
}
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index bf25f38..5e00a72 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -2299,7 +2299,8 @@
}
}
- void sendCallEvent(Call call, String event, Bundle extras) {
+ @Override
+ public void sendCallEvent(Call call, String event, Bundle extras) {
final String callId = mCallIdMapper.getCallId(call);
if (callId != null && isServiceValid("sendCallEvent")) {
try {
diff --git a/src/com/android/server/telecom/InCallAdapter.java b/src/com/android/server/telecom/InCallAdapter.java
index 514ba48..8836fff 100755
--- a/src/com/android/server/telecom/InCallAdapter.java
+++ b/src/com/android/server/telecom/InCallAdapter.java
@@ -606,7 +606,7 @@
synchronized (mLock) {
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
- call.sendCallEvent(event, targetSdkVer, extras);
+ call.sendCallEvent(event, extras);
} else {
Log.w(this, "sendCallEvent, unknown call id: %s", callId);
}
diff --git a/src/com/android/server/telecom/TransactionalServiceWrapper.java b/src/com/android/server/telecom/TransactionalServiceWrapper.java
index 50ef2e8..b73de23 100644
--- a/src/com/android/server/telecom/TransactionalServiceWrapper.java
+++ b/src/com/android/server/telecom/TransactionalServiceWrapper.java
@@ -626,7 +626,8 @@
}
}
- public void onEvent(Call call, String event, Bundle extras) {
+ @Override
+ public void sendCallEvent(Call call, String event, Bundle extras) {
if (call != null) {
try {
mICallEventCallback.onEvent(call.getId(), event, extras);
diff --git a/tests/src/com/android/server/telecom/tests/CallTest.java b/tests/src/com/android/server/telecom/tests/CallTest.java
index 240e641..fa7d21a 100644
--- a/tests/src/com/android/server/telecom/tests/CallTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallTest.java
@@ -23,10 +23,8 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -42,6 +40,7 @@
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Bundle;
+import android.os.PersistableBundle;
import android.os.UserHandle;
import android.telecom.CallAttributes;
import android.telecom.CallEndpoint;
@@ -56,12 +55,12 @@
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import android.telephony.CallQuality;
-import android.widget.Toast;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.server.telecom.CachedAvailableEndpointsChange;
+import com.android.server.telecom.CachedCallEventQueue;
import com.android.server.telecom.CachedCurrentEndpointChange;
import com.android.server.telecom.CachedMuteStateChange;
import com.android.server.telecom.Call;
@@ -216,6 +215,44 @@
}
@Test
+ public void testMultipleCachedCallEvents() {
+ when(mFeatureFlags.cacheCallAudioCallbacks()).thenReturn(true);
+ when(mFeatureFlags.cacheCallEvents()).thenReturn(true);
+ TransactionalServiceWrapper tsw = Mockito.mock(TransactionalServiceWrapper.class);
+ Call call = createCall("1", Call.CALL_DIRECTION_INCOMING);
+
+ assertNull(call.getTransactionServiceWrapper());
+
+ String testEvent1 = "test1";
+ Bundle testBundle1 = new Bundle();
+ testBundle1.putInt("testKey", 1);
+ call.sendCallEvent(testEvent1, testBundle1);
+ assertEquals(1,
+ call.getCachedServiceCallbacksCopy().get(CachedCallEventQueue.ID).size());
+
+ String testEvent2 = "test2";
+ Bundle testBundle2 = new Bundle();
+ testBundle2.putInt("testKey", 2);
+ call.sendCallEvent(testEvent2, testBundle2);
+ assertEquals(2,
+ call.getCachedServiceCallbacksCopy().get(CachedCallEventQueue.ID).size());
+
+ String testEvent3 = "test3";
+ Bundle testBundle3 = new Bundle();
+ testBundle2.putInt("testKey", 3);
+ call.sendCallEvent(testEvent3, testBundle3);
+ assertEquals(3,
+ call.getCachedServiceCallbacksCopy().get(CachedCallEventQueue.ID).size());
+
+ verify(tsw, times(0)).sendCallEvent(any(), any(), any());
+ call.setTransactionServiceWrapper(tsw);
+ verify(tsw, times(1)).sendCallEvent(any(), eq(testEvent1), eq(testBundle1));
+ verify(tsw, times(1)).sendCallEvent(any(), eq(testEvent2), eq(testBundle2));
+ verify(tsw, times(1)).sendCallEvent(any(), eq(testEvent3), eq(testBundle3));
+ assertEquals(0, call.getCachedServiceCallbacksCopy().size());
+ }
+
+ @Test
public void testMultipleCachedMuteStateChanges() {
when(mFeatureFlags.cacheCallAudioCallbacks()).thenReturn(true);
TransactionalServiceWrapper tsw = Mockito.mock(TransactionalServiceWrapper.class);
@@ -224,20 +261,39 @@
assertNull(call.getTransactionServiceWrapper());
call.cacheServiceCallback(new CachedMuteStateChange(true));
- assertEquals(1, call.getCachedServiceCallbacks().size());
+ assertEquals(1,
+ call.getCachedServiceCallbacksCopy().get(CachedMuteStateChange.ID).size());
call.cacheServiceCallback(new CachedMuteStateChange(false));
- assertEquals(1, call.getCachedServiceCallbacks().size());
+ assertEquals(1,
+ call.getCachedServiceCallbacksCopy().get(CachedMuteStateChange.ID).size());
CachedMuteStateChange currentCacheMuteState = (CachedMuteStateChange) call
- .getCachedServiceCallbacks()
- .get(CachedMuteStateChange.ID);
+ .getCachedServiceCallbacksCopy()
+ .get(CachedMuteStateChange.ID)
+ .getLast();
assertFalse(currentCacheMuteState.isMuted());
call.setTransactionServiceWrapper(tsw);
verify(tsw, times(1)).onMuteStateChanged(any(), eq(false));
- assertEquals(0, call.getCachedServiceCallbacks().size());
+ assertEquals(0, call.getCachedServiceCallbacksCopy().size());
+ }
+
+ @Test
+ public void testCacheAfterServiceSet() {
+ when(mFeatureFlags.cacheCallAudioCallbacks()).thenReturn(true);
+ when(mFeatureFlags.cacheCallEvents()).thenReturn(true);
+ TransactionalServiceWrapper tsw = Mockito.mock(TransactionalServiceWrapper.class);
+ Call call = createCall("1", Call.CALL_DIRECTION_INCOMING);
+
+ assertNull(call.getTransactionServiceWrapper());
+ call.setTransactionServiceWrapper(tsw);
+ call.cacheServiceCallback(new CachedMuteStateChange(true));
+ // Ensure that we do not lose events if for some reason a CachedCallback is cached after
+ // the service is set
+ verify(tsw, times(1)).onMuteStateChanged(any(), eq(true));
+ assertEquals(0, call.getCachedServiceCallbacksCopy().size());
}
@Test
@@ -254,21 +310,24 @@
assertNull(call.getTransactionServiceWrapper());
call.cacheServiceCallback(new CachedCurrentEndpointChange(earpiece));
- assertEquals(1, call.getCachedServiceCallbacks().size());
+ assertEquals(1,
+ call.getCachedServiceCallbacksCopy().get(CachedCurrentEndpointChange.ID).size());
call.cacheServiceCallback(new CachedCurrentEndpointChange(speaker));
- assertEquals(1, call.getCachedServiceCallbacks().size());
+ assertEquals(1,
+ call.getCachedServiceCallbacksCopy().get(CachedCurrentEndpointChange.ID).size());
CachedCurrentEndpointChange currentEndpointChange = (CachedCurrentEndpointChange) call
- .getCachedServiceCallbacks()
- .get(CachedCurrentEndpointChange.ID);
+ .getCachedServiceCallbacksCopy()
+ .get(CachedCurrentEndpointChange.ID)
+ .getLast();
assertEquals(CallEndpoint.TYPE_SPEAKER,
currentEndpointChange.getCurrentCallEndpoint().getEndpointType());
call.setTransactionServiceWrapper(tsw);
verify(tsw, times(1)).onCallEndpointChanged(any(), any());
- assertEquals(0, call.getCachedServiceCallbacks().size());
+ assertEquals(0, call.getCachedServiceCallbacksCopy().size());
}
@Test
@@ -287,20 +346,23 @@
assertNull(call.getTransactionServiceWrapper());
call.cacheServiceCallback(new CachedAvailableEndpointsChange(initialSet));
- assertEquals(1, call.getCachedServiceCallbacks().size());
+ assertEquals(1,
+ call.getCachedServiceCallbacksCopy().get(CachedAvailableEndpointsChange.ID).size());
call.cacheServiceCallback(new CachedAvailableEndpointsChange(finalSet));
- assertEquals(1, call.getCachedServiceCallbacks().size());
+ assertEquals(1,
+ call.getCachedServiceCallbacksCopy().get(CachedAvailableEndpointsChange.ID).size());
CachedAvailableEndpointsChange availableEndpoints = (CachedAvailableEndpointsChange) call
- .getCachedServiceCallbacks()
- .get(CachedAvailableEndpointsChange.ID);
+ .getCachedServiceCallbacksCopy()
+ .get(CachedAvailableEndpointsChange.ID)
+ .getLast();
assertEquals(2, availableEndpoints.getAvailableEndpoints().size());
call.setTransactionServiceWrapper(tsw);
verify(tsw, times(1)).onAvailableCallEndpointsChanged(any(), any());
- assertEquals(0, call.getCachedServiceCallbacks().size());
+ assertEquals(0, call.getCachedServiceCallbacksCopy().size());
}
/**
@@ -310,6 +372,7 @@
@Test
public void testAllCachedCallbacks() {
when(mFeatureFlags.cacheCallAudioCallbacks()).thenReturn(true);
+ when(mFeatureFlags.cacheCallEvents()).thenReturn(true);
TransactionalServiceWrapper tsw = Mockito.mock(TransactionalServiceWrapper.class);
CallEndpoint earpiece = Mockito.mock(CallEndpoint.class);
CallEndpoint bluetooth = Mockito.mock(CallEndpoint.class);
@@ -323,23 +386,29 @@
// add cached callbacks
call.cacheServiceCallback(new CachedMuteStateChange(false));
- assertEquals(1, call.getCachedServiceCallbacks().size());
+ assertEquals(1, call.getCachedServiceCallbacksCopy().size());
call.cacheServiceCallback(new CachedCurrentEndpointChange(earpiece));
- assertEquals(2, call.getCachedServiceCallbacks().size());
+ assertEquals(2, call.getCachedServiceCallbacksCopy().size());
call.cacheServiceCallback(new CachedAvailableEndpointsChange(availableEndpointsSet));
- assertEquals(3, call.getCachedServiceCallbacks().size());
+ assertEquals(3, call.getCachedServiceCallbacksCopy().size());
+ String testEvent = "testEvent";
+ Bundle testBundle = new Bundle();
+ call.sendCallEvent("testEvent", testBundle);
// verify the cached callbacks are stored properly within the cache map and the values
// can be evaluated
CachedMuteStateChange currentCacheMuteState = (CachedMuteStateChange) call
- .getCachedServiceCallbacks()
- .get(CachedMuteStateChange.ID);
+ .getCachedServiceCallbacksCopy()
+ .get(CachedMuteStateChange.ID)
+ .getLast();
CachedCurrentEndpointChange currentEndpointChange = (CachedCurrentEndpointChange) call
- .getCachedServiceCallbacks()
- .get(CachedCurrentEndpointChange.ID);
+ .getCachedServiceCallbacksCopy()
+ .get(CachedCurrentEndpointChange.ID)
+ .getLast();
CachedAvailableEndpointsChange availableEndpoints = (CachedAvailableEndpointsChange) call
- .getCachedServiceCallbacks()
- .get(CachedAvailableEndpointsChange.ID);
+ .getCachedServiceCallbacksCopy()
+ .get(CachedAvailableEndpointsChange.ID)
+ .getLast();
assertFalse(currentCacheMuteState.isMuted());
assertEquals(CallEndpoint.TYPE_EARPIECE,
currentEndpointChange.getCurrentCallEndpoint().getEndpointType());
@@ -352,9 +421,10 @@
verify(tsw, times(1)).onMuteStateChanged(any(), anyBoolean());
verify(tsw, times(1)).onCallEndpointChanged(any(), any());
verify(tsw, times(1)).onAvailableCallEndpointsChanged(any(), any());
+ verify(tsw, times(1)).sendCallEvent(any(), eq(testEvent), eq(testBundle));
// the cache map should be cleared
- assertEquals(0, call.getCachedServiceCallbacks().size());
+ assertEquals(0, call.getCachedServiceCallbacksCopy().size());
}
/**