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

Commit 967b8f86 authored by Pranav Madapurmath's avatar Pranav Madapurmath Committed by Android (Google) Code Review
Browse files

Merge "Resolve call audio refactoring issues." into main

parents 11ec12e8 47dc4bbd
Loading
Loading
Loading
Loading
+127 −16
Original line number Diff line number Diff line
@@ -23,11 +23,14 @@ import static com.android.server.telecom.CallAudioRouteAdapter.SPEAKER_OFF;
import static com.android.server.telecom.CallAudioRouteAdapter.SPEAKER_ON;

import android.annotation.IntDef;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothStatusCodes;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.telecom.Log;

import com.android.internal.annotations.VisibleForTesting;
import com.android.server.telecom.bluetooth.BluetoothRouteManager;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -38,6 +41,7 @@ import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@@ -46,9 +50,10 @@ public class AudioRoute {
    public static class Factory {
        private final ScheduledExecutorService mScheduledExecutorService =
                new ScheduledThreadPoolExecutor(1);
        private final CompletableFuture<AudioRoute> mAudioRouteFuture = new CompletableFuture<>();
        private CompletableFuture<AudioRoute> mAudioRouteFuture;
        public AudioRoute create(@AudioRouteType int type, String bluetoothAddress,
                                 AudioManager audioManager) throws RuntimeException {
            mAudioRouteFuture = new CompletableFuture();
            createRetry(type, bluetoothAddress, audioManager, MAX_CONNECTION_RETRIES);
            try {
                return mAudioRouteFuture.get();
@@ -58,8 +63,10 @@ public class AudioRoute {
        }
        private void createRetry(@AudioRouteType int type, String bluetoothAddress,
                                       AudioManager audioManager, int retryCount) {
            // Early exit if exceeded max number of retries (and complete the future).
            if (retryCount == 0) {
                mAudioRouteFuture.complete(null);
                return;
            }

            Log.i(this, "creating AudioRoute with type %s and address %s, retry count %d",
@@ -81,10 +88,17 @@ public class AudioRoute {
                    }
                }
            }
            if (routeInfo == null) {
            // Try connecting BT device anyway (to handle wearables not showing as available
            // communication device or LE device not showing up since it may not be the lead
            // device).
            if (routeInfo == null && bluetoothAddress == null) {
                try {
                    mScheduledExecutorService.schedule(
                            () -> createRetry(type, bluetoothAddress, audioManager, retryCount - 1),
                            RETRY_TIME_DELAY, TimeUnit.MILLISECONDS);
                } catch (RejectedExecutionException e) {
                    Log.e(this, e, "Could not schedule retry for audio routing.");
                }
            } else {
                mAudioRouteFuture.complete(new AudioRoute(type, bluetoothAddress, routeInfo));
            }
@@ -222,30 +236,81 @@ public class AudioRoute {

    // Invoked when entered pending route whose dest route is this route
    void onDestRouteAsPendingRoute(boolean active, PendingAudioRoute pendingAudioRoute,
                                   AudioManager audioManager) {
            BluetoothDevice device, AudioManager audioManager,
            BluetoothRouteManager bluetoothRouteManager) {
        Log.i(this, "onDestRouteAsPendingRoute: active (%b), type (%d)", active, mAudioRouteType);
        if (pendingAudioRoute.isActive() && !active) {
            Log.i(this, "clearCommunicationDevice");
            audioManager.clearCommunicationDevice();
            clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager, audioManager);
        } else if (active) {
            // Handle BT routing case.
            if (BT_AUDIO_ROUTE_TYPES.contains(mAudioRouteType)) {
                boolean connectedBtAudio = connectBtAudio(pendingAudioRoute, device,
                        audioManager, bluetoothRouteManager);
                // Special handling for SCO case.
                if (mAudioRouteType == TYPE_BLUETOOTH_SCO) {
                    // Check if the communication device was set for the device, even if
                    // BluetoothHeadset#connectAudio reports that the SCO connection wasn't
                    // successfully established.
                    boolean scoConnected = audioManager.getCommunicationDevice().equals(mInfo);
                    if (connectedBtAudio || scoConnected) {
                        pendingAudioRoute.setCommunicationDeviceType(mAudioRouteType);
                    }
                    if (connectedBtAudio) {
                        pendingAudioRoute.addMessage(BT_AUDIO_CONNECTED);
                    } else if (!scoConnected) {
                        pendingAudioRoute.onMessageReceived(
                                PENDING_ROUTE_FAILED, mBluetoothAddress);
                    }
                    return;
                }
            } else if (mAudioRouteType == TYPE_SPEAKER) {
                pendingAudioRoute.addMessage(SPEAKER_ON);
            }
            if (!audioManager.setCommunicationDevice(mInfo)) {
                pendingAudioRoute.onMessageReceived(PENDING_ROUTE_FAILED);

            boolean result = false;
            List<AudioDeviceInfo> devices = audioManager.getAvailableCommunicationDevices();
            for (AudioDeviceInfo deviceInfo : devices) {
                // It's possible for the AudioDeviceInfo to be updated for the BT device so adjust
                // mInfo accordingly.
                if (BT_AUDIO_ROUTE_TYPES.contains(mAudioRouteType) && mBluetoothAddress
                        .equals(deviceInfo.getAddress())) {
                    mInfo = deviceInfo;
                }
                if (deviceInfo.equals(mInfo)) {
                    result = audioManager.setCommunicationDevice(mInfo);
                    if (result) {
                        pendingAudioRoute.setCommunicationDeviceType(mAudioRouteType);
                    }
                    Log.i(this, "Result of setting communication device for audio "
                            + "route (%s) - %b", this, result);
                    break;
                }
            }

            // It's possible that BluetoothStateReceiver needs to report that the device is active
            // before being able to successfully set the communication device. Refrain from sending
            // pending route failed message for BT route until the second attempt fails.
            if (!result && !BT_AUDIO_ROUTE_TYPES.contains(mAudioRouteType)) {
                pendingAudioRoute.onMessageReceived(PENDING_ROUTE_FAILED, null);
            }
        }
    }

    // Takes care of cleaning up original audio route (i.e. clearCommunicationDevice,
    // sending SPEAKER_OFF, or disconnecting SCO).
    void onOrigRouteAsPendingRoute(boolean active, PendingAudioRoute pendingAudioRoute,
                                   AudioManager audioManager) {
            AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager) {
        Log.i(this, "onOrigRouteAsPendingRoute: active (%b), type (%d)", active, mAudioRouteType);
        if (active) {
            if (mAudioRouteType == TYPE_BLUETOOTH_SCO) {
                pendingAudioRoute.addMessage(BT_AUDIO_DISCONNECTED);
            } else if (mAudioRouteType == TYPE_SPEAKER) {
            if (mAudioRouteType == TYPE_SPEAKER) {
                pendingAudioRoute.addMessage(SPEAKER_OFF);
            }
            int result = clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager,
                    audioManager);
            // Only send BT_AUDIO_DISCONNECTED for SCO if disconnect was successful.
            if (mAudioRouteType == TYPE_BLUETOOTH_SCO && result == BluetoothStatusCodes.SUCCESS) {
                pendingAudioRoute.addMessage(BT_AUDIO_DISCONNECTED);
            }
        }
    }

@@ -282,4 +347,50 @@ public class AudioRoute {
                + ", Address=" + ((mBluetoothAddress != null) ? mBluetoothAddress : "invalid")
                + "]";
    }

    private boolean connectBtAudio(PendingAudioRoute pendingAudioRoute, BluetoothDevice device,
            AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager) {
        // Ensure that if another BT device was set, it is disconnected before connecting
        // the new one.
        AudioRoute currentRoute = pendingAudioRoute.getOrigRoute();
        if (currentRoute.getBluetoothAddress() != null &&
                !currentRoute.getBluetoothAddress().equals(device.getAddress())) {
            clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager, audioManager);
        }

        // Connect to the device (explicit handling for HFP devices).
        boolean success = false;
        if (device != null) {
            success = bluetoothRouteManager.getDeviceManager()
                    .connectAudio(device, mAudioRouteType);
        }

        Log.i(this, "connectBtAudio: routeToConnectTo = %s, successful = %b",
                this, success);
        return success;
    }

    private int clearCommunicationDevice(PendingAudioRoute pendingAudioRoute,
            BluetoothRouteManager bluetoothRouteManager, AudioManager audioManager) {
        // Try to see if there's a previously set device for communication that should be cleared.
        // This only serves to help in the SCO case to ensure that we disconnect the headset.
        if (pendingAudioRoute.getCommunicationDeviceType() == AudioRoute.TYPE_INVALID) {
            return -1;
        }

        int result = BluetoothStatusCodes.SUCCESS;
        if (pendingAudioRoute.getCommunicationDeviceType() == TYPE_BLUETOOTH_SCO) {
            Log.i(this, "Disconnecting SCO device.");
            result = bluetoothRouteManager.getDeviceManager().disconnectSco();
        } else {
            Log.i(this, "Clearing communication device for audio type %d.",
                    pendingAudioRoute.getCommunicationDeviceType());
            audioManager.clearCommunicationDevice();
        }

        if (result == BluetoothStatusCodes.SUCCESS) {
            pendingAudioRoute.setCommunicationDeviceType(AudioRoute.TYPE_INVALID);
        }
        return result;
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -134,5 +134,6 @@ public interface CallAudioRouteAdapter {
    CallAudioState getCurrentCallAudioState();
    boolean isHfpDeviceAvailable();
    Handler getAdapterHandler();
    PendingAudioRoute getPendingAudioRoute();
    void dump(IndentingPrintWriter pw);
}
+293 −50

File changed.

Preview size limit exceeded, changes collapsed.

+7 −0
Original line number Diff line number Diff line
@@ -2095,7 +2095,14 @@ public class CallAudioRouteStateMachine extends StateMachine implements CallAudi
        return base;
    }

    @Override
    public Handler getAdapterHandler() {
        return getHandler();
    }

    @Override
    public PendingAudioRoute getPendingAudioRoute() {
        // Only used by CallAudioRouteController.
        return null;
    }
}
+2 −1
Original line number Diff line number Diff line
@@ -654,7 +654,8 @@ public class CallsManager extends Call.ListenerBase
            );
        } else {
            callAudioRouteAdapter = new CallAudioRouteController(context, this, audioServiceFactory,
                    new AudioRoute.Factory(), wiredHeadsetManager, mBluetoothRouteManager);
                    new AudioRoute.Factory(), wiredHeadsetManager, mBluetoothRouteManager,
                    statusBarNotifier, featureFlags);
        }
        callAudioRouteAdapter.initialize();
        bluetoothStateReceiver.setCallAudioRouteAdapter(callAudioRouteAdapter);
Loading