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

Commit 6f34f5ab authored by Jungshik Jang's avatar Jungshik Jang
Browse files

Revamp HDMI-CEC system audio mode integration with audio service.

In previous change we use setForceUse(FORCE_MEDIA, XXX) method
in order to set specific audio output type, such as LINE, HDMI_ARC,
and SPDIF. But it turns out that it conflicts with bluetooth
a2dp which uses the same setForceUse(FORCE_MEDIA, yyy).

This change is based on several conditions.

1. When other non-speaker devices are on, prevent system audio
   from turing on.
2. In order to keep track of other devices' connectivity and to
   turn off system audio if other device like bluetooth or
   headphone preempts current output, register OnAudioPortChangeListner
   to audio manager.
3. All possible system audio outputs can be merged with other
   outputs without priority.

Change-Id: Id4e47d99db64b9f77a17c2c28c47787ab8980bf7
parent b2492254
Loading
Loading
Loading
Loading
+4 −2
Original line number Diff line number Diff line
@@ -2895,13 +2895,15 @@ public class AudioManager {
     * @param device out device type to be used for system audio mode.
     *               Ignored if {@code on} is {@code false}
     * @param name name of system audio device
     * @return output device type. 0 (DEVICE_NONE) if failed to set device.
     * @hide
     */
    public void setHdmiSystemAudioSupported(boolean on, int device, String name) {
    public int setHdmiSystemAudioSupported(boolean on, int device, String name) {
        try {
            getService().setHdmiSystemAudioSupported(on, device, name);
            return getService().setHdmiSystemAudioSupported(on, device, name);
        } catch (RemoteException e) {
            Log.w(TAG, "Error setting system audio mode", e);
            return AudioSystem.DEVICE_NONE;
        }
    }

+91 −28
Original line number Diff line number Diff line
@@ -65,7 +65,6 @@ import android.os.UserHandle;
import android.os.Vibrator;
import android.provider.Settings;
import android.provider.Settings.System;

import android.telecomm.TelecommManager;
import android.text.TextUtils;
import android.util.Log;
@@ -84,6 +83,7 @@ import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -3776,6 +3776,11 @@ public class AudioService extends IAudioService.Stub {
                                        AudioSystem.FORCE_ANALOG_DOCK : AudioSystem.FORCE_NONE);
                    }

                    if (mHdmiTvClient != null) {
                        setHdmiSystemAudioSupported(mHdmiSystemAudioSupported,
                                mHdmiSystemAudioOutputDevice, "");
                    }

                    // indicate the end of reconfiguration phase to audio HAL
                    AudioSystem.setParameters("restarting=false");
                    break;
@@ -4754,53 +4759,111 @@ public class AudioService extends IAudioService.Stub {
    private boolean mHdmiSystemAudioSupported = false;
    // Set only when device is tv.
    private HdmiTvClient mHdmiTvClient;
    private int mHdmiSystemAudioOutputDevice = AudioSystem.DEVICE_NONE;
    private int[] mSpeakerGains;

    @Override
    public void setHdmiSystemAudioSupported(boolean on, int device, String name) {
    public int setHdmiSystemAudioSupported(boolean on, int device, String name) {
        if (mHdmiTvClient == null) {
            Log.w(TAG, "Only Hdmi-Cec enabled TV device supports system audio mode.");
            return;
            return AudioSystem.DEVICE_NONE;
        }

        if ((device & AudioSystem.DEVICE_OUT_ALL_HDMI_SYSTEM_AUDIO) == 0) {
            Log.w(TAG, "Unsupported Hdmi-Cec system audio output:" + device);
            return;
        if (on && !checkHdmiSystemAudioOutput(device)) {
            return AudioSystem.DEVICE_NONE;
        }

        VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC];
        int oldStreamDevice = getDeviceForStream(AudioSystem.STREAM_MUSIC);
        int oldIndex = streamState.getIndex(oldStreamDevice);

        synchronized (mHdmiTvClient) {
            if (on) {
                mHdmiSystemAudioOutputDevice = device;
            }
            if (mHdmiSystemAudioSupported == on) {
                return AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC);
            }
            mHdmiSystemAudioSupported = on;
            updateHdmiSystemAudioVolumeLocked(on);
        }
        return AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC);
    }

            // TODO: call AudioSystem.setForceUse(FORCE_FOR_MEDIA,
            //         on ? AudioSystem.FORCE_SYSTEM_AUDIO_XXX : AudioSystem.FORCE_NONE;
    private boolean checkHdmiSystemAudioOutput(int device) {
        if ((device & AudioSystem.DEVICE_OUT_ALL_HDMI_SYSTEM_AUDIO) == 0) {
            Log.w(TAG, "Unsupported Hdmi-Cec system audio output:" + device);
            return false;
        }

        int newStreamDevice = getDeviceForStream(AudioSystem.STREAM_MUSIC);
        boolean updateSpeakerVolume = false;
        int streamDevice = AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC);
        // If other devices except for system audio and speaker are available,
        // fails to start system audio mode.
        if ((streamDevice & ~AudioSystem.DEVICE_ALL_HDMI_SYSTEM_AUDIO_AND_SPEAKER) != 0) {
            Log.w(TAG, "Should turn off other devices before starting system audio:"
                    + streamDevice);
            return false;
        }
        if (AudioSystem.getDeviceConnectionState(device, "") !=
            AudioSystem.DEVICE_STATE_AVAILABLE) {
            Log.w(TAG, "Output device is not connected:" + device);
            return false;
        }
        return true;
    }

    private void updateHdmiSystemAudioVolumeLocked(boolean on) {
        AudioDevicePort speaker = findAudioDevicePort(AudioSystem.DEVICE_OUT_SPEAKER);
        if (speaker == null) {
            Log.w(TAG, "Has no speaker output.");
            return;
        }

        AudioPortConfig portConfig = speaker.activeConfig();
        AudioGainConfig gainConfig = portConfig.gain();
        int[] newGains;
        // When system audio is on, backup original gains and mute all channels of speaker by
        // setting gains to 0; otherwise, restore gains of speaker.
        if (on) {
            if ((oldStreamDevice & AudioSystem.DEVICE_OUT_SPEAKER) != 0) {
                // Mute tv speaker. Note that set volume 0 instead of call mute() method because
                // it's not possible to mute for a specific device.
                streamState.setIndex(0, AudioSystem.DEVICE_OUT_SPEAKER);
                updateSpeakerVolume = true;
            if (gainConfig == null) {
                Log.w(TAG, "Speaker has no gain control.");
                return;
            }
            // Back up original gains.
            mSpeakerGains = Arrays.copyOf(gainConfig.values(), gainConfig.values().length);
            // Set all gains to 0.
            newGains = new int[gainConfig.values().length];
        } else {
            if ((newStreamDevice & AudioSystem.DEVICE_OUT_SPEAKER) != 0) {
                // Restore speaker volume if exists. As there is no way to mute a device here,
                // load system audio's volume and set it to speaker.
                streamState.setIndex(oldIndex, AudioSystem.DEVICE_OUT_SPEAKER);
                updateSpeakerVolume = true;
            if (mSpeakerGains == null) {
                Log.w(TAG, "mSpeakerGains should not be null.");
                return;
            }
            newGains = Arrays.copyOf(mSpeakerGains, mSpeakerGains.length);
        }

        if (updateSpeakerVolume) {
            sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE,
                    AudioSystem.DEVICE_OUT_SPEAKER, 0,
                    streamState, 0);
        gainConfig = gainConfig.mGain.buildConfig(gainConfig.mode(),
                gainConfig.channelMask(),
                newGains,
                gainConfig.rampDurationMs());

        AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
        if (AudioSystem.SUCCESS !=  audioManager.setAudioPortGain(speaker, gainConfig)) {
            Log.w(TAG, "Failed to update audio port config.");
        }
    }

    private AudioDevicePort findAudioDevicePort(int type) {
        ArrayList<AudioPort> devicePorts = new ArrayList<>();
        AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
        int status = audioManager.listAudioDevicePorts(devicePorts);
        if (status != AudioSystem.SUCCESS)  {
            Log.w(TAG, "Failed to list up all audio ports");
            return null;
        }

        for (AudioPort port : devicePorts) {
            AudioDevicePort devicePort = (AudioDevicePort) port;
            if (devicePort.type() == type) {
                return devicePort;
            }
        }
        return null;
    }

    //==========================================================================================
+4 −4
Original line number Diff line number Diff line
@@ -290,6 +290,9 @@ public class AudioSystem
    public static final int DEVICE_OUT_ALL_HDMI_SYSTEM_AUDIO = (DEVICE_OUT_LINE |
                                                                DEVICE_OUT_HDMI_ARC |
                                                                DEVICE_OUT_SPDIF);
    public static final int DEVICE_ALL_HDMI_SYSTEM_AUDIO_AND_SPEAKER =
            (DEVICE_OUT_ALL_HDMI_SYSTEM_AUDIO |
             DEVICE_OUT_SPEAKER);

    // input devices
    public static final int DEVICE_IN_COMMUNICATION = DEVICE_BIT_IN | 0x1;
@@ -437,10 +440,7 @@ public class AudioSystem
    public static final int FORCE_DIGITAL_DOCK = 9;
    public static final int FORCE_NO_BT_A2DP = 10;
    public static final int FORCE_SYSTEM_ENFORCED = 11;
    public static final int FORCE_SYSTEM_AUDIO_HDMI_ARC = 12;
    public static final int FORCE_SYSTEM_AUDIO_SPDIF = 13;
    public static final int FORCE_SYSTEM_LINE = 14;
    private static final int NUM_FORCE_CONFIG = 15;
    private static final int NUM_FORCE_CONFIG = 12;
    public static final int FORCE_DEFAULT = FORCE_NONE;

    // usage for setForceUse, must match AudioSystem::force_use
+1 −1
Original line number Diff line number Diff line
@@ -198,5 +198,5 @@ interface IAudioService {

    void disableSafeMediaVolume();

    oneway void setHdmiSystemAudioSupported(boolean on, int device, String name);
    int setHdmiSystemAudioSupported(boolean on, int device, String name);
}
+30 −0
Original line number Diff line number Diff line
@@ -20,6 +20,9 @@ import android.content.Intent;
import android.hardware.hdmi.HdmiCecDeviceInfo;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.IHdmiControlCallback;
import android.media.AudioManager.OnAudioPortUpdateListener;
import android.media.AudioPatch;
import android.media.AudioPort;
import android.media.AudioSystem;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -101,6 +104,33 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
                mAddress, mService.getVendorId()));
        launchRoutingControl(true);
        launchDeviceDiscovery();

        registerAudioPortUpdateListener();
        // TODO: unregister audio port update listener if local device is released.
    }

    private void registerAudioPortUpdateListener() {
        mService.getAudioManager().registerAudioPortUpdateListener(
                new OnAudioPortUpdateListener() {
                    @Override
                    public void OnAudioPatchListUpdate(AudioPatch[] patchList) {}

                    @Override
                    public void OnAudioPortListUpdate(AudioPort[] portList) {
                        if (!mSystemAudioMode) {
                            return;
                        }
                        int devices = mService.getAudioManager().getDevicesForStream(
                                AudioSystem.STREAM_MUSIC);
                        if ((devices & ~AudioSystem.DEVICE_ALL_HDMI_SYSTEM_AUDIO_AND_SPEAKER)
                                != 0) {
                            // TODO: release system audio here.
                        }
                    }

                    @Override
                    public void OnServiceDied() {}
                });
    }

    /**