Merge "CEC: Make HDMI CEC volume control configurable" into rvc-dev
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index 65a8e15..6bc962b 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -650,6 +650,68 @@
     }
 
     /**
+     * Controls whether volume control commands via HDMI CEC are enabled.
+     *
+     * <p>When disabled:
+     * <ul>
+     *     <li>the device will not send any HDMI CEC audio messages
+     *     <li>received HDMI CEC audio messages are responded to with {@code <Feature Abort>}
+     * </ul>
+     *
+     * <p>Effects on different device types:
+     * <table>
+     *     <tr><th>HDMI CEC device type</th><th>enabled</th><th>disabled</th></tr>
+     *     <tr>
+     *         <td>TV (type: 0)</td>
+     *         <td>Per CEC specification.</td>
+     *         <td>TV changes system volume. TV no longer reacts to incoming volume changes via
+     *         {@code <User Control Pressed>}. TV no longer handles {@code <Report Audio Status>}
+     *         .</td>
+     *     </tr>
+     *     <tr>
+     *         <td>Playback device (type: 4)</td>
+     *         <td>Device sends volume commands to TV/Audio system via {@code <User Control
+     *         Pressed>}</td><td>Device does not send volume commands via {@code <User Control
+     *         Pressed>}.</td>
+     *     </tr>
+     *     <tr>
+     *         <td>Audio device (type: 5)</td>
+     *         <td>Full "System Audio Control" capabilities.</td>
+     *         <td>Audio device no longer reacts to incoming {@code <User Control Pressed>}
+     *         volume commands. Audio device no longer reports volume changes via {@code <Report
+     *         Audio Status>}.</td>
+     *     </tr>
+     * </table>
+     *
+     * <p> Due to the resulting behavior, usage on TV and Audio devices is discouraged.
+     *
+     * @param isHdmiCecVolumeControlEnabled target state of HDMI CEC volume control.
+     * @see Settings.Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+    public void setHdmiCecVolumeControlEnabled(boolean isHdmiCecVolumeControlEnabled) {
+        try {
+            mService.setHdmiCecVolumeControlEnabled(isHdmiCecVolumeControlEnabled);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns whether volume changes via HDMI CEC are enabled.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+    public boolean isHdmiCecVolumeControlEnabled() {
+        try {
+            return mService.isHdmiCecVolumeControlEnabled();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Gets whether the system is in system audio mode.
      *
      * @hide
diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
index a8fed2b..3582a92 100644
--- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
@@ -80,6 +80,8 @@
     void sendMhlVendorCommand(int portId, int offset, int length, in byte[] data);
     void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener);
     void setStandbyMode(boolean isStandbyModeOn);
+    void setHdmiCecVolumeControlEnabled(boolean isHdmiCecVolumeControlEnabled);
+    boolean isHdmiCecVolumeControlEnabled();
     void reportAudioStatus(int deviceType, int volume, int maxVolume, boolean isMute);
     void setSystemAudioModeOnForAudioOnlySource();
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ac1998a..fbd6cba 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9614,6 +9614,43 @@
         */
        public static final String HDMI_CONTROL_ENABLED = "hdmi_control_enabled";
 
+        /**
+         * Controls whether volume control commands via HDMI CEC are enabled. (0 = false, 1 =
+         * true).
+         *
+         * <p>Effects on different device types:
+         * <table>
+         *     <tr><th>HDMI CEC device type</th><th>0: disabled</th><th>1: enabled</th></tr>
+         *     <tr>
+         *         <td>TV (type: 0)</td>
+         *         <td>Per CEC specification.</td>
+         *         <td>TV changes system volume. TV no longer reacts to incoming volume changes
+         *         via {@code <User Control Pressed>}. TV no longer handles {@code <Report Audio
+         *         Status>}.</td>
+         *     </tr>
+         *     <tr>
+         *         <td>Playback device (type: 4)</td>
+         *         <td>Device sends volume commands to TV/Audio system via {@code <User Control
+         *         Pressed>}</td>
+         *         <td>Device does not send volume commands via {@code <User Control Pressed>}.</td>
+         *     </tr>
+         *     <tr>
+         *         <td>Audio device (type: 5)</td>
+         *         <td>Full "System Audio Control" capabilities.</td>
+         *         <td>Audio device no longer reacts to incoming {@code <User Control Pressed>}
+         *         volume commands. Audio device no longer reports volume changes via {@code
+         *         <Report Audio Status>}.</td>
+         *     </tr>
+         * </table>
+         *
+         * <p> Due to the resulting behavior, usage on TV and Audio devices is discouraged.
+         *
+         * @hide
+         * @see android.hardware.hdmi.HdmiControlManager#setHdmiCecVolumeControlEnabled(boolean)
+         */
+        public static final String HDMI_CONTROL_VOLUME_CONTROL_ENABLED =
+                "hdmi_control_volume_control_enabled";
+
        /**
         * Whether HDMI System Audio Control feature is enabled. If enabled, TV will try to turn on
         * system audio mode if there's a connected CEC-enabled AV Receiver. Then audio stream will
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
index 2141b81..7cd2f3b 100644
--- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
@@ -354,6 +354,15 @@
         @Override
         public void askRemoteDeviceToBecomeActiveSource(int physicalAddress) {
         }
+
+        @Override
+        public void setHdmiCecVolumeControlEnabled(boolean isHdmiCecVolumeControlEnabled) {
+        }
+
+        @Override
+        public boolean isHdmiCecVolumeControlEnabled() {
+            return true;
+        }
     }
 
 }
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 03f6df0..0dd7fb8 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -296,6 +296,7 @@
                     Settings.Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
                     Settings.Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
                     Settings.Global.HDMI_CONTROL_ENABLED,
+                    Settings.Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED,
                     Settings.Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED,
                     Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
                     Settings.Global.HIDDEN_API_POLICY,
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index a358707..3ff6ec1 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -32,6 +32,7 @@
 import android.view.KeyEvent;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.hdmi.Constants.LocalActivePort;
 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
@@ -144,7 +145,8 @@
 
     // A collection of FeatureAction.
     // Note that access to this collection should happen in service thread.
-    private final ArrayList<HdmiCecFeatureAction> mActions = new ArrayList<>();
+    @VisibleForTesting
+    final ArrayList<HdmiCecFeatureAction> mActions = new ArrayList<>();
 
     private final Handler mHandler =
             new Handler() {
@@ -544,6 +546,8 @@
         } else if (mService.isPowerStandbyOrTransient() && isPowerOnOrToggleCommand(message)) {
             mService.wakeUp();
             return true;
+        } else if (!mService.isHdmiCecVolumeControlEnabled() && isVolumeOrMuteCommand(message)) {
+            return false;
         }
 
         final long downTime = SystemClock.uptimeMillis();
@@ -618,6 +622,16 @@
                         || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION);
     }
 
+    static boolean isVolumeOrMuteCommand(HdmiCecMessage message) {
+        byte[] params = message.getParams();
+        return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED
+                && (params[0] == HdmiCecKeycode.CEC_KEYCODE_VOLUME_DOWN
+                    || params[0] == HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP
+                    || params[0] == HdmiCecKeycode.CEC_KEYCODE_MUTE
+                    || params[0] == HdmiCecKeycode.CEC_KEYCODE_MUTE_FUNCTION
+                    || params[0] == HdmiCecKeycode.CEC_KEYCODE_RESTORE_VOLUME_FUNCTION);
+    }
+
     protected boolean handleTextViewOn(HdmiCecMessage message) {
         return false;
     }
@@ -1038,6 +1052,9 @@
     @ServiceThreadOnly
     protected void sendVolumeKeyEvent(int keyCode, boolean isPressed) {
         assertRunOnServiceThread();
+        if (!mService.isHdmiCecVolumeControlEnabled()) {
+            return;
+        }
         if (!HdmiCecKeycode.isVolumeKeycode(keyCode)) {
             Slog.w(TAG, "Not a volume key: " + keyCode);
             return;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index e5a08d3..611b8c6 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -575,7 +575,7 @@
     @ServiceThreadOnly
     protected boolean handleGiveAudioStatus(HdmiCecMessage message) {
         assertRunOnServiceThread();
-        if (isSystemAudioControlFeatureEnabled()) {
+        if (isSystemAudioControlFeatureEnabled() && mService.isHdmiCecVolumeControlEnabled()) {
             reportAudioStatus(message.getSource());
         } else {
             mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
@@ -930,6 +930,9 @@
 
     void reportAudioStatus(int source) {
         assertRunOnServiceThread();
+        if (!mService.isHdmiCecVolumeControlEnabled()) {
+            return;
+        }
 
         int volume = mService.getAudioManager().getStreamVolume(AudioManager.STREAM_MUSIC);
         boolean mute = mService.getAudioManager().isStreamMute(AudioManager.STREAM_MUSIC);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index a702ce5..0ac4f9e 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -678,6 +678,9 @@
     @ServiceThreadOnly
     protected boolean handleReportAudioStatus(HdmiCecMessage message) {
         assertRunOnServiceThread();
+        if (!mService.isHdmiCecVolumeControlEnabled()) {
+            return false;
+        }
 
         boolean mute = HdmiUtils.isAudioStatusMute(message);
         int volume = HdmiUtils.getAudioStatusVolume(message);
@@ -987,7 +990,7 @@
     }
 
     void setAudioStatus(boolean mute, int volume) {
-        if (!isSystemAudioActivated()) {
+        if (!isSystemAudioActivated() || !mService.isHdmiCecVolumeControlEnabled()) {
             return;
         }
         synchronized (mLock) {
@@ -1009,7 +1012,7 @@
             // On initialization process, getAvrDeviceInfo() may return null and cause exception
             return;
         }
-        if (delta == 0 || !isSystemAudioActivated()) {
+        if (delta == 0 || !isSystemAudioActivated() || !mService.isHdmiCecVolumeControlEnabled()) {
             return;
         }
 
@@ -1038,7 +1041,7 @@
     @ServiceThreadOnly
     void changeMute(boolean mute) {
         assertRunOnServiceThread();
-        if (getAvrDeviceInfo() == null) {
+        if (getAvrDeviceInfo() == null || !mService.isHdmiCecVolumeControlEnabled()) {
             // On initialization process, getAvrDeviceInfo() may return null and cause exception
             return;
         }
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index d9e3025..53f9ebc 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -177,6 +177,10 @@
     @GuardedBy("mLock")
     protected final ActiveSource mActiveSource = new ActiveSource();
 
+    // Whether HDMI CEC volume control is enabled or not.
+    @GuardedBy("mLock")
+    private boolean mHdmiCecVolumeControlEnabled;
+
     // Whether System Audio Mode is activated or not.
     @GuardedBy("mLock")
     private boolean mSystemAudioActivated = false;
@@ -497,6 +501,8 @@
         mPowerStatus = getInitialPowerStatus();
         mProhibitMode = false;
         mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true);
+        mHdmiCecVolumeControlEnabled = readBooleanSetting(
+                Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, true);
         mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true);
 
         if (mCecController == null) {
@@ -646,6 +652,7 @@
         ContentResolver resolver = getContext().getContentResolver();
         String[] settings = new String[] {
                 Global.HDMI_CONTROL_ENABLED,
+                Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED,
                 Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
                 Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
                 Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED,
@@ -674,6 +681,9 @@
                 case Global.HDMI_CONTROL_ENABLED:
                     setControlEnabled(enabled);
                     break;
+                case Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED:
+                    setHdmiCecVolumeControlEnabled(enabled);
+                    break;
                 case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED:
                     if (isTvDeviceEnabled()) {
                         tv().setAutoWakeup(enabled);
@@ -1273,7 +1283,9 @@
     }
 
     void setAudioStatus(boolean mute, int volume) {
-        if (!isTvDeviceEnabled() || !tv().isSystemAudioActivated()) {
+        if (!isTvDeviceEnabled()
+                || !tv().isSystemAudioActivated()
+                || !isHdmiCecVolumeControlEnabled()) {
             return;
         }
         AudioManager audioManager = getAudioManager();
@@ -2187,6 +2199,24 @@
         }
 
         @Override
+        public boolean isHdmiCecVolumeControlEnabled() {
+            enforceAccessPermission();
+            return HdmiControlService.this.isHdmiCecVolumeControlEnabled();
+        }
+
+        @Override
+        public void setHdmiCecVolumeControlEnabled(final boolean isHdmiCecVolumeControlEnabled) {
+            enforceAccessPermission();
+            runOnServiceThread(new Runnable() {
+                @Override
+                public void run() {
+                    HdmiControlService.this.setHdmiCecVolumeControlEnabled(
+                            isHdmiCecVolumeControlEnabled);
+                }
+            });
+        }
+
+        @Override
         public void reportAudioStatus(final int deviceType, final int volume, final int maxVolume,
                 final boolean isMute) {
             enforceAccessPermission();
@@ -2250,6 +2280,7 @@
             pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled);
             pw.println("mMhlInputChangeEnabled: " + mMhlInputChangeEnabled);
             pw.println("mSystemAudioActivated: " + isSystemAudioActivated());
+            pw.println("mHdmiCecVolumeControlEnabled " + mHdmiCecVolumeControlEnabled);
             pw.decreaseIndent();
 
             pw.println("mMhlController: ");
@@ -2982,6 +3013,29 @@
         }
     }
 
+    void setHdmiCecVolumeControlEnabled(boolean isHdmiCecVolumeControlEnabled) {
+        assertRunOnServiceThread();
+        synchronized (mLock) {
+            mHdmiCecVolumeControlEnabled = isHdmiCecVolumeControlEnabled;
+
+            boolean storedValue = readBooleanSetting(Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED,
+                    true);
+            if (storedValue != isHdmiCecVolumeControlEnabled) {
+                HdmiLogger.debug("Changing HDMI CEC volume control feature state: %s",
+                        isHdmiCecVolumeControlEnabled);
+                writeBooleanSetting(Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED,
+                        isHdmiCecVolumeControlEnabled);
+            }
+        }
+    }
+
+    boolean isHdmiCecVolumeControlEnabled() {
+        assertRunOnServiceThread();
+        synchronized (mLock) {
+            return mHdmiCecVolumeControlEnabled;
+        }
+    }
+
     boolean isProhibitMode() {
         synchronized (mLock) {
             return mProhibitMode;
@@ -3022,8 +3076,12 @@
 
         if (enabled) {
             enableHdmiControlService();
+            setHdmiCecVolumeControlEnabled(
+                    readBooleanSetting(Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, true));
             return;
         }
+
+        setHdmiCecVolumeControlEnabled(false);
         // Call the vendor handler before the service is disabled.
         invokeVendorCommandListenersOnControlStateChanged(false,
                 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index a587029..28887fd 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -24,6 +24,7 @@
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
 import static com.android.server.hdmi.Constants.ADDR_TUNER_1;
 import static com.android.server.hdmi.Constants.ADDR_TV;
+import static com.android.server.hdmi.Constants.MESSAGE_GIVE_AUDIO_STATUS;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
 import static com.android.server.hdmi.HdmiControlService.STANDBY_SCREEN_OFF;
 
@@ -47,6 +48,7 @@
 import org.junit.runners.JUnit4;
 
 import java.util.ArrayList;
+
 @SmallTest
 @RunWith(JUnit4.class)
 /** Tests for {@link HdmiCecLocalDeviceAudioSystem} class. */
@@ -167,6 +169,8 @@
                 }
             };
 
+        mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+
         mMyLooper = mTestLooper.getLooper();
         mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(mHdmiControlService);
         mHdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(mHdmiControlService) {
@@ -717,4 +721,112 @@
         mHdmiCecLocalDeviceAudioSystem.onHotplug(0, false);
         assertThat(mWokenUp).isFalse();
     }
+
+    @Test
+    public void giveAudioStatus_volumeEnabled() {
+        mMusicVolume = 50;
+        mMusicMaxVolume = 100;
+        mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+        mHdmiCecLocalDeviceAudioSystem.setSystemAudioControlFeatureEnabled(true);
+
+        int volume = mHdmiControlService.getAudioManager()
+                .getStreamVolume(AudioManager.STREAM_MUSIC);
+        boolean mute = mHdmiControlService.getAudioManager()
+                .isStreamMute(AudioManager.STREAM_MUSIC);
+        int maxVolume = mHdmiControlService.getAudioManager()
+                .getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume);
+        HdmiCecMessage expected = HdmiCecMessageBuilder.buildReportAudioStatus(ADDR_AUDIO_SYSTEM,
+                ADDR_TV, scaledVolume, mute);
+        HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                ADDR_AUDIO_SYSTEM, ADDR_TV, MESSAGE_GIVE_AUDIO_STATUS, Constants.ABORT_REFUSED);
+
+        HdmiCecMessage giveAudioStatus = HdmiCecMessageBuilder.buildGiveAudioStatus(ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        mNativeWrapper.clearResultMessages();
+        boolean handled = mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(giveAudioStatus);
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbort);
+        assertThat(handled).isTrue();
+    }
+
+    @Test
+    public void giveAudioStatus_volumeDisabled() {
+        mMusicVolume = 50;
+        mMusicMaxVolume = 100;
+        mHdmiControlService.setHdmiCecVolumeControlEnabled(false);
+        mHdmiCecLocalDeviceAudioSystem.setSystemAudioControlFeatureEnabled(true);
+
+        int volume = mHdmiControlService.getAudioManager()
+                .getStreamVolume(AudioManager.STREAM_MUSIC);
+        boolean mute = mHdmiControlService.getAudioManager()
+                .isStreamMute(AudioManager.STREAM_MUSIC);
+        int maxVolume = mHdmiControlService.getAudioManager()
+                .getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume);
+        HdmiCecMessage unexpected = HdmiCecMessageBuilder.buildReportAudioStatus(ADDR_AUDIO_SYSTEM,
+                ADDR_TV, scaledVolume, mute);
+        HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                ADDR_AUDIO_SYSTEM, ADDR_TV, MESSAGE_GIVE_AUDIO_STATUS, Constants.ABORT_REFUSED);
+
+        HdmiCecMessage giveAudioStatus = HdmiCecMessageBuilder.buildGiveAudioStatus(ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        mNativeWrapper.clearResultMessages();
+        boolean handled = mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(giveAudioStatus);
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(featureAbort);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(unexpected);
+        assertThat(handled).isTrue();
+    }
+
+    @Test
+    public void reportAudioStatus_volumeEnabled() {
+        mMusicVolume = 50;
+        mMusicMaxVolume = 100;
+        mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+        mHdmiCecLocalDeviceAudioSystem.setSystemAudioControlFeatureEnabled(true);
+
+        int volume = mHdmiControlService.getAudioManager()
+                .getStreamVolume(AudioManager.STREAM_MUSIC);
+        boolean mute = mHdmiControlService.getAudioManager()
+                .isStreamMute(AudioManager.STREAM_MUSIC);
+        int maxVolume = mHdmiControlService.getAudioManager()
+                .getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume);
+        HdmiCecMessage expected = HdmiCecMessageBuilder.buildReportAudioStatus(ADDR_AUDIO_SYSTEM,
+                ADDR_TV, scaledVolume, mute);
+
+        mNativeWrapper.clearResultMessages();
+        mHdmiCecLocalDeviceAudioSystem.reportAudioStatus(ADDR_TV);
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(expected);
+    }
+
+    @Test
+    public void reportAudioStatus_volumeDisabled() {
+        mMusicVolume = 50;
+        mMusicMaxVolume = 100;
+        mHdmiControlService.setHdmiCecVolumeControlEnabled(false);
+        mHdmiCecLocalDeviceAudioSystem.setSystemAudioControlFeatureEnabled(true);
+
+        int volume = mHdmiControlService.getAudioManager()
+                .getStreamVolume(AudioManager.STREAM_MUSIC);
+        boolean mute = mHdmiControlService.getAudioManager()
+                .isStreamMute(AudioManager.STREAM_MUSIC);
+        int maxVolume = mHdmiControlService.getAudioManager()
+                .getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume);
+        HdmiCecMessage unexpected = HdmiCecMessageBuilder.buildReportAudioStatus(ADDR_AUDIO_SYSTEM,
+                ADDR_TV, scaledVolume, mute);
+
+        mNativeWrapper.clearResultMessages();
+        mHdmiCecLocalDeviceAudioSystem.reportAudioStatus(ADDR_TV);
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(unexpected);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index f72d622..b8394e3 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -22,6 +22,7 @@
 
 import android.os.Looper;
 import android.os.test.TestLooper;
+import android.view.KeyEvent;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -153,4 +154,75 @@
         mHdmiCecLocalDevicePlayback.onHotplug(0, false);
         assertThat(mWokenUp).isFalse();
     }
+
+    @Test
+    @Ignore("b/151147315")
+    public void sendVolumeKeyEvent_up_volumeEnabled() {
+        mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+        mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_UP, true);
+        mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_UP, false);
+
+        assertThat(hasSendKeyAction()).isTrue();
+    }
+
+    @Test
+    @Ignore("b/151147315")
+    public void sendVolumeKeyEvent_down_volumeEnabled() {
+        mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+        mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_DOWN, true);
+        mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_DOWN, false);
+
+        assertThat(hasSendKeyAction()).isTrue();
+    }
+
+    @Test
+    @Ignore("b/151147315")
+    public void sendVolumeKeyEvent_mute_volumeEnabled() {
+        mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+        mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_MUTE, true);
+        mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_MUTE, false);
+
+        assertThat(hasSendKeyAction()).isTrue();
+    }
+
+    @Test
+    @Ignore("b/151147315")
+    public void sendVolumeKeyEvent_up_volumeDisabled() {
+        mHdmiControlService.setHdmiCecVolumeControlEnabled(false);
+        mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_UP, true);
+        mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_UP, false);
+
+        assertThat(hasSendKeyAction()).isFalse();
+    }
+
+    @Test
+    @Ignore("b/151147315")
+    public void sendVolumeKeyEvent_down_volumeDisabled() {
+        mHdmiControlService.setHdmiCecVolumeControlEnabled(false);
+        mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_DOWN, true);
+        mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_DOWN, false);
+
+        assertThat(hasSendKeyAction()).isFalse();
+    }
+
+    @Test
+    @Ignore("b/151147315")
+    public void sendVolumeKeyEvent_mute_volumeDisabled() {
+        mHdmiControlService.setHdmiCecVolumeControlEnabled(false);
+        mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_MUTE, true);
+        mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_MUTE, false);
+
+        assertThat(hasSendKeyAction()).isFalse();
+    }
+
+    private boolean hasSendKeyAction() {
+        boolean match = false;
+        for (HdmiCecFeatureAction action : mHdmiCecLocalDevicePlayback.mActions) {
+            if (action instanceof SendKeyAction) {
+                match = true;
+                break;
+            }
+        }
+        return match;
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
index 039b904..e0bada31 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
@@ -19,6 +19,7 @@
 
 import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
 import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
+import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
 import static com.android.server.hdmi.Constants.ADDR_TV;
 import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED;
 import static com.android.server.hdmi.Constants.MESSAGE_DEVICE_VENDOR_ID;
@@ -185,4 +186,64 @@
                 HdmiCecMessageBuilder.buildStandby(ADDR_TV, ADDR_AUDIO_SYSTEM));
         assertTrue(mStandbyMessageReceived);
     }
+
+    @Test
+    public void handleUserControlPressed_volumeUp() {
+        mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+        boolean result = mHdmiLocalDevice.handleUserControlPressed(
+                HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV,
+                        HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP));
+
+        assertTrue(result);
+    }
+
+    @Test
+    public void handleUserControlPressed_volumeDown() {
+        mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+        boolean result = mHdmiLocalDevice.handleUserControlPressed(
+                HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV,
+                        HdmiCecKeycode.CEC_KEYCODE_VOLUME_DOWN));
+
+        assertTrue(result);
+    }
+
+    @Test
+    public void handleUserControlPressed_volumeMute() {
+        mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+        boolean result = mHdmiLocalDevice.handleUserControlPressed(
+                HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV,
+                        HdmiCecKeycode.CEC_KEYCODE_MUTE));
+
+        assertTrue(result);
+    }
+
+    @Test
+    public void handleUserControlPressed_volumeUp_disabled() {
+        mHdmiControlService.setHdmiCecVolumeControlEnabled(false);
+        boolean result = mHdmiLocalDevice.handleUserControlPressed(
+                HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV,
+                        HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP));
+
+        assertFalse(result);
+    }
+
+    @Test
+    public void handleUserControlPressed_volumeDown_disabled() {
+        mHdmiControlService.setHdmiCecVolumeControlEnabled(false);
+        boolean result = mHdmiLocalDevice.handleUserControlPressed(
+                HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV,
+                        HdmiCecKeycode.CEC_KEYCODE_VOLUME_DOWN));
+
+        assertFalse(result);
+    }
+
+    @Test
+    public void handleUserControlPressed_volumeMute_disabled() {
+        mHdmiControlService.setHdmiCecVolumeControlEnabled(false);
+        boolean result = mHdmiLocalDevice.handleUserControlPressed(
+                HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV,
+                        HdmiCecKeycode.CEC_KEYCODE_MUTE));
+
+        assertFalse(result);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index fa19814..7af7a23 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -252,4 +252,13 @@
         assertThat(mHdmiControlService.getPowerStatus()).isEqualTo(
                 HdmiControlManager.POWER_STATUS_STANDBY);
     }
+
+    @Test
+    public void setAndGetCecVolumeControlEnabled_isApi() {
+        mHdmiControlService.setHdmiCecVolumeControlEnabled(false);
+        assertThat(mHdmiControlService.isHdmiCecVolumeControlEnabled()).isFalse();
+
+        mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
+        assertThat(mHdmiControlService.isHdmiCecVolumeControlEnabled()).isTrue();
+    }
 }