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

Commit ec51aa82 authored by Eric Laurent's avatar Eric Laurent
Browse files

AudioService: refactor communication route control

Audio routing for communication use cases (cell, video and VoIP calls)
is controlled by separate APIs: setSpeakerPhoneOn(), startBluetoothSco()
and stopBluetoothSco().
Requests for speakerphone and bluetooth are managed by separate client
stacks although in the end they control a single routing state in audio
policy manager, which creates problems in case of conflicting requests
and race conditions.
This CL refactors the implementation by regrouping the Bluetooth Sco and
Speaker client stacks into a single one. This makes sure that no
conflicting routing request exist for a given client.
It also makes handling of race conditions and prioritization between
clients easier.
Finally, it prepares the introduction of a new API that will be the
single entry point for communication route control.

Also in this CL:
- Option to enable more debug log for communication route
- Fixes in BtHelper.receiveBtEvent()
  1) Fix intent broadcast missing in case of transition from external
  activation to internal activation.
  2) Do not clear SCO requests when SCO audio is disconnected: this can
  happen because current active communication route request is different
  from SCO but pending requests must stay in the stack.
  3) Do not clear SCO requests when the audio mode owner changes. Any Active
  communication route requested by new audio mode owner will be honored
  and pending requests will be restored when mode owner changes again.

Test: regression tests with cell and VoIP calls

Change-Id: If9d2f24b9def78ccd791294ed42d95ce30f0ba8b
Merged-In: If9d2f24b9def78ccd791294ed42d95ce30f0ba8b
parent 617d1495
Loading
Loading
Loading
Loading
+314 −175

File changed.

Preview size limit exceeded, changes collapsed.

+25 −24
Original line number Original line Diff line number Diff line
@@ -207,6 +207,9 @@ public class AudioService extends IAudioService.Stub
    /** debug calls to devices APIs */
    /** debug calls to devices APIs */
    protected static final boolean DEBUG_DEVICES = false;
    protected static final boolean DEBUG_DEVICES = false;


    /** Debug communication route */
    protected static final boolean DEBUG_COMM_RTE = false;

    /** How long to delay before persisting a change in volume/ringer mode. */
    /** How long to delay before persisting a change in volume/ringer mode. */
    private static final int PERSIST_DELAY = 500;
    private static final int PERSIST_DELAY = 500;


@@ -3689,7 +3692,7 @@ public class AudioService extends IAudioService.Stub
        final boolean ringerModeMute = ringerMode == AudioManager.RINGER_MODE_VIBRATE
        final boolean ringerModeMute = ringerMode == AudioManager.RINGER_MODE_VIBRATE
                || ringerMode == AudioManager.RINGER_MODE_SILENT;
                || ringerMode == AudioManager.RINGER_MODE_SILENT;
        final boolean shouldRingSco = ringerMode == AudioManager.RINGER_MODE_VIBRATE
        final boolean shouldRingSco = ringerMode == AudioManager.RINGER_MODE_VIBRATE
                && isBluetoothScoOn();
                && mDeviceBroker.isBluetoothScoOn();
        // Ask audio policy engine to force use Bluetooth SCO channel if needed
        // Ask audio policy engine to force use Bluetooth SCO channel if needed
        final String eventSource = "muteRingerModeStreams() from u/pid:" + Binder.getCallingUid()
        final String eventSource = "muteRingerModeStreams() from u/pid:" + Binder.getCallingUid()
                + "/" + Binder.getCallingPid();
                + "/" + Binder.getCallingPid();
@@ -4274,10 +4277,10 @@ public class AudioService extends IAudioService.Stub
        // for logging only
        // for logging only
        final int uid = Binder.getCallingUid();
        final int uid = Binder.getCallingUid();
        final int pid = Binder.getCallingPid();
        final int pid = Binder.getCallingPid();

        final String eventSource = new StringBuilder("setSpeakerphoneOn(").append(on)
        final String eventSource = new StringBuilder("setSpeakerphoneOn(").append(on)
                .append(") from u/pid:").append(uid).append("/")
                .append(") from u/pid:").append(uid).append("/")
                .append(pid).toString();
                .append(pid).toString();
        final boolean stateChanged = mDeviceBroker.setSpeakerphoneOn(cb, pid, on, eventSource);
        new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE
        new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE
                + MediaMetrics.SEPARATOR + "setSpeakerphoneOn")
                + MediaMetrics.SEPARATOR + "setSpeakerphoneOn")
                .setUid(uid)
                .setUid(uid)
@@ -4285,24 +4288,21 @@ public class AudioService extends IAudioService.Stub
                .set(MediaMetrics.Property.STATE, on
                .set(MediaMetrics.Property.STATE, on
                        ? MediaMetrics.Value.ON : MediaMetrics.Value.OFF)
                        ? MediaMetrics.Value.ON : MediaMetrics.Value.OFF)
                .record();
                .record();

        if (stateChanged) {
        final long ident = Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
            try {
        mDeviceBroker.setSpeakerphoneOn(cb, pid, on, eventSource);
                mContext.sendBroadcastAsUser(
                        new Intent(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED)
                                .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), UserHandle.ALL);
            } finally {
        Binder.restoreCallingIdentity(ident);
        Binder.restoreCallingIdentity(ident);
    }
    }
        }
    }


    /** @see AudioManager#isSpeakerphoneOn() */
    /** @see AudioManager#isSpeakerphoneOn() */
    public boolean isSpeakerphoneOn() {
    public boolean isSpeakerphoneOn() {
        return mDeviceBroker.isSpeakerphoneOn();
        return mDeviceBroker.isSpeakerphoneOn();
    }
    }



    /** BT SCO audio state seen by apps using the deprecated API setBluetoothScoOn().
     * @see isBluetoothScoOn() */
    private boolean mBtScoOnByApp;

    /** @see AudioManager#setBluetoothScoOn(boolean) */
    /** @see AudioManager#setBluetoothScoOn(boolean) */
    public void setBluetoothScoOn(boolean on) {
    public void setBluetoothScoOn(boolean on) {
        if (!checkAudioSettingsPermission("setBluetoothScoOn()")) {
        if (!checkAudioSettingsPermission("setBluetoothScoOn()")) {
@@ -4311,7 +4311,7 @@ public class AudioService extends IAudioService.Stub


        // Only enable calls from system components
        // Only enable calls from system components
        if (UserHandle.getCallingAppId() >= FIRST_APPLICATION_UID) {
        if (UserHandle.getCallingAppId() >= FIRST_APPLICATION_UID) {
            mDeviceBroker.setBluetoothScoOnByApp(on);
            mBtScoOnByApp = on;
            return;
            return;
        }
        }


@@ -4337,7 +4337,7 @@ public class AudioService extends IAudioService.Stub
     * Note that it doesn't report internal state, but state seen by apps (which may have
     * Note that it doesn't report internal state, but state seen by apps (which may have
     * called setBluetoothScoOn() */
     * called setBluetoothScoOn() */
    public boolean isBluetoothScoOn() {
    public boolean isBluetoothScoOn() {
        return mDeviceBroker.isBluetoothScoOnForApp();
        return mBtScoOnByApp || mDeviceBroker.isBluetoothScoOn();
    }
    }


    // TODO investigate internal users due to deprecation of SDK API
    // TODO investigate internal users due to deprecation of SDK API
@@ -4384,7 +4384,7 @@ public class AudioService extends IAudioService.Stub
                .set(MediaMetrics.Property.SCO_AUDIO_MODE,
                .set(MediaMetrics.Property.SCO_AUDIO_MODE,
                        BtHelper.scoAudioModeToString(scoAudioMode))
                        BtHelper.scoAudioModeToString(scoAudioMode))
                .record();
                .record();
        startBluetoothScoInt(cb, scoAudioMode, eventSource);
        startBluetoothScoInt(cb, pid, scoAudioMode, eventSource);


    }
    }


@@ -4403,10 +4403,10 @@ public class AudioService extends IAudioService.Stub
                .set(MediaMetrics.Property.SCO_AUDIO_MODE,
                .set(MediaMetrics.Property.SCO_AUDIO_MODE,
                        BtHelper.scoAudioModeToString(BtHelper.SCO_MODE_VIRTUAL_CALL))
                        BtHelper.scoAudioModeToString(BtHelper.SCO_MODE_VIRTUAL_CALL))
                .record();
                .record();
        startBluetoothScoInt(cb, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource);
        startBluetoothScoInt(cb, pid, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource);
    }
    }


    void startBluetoothScoInt(IBinder cb, int scoAudioMode, @NonNull String eventSource) {
    void startBluetoothScoInt(IBinder cb, int pid, int scoAudioMode, @NonNull String eventSource) {
        MediaMetrics.Item mmi = new MediaMetrics.Item(MediaMetrics.Name.AUDIO_BLUETOOTH)
        MediaMetrics.Item mmi = new MediaMetrics.Item(MediaMetrics.Name.AUDIO_BLUETOOTH)
                .set(MediaMetrics.Property.EVENT, "startBluetoothScoInt")
                .set(MediaMetrics.Property.EVENT, "startBluetoothScoInt")
                .set(MediaMetrics.Property.SCO_AUDIO_MODE,
                .set(MediaMetrics.Property.SCO_AUDIO_MODE,
@@ -4417,9 +4417,9 @@ public class AudioService extends IAudioService.Stub
            mmi.set(MediaMetrics.Property.EARLY_RETURN, "permission or systemReady").record();
            mmi.set(MediaMetrics.Property.EARLY_RETURN, "permission or systemReady").record();
            return;
            return;
        }
        }
        synchronized (mDeviceBroker.mSetModeLock) {
        final long ident = Binder.clearCallingIdentity();
            mDeviceBroker.startBluetoothScoForClient_Sync(cb, scoAudioMode, eventSource);
        mDeviceBroker.startBluetoothScoForClient(cb, pid, scoAudioMode, eventSource);
        }
        Binder.restoreCallingIdentity(ident);
        mmi.record();
        mmi.record();
    }
    }


@@ -4434,9 +4434,9 @@ public class AudioService extends IAudioService.Stub
        final String eventSource =  new StringBuilder("stopBluetoothSco()")
        final String eventSource =  new StringBuilder("stopBluetoothSco()")
                .append(") from u/pid:").append(uid).append("/")
                .append(") from u/pid:").append(uid).append("/")
                .append(pid).toString();
                .append(pid).toString();
        synchronized (mDeviceBroker.mSetModeLock) {
        final long ident = Binder.clearCallingIdentity();
            mDeviceBroker.stopBluetoothScoForClient_Sync(cb, eventSource);
        mDeviceBroker.stopBluetoothScoForClient(cb, pid, eventSource);
        }
        Binder.restoreCallingIdentity(ident);
        new MediaMetrics.Item(MediaMetrics.Name.AUDIO_BLUETOOTH)
        new MediaMetrics.Item(MediaMetrics.Name.AUDIO_BLUETOOTH)
                .setUid(uid)
                .setUid(uid)
                .setPid(pid)
                .setPid(pid)
@@ -7670,6 +7670,7 @@ public class AudioService extends IAudioService.Stub
        pw.print("  mHasVibrator="); pw.println(mHasVibrator);
        pw.print("  mHasVibrator="); pw.println(mHasVibrator);
        pw.print("  mVolumePolicy="); pw.println(mVolumePolicy);
        pw.print("  mVolumePolicy="); pw.println(mVolumePolicy);
        pw.print("  mAvrcpAbsVolSupported="); pw.println(mAvrcpAbsVolSupported);
        pw.print("  mAvrcpAbsVolSupported="); pw.println(mAvrcpAbsVolSupported);
        pw.print("  mBtScoOnByApp="); pw.println(mBtScoOnByApp);
        pw.print("  mIsSingleVolume="); pw.println(mIsSingleVolume);
        pw.print("  mIsSingleVolume="); pw.println(mIsSingleVolume);
        pw.print("  mUseFixedVolume="); pw.println(mUseFixedVolume);
        pw.print("  mUseFixedVolume="); pw.println(mUseFixedVolume);
        pw.print("  mFixedVolumeDevices="); pw.println(dumpDeviceTypes(mFixedVolumeDevices));
        pw.print("  mFixedVolumeDevices="); pw.println(dumpDeviceTypes(mFixedVolumeDevices));
+112 −308
Original line number Original line Diff line number Diff line
@@ -30,8 +30,6 @@ import android.content.Intent;
import android.media.AudioManager;
import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.AudioSystem;
import android.os.Binder;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserHandle;
import android.provider.Settings;
import android.provider.Settings;
import android.util.Log;
import android.util.Log;
@@ -39,9 +37,7 @@ import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.GuardedBy;


import java.io.PrintWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Objects;


/**
/**
@@ -58,10 +54,6 @@ public class BtHelper {
        mDeviceBroker = broker;
        mDeviceBroker = broker;
    }
    }


    // List of clients having issued a SCO start request
    @GuardedBy("BtHelper.this")
    private final @NonNull ArrayList<ScoClient> mScoClients = new ArrayList<ScoClient>();

    // BluetoothHeadset API to control SCO connection
    // BluetoothHeadset API to control SCO connection
    private @Nullable BluetoothHeadset mBluetoothHeadset;
    private @Nullable BluetoothHeadset mBluetoothHeadset;


@@ -301,6 +293,8 @@ public class BtHelper {
    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
    /*package*/ synchronized void receiveBtEvent(Intent intent) {
    /*package*/ synchronized void receiveBtEvent(Intent intent) {
        final String action = intent.getAction();
        final String action = intent.getAction();

        Log.i(TAG, "receiveBtEvent action: " + action + " mScoAudioState: " + mScoAudioState);
        if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) {
        if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) {
            BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            setBtScoActiveDevice(btDevice);
            setBtScoActiveDevice(btDevice);
@@ -308,20 +302,16 @@ public class BtHelper {
            boolean broadcast = false;
            boolean broadcast = false;
            int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
            int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
            int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
            int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
            // broadcast intent if the connection was initated by AudioService
            Log.i(TAG, "receiveBtEvent ACTION_AUDIO_STATE_CHANGED: " + btState);
            if (!mScoClients.isEmpty()
                    && (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL
                    || mScoAudioState == SCO_STATE_ACTIVATE_REQ
                    || mScoAudioState == SCO_STATE_DEACTIVATE_REQ
                    || mScoAudioState == SCO_STATE_DEACTIVATING)) {
                broadcast = true;
            }
            switch (btState) {
            switch (btState) {
                case BluetoothHeadset.STATE_AUDIO_CONNECTED:
                case BluetoothHeadset.STATE_AUDIO_CONNECTED:
                    scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
                    scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
                    if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
                    if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
                            && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
                            && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
                        mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
                        mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
                    } else if (mDeviceBroker.isBluetoothScoRequested()) {
                        // broadcast intent if the connection was initated by AudioService
                        broadcast = true;
                    }
                    }
                    mDeviceBroker.setBluetoothScoOn(true, "BtHelper.receiveBtEvent");
                    mDeviceBroker.setBluetoothScoOn(true, "BtHelper.receiveBtEvent");
                    break;
                    break;
@@ -333,21 +323,21 @@ public class BtHelper {
                    // notified by requestScoState() setting state to SCO_STATE_ACTIVATE_REQ.
                    // notified by requestScoState() setting state to SCO_STATE_ACTIVATE_REQ.
                    // 2) If audio was connected then disconnected via Bluetooth APIs and
                    // 2) If audio was connected then disconnected via Bluetooth APIs and
                    // we still have pending activation requests by apps: this is indicated by
                    // we still have pending activation requests by apps: this is indicated by
                    // state SCO_STATE_ACTIVE_EXTERNAL and the mScoClients list not empty.
                    // state SCO_STATE_ACTIVE_EXTERNAL and BT SCO is requested.
                    if (mScoAudioState == SCO_STATE_ACTIVATE_REQ
                    if (mScoAudioState == SCO_STATE_ACTIVATE_REQ
                            || (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL
                            || (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL
                                    && !mScoClients.isEmpty())) {
                                    && mDeviceBroker.isBluetoothScoRequested())) {
                        if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
                        if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
                                && connectBluetoothScoAudioHelper(mBluetoothHeadset,
                                && connectBluetoothScoAudioHelper(mBluetoothHeadset,
                                mBluetoothHeadsetDevice, mScoAudioMode)) {
                                mBluetoothHeadsetDevice, mScoAudioMode)) {
                            mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
                            mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
                            broadcast = false;
                            scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTING;
                            broadcast = true;
                            break;
                            break;
                        }
                        }
                    }
                    }
                    // Tear down SCO if disconnected from external
                    if (mScoAudioState != SCO_STATE_ACTIVE_EXTERNAL) {
                    if (mScoAudioState == SCO_STATE_DEACTIVATING) {
                        broadcast = true;
                        clearAllScoClients(0, false);
                    }
                    }
                    mScoAudioState = SCO_STATE_INACTIVE;
                    mScoAudioState = SCO_STATE_INACTIVE;
                    break;
                    break;
@@ -356,11 +346,8 @@ public class BtHelper {
                            && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
                            && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
                        mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
                        mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
                    }
                    }
                    broadcast = false;
                    break;
                    break;
                default:
                default:
                    // do not broadcast CONNECTING or invalid state
                    broadcast = false;
                    break;
                    break;
            }
            }
            if (broadcast) {
            if (broadcast) {
@@ -386,81 +373,19 @@ public class BtHelper {
                == BluetoothHeadset.STATE_AUDIO_CONNECTED;
                == BluetoothHeadset.STATE_AUDIO_CONNECTED;
    }
    }


    /**
     * Disconnect all SCO connections started by {@link AudioManager} except those started by
     * {@param exceptPid}
     *
     * @param exceptPid pid whose SCO connections through {@link AudioManager} should be kept
     */
    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
    /*package*/ synchronized void disconnectBluetoothSco(int exceptPid) {
        checkScoAudioState();
        if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL) {
            return;
        }
        clearAllScoClients(exceptPid, true);
    }

    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
    /*package*/ synchronized void startBluetoothScoForClient(IBinder cb, int scoAudioMode,
    /*package*/ synchronized boolean startBluetoothSco(int scoAudioMode,
                @NonNull String eventSource) {
                @NonNull String eventSource) {
        ScoClient client = getScoClient(cb, true);
        // The calling identity must be cleared before calling ScoClient.incCount().
        // inCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
        // and this must be done on behalf of system server to make sure permissions are granted.
        // The caller identity must be cleared after getScoClient() because it is needed if a new
        // client is created.
        final long ident = Binder.clearCallingIdentity();
        try {
        AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource));
        AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource));
            client.requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
        return requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
        } catch (NullPointerException e) {
            Log.e(TAG, "Null ScoClient", e);
        }
        Binder.restoreCallingIdentity(ident);
    }
    }


    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
    /*package*/ synchronized void stopBluetoothScoForClient(IBinder cb,
    /*package*/ synchronized boolean stopBluetoothSco(@NonNull String eventSource) {
            @NonNull String eventSource) {
        ScoClient client = getScoClient(cb, false);
        // The calling identity must be cleared before calling ScoClient.decCount().
        // decCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
        // and this must be done on behalf of system server to make sure permissions are granted.
        final long ident = Binder.clearCallingIdentity();
        if (client != null) {
            stopAndRemoveClient(client, eventSource);
        }
        Binder.restoreCallingIdentity(ident);
    }

    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
    /*package*/ synchronized void stopBluetoothScoForPid(int pid) {
        ScoClient client = getScoClientForPid(pid);
        if (client == null) {
            return;
        }
        final String eventSource = new StringBuilder("stopBluetoothScoForPid(")
                .append(pid).append(")").toString();
        stopAndRemoveClient(client, eventSource);
    }

    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
    // @GuardedBy("BtHelper.this")
    private void stopAndRemoveClient(ScoClient client, @NonNull String eventSource) {
        AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource));
        AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource));
        client.requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
        return requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, SCO_MODE_VIRTUAL_CALL);
                SCO_MODE_VIRTUAL_CALL);
        // If a disconnection is pending, the client will be removed when clearAllScoClients()
        // is called form receiveBtEvent()
        if (mScoAudioState != SCO_STATE_DEACTIVATE_REQ
                && mScoAudioState != SCO_STATE_DEACTIVATING) {
            client.remove(false /*stop */, true /*unregister*/);
        }
    }
    }


    /*package*/ synchronized void setHearingAidVolume(int index, int streamType) {
    /*package*/ synchronized void setHearingAidVolume(int index, int streamType) {
@@ -507,7 +432,6 @@ public class BtHelper {
    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
    /*package*/ synchronized void resetBluetoothSco() {
    /*package*/ synchronized void resetBluetoothSco() {
        clearAllScoClients(0, false);
        mScoAudioState = SCO_STATE_INACTIVE;
        mScoAudioState = SCO_STATE_INACTIVE;
        broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
        broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
        AudioSystem.setParameters("A2dpSuspended=false");
        AudioSystem.setParameters("A2dpSuspended=false");
@@ -733,77 +657,16 @@ public class BtHelper {
            };
            };


    //----------------------------------------------------------------------
    //----------------------------------------------------------------------
    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
    /*package*/ synchronized void scoClientDied(Object obj) {
        final ScoClient client = (ScoClient) obj;
        client.remove(true /*stop*/, false /*unregister*/);
        Log.w(TAG, "SCO client died");
    }

    private class ScoClient implements IBinder.DeathRecipient {
        private IBinder mCb; // To be notified of client's death
        private int mCreatorPid;

        ScoClient(IBinder cb) {
            mCb = cb;
            mCreatorPid = Binder.getCallingPid();
        }

        public void registerDeathRecipient() {
            try {
                mCb.linkToDeath(this, 0);
            } catch (RemoteException e) {
                Log.w(TAG, "ScoClient could not link to " + mCb + " binder death");
            }
        }

        public void unregisterDeathRecipient() {
            try {
                mCb.unlinkToDeath(this, 0);
            } catch (NoSuchElementException e) {
                Log.w(TAG, "ScoClient could not not unregistered to binder");
            }
        }

        @Override
        public void binderDied() {
            // process this from DeviceBroker's message queue to take the right locks since
            // this event can impact SCO mode and requires querying audio mode stack
            mDeviceBroker.postScoClientDied(this);
        }

        IBinder getBinder() {
            return mCb;
        }

        int getPid() {
            return mCreatorPid;
        }


    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
    //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
    //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
    @GuardedBy("BtHelper.this")
    @GuardedBy("BtHelper.this")
    private boolean requestScoState(int state, int scoAudioMode) {
    private boolean requestScoState(int state, int scoAudioMode) {
        checkScoAudioState();
        checkScoAudioState();
            if (mScoClients.size() != 1) {
                Log.i(TAG, "requestScoState: state=" + state + ", scoAudioMode=" + scoAudioMode
                        + ", num SCO clients=" + mScoClients.size());
                return true;
            }
        if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
        if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
            // Make sure that the state transitions to CONNECTING even if we cannot initiate
            // Make sure that the state transitions to CONNECTING even if we cannot initiate
            // the connection.
            // the connection.
            broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING);
            broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING);
                // Accept SCO audio activation only in NORMAL audio mode or if the mode is
                // currently controlled by the same client process.
                final int modeOwnerPid =  mDeviceBroker.getModeOwnerPid();
                if (modeOwnerPid != 0 && (modeOwnerPid != mCreatorPid)) {
                    Log.w(TAG, "requestScoState: audio mode is not NORMAL and modeOwnerPid "
                            + modeOwnerPid + " != creatorPid " + mCreatorPid);
                    broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
                    return false;
                }
            switch (mScoAudioState) {
            switch (mScoAudioState) {
                case SCO_STATE_INACTIVE:
                case SCO_STATE_INACTIVE:
                    mScoAudioMode = scoAudioMode;
                    mScoAudioMode = scoAudioMode;
@@ -843,7 +706,8 @@ public class BtHelper {
                            mBluetoothHeadsetDevice, mScoAudioMode)) {
                            mBluetoothHeadsetDevice, mScoAudioMode)) {
                        mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
                        mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
                    } else {
                    } else {
                            Log.w(TAG, "requestScoState: connect to " + mBluetoothHeadsetDevice
                        Log.w(TAG, "requestScoState: connect to "
                                + getAnonymizedAddress(mBluetoothHeadsetDevice)
                                + " failed, mScoAudioMode=" + mScoAudioMode);
                                + " failed, mScoAudioMode=" + mScoAudioMode);
                        broadcastScoConnectionState(
                        broadcastScoConnectionState(
                                AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
                                AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
@@ -911,19 +775,6 @@ public class BtHelper {
        return true;
        return true;
    }
    }


        @GuardedBy("BtHelper.this")
        void remove(boolean stop, boolean unregister) {
            if (unregister) {
                unregisterDeathRecipient();
            }
            if (stop) {
                requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
                        SCO_MODE_VIRTUAL_CALL);
            }
            mScoClients.remove(this);
        }
    }

    //-----------------------------------------------------
    //-----------------------------------------------------
    // Utilities
    // Utilities
    private void sendStickyBroadcastToAll(Intent intent) {
    private void sendStickyBroadcastToAll(Intent intent) {
@@ -974,49 +825,6 @@ public class BtHelper {
        }
        }
    }
    }



    @GuardedBy("BtHelper.this")
    private ScoClient getScoClient(IBinder cb, boolean create) {
        for (ScoClient existingClient : mScoClients) {
            if (existingClient.getBinder() == cb) {
                return existingClient;
            }
        }
        if (create) {
            ScoClient newClient = new ScoClient(cb);
            newClient.registerDeathRecipient();
            mScoClients.add(newClient);
            return newClient;
        }
        return null;
    }

    @GuardedBy("BtHelper.this")
    private ScoClient getScoClientForPid(int pid) {
        for (ScoClient cl : mScoClients) {
            if (cl.getPid() == pid) {
                return cl;
            }
        }
        return null;
    }

    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
    //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
    @GuardedBy("BtHelper.this")
    private void clearAllScoClients(int exceptPid, boolean stopSco) {
        final ArrayList<ScoClient> clients = new ArrayList<ScoClient>();
        for (ScoClient cl : mScoClients) {
            if (cl.getPid() != exceptPid) {
                clients.add(cl);
            }
        }
        for (ScoClient cl : clients) {
            cl.remove(stopSco, true /*unregister*/);
        }

    }

    private boolean getBluetoothHeadset() {
    private boolean getBluetoothHeadset() {
        boolean result = false;
        boolean result = false;
        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
@@ -1062,10 +870,6 @@ public class BtHelper {
        pw.println(prefix + "mBluetoothHeadsetDevice: " + mBluetoothHeadsetDevice);
        pw.println(prefix + "mBluetoothHeadsetDevice: " + mBluetoothHeadsetDevice);
        pw.println(prefix + "mScoAudioState: " + scoAudioStateToString(mScoAudioState));
        pw.println(prefix + "mScoAudioState: " + scoAudioStateToString(mScoAudioState));
        pw.println(prefix + "mScoAudioMode: " + scoAudioModeToString(mScoAudioMode));
        pw.println(prefix + "mScoAudioMode: " + scoAudioModeToString(mScoAudioMode));
        pw.println(prefix + "Sco clients:");
        mScoClients.forEach((cl) -> {
            pw.println("  " + prefix + "pid: " + cl.getPid() + " cb: " + cl.getBinder()); });

        pw.println("\n" + prefix + "mHearingAid: " + mHearingAid);
        pw.println("\n" + prefix + "mHearingAid: " + mHearingAid);
        pw.println(prefix + "mA2dp: " + mA2dp);
        pw.println(prefix + "mA2dp: " + mA2dp);
        pw.println(prefix + "mAvrcpAbsVolSupported: " + mAvrcpAbsVolSupported);
        pw.println(prefix + "mAvrcpAbsVolSupported: " + mAvrcpAbsVolSupported);