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

Commit 160ab0fc authored by Tyler Gunn's avatar Tyler Gunn
Browse files

Reduce use of clearCommunicationDevice.

We can reduce the use of clearCommunicationDevice so that it is only
called at the end of a call.  When switching routes mid-call, there is no
need to call it.

To fix this, we pass the destination audio route's active/inactive status
into `setOrigRoute` and update the `PendingAudioRoute` with that.
When `AudioRoute#clearCommunicationDevice` is called, we'll now only
clear if we are transitioning to an inactive state.

Flag: com.android.server.telecom.flags.only_clear_communication_device_on_inactive
Fixes: 376781369
Test: Run Telecom unit tests.
Test: Swap between earpiece/speaker, disconnect call.
Test: Swap between earpiece/speaker/SCO, disconnect call.
Test: Swap between earpiece/speaker/LE, disconnect call.
Change-Id: Ibc444784d96d9bf99700ba52cf321fcdcbfc2ea0
parent 6b2d00fc
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -129,3 +129,14 @@ flag {
    purpose: PURPOSE_BUGFIX
  }
}

# OWNER=tgunn TARGET=25Q2
flag {
  name: "only_clear_communication_device_on_inactive"
  namespace: "telecom"
  description: "Only clear the communication device when transitioning to an inactive route."
  bug: "376781369"
  metadata {
    purpose: PURPOSE_BUGFIX
  }
}
+59 −10
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import static com.android.server.telecom.CallAudioRouteAdapter.SPEAKER_ON;

import android.annotation.IntDef;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothStatusCodes;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
@@ -300,7 +301,8 @@ public class AudioRoute {
                        pendingAudioRoute.setCommunicationDeviceType(mAudioRouteType);
                    }
                    Log.i(this, "onDestRouteAsPendingRoute: route=%s, "
                            + "AudioManager#setCommunicationDevice()=%b", this, result);
                            + "AudioManager#setCommunicationDevice(%s)=%b", this,
                            audioDeviceTypeToString(mInfo.getType()), result);
                    break;
                }
            }
@@ -314,13 +316,19 @@ public class AudioRoute {
        }
    }

    // Takes care of cleaning up original audio route (i.e. clearCommunicationDevice,
    // sending SPEAKER_OFF, or disconnecting SCO).
    void onOrigRouteAsPendingRoute(boolean active, PendingAudioRoute pendingAudioRoute,
    /**
     * Takes care of cleaning up original audio route (i.e. clearCommunicationDevice,
     * sending SPEAKER_OFF, or disconnecting SCO).
     * @param wasActive Was the origin route active or not.
     * @param pendingAudioRoute The pending audio route change we're performing.
     * @param audioManager Good 'ol audio manager.
     * @param bluetoothRouteManager The BT route manager.
     */
    void onOrigRouteAsPendingRoute(boolean wasActive, PendingAudioRoute pendingAudioRoute,
            AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager) {
        Log.i(this, "onOrigRouteAsPendingRoute: active (%b), type (%s)", active,
                DEVICE_TYPE_STRINGS.get(mAudioRouteType));
        if (active) {
        Log.i(this, "onOrigRouteAsPendingRoute: wasActive (%b), type (%s), pending(%s)", wasActive,
                DEVICE_TYPE_STRINGS.get(mAudioRouteType), pendingAudioRoute);
        if (wasActive) {
            int result = clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager,
                    audioManager);
            if (mAudioRouteType == TYPE_SPEAKER) {
@@ -389,6 +397,20 @@ public class AudioRoute {
        return success;
    }

    /**
     * Clears the communication device; this takes into account the fact that SCO devices require
     * us to call {@link BluetoothHeadset#disconnectAudio()} rather than
     * {@link AudioManager#clearCommunicationDevice()}.
     * As a general rule, if we are transitioning from an active route to another active route, we
     * do NOT need to call {@link AudioManager#clearCommunicationDevice()}, but if the device is a
     * legacy SCO device we WILL need to call {@link BluetoothHeadset#disconnectAudio()}.  We rely
     * on the {@link PendingAudioRoute#isActive()} indicator to tell us if the destination route
     * is going to be active or not.
     * @param pendingAudioRoute The pending audio route transition we're implementing.
     * @param bluetoothRouteManager The BT route manager.
     * @param audioManager The audio manager.
     * @return -1 if nothing was done, or the result code from the BT SCO disconnect.
     */
    int clearCommunicationDevice(PendingAudioRoute pendingAudioRoute,
            BluetoothRouteManager bluetoothRouteManager, AudioManager audioManager) {
        // Try to see if there's a previously set device for communication that should be cleared.
@@ -402,10 +424,18 @@ public class AudioRoute {
            Log.i(this, "clearCommunicationDevice: Disconnecting SCO device.");
            result = bluetoothRouteManager.getDeviceManager().disconnectSco();
        } else {
            Log.i(this, "clearCommunicationDevice: AudioManager#clearCommunicationDevice, type=%s",
            // Only clear communication device if the destination route will be inactive; route to
            // route transitions do not require clearing the communication device.
            boolean onlyClearCommunicationDeviceOnInactive =
                    pendingAudioRoute.getFeatureFlags().onlyClearCommunicationDeviceOnInactive();
            if (!onlyClearCommunicationDeviceOnInactive
                    || (onlyClearCommunicationDeviceOnInactive && !pendingAudioRoute.isActive())) {
                Log.i(this,
                        "clearCommunicationDevice: AudioManager#clearCommunicationDevice, type=%s",
                        DEVICE_TYPE_STRINGS.get(pendingAudioRoute.getCommunicationDeviceType()));
                audioManager.clearCommunicationDevice();
            }
        }

        if (result == BluetoothStatusCodes.SUCCESS) {
            if (pendingAudioRoute.getFeatureFlags().resolveActiveBtRoutingAndBtTimingIssue()) {
@@ -430,4 +460,23 @@ public class AudioRoute {
            pendingAudioRoute.clearPendingMessage(new Pair<>(SPEAKER_ON, null));
        }
    }

    /**
     * Get a human readable (for logs) version of an an audio device type.
     * @param type the device type
     * @return the human readable string
     */
    private static String audioDeviceTypeToString(int type) {
        return switch (type) {
            case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE -> "earpiece";
            case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER -> "speaker";
            case AudioDeviceInfo.TYPE_BUS -> "bus(auto speaker)";
            case AudioDeviceInfo.TYPE_BLUETOOTH_SCO -> "bt sco";
            case AudioDeviceInfo.TYPE_BLE_HEADSET -> "bt le";
            case AudioDeviceInfo.TYPE_HEARING_AID -> "bt hearing aid";
            case AudioDeviceInfo.TYPE_USB_HEADSET -> "usb headset";
            case AudioDeviceInfo.TYPE_WIRED_HEADSET -> "wired headset";
            default -> Integer.toString(type);
        };
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -251,6 +251,7 @@ public class CallAudioCommunicationDeviceTracker {
        }

        // Clear device and reset locally saved device type.
        Log.i(this, "clearCommunicationDevice: AudioManager#clearCommunicationDevice()");
        mAudioManager.clearCommunicationDevice();
        mAudioDeviceType = sAUDIO_DEVICE_TYPE_INVALID;

+6 −3
Original line number Diff line number Diff line
@@ -567,7 +567,8 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
            }
            // override pending route while keep waiting for still pending messages for the
            // previous pending route
            mPendingAudioRoute.setOrigRoute(mIsActive, mPendingAudioRoute.getDestRoute());
            mPendingAudioRoute.setOrigRoute(mIsActive /* origin */,
                    mPendingAudioRoute.getDestRoute(), active /* dest */);
        } else {
            if (mCurrentRoute.equals(destRoute) && (mIsActive == active)) {
                return;
@@ -576,10 +577,12 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
                    mIsActive, destRoute, active);
            // route to pending route
            if (getCallSupportedRoutes().contains(mCurrentRoute)) {
                mPendingAudioRoute.setOrigRoute(mIsActive, mCurrentRoute);
                mPendingAudioRoute.setOrigRoute(mIsActive /* origin */, mCurrentRoute,
                        active /* dest */);
            } else {
                // Avoid waiting for pending messages for an unavailable route
                mPendingAudioRoute.setOrigRoute(mIsActive, DUMMY_ROUTE);
                mPendingAudioRoute.setOrigRoute(mIsActive /* origin */, DUMMY_ROUTE,
                        active /* dest */);
            }
            mIsPending = true;
        }
+31 −2
Original line number Diff line number Diff line
@@ -70,8 +70,23 @@ public class PendingAudioRoute {
        mCommunicationDeviceType = AudioRoute.TYPE_INVALID;
    }

    void setOrigRoute(boolean active, AudioRoute origRoute) {
        origRoute.onOrigRouteAsPendingRoute(active, this, mAudioManager, mBluetoothRouteManager);
    /**
     * Sets the originating route information, and begins the process of transitioning OUT of the
     * originating route.
     * Note: We also pass in whether the destination route is going to be active.  This is so that
     * {@link AudioRoute#onOrigRouteAsPendingRoute(boolean, PendingAudioRoute, AudioManager,
     * BluetoothRouteManager)} knows whether or not the destination route will be active or not and
     * can determine whether or not it needs to call {@link AudioManager#clearCommunicationDevice()}
     * or not.  To optimize audio performance we only need to clear the communication device if the
     * end result is going to be that we are in an inactive state.
     * @param isOriginActive Whether the origin is active.
     * @param origRoute The origin.
     * @param isDestActive Whether the destination will be active.
     */
    void setOrigRoute(boolean isOriginActive, AudioRoute origRoute, boolean isDestActive) {
        mActive = isDestActive;
        origRoute.onOrigRouteAsPendingRoute(isOriginActive, this, mAudioManager,
                mBluetoothRouteManager);
        mOrigRoute = origRoute;
    }

@@ -134,6 +149,10 @@ public class PendingAudioRoute {
        return mPendingMessages;
    }

    /**
     * Whether the destination {@link #getDestRoute()} will be active or not.
     * @return {@code true} if destination will be active, {@code false} otherwise.
     */
    public boolean isActive() {
        return mActive;
    }
@@ -154,4 +173,14 @@ public class PendingAudioRoute {
    public FeatureFlags getFeatureFlags() {
        return mFeatureFlags;
    }

    @Override
    public String toString() {
        return "PendingAudioRoute{" +
                ", mOrigRoute=" + mOrigRoute +
                ", mDestRoute=" + mDestRoute +
                ", mActive=" + mActive +
                ", mCommunicationDeviceType=" + mCommunicationDeviceType +
                '}';
    }
}
Loading