Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 410f0107 authored by Pranav Madapurmath's avatar Pranav Madapurmath
Browse files

Use communication device callback and fix reinitialization routing

This CL replaces the speaker broadcasts with using the provided
AudioManager onCommunicationDeviceChanged callback in order to listen to
updates to the communication device. This is done with the hope of
seeing improved performance with when Telecom receives indication of
when the speaker toggles on/off. We have seen cases where when the
speaker is toggled on/off quickly, the broadcasts can be received out of
order and cause Telecom to show the audio route as speaker when audio is
routing to earpiece, for example. This occurs as a result of switching
from earpiece -> speaker -> earpiece and Telecom receiving SPEAKER_ON
after SPEAKER_OFF.

There is also a fix done to ensure that the audio routing is
reinitialized at the end of the call. We've gotten reports from user
that if the audio is routed to speaker and another call is placed, then
there is an audio flicker from speaker to earpiece in the UI at the
beginning of the call.

Bug: 353419513
Bug: 371625143
Flag:
com.android.server.telecom.flags.new_audio_path_speaker_broadcast_and_unfocused_routing
Test: Added unit test for verifying call audio route reintialization
Test: Manual to confirm that onCommunicationDeviceChanged callback is
being received for speaker phone switches. Tested with switching between
earpiece, speaker, and LEHS.

Change-Id: If51cc560791cbc81734a2ff6e3e57067910531cb
parent 26b000fa
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -107,3 +107,14 @@ flag {
    purpose: PURPOSE_BUGFIX
  }
}

# OWNER=pmadapurmath TARGET=25Q1
flag {
  name: "new_audio_path_speaker_broadcast_and_unfocused_routing"
  namespace: "telecom"
  description: "Replace the speaker broadcasts with the communication device changed listener and resolve baseline routing issues when a call ends."
  bug: "353419513"
  metadata {
    purpose: PURPOSE_BUGFIX
  }
}
+2 −1
Original line number Diff line number Diff line
@@ -318,7 +318,8 @@ public class AudioRoute {
    // sending SPEAKER_OFF, or disconnecting SCO).
    void onOrigRouteAsPendingRoute(boolean active, PendingAudioRoute pendingAudioRoute,
            AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager) {
        Log.i(this, "onOrigRouteAsPendingRoute: active (%b), type (%d)", active, mAudioRouteType);
        Log.i(this, "onOrigRouteAsPendingRoute: active (%b), type (%s)", active,
                DEVICE_TYPE_STRINGS.get(mAudioRouteType));
        if (active) {
            int result = clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager,
                    audioManager);
+46 −9
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.server.telecom;

import static com.android.server.telecom.AudioRoute.BT_AUDIO_ROUTE_TYPES;
import static com.android.server.telecom.AudioRoute.DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE;
import static com.android.server.telecom.AudioRoute.TYPE_INVALID;
import static com.android.server.telecom.AudioRoute.TYPE_SPEAKER;

@@ -63,6 +64,8 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CallAudioRouteController implements CallAudioRouteAdapter {
    private static final AudioRoute DUMMY_ROUTE = new AudioRoute(TYPE_INVALID, null, null);
@@ -107,6 +110,8 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
    private PendingAudioRoute mPendingAudioRoute;
    private AudioRoute.Factory mAudioRouteFactory;
    private StatusBarNotifier mStatusBarNotifier;
    private AudioManager.OnCommunicationDeviceChangedListener mCommunicationDeviceListener;
    private ExecutorService mCommunicationDeviceChangedExecutor;
    private FeatureFlags mFeatureFlags;
    private int mFocusType;
    private int mCallSupportedRouteMask = -1;
@@ -200,10 +205,12 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
        handlerThread.start();

        // Register broadcast receivers
        if (!mFeatureFlags.newAudioPathSpeakerBroadcastAndUnfocusedRouting()) {
            IntentFilter speakerChangedFilter = new IntentFilter(
                    AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED);
            speakerChangedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
            context.registerReceiver(mSpeakerPhoneChangeReceiver, speakerChangedFilter);
        }

        IntentFilter micMuteChangedFilter = new IntentFilter(
                AudioManager.ACTION_MICROPHONE_MUTE_CHANGED);
@@ -214,6 +221,31 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
        muteChangedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
        context.registerReceiver(mMuteChangeReceiver, muteChangedFilter);

        // Register AudioManager#onCommunicationDeviceChangedListener listener to receive updates
        // to communication device (via AudioManager#setCommunicationDevice). This is a replacement
        // to using broadcasts in the hopes of improving performance.
        mCommunicationDeviceChangedExecutor = Executors.newSingleThreadExecutor();
        mCommunicationDeviceListener = new AudioManager.OnCommunicationDeviceChangedListener() {
            @Override
            public void onCommunicationDeviceChanged(AudioDeviceInfo device) {
                @AudioRoute.AudioRouteType int audioType = device != null
                        ? DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.get(device.getType())
                        : TYPE_INVALID;
                Log.i(this, "onCommunicationDeviceChanged: %d", audioType);
                if (device != null && device.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
                    sendMessageWithSessionInfo(SPEAKER_ON);
                } else if (mPendingAudioRoute != null && mPendingAudioRoute.getOrigRoute() != null
                        && mPendingAudioRoute.getOrigRoute().getType() == AudioRoute.TYPE_SPEAKER) {
                    sendMessageWithSessionInfo(SPEAKER_OFF);
                }
            }
        };
        if (mFeatureFlags.newAudioPathSpeakerBroadcastAndUnfocusedRouting()) {
            mAudioManager.addOnCommunicationDeviceChangedListener(
                    mCommunicationDeviceChangedExecutor,
                    mCommunicationDeviceListener);
        }

        // Create handler
        mHandler = new Handler(handlerThread.getLooper()) {
            @Override
@@ -798,11 +830,11 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
        boolean currentRouteNeedsUpdate = mCurrentRoute.getType() == type;
        if (mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) {
            if (pendingRouteNeedsUpdate) {
                pendingRouteNeedsUpdate &= mPendingAudioRoute.getDestRoute().getBluetoothAddress()
                pendingRouteNeedsUpdate = mPendingAudioRoute.getDestRoute().getBluetoothAddress()
                        .equals(previouslyActiveDeviceAddress);
            }
            if (currentRouteNeedsUpdate) {
                currentRouteNeedsUpdate &= mCurrentRoute.getBluetoothAddress()
                currentRouteNeedsUpdate = mCurrentRoute.getBluetoothAddress()
                        .equals(previouslyActiveDeviceAddress);
            }
        }
@@ -852,8 +884,13 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {

                    // Reset mute state after call ends.
                    handleMuteChanged(false);
                    // Route back to inactive route.
                    routeTo(false, mCurrentRoute);
                    // Ensure we reset call audio state at the end of the call (i.e. if we're on
                    // speaker, route back to earpiece). If we're on BT, remain on BT if it's still
                    // connected.
                    AudioRoute route = mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()
                            ? calculateBaselineRoute(true, null)
                            : mCurrentRoute;
                    routeTo(false, route);
                    // Clear pending messages
                    mPendingAudioRoute.clearPendingMessages();
                    clearRingingBluetoothAddress();
@@ -1173,7 +1210,7 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
        }

        // Get corresponding audio route
        @AudioRoute.AudioRouteType int type = AudioRoute.DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.get(
        @AudioRoute.AudioRouteType int type = DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.get(
                deviceAttr.getType());
        if (BT_AUDIO_ROUTE_TYPES.contains(type)) {
            return getBluetoothRoute(type, deviceAttr.getAddress());
+27 −0
Original line number Diff line number Diff line
@@ -193,6 +193,7 @@ public class CallAudioRouteControllerTest extends TelecomTestCase {
        when(mFeatureFlags.ignoreAutoRouteToWatchDevice()).thenReturn(false);
        when(mFeatureFlags.useRefactoredAudioRouteSwitching()).thenReturn(true);
        when(mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()).thenReturn(false);
        when(mFeatureFlags.newAudioPathSpeakerBroadcastAndUnfocusedRouting()).thenReturn(false);
    }

    @After
@@ -1031,6 +1032,32 @@ public class CallAudioRouteControllerTest extends TelecomTestCase {
        BLUETOOTH_DEVICES.remove(scoDevice);
    }

    @Test
    @SmallTest
    public void verifyRouteReinitializedAfterCallEnd() {
        when(mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()).thenReturn(true);
        mController.initialize();
        mController.setActive(true);

        // Switch to speaker
        mController.sendMessageWithSessionInfo(SPEAKER_ON);
        CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER, null,
                new HashSet<>());
        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
                any(CallAudioState.class), eq(expectedState));

        // Verify that call audio route is reinitialized to default (in this case, earpiece) when
        // call audio focus is lost.
        mController.sendMessageWithSessionInfo(SWITCH_FOCUS, NO_FOCUS, 0);
        mController.sendMessageWithSessionInfo(SPEAKER_OFF);
        expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER, null,
                new HashSet<>());
        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
                any(CallAudioState.class), eq(expectedState));
    }

    private void verifyConnectBluetoothDevice(int audioType) {
        mController.initialize();
        mController.setActive(true);