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

Commit 26454bc3 authored by Eric Laurent's avatar Eric Laurent Committed by Automerger Merge Worker
Browse files

Merge "AudioService AudioDeviceBroker: fix speakerphone control" into rvc-dev...

Merge "AudioService AudioDeviceBroker: fix speakerphone control" into rvc-dev am: bf01a79a am: 65701ab1 am: a8cad725

Change-Id: I0c91d84850c4b37eb36b161aca3f00d8ecd6081f
parents d6bad160 a8cad725
Loading
Loading
Loading
Loading
+1 −1
Original line number Original line Diff line number Diff line
@@ -1505,7 +1505,7 @@ public class AudioManager {
    public void setSpeakerphoneOn(boolean on){
    public void setSpeakerphoneOn(boolean on){
        final IAudioService service = getService();
        final IAudioService service = getService();
        try {
        try {
            service.setSpeakerphoneOn(on);
            service.setSpeakerphoneOn(mICallBack, on);
        } catch (RemoteException e) {
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
            throw e.rethrowFromSystemServer();
        }
        }
+1 −1
Original line number Original line Diff line number Diff line
@@ -150,7 +150,7 @@ interface IAudioService {


    oneway void avrcpSupportsAbsoluteVolume(String address, boolean support);
    oneway void avrcpSupportsAbsoluteVolume(String address, boolean support);


    void setSpeakerphoneOn(boolean on);
    void setSpeakerphoneOn(IBinder cb, boolean on);


    boolean isSpeakerphoneOn();
    boolean isSpeakerphoneOn();


+180 −25
Original line number Original line Diff line number Diff line
@@ -36,6 +36,7 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Looper;
import android.os.Message;
import android.os.Message;
import android.os.PowerManager;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemClock;
import android.util.Log;
import android.util.Log;
import android.util.PrintWriterPrinter;
import android.util.PrintWriterPrinter;
@@ -43,6 +44,9 @@ import android.util.PrintWriterPrinter;
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.NoSuchElementException;



/** @hide */
/** @hide */
/*package*/ final class AudioDeviceBroker {
/*package*/ final class AudioDeviceBroker {
@@ -91,6 +95,9 @@ import java.io.PrintWriter;
    // TODO do not "share" the lock between AudioService and BtHelpr, see b/123769055
    // TODO do not "share" the lock between AudioService and BtHelpr, see b/123769055
    /*package*/ final Object mSetModeLock = new Object();
    /*package*/ final Object mSetModeLock = new Object();


    /** PID of current audio mode owner communicated by AudioService */
    private int mModeOwnerPid = 0;

    //-------------------------------------------------------------------
    //-------------------------------------------------------------------
    /*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service) {
    /*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service) {
        mContext = context;
        mContext = context;
@@ -136,6 +143,7 @@ import java.io.PrintWriter;
    /*package*/ void onSystemReady() {
    /*package*/ void onSystemReady() {
        synchronized (mSetModeLock) {
        synchronized (mSetModeLock) {
            synchronized (mDeviceStateLock) {
            synchronized (mDeviceStateLock) {
                mModeOwnerPid = mAudioService.getModeOwnerPid();
                mBtHelper.onSystemReady();
                mBtHelper.onSystemReady();
            }
            }
        }
        }
@@ -202,10 +210,20 @@ import java.io.PrintWriter;
     * @param eventSource for logging purposes
     * @param eventSource for logging purposes
     * @return true if speakerphone state changed
     * @return true if speakerphone state changed
     */
     */
    /*package*/ boolean setSpeakerphoneOn(boolean on, String eventSource) {
    /*package*/ boolean setSpeakerphoneOn(IBinder cb, int pid, boolean on, String eventSource) {
        synchronized (mDeviceStateLock) {
        synchronized (mDeviceStateLock) {
            if (!addSpeakerphoneClient(cb, pid, on)) {
                return false;
            }
            final boolean wasOn = isSpeakerphoneOn();
            final boolean wasOn = isSpeakerphoneOn();
            if (on) {
            updateSpeakerphoneOn(eventSource);
            return (wasOn != isSpeakerphoneOn());
        }
    }

    @GuardedBy("mDeviceStateLock")
    private void updateSpeakerphoneOn(String eventSource) {
        if (isSpeakerphoneOnRequested()) {
            if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
            if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
                setForceUse_Async(AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, eventSource);
                setForceUse_Async(AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, eventSource);
            }
            }
@@ -219,11 +237,28 @@ import java.io.PrintWriter;
                mForcedUseForComm = AudioSystem.FORCE_NONE;
                mForcedUseForComm = AudioSystem.FORCE_NONE;
            }
            }
        }
        }

        mForcedUseForCommExt = mForcedUseForComm;
        mForcedUseForCommExt = mForcedUseForComm;
        setForceUse_Async(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource);
        setForceUse_Async(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource);
            return (wasOn != isSpeakerphoneOn());
    }
    }

    /**
     * Returns if speakerphone is requested ON or OFF.
     * If the current audio mode owner is in the speakerphone client list, use this preference.
     * Otherwise use first client's preference (first client corresponds to latest request).
     * Speakerphone is requested OFF if no client is in the list.
     * @return true if speakerphone is requested ON, false otherwise
     */
    @GuardedBy("mDeviceStateLock")
    private boolean isSpeakerphoneOnRequested() {
        if (mSpeakerphoneClients.isEmpty()) {
            return false;
        }
        for (SpeakerphoneClient cl : mSpeakerphoneClients) {
            if (cl.getPid() == mModeOwnerPid) {
                return cl.isOn();
            }
        }
        return mSpeakerphoneClients.get(0).isOn();
    }
    }


    /*package*/ boolean isSpeakerphoneOn() {
    /*package*/ boolean isSpeakerphoneOn() {
@@ -384,7 +419,8 @@ import java.io.PrintWriter;
                }
                }
                mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
                mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
            } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
            } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
                mForcedUseForComm = AudioSystem.FORCE_NONE;
                mForcedUseForComm = isSpeakerphoneOnRequested()
                        ? AudioSystem.FORCE_SPEAKER : AudioSystem.FORCE_NONE;
            }
            }
            mForcedUseForCommExt = mForcedUseForComm;
            mForcedUseForCommExt = mForcedUseForComm;
            AudioSystem.setParameters("BT_SCO=" + (on ? "on" : "off"));
            AudioSystem.setParameters("BT_SCO=" + (on ? "on" : "off"));
@@ -429,8 +465,8 @@ import java.io.PrintWriter;
        sendIIMsgNoDelay(MSG_II_SET_HEARING_AID_VOLUME, SENDMSG_REPLACE, index, streamType);
        sendIIMsgNoDelay(MSG_II_SET_HEARING_AID_VOLUME, SENDMSG_REPLACE, index, streamType);
    }
    }


    /*package*/ void postDisconnectBluetoothSco(int exceptPid) {
    /*package*/ void postSetModeOwnerPid(int pid) {
        sendIMsgNoDelay(MSG_I_DISCONNECT_BT_SCO, SENDMSG_REPLACE, exceptPid);
        sendIMsgNoDelay(MSG_I_SET_MODE_OWNER_PID, SENDMSG_REPLACE, pid);
    }
    }


    /*package*/ void postBluetoothA2dpDeviceConfigChange(@NonNull BluetoothDevice device) {
    /*package*/ void postBluetoothA2dpDeviceConfigChange(@NonNull BluetoothDevice device) {
@@ -483,7 +519,7 @@ import java.io.PrintWriter;
    }
    }


    /*package*/ int getModeOwnerPid() {
    /*package*/ int getModeOwnerPid() {
        return mAudioService.getModeOwnerPid();
        return mModeOwnerPid;
    }
    }


    /*package*/ int getDeviceForStream(int streamType) {
    /*package*/ int getDeviceForStream(int streamType) {
@@ -605,6 +641,10 @@ import java.io.PrintWriter;
        sendLMsgNoDelay(MSG_L_SCOCLIENT_DIED, SENDMSG_QUEUE, obj);
        sendLMsgNoDelay(MSG_L_SCOCLIENT_DIED, SENDMSG_QUEUE, obj);
    }
    }


    /*package*/ void postSpeakerphoneClientDied(Object obj) {
        sendLMsgNoDelay(MSG_L_SPEAKERPHONE_CLIENT_DIED, SENDMSG_QUEUE, obj);
    }

    /*package*/ void postSaveSetPreferredDeviceForStrategy(int strategy,
    /*package*/ void postSaveSetPreferredDeviceForStrategy(int strategy,
                                                           AudioDeviceAttributes device)
                                                           AudioDeviceAttributes device)
    {
    {
@@ -707,7 +747,20 @@ import java.io.PrintWriter;
        } else {
        } else {
            pw.println("Message handler is null");
            pw.println("Message handler is null");
        }
        }

        mDeviceInventory.dump(pw, prefix);
        mDeviceInventory.dump(pw, prefix);

        pw.println("\n" + prefix + "mForcedUseForComm: "
                +  AudioSystem.forceUseConfigToString(mForcedUseForComm));
        pw.println(prefix + "mForcedUseForCommExt: "
                + AudioSystem.forceUseConfigToString(mForcedUseForCommExt));
        pw.println(prefix + "mModeOwnerPid: " + mModeOwnerPid);
        pw.println(prefix + "Speakerphone clients:");
        mSpeakerphoneClients.forEach((cl) -> {
            pw.println("  " + prefix + "pid: " + cl.getPid() + " on: "
                        + cl.isOn() + " cb: " + cl.getBinder()); });

        mBtHelper.dump(pw, prefix);
    }
    }


    //---------------------------------------------------------------------
    //---------------------------------------------------------------------
@@ -877,10 +930,16 @@ import java.io.PrintWriter;
                        mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1);
                        mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1);
                    }
                    }
                    break;
                    break;
                case MSG_I_DISCONNECT_BT_SCO:
                case MSG_I_SET_MODE_OWNER_PID:
                    synchronized (mSetModeLock) {
                    synchronized (mSetModeLock) {
                        synchronized (mDeviceStateLock) {
                        synchronized (mDeviceStateLock) {
                            mBtHelper.disconnectBluetoothSco(msg.arg1);
                            if (mModeOwnerPid != msg.arg1) {
                                mModeOwnerPid = msg.arg1;
                                updateSpeakerphoneOn("setNewModeOwner");
                                if (mModeOwnerPid != 0) {
                                    mBtHelper.disconnectBluetoothSco(mModeOwnerPid);
                                }
                            }
                        }
                        }
                    }
                    }
                    break;
                    break;
@@ -891,6 +950,11 @@ import java.io.PrintWriter;
                        }
                        }
                    }
                    }
                    break;
                    break;
                case MSG_L_SPEAKERPHONE_CLIENT_DIED:
                    synchronized (mDeviceStateLock) {
                        speakerphoneClientDied(msg.obj);
                    }
                    break;
                case MSG_TOGGLE_HDMI:
                case MSG_TOGGLE_HDMI:
                    synchronized (mDeviceStateLock) {
                    synchronized (mDeviceStateLock) {
                        mDeviceInventory.onToggleHdmi();
                        mDeviceInventory.onToggleHdmi();
@@ -1021,7 +1085,7 @@ import java.io.PrintWriter;
    private static final int MSG_REPORT_NEW_ROUTES = 13;
    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_II_SET_HEARING_AID_VOLUME = 14;
    private static final int MSG_I_SET_AVRCP_ABSOLUTE_VOLUME = 15;
    private static final int MSG_I_SET_AVRCP_ABSOLUTE_VOLUME = 15;
    private static final int MSG_I_DISCONNECT_BT_SCO = 16;
    private static final int MSG_I_SET_MODE_OWNER_PID = 16;


    // process active A2DP device change, obj is BtHelper.BluetoothA2dpDeviceInfo
    // process active A2DP device change, obj is BtHelper.BluetoothA2dpDeviceInfo
    private static final int MSG_L_A2DP_ACTIVE_DEVICE_CHANGE = 18;
    private static final int MSG_L_A2DP_ACTIVE_DEVICE_CHANGE = 18;
@@ -1051,6 +1115,8 @@ import java.io.PrintWriter;
    private static final int MSG_IL_SAVE_PREF_DEVICE_FOR_STRATEGY = 33;
    private static final int MSG_IL_SAVE_PREF_DEVICE_FOR_STRATEGY = 33;
    private static final int MSG_I_SAVE_REMOVE_PREF_DEVICE_FOR_STRATEGY = 34;
    private static final int MSG_I_SAVE_REMOVE_PREF_DEVICE_FOR_STRATEGY = 34;


    private static final int MSG_L_SPEAKERPHONE_CLIENT_DIED = 35;



    private static boolean isMessageHandledUnderWakelock(int msgId) {
    private static boolean isMessageHandledUnderWakelock(int msgId) {
        switch(msgId) {
        switch(msgId) {
@@ -1166,4 +1232,93 @@ import java.io.PrintWriter;
                    time);
                    time);
        }
        }
    }
    }

    private class SpeakerphoneClient implements IBinder.DeathRecipient {
        private final IBinder mCb;
        private final int mPid;
        private final boolean mOn;
        SpeakerphoneClient(IBinder cb, int pid, boolean on) {
            mCb = cb;
            mPid = pid;
            mOn = on;
        }

        public boolean registerDeathRecipient() {
            boolean status = false;
            try {
                mCb.linkToDeath(this, 0);
                status = true;
            } catch (RemoteException e) {
                Log.w(TAG, "SpeakerphoneClient could not link to " + mCb + " binder death");
            }
            return status;
        }

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

        @Override
        public void binderDied() {
            postSpeakerphoneClientDied(this);
        }

        IBinder getBinder() {
            return mCb;
        }

        int getPid() {
            return mPid;
        }

        boolean isOn() {
            return mOn;
        }
    }

    @GuardedBy("mDeviceStateLock")
    private void speakerphoneClientDied(Object obj) {
        if (obj == null) {
            return;
        }
        Log.w(TAG, "Speaker client died");
        if (removeSpeakerphoneClient(((SpeakerphoneClient) obj).getBinder(), false) != null) {
            updateSpeakerphoneOn("speakerphoneClientDied");
        }
    }

    private SpeakerphoneClient removeSpeakerphoneClient(IBinder cb, boolean unregister) {
        for (SpeakerphoneClient cl : mSpeakerphoneClients) {
            if (cl.getBinder() == cb) {
                if (unregister) {
                    cl.unregisterDeathRecipient();
                }
                mSpeakerphoneClients.remove(cl);
                return cl;
            }
        }
        return null;
    }

    @GuardedBy("mDeviceStateLock")
    private boolean addSpeakerphoneClient(IBinder cb, int pid, boolean on) {
        // always insert new request at first position
        removeSpeakerphoneClient(cb, true);
        SpeakerphoneClient client = new SpeakerphoneClient(cb, pid, on);
        if (client.registerDeathRecipient()) {
            mSpeakerphoneClients.add(0, client);
            return true;
        }
        return false;
    }

    // List of clients requesting speakerPhone ON
    @GuardedBy("mDeviceStateLock")
    private final @NonNull ArrayList<SpeakerphoneClient> mSpeakerphoneClients =
            new ArrayList<SpeakerphoneClient>();

}
}
+12 −29
Original line number Original line Diff line number Diff line
@@ -3567,11 +3567,9 @@ public class AudioService extends IAudioService.Stub
        }
        }


        public void binderDied() {
        public void binderDied() {
            int oldModeOwnerPid;
            int newModeOwnerPid = 0;
            int newModeOwnerPid = 0;
            synchronized (mDeviceBroker.mSetModeLock) {
            synchronized (mDeviceBroker.mSetModeLock) {
                Log.w(TAG, "setMode() client died");
                Log.w(TAG, "setMode() client died");
                oldModeOwnerPid = getModeOwnerPid();
                int index = mSetModeDeathHandlers.indexOf(this);
                int index = mSetModeDeathHandlers.indexOf(this);
                if (index < 0) {
                if (index < 0) {
                    Log.w(TAG, "unregistered setMode() client died");
                    Log.w(TAG, "unregistered setMode() client died");
@@ -3581,9 +3579,7 @@ public class AudioService extends IAudioService.Stub
            }
            }
            // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
            // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
            // SCO connections not started by the application changing the mode when pid changes
            // SCO connections not started by the application changing the mode when pid changes
            if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) {
            mDeviceBroker.postSetModeOwnerPid(newModeOwnerPid);
                mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid);
            }
        }
        }


        public int getPid() {
        public int getPid() {
@@ -3635,16 +3631,19 @@ public class AudioService extends IAudioService.Stub
            return;
            return;
        }
        }


        int oldModeOwnerPid;
        int newModeOwnerPid;
        int newModeOwnerPid;
        synchronized (mDeviceBroker.mSetModeLock) {
        synchronized (mDeviceBroker.mSetModeLock) {
            if (mode == AudioSystem.MODE_CURRENT) {
            if (mode == AudioSystem.MODE_CURRENT) {
                mode = mMode;
                mode = mMode;
            }
            }
            oldModeOwnerPid = getModeOwnerPid();
            int oldModeOwnerPid = getModeOwnerPid();
            // Do not allow changing mode if a call is active and the requester
            // Do not allow changing mode if a call is active and the requester
            // does not have permission to modify phone state or is not the mode owner.
            // does not have permission to modify phone state or is not the mode owner,
            if (((mMode == AudioSystem.MODE_IN_CALL)
            // unless returning to NORMAL mode (will not change current mode owner) or
            // not changing mode in which case the mode owner will reflect the last
            // requester of current mode
            if (!((mode == mMode) || (mode == AudioSystem.MODE_NORMAL))
                    && ((mMode == AudioSystem.MODE_IN_CALL)
                        || (mMode == AudioSystem.MODE_IN_COMMUNICATION))
                        || (mMode == AudioSystem.MODE_IN_COMMUNICATION))
                    && !(hasModifyPhoneStatePermission || (oldModeOwnerPid == callingPid))) {
                    && !(hasModifyPhoneStatePermission || (oldModeOwnerPid == callingPid))) {
                Log.w(TAG, "setMode(" + mode + ") from pid=" + callingPid
                Log.w(TAG, "setMode(" + mode + ") from pid=" + callingPid
@@ -3658,9 +3657,7 @@ public class AudioService extends IAudioService.Stub
        }
        }
        // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
        // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
        // SCO connections not started by the application changing the mode when pid changes
        // SCO connections not started by the application changing the mode when pid changes
        if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) {
        mDeviceBroker.postSetModeOwnerPid(newModeOwnerPid);
            mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid);
        }
    }
    }


    // setModeInt() returns a valid PID if the audio mode was successfully set to
    // setModeInt() returns a valid PID if the audio mode was successfully set to
@@ -3937,32 +3934,18 @@ public class AudioService extends IAudioService.Stub
    }
    }


    /** @see AudioManager#setSpeakerphoneOn(boolean) */
    /** @see AudioManager#setSpeakerphoneOn(boolean) */
    public void setSpeakerphoneOn(boolean on){
    public void setSpeakerphoneOn(IBinder cb, boolean on) {
        if (!checkAudioSettingsPermission("setSpeakerphoneOn()")) {
        if (!checkAudioSettingsPermission("setSpeakerphoneOn()")) {
            return;
            return;
        }
        }


        if (mContext.checkCallingOrSelfPermission(
                android.Manifest.permission.MODIFY_PHONE_STATE)
                != PackageManager.PERMISSION_GRANTED) {
            synchronized (mSetModeDeathHandlers) {
                for (SetModeDeathHandler h : mSetModeDeathHandlers) {
                    if (h.getMode() == AudioSystem.MODE_IN_CALL) {
                        Log.w(TAG, "getMode is call, Permission Denial: setSpeakerphoneOn from pid="
                                + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
                        return;
                    }
                }
            }
        }

        // 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(on, eventSource);
        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)
+37 −0
Original line number Original line Diff line number Diff line
@@ -38,6 +38,7 @@ import android.util.Log;


import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.GuardedBy;


import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.ArrayList;
import java.util.List;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.NoSuchElementException;
@@ -131,6 +132,26 @@ public class BtHelper {
        }
        }
    }
    }


    /**
     * Returns a string representation of the scoAudioState.
     */
    public static String scoAudioStateToString(int scoAudioState) {
        switch (scoAudioState) {
            case SCO_STATE_INACTIVE:
                return "SCO_STATE_INACTIVE";
            case SCO_STATE_ACTIVATE_REQ:
                return "SCO_STATE_ACTIVATE_REQ";
            case SCO_STATE_ACTIVE_EXTERNAL:
                return "SCO_STATE_ACTIVE_EXTERNAL";
            case SCO_STATE_ACTIVE_INTERNAL:
                return "SCO_STATE_ACTIVE_INTERNAL";
            case SCO_STATE_DEACTIVATING:
                return "SCO_STATE_DEACTIVATING";
            default:
                return "SCO_STATE_(" + scoAudioState + ")";
        }
    }

    //----------------------------------------------------------------------
    //----------------------------------------------------------------------
    /*package*/ static class BluetoothA2dpDeviceInfo {
    /*package*/ static class BluetoothA2dpDeviceInfo {
        private final @NonNull BluetoothDevice mBtDevice;
        private final @NonNull BluetoothDevice mBtDevice;
@@ -1007,4 +1028,20 @@ public class BtHelper {
                return "ENCODING_BT_CODEC_TYPE(" + btCodecType + ")";
                return "ENCODING_BT_CODEC_TYPE(" + btCodecType + ")";
        }
        }
    }
    }

    //------------------------------------------------------------
    /*package*/ void dump(PrintWriter pw, String prefix) {
        pw.println("\n" + prefix + "mBluetoothHeadset: " + mBluetoothHeadset);
        pw.println(prefix + "mBluetoothHeadsetDevice: " + mBluetoothHeadsetDevice);
        pw.println(prefix + "mScoAudioState: " + scoAudioStateToString(mScoAudioState));
        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(prefix + "mA2dp: " + mA2dp);
        pw.println(prefix + "mAvrcpAbsVolSupported: " + mAvrcpAbsVolSupported);
    }

}
}