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

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

Fix issue 4673378: switching from VoIP to GSM call

The problem is that any app can change the audio mode and override
a mode previously set by another app. If two apps (gTalk and Phone) compete
for audio mode ownership, there is no mechanism to maintain coherency
in the actual audio mode selected.

Added a mechanism in AudioService to manage an audio mode request stack.
Any app requesting a mode different from NORMAL enters at the top of the stack
and the requested mode is applied. When an app sets mode back to NORMAL, it exits
the stack and the new mode corresponding to the request at the top of the
stack (if any) is applied.

Change-Id: I68d1755d0922f680df4a19bfc5ab924f5a5d8ccd
parent 6a39c020
Loading
Loading
Loading
Loading
+108 −80
Original line number Diff line number Diff line
@@ -343,9 +343,8 @@ public class AudioService extends IAudioService.Stub {
        readPersistedSettings();
        mSettingsObserver = new SettingsObserver();
        createStreamStates();
        // Call setMode() to initialize mSetModeDeathHandlers
        mMode = AudioSystem.MODE_INVALID;
        setMode(AudioSystem.MODE_NORMAL, null);

        mMode = AudioSystem.MODE_NORMAL;
        mMediaServerOk = true;

        // Call setRingerModeInt() to apply correct mute
@@ -768,36 +767,26 @@ public class AudioService extends IAudioService.Stub {
        private int mPid;
        private int mMode = AudioSystem.MODE_NORMAL; // Current mode set by this client

        SetModeDeathHandler(IBinder cb) {
        SetModeDeathHandler(IBinder cb, int pid) {
            mCb = cb;
            mPid = Binder.getCallingPid();
            mPid = pid;
        }

        public void binderDied() {
            IBinder newModeOwner = null;
            synchronized(mSetModeDeathHandlers) {
                Log.w(TAG, "setMode() client died");
                int index = mSetModeDeathHandlers.indexOf(this);
                if (index < 0) {
                    Log.w(TAG, "unregistered setMode() client died");
                } else {
                    mSetModeDeathHandlers.remove(this);
                    // If dead client was a the top of client list,
                    // apply next mode in the stack
                    if (index == 0) {
                        // mSetModeDeathHandlers is never empty as the initial entry
                        // created when AudioService starts is never removed
                        SetModeDeathHandler hdlr = mSetModeDeathHandlers.get(0);
                        int mode = hdlr.getMode();
                        if (AudioService.this.mMode != mode) {
                            if (AudioSystem.setPhoneState(mode) == AudioSystem.AUDIO_STATUS_OK) {
                                AudioService.this.mMode = mode;
                                if (mode != AudioSystem.MODE_NORMAL) {
                                    disconnectBluetoothSco(mCb);
                                }
                            }
                        }
                    newModeOwner = setModeInt(AudioSystem.MODE_NORMAL, mCb, mPid);
                }
            }
            // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
            // SCO connections not started by the application changing the mode
            if (newModeOwner != null) {
                 disconnectBluetoothSco(newModeOwner);
            }
        }

@@ -828,34 +817,55 @@ public class AudioService extends IAudioService.Stub {
            return;
        }

        synchronized (mSettingsLock) {
        IBinder newModeOwner = null;
        synchronized(mSetModeDeathHandlers) {
            if (mode == AudioSystem.MODE_CURRENT) {
                mode = mMode;
            }
            if (mode != mMode) {

                // automatically handle audio focus for mode changes
                handleFocusForCalls(mMode, mode, cb);
            newModeOwner = setModeInt(mode, cb, Binder.getCallingPid());
        }
        // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
        // SCO connections not started by the application changing the mode
        if (newModeOwner != null) {
             disconnectBluetoothSco(newModeOwner);
        }
    }

                if (AudioSystem.setPhoneState(mode) == AudioSystem.AUDIO_STATUS_OK) {
                    mMode = mode;
    // must be called synchronized on mSetModeDeathHandlers
    // setModeInt() returns a non null IBInder if the audio mode was successfully set to
    // any mode other than NORMAL.
    IBinder setModeInt(int mode, IBinder cb, int pid) {
        IBinder newModeOwner = null;
        if (cb == null) {
            Log.e(TAG, "setModeInt() called with null binder");
            return newModeOwner;
        }

                    synchronized(mSetModeDeathHandlers) {
        SetModeDeathHandler hdlr = null;
        Iterator iter = mSetModeDeathHandlers.iterator();
        while (iter.hasNext()) {
            SetModeDeathHandler h = (SetModeDeathHandler)iter.next();
                            if (h.getBinder() == cb) {
            if (h.getPid() == pid) {
                hdlr = h;
                // Remove from client list so that it is re-inserted at top of list
                iter.remove();
                hdlr.getBinder().unlinkToDeath(hdlr, 0);
                break;
            }
        }
        int status = AudioSystem.AUDIO_STATUS_OK;
        do {
            if (mode == AudioSystem.MODE_NORMAL) {
                // get new mode from client at top the list if any
                if (!mSetModeDeathHandlers.isEmpty()) {
                    hdlr = mSetModeDeathHandlers.get(0);
                    cb = hdlr.getBinder();
                    mode = hdlr.getMode();
                }
            } else {
                if (hdlr == null) {
                            hdlr = new SetModeDeathHandler(cb);
                            // cb is null when setMode() is called by AudioService constructor
                            if (cb != null) {
                    hdlr = new SetModeDeathHandler(cb, pid);
                }
                // Register for client death notification
                try {
                    cb.linkToDeath(hdlr, 0);
@@ -863,25 +873,41 @@ public class AudioService extends IAudioService.Stub {
                    // Client has died!
                    Log.w(TAG, "setMode() could not link to "+cb+" binder death");
                }
                            }
                        }

                // Last client to call setMode() is always at top of client list
                // as required by SetModeDeathHandler.binderDied()
                mSetModeDeathHandlers.add(0, hdlr);
                hdlr.setMode(mode);
            }

                    // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
                    // SCO connections not started by the application changing the mode
                    if (mode != AudioSystem.MODE_NORMAL) {
                        disconnectBluetoothSco(cb);
            if (mode != mMode) {
                status = AudioSystem.setPhoneState(mode);
                if (status == AudioSystem.AUDIO_STATUS_OK) {
                    // automatically handle audio focus for mode changes
                    handleFocusForCalls(mMode, mode, cb);
                    mMode = mode;
                } else {
                    if (hdlr != null) {
                        mSetModeDeathHandlers.remove(hdlr);
                        cb.unlinkToDeath(hdlr, 0);
                    }
                    // force reading new top of mSetModeDeathHandlers stack
                    mode = AudioSystem.MODE_NORMAL;
                }
            } else {
                status = AudioSystem.AUDIO_STATUS_OK;
            }
        } while (status != AudioSystem.AUDIO_STATUS_OK && !mSetModeDeathHandlers.isEmpty());

        if (status == AudioSystem.AUDIO_STATUS_OK) {
            if (mode != AudioSystem.MODE_NORMAL) {
                newModeOwner = cb;
            }
            int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
            int index = mStreamStates[STREAM_VOLUME_ALIAS[streamType]].mIndex;
            setStreamVolumeInt(STREAM_VOLUME_ALIAS[streamType], index, true, false);
        }
        return newModeOwner;
    }

    /** pre-condition: oldMode != newMode */
@@ -1345,7 +1371,8 @@ public class AudioService extends IAudioService.Stub {
                    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.
                    if ((AudioService.this.mMode == AudioSystem.MODE_NORMAL ||
                    synchronized(mSetModeDeathHandlers) {
                        if ((mSetModeDeathHandlers.isEmpty() ||
                                mSetModeDeathHandlers.get(0).getPid() == mCreatorPid) &&
                                (mScoAudioState == SCO_STATE_INACTIVE ||
                                 mScoAudioState == SCO_STATE_DEACTIVATE_REQ)) {
@@ -1368,6 +1395,7 @@ public class AudioService extends IAudioService.Stub {
                        } else {
                            broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
                        }
                    }
                } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED &&
                              (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL ||
                               mScoAudioState == SCO_STATE_ACTIVATE_REQ)) {