Merge "Implement new APIs to notify call audio routing information as a form of CallEndpoint"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d3196b9..d67df4b 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -383,4 +383,16 @@
<string name="cancel">Cancel</string>
<!-- Button label for generic back action [CHAR LIMIT=20] -->
<string name="back">Back</string>
+ <!-- The user-visible name of the earpiece type CallEndpoint -->
+ <string name="callendpoint_name_earpiece">Earpiece</string>
+ <!-- The user-visible name of the bluetooth type CallEndpoint -->
+ <string name="callendpoint_name_bluetooth">Bluetooth</string>
+ <!-- The user-visible name of the wired headset type CallEndpoint -->
+ <string name="callendpoint_name_wiredheadset">Wired headset</string>
+ <!-- The user-visible name of the speaker type CallEndpoint -->
+ <string name="callendpoint_name_speaker">Speaker</string>
+ <!-- The user-visible name of the streaming type CallEndpoint -->
+ <string name="callendpoint_name_streaming">External</string>
+ <!-- The user-visible name of the unknown new type CallEndpoint -->
+ <string name="callendpoint_name_unknown">Unknown</string>
</resources>
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 672ee3b..4a15b0f 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -413,7 +413,8 @@
* @param bluetoothAddress the address of the desired bluetooth device, if route is
* {@link CallAudioState#ROUTE_BLUETOOTH}.
*/
- void setAudioRoute(int route, String bluetoothAddress) {
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+ public void setAudioRoute(int route, String bluetoothAddress) {
Log.v(this, "setAudioRoute, route: %s", CallAudioState.audioRouteToString(route));
switch (route) {
case CallAudioState.ROUTE_BLUETOOTH:
diff --git a/src/com/android/server/telecom/CallEndpointController.java b/src/com/android/server/telecom/CallEndpointController.java
new file mode 100644
index 0000000..c8042ea
--- /dev/null
+++ b/src/com/android/server/telecom/CallEndpointController.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright 2022, 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.content.Context;
+import android.bluetooth.BluetoothDevice;
+import android.os.Bundle;
+import android.os.ParcelUuid;
+import android.os.ResultReceiver;
+import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
+import android.telecom.CallEndpointException;
+import android.telecom.Log;
+import com.android.internal.annotations.VisibleForTesting;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Provides to {@link CallsManager} the service that can request change of CallEndpoint to the
+ * {@link CallAudioManager}. And notify change of CallEndpoint status to {@link CallsManager}
+ */
+public class CallEndpointController extends CallsManagerListenerBase {
+ public static final int CHANGE_TIMEOUT_SEC = 2;
+ public static final int RESULT_REQUEST_SUCCESS = 0;
+ public static final int RESULT_ENDPOINT_DOES_NOT_EXIST = 1;
+ public static final int RESULT_REQUEST_TIME_OUT = 2;
+ public static final int RESULT_ANOTHER_REQUEST = 3;
+ public static final int RESULT_UNSPECIFIED_ERROR = 4;
+
+ private final Context mContext;
+ private final CallsManager mCallsManager;
+ private final HashMap<Integer, Integer> mRouteToTypeMap;
+ private final HashMap<Integer, Integer> mTypeToRouteMap;
+ private final Map<ParcelUuid, String> mBluetoothAddressMap = new HashMap<>();
+ private final Set<CallEndpoint> mAvailableCallEndpoints = new HashSet<>();
+ private CallEndpoint mActiveCallEndpoint;
+ private ParcelUuid mRequestedEndpointId;
+ private CompletableFuture<Integer> mPendingChangeRequest;
+
+ public CallEndpointController(Context context, CallsManager callsManager) {
+ mContext = context;
+ mCallsManager = callsManager;
+
+ mRouteToTypeMap = new HashMap<>(5);
+ mRouteToTypeMap.put(CallAudioState.ROUTE_EARPIECE, CallEndpoint.TYPE_EARPIECE);
+ mRouteToTypeMap.put(CallAudioState.ROUTE_BLUETOOTH, CallEndpoint.TYPE_BLUETOOTH);
+ mRouteToTypeMap.put(CallAudioState.ROUTE_WIRED_HEADSET, CallEndpoint.TYPE_WIRED_HEADSET);
+ mRouteToTypeMap.put(CallAudioState.ROUTE_SPEAKER, CallEndpoint.TYPE_SPEAKER);
+
+ mTypeToRouteMap = new HashMap<>(5);
+ mTypeToRouteMap.put(CallEndpoint.TYPE_EARPIECE, CallAudioState.ROUTE_EARPIECE);
+ mTypeToRouteMap.put(CallEndpoint.TYPE_BLUETOOTH, CallAudioState.ROUTE_BLUETOOTH);
+ mTypeToRouteMap.put(CallEndpoint.TYPE_WIRED_HEADSET, CallAudioState.ROUTE_WIRED_HEADSET);
+ mTypeToRouteMap.put(CallEndpoint.TYPE_SPEAKER, CallAudioState.ROUTE_SPEAKER);
+ }
+
+ @VisibleForTesting
+ public CallEndpoint getCurrentCallEndpoint() {
+ return mActiveCallEndpoint;
+ }
+
+ @VisibleForTesting
+ public Set<CallEndpoint> getAvailableEndpoints() {
+ return mAvailableCallEndpoints;
+ }
+
+ public void requestCallEndpointChange(CallEndpoint endpoint, ResultReceiver callback) {
+ Log.d(this, "requestCallEndpointChange %s", endpoint);
+ int route = mTypeToRouteMap.get(endpoint.getEndpointType());
+ String bluetoothAddress = getBluetoothAddress(endpoint);
+
+ if (findMatchingTypeEndpoint(endpoint.getEndpointType()) == null ||
+ (route == CallAudioState.ROUTE_BLUETOOTH && bluetoothAddress == null)) {
+ callback.send(CallEndpoint.ENDPOINT_OPERATION_FAILED,
+ getErrorResult(RESULT_ENDPOINT_DOES_NOT_EXIST));
+ return;
+ }
+
+ if (mPendingChangeRequest != null && !mPendingChangeRequest.isDone()) {
+ mPendingChangeRequest.complete(RESULT_ANOTHER_REQUEST);
+ mPendingChangeRequest = null;
+ mRequestedEndpointId = null;
+ }
+
+ mPendingChangeRequest = new CompletableFuture<Integer>()
+ .completeOnTimeout(RESULT_REQUEST_TIME_OUT, CHANGE_TIMEOUT_SEC, TimeUnit.SECONDS);
+
+ mPendingChangeRequest.thenAcceptAsync((result) -> {
+ if (result == RESULT_REQUEST_SUCCESS) {
+ callback.send(CallEndpoint.ENDPOINT_OPERATION_SUCCESS, new Bundle());
+ } else {
+ callback.send(CallEndpoint.ENDPOINT_OPERATION_FAILED, getErrorResult(result));
+ }
+ });
+ mRequestedEndpointId = endpoint.getIdentifier();
+ mCallsManager.getCallAudioManager().setAudioRoute(route, bluetoothAddress);
+ }
+
+ private Bundle getErrorResult(int result) {
+ String message;
+ int resultCode;
+ switch (result) {
+ case RESULT_ENDPOINT_DOES_NOT_EXIST:
+ message = "Requested CallEndpoint does not exist";
+ resultCode = CallEndpointException.ERROR_ENDPOINT_DOES_NOT_EXIST;
+ break;
+ case RESULT_REQUEST_TIME_OUT:
+ message = "The operation was not completed on time";
+ resultCode = CallEndpointException.ERROR_REQUEST_TIME_OUT;
+ break;
+ case RESULT_ANOTHER_REQUEST:
+ message = "The operation was canceled by another request";
+ resultCode = CallEndpointException.ERROR_ANOTHER_REQUEST;
+ break;
+ default:
+ message = "The operation has failed due to an unknown or unspecified error";
+ resultCode = CallEndpointException.ERROR_UNSPECIFIED;
+ }
+ CallEndpointException exception = new CallEndpointException(message, resultCode);
+ Bundle extras = new Bundle();
+ extras.putParcelable(CallEndpointException.CHANGE_ERROR, exception);
+ return extras;
+ }
+
+ @VisibleForTesting
+ public String getBluetoothAddress(CallEndpoint endpoint) {
+ return mBluetoothAddressMap.get(endpoint.getIdentifier());
+ }
+
+ private void notifyCallEndpointChange() {
+ if (mActiveCallEndpoint == null) {
+ Log.i(this, "notifyCallEndpointChange, invalid CallEndpoint");
+ return;
+ }
+
+ if (mRequestedEndpointId != null && mPendingChangeRequest != null &&
+ mRequestedEndpointId.equals(mActiveCallEndpoint.getIdentifier())) {
+ mPendingChangeRequest.complete(RESULT_REQUEST_SUCCESS);
+ mPendingChangeRequest = null;
+ mRequestedEndpointId = null;
+ }
+ mCallsManager.updateCallEndpoint(mActiveCallEndpoint);
+
+ Set<Call> calls = mCallsManager.getTrackedCalls();
+ for (Call call : calls) {
+ if (call != null && call.getConnectionService() != null) {
+ call.getConnectionService().onCallEndpointChanged(call, mActiveCallEndpoint);
+ }
+ }
+ }
+
+ private void notifyAvailableCallEndpointsChange() {
+ mCallsManager.updateAvailableCallEndpoints(mAvailableCallEndpoints);
+
+ Set<Call> calls = mCallsManager.getTrackedCalls();
+ for (Call call : calls) {
+ if (call != null && call.getConnectionService() != null) {
+ call.getConnectionService().onAvailableCallEndpointsChanged(call,
+ mAvailableCallEndpoints);
+ }
+ }
+ }
+
+ private void notifyMuteStateChange(boolean isMuted) {
+ mCallsManager.updateMuteState(isMuted);
+
+ Set<Call> calls = mCallsManager.getTrackedCalls();
+ for (Call call : calls) {
+ if (call != null && call.getConnectionService() != null) {
+ call.getConnectionService().onMuteStateChanged(call, isMuted);
+ }
+ }
+ }
+
+ private void createAvailableCallEndpoints(CallAudioState state) {
+ Set<CallEndpoint> newAvailableEndpoints = new HashSet<>();
+ Map<ParcelUuid, String> newBluetoothDevices = new HashMap<>();
+
+ mRouteToTypeMap.forEach((route, type)->{
+ if ((state.getSupportedRouteMask() & route) != 0) {
+ if (type == CallEndpoint.TYPE_BLUETOOTH) {
+ for (BluetoothDevice device : state.getSupportedBluetoothDevices()) {
+ CallEndpoint endpoint = findMatchingBluetoothEndpoint(device);
+ if (endpoint == null) {
+ endpoint = new CallEndpoint(
+ device.getName() != null ? device.getName() : "",
+ CallEndpoint.TYPE_BLUETOOTH);
+ }
+ newAvailableEndpoints.add(endpoint);
+ newBluetoothDevices.put(endpoint.getIdentifier(), device.getAddress());
+
+ BluetoothDevice activeDevice = state.getActiveBluetoothDevice();
+ if (state.getRoute() == route && device.equals(activeDevice)) {
+ mActiveCallEndpoint = endpoint;
+ }
+ }
+ } else {
+ CallEndpoint endpoint = findMatchingTypeEndpoint(type);
+ if (endpoint == null) {
+ endpoint = new CallEndpoint(
+ getEndpointName(type) != null ? getEndpointName(type) : "", type);
+ }
+ newAvailableEndpoints.add(endpoint);
+ if (state.getRoute() == route) {
+ mActiveCallEndpoint = endpoint;
+ }
+ }
+ }
+ });
+ mAvailableCallEndpoints.clear();
+ mAvailableCallEndpoints.addAll(newAvailableEndpoints);
+ mBluetoothAddressMap.clear();
+ mBluetoothAddressMap.putAll(newBluetoothDevices);
+ }
+
+ private CallEndpoint findMatchingTypeEndpoint(int targetType) {
+ for (CallEndpoint endpoint : mAvailableCallEndpoints) {
+ if (endpoint.getEndpointType() == targetType) {
+ return endpoint;
+ }
+ }
+ return null;
+ }
+
+ private CallEndpoint findMatchingBluetoothEndpoint(BluetoothDevice device) {
+ final String targetAddress = device.getAddress();
+ if (targetAddress != null) {
+ for (CallEndpoint endpoint : mAvailableCallEndpoints) {
+ final String address = mBluetoothAddressMap.get(endpoint.getIdentifier());
+ if (targetAddress.equals(address)) {
+ return endpoint;
+ }
+ }
+ }
+ return null;
+ }
+
+ private boolean isAvailableEndpointChanged(CallAudioState oldState, CallAudioState newState) {
+ if (oldState == null) {
+ return true;
+ }
+ if ((oldState.getSupportedRouteMask() ^ newState.getSupportedRouteMask()) != 0) {
+ return true;
+ }
+ if (oldState.getSupportedBluetoothDevices().size() !=
+ newState.getSupportedBluetoothDevices().size()) {
+ return true;
+ }
+ for (BluetoothDevice device : newState.getSupportedBluetoothDevices()) {
+ if (!oldState.getSupportedBluetoothDevices().contains(device)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isEndpointChanged(CallAudioState oldState, CallAudioState newState) {
+ if (oldState == null) {
+ return true;
+ }
+ if (oldState.getRoute() != newState.getRoute()) {
+ return true;
+ }
+ if (newState.getRoute() == CallAudioState.ROUTE_BLUETOOTH) {
+ return !oldState.getActiveBluetoothDevice().equals(newState.getActiveBluetoothDevice());
+ }
+ return false;
+ }
+
+ private boolean isMuteStateChanged(CallAudioState oldState, CallAudioState newState) {
+ if (oldState == null) {
+ return true;
+ }
+ return oldState.isMuted() != newState.isMuted();
+ }
+
+ private CharSequence getEndpointName(int endpointType) {
+ switch (endpointType) {
+ case CallEndpoint.TYPE_EARPIECE:
+ return mContext.getText(R.string.callendpoint_name_earpiece);
+ case CallEndpoint.TYPE_BLUETOOTH:
+ return mContext.getText(R.string.callendpoint_name_bluetooth);
+ case CallEndpoint.TYPE_WIRED_HEADSET:
+ return mContext.getText(R.string.callendpoint_name_wiredheadset);
+ case CallEndpoint.TYPE_SPEAKER:
+ return mContext.getText(R.string.callendpoint_name_speaker);
+ case CallEndpoint.TYPE_STREAMING:
+ return mContext.getText(R.string.callendpoint_name_streaming);
+ default:
+ return mContext.getText(R.string.callendpoint_name_unknown);
+ }
+ }
+
+ @Override
+ public void onCallAudioStateChanged(CallAudioState oldState, CallAudioState newState) {
+ Log.i(this, "onCallAudioStateChanged, audioState: %s -> %s", oldState, newState);
+
+ if (newState == null) {
+ Log.i(this, "onCallAudioStateChanged, invalid audioState");
+ return;
+ }
+
+ createAvailableCallEndpoints(newState);
+
+ boolean isforce = true;
+ if (isAvailableEndpointChanged(oldState, newState)) {
+ notifyAvailableCallEndpointsChange();
+ isforce = false;
+ }
+
+ if (isEndpointChanged(oldState, newState)) {
+ notifyCallEndpointChange();
+ isforce = false;
+ }
+
+ if (isMuteStateChanged(oldState, newState)) {
+ notifyMuteStateChange(newState.isMuted());
+ isforce = false;
+ }
+
+ if (isforce) {
+ notifyAvailableCallEndpointsChange();
+ notifyCallEndpointChange();
+ notifyMuteStateChange(newState.isMuted());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/CallEndpointControllerFactory.java b/src/com/android/server/telecom/CallEndpointControllerFactory.java
new file mode 100644
index 0000000..a9b03c3
--- /dev/null
+++ b/src/com/android/server/telecom/CallEndpointControllerFactory.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2022, 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.content.Context;
+
+/**
+ * Abstracts out creation of CallEndpointController for unit test purposes.
+ */
+public interface CallEndpointControllerFactory {
+ CallEndpointController create(Context context, TelecomSystem.SyncRoot lock,
+ CallsManager callsManager);
+}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index d3c374b..75b3864 100755
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -66,6 +66,7 @@
import android.os.Looper;
import android.os.PersistableBundle;
import android.os.Process;
+import android.os.ResultReceiver;
import android.os.SystemClock;
import android.os.SystemVibrator;
import android.os.Trace;
@@ -77,6 +78,7 @@
import android.provider.Settings;
import android.sysprop.TelephonyProperties;
import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
import android.telecom.CallScreeningService;
import android.telecom.CallerInfo;
import android.telecom.Conference;
@@ -174,6 +176,9 @@
void onIncomingCallAnswered(Call call);
void onIncomingCallRejected(Call call, boolean rejectWithMessage, String textMessage);
void onCallAudioStateChanged(CallAudioState oldAudioState, CallAudioState newAudioState);
+ void onCallEndpointChanged(CallEndpoint callEndpoint);
+ void onAvailableCallEndpointsChanged(Set<CallEndpoint> availableCallEndpoints);
+ void onMuteStateChanged(boolean isMuted);
void onRingbackRequested(Call call, boolean ringback);
void onIsConferencedChanged(Call call);
void onIsVoipAudioModeChanged(Call call);
@@ -376,6 +381,7 @@
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final EmergencyCallHelper mEmergencyCallHelper;
private final RoleManagerAdapter mRoleManagerAdapter;
+ private final CallEndpointController mCallEndpointController;
private final ConnectionServiceFocusManager.CallsManagerRequester mRequester =
new ConnectionServiceFocusManager.CallsManagerRequester() {
@@ -496,7 +502,8 @@
InCallControllerFactory inCallControllerFactory,
CallDiagnosticServiceController callDiagnosticServiceController,
RoleManagerAdapter roleManagerAdapter,
- ToastFactory toastFactory) {
+ ToastFactory toastFactory,
+ CallEndpointControllerFactory callEndpointControllerFactory) {
mContext = context;
mLock = lock;
mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
@@ -550,6 +557,7 @@
mInCallController = inCallControllerFactory.create(context, mLock, this,
systemStateHelper, defaultDialerCache, mTimeoutsAdapter,
emergencyCallHelper);
+ mCallEndpointController = callEndpointControllerFactory.create(context, mLock, this);
mCallDiagnosticServiceController = callDiagnosticServiceController;
mCallDiagnosticServiceController.setInCallTonePlayerFactory(playerFactory);
mRinger = new Ringer(playerFactory, context, systemSettingsUtil, asyncRingtonePlayer,
@@ -581,6 +589,7 @@
mListeners.add(statusBarNotifier);
mListeners.add(mCallLogManager);
mListeners.add(mInCallController);
+ mListeners.add(mCallEndpointController);
mListeners.add(mCallDiagnosticServiceController);
mListeners.add(mCallAudioManager);
mListeners.add(mCallRecordingTonePlayer);
@@ -1193,6 +1202,10 @@
return mInCallController;
}
+ public CallEndpointController getCallEndpointController() {
+ return mCallEndpointController;
+ }
+
EmergencyCallHelper getEmergencyCallHelper() {
return mEmergencyCallHelper;
}
@@ -3010,6 +3023,14 @@
mCallAudioManager.setAudioRoute(route, bluetoothAddress);
}
+ /**
+ * Called by the in-call UI to change the CallEndpoint
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public void requestCallEndpointChange(CallEndpoint endpoint, ResultReceiver callback) {
+ mCallEndpointController.requestCallEndpointChange(endpoint, callback);
+ }
+
/** Called by the in-call UI to turn the proximity sensor on. */
void turnOnProximitySensor() {
mProximitySensorManager.turnOn();
@@ -3068,6 +3089,30 @@
}
}
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public void updateCallEndpoint(CallEndpoint callEndpoint) {
+ Log.v(this, "updateCallEndpoint");
+ for (CallsManagerListener listener : mListeners) {
+ listener.onCallEndpointChanged(callEndpoint);
+ }
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public void updateAvailableCallEndpoints(Set<CallEndpoint> availableCallEndpoints) {
+ Log.v(this, "updateAvailableCallEndpoints");
+ for (CallsManagerListener listener : mListeners) {
+ listener.onAvailableCallEndpointsChanged(availableCallEndpoints);
+ }
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public void updateMuteState(boolean isMuted) {
+ Log.v(this, "updateMuteState");
+ for (CallsManagerListener listener : mListeners) {
+ listener.onMuteStateChanged(isMuted);
+ }
+ }
+
/**
* Called when disconnect tone is started or stopped, including any InCallTone
* after disconnected call.
diff --git a/src/com/android/server/telecom/CallsManagerListenerBase.java b/src/com/android/server/telecom/CallsManagerListenerBase.java
index 55c7b53..85e6d65 100644
--- a/src/com/android/server/telecom/CallsManagerListenerBase.java
+++ b/src/com/android/server/telecom/CallsManagerListenerBase.java
@@ -18,7 +18,9 @@
import android.telecom.AudioState;
import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
import android.telecom.VideoProfile;
+import java.util.Set;
/**
* Provides a default implementation for listeners of CallsManager.
@@ -57,6 +59,18 @@
}
@Override
+ public void onCallEndpointChanged(CallEndpoint callEndpoint) {
+ }
+
+ @Override
+ public void onAvailableCallEndpointsChanged(Set<CallEndpoint> availableCallEndpoints) {
+ }
+
+ @Override
+ public void onMuteStateChanged(boolean isMuted) {
+ }
+
+ @Override
public void onRingbackRequested(Call call, boolean ringback) {
}
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 0243b67..729aa0e 100755
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -29,8 +29,10 @@
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.UserHandle;
import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
import android.telecom.CallScreeningService;
import android.telecom.Connection;
import android.telecom.ConnectionRequest;
@@ -723,6 +725,26 @@
}
@Override
+ public void requestCallEndpointChange(String callId, CallEndpoint endpoint,
+ ResultReceiver callback, Session.Info sessionInfo) {
+ Log.startSession(sessionInfo, "CSW.rCEC", mPackageAbbreviation);
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ logIncoming("requestCallEndpointChange %s %s", callId,
+ endpoint.getEndpointName());
+ mCallsManager.requestCallEndpointChange(endpoint, callback);
+ }
+ } catch (Throwable t) {
+ Log.e(ConnectionServiceWrapper.this, t, "");
+ throw t;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ Log.endSession();
+ }
+ }
+
+ @Override
public void setStatusHints(String callId, StatusHints statusHints,
Session.Info sessionInfo) {
Log.startSession(sessionInfo, "CSW.sSH", mPackageAbbreviation);
@@ -1673,6 +1695,54 @@
}
}
+ /** @see IConnectionService#onCallEndpointChanged(String, CallEndpoint, Session.Info) */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public void onCallEndpointChanged(Call activeCall, CallEndpoint callEndpoint) {
+ final String callId = mCallIdMapper.getCallId(activeCall);
+ if (callId != null && isServiceValid("onCallEndpointChanged")) {
+ try {
+ logOutgoing("onCallEndpointChanged %s %s", callId, callEndpoint);
+ mServiceInterface.onCallEndpointChanged(callId, callEndpoint,
+ Log.getExternalSession(TELECOM_ABBREVIATION));
+ } catch (RemoteException e) {
+ Log.d(this, "Remote exception calling onCallEndpointChanged");
+ }
+ }
+ }
+
+ /** @see IConnectionService#onAvailableCallEndpointsChanged(String, List, Session.Info) */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public void onAvailableCallEndpointsChanged(Call activeCall,
+ Set<CallEndpoint> availableCallEndpoints) {
+ final String callId = mCallIdMapper.getCallId(activeCall);
+ if (callId != null && isServiceValid("onAvailableCallEndpointsChanged")) {
+ try {
+ logOutgoing("onAvailableCallEndpointsChanged %s", callId);
+ List<CallEndpoint> availableEndpoints = new ArrayList<>(availableCallEndpoints);
+ mServiceInterface.onAvailableCallEndpointsChanged(callId, availableEndpoints,
+ Log.getExternalSession(TELECOM_ABBREVIATION));
+ } catch (RemoteException e) {
+ Log.d(this,
+ "Remote exception calling onAvailableCallEndpointsChanged");
+ }
+ }
+ }
+
+ /** @see IConnectionService#onMuteStateChanged(String, boolean, Session.Info) */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public void onMuteStateChanged(Call activeCall, boolean isMuted) {
+ final String callId = mCallIdMapper.getCallId(activeCall);
+ if (callId != null && isServiceValid("onMuteStateChanged")) {
+ try {
+ logOutgoing("onMuteStateChanged %s %s", callId, isMuted);
+ mServiceInterface.onMuteStateChanged(callId, isMuted,
+ Log.getExternalSession(TELECOM_ABBREVIATION));
+ } catch (RemoteException e) {
+ Log.d(this, "Remote exception calling onMuteStateChanged");
+ }
+ }
+ }
+
/** @see IConnectionService#onUsingAlternativeUi(String, boolean, Session.Info) */
@VisibleForTesting
public void onUsingAlternativeUi(Call activeCall, boolean isUsingAlternativeUi) {
diff --git a/src/com/android/server/telecom/InCallAdapter.java b/src/com/android/server/telecom/InCallAdapter.java
index 0fda5f8..f52d489 100755
--- a/src/com/android/server/telecom/InCallAdapter.java
+++ b/src/com/android/server/telecom/InCallAdapter.java
@@ -19,6 +19,8 @@
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.telecom.CallEndpoint;
import android.telecom.Log;
import android.telecom.PhoneAccountHandle;
@@ -396,6 +398,23 @@
}
@Override
+ public void requestCallEndpointChange(CallEndpoint endpoint, ResultReceiver callback) {
+ try {
+ Log.startSession(LogUtils.Sessions.ICA_SET_AUDIO_ROUTE, mOwnerPackageAbbreviation);
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ mCallsManager.requestCallEndpointChange(endpoint, callback);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
public void enterBackgroundAudioProcessing(String callId) {
try {
Log.startSession(LogUtils.Sessions.ICA_ENTER_AUDIO_PROCESSING,
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index d830dfc..7a38583 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -52,6 +52,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
import android.telecom.ConnectionService;
import android.telecom.InCallService;
import android.telecom.Log;
@@ -1379,6 +1380,49 @@
}
@Override
+ public void onCallEndpointChanged(CallEndpoint callEndpoint) {
+ if (!mInCallServices.isEmpty()) {
+ Log.i(this, "Calling onCallEndpointChanged");
+ for (IInCallService inCallService : mInCallServices.values()) {
+ try {
+ inCallService.onCallEndpointChanged(callEndpoint);
+ } catch (RemoteException ignored) {
+ Log.d(this, "Remote exception calling onCallEndpointChanged");
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onAvailableCallEndpointsChanged(Set<CallEndpoint> availableCallEndpoints) {
+ if (!mInCallServices.isEmpty()) {
+ Log.i(this, "Calling onAvailableCallEndpointsChanged");
+ List<CallEndpoint> availableEndpoints = new ArrayList<>(availableCallEndpoints);
+ for (IInCallService inCallService : mInCallServices.values()) {
+ try {
+ inCallService.onAvailableCallEndpointsChanged(availableEndpoints);
+ } catch (RemoteException ignored) {
+ Log.d(this, "Remote exception calling onAvailableCallEndpointsChanged");
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onMuteStateChanged(boolean isMuted) {
+ if (!mInCallServices.isEmpty()) {
+ Log.i(this, "Calling onMuteStateChanged");
+ for (IInCallService inCallService : mInCallServices.values()) {
+ try {
+ inCallService.onMuteStateChanged(isMuted);
+ } catch (RemoteException ignored) {
+ Log.d(this, "Remote exception calling onMuteStateChanged");
+ }
+ }
+ }
+ }
+
+ @Override
public void onCanAddCallChanged(boolean canAddCall) {
if (!mInCallServices.isEmpty()) {
Log.i(this, "onCanAddCallChanged : %b", canAddCall);
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 237f039..b6c9925 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -269,6 +269,15 @@
}
};
+ CallEndpointControllerFactory callEndpointControllerFactory =
+ new CallEndpointControllerFactory() {
+ @Override
+ public CallEndpointController create(Context context, SyncRoot lock,
+ CallsManager callsManager) {
+ return new CallEndpointController(context, callsManager);
+ }
+ };
+
CallDiagnosticServiceController callDiagnosticServiceController =
new CallDiagnosticServiceController(
new CallDiagnosticServiceController.ContextProxy() {
@@ -348,7 +357,8 @@
inCallControllerFactory,
callDiagnosticServiceController,
roleManagerAdapter,
- toastFactory);
+ toastFactory,
+ callEndpointControllerFactory);
mIncomingCallNotifier = incomingCallNotifier;
incomingCallNotifier.setCallsManagerProxy(new IncomingCallNotifier.CallsManagerProxy() {
diff --git a/tests/src/com/android/server/telecom/tests/CallEndpointControllerTest.java b/tests/src/com/android/server/telecom/tests/CallEndpointControllerTest.java
new file mode 100644
index 0000000..b15d3c2
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallEndpointControllerTest.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2022, 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothDevice;
+import android.os.ResultReceiver;
+import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
+import android.test.mock.MockContext;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallAudioManager;
+import com.android.server.telecom.CallEndpointController;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.ConnectionServiceWrapper;
+
+import org.junit.Before;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+@RunWith(JUnit4.class)
+public class CallEndpointControllerTest extends TelecomTestCase {
+ private static final BluetoothDevice bluetoothDevice1 =
+ BluetoothRouteManagerTest.makeBluetoothDevice("00:00:00:00:00:01");
+ private static final BluetoothDevice bluetoothDevice2 =
+ BluetoothRouteManagerTest.makeBluetoothDevice("00:00:00:00:00:02");
+ private static final Collection<BluetoothDevice> availableBluetooth1 =
+ Arrays.asList(bluetoothDevice1, bluetoothDevice2);
+ private static final Collection<BluetoothDevice> availableBluetooth2 =
+ Arrays.asList(bluetoothDevice1);
+
+ private static final CallAudioState audioState1 = new CallAudioState(false,
+ CallAudioState.ROUTE_EARPIECE, CallAudioState.ROUTE_ALL, null, availableBluetooth1);
+ private static final CallAudioState audioState2 = new CallAudioState(false,
+ CallAudioState.ROUTE_BLUETOOTH, CallAudioState.ROUTE_ALL, bluetoothDevice1,
+ availableBluetooth1);
+ private static final CallAudioState audioState3 = new CallAudioState(false,
+ CallAudioState.ROUTE_BLUETOOTH, CallAudioState.ROUTE_ALL, bluetoothDevice2,
+ availableBluetooth1);
+ private static final CallAudioState audioState4 = new CallAudioState(false,
+ CallAudioState.ROUTE_BLUETOOTH, CallAudioState.ROUTE_ALL, bluetoothDevice1,
+ availableBluetooth2);
+ private static final CallAudioState audioState5 = new CallAudioState(true,
+ CallAudioState.ROUTE_EARPIECE, CallAudioState.ROUTE_ALL, null, availableBluetooth1);
+ private static final CallAudioState audioState6 = new CallAudioState(false,
+ CallAudioState.ROUTE_EARPIECE, CallAudioState.ROUTE_EARPIECE, null,
+ availableBluetooth1);
+
+ private CallEndpointController mCallEndpointController;
+
+ @Mock private CallsManager mCallsManager;
+ @Mock private Call mCall;
+ @Mock private ConnectionServiceWrapper mConnectionService;
+ @Mock private CallAudioManager mCallAudioManager;
+ @Mock private MockContext mMockContext;
+ @Mock private ResultReceiver mResultReceiver;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ mCallEndpointController = new CallEndpointController(mMockContext, mCallsManager);
+ doReturn(new HashSet<>(Arrays.asList(mCall))).when(mCallsManager).getTrackedCalls();
+ doReturn(mConnectionService).when(mCall).getConnectionService();
+ doReturn(mCallAudioManager).when(mCallsManager).getCallAudioManager();
+ when(mMockContext.getText(R.string.callendpoint_name_earpiece)).thenReturn("Earpiece");
+ when(mMockContext.getText(R.string.callendpoint_name_bluetooth)).thenReturn("Bluetooth");
+ when(mMockContext.getText(R.string.callendpoint_name_wiredheadset))
+ .thenReturn("Wired headset");
+ when(mMockContext.getText(R.string.callendpoint_name_speaker)).thenReturn("Speaker");
+ when(mMockContext.getText(R.string.callendpoint_name_streaming)).thenReturn("External");
+ when(mMockContext.getText(R.string.callendpoint_name_unknown)).thenReturn("Unknown");
+ }
+
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @Test
+ public void testCurrentEndpointChangedToBluetooth() throws Exception {
+ mCallEndpointController.onCallAudioStateChanged(audioState1, audioState2);
+ CallEndpoint endpoint = mCallEndpointController.getCurrentCallEndpoint();
+ Set<CallEndpoint> availableEndpoints = mCallEndpointController.getAvailableEndpoints();
+ String bluetoothAddress = mCallEndpointController.getBluetoothAddress(endpoint);
+
+ // Earpiece, Wired headset, Speaker and two Bluetooth endpoint is available
+ assertEquals(5, availableEndpoints.size());
+ // type of current CallEndpoint is Bluetooth
+ assertEquals(CallEndpoint.TYPE_BLUETOOTH, endpoint.getEndpointType());
+ assertEquals(bluetoothDevice1.getAddress(), bluetoothAddress);
+
+ verify(mCallsManager).updateCallEndpoint(eq(endpoint));
+ verify(mConnectionService, times(1)).onCallEndpointChanged(eq(mCall), eq(endpoint));
+ verify(mCallsManager, never()).updateAvailableCallEndpoints(any());
+ verify(mConnectionService, never()).onAvailableCallEndpointsChanged(any(), any());
+ verify(mCallsManager, never()).updateMuteState(anyBoolean());
+ verify(mConnectionService, never()).onMuteStateChanged(any(), anyBoolean());
+ }
+
+ @Test
+ public void testCurrentEndpointChangedBetweenBluetooth() throws Exception {
+ mCallEndpointController.onCallAudioStateChanged(audioState2, audioState3);
+ CallEndpoint endpoint = mCallEndpointController.getCurrentCallEndpoint();
+ Set<CallEndpoint> availableEndpoints = mCallEndpointController.getAvailableEndpoints();
+ String bluetoothAddress = mCallEndpointController.getBluetoothAddress(endpoint);
+
+ // Earpiece, Wired headset, Speaker and two Bluetooth endpoint is available
+ assertEquals(5, availableEndpoints.size());
+ // type of current CallEndpoint is Bluetooth
+ assertEquals(CallEndpoint.TYPE_BLUETOOTH, endpoint.getEndpointType());
+ assertEquals(bluetoothDevice2.getAddress(), bluetoothAddress);
+
+ verify(mCallsManager).updateCallEndpoint(eq(endpoint));
+ verify(mConnectionService, times(1)).onCallEndpointChanged(eq(mCall), eq(endpoint));
+ verify(mCallsManager, never()).updateAvailableCallEndpoints(any());
+ verify(mConnectionService, never()).onAvailableCallEndpointsChanged(any(), any());
+ verify(mCallsManager, never()).updateMuteState(anyBoolean());
+ verify(mConnectionService, never()).onMuteStateChanged(any(), anyBoolean());
+ }
+
+ @Test
+ public void testAvailableEndpointChanged() throws Exception {
+ mCallEndpointController.onCallAudioStateChanged(audioState1, audioState6);
+ CallEndpoint endpoint = mCallEndpointController.getCurrentCallEndpoint();
+ Set<CallEndpoint> availableEndpoints = mCallEndpointController.getAvailableEndpoints();
+
+ // Only Earpiece is available
+ assertEquals(1, availableEndpoints.size());
+ // type of current CallEndpoint is Earpiece
+ assertEquals(CallEndpoint.TYPE_EARPIECE, endpoint.getEndpointType());
+ assertTrue(availableEndpoints.contains(endpoint));
+
+ verify(mCallsManager, never()).updateCallEndpoint(any());
+ verify(mConnectionService, never()).onCallEndpointChanged(any(), any());
+ verify(mCallsManager).updateAvailableCallEndpoints(eq(availableEndpoints));
+ verify(mConnectionService, times(1)).onAvailableCallEndpointsChanged(eq(mCall),
+ eq(availableEndpoints));
+ verify(mCallsManager, never()).updateMuteState(anyBoolean());
+ verify(mConnectionService, never()).onMuteStateChanged(any(), anyBoolean());
+ }
+
+ @Test
+ public void testAvailableBluetoothEndpointChanged() throws Exception {
+ mCallEndpointController.onCallAudioStateChanged(audioState2, audioState4);
+ CallEndpoint endpoint = mCallEndpointController.getCurrentCallEndpoint();
+ Set<CallEndpoint> availableEndpoints = mCallEndpointController.getAvailableEndpoints();
+ String bluetoothAddress = mCallEndpointController.getBluetoothAddress(endpoint);
+
+ // Earpiece, Wired headset, Speaker and one Bluetooth endpoint is available
+ assertEquals(4, availableEndpoints.size());
+ // type of current CallEndpoint is Bluetooth
+ assertEquals(CallEndpoint.TYPE_BLUETOOTH, endpoint.getEndpointType());
+ assertEquals(bluetoothDevice1.getAddress(), bluetoothAddress);
+
+ verify(mCallsManager, never()).updateCallEndpoint(any());
+ verify(mConnectionService, never()).onCallEndpointChanged(any(), any());
+ verify(mCallsManager).updateAvailableCallEndpoints(eq(availableEndpoints));
+ verify(mConnectionService, times(1)).onAvailableCallEndpointsChanged(eq(mCall),
+ eq(availableEndpoints));
+ verify(mCallsManager, never()).updateMuteState(anyBoolean());
+ verify(mConnectionService, never()).onMuteStateChanged(any(), anyBoolean());
+ }
+
+ @Test
+ public void testMuteStateChanged() throws Exception {
+ mCallEndpointController.onCallAudioStateChanged(audioState1, audioState5);
+ CallEndpoint endpoint = mCallEndpointController.getCurrentCallEndpoint();
+ Set<CallEndpoint> availableEndpoints = mCallEndpointController.getAvailableEndpoints();
+
+ // Earpiece, Wired headset, Speaker and two Bluetooth endpoint is available
+ assertEquals(5, availableEndpoints.size());
+ // type of current CallEndpoint is Earpiece
+ assertEquals(CallEndpoint.TYPE_EARPIECE, endpoint.getEndpointType());
+
+ verify(mCallsManager, never()).updateCallEndpoint(any());
+ verify(mConnectionService, never()).onCallEndpointChanged(any(), any());
+ verify(mCallsManager, never()).updateAvailableCallEndpoints(any());
+ verify(mConnectionService, never()).onAvailableCallEndpointsChanged(any(), any());
+ verify(mCallsManager).updateMuteState(eq(true));
+ verify(mConnectionService, times(1)).onMuteStateChanged(eq(mCall), eq(true));
+ }
+
+ @Test
+ public void testNotifyForcely() throws Exception {
+ mCallEndpointController.onCallAudioStateChanged(audioState1, audioState1);
+ CallEndpoint endpoint = mCallEndpointController.getCurrentCallEndpoint();
+ Set<CallEndpoint> availableEndpoints = mCallEndpointController.getAvailableEndpoints();
+
+ // Earpiece, Wired headset, Speaker and two Bluetooth endpoint is available
+ assertEquals(5, availableEndpoints.size());
+ // type of current CallEndpoint is Earpiece
+ assertEquals(CallEndpoint.TYPE_EARPIECE, endpoint.getEndpointType());
+
+ verify(mCallsManager).updateCallEndpoint(eq(endpoint));
+ verify(mConnectionService, times(1)).onCallEndpointChanged(eq(mCall), eq(endpoint));
+ verify(mCallsManager).updateAvailableCallEndpoints(eq(availableEndpoints));
+ verify(mConnectionService, times(1)).onAvailableCallEndpointsChanged(eq(mCall),
+ eq(availableEndpoints));
+ verify(mCallsManager).updateMuteState(eq(false));
+ verify(mConnectionService, times(1)).onMuteStateChanged(eq(mCall), eq(false));
+ }
+
+ @Test
+ public void testEndpointChangeRequest() throws Exception {
+ mCallEndpointController.onCallAudioStateChanged(null, audioState1);
+ CallEndpoint endpoint1 = mCallEndpointController.getCurrentCallEndpoint();
+
+ mCallEndpointController.onCallAudioStateChanged(audioState1, audioState2);
+ CallEndpoint endpoint2 = mCallEndpointController.getCurrentCallEndpoint();
+
+ mCallEndpointController.requestCallEndpointChange(endpoint1, mResultReceiver);
+ verify(mCallAudioManager).setAudioRoute(eq(CallAudioState.ROUTE_EARPIECE), eq(null));
+
+ mCallEndpointController.requestCallEndpointChange(endpoint2, mResultReceiver);
+ verify(mCallAudioManager).setAudioRoute(eq(CallAudioState.ROUTE_BLUETOOTH),
+ eq(bluetoothDevice1.getAddress()));
+ }
+
+ @Test
+ public void testEndpointChangeRequest_EndpointDoesNotExist() throws Exception {
+ mCallEndpointController.onCallAudioStateChanged(null, audioState2);
+ CallEndpoint endpoint = mCallEndpointController.getCurrentCallEndpoint();
+ mCallEndpointController.onCallAudioStateChanged(audioState2, audioState6);
+
+ mCallEndpointController.requestCallEndpointChange(endpoint, mResultReceiver);
+ verify(mCallAudioManager, never()).setAudioRoute(eq(CallAudioState.ROUTE_BLUETOOTH),
+ eq(bluetoothDevice1.getAddress()));
+ verify(mResultReceiver).send(eq(CallEndpoint.ENDPOINT_OPERATION_FAILED), any());
+ }
+}
\ No newline at end of file
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index 526ab69..8fe64e2 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -72,6 +72,8 @@
import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.CallAudioModeStateMachine;
import com.android.server.telecom.CallAudioRouteStateMachine;
+import com.android.server.telecom.CallEndpointController;
+import com.android.server.telecom.CallEndpointControllerFactory;
import com.android.server.telecom.CallDiagnosticServiceController;
import com.android.server.telecom.CallState;
import com.android.server.telecom.CallerInfoLookupHelper;
@@ -199,6 +201,8 @@
@Mock private AudioProcessingNotification mAudioProcessingNotification;
@Mock private InCallControllerFactory mInCallControllerFactory;
@Mock private InCallController mInCallController;
+ @Mock private CallEndpointControllerFactory mCallEndpointControllerFactory;
+ @Mock private CallEndpointController mCallEndpointController;
@Mock private ConnectionServiceFocusManager mConnectionSvrFocusMgr;
@Mock private CallAudioRouteStateMachine mCallAudioRouteStateMachine;
@Mock private CallAudioRouteStateMachine.Factory mCallAudioRouteStateMachineFactory;
@@ -225,6 +229,8 @@
mProximitySensorManager);
when(mInCallControllerFactory.create(any(), any(), any(), any(), any(), any(),
any())).thenReturn(mInCallController);
+ when(mCallEndpointControllerFactory.create(any(), any(), any())).thenReturn(
+ mCallEndpointController);
when(mCallAudioRouteStateMachineFactory.create(any(), any(), any(), any(), any(), any(),
anyInt())).thenReturn(mCallAudioRouteStateMachine);
when(mCallAudioModeStateMachineFactory.create(any(), any()))
@@ -266,7 +272,8 @@
mInCallControllerFactory,
mCallDiagnosticServiceController,
mRoleManagerAdapter,
- mToastFactory);
+ mToastFactory,
+ mCallEndpointControllerFactory);
when(mPhoneAccountRegistrar.getPhoneAccount(
eq(SELF_MANAGED_HANDLE), any())).thenReturn(SELF_MANAGED_ACCOUNT);
diff --git a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
index 6e6646f..c8da78c 100755
--- a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
@@ -34,6 +34,7 @@
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
import android.telecom.CallScreeningService;
import android.telecom.Conference;
import android.telecom.Connection;
@@ -343,6 +344,18 @@
throws RemoteException { }
@Override
+ public void onCallEndpointChanged(String callId, CallEndpoint callEndpoint,
+ Session.Info sessionInfo) { }
+
+ @Override
+ public void onAvailableCallEndpointsChanged(String callId,
+ List<CallEndpoint> availableCallEndpoints, Session.Info sessionInfo) { }
+
+ @Override
+ public void onMuteStateChanged(String callId, boolean isMuted,
+ Session.Info sessionInfo) { }
+
+ @Override
public void onUsingAlternativeUi(String activeCallId, boolean usingAlternativeUi,
Session.Info info) throws RemoteException { }
diff --git a/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java b/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
index d114cb8..88b5bb5 100644
--- a/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
@@ -26,9 +26,11 @@
import android.os.IInterface;
import android.os.RemoteException;
import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
import android.telecom.ParcelableCall;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -105,6 +107,15 @@
}
@Override
+ public void onCallEndpointChanged(CallEndpoint callEndpoint) {}
+
+ @Override
+ public void onAvailableCallEndpointsChanged(List<CallEndpoint> availableCallEndpoints) {}
+
+ @Override
+ public void onMuteStateChanged(boolean isMuted) {}
+
+ @Override
public void bringToForeground(boolean showDialpad) throws RemoteException {
mBringToForeground = true;
mShowDialpad = showDialpad;