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

Commit 3aad0adc authored by Eric Laurent's avatar Eric Laurent
Browse files

AudioService AudioDeviceBroker: fix speakerphone control

Add a list of clients for speakerphone mode requests similar
to Bluetooth SCO clients. This allows to keep track of requests
by different apps in case of overlap or concurrency and to apply
the most relevant mode according to current audio mode owner.

Also:
- Restore requested speakerphone mode when SCO audio is disconnected
instead of returning to earpiece.

- Remove special check on permission while in call in
AudioService.setSpeakerphoneOn because the priority is now
managed by AudioDeviceBroker based on audio mode owner (the owner
for MODE_IN_CALL must have the modify phone state permission).

- Fix condition in AudioService.setMode() preventing from changing
mode while in call. Now allows releasing mode to NORMAL or
reapplying the same mode and just change mode owner.

- Add more information in dumpsys for AudioDeviceBroker and BtHelper.

Bug: 154464603
Test: test transitions between cell call and VoIP calls
Test: Test regressions with calls in speakerphone mode and Bluetooth
Test: AudioManagerTest#testRouting, NoAudioPermissionTest#testRouting

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

    oneway void avrcpSupportsAbsoluteVolume(String address, boolean support);

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

    boolean isSpeakerphoneOn();

+180 −25
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
import android.util.PrintWriterPrinter;
@@ -43,6 +44,9 @@ import android.util.PrintWriterPrinter;
import com.android.internal.annotations.GuardedBy;

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


/** @hide */
/*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
    /*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) {
        mContext = context;
@@ -136,6 +143,7 @@ import java.io.PrintWriter;
    /*package*/ void onSystemReady() {
        synchronized (mSetModeLock) {
            synchronized (mDeviceStateLock) {
                mModeOwnerPid = mAudioService.getModeOwnerPid();
                mBtHelper.onSystemReady();
            }
        }
@@ -202,10 +210,20 @@ import java.io.PrintWriter;
     * @param eventSource for logging purposes
     * @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) {
            if (!addSpeakerphoneClient(cb, pid, on)) {
                return false;
            }
            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) {
                setForceUse_Async(AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, eventSource);
            }
@@ -219,11 +237,28 @@ import java.io.PrintWriter;
                mForcedUseForComm = AudioSystem.FORCE_NONE;
            }
        }

        mForcedUseForCommExt = mForcedUseForComm;
        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() {
@@ -384,7 +419,8 @@ import java.io.PrintWriter;
                }
                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;
            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);
    }

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

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

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

    /*package*/ int getDeviceForStream(int streamType) {
@@ -605,6 +641,10 @@ import java.io.PrintWriter;
        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,
                                                           AudioDeviceAttributes device)
    {
@@ -707,7 +747,20 @@ import java.io.PrintWriter;
        } else {
            pw.println("Message handler is null");
        }

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

    // process active A2DP device change, obj is BtHelper.BluetoothA2dpDeviceInfo
    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_I_SAVE_REMOVE_PREF_DEVICE_FOR_STRATEGY = 34;

    private static final int MSG_L_SPEAKERPHONE_CLIENT_DIED = 35;


    private static boolean isMessageHandledUnderWakelock(int msgId) {
        switch(msgId) {
@@ -1166,4 +1232,93 @@ import java.io.PrintWriter;
                    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 Diff line number Diff line
@@ -3594,11 +3594,9 @@ public class AudioService extends IAudioService.Stub
        }

        public void binderDied() {
            int oldModeOwnerPid;
            int newModeOwnerPid = 0;
            synchronized (mDeviceBroker.mSetModeLock) {
                Log.w(TAG, "setMode() client died");
                oldModeOwnerPid = getModeOwnerPid();
                int index = mSetModeDeathHandlers.indexOf(this);
                if (index < 0) {
                    Log.w(TAG, "unregistered setMode() client died");
@@ -3608,9 +3606,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
            if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) {
                mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid);
            }
            mDeviceBroker.postSetModeOwnerPid(newModeOwnerPid);
        }

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

        int oldModeOwnerPid;
        int newModeOwnerPid;
        synchronized (mDeviceBroker.mSetModeLock) {
            if (mode == AudioSystem.MODE_CURRENT) {
                mode = mMode;
            }
            oldModeOwnerPid = getModeOwnerPid();
            int oldModeOwnerPid = getModeOwnerPid();
            // 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.
            if (((mMode == AudioSystem.MODE_IN_CALL)
            // does not have permission to modify phone state or is not the mode owner,
            // 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))
                    && !(hasModifyPhoneStatePermission || (oldModeOwnerPid == callingPid))) {
                Log.w(TAG, "setMode(" + mode + ") from pid=" + callingPid
@@ -3685,9 +3684,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
        if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) {
            mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid);
        }
        mDeviceBroker.postSetModeOwnerPid(newModeOwnerPid);
    }

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

    /** @see AudioManager#setSpeakerphoneOn(boolean) */
    public void setSpeakerphoneOn(boolean on){
    public void setSpeakerphoneOn(IBinder cb, boolean on) {
        if (!checkAudioSettingsPermission("setSpeakerphoneOn()")) {
            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
        final int uid = Binder.getCallingUid();
        final int pid = Binder.getCallingPid();
        final String eventSource = new StringBuilder("setSpeakerphoneOn(").append(on)
                .append(") from u/pid:").append(uid).append("/")
                .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
                + MediaMetrics.SEPARATOR + "setSpeakerphoneOn")
                .setUid(uid)
+37 −0
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ import android.util.Log;

import com.android.internal.annotations.GuardedBy;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
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 {
        private final @NonNull BluetoothDevice mBtDevice;
@@ -1007,4 +1028,20 @@ public class BtHelper {
                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);
    }

}