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

Commit c480a710 authored by Shubang's avatar Shubang Committed by Nick Chalko
Browse files

Send <Report audio status> CEC commands when it's changed

According to HDMI CEC specification, an audio system
can report its audio status when System Audio Mode
is on, so that the TV can display the audio status
of external amplifier.

More details can be found in section CEC-13.15 of
HDMI Specification 1.4b

Bug: 80297383
Test: make; flashall; local tests
Change-Id: Id71b709a62add7bf5dccb418489e200350debb8e
parent 4d3246e6
Loading
Loading
Loading
Loading
+18 −1
Original line number Diff line number Diff line
@@ -15,6 +15,8 @@
 */
package android.hardware.hdmi;

import android.os.RemoteException;

/**
 * HdmiAudioSystemClient represents HDMI-CEC logical device of type Audio System in the Android
 * system which acts as an audio system device such as sound bar.
@@ -40,5 +42,20 @@ public final class HdmiAudioSystemClient extends HdmiClient {
        return HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM;
    }


    /**
     * Sends a Report Audio Status HDMI CEC command to TV devices when necessary.
     *
     * According to HDMI CEC specification, an audio system can report its audio status when System
     * Audio Mode is on, so that the TV can display the audio status of external amplifier.
     *
     * @hide
     */
    public void sendReportAudioStatusCecCommand(boolean isMuteAdjust, int volume, int maxVolume,
            boolean isMute) {
        try {
            mService.reportAudioStatus(getDeviceType(), volume, maxVolume, isMute);
        } catch (RemoteException e) {
            // do nothing. Reporting audio status is optional.
        }
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -72,4 +72,5 @@ interface IHdmiControlService {
    void sendMhlVendorCommand(int portId, int offset, int length, in byte[] data);
    void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener);
    void setStandbyMode(boolean isStandbyModeOn);
    void reportAudioStatus(int deviceType, int volume, int maxVolume, boolean isMute);
}
+84 −68
Original line number Diff line number Diff line
@@ -57,6 +57,7 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.database.ContentObserver;
import android.hardware.hdmi.HdmiAudioSystemClient;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiPlaybackClient;
import android.hardware.hdmi.HdmiTvClient;
@@ -926,14 +927,15 @@ public class AudioService extends IAudioService.Stub
        }

        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) {
            synchronized (mHdmiClientLock) {
                mHdmiManager = mContext.getSystemService(HdmiControlManager.class);
            synchronized (mHdmiManager) {
                mHdmiTvClient = mHdmiManager.getTvClient();
                if (mHdmiTvClient != null) {
                    mFixedVolumeDevices &= ~AudioSystem.DEVICE_ALL_HDMI_SYSTEM_AUDIO_AND_SPEAKER;
                }
                mHdmiPlaybackClient = mHdmiManager.getPlaybackClient();
                mHdmiCecSink = false;
                mHdmiAudioSystemClient = mHdmiManager.getAudioSystemClient();
            }
        }

@@ -1054,13 +1056,11 @@ public class AudioService extends IAudioService.Stub
            sendEncodedSurroundMode(mContentResolver, "onAudioServerDied");
            sendEnabledSurroundFormats(mContentResolver, true);
        }
        if (mHdmiManager != null) {
            synchronized (mHdmiManager) {
                if (mHdmiTvClient != null) {
        synchronized (mHdmiClientLock) {
            if (mHdmiManager != null && mHdmiTvClient != null) {
                setHdmiSystemAudioSupported(mHdmiSystemAudioSupported);
            }
        }
        }

        synchronized (mAudioPolicies) {
            for (AudioPolicyProxy policy : mAudioPolicies.values()) {
@@ -1763,13 +1763,12 @@ public class AudioService extends IAudioService.Stub
            if (streamTypeAlias == AudioSystem.STREAM_MUSIC) {
                setSystemAudioVolume(oldIndex, newIndex, getStreamMaxVolume(streamType), flags);
            }
            synchronized (mHdmiClientLock) {
                if (mHdmiManager != null) {
                synchronized (mHdmiManager) {
                    // mHdmiCecSink true => mHdmiPlaybackClient != null
                    if (mHdmiCecSink &&
                            streamTypeAlias == AudioSystem.STREAM_MUSIC &&
                            oldIndex != newIndex) {
                        synchronized (mHdmiPlaybackClient) {
                        int keyCode = (direction == -1) ? KeyEvent.KEYCODE_VOLUME_DOWN :
                                KeyEvent.KEYCODE_VOLUME_UP;
                        final long ident = Binder.clearCallingIdentity();
@@ -1780,6 +1779,14 @@ public class AudioService extends IAudioService.Stub
                            Binder.restoreCallingIdentity(ident);
                        }
                    }

                    if (mHdmiAudioSystemClient != null &&
                            streamTypeAlias == AudioSystem.STREAM_MUSIC &&
                            (oldIndex != newIndex || isMuteAdjust)) {
                        mHdmiAudioSystemClient.sendReportAudioStatusCecCommand(
                                isMuteAdjust, getStreamVolume(AudioSystem.STREAM_MUSIC),
                                getStreamMaxVolume(AudioSystem.STREAM_MUSIC),
                                isStreamMute(AudioSystem.STREAM_MUSIC));
                    }
                }
            }
@@ -1799,16 +1806,14 @@ public class AudioService extends IAudioService.Stub
    }

    private void setSystemAudioVolume(int oldVolume, int newVolume, int maxVolume, int flags) {
        // Sets the audio volume of AVR when we are in system audio mode. The new volume info
        // is tranformed to HDMI-CEC commands and passed through CEC bus.
        synchronized (mHdmiClientLock) {
            if (mHdmiManager == null
                    || mHdmiTvClient == null
                    || oldVolume == newVolume
                || (flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) != 0) return;

        // Sets the audio volume of AVR when we are in system audio mode. The new volume info
        // is tranformed to HDMI-CEC commands and passed through CEC bus.
        synchronized (mHdmiManager) {
            if (!mHdmiSystemAudioSupported) return;
            synchronized (mHdmiTvClient) {
                    || (flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) != 0
                    || !mHdmiSystemAudioSupported) return;
            final long token = Binder.clearCallingIdentity();
            try {
                mHdmiTvClient.setSystemAudioVolume(oldVolume, newVolume, maxVolume);
@@ -1817,7 +1822,6 @@ public class AudioService extends IAudioService.Stub
            }
        }
    }
    }

    // StreamVolumeCommand contains the information needed to defer the process of
    // setStreamVolume() in case the user has to acknowledge the safe volume warning message.
@@ -2037,6 +2041,17 @@ public class AudioService extends IAudioService.Stub
                index = mStreamStates[streamType].getIndex(device);
            }
        }
        synchronized (mHdmiClientLock) {
            if (mHdmiManager != null &&
                    mHdmiAudioSystemClient != null &&
                    streamTypeAlias == AudioSystem.STREAM_MUSIC &&
                    (oldIndex != index)) {
                mHdmiAudioSystemClient.sendReportAudioStatusCecCommand(
                        false, getStreamVolume(AudioSystem.STREAM_MUSIC),
                        getStreamMaxVolume(AudioSystem.STREAM_MUSIC),
                        isStreamMute(AudioSystem.STREAM_MUSIC));
            }
        }
        sendVolumeUpdate(streamType, oldIndex, index, flags);
    }

@@ -2177,8 +2192,8 @@ public class AudioService extends IAudioService.Stub
    // If Hdmi-CEC system audio mode is on, we show volume bar only when TV
    // receives volume notification from Audio Receiver.
    private int updateFlagsForSystemAudio(int flags) {
        synchronized (mHdmiClientLock) {
            if (mHdmiTvClient != null) {
            synchronized (mHdmiTvClient) {
                if (mHdmiSystemAudioSupported &&
                        ((flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) == 0)) {
                    flags &= ~AudioManager.FLAG_SHOW_UI;
@@ -2233,10 +2248,8 @@ public class AudioService extends IAudioService.Stub
    }

    private void setSystemAudioMute(boolean state) {
        if (mHdmiManager == null || mHdmiTvClient == null) return;
        synchronized (mHdmiManager) {
            if (!mHdmiSystemAudioSupported) return;
            synchronized (mHdmiTvClient) {
        synchronized (mHdmiClientLock) {
            if (mHdmiManager == null || mHdmiTvClient == null || !mHdmiSystemAudioSupported) return;
            final long token = Binder.clearCallingIdentity();
            try {
                mHdmiTvClient.setSystemAudioMute(state);
@@ -2245,7 +2258,6 @@ public class AudioService extends IAudioService.Stub
            }
        }
    }
    }

    /** get stream mute state. */
    public boolean isStreamMute(int streamType) {
@@ -6370,22 +6382,20 @@ public class AudioService extends IAudioService.Stub
                if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) {
                    mFixedVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI;
                    checkAllFixedVolumeDevices();
                    if (mHdmiManager != null) {
                        synchronized (mHdmiManager) {
                            if (mHdmiPlaybackClient != null) {
                    synchronized (mHdmiClientLock) {
                        if (mHdmiManager != null && mHdmiPlaybackClient != null) {
                            mHdmiCecSink = false;
                            mHdmiPlaybackClient.queryDisplayStatus(mHdmiDisplayStatusCallback);
                        }
                    }
                }
                }
                if ((device & AudioSystem.DEVICE_OUT_HDMI) != 0) {
                    sendEnabledSurroundFormats(mContentResolver, true);
                }
            } else {
                if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) {
                    synchronized (mHdmiClientLock) {
                        if (mHdmiManager != null) {
                        synchronized (mHdmiManager) {
                            mHdmiCecSink = false;
                        }
                    }
@@ -7054,8 +7064,8 @@ public class AudioService extends IAudioService.Stub

    private class MyDisplayStatusCallback implements HdmiPlaybackClient.DisplayStatusCallback {
        public void onComplete(int status) {
            synchronized (mHdmiClientLock) {
                if (mHdmiManager != null) {
                synchronized (mHdmiManager) {
                    mHdmiCecSink = (status != HdmiControlManager.POWER_STATUS_UNKNOWN);
                    // Television devices without CEC service apply software volume on HDMI output
                    if (isPlatformTelevision() && !mHdmiCecSink) {
@@ -7065,33 +7075,40 @@ public class AudioService extends IAudioService.Stub
                }
            }
        }
    };
    }

    private final Object mHdmiClientLock = new Object();

    // If HDMI-CEC system audio is supported
    private boolean mHdmiSystemAudioSupported = false;
    // Set only when device is tv.
    @GuardedBy("mHdmiClientLock")
    private HdmiTvClient mHdmiTvClient;
    // true if the device has system feature PackageManager.FEATURE_LEANBACK.
    // cached HdmiControlManager interface
    @GuardedBy("mHdmiClientLock")
    private HdmiControlManager mHdmiManager;
    // Set only when device is a set-top box.
    @GuardedBy("mHdmiClientLock")
    private HdmiPlaybackClient mHdmiPlaybackClient;
    // true if we are a set-top box, an HDMI sink is connected and it supports CEC.
    private boolean mHdmiCecSink;
    // Set only when device is an audio system.
    @GuardedBy("mHdmiClientLock")
    private HdmiAudioSystemClient mHdmiAudioSystemClient;

    private MyDisplayStatusCallback mHdmiDisplayStatusCallback = new MyDisplayStatusCallback();

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

                synchronized (mHdmiTvClient) {
                if (mHdmiSystemAudioSupported != on) {
                    mHdmiSystemAudioSupported = on;
                    final int config = on ? AudioSystem.FORCE_HDMI_SYSTEM_AUDIO_ENFORCED :
@@ -7103,7 +7120,6 @@ public class AudioService extends IAudioService.Stub
                device = getDevicesForStream(AudioSystem.STREAM_MUSIC);
            }
        }
        }
        return device;
    }

+2 −1
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice {
    // Whether the System Audio Control feature is enabled or not. True by default.
    @GuardedBy("mLock")
    private boolean mSystemAudioControlFeatureEnabled;
    protected Integer mSystemAudioSource;

    protected HdmiCecLocalDeviceAudioSystem(HdmiControlService service) {
        super(service, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
@@ -183,7 +184,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice {
            mService.getAudioManager().adjustStreamVolume(AudioManager.STREAM_MUSIC,
                systemAudioStatusOn ? AudioManager.ADJUST_UNMUTE : AudioManager.ADJUST_MUTE, 0);
        }

        mSystemAudioSource = systemAudioStatusOn ? message.getSource() : null;
        mService.sendCecCommand(HdmiCecMessageBuilder
            .buildSetSystemAudioMode(mAddress, Constants.ADDR_BROADCAST, systemAudioStatusOn));
        return true;
+37 −0
Original line number Diff line number Diff line
@@ -1716,6 +1716,38 @@ public class HdmiControlService extends SystemService {
            });
        }

        @Override
        public void reportAudioStatus(final int deviceType, final int volume, final int maxVolume,
                final boolean isMute) {
            enforceAccessPermission();
            runOnServiceThread(new Runnable() {
                @Override
                public void run() {
                    HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
                    if (device == null) {
                        Slog.w(TAG, "Local device not available");
                        return;
                    }
                    if (audioSystem() == null) {
                        Slog.w(TAG, "audio system is not available");
                        return;
                    }
                    if (audioSystem().mSystemAudioSource == null) {
                        Slog.w(TAG, "audio system is not in system audio mode");
                        return;
                    }
                    int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume);

                    sendCecCommand(HdmiCecMessageBuilder
                            .buildReportAudioStatus(
                                    device.getDeviceInfo().getLogicalAddress(),
                                    audioSystem().mSystemAudioSource,
                                    scaledVolume,
                                    isMute));
                }
            });
        }

        @Override
        protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
            if (!DumpUtils.checkDumpPermission(getContext(), TAG, writer)) return;
@@ -2018,6 +2050,11 @@ public class HdmiControlService extends SystemService {
                mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
    }

    public HdmiCecLocalDeviceAudioSystem audioSystem() {
        return (HdmiCecLocalDeviceAudioSystem) mCecController.getLocalDevice(
                HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
    }

    AudioManager getAudioManager() {
        return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
    }