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

Commit 9dedc0af authored by Eric Laurent's avatar Eric Laurent
Browse files

AudioDeviceBroker: Bluetooth LE communication route compatibility

Implement compatibility mode for Bluetooth LE audio or Hearing Aid (HA)
deviced and VoIP apps targetting a SDK before 32.
Before SDK 31 (Android S), there is no API to control routing to those
devices and users will not understand why their Bluetooth LE or HA device
does not work for popular VoIP applications. We set the bar at SDK 32 to
leave a grace period for app developers to adopt the new API.
In order to offer a better UX during the transition period when
applications migrate to the new routing APIs
(AudioManager.setCommunicationDevice()), we select the HA or LE Audio device
by default for VoIP calls if connected.
This behavior is similar to what is done for wired headsets and users can
transfer the call to the earpiece by disconnecting the Bluetooth device.
Speaker phone ON/OFF function still works.
We do not implement this compatibility mode for cell calls (audio mode IN_CALL)
because the Dialer app has already migrated to new APIs and supports
Bluetooth LE Audio adn HA devices.

Bug: 243827847
Test: regression on Voice and VoIP calls with HFP profile
Change-Id: I5d4391896a407dad9853ee7a0b964c4ebba203d6
Merged-In: I5d4391896a407dad9853ee7a0b964c4ebba203d6
parent 6ffa5fcc
Loading
Loading
Loading
Loading
+112 −38
Original line number Diff line number Diff line
@@ -17,9 +17,12 @@ package com.android.server.audio;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.compat.CompatChanges;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -118,8 +121,39 @@ import java.util.concurrent.atomic.AtomicBoolean;
    // TODO do not "share" the lock between AudioService and BtHelpr, see b/123769055
    /*package*/ final Object mSetModeLock = new Object();

    /** PID of current audio mode owner communicated by AudioService */
    private int mModeOwnerPid = 0;
    /** AudioModeInfo contains information on current audio mode owner
     * communicated by AudioService */
    /* package */ static final class AudioModeInfo {
        /** Current audio mode */
        final int mMode;
        /** PID of current audio mode owner */
        final int mPid;
        /** UID of current audio mode owner */
        final int mUid;

        AudioModeInfo(int mode, int pid, int uid) {
            mMode = mode;
            mPid = pid;
            mUid = uid;
        }

        @Override
        public String toString() {
            return "AudioModeInfo: mMode=" + AudioSystem.modeToString(mMode)
                    + ", mPid=" + mPid
                    + ", mUid=" + mUid;
        }
    };

    private AudioModeInfo mAudioModeOwner = new AudioModeInfo(AudioSystem.MODE_NORMAL, 0, 0);

    /**
     * Indicates that default communication device is chosen by routing rules in audio policy
     * manager and not forced by AudioDeviceBroker.
     */
    @ChangeId
    @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S_V2)
    public static final long USE_SET_COMMUNICATION_DEVICE = 243827847L;

    //-------------------------------------------------------------------
    /*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service) {
@@ -187,7 +221,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
    /*package*/ void onSystemReady() {
        synchronized (mSetModeLock) {
            synchronized (mDeviceStateLock) {
                mModeOwnerPid = mAudioService.getModeOwnerPid();
                mAudioModeOwner = mAudioService.getAudioModeOwner();
                mBtHelper.onSystemReady();
            }
        }
@@ -368,11 +402,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
    @GuardedBy("mDeviceStateLock")
    private CommunicationRouteClient topCommunicationRouteClient() {
        for (CommunicationRouteClient crc : mCommunicationRouteClients) {
            if (crc.getPid() == mModeOwnerPid) {
            if (crc.getPid() == mAudioModeOwner.mPid) {
                return crc;
            }
        }
        if (!mCommunicationRouteClients.isEmpty() && mModeOwnerPid == 0) {
        if (!mCommunicationRouteClients.isEmpty() && mAudioModeOwner.mPid == 0) {
            return mCommunicationRouteClients.get(0);
        }
        return null;
@@ -390,7 +424,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
        AudioDeviceAttributes device = crc != null ? crc.getDevice() : null;
        if (AudioService.DEBUG_COMM_RTE) {
            Log.v(TAG, "requestedCommunicationDevice, device: "
                    + device + " mode owner pid: " + mModeOwnerPid);
                    + device + "mAudioModeOwner: " + mAudioModeOwner.toString());
        }
        return device;
    }
@@ -774,8 +808,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
        sendLMsgNoDelay(MSG_II_SET_LE_AUDIO_OUT_VOLUME, SENDMSG_REPLACE, info);
    }

    /*package*/ void postSetModeOwnerPid(int pid, int mode) {
        sendIIMsgNoDelay(MSG_I_SET_MODE_OWNER_PID, SENDMSG_REPLACE, pid, mode);
    /*package*/ void postSetModeOwner(int mode, int pid, int uid) {
        sendLMsgNoDelay(MSG_I_SET_MODE_OWNER, SENDMSG_REPLACE,
                new AudioModeInfo(mode, pid, uid));
    }

    /*package*/ void postBluetoothA2dpDeviceConfigChange(@NonNull BluetoothDevice device) {
@@ -1162,7 +1197,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
        pw.println(prefix + "mAccessibilityStrategyId: "
                +  mAccessibilityStrategyId);

        pw.println("\n" + prefix + "mModeOwnerPid: " + mModeOwnerPid);
        pw.println("\n" + prefix + "mAudioModeOwner: " + mAudioModeOwner);

        mBtHelper.dump(pw, prefix);
    }
@@ -1288,12 +1323,19 @@ import java.util.concurrent.atomic.AtomicBoolean;
                    }
                    break;
                case MSG_L_SET_BT_ACTIVE_DEVICE:
                    synchronized (mSetModeLock) {
                        synchronized (mDeviceStateLock) {
                            BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj;
                            mDeviceInventory.onSetBtActiveDevice(btInfo,
                                (btInfo.mProfile != BluetoothProfile.LE_AUDIO || btInfo.mIsLeOutput)
                                    (btInfo.mProfile
                                            != BluetoothProfile.LE_AUDIO || btInfo.mIsLeOutput)
                                            ? mAudioService.getBluetoothContextualVolumeStream()
                                            : AudioSystem.STREAM_DEFAULT);
                            if (btInfo.mProfile == BluetoothProfile.LE_AUDIO
                                    || btInfo.mProfile == BluetoothProfile.HEARING_AID) {
                                onUpdateCommunicationRouteClient("setBluetoothActiveDevice");
                            }
                        }
                    }
                    break;
                case MSG_BT_HEADSET_CNCT_FAILED:
@@ -1338,11 +1380,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
                        mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1);
                    }
                    break;
                case MSG_I_SET_MODE_OWNER_PID:
                case MSG_I_SET_MODE_OWNER:
                    synchronized (mSetModeLock) {
                        synchronized (mDeviceStateLock) {
                            mModeOwnerPid = msg.arg1;
                            if (msg.arg2 != AudioSystem.MODE_RINGTONE) {
                            mAudioModeOwner = (AudioModeInfo) msg.obj;
                            if (mAudioModeOwner.mMode != AudioSystem.MODE_RINGTONE) {
                                onUpdateCommunicationRouteClient("setNewModeOwner");
                            }
                        }
@@ -1504,7 +1546,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
    private static final int MSG_REPORT_NEW_ROUTES = 13;
    private static final int MSG_II_SET_HEARING_AID_VOLUME = 14;
    private static final int MSG_I_SET_AVRCP_ABSOLUTE_VOLUME = 15;
    private static final int MSG_I_SET_MODE_OWNER_PID = 16;
    private static final int MSG_I_SET_MODE_OWNER = 16;

    private static final int MSG_I_BT_SERVICE_DISCONNECTED_PROFILE = 22;
    private static final int MSG_IL_BT_SERVICE_CONNECTED_PROFILE = 23;
@@ -1826,8 +1868,16 @@ import java.util.concurrent.atomic.AtomicBoolean;
            AudioSystem.setParameters("BT_SCO=on");
        }
        if (preferredCommunicationDevice == null) {
            AudioDeviceAttributes defaultDevice = getDefaultCommunicationDevice();
            if (defaultDevice != null) {
                setPreferredDevicesForStrategySync(
                        mCommunicationStrategyId, Arrays.asList(defaultDevice));
                setPreferredDevicesForStrategySync(
                        mAccessibilityStrategyId, Arrays.asList(defaultDevice));
            } else {
                removePreferredDevicesForStrategySync(mCommunicationStrategyId);
                removePreferredDevicesForStrategySync(mAccessibilityStrategyId);
            }
        } else {
            setPreferredDevicesForStrategySync(
                    mCommunicationStrategyId, Arrays.asList(preferredCommunicationDevice));
@@ -1856,9 +1906,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
        }
    }

    // @GuardedBy("mSetModeLock")
    @GuardedBy("mDeviceStateLock")
    private void onUpdatePhoneStrategyDevice(AudioDeviceAttributes device) {
        synchronized (mSetModeLock) {
            synchronized (mDeviceStateLock) {
        boolean wasSpeakerphoneActive = isSpeakerphoneActive();
        mPreferredCommunicationDevice = device;
        updateActiveCommunicationDevice();
@@ -1875,8 +1925,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
        mAudioService.postUpdateRingerModeServiceInt();
        dispatchCommunicationDevice();
    }
        }
    }

    private CommunicationRouteClient removeCommunicationRouteClient(
                    IBinder cb, boolean unregister) {
@@ -1915,6 +1963,32 @@ import java.util.concurrent.atomic.AtomicBoolean;
        return null;
    }

    @GuardedBy("mDeviceStateLock")
    private boolean communnicationDeviceCompatOn() {
        return mAudioModeOwner.mMode == AudioSystem.MODE_IN_COMMUNICATION
                && !(CompatChanges.isChangeEnabled(
                        USE_SET_COMMUNICATION_DEVICE, mAudioModeOwner.mUid)
                     || mAudioModeOwner.mUid == android.os.Process.SYSTEM_UID);
    }

    @GuardedBy("mDeviceStateLock")
    AudioDeviceAttributes getDefaultCommunicationDevice() {
        // For system server (Telecom) and APKs targeting S and above, we let the audio
        // policy routing rules select the default communication device.
        // For older APKs, we force Hearing Aid or LE Audio headset when connected as
        // those APKs cannot select a LE Audio or Hearing Aid device explicitly.
        AudioDeviceAttributes device = null;
        if (communnicationDeviceCompatOn()) {
            // If both LE and Hearing Aid are active (thie should not happen),
            // priority to Hearing Aid.
            device = mDeviceInventory.getDeviceOfType(AudioSystem.DEVICE_OUT_HEARING_AID);
            if (device == null) {
                device = mDeviceInventory.getDeviceOfType(AudioSystem.DEVICE_OUT_BLE_HEADSET);
            }
        }
        return device;
    }

    @Nullable UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
        synchronized (mDeviceStateLock) {
            return mDeviceInventory.getDeviceSensorUuid(device);
+14 −0
Original line number Diff line number Diff line
@@ -294,6 +294,7 @@ public class AudioDeviceInventory {
        }
    }

    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
    void onSetBtActiveDevice(@NonNull AudioDeviceBroker.BtDeviceInfo btInfo, int streamType) {
        if (AudioService.DEBUG_DEVICES) {
@@ -1526,6 +1527,19 @@ public class AudioDeviceInventory {
            return di.mSensorUuid;
        }
    }

    /* package */ AudioDeviceAttributes getDeviceOfType(int type) {
        synchronized (mDevicesLock) {
            for (DeviceInfo di : mConnectedDevices.values()) {
                if (di.mDeviceType == type) {
                    return new AudioDeviceAttributes(
                            di.mDeviceType, di.mDeviceAddress, di.mDeviceName);
                }
            }
        }
        return null;
    }

    //----------------------------------------------------------
    // For tests only

+12 −7
Original line number Diff line number Diff line
@@ -5053,16 +5053,17 @@ public class AudioService extends IAudioService.Stub
    }

    /**
     * Return the pid of the current audio mode owner
     * Return information on the current audio mode owner
     * @return 0 if nobody owns the mode
     */
    @GuardedBy("mDeviceBroker.mSetModeLock")
    /*package*/ int getModeOwnerPid() {
    /*package*/ AudioDeviceBroker.AudioModeInfo getAudioModeOwner() {
        SetModeDeathHandler hdlr = getAudioModeOwnerHandler();
        if (hdlr != null) {
            return hdlr.getPid();
            return new AudioDeviceBroker.AudioModeInfo(
                    hdlr.getMode(), hdlr.getPid(), hdlr.getUid());
        }
        return 0;
        return new AudioDeviceBroker.AudioModeInfo(AudioSystem.MODE_NORMAL, 0 , 0);
    }

    /**
@@ -5248,7 +5249,7 @@ public class AudioService extends IAudioService.Stub

                // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all SCO
                // connections not started by the application changing the mode when pid changes
                mDeviceBroker.postSetModeOwnerPid(pid, mode);
                mDeviceBroker.postSetModeOwner(mode, pid, uid);
            } else {
                Log.w(TAG, "onUpdateAudioMode: failed to set audio mode to: " + mode);
            }
@@ -5575,7 +5576,10 @@ public class AudioService extends IAudioService.Stub
        }
        return deviceIds.stream().mapToInt(Integer::intValue).toArray();
    }
        /** @see AudioManager#setCommunicationDevice(int) */
        /**
         * @see AudioManager#setCommunicationDevice(int)
         * @see AudioManager#clearCommunicationDevice()
         */
    public boolean setCommunicationDevice(IBinder cb, int portId) {
        final int uid = Binder.getCallingUid();
        final int pid = Binder.getCallingPid();
@@ -5590,7 +5594,8 @@ public class AudioService extends IAudioService.Stub
                throw new IllegalArgumentException("invalid device type " + device.getType());
            }
        }
        final String eventSource = new StringBuilder("setCommunicationDevice(")
        final String eventSource = new StringBuilder()
                .append(device == null ? "clearCommunicationDevice(" : "setCommunicationDevice(")
                .append(") from u/pid:").append(uid).append("/")
                .append(pid).toString();