Update HDMI CEC CTS tests

This syncs all changes made to these tests in AOSP and internal branches to the current
branch.

This inculdes:
 - New tests
 - Test fixes (fixing failing tests)
 - Refactors to tests

Bug: 149530408
Test: atest CtsHdmiCecHostSideTests
Change-Id: I197b2e579c6dfb30e7484b17979a4d21e973257e
diff --git a/hostsidetests/hdmicec/Android.bp b/hostsidetests/hdmicec/Android.bp
index 66d0e0a..4268ff1 100644
--- a/hostsidetests/hdmicec/Android.bp
+++ b/hostsidetests/hdmicec/Android.bp
@@ -28,6 +28,6 @@
         "compatibility-host-util",
     ],
     data: [
-        ":HdmiCecKeyEventCaptureApp",
+        ":HdmiCecHelperApp",
     ],
 }
diff --git a/hostsidetests/hdmicec/AndroidTest.xml b/hostsidetests/hdmicec/AndroidTest.xml
index f4f5281..a0a4350 100644
--- a/hostsidetests/hdmicec/AndroidTest.xml
+++ b/hostsidetests/hdmicec/AndroidTest.xml
@@ -22,7 +22,7 @@
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="HdmiCecKeyEventCaptureApp.apk" />
+        <option name="test-file-name" value="HdmiCecHelperApp.apk" />
     </target_preparer>
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsHdmiCecHostTestCases.jar" />
diff --git a/hostsidetests/hdmicec/README.md b/hostsidetests/hdmicec/README.md
new file mode 100644
index 0000000..3d5b003
--- /dev/null
+++ b/hostsidetests/hdmicec/README.md
@@ -0,0 +1,28 @@
+# CEC CTS testing for Android TV devices
+
+NOTE: CTS has two meanings here. HDMI defines a set of tests in **Compliance
+Test Specification** in HDMI 1.4b *HDMI Compliance Test Specification 1.4b and
+**Android Compatibility Test Suite**.
+
+Android Compatibility Test Suite include specific tests from HDMI Compliance
+Test Specification and other Android specific tests.
+
+## Setup
+
+### Playback devices (aka Set Top Boxes)
+
+Running these CTS tests requires a specific HDMI layout with a CEC adapter.
+
+*   Android TV playback device
+*   CEC adapter see [External CEC Adapter instructions](cec_adapter.md)
+*   HDMI Display (aka a TV)
+
+![drawing](setup.png)
+
+### Automation
+
+Given the setup described above you can run all of these tests with the command
+
+```
+atest CtsHdmiCecHostTestCases
+```
diff --git a/hostsidetests/hdmicec/app/Android.bp b/hostsidetests/hdmicec/app/Android.bp
index 910aa30..ed41e1a 100644
--- a/hostsidetests/hdmicec/app/Android.bp
+++ b/hostsidetests/hdmicec/app/Android.bp
@@ -13,8 +13,13 @@
 // limitations under the License.
 
 android_test_helper_app {
-    name: "HdmiCecKeyEventCaptureApp",
+    name: "HdmiCecHelperApp",
     defaults: ["cts_defaults"],
     srcs: ["src/**/*.java"],
-    sdk_version: "current",
+    static_libs: [
+        "services.core",
+        "guava",
+        "androidx.test.runner",
+    ],
+    min_sdk_version: "28",
 }
diff --git a/hostsidetests/hdmicec/app/AndroidManifest.xml b/hostsidetests/hdmicec/app/AndroidManifest.xml
index ce9fff7..603445e6 100644
--- a/hostsidetests/hdmicec/app/AndroidManifest.xml
+++ b/hostsidetests/hdmicec/app/AndroidManifest.xml
@@ -16,16 +16,30 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.hdmicec.app">
+     package="android.hdmicec.app">
     <uses-feature android:name="android.software.leanback"
-        android:required="false" />
-    <application >
-        <activity android:name=".HdmiCecKeyEventCapture" >
+         android:required="false"/>
+    <application>
+        <activity android:name=".HdmiCecKeyEventCapture"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
+            </intent-filter>
+        </activity>
+         <activity android:name=".HdmiCecAudioManager"
+              android:exported="true">
+            <intent-filter>
+                <action android:name="android.hdmicec.app.MUTE"/>
+                <action android:name="android.hdmicec.app.UNMUTE"/>
+                <action android:name="android.hdmicec.app.REPORT_VOLUME"/>
+                <action android:name="android.hdmicec.app.SET_VOLUME"/>
+                <action android:name="android.hdmicec.app.GET_SUPPORTED_SAD_FORMATS"/>
+                <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
+    <uses-sdk android:minSdkVersion="28"   android:targetSdkVersion="28" />
+
 </manifest>
diff --git a/hostsidetests/hdmicec/app/src/android/hdmicec/app/HdmiCecAudioManager.java b/hostsidetests/hdmicec/app/src/android/hdmicec/app/HdmiCecAudioManager.java
new file mode 100644
index 0000000..f04490f
--- /dev/null
+++ b/hostsidetests/hdmicec/app/src/android/hdmicec/app/HdmiCecAudioManager.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hdmicec.app;
+
+import android.app.Activity;
+import android.content.Context;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.server.hdmi.SadConfigurationReaderTest;
+
+import org.junit.runner.JUnitCore;
+
+/**
+ * A simple app that can be used to mute, unmute, set volume or get the volume status of a device.
+ * The actions supported are:
+ *
+ * 1. android.hdmicec.app.MUTE: Mutes the STREAM_MUSIC of the device,
+ *                              irrespective of the previous state.
+ *    Usage: START_COMMAND -a android.hdmicec.app.MUTE
+ * 2. android.hdmicec.app.UNMUTE: Unmutes the STREAM_MUSIC of the device,
+ *                                irrespective of the previous state.
+ *    Usage: START_COMMAND -a android.hdmicec.app.UNMUTE
+ * 3. android.hdmicec.app.REPORT_VOLUME: Reports if the STREAM_MUSIC of the device is muted and
+ *                                       if not muted, the current volume level in percent.
+ *    Usage: START_COMMAND -a android.hdmicec.app.REPORT_VOLUME
+ * 4. android.hdmicec.app.SET_VOLUME: Sets the volume of STREAM_MUSIC to a particular level.
+ *                                    Has to be used with --ei "volumePercent" x.
+ *    Usage: START_COMMAND -a android.hdmicec.app.SET_VOLUME --ei "volumePercent" x
+ *
+ * where START_COMMAND is
+ * adb shell am start -n "android.hdmicec.app/android.hdmicec.app.HdmiCecAudioManager"
+ */
+public class HdmiCecAudioManager extends Activity {
+
+    private static final String TAG = HdmiCecAudioManager.class.getSimpleName();
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+
+        switch(getIntent().getAction()) {
+            case "android.hdmicec.app.MUTE":
+                audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
+                        AudioManager.ADJUST_MUTE, 0);
+                break;
+            case "android.hdmicec.app.UNMUTE":
+                audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
+                        AudioManager.ADJUST_UNMUTE, 0);
+                break;
+            case "android.hdmicec.app.REPORT_VOLUME":
+                if (audioManager.isStreamMute(AudioManager.STREAM_MUSIC)) {
+                    Log.i(TAG, "Device muted.");
+                } else {
+                    int minVolume = audioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC);
+                    int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+                    int volume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+                    int percentVolume = 100 * volume / (maxVolume - minVolume);
+                    Log.i(TAG, "Volume at " + percentVolume + "%");
+                }
+                break;
+            case "android.hdmicec.app.SET_VOLUME":
+                int percentVolume = getIntent().getIntExtra("volumePercent", 50);
+                int minVolume = audioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC);
+                int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+                int volume = minVolume + ((maxVolume - minVolume) * percentVolume / 100);
+                audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
+                Log.i(TAG, "Set volume to " + volume + " (" + percentVolume + "%)");
+                break;
+            case "android.hdmicec.app.GET_SUPPORTED_SAD_FORMATS":
+                JUnitCore junit = new JUnitCore();
+                junit.run(SadConfigurationReaderTest.class);
+                break;
+            default:
+                Log.w(TAG, "Unknown intent!");
+        }
+        finishAndRemoveTask();
+    }
+}
+
diff --git a/hostsidetests/hdmicec/app/src/com/android/server/hdmi/SadConfigurationReaderTest.java b/hostsidetests/hdmicec/app/src/com/android/server/hdmi/SadConfigurationReaderTest.java
new file mode 100644
index 0000000..48665ac
--- /dev/null
+++ b/hostsidetests/hdmicec/app/src/com/android/server/hdmi/SadConfigurationReaderTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.util.Log;
+
+import com.android.server.hdmi.HdmiUtils.CodecSad;
+import com.android.server.hdmi.HdmiUtils.DeviceConfig;
+import com.google.common.hash.HashCode;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+/**
+ * Reads short audio descriptors from a configuration file and outputs to a log
+ * for use by host side tests.
+ */
+public class SadConfigurationReaderTest {
+
+    private static final String TAG = "SadConfigurationReaderTest";
+
+    // Variable should be copy of SHORT_AUDIO_DESCRIPTOR_CONFIG_PATH in
+    // frameworks/base/services/core/java/com/android/server/hdmi/
+    //  HdmiCecLocalDeviceAudioSystem.java
+    private final String SHORT_AUDIO_DESCRIPTOR_CONFIG_PATH = "/vendor/etc/sadConfig.xml";
+
+    @Test
+    public void parseSadConfigXML() {
+        List<DeviceConfig> deviceConfigs = null;
+        File file = new File(SHORT_AUDIO_DESCRIPTOR_CONFIG_PATH);
+        if (file.exists()) {
+            try {
+                InputStream in = new FileInputStream(file);
+                deviceConfigs = HdmiUtils.ShortAudioDescriptorXmlParser.parse(in);
+                in.close();
+            } catch (IOException e) {
+                Log.e(TAG, "Error reading file: " + file.getAbsolutePath(), e);
+            } catch (XmlPullParserException e) {
+                Log.e(TAG, "Unable to parse file: " + file.getAbsolutePath(), e);
+            }
+        } else {
+            Log.e(TAG, "No config file present at " + file.getAbsolutePath());
+            return;
+        }
+        DeviceConfig deviceConfigToUse = null;
+        if (deviceConfigs != null && deviceConfigs.size() > 0) {
+            for (DeviceConfig deviceConfig : deviceConfigs) {
+                if (deviceConfig.name.equals("VX_AUDIO_DEVICE_IN_HDMI_ARC")) {
+                    deviceConfigToUse = deviceConfig;
+                    break;
+                }
+            }
+            if (deviceConfigToUse == null) {
+                Log.w(TAG, "sadConfig.xml does not have required device info for "
+                                   + "VX_AUDIO_DEVICE_IN_HDMI_ARC");
+                return;
+            }
+            List<Integer> audioCodecFormats = new ArrayList<>();
+            List<String> shortAudioDescriptors = new ArrayList<>();
+            for (CodecSad codecSad : deviceConfigToUse.supportedCodecs) {
+                audioCodecFormats.add(codecSad.audioCodec);
+                shortAudioDescriptors.add(HashCode.fromBytes(codecSad.sad).toString());
+            }
+            String audioCodecFormatsString = audioCodecFormats.toString();
+            String shortAudioDescriptorsString = shortAudioDescriptors.toString();
+            Log.i(TAG, "Supported Audio Formats");
+            Log.i(TAG, audioCodecFormatsString.substring(1, audioCodecFormatsString.length() - 1));
+            Log.i(TAG, shortAudioDescriptorsString
+                               .substring(1, shortAudioDescriptorsString.length() - 1));
+        }
+    }
+}
diff --git a/hostsidetests/hdmicec/cec_adapter.md b/hostsidetests/hdmicec/cec_adapter.md
new file mode 100644
index 0000000..3a325d7
--- /dev/null
+++ b/hostsidetests/hdmicec/cec_adapter.md
@@ -0,0 +1,221 @@
+# External CEC adapter
+
+## USB - CEC Adapter from Pulse-Eight
+
+![Picture of USB - CEC Adapter](https://www.pulse-eight.com/generated-assets/products/0000237_555.jpeg)
+
+## Get the hardware
+
+*   [Order from Pulse-Eight](https://www.pulse-eight.com/p/104/usb-hdmi-cec-adapter#)
+*   [Pulse-Eight USB CEC adapter on Amazon](https://www.amazon.com/s/ref=nb_sb_ss_i_1_22?url=search-alias%3Daps&field-keywords=pulse+eight+usb+cec+adapter&sprefix=usb+cec+adapter+pulse+%2Caps%2C218&crid=UK4LY390M5H2)
+
+## Get the software {#software}
+
+1.  Connect "TV" port on the adapter to your TV
+2.  Connect USB mini port to you computer
+3.  Install the cec-client
+
+    *   Linux
+
+    ```shell
+    sudo apt-get install cec-utils
+    ```
+
+    *   mac (using [MacPorts](https://guide.macports.org/#installing))
+
+    ```shell
+     sudo /opt/local/bin/port install libcec
+    ```
+
+4.  run the client
+
+    ```shell
+    $ cec-client
+    No device type given. Using 'recording device'
+    CEC Parser created - libCEC version 4.0.2
+    no serial port given. trying autodetect:
+     path:     /dev/cu.usbmodemv1
+     com port: /dev/cu.usbmodemv1
+
+    opening a connection to the CEC adapter...
+    DEBUG:   [               1] Broadcast (F): osd name set to 'Broadcast'
+    DEBUG:   [               3] connection opened, clearing any previous input     and waiting for active transmissions to end before starting
+    DEBUG:   [              10] communication thread started
+    DEBUG:   [              70] turning controlled mode on
+    NOTICE:  [             255] connection opened
+    DEBUG:   [             255] processor thread started
+    DEBUG:   [             255] << Broadcast (F) -> TV (0): POLL
+    TRAFFIC: [             255] << f0
+    DEBUG:   [             255] setting the line timeout to 3
+    DEBUG:   [             403] >> POLL sent
+    DEBUG:   [             403] TV (0): device status changed into 'present'
+    ```
+
+## Add timestamps
+
+Use the `ts` command to add timestamps.
+
+```shell
+$ cec-client  | ts
+Nov 18 16:15:46 No device type given. Using 'recording device'
+Nov 18 16:15:46 CEC Parser created - libCEC version 4.0.4
+Nov 18 16:15:46 no serial port given. trying autodetect:
+Nov 18 16:15:46  path:     /sys/devices/pci0000:00/0000:00:14.0/usb2/2-9
+Nov 18 16:15:46  com port: /dev/ttyACM0
+Nov 18 16:15:46
+Nov 18 16:15:46 opening a connection to the CEC adapter...
+Nov 18 16:15:46 DEBUG:   [             386] Broadcast (F): osd name set to 'Broadcast'
+```
+
+### ts is part of the moreutils package
+
+```shell
+sudo apt-get install moreutils
+```
+
+## cheat sheets
+
+*   Show all connected devices
+
+    ```shell
+    $ echo scan | cec-client -s -d 1
+
+    ```
+
+## Available Commands
+
+[tx] \{bytes\}
+:   transfer bytes over the CEC line.
+
+[txn] \{bytes\}
+:   transfer bytes but don't wait for transmission ACK.
+
+[on] \{address\}
+:   power on the device with the given logical address.
+
+[standby] \{address\}
+:   put the device with the given address in standby mode.
+
+[la] \{logical address\}
+:   change the logical address of the CEC adapter.
+
+[p] \{device\} \{port\}
+:   change the HDMI port number of the CEC adapter.
+
+[pa] \{physical address\}
+:   change the physical address of the CEC adapter.
+
+[as]
+:   make the CEC adapter the active source.
+
+[is]
+:   mark the CEC adapter as inactive source.
+
+[osd] \{addr\} \{string\}
+:   set OSD message on the specified device.
+
+[ver] \{addr\}
+:   get the CEC version of the specified device.
+
+[ven] \{addr\}
+:   get the vendor ID of the specified device.
+
+[lang] \{addr\}
+:   get the menu language of the specified device.
+
+[pow] \{addr\}
+:   get the power status of the specified device.
+
+[name] \{addr\}
+:   get the OSD name of the specified device.
+
+[poll] \{addr\}
+:   poll the specified device.
+
+[lad]
+:   lists active devices on the bus
+
+[ad] \{addr\}
+:   checks whether the specified device is active.
+
+[at] \{type\}
+:   checks whether the specified device type is active.
+
+[sp] \{addr\}
+:   makes the specified physical address active.
+
+[spl] \{addr\}
+:   makes the specified logical address active.
+
+[volup]
+:   send a volume up command to the amp if present
+
+[voldown]
+:   send a volume down command to the amp if present
+
+[mute]
+:   send a mute/unmute command to the amp if present
+
+[self]
+:   show the list of addresses controlled by libCEC
+
+[scan]
+:   scan the CEC bus and display device info
+
+## Sending Remote Control Events
+
+You can send CEC remote events using the `tx` command above. The format is as
+follows:
+
+```
+tx <source id><destination id>:<command id>:<param value>
+```
+
+where all of the above parameters should be filled in with a single HEX digit
+(except `<command id>`, which requires 2 digits). Here, we want to send commands
+to the Android TV, so we will place its ID in `<destination id>`. The scan
+command above will give you the IDs for each device that the CEC adapter is
+aware of.
+
+In the examples below the DUT is a CEC player device with a logical address of
+4. Here are some useful commands to execute remote actions:
+
+*   Press home
+
+    ```
+    tx 04:44:09
+    ```
+
+*   Press select
+
+    ```
+    tx 04:44:00
+    ```
+
+*   Press d-pad up
+
+    ```
+    tx 04:44:01
+    ```
+
+*   Press d-pad down
+
+    ```
+    tx 04:44:02
+    ```
+
+*   Press d-pad left
+
+    ```
+    tx 04:44:03
+    ```
+
+*   Press d-pad right
+
+    ```
+    tx 04:44:04
+    ```
+
+You can check out [https://www.cec-o-matic.com/](https]://www.cec-o-matic.com/)
+for more info on formatting your request and a full list of commands and their
+respective parameters.
diff --git a/hostsidetests/hdmicec/setup.png b/hostsidetests/hdmicec/setup.png
new file mode 100644
index 0000000..e89417e
--- /dev/null
+++ b/hostsidetests/hdmicec/setup.png
Binary files differ
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/CecMessage.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/CecMessage.java
index 8d4ece1..bce34e1 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/CecMessage.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/CecMessage.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,56 +16,36 @@
 
 package android.hdmicec.cts;
 
-import java.util.HashMap;
-import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
-public enum CecMessage {
-    FEATURE_ABORT(0x00),
-    TEXT_VIEW_ON(0x0d),
-    SET_MENU_LANGUAGE(0x32),
-    STANDBY(0x36),
-    USER_CONTROL_PRESSED(0x44),
-    USER_CONTROL_RELEASED(0x45),
-    GIVE_OSD_NAME(0x46),
-    SET_OSD_NAME(0x47),
-    SYSTEM_AUDIO_MODE_REQUEST(0x70),
-    SET_SYSTEM_AUDIO_MODE(0x72),
-    GIVE_SYSTEM_AUDIO_MODE_STATUS(0x7d),
-    ACTIVE_SOURCE(0x82),
-    GIVE_PHYSICAL_ADDRESS(0x83),
-    REPORT_PHYSICAL_ADDRESS(0x84),
-    REQUEST_ACTIVE_SOURCE(0x85),
-    SET_STREAM_PATH(0x86),
-    DEVICE_VENDOR_ID(0x87),
-    GIVE_DEVICE_VENDOR_ID(0x8c),
-    GIVE_POWER_STATUS(0x8f),
-    REPORT_POWER_STATUS(0x90),
-    GET_MENU_LANGUAGE(0x91),
-    INACTIVE_SOURCE(0x9d),
-    CEC_VERSION(0x9e),
-    GET_CEC_VERSION(0x9f),
-    ABORT(0xff);
+public class CecMessage {
 
-    private final int messageId;
-    private static Map messageMap = new HashMap<>();
+    private static final int HEXADECIMAL_RADIX = 16;
 
-    static {
-        for (CecMessage message : CecMessage.values()) {
-            messageMap.put(message.messageId, message);
+    /** Gets the hexadecimal ASCII character values of a string. */
+    public static String getHexAsciiString(String string) {
+        String asciiString = "";
+        byte[] ascii = string.trim().getBytes();
+
+        for (byte b : ascii) {
+            asciiString.concat(Integer.toHexString(b));
         }
+
+        return asciiString;
     }
 
-    public static CecMessage getMessage(int messageId) {
-        return (CecMessage) messageMap.get(messageId);
-    }
+    public static String formatParams(String rawParams) {
+        StringBuilder params = new StringBuilder("");
+        int position = 0;
+        int endPosition = 2;
 
-    @Override
-    public String toString() {
-        return String.format("%02x", messageId);
-    }
-
-    private CecMessage(int messageId) {
-        this.messageId = messageId;
+        do {
+            params.append(":" + rawParams.substring(position, endPosition));
+            position = endPosition;
+            endPosition += 2;
+        } while (endPosition <= rawParams.length());
+        return params.toString();
     }
 
     public static String formatParams(long rawParam) {
@@ -78,4 +58,110 @@
 
         return params.toString();
     }
+
+    /**
+     * Formats the rawParam into CEC message parameters. The parameters will be at least
+     * minimumNibbles long.
+     */
+    public static String formatParams(long rawParam, int minimumNibbles) {
+        StringBuilder params = new StringBuilder("");
+
+        do {
+            params.insert(0, ":" + String.format("%02x", rawParam % 256));
+            rawParam >>= 8;
+            minimumNibbles -= 2;
+        } while (rawParam > 0 || minimumNibbles > 0);
+
+        return params.toString();
+    }
+
+    public static int hexStringToInt(String message) {
+        return Integer.parseInt(message, HEXADECIMAL_RADIX);
+    }
+
+    public static String getAsciiString(String message) {
+        String params = getNibbles(message).substring(4);
+        StringBuilder builder = new StringBuilder();
+
+        for (int i = 2; i <= params.length(); i += 2) {
+            builder.append((char) hexStringToInt(params.substring(i - 2, i)));
+        }
+
+        return builder.toString();
+    }
+
+    public static String getParamsAsString(String message) {
+        return getNibbles(message).substring(4);
+    }
+
+    /** Gets the params from a CEC message. */
+    public static int getParams(String message) {
+        return hexStringToInt(getNibbles(message).substring(4));
+    }
+
+    /** Gets the first 'numNibbles' number of param nibbles from a CEC message. */
+    public static int getParams(String message, int numNibbles) {
+        int paramStart = 4;
+        int end = numNibbles + paramStart;
+        return hexStringToInt(getNibbles(message).substring(paramStart, end));
+    }
+
+    /**
+     * From the params of a CEC message, gets the nibbles from position start to position end.
+     * The start and end are relative to the beginning of the params. For example, in the following
+     * message - 4F:82:10:00:04, getParamsFromMessage(message, 0, 4) will return 0x1000 and
+     * getParamsFromMessage(message, 4, 6) will return 0x04.
+     */
+    public static int getParams(String message, int start, int end) {
+        return hexStringToInt(getNibbles(message).substring(4).substring(start, end));
+    }
+
+    /**
+     * Gets the source logical address from a CEC message.
+     */
+    public static LogicalAddress getSource(String message) {
+        String param = getNibbles(message).substring(0, 1);
+        return LogicalAddress.getLogicalAddress(hexStringToInt(param));
+    }
+
+    /** Gets the destination logical address from a CEC message. */
+    public static LogicalAddress getDestination(String message) {
+        String param = getNibbles(message).substring(1, 2);
+        return LogicalAddress.getLogicalAddress(hexStringToInt(param));
+    }
+
+    /** Gets the operand from a CEC message. */
+    public static CecOperand getOperand(String message) {
+        String param = getNibbles(message).substring(2, 4);
+        return CecOperand.getOperand(hexStringToInt(param));
+    }
+
+    /**
+     * Converts ascii characters to hexadecimal numbers that can be appended to a CEC message as
+     * params. For example, "spa" will be converted to ":73:70:61"
+     */
+    public static String convertStringToHexParams(String rawParams) {
+        StringBuilder params = new StringBuilder("");
+        for (int i = 0; i < rawParams.length(); i++) {
+            params.append(String.format(":%02x", (int) rawParams.charAt(i)));
+        }
+        return params.toString();
+    }
+
+    private static String getNibbles(String message) {
+        final String tag1 = "group1";
+        final String tag2 = "group2";
+        String paramsPattern = "(?:.*[>>|<<].*?)" +
+                "(?<" + tag1 + ">[\\p{XDigit}{2}:]+)" +
+                "(?<" + tag2 + ">\\p{XDigit}{2})" +
+                "(?:.*?)";
+        String nibbles = "";
+
+        Pattern p = Pattern.compile(paramsPattern);
+        Matcher m = p.matcher(message);
+        if (m.matches()) {
+            nibbles = m.group(tag1).replace(":", "") + m.group(tag2);
+        }
+        return nibbles;
+    }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/CecOperand.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/CecOperand.java
new file mode 100644
index 0000000..5aa547e
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/CecOperand.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hdmicec.cts;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum CecOperand {
+    FEATURE_ABORT(0x00),
+    TEXT_VIEW_ON(0x0d),
+    SET_MENU_LANGUAGE(0x32),
+    STANDBY(0x36),
+    USER_CONTROL_PRESSED(0x44),
+    USER_CONTROL_RELEASED(0x45),
+    GIVE_OSD_NAME(0x46),
+    SET_OSD_NAME(0x47),
+    SYSTEM_AUDIO_MODE_REQUEST(0x70),
+    GIVE_AUDIO_STATUS(0x71),
+    SET_SYSTEM_AUDIO_MODE(0x72),
+    REPORT_AUDIO_STATUS(0x7a),
+    GIVE_SYSTEM_AUDIO_MODE_STATUS(0x7d),
+    SYSTEM_AUDIO_MODE_STATUS(0x7e),
+    ACTIVE_SOURCE(0x82),
+    GIVE_PHYSICAL_ADDRESS(0x83),
+    REPORT_PHYSICAL_ADDRESS(0x84),
+    REQUEST_ACTIVE_SOURCE(0x85),
+    SET_STREAM_PATH(0x86),
+    DEVICE_VENDOR_ID(0x87),
+    VENDOR_COMMAND(0x89),
+    GIVE_DEVICE_VENDOR_ID(0x8c),
+    GIVE_POWER_STATUS(0x8f),
+    REPORT_POWER_STATUS(0x90),
+    GET_MENU_LANGUAGE(0x91),
+    INACTIVE_SOURCE(0x9d),
+    CEC_VERSION(0x9e),
+    GET_CEC_VERSION(0x9f),
+    REPORT_SHORT_AUDIO_DESCRIPTOR(0xa3),
+    REQUEST_SHORT_AUDIO_DESCRIPTOR(0xa4),
+    INITIATE_ARC(0xc0),
+    ARC_INITIATED(0xc1),
+    REQUEST_ARC_INITIATION(0xc3),
+    REQUEST_ARC_TERMINATION(0xc4),
+    TERMINATE_ARC(0xc5),
+    ABORT(0xff);
+
+    private final int operandCode;
+    private static Map operandMap = new HashMap<>();
+
+    static {
+        for (CecOperand operand : CecOperand.values()) {
+            operandMap.put(operand.operandCode, operand);
+        }
+    }
+
+    public static CecOperand getOperand(int messageId) {
+        return (CecOperand) operandMap.get(messageId);
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%02x", operandCode);
+    }
+
+    private CecOperand(int operandCode) {
+        this.operandCode = operandCode;
+    }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecClientWrapper.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecClientWrapper.java
index f9b1899..51da280 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecClientWrapper.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecClientWrapper.java
@@ -16,10 +16,6 @@
 
 package android.hdmicec.cts;
 
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assume.assumeTrue;
-
-import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 import com.android.tradefed.util.RunUtil;
@@ -32,10 +28,8 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import org.junit.Rule;
 import org.junit.rules.ExternalResource;
 
 /** Class that helps communicate with the cec-client */
@@ -44,8 +38,6 @@
     private static final String CEC_CONSOLE_READY = "waiting for input";
     private static final int MILLISECONDS_TO_READY = 10000;
     private static final int DEFAULT_TIMEOUT = 20000;
-    private static final String HDMI_CEC_FEATURE = "feature:android.hardware.hdmi.cec";
-    private static final int HEXADECIMAL_RADIX = 16;
     private static final int BUFFER_SIZE = 1024;
 
     private Process mCecClient;
@@ -53,29 +45,16 @@
     private BufferedReader mInputConsole;
     private boolean mCecClientInitialised = false;
 
-    private CecDevice targetDevice;
-    private BaseHostJUnit4Test testObject;
+    private LogicalAddress targetDevice;
     private String clientParams[];
 
-    public HdmiCecClientWrapper(CecDevice targetDevice, BaseHostJUnit4Test testObject,
-            String ...clientParams) {
+    public HdmiCecClientWrapper(LogicalAddress targetDevice, String ...clientParams) {
         this.targetDevice = targetDevice;
-        this.testObject = testObject;
         this.clientParams = clientParams;
     }
 
     @Override
     protected void before() throws Throwable {
-        ITestDevice testDevice;
-        testDevice = testObject.getDevice();
-        assertNotNull("Device not set", testDevice);
-
-        assumeTrue(isHdmiCecFeatureSupported(testDevice));
-
-        String deviceTypeCsv = testDevice.executeShellCommand("getprop ro.hdmi.device_type").trim();
-        List<String> deviceType = Arrays.asList(deviceTypeCsv.replaceAll("\\s+", "").split(","));
-        assumeTrue(deviceType.contains(CecDevice.getDeviceType(targetDevice)));
-
         this.init();
     };
 
@@ -84,24 +63,18 @@
         this.killCecProcess();
     };
 
-    /**
-     * Checks if the HDMI CEC feature is running on the device. Call this function before running
-     * any HDMI CEC tests.
-     * This could throw a DeviceNotAvailableException.
-     */
-    private static boolean isHdmiCecFeatureSupported(ITestDevice device) throws Exception {
-        return device.hasFeature(HDMI_CEC_FEATURE);
-    }
-
     /** Initialise the client */
     private void init() throws Exception {
-        boolean gotExpectedOut = false;
         List<String> commands = new ArrayList();
-        int seconds = 0;
 
         commands.add("cec-client");
+        /* "-p 2" starts the client as if it is connected to HDMI port 2, taking the physical
+         * address 2.0.0.0 */
         commands.add("-p");
         commands.add("2");
+        /* "-t x" starts the client as a TV device */
+        commands.add("-t");
+        commands.add("x");
         commands.addAll(Arrays.asList(clientParams));
 
         mCecClient = RunUtil.getDefault().runCmdInBackground(commands);
@@ -133,15 +106,15 @@
      * Sends a CEC message with source marked as broadcast to the device passed in the constructor
      * through the output console of the cec-communication channel.
      */
-    public void sendCecMessage(CecMessage message) throws Exception {
-        sendCecMessage(CecDevice.BROADCAST, targetDevice, message, "");
+    public void sendCecMessage(CecOperand message) throws Exception {
+        sendCecMessage(LogicalAddress.BROADCAST, targetDevice, message, "");
     }
 
     /**
      * Sends a CEC message from source device to the device passed in the constructor through the
      * output console of the cec-communication channel.
      */
-    public void sendCecMessage(CecDevice source, CecMessage message) throws Exception {
+    public void sendCecMessage(LogicalAddress source, CecOperand message) throws Exception {
         sendCecMessage(source, targetDevice, message, "");
     }
 
@@ -149,8 +122,8 @@
      * Sends a CEC message from source device to a destination device through the output console of
      * the cec-communication channel.
      */
-    public void sendCecMessage(CecDevice source, CecDevice destination,
-        CecMessage message) throws Exception {
+    public void sendCecMessage(LogicalAddress source, LogicalAddress destination,
+        CecOperand message) throws Exception {
         sendCecMessage(source, destination, message, "");
     }
 
@@ -158,10 +131,11 @@
      * Sends a CEC message from source device to a destination device through the output console of
      * the cec-communication channel with the appended params.
      */
-    public void sendCecMessage(CecDevice source, CecDevice destination,
-            CecMessage message, String params) throws Exception {
+    public void sendCecMessage(LogicalAddress source, LogicalAddress destination,
+            CecOperand message, String params) throws Exception {
         checkCecClient();
         mOutputConsole.write("tx " + source + destination + ":" + message + params);
+        mOutputConsole.newLine();
         mOutputConsole.flush();
     }
 
@@ -169,13 +143,13 @@
      * Sends a <USER_CONTROL_PRESSED> and <USER_CONTROL_RELEASED> from source to destination
      * through the output console of the cec-communication channel with the mentioned keycode.
      */
-    public void sendUserControlPressAndRelease(CecDevice source, CecDevice destination,
+    public void sendUserControlPressAndRelease(LogicalAddress source, LogicalAddress destination,
             int keycode, boolean holdKey) throws Exception {
         sendUserControlPress(source, destination, keycode, holdKey);
         /* Sleep less than 200ms between press and release */
         TimeUnit.MILLISECONDS.sleep(100);
         mOutputConsole.write("tx " + source + destination + ":" +
-                              CecMessage.USER_CONTROL_RELEASED);
+                              CecOperand.USER_CONTROL_RELEASED);
         mOutputConsole.flush();
     }
 
@@ -184,11 +158,11 @@
      * cec-communication channel with the mentioned keycode. If holdKey is true, the method will
      * send multiple <UCP> messages to simulate a long press. No <UCR> will be sent.
      */
-    public void sendUserControlPress(CecDevice source, CecDevice destination,
+    public void sendUserControlPress(LogicalAddress source, LogicalAddress destination,
             int keycode, boolean holdKey) throws Exception {
         String key = String.format("%02x", keycode);
         String command = "tx " + source + destination + ":" +
-                CecMessage.USER_CONTROL_PRESSED + ":" + key;
+                CecOperand.USER_CONTROL_PRESSED + ":" + key;
 
         if (holdKey) {
             /* Repeat once between 200ms and 450ms for at least 5 seconds. Since message will be
@@ -212,7 +186,8 @@
      * of the cec-communication channel immediately followed by <UCP> [secondKeycode]. No <UCR>
      *  message is sent.
      */
-    public void sendUserControlInterruptedPressAndHold(CecDevice source, CecDevice destination,
+    public void sendUserControlInterruptedPressAndHold(
+        LogicalAddress source, LogicalAddress destination,
             int firstKeycode, int secondKeycode, boolean holdKey) throws Exception {
         sendUserControlPress(source, destination, firstKeycode, holdKey);
         /* Sleep less than 200ms between press and release */
@@ -253,13 +228,40 @@
         return false;
     }
 
+    /** Gets all the messages received from the given source device during a period of duration
+     * seconds.
+     */
+    public List<CecOperand> getAllMessages(LogicalAddress source, int duration) throws Exception {
+        List<CecOperand> receivedOperands = new ArrayList<>();
+        long startTime = System.currentTimeMillis();
+        long endTime = startTime;
+        Pattern pattern = Pattern.compile("(.*>>)(.*?)" +
+                "(" + source + "\\p{XDigit}):(.*)",
+            Pattern.CASE_INSENSITIVE);
+
+        while ((endTime - startTime <= duration)) {
+            if (mInputConsole.ready()) {
+                String line = mInputConsole.readLine();
+                if (pattern.matcher(line).matches()) {
+                    CecOperand operand = CecMessage.getOperand(line);
+                    if (!receivedOperands.contains(operand)) {
+                        receivedOperands.add(operand);
+                    }
+                }
+            }
+            endTime = System.currentTimeMillis();
+        }
+        return receivedOperands;
+    }
+
+
     /**
      * Looks for the CEC expectedMessage broadcast on the cec-client communication channel and
      * returns the first line that contains that message within default timeout. If the CEC message
      * is not found within the timeout, an exception is thrown.
      */
-    public String checkExpectedOutput(CecMessage expectedMessage) throws Exception {
-        return checkExpectedOutput(CecDevice.BROADCAST, expectedMessage, DEFAULT_TIMEOUT);
+    public String checkExpectedOutput(CecOperand expectedMessage) throws Exception {
+        return checkExpectedOutput(LogicalAddress.BROADCAST, expectedMessage, DEFAULT_TIMEOUT);
     }
 
     /**
@@ -267,8 +269,8 @@
      * communication channel and returns the first line that contains that message within
      * default timeout. If the CEC message is not found within the timeout, an exception is thrown.
      */
-    public String checkExpectedOutput(CecDevice toDevice,
-                                      CecMessage expectedMessage) throws Exception {
+    public String checkExpectedOutput(LogicalAddress toDevice,
+                                      CecOperand expectedMessage) throws Exception {
         return checkExpectedOutput(toDevice, expectedMessage, DEFAULT_TIMEOUT);
     }
 
@@ -277,9 +279,9 @@
      * returns the first line that contains that message within timeoutMillis. If the CEC message
      * is not found within the timeout, an exception is thrown.
      */
-    public String checkExpectedOutput(CecMessage expectedMessage,
+    public String checkExpectedOutput(CecOperand expectedMessage,
                                       long timeoutMillis) throws Exception {
-        return checkExpectedOutput(CecDevice.BROADCAST, expectedMessage, timeoutMillis);
+        return checkExpectedOutput(LogicalAddress.BROADCAST, expectedMessage, timeoutMillis);
     }
 
     /**
@@ -287,7 +289,7 @@
      * communication channel and returns the first line that contains that message within
      * timeoutMillis. If the CEC message is not found within the timeout, an exception is thrown.
      */
-    public String checkExpectedOutput(CecDevice toDevice, CecMessage expectedMessage,
+    public String checkExpectedOutput(LogicalAddress toDevice, CecOperand expectedMessage,
                                        long timeoutMillis) throws Exception {
         checkCecClient();
         long startTime = System.currentTimeMillis();
@@ -316,8 +318,8 @@
      * within the default timeout. If the CEC message is not found within the timeout, function
      * returns without error.
      */
-    public void checkOutputDoesNotContainMessage(CecDevice toDevice,
-            CecMessage incorrectMessage) throws Exception {
+    public void checkOutputDoesNotContainMessage(LogicalAddress toDevice,
+            CecOperand incorrectMessage) throws Exception {
         checkOutputDoesNotContainMessage(toDevice, incorrectMessage, DEFAULT_TIMEOUT);
      }
 
@@ -327,7 +329,7 @@
      * within timeoutMillis. If the CEC message is not found within the timeout, function returns
      * without error.
      */
-    public void checkOutputDoesNotContainMessage(CecDevice toDevice, CecMessage incorrectMessage,
+    public void checkOutputDoesNotContainMessage(LogicalAddress toDevice, CecOperand incorrectMessage,
             long timeoutMillis) throws Exception {
 
         checkCecClient();
@@ -344,146 +346,13 @@
                 if (pattern.matcher(line).matches()) {
                     CLog.v("Found " + incorrectMessage.name() + " in " + line);
                     throw new Exception("Found " + incorrectMessage.name() + " to " + toDevice +
-                            " with params " + getParamsFromMessage(line));
+                            " with params " + CecMessage.getParamsAsString(line));
                 }
             }
             endTime = System.currentTimeMillis();
         }
      }
 
-    /** Gets the hexadecimal ASCII character values of a string. */
-    public String getHexAsciiString(String string) {
-        String asciiString = "";
-        byte[] ascii = string.trim().getBytes();
-
-        for (byte b : ascii) {
-            asciiString.concat(Integer.toHexString(b));
-        }
-
-        return asciiString;
-    }
-
-    public String formatParams(String rawParams) {
-        StringBuilder params = new StringBuilder("");
-        int position = 0;
-        int endPosition = 2;
-
-        do {
-            params.append(":" + rawParams.substring(position, endPosition));
-            position = endPosition;
-            endPosition += 2;
-        } while (endPosition <= rawParams.length());
-        return params.toString();
-    }
-
-    public String formatParams(long rawParam) {
-        StringBuilder params = new StringBuilder("");
-
-        do {
-            params.insert(0, ":" + String.format("%02x", rawParam % 256));
-            rawParam >>= 8;
-        } while (rawParam > 0);
-
-        return params.toString();
-    }
-
-    /** Formats a CEC message in the hex colon format (sd:op:xx:xx). */
-    public String formatMessage(CecDevice source, CecDevice destination, CecMessage message,
-            int params) {
-        StringBuilder cecMessage = new StringBuilder("" + source + destination + ":" + message);
-
-        cecMessage.append(formatParams(params));
-
-        return cecMessage.toString();
-    }
-
-    public static int hexStringToInt(String message) {
-        return Integer.parseInt(message, HEXADECIMAL_RADIX);
-    }
-
-    public String getAsciiStringFromMessage(String message) {
-        String params = getNibbles(message).substring(4);
-        StringBuilder builder = new StringBuilder();
-
-        for (int i = 2; i <= params.length(); i += 2) {
-            builder.append((char) hexStringToInt(params.substring(i - 2, i)));
-        }
-
-        return builder.toString();
-    }
-
-    /**
-     * Gets the params from a CEC message.
-     */
-    public int getParamsFromMessage(String message) {
-        return hexStringToInt(getNibbles(message).substring(4));
-    }
-
-    /**
-     * Gets the first 'numNibbles' number of param nibbles from a CEC message.
-     */
-    public int getParamsFromMessage(String message, int numNibbles) {
-        int paramStart = 4;
-        int end = numNibbles + paramStart;
-        return hexStringToInt(getNibbles(message).substring(paramStart, end));
-    }
-
-    /**
-     * From the params of a CEC message, gets the nibbles from position start to position end.
-     * The start and end are relative to the beginning of the params. For example, in the following
-     * message - 4F:82:10:00:04, getParamsFromMessage(message, 0, 4) will return 0x1000 and
-     * getParamsFromMessage(message, 4, 6) will return 0x04.
-     */
-    public int getParamsFromMessage(String message, int start, int end) {
-        return hexStringToInt(getNibbles(message).substring(4).substring(start, end));
-    }
-
-    /**
-     * Gets the source logical address from a CEC message.
-     */
-    public CecDevice getSourceFromMessage(String message) {
-        String param = getNibbles(message).substring(0, 1);
-        return CecDevice.getDevice(hexStringToInt(param));
-    }
-
-    /**
-     * Converts ascii characters to hexadecimal numbers that can be appended to a CEC message as
-     * params. For example, "spa" will be converted to ":73:70:61"
-     */
-    public static String convertStringToHexParams(String rawParams) {
-        StringBuilder params = new StringBuilder("");
-        for (int i = 0; i < rawParams.length(); i++) {
-            params.append(String.format(":%02x", (int) rawParams.charAt(i)));
-        }
-        return params.toString();
-    }
-
-
-    /**
-     * Gets the destination logical address from a CEC message.
-     */
-    public CecDevice getDestinationFromMessage(String message) {
-        String param = getNibbles(message).substring(1, 2);
-        return CecDevice.getDevice(hexStringToInt(param));
-    }
-
-    private String getNibbles(String message) {
-        final String tag1 = "group1";
-        final String tag2 = "group2";
-        String paramsPattern = "(?:.*[>>|<<].*?)" +
-                               "(?<" + tag1 + ">[\\p{XDigit}{2}:]+)" +
-                               "(?<" + tag2 + ">\\p{XDigit}{2})" +
-                               "(?:.*?)";
-        String nibbles = "";
-
-        Pattern p = Pattern.compile(paramsPattern);
-        Matcher m = p.matcher(message);
-        if (m.matches()) {
-            nibbles = m.group(tag1).replace(":", "") + m.group(tag2);
-        }
-        return nibbles;
-    }
-
     /**
      * Kills the cec-client process that was created in init().
      */
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecConstants.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecConstants.java
index 0565eae..8af4a8b 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecConstants.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecConstants.java
@@ -47,4 +47,11 @@
     public static final int CEC_DEVICE_TYPE_PLAYBACK_DEVICE = 4;
     public static final int CEC_DEVICE_TYPE_AUDIO_SYSTEM = 5;
 
+    /** Feature Abort Reasons */
+    public static final int ABORT_UNRECOGNIZED_MODE = 0;
+    public static final int ABORT_NOT_IN_CORRECT_MODE = 1;
+    public static final int ABORT_CANNOT_PROVIDE_SOURCE = 2;
+    public static final int ABORT_INVALID_OPERAND = 3;
+    public static final int ABORT_REFUSED = 4;
+    public static final int ABORT_UNABLE_TO_DETERMINE = 5;
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/LogHelper.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/LogHelper.java
new file mode 100644
index 0000000..a7692f38
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/LogHelper.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hdmicec.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tradefed.device.ITestDevice;
+
+import com.google.common.collect.Lists;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Scanner;
+import java.util.concurrent.TimeUnit;
+
+/** Helper class to get for device logcat. */
+public final class LogHelper {
+
+    private static final int WAIT_TIME = 10;
+
+    /**
+     * The tag of the SadConfigurationReaderTest launched by the APK.
+     */
+    private static final String SAD_READER = "SadConfigurationReaderTest";
+
+    private static final String SAD_CONFIGURATION_MARKER = "Supported Audio Formats";
+
+    private static String getLog(ITestDevice device, String tag) throws Exception {
+        TimeUnit.SECONDS.sleep(WAIT_TIME);
+        String logs = device.executeAdbCommand("logcat", "-v", "brief", "-d", tag + ":I", "*:S");
+        // Search for string.
+        String testString = "";
+        Scanner in = new Scanner(logs);
+        while (in.hasNextLine()) {
+            String line = in.nextLine();
+            if (line.startsWith("I/" + tag)) {
+                testString = line.split(":")[1].trim();
+            }
+        }
+        device.executeAdbCommand("logcat", "-c");
+        return testString;
+    }
+
+    public static void assertLog(ITestDevice device, String tag, String ...expectedOutput)
+            throws Exception {
+        String testString = getLog(device, tag);
+        List<String> expectedOutputs = new ArrayList<>(Arrays.asList(expectedOutput));
+        assertThat(testString).isIn(expectedOutputs);
+    }
+
+    public static void assertLogDoesNotContain(ITestDevice device, String tag,
+                                               String expectedOutput) throws Exception {
+        String testString = getLog(device, tag);
+        assertThat(testString).doesNotContain(expectedOutput);
+    }
+
+    public static List<Integer> getSupportedAudioFormats(ITestDevice device) throws Exception {
+        TimeUnit.SECONDS.sleep(WAIT_TIME);
+        String logs =
+                device.executeAdbCommand("logcat", "-v", "brief", "-d", SAD_READER + ":I", "*:S");
+        // Search for string.
+        String testString = "";
+        Scanner in = new Scanner(logs);
+        List<Integer> mSupportedAudioFormats = null;
+        while (in.hasNextLine()) {
+            String line = in.nextLine();
+            if (line.startsWith("I/" + SAD_READER)) {
+                testString = line.split(":")[1].trim();
+                if (testString.equals(SAD_CONFIGURATION_MARKER)) {
+                    List<String> mFormatsLine =
+                            Arrays.asList(in.nextLine().split(":")[1].trim().split(", "));
+                    List<String> mCodecSADsLine =
+                            Arrays.asList(in.nextLine().split(":")[1].trim().split(", "));
+                    mSupportedAudioFormats =
+                            Lists.transform(mFormatsLine, fl -> Integer.parseInt(fl));
+                }
+            }
+        }
+        device.executeAdbCommand("logcat", "-c");
+        assumeTrue(testString.equals(SAD_CONFIGURATION_MARKER));
+        return mSupportedAudioFormats;
+    }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/LogicalAddress.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/LogicalAddress.java
new file mode 100644
index 0000000..5bcf5d1
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/LogicalAddress.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hdmicec.cts;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum LogicalAddress {
+    TV(0x0),
+    RECORDER_1(0x1),
+    RECORDER_2(0x2),
+    TUNER_1(0x3),
+    PLAYBACK_1(0x4),
+    AUDIO_SYSTEM(0x5),
+    TUNER_2(0x6),
+    TUNER_3(0x7),
+    PLAYBACK_2(0x8),
+    RECORDER_3(0x9),
+    TUNER_4(0xa),
+    PLAYBACK_3(0xb),
+    RESERVED_1(0xc),
+    RESERVED_2(0xd),
+    SPECIFIC_USE(0xe),
+    BROADCAST(0xf);
+
+    private final int address;
+    private static Map deviceMap = new HashMap<>();
+
+    // CEC Device feature list
+    public static final String HDMI_CEC_FEATURE = "feature:android.hardware.hdmi.cec";
+    public static final String LEANBACK_FEATURE = "feature:android.software.leanback";
+
+    // CEC Device property list
+    public static final String HDMI_DEVICE_TYPE_PROPERTY = "ro.hdmi.device_type";
+
+    @Override
+    public String toString() {
+        return Integer.toHexString(this.address);
+    }
+
+    static {
+        for (LogicalAddress device : LogicalAddress.values()) {
+            deviceMap.put(device.address, device);
+        }
+    }
+
+    public String getDeviceType() {
+        switch (this) {
+            case PLAYBACK_1:
+            case PLAYBACK_2:
+            case PLAYBACK_3:
+                return Integer.toString(HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE);
+            case TV:
+                return Integer.toString(HdmiCecConstants.CEC_DEVICE_TYPE_TV);
+            case AUDIO_SYSTEM:
+                return Integer.toString(HdmiCecConstants.CEC_DEVICE_TYPE_AUDIO_SYSTEM);
+            case RECORDER_1:
+            case RECORDER_2:
+            case RECORDER_3:
+                return Integer.toString(HdmiCecConstants.CEC_DEVICE_TYPE_RECORDER);
+            case TUNER_1:
+            case TUNER_2:
+            case TUNER_3:
+            case TUNER_4:
+                return Integer.toString(HdmiCecConstants.CEC_DEVICE_TYPE_TUNER);
+            default:
+                return Integer.toString(HdmiCecConstants.CEC_DEVICE_TYPE_RESERVED);
+        }
+    }
+
+    public static LogicalAddress getLogicalAddress(int address) {
+        return (LogicalAddress) deviceMap.get(address);
+    }
+
+    private LogicalAddress(int address) {
+        this.address = address;
+    }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/RequiredFeatureRule.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/RequiredFeatureRule.java
new file mode 100644
index 0000000..8fb70ec
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/RequiredFeatureRule.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hdmicec.cts;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * Rule to check if the required device feature is available on device.
+ */
+public class RequiredFeatureRule implements TestRule {
+
+    private final BaseHostJUnit4Test mTest;
+    private final String mFeature;
+
+    public RequiredFeatureRule(BaseHostJUnit4Test test, String feature) {
+        mTest = test;
+        mFeature = feature;
+    }
+
+    @Override
+    public Statement apply(final Statement base, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                ITestDevice testDevice = mTest.getDevice();
+                // Checks if the device is available.
+                assumeTrue("Test device is not available", testDevice != null);
+                // Checks if the requested feature is available on the device.
+                assumeTrue(mFeature + " not present in DUT " + testDevice.getSerialNumber(),
+                    testDevice.hasFeature(mFeature));
+                base.evaluate();
+            }
+        };
+    }
+}
+
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/RequiredPropertyRule.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/RequiredPropertyRule.java
new file mode 100644
index 0000000..f3998adb
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/RequiredPropertyRule.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hdmicec.cts;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * Rule class that allows for checking device property value against required values.
+ * Static functions in this class can be used to check properties as a list or single value.
+ */
+public class RequiredPropertyRule implements TestRule {
+
+    // Do not allow instantiation.
+    private RequiredPropertyRule(){}
+
+    @Override
+    public Statement apply(final Statement base, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                base.evaluate();
+            }
+        };
+    }
+
+    private static String getDevicePropertyValue(BaseHostJUnit4Test test, String propertyName)
+        throws Throwable {
+        ITestDevice testDevice = test.getDevice();
+        // Checks if the device is available.
+        assumeTrue("Test device is not available", testDevice != null);
+        return testDevice.executeShellCommand("getprop " + propertyName).trim();
+
+    }
+
+    public static RequiredPropertyRule isEqualTo(final BaseHostJUnit4Test test,
+        final String propertyName, final String propertyValue) {
+        return new RequiredPropertyRule() {
+            @Override
+            public Statement apply(final Statement base, final Description description) {
+                return new Statement() {
+                    @Override
+                    public void evaluate() throws Throwable {
+                        String deviceProperty = getDevicePropertyValue(test, propertyName);
+                        assumeTrue("Required property " + propertyName + " = " + propertyValue
+                                + " is not present in " + deviceProperty
+                                + " of device " + test.getDevice().getSerialNumber(),
+                            deviceProperty.equals(propertyValue));
+                        base.evaluate();
+                    }
+                };
+            }
+        };
+    }
+
+    public static RequiredPropertyRule asCsvContainsValue(final BaseHostJUnit4Test test,
+        final String propertyName, final String propertyValue) {
+        return new RequiredPropertyRule() {
+            @Override
+            public Statement apply(final Statement base, final Description description) {
+                return new Statement() {
+                    @Override
+                    public void evaluate() throws Throwable {
+                        List<String> deviceProperties =
+                            Arrays.asList(getDevicePropertyValue(test, propertyName)
+                                .replaceAll("\\s+", "")
+                                .split(","));
+                        assumeTrue(
+                            "Required property " + propertyName + " = " + propertyValue
+                                + " is not present in " + deviceProperties.toString()
+                                + " of device " + test.getDevice().getSerialNumber(),
+                            deviceProperties.contains(propertyValue));
+                        base.evaluate();
+                    }
+                };
+            }
+        };
+    }
+}
+
+
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecAudioReturnChannelControlTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecAudioReturnChannelControlTest.java
new file mode 100644
index 0000000..897a35e
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecAudioReturnChannelControlTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hdmicec.cts.audio;
+
+import static org.junit.Assume.assumeNoException;
+
+import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
+import android.hdmicec.cts.HdmiCecClientWrapper;
+import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogicalAddress;
+import android.hdmicec.cts.RequiredPropertyRule;
+import android.hdmicec.cts.RequiredFeatureRule;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+/** HDMI CEC test to test audio return channel control (Section 11.2.17) */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class HdmiCecAudioReturnChannelControlTest extends BaseHostJUnit4Test {
+
+    private static final LogicalAddress AUDIO_DEVICE = LogicalAddress.AUDIO_SYSTEM;
+
+    public HdmiCecClientWrapper hdmiCecClient = new HdmiCecClientWrapper(AUDIO_DEVICE);
+
+    @Rule
+    public RuleChain ruleChain =
+        RuleChain
+            .outerRule(new RequiredFeatureRule(this, LogicalAddress.HDMI_CEC_FEATURE))
+            .around(new RequiredFeatureRule(this, LogicalAddress.LEANBACK_FEATURE))
+            .around(RequiredPropertyRule.asCsvContainsValue(
+                this,
+                LogicalAddress.HDMI_DEVICE_TYPE_PROPERTY,
+                AUDIO_DEVICE.getDeviceType()))
+            .around(hdmiCecClient);
+
+    private void checkArcIsInitiated(){
+        try {
+            hdmiCecClient.sendCecMessage(LogicalAddress.TV, AUDIO_DEVICE,
+                    CecOperand.REQUEST_ARC_INITIATION);
+            hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.INITIATE_ARC);
+        } catch(Exception e) {
+            assumeNoException(e);
+        }
+    }
+
+    /**
+     * Test 11.2.17-1
+     * Tests that the device sends a directly addressed <Initiate ARC> message
+     * when it wants to initiate ARC.
+     */
+    @Test
+    public void cect_11_2_17_1_InitiateArc() throws Exception {
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, LogicalAddress.BROADCAST,
+                CecOperand.REPORT_PHYSICAL_ADDRESS,
+                CecMessage.formatParams(HdmiCecConstants.TV_PHYSICAL_ADDRESS,
+                HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH));
+        getDevice().executeShellCommand("reboot");
+        getDevice().waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
+        hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.INITIATE_ARC);
+    }
+
+    /**
+     * Test 11.2.17-2
+     * Tests that the device sends a directly addressed <Terminate ARC> message
+     * when it wants to terminate ARC.
+     */
+    @Test
+    public void cect_11_2_17_2_TerminateArc() throws Exception {
+        checkArcIsInitiated();
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, LogicalAddress.BROADCAST,
+                CecOperand.REPORT_PHYSICAL_ADDRESS,
+                CecMessage.formatParams(HdmiCecConstants.TV_PHYSICAL_ADDRESS,
+                        HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH));
+        getDevice().executeShellCommand("input keyevent KEYCODE_SLEEP");
+        try {
+            hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.TERMINATE_ARC);
+        } finally {
+            getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        }
+    }
+
+    /**
+     * Test 11.2.17-3
+     * Tests that the device sends a directly addressed <Initiate ARC>
+     * message when it is requested to initiate ARC.
+     */
+    @Test
+    public void cect_11_2_17_3_RequestToInitiateArc() throws Exception {
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, LogicalAddress.BROADCAST,
+                CecOperand.REPORT_PHYSICAL_ADDRESS,
+                CecMessage.formatParams(HdmiCecConstants.TV_PHYSICAL_ADDRESS,
+                HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH));
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, AUDIO_DEVICE,
+                CecOperand.REQUEST_ARC_INITIATION);
+        hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.INITIATE_ARC);
+    }
+
+    /**
+     * Test 11.2.17-4
+     * Tests that the device sends a directly addressed <Terminate ARC> message
+     * when it is requested to terminate ARC.
+     */
+    @Test
+    public void cect_11_2_17_4_RequestToTerminateArc() throws Exception {
+        checkArcIsInitiated();
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, LogicalAddress.BROADCAST,
+                CecOperand.REPORT_PHYSICAL_ADDRESS,
+                CecMessage.formatParams(HdmiCecConstants.TV_PHYSICAL_ADDRESS,
+                        HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH));
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, AUDIO_DEVICE,
+                CecOperand.REQUEST_ARC_TERMINATION);
+        hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.TERMINATE_ARC);
+    }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecInvalidMessagesTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecInvalidMessagesTest.java
new file mode 100644
index 0000000..a77573e
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecInvalidMessagesTest.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hdmicec.cts.audio;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeNoException;
+import static org.junit.Assume.assumeTrue;
+
+import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
+import android.hdmicec.cts.HdmiCecClientWrapper;
+import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogHelper;
+import android.hdmicec.cts.LogicalAddress;
+import android.hdmicec.cts.RequiredPropertyRule;
+import android.hdmicec.cts.RequiredFeatureRule;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+/** HDMI CEC test to verify that device ignores invalid messages (Section 12) */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class HdmiCecInvalidMessagesTest extends BaseHostJUnit4Test {
+
+    private static final LogicalAddress AUDIO_DEVICE = LogicalAddress.AUDIO_SYSTEM;
+    private static final String PROPERTY_LOCALE = "persist.sys.locale";
+
+    /** The package name of the APK. */
+    private static final String PACKAGE = "android.hdmicec.app";
+
+    /** The class name of the main activity in the APK. */
+    private static final String CLASS = "HdmiCecKeyEventCapture";
+
+    /** The command to launch the main activity. */
+    private static final String START_COMMAND = String.format(
+            "am start -W -a android.intent.action.MAIN -n %s/%s.%s", PACKAGE, PACKAGE, CLASS);
+
+    /** The command to clear the main activity. */
+    private static final String CLEAR_COMMAND = String.format("pm clear %s", PACKAGE);
+
+    public HdmiCecClientWrapper hdmiCecClient = new HdmiCecClientWrapper(AUDIO_DEVICE);
+
+    @Rule
+    public RuleChain ruleChain =
+        RuleChain
+            .outerRule(new RequiredFeatureRule(this, LogicalAddress.HDMI_CEC_FEATURE))
+            .around(new RequiredFeatureRule(this, LogicalAddress.LEANBACK_FEATURE))
+            .around(RequiredPropertyRule.asCsvContainsValue(
+                this,
+                LogicalAddress.HDMI_DEVICE_TYPE_PROPERTY,
+                AUDIO_DEVICE.getDeviceType()))
+            .around(hdmiCecClient);
+
+    private String getSystemLocale() throws Exception {
+        ITestDevice device = getDevice();
+        return device.executeShellCommand("getprop " + PROPERTY_LOCALE).trim();
+    }
+
+    private void setSystemLocale(String locale) throws Exception {
+        ITestDevice device = getDevice();
+        device.executeShellCommand("setprop " + PROPERTY_LOCALE + " " + locale);
+    }
+
+    private boolean isLanguageEditable() throws Exception {
+        String val = getDevice().executeShellCommand("getprop ro.hdmi.set_menu_language");
+        return val.trim().equals("true") ? true : false;
+    }
+
+    private static String extractLanguage(String locale) {
+        return locale.split("[^a-zA-Z]")[0];
+    }
+
+    private void checkArcIsInitiated(){
+        try {
+            hdmiCecClient.sendCecMessage(LogicalAddress.TV, AUDIO_DEVICE,
+                CecOperand.REQUEST_ARC_INITIATION);
+            hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.INITIATE_ARC);
+        } catch(Exception e) {
+            assumeNoException(e);
+        }
+    }
+
+    /**
+     * Test 12-1
+     * Tests that the device ignores every broadcast only message that is received as
+     * directly addressed.
+     */
+    @Test
+    public void cect_12_1_BroadcastReceivedAsDirectlyAddressed() throws Exception {
+        /* <Set Menu Language> */
+        assumeTrue(isLanguageEditable());
+        final String locale = getSystemLocale();
+        final String originalLanguage = extractLanguage(locale);
+        final String language = originalLanguage.equals("spa") ? "eng" : "spa";
+        try {
+            hdmiCecClient.sendCecMessage(
+                    LogicalAddress.TV,
+                    AUDIO_DEVICE,
+                    CecOperand.SET_MENU_LANGUAGE,
+                    CecMessage.convertStringToHexParams(language));
+            assertThat(originalLanguage).isEqualTo(extractLanguage(getSystemLocale()));
+        } finally {
+            // If the language was incorrectly changed during the test, restore it.
+            setSystemLocale(locale);
+        }
+    }
+
+    /**
+     * Test 12-2
+     * Tests that the device ignores directly addressed message <GET_CEC_VERSION> if received as
+     * a broadcast message
+     */
+    @Test
+    public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_getCecVersion() throws Exception {
+        hdmiCecClient.sendCecMessage(
+                LogicalAddress.TV,
+                LogicalAddress.BROADCAST,
+                CecOperand.GET_CEC_VERSION);
+        hdmiCecClient.checkOutputDoesNotContainMessage(
+                LogicalAddress.TV,
+                CecOperand.CEC_VERSION);
+    }
+
+    /**
+     * Test 12-2
+     * Tests that the device ignores directly addressed message <GIVE_PHYSICAL_ADDRESS> if received
+     * as a broadcast message
+     */
+    @Test
+    public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_givePhysicalAddress()
+        throws Exception {
+        hdmiCecClient.sendCecMessage(
+                LogicalAddress.TV,
+                LogicalAddress.BROADCAST,
+                CecOperand.GIVE_PHYSICAL_ADDRESS);
+        hdmiCecClient.checkOutputDoesNotContainMessage(
+                LogicalAddress.BROADCAST,
+                CecOperand.REPORT_PHYSICAL_ADDRESS);
+    }
+
+    /**
+     * Test 12-2
+     * Tests that the device ignores directly addressed message <GIVE_AUDIO_STATUS> if received as
+     * a broadcast message
+     */
+    @Test
+    public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_giveAudioStatus() throws Exception {
+        hdmiCecClient.sendCecMessage(
+                LogicalAddress.TV,
+                LogicalAddress.BROADCAST,
+                CecOperand.GIVE_AUDIO_STATUS);
+        hdmiCecClient.checkOutputDoesNotContainMessage(
+                LogicalAddress.TV,
+                CecOperand.REPORT_AUDIO_STATUS);
+    }
+
+    /**
+     * Test 12-2
+     * Tests that the device ignores directly addressed message <GIVE_POWER_STATUS> if received as
+     * a broadcast message
+     */
+    @Test
+    public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_givePowerStatus() throws Exception {
+        hdmiCecClient.sendCecMessage(
+                LogicalAddress.TV,
+                LogicalAddress.BROADCAST,
+                CecOperand.GIVE_POWER_STATUS);
+        hdmiCecClient.checkOutputDoesNotContainMessage(
+                LogicalAddress.TV,
+                CecOperand.REPORT_POWER_STATUS);
+    }
+
+    /**
+     * Test 12-2
+     * Tests that the device ignores directly addressed message <GIVE_DEVICE_VENDOR_ID> if received
+     * as a broadcast message
+     */
+    @Test
+    public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_giveDeviceVendorId()
+        throws Exception {
+        hdmiCecClient.sendCecMessage(
+                LogicalAddress.TV,
+                LogicalAddress.BROADCAST,
+                CecOperand.GIVE_DEVICE_VENDOR_ID);
+        hdmiCecClient.checkOutputDoesNotContainMessage(
+                LogicalAddress.BROADCAST,
+                CecOperand.DEVICE_VENDOR_ID);
+    }
+
+    /**
+     * Test 12-2
+     * Tests that the device ignores directly addressed message <GIVE_OSD_NAME> if received as
+     * a broadcast message
+     */
+    @Test
+    public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_giveOsdName() throws Exception {
+        hdmiCecClient.sendCecMessage(
+                LogicalAddress.TV,
+                LogicalAddress.BROADCAST,
+                CecOperand.GIVE_OSD_NAME);
+        hdmiCecClient.checkOutputDoesNotContainMessage(
+                LogicalAddress.TV,
+                CecOperand.SET_OSD_NAME);
+    }
+
+    /**
+     * Test 12-2
+     * Tests that the device ignores directly addressed message <GIVE_SYSTEM_AUDIO_MODE_STATUS> if
+     * received as a broadcast message
+     */
+    @Test
+    public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_giveSystemAudioModeStatus()
+        throws Exception {
+        hdmiCecClient.sendCecMessage(
+                LogicalAddress.TV,
+                LogicalAddress.BROADCAST,
+                CecOperand.GIVE_SYSTEM_AUDIO_MODE_STATUS);
+        hdmiCecClient.checkOutputDoesNotContainMessage(
+                LogicalAddress.TV,
+                CecOperand.SYSTEM_AUDIO_MODE_STATUS);
+    }
+
+    /**
+     * Test 12-2
+     * Tests that the device ignores directly addressed message <REQUEST_SHORT_AUDIO_DESCRIPTOR> if
+     * received as a broadcast message
+     */
+    @Test
+    public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_requestShortAudioDescriptor()
+        throws Exception {
+        hdmiCecClient.sendCecMessage(
+                LogicalAddress.TV,
+                LogicalAddress.BROADCAST,
+                CecOperand.REQUEST_SHORT_AUDIO_DESCRIPTOR,
+                CecMessage.formatParams("01020304"));
+        hdmiCecClient.checkOutputDoesNotContainMessage(
+                LogicalAddress.TV,
+                CecOperand.REPORT_SHORT_AUDIO_DESCRIPTOR);
+    }
+
+    /**
+     * Test 12-2
+     * Tests that the device ignores directly addressed message <SYSTEM_AUDIO_MODE_REQUEST> if
+     * received as a broadcast message
+     */
+    @Test
+    public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_systemAudioModeRequest()
+        throws Exception {
+        hdmiCecClient.sendCecMessage(
+                LogicalAddress.TV,
+                LogicalAddress.BROADCAST,
+                CecOperand.SYSTEM_AUDIO_MODE_REQUEST);
+        hdmiCecClient.checkOutputDoesNotContainMessage(
+                LogicalAddress.BROADCAST,
+                CecOperand.SET_SYSTEM_AUDIO_MODE);
+    }
+
+    /**
+     * Test 12-2
+     * Tests that the device ignores directly addressed message <REQUEST_ARC_INITIATION> if
+     * received as a broadcast message
+     */
+    @Test
+    public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_requestArcInitiation()
+        throws Exception {
+        hdmiCecClient.sendCecMessage(
+                LogicalAddress.TV,
+                LogicalAddress.BROADCAST,
+                CecOperand.REQUEST_ARC_INITIATION);
+        hdmiCecClient.checkOutputDoesNotContainMessage(
+                LogicalAddress.BROADCAST,
+                CecOperand.INITIATE_ARC);
+    }
+
+    /**
+     * Test 12-2
+     * Tests that the device ignores directly addressed message <REQUEST_ARC_TERMINATION> if
+     * received as a broadcast message
+     */
+    @Test
+    public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_requestArcTermination()
+        throws Exception {
+        checkArcIsInitiated();
+        hdmiCecClient.sendCecMessage(
+                LogicalAddress.TV,
+                LogicalAddress.BROADCAST,
+                CecOperand.REQUEST_ARC_TERMINATION);
+        hdmiCecClient.checkOutputDoesNotContainMessage(
+                LogicalAddress.BROADCAST,
+                CecOperand.TERMINATE_ARC);
+    }
+
+    /**
+     * Test 12-2
+     * Tests that the device ignores directly addressed message <USER_CONTROL_PRESSED> if received
+     * as a broadcast message
+     */
+    @Test
+    public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_userControlPressed()
+        throws Exception {
+        ITestDevice device = getDevice();
+        // Clear activity
+        device.executeShellCommand(CLEAR_COMMAND);
+        // Clear logcat.
+        device.executeAdbCommand("logcat", "-c");
+        // Start the APK and wait for it to complete.
+        device.executeShellCommand(START_COMMAND);
+        hdmiCecClient.sendUserControlPressAndRelease(
+                LogicalAddress.TV,
+                LogicalAddress.BROADCAST,
+                HdmiCecConstants.CEC_CONTROL_UP,
+                false);
+        LogHelper.assertLogDoesNotContain(getDevice(), CLASS, "Short press KEYCODE_DPAD_UP");
+    }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecLogicalAddressTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecLogicalAddressTest.java
index 1305a2b..d0f052d 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecLogicalAddressTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecLogicalAddressTest.java
@@ -16,29 +16,43 @@
 
 package android.hdmicec.cts.audio;
 
-import static org.junit.Assert.assertThat;
-import static org.hamcrest.CoreMatchers.is;
+import static com.google.common.truth.Truth.assertThat;
 
-import android.hdmicec.cts.CecDevice;
 import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
 import android.hdmicec.cts.HdmiCecClientWrapper;
 import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogicalAddress;
+import android.hdmicec.cts.RequiredPropertyRule;
+import android.hdmicec.cts.RequiredFeatureRule;
 
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
 import org.junit.Rule;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 import org.junit.Test;
 
 /** HDMI CEC test to verify logical address after device reboot (Section 10.2.5) */
 @RunWith(DeviceJUnit4ClassRunner.class)
 public final class HdmiCecLogicalAddressTest extends BaseHostJUnit4Test {
-    private static final CecDevice AUDIO_DEVICE = CecDevice.AUDIO_SYSTEM;
+
+    private static final LogicalAddress AUDIO_DEVICE = LogicalAddress.AUDIO_SYSTEM;
+
+    public HdmiCecClientWrapper hdmiCecClient = new HdmiCecClientWrapper(AUDIO_DEVICE);
 
     @Rule
-    public HdmiCecClientWrapper hdmiCecClient = new HdmiCecClientWrapper(AUDIO_DEVICE, this);
+    public RuleChain ruleChain =
+        RuleChain
+            .outerRule(new RequiredFeatureRule(this, LogicalAddress.HDMI_CEC_FEATURE))
+            .around(new RequiredFeatureRule(this, LogicalAddress.LEANBACK_FEATURE))
+            .around(RequiredPropertyRule.asCsvContainsValue(
+                this,
+                LogicalAddress.HDMI_DEVICE_TYPE_PROPERTY,
+                AUDIO_DEVICE.getDeviceType()))
+            .around(hdmiCecClient);
 
     /**
      * Test 10.2.5-1
@@ -50,7 +64,7 @@
         ITestDevice device = getDevice();
         device.executeShellCommand("reboot");
         device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
-        String message = hdmiCecClient.checkExpectedOutput(CecMessage.REPORT_PHYSICAL_ADDRESS);
-        assertThat(hdmiCecClient.getSourceFromMessage(message), is(AUDIO_DEVICE));
+        String message = hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_PHYSICAL_ADDRESS);
+        assertThat(CecMessage.getSource(message)).isEqualTo(AUDIO_DEVICE);
     }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecRemoteControlPassThroughTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecRemoteControlPassThroughTest.java
index 9931e26..d01b506 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecRemoteControlPassThroughTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecRemoteControlPassThroughTest.java
@@ -16,23 +16,22 @@
 
 package android.hdmicec.cts.audio;
 
-import static com.google.common.truth.Truth.assertThat;
-
-import android.hdmicec.cts.CecDevice;
 import android.hdmicec.cts.HdmiCecClientWrapper;
 import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogHelper;
+import android.hdmicec.cts.LogicalAddress;
+import android.hdmicec.cts.RequiredPropertyRule;
+import android.hdmicec.cts.RequiredFeatureRule;
 
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
 import org.junit.Rule;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 import org.junit.Test;
 
-import java.util.Scanner;
-import java.util.concurrent.TimeUnit;
-
 @RunWith(DeviceJUnit4ClassRunner.class)
 public final class HdmiCecRemoteControlPassThroughTest extends BaseHostJUnit4Test {
 
@@ -49,29 +48,18 @@
     /** The command to clear the main activity. */
     private static final String CLEAR_COMMAND = String.format("pm clear %s", PACKAGE);
 
-    private static final int WAIT_TIME = 10;
+    public HdmiCecClientWrapper hdmiCecClient = new HdmiCecClientWrapper(LogicalAddress.AUDIO_SYSTEM);
 
     @Rule
-    public HdmiCecClientWrapper hdmiCecClient =
-            new HdmiCecClientWrapper(CecDevice.AUDIO_SYSTEM, this);
-
-    private void lookForLog(String expectedOut) throws Exception {
-        ITestDevice device = getDevice();
-        TimeUnit.SECONDS.sleep(WAIT_TIME);
-        String logs = device.executeAdbCommand("logcat", "-v", "brief", "-d", CLASS + ":I", "*:S");
-        // Search for string.
-        String testString = "";
-        Scanner in = new Scanner(logs);
-        while (in.hasNextLine()) {
-            String line = in.nextLine();
-            if(line.startsWith("I/" + CLASS)) {
-                testString = line.split(":")[1].trim();
-                break;
-            }
-        }
-        device.executeAdbCommand("logcat", "-c");
-        assertThat(testString).isEqualTo(expectedOut);
-    }
+    public RuleChain ruleChain =
+        RuleChain
+            .outerRule(new RequiredFeatureRule(this, LogicalAddress.HDMI_CEC_FEATURE))
+            .around(new RequiredFeatureRule(this, LogicalAddress.LEANBACK_FEATURE))
+            .around(RequiredPropertyRule.asCsvContainsValue(
+                this,
+                LogicalAddress.HDMI_DEVICE_TYPE_PROPERTY,
+                LogicalAddress.AUDIO_SYSTEM.getDeviceType()))
+            .around(hdmiCecClient);
 
     /**
      * Test 11.2.13-1
@@ -87,24 +75,24 @@
         device.executeAdbCommand("logcat", "-c");
         // Start the APK and wait for it to complete.
         device.executeShellCommand(START_COMMAND);
-        hdmiCecClient.sendUserControlPressAndRelease(CecDevice.TV, CecDevice.AUDIO_SYSTEM,
+        hdmiCecClient.sendUserControlPressAndRelease(LogicalAddress.TV, LogicalAddress.AUDIO_SYSTEM,
                 HdmiCecConstants.CEC_CONTROL_UP, false);
-        lookForLog("Short press KEYCODE_DPAD_UP");
-        hdmiCecClient.sendUserControlPressAndRelease(CecDevice.TV, CecDevice.AUDIO_SYSTEM,
+        LogHelper.assertLog(getDevice(), CLASS, "Short press KEYCODE_DPAD_UP");
+        hdmiCecClient.sendUserControlPressAndRelease(LogicalAddress.TV, LogicalAddress.AUDIO_SYSTEM,
                 HdmiCecConstants.CEC_CONTROL_DOWN, false);
-        lookForLog("Short press KEYCODE_DPAD_DOWN");
-        hdmiCecClient.sendUserControlPressAndRelease(CecDevice.TV, CecDevice.AUDIO_SYSTEM,
+        LogHelper.assertLog(getDevice(), CLASS, "Short press KEYCODE_DPAD_DOWN");
+        hdmiCecClient.sendUserControlPressAndRelease(LogicalAddress.TV, LogicalAddress.AUDIO_SYSTEM,
                 HdmiCecConstants.CEC_CONTROL_LEFT, false);
-        lookForLog("Short press KEYCODE_DPAD_LEFT");
-        hdmiCecClient.sendUserControlPressAndRelease(CecDevice.TV, CecDevice.AUDIO_SYSTEM,
+        LogHelper.assertLog(getDevice(), CLASS, "Short press KEYCODE_DPAD_LEFT");
+        hdmiCecClient.sendUserControlPressAndRelease(LogicalAddress.TV, LogicalAddress.AUDIO_SYSTEM,
                 HdmiCecConstants.CEC_CONTROL_RIGHT, false);
-        lookForLog("Short press KEYCODE_DPAD_RIGHT");
-        hdmiCecClient.sendUserControlPressAndRelease(CecDevice.TV, CecDevice.AUDIO_SYSTEM,
+        LogHelper.assertLog(getDevice(), CLASS, "Short press KEYCODE_DPAD_RIGHT");
+        hdmiCecClient.sendUserControlPressAndRelease(LogicalAddress.TV, LogicalAddress.AUDIO_SYSTEM,
                 HdmiCecConstants.CEC_CONTROL_SELECT, false);
-        lookForLog("Short press KEYCODE_DPAD_CENTER");
-        hdmiCecClient.sendUserControlPressAndRelease(CecDevice.TV, CecDevice.AUDIO_SYSTEM,
+        LogHelper.assertLog(getDevice(), CLASS, "Short press KEYCODE_DPAD_CENTER");
+        hdmiCecClient.sendUserControlPressAndRelease(LogicalAddress.TV, LogicalAddress.AUDIO_SYSTEM,
                 HdmiCecConstants.CEC_CONTROL_BACK, false);
-        lookForLog("Short press KEYCODE_BACK");
+        LogHelper.assertLog(getDevice(), CLASS, "Short press KEYCODE_BACK");
     }
 
     /**
@@ -121,24 +109,24 @@
         device.executeAdbCommand("logcat", "-c");
         // Start the APK and wait for it to complete.
         device.executeShellCommand(START_COMMAND);
-        hdmiCecClient.sendUserControlPressAndRelease(CecDevice.TV, CecDevice.AUDIO_SYSTEM,
+        hdmiCecClient.sendUserControlPressAndRelease(LogicalAddress.TV, LogicalAddress.AUDIO_SYSTEM,
                 HdmiCecConstants.CEC_CONTROL_UP, true);
-        lookForLog("Long press KEYCODE_DPAD_UP");
-        hdmiCecClient.sendUserControlPressAndRelease(CecDevice.TV, CecDevice.AUDIO_SYSTEM,
+        LogHelper.assertLog(getDevice(), CLASS, "Long press KEYCODE_DPAD_UP");
+        hdmiCecClient.sendUserControlPressAndRelease(LogicalAddress.TV, LogicalAddress.AUDIO_SYSTEM,
                 HdmiCecConstants.CEC_CONTROL_DOWN, true);
-        lookForLog("Long press KEYCODE_DPAD_DOWN");
-        hdmiCecClient.sendUserControlPressAndRelease(CecDevice.TV, CecDevice.AUDIO_SYSTEM,
+        LogHelper.assertLog(getDevice(), CLASS, "Long press KEYCODE_DPAD_DOWN");
+        hdmiCecClient.sendUserControlPressAndRelease(LogicalAddress.TV, LogicalAddress.AUDIO_SYSTEM,
                 HdmiCecConstants.CEC_CONTROL_LEFT, true);
-        lookForLog("Long press KEYCODE_DPAD_LEFT");
-        hdmiCecClient.sendUserControlPressAndRelease(CecDevice.TV, CecDevice.AUDIO_SYSTEM,
+        LogHelper.assertLog(getDevice(), CLASS, "Long press KEYCODE_DPAD_LEFT");
+        hdmiCecClient.sendUserControlPressAndRelease(LogicalAddress.TV, LogicalAddress.AUDIO_SYSTEM,
                 HdmiCecConstants.CEC_CONTROL_RIGHT, true);
-        lookForLog("Long press KEYCODE_DPAD_RIGHT");
-        hdmiCecClient.sendUserControlPressAndRelease(CecDevice.TV, CecDevice.AUDIO_SYSTEM,
+        LogHelper.assertLog(getDevice(), CLASS, "Long press KEYCODE_DPAD_RIGHT");
+        hdmiCecClient.sendUserControlPressAndRelease(LogicalAddress.TV, LogicalAddress.AUDIO_SYSTEM,
                 HdmiCecConstants.CEC_CONTROL_SELECT, true);
-        lookForLog("Long press KEYCODE_DPAD_CENTER");
-        hdmiCecClient.sendUserControlPressAndRelease(CecDevice.TV, CecDevice.AUDIO_SYSTEM,
+        LogHelper.assertLog(getDevice(), CLASS, "Long press KEYCODE_DPAD_CENTER");
+        hdmiCecClient.sendUserControlPressAndRelease(LogicalAddress.TV, LogicalAddress.AUDIO_SYSTEM,
                 HdmiCecConstants.CEC_CONTROL_BACK, true);
-        lookForLog("Long press KEYCODE_BACK");
+        LogHelper.assertLog(getDevice(), CLASS, "Long press KEYCODE_BACK");
     }
 
     /**
@@ -155,24 +143,24 @@
         device.executeAdbCommand("logcat", "-c");
         // Start the APK and wait for it to complete.
         device.executeShellCommand(START_COMMAND);
-        hdmiCecClient.sendUserControlPress(CecDevice.TV, CecDevice.AUDIO_SYSTEM,
+        hdmiCecClient.sendUserControlPress(LogicalAddress.TV, LogicalAddress.AUDIO_SYSTEM,
                 HdmiCecConstants.CEC_CONTROL_UP, true);
-        lookForLog("Long press KEYCODE_DPAD_UP");
-        hdmiCecClient.sendUserControlPress(CecDevice.TV, CecDevice.AUDIO_SYSTEM,
+        LogHelper.assertLog(getDevice(), CLASS, "Long press KEYCODE_DPAD_UP");
+        hdmiCecClient.sendUserControlPress(LogicalAddress.TV, LogicalAddress.AUDIO_SYSTEM,
                 HdmiCecConstants.CEC_CONTROL_DOWN, true);
-        lookForLog("Long press KEYCODE_DPAD_DOWN");
-        hdmiCecClient.sendUserControlPress(CecDevice.TV, CecDevice.AUDIO_SYSTEM,
+        LogHelper.assertLog(getDevice(), CLASS, "Long press KEYCODE_DPAD_DOWN");
+        hdmiCecClient.sendUserControlPress(LogicalAddress.TV, LogicalAddress.AUDIO_SYSTEM,
                 HdmiCecConstants.CEC_CONTROL_LEFT, true);
-        lookForLog("Long press KEYCODE_DPAD_LEFT");
-        hdmiCecClient.sendUserControlPress(CecDevice.TV, CecDevice.AUDIO_SYSTEM,
+        LogHelper.assertLog(getDevice(), CLASS, "Long press KEYCODE_DPAD_LEFT");
+        hdmiCecClient.sendUserControlPress(LogicalAddress.TV, LogicalAddress.AUDIO_SYSTEM,
                 HdmiCecConstants.CEC_CONTROL_RIGHT, true);
-        lookForLog("Long press KEYCODE_DPAD_RIGHT");
-        hdmiCecClient.sendUserControlPress(CecDevice.TV, CecDevice.AUDIO_SYSTEM,
+        LogHelper.assertLog(getDevice(), CLASS, "Long press KEYCODE_DPAD_RIGHT");
+        hdmiCecClient.sendUserControlPress(LogicalAddress.TV, LogicalAddress.AUDIO_SYSTEM,
                 HdmiCecConstants.CEC_CONTROL_SELECT, true);
-        lookForLog("Long press KEYCODE_DPAD_CENTER");
-        hdmiCecClient.sendUserControlPress(CecDevice.TV, CecDevice.AUDIO_SYSTEM,
+        LogHelper.assertLog(getDevice(), CLASS, "Long press KEYCODE_DPAD_CENTER");
+        hdmiCecClient.sendUserControlPress(LogicalAddress.TV, LogicalAddress.AUDIO_SYSTEM,
                 HdmiCecConstants.CEC_CONTROL_BACK, true);
-        lookForLog("Long press KEYCODE_BACK");
+        LogHelper.assertLog(getDevice(), CLASS, "Long press KEYCODE_BACK");
     }
 
     /**
@@ -190,29 +178,29 @@
         device.executeAdbCommand("logcat", "-c");
         // Start the APK and wait for it to complete.
         device.executeShellCommand(START_COMMAND);
-        hdmiCecClient.sendUserControlInterruptedPressAndHold(CecDevice.TV,
-                CecDevice.AUDIO_SYSTEM, HdmiCecConstants.CEC_CONTROL_UP,
+        hdmiCecClient.sendUserControlInterruptedPressAndHold(LogicalAddress.TV,
+                LogicalAddress.AUDIO_SYSTEM, HdmiCecConstants.CEC_CONTROL_UP,
                 HdmiCecConstants.CEC_CONTROL_BACK, true);
-        lookForLog("Long press KEYCODE_DPAD_UP");
-        hdmiCecClient.sendUserControlInterruptedPressAndHold(CecDevice.TV,
-                CecDevice.AUDIO_SYSTEM, HdmiCecConstants.CEC_CONTROL_DOWN,
+        LogHelper.assertLog(getDevice(), CLASS, "Long press KEYCODE_DPAD_UP");
+        hdmiCecClient.sendUserControlInterruptedPressAndHold(LogicalAddress.TV,
+                LogicalAddress.AUDIO_SYSTEM, HdmiCecConstants.CEC_CONTROL_DOWN,
                 HdmiCecConstants.CEC_CONTROL_UP, true);
-        lookForLog("Long press KEYCODE_DPAD_DOWN");
-        hdmiCecClient.sendUserControlInterruptedPressAndHold(CecDevice.TV,
-                CecDevice.AUDIO_SYSTEM, HdmiCecConstants.CEC_CONTROL_LEFT,
+        LogHelper.assertLog(getDevice(), CLASS, "Long press KEYCODE_DPAD_DOWN");
+        hdmiCecClient.sendUserControlInterruptedPressAndHold(LogicalAddress.TV,
+                LogicalAddress.AUDIO_SYSTEM, HdmiCecConstants.CEC_CONTROL_LEFT,
                 HdmiCecConstants.CEC_CONTROL_DOWN, true);
-        lookForLog("Long press KEYCODE_DPAD_LEFT");
-        hdmiCecClient.sendUserControlInterruptedPressAndHold(CecDevice.TV,
-                CecDevice.AUDIO_SYSTEM, HdmiCecConstants.CEC_CONTROL_RIGHT,
+        LogHelper.assertLog(getDevice(), CLASS, "Long press KEYCODE_DPAD_LEFT");
+        hdmiCecClient.sendUserControlInterruptedPressAndHold(LogicalAddress.TV,
+                LogicalAddress.AUDIO_SYSTEM, HdmiCecConstants.CEC_CONTROL_RIGHT,
                 HdmiCecConstants.CEC_CONTROL_LEFT, true);
-        lookForLog("Long press KEYCODE_DPAD_RIGHT");
-        hdmiCecClient.sendUserControlInterruptedPressAndHold(CecDevice.TV,
-                CecDevice.AUDIO_SYSTEM, HdmiCecConstants.CEC_CONTROL_SELECT,
+        LogHelper.assertLog(getDevice(), CLASS, "Long press KEYCODE_DPAD_RIGHT");
+        hdmiCecClient.sendUserControlInterruptedPressAndHold(LogicalAddress.TV,
+                LogicalAddress.AUDIO_SYSTEM, HdmiCecConstants.CEC_CONTROL_SELECT,
                 HdmiCecConstants.CEC_CONTROL_RIGHT, true);
-        lookForLog("Long press KEYCODE_DPAD_CENTER");
-        hdmiCecClient.sendUserControlInterruptedPressAndHold(CecDevice.TV,
-                CecDevice.AUDIO_SYSTEM, HdmiCecConstants.CEC_CONTROL_BACK,
+        LogHelper.assertLog(getDevice(), CLASS, "Long press KEYCODE_DPAD_CENTER");
+        hdmiCecClient.sendUserControlInterruptedPressAndHold(LogicalAddress.TV,
+                LogicalAddress.AUDIO_SYSTEM, HdmiCecConstants.CEC_CONTROL_BACK,
                 HdmiCecConstants.CEC_CONTROL_SELECT, true);
-        lookForLog("Long press KEYCODE_BACK");
+        LogHelper.assertLog(getDevice(), CLASS, "Long press KEYCODE_BACK");
     }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecSystemAudioModeTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecSystemAudioModeTest.java
index 6f6e4dc..ac21a0f 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecSystemAudioModeTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecSystemAudioModeTest.java
@@ -16,29 +16,206 @@
 
 package android.hdmicec.cts.audio;
 
-import static org.junit.Assert.assertThat;
-import static org.hamcrest.CoreMatchers.is;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
-import android.hdmicec.cts.CecDevice;
+import com.google.common.collect.Range;
+
 import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
 import android.hdmicec.cts.HdmiCecClientWrapper;
 import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogHelper;
+import android.hdmicec.cts.LogicalAddress;
+import android.hdmicec.cts.RequiredPropertyRule;
+import android.hdmicec.cts.RequiredFeatureRule;
 
+import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
+import org.junit.After;
 import org.junit.Rule;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 import org.junit.Test;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
 /** HDMI CEC test to test system audio mode (Section 11.2.15) */
 @RunWith(DeviceJUnit4ClassRunner.class)
 public final class HdmiCecSystemAudioModeTest extends BaseHostJUnit4Test {
-    private static final CecDevice AUDIO_DEVICE = CecDevice.AUDIO_SYSTEM;
+
+    /** The package name of the APK. */
+    private static final String PACKAGE = "android.hdmicec.app";
+
+    /** The class name of the main activity in the APK. */
+    private static final String CLASS = "HdmiCecAudioManager";
+
+    /** The command to launch the main activity. */
+    private static final String START_COMMAND = String.format(
+            "am start -n %s/%s.%s -a ", PACKAGE, PACKAGE, CLASS);
+
+    /** The command to clear the main activity. */
+    private static final String CLEAR_COMMAND = String.format("pm clear %s", PACKAGE);
+
+    private static final LogicalAddress AUDIO_DEVICE = LogicalAddress.AUDIO_SYSTEM;
     private static final int ON = 0x1;
+    private static final int OFF = 0x0;
+    private static final int MAX_AUDIO_FORMATS = 4;
+    private static final int MAX_VALID_AUDIO_FORMATS = 2;
+
+    private List<Integer> mSupportedAudioFormats = null;
+
+    public HdmiCecClientWrapper hdmiCecClient = new HdmiCecClientWrapper(AUDIO_DEVICE, "-t", "t");
 
     @Rule
-    public HdmiCecClientWrapper hdmiCecClient = new HdmiCecClientWrapper(AUDIO_DEVICE, this);
+    public RuleChain ruleChain =
+        RuleChain
+            .outerRule(new RequiredFeatureRule(this, LogicalAddress.HDMI_CEC_FEATURE))
+            .around(new RequiredFeatureRule(this, LogicalAddress.LEANBACK_FEATURE))
+            .around(RequiredPropertyRule.asCsvContainsValue(
+                this,
+                LogicalAddress.HDMI_DEVICE_TYPE_PROPERTY,
+                AUDIO_DEVICE.getDeviceType()))
+            .around(hdmiCecClient);
+
+    private String getRequestSadFormatsParams(boolean sendValidFormats) throws Exception {
+        ITestDevice device = getDevice();
+        // Clear activity
+        device.executeShellCommand(CLEAR_COMMAND);
+        // Clear logcat.
+        device.executeAdbCommand("logcat", "-c");
+        // Start the APK and wait for it to complete.
+        device.executeShellCommand(START_COMMAND + "android.hdmicec.app.GET_SUPPORTED_SAD_FORMATS");
+        mSupportedAudioFormats = LogHelper.getSupportedAudioFormats(getDevice());
+
+        // Create a list of all the audio format codes according to CEA-861-D. Remove the supported
+        // audio format codes from it, to get the unsupported audio format codes.
+        List<Integer> mAllCodecFormats =
+                IntStream.range(1, 15).boxed().collect(Collectors.toList());
+        List<Integer> unsupportedAudioFormats = new ArrayList<>();
+        unsupportedAudioFormats.addAll(mAllCodecFormats);
+        unsupportedAudioFormats.removeAll(mSupportedAudioFormats);
+        // Create params message for REQUEST_SHORT_AUDIO_DESCRIPTOR
+        String messageParams = "";
+        int i = 0;
+        int listIndex = 0;
+        if (sendValidFormats) {
+            while (i < Math.min(MAX_VALID_AUDIO_FORMATS, mSupportedAudioFormats.size())) {
+                messageParams +=
+                        CecMessage.formatParams(mSupportedAudioFormats.get(listIndex), 2);
+                i++;
+                listIndex++;
+            }
+            listIndex = 0;
+        }
+        while (i < Math.min(MAX_AUDIO_FORMATS, unsupportedAudioFormats.size())) {
+            messageParams += CecMessage.formatParams(unsupportedAudioFormats.get(listIndex), 2);
+            i++;
+            listIndex++;
+        }
+        return messageParams;
+    }
+
+    private void muteDevice() throws Exception {
+        ITestDevice device = getDevice();
+        // Clear activity
+        device.executeShellCommand(CLEAR_COMMAND);
+        // Clear logcat.
+        device.executeAdbCommand("logcat", "-c");
+        // Start the APK and wait for it to complete.
+        device.executeShellCommand(START_COMMAND + "android.hdmicec.app.MUTE");
+    }
+
+    private void unmuteDevice() throws Exception {
+        ITestDevice device = getDevice();
+        // Clear activity
+        device.executeShellCommand(CLEAR_COMMAND);
+        // Start the APK and wait for it to complete.
+        device.executeShellCommand(START_COMMAND + "android.hdmicec.app.UNMUTE");
+    }
+
+    public boolean isDeviceMuted() throws Exception {
+        ITestDevice device = getDevice();
+        // Clear activity
+        device.executeShellCommand(CLEAR_COMMAND);
+        // Clear logcat.
+        device.executeAdbCommand("logcat", "-c");
+        // Start the APK and wait for it to complete.
+        device.executeShellCommand(START_COMMAND + "android.hdmicec.app.REPORT_VOLUME");
+        try {
+            LogHelper.assertLog(getDevice(), CLASS, "Device muted.");
+            return true;
+        } catch(Exception e) {
+            return false;
+        }
+    }
+
+    public void setDeviceVolume(int percentVolume) throws Exception {
+        ITestDevice device = getDevice();
+        // Clear activity
+        device.executeShellCommand(CLEAR_COMMAND);
+        // Start the APK and wait for it to complete.
+        device.executeShellCommand(START_COMMAND + "android.hdmicec.app.SET_VOLUME --ei " +
+                "\"volumePercent\" " + percentVolume);
+    }
+
+    public void sendSystemAudioModeTermination() throws Exception {
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, AUDIO_DEVICE,
+                CecOperand.SYSTEM_AUDIO_MODE_REQUEST);
+    }
+
+    public void sendSystemAudioModeInitiation() throws Exception {
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, AUDIO_DEVICE,
+                CecOperand.SYSTEM_AUDIO_MODE_REQUEST,
+                CecMessage.formatParams(HdmiCecConstants.TV_PHYSICAL_ADDRESS,
+                    HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH));
+    }
+
+    private int getDutAudioStatus() throws Exception {
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, AUDIO_DEVICE, CecOperand.GIVE_AUDIO_STATUS);
+        String message = hdmiCecClient.checkExpectedOutput(LogicalAddress.TV,
+                CecOperand.REPORT_AUDIO_STATUS);
+        return CecMessage.getParams(message);
+    }
+
+    private void initateSystemAudioModeFromTuner() throws Exception {
+        getDevice().reboot();
+        hdmiCecClient.sendCecMessage(LogicalAddress.TUNER_1, AUDIO_DEVICE,
+                CecOperand.SYSTEM_AUDIO_MODE_REQUEST,
+                CecMessage.formatParams(HdmiCecConstants.TV_PHYSICAL_ADDRESS,
+                        HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH));
+        handleSetSystemAudioModeOnToTv();
+    }
+
+    private void handleSetSystemAudioModeOnToTv() throws Exception {
+        hdmiCecClient.checkExpectedOutput(CecOperand.REQUEST_ACTIVE_SOURCE);
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, LogicalAddress.BROADCAST, CecOperand.ACTIVE_SOURCE,
+                CecMessage.formatParams("2000"));
+        String message = hdmiCecClient.checkExpectedOutput(LogicalAddress.TV,
+                CecOperand.SET_SYSTEM_AUDIO_MODE);
+        assertThat(CecMessage.getParams(message)).isEqualTo(ON);
+    }
+
+    private void initiateSystemAudioModeFromDut() throws Exception {
+        getDevice().reboot();
+        hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_PHYSICAL_ADDRESS);
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, LogicalAddress.AUDIO_SYSTEM,
+                CecOperand.GIVE_SYSTEM_AUDIO_MODE_STATUS);
+        hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.INITIATE_ARC);
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, LogicalAddress.AUDIO_SYSTEM,
+                CecOperand.ARC_INITIATED);
+        handleSetSystemAudioModeOnToTv();
+    }
+
+    @After
+    public void resetVolume() throws Exception {
+        setDeviceVolume(20);
+    }
 
     /**
      * Test 11.2.15-1
@@ -47,17 +224,306 @@
      */
     @Test
     public void cect_11_2_15_1_SystemAudioModeRequestAsFollower() throws Exception {
-        hdmiCecClient.sendCecMessage(CecDevice.TV, AUDIO_DEVICE,
-                CecMessage.SYSTEM_AUDIO_MODE_REQUEST,
-                hdmiCecClient.formatParams(HdmiCecConstants.TV_PHYSICAL_ADDRESS));
-        String message = hdmiCecClient.checkExpectedOutput(CecMessage.SET_SYSTEM_AUDIO_MODE);
-        assertThat(hdmiCecClient.getParamsFromMessage(message), is(ON));
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, AUDIO_DEVICE,
+                CecOperand.SYSTEM_AUDIO_MODE_REQUEST,
+                CecMessage.formatParams(HdmiCecConstants.TV_PHYSICAL_ADDRESS,
+                    HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH));
+        String message = hdmiCecClient.checkExpectedOutput(CecOperand.SET_SYSTEM_AUDIO_MODE);
+        assertThat(CecMessage.getParams(message)).isEqualTo(ON);
 
         /* Repeat test for device 0x3 (TUNER_1) */
-        hdmiCecClient.sendCecMessage(CecDevice.TUNER_1, AUDIO_DEVICE,
-                CecMessage.SYSTEM_AUDIO_MODE_REQUEST,
-                hdmiCecClient.formatParams(HdmiCecConstants.TV_PHYSICAL_ADDRESS));
-        message = hdmiCecClient.checkExpectedOutput(CecMessage.SET_SYSTEM_AUDIO_MODE);
-        assertThat(hdmiCecClient.getParamsFromMessage(message), is(ON));
+        hdmiCecClient.sendCecMessage(LogicalAddress.TUNER_1, AUDIO_DEVICE,
+                CecOperand.SYSTEM_AUDIO_MODE_REQUEST,
+                CecMessage.formatParams(HdmiCecConstants.TV_PHYSICAL_ADDRESS,
+                    HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH));
+        message = hdmiCecClient.checkExpectedOutput(CecOperand.SET_SYSTEM_AUDIO_MODE);
+        assertThat(CecMessage.getParams(message)).isEqualTo(ON);
+    }
+
+    /**
+     * Test 11.2.15-2
+     * Tests that the device issues <Set System Audio Mode>
+     * message when the feature is initiated in the device .
+     */
+    @Test
+    public void cect_11_2_15_2_SystemAudioModeWithFeatureInitiation() throws Exception {
+        initiateSystemAudioModeFromDut();
+        String message = hdmiCecClient.checkExpectedOutput(CecOperand.SET_SYSTEM_AUDIO_MODE);
+        assertThat(CecMessage.getParams(message)).isEqualTo(ON);
+    }
+
+    /**
+     * Test 11.2.15-3
+     * Tests that the device doesn't broadcast any <Set System Audio Mode>
+     * messages when TV responds with a <Feature Abort> to a directly addressed
+     * <Set System Audio Mode> message.
+     */
+    @Test
+    public void cect_11_2_15_3_SystemAudioModeWithFeatureAbort() throws Exception {
+        initiateSystemAudioModeFromDut();
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, AUDIO_DEVICE, CecOperand.FEATURE_ABORT,
+                CecMessage.formatParams(CecOperand.SET_SYSTEM_AUDIO_MODE + "04"));
+        hdmiCecClient.checkOutputDoesNotContainMessage(LogicalAddress.BROADCAST,
+                CecOperand.SET_SYSTEM_AUDIO_MODE);
+        //The DUT will need a reboot here so it'll forget the feature abort from the previous
+        // message. Else it may not respond correctly with a SET_SYSTEM_AUDIO_MODE message
+        // in future tests.
+        getDevice().reboot();
+    }
+
+    /**
+     * Test 11.2.15-4
+     * Tests that the device responds correctly to a <Give System Audio Status>
+     * message when System Audio Mode is "On".
+     */
+    @Test
+    public void cect_11_2_15_4_SystemAudioModeStatusOn() throws Exception {
+        sendSystemAudioModeInitiation();
+        String message = hdmiCecClient.checkExpectedOutput(CecOperand.SET_SYSTEM_AUDIO_MODE);
+        assertThat(CecMessage.getParams(message)).isEqualTo(ON);
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, AUDIO_DEVICE,
+                CecOperand.GIVE_SYSTEM_AUDIO_MODE_STATUS);
+        message = hdmiCecClient.checkExpectedOutput(LogicalAddress.TV,
+                CecOperand.SYSTEM_AUDIO_MODE_STATUS);
+        assertThat(CecMessage.getParams(message)).isEqualTo(ON);
+    }
+
+    /**
+     * Test 11.2.15-5
+     * Tests that the device sends a <Set System Audio Mode> ["Off"]
+     * message when a <System Audio Mode Request> is received with no operands
+     */
+    @Test
+    public void cect_11_2_15_5_SetSystemAudioModeOff() throws Exception {
+        sendSystemAudioModeInitiation();
+        String message = hdmiCecClient.checkExpectedOutput(CecOperand.SET_SYSTEM_AUDIO_MODE);
+        assertThat(CecMessage.getParams(message)).isEqualTo(ON);
+        sendSystemAudioModeTermination();
+        message = hdmiCecClient.checkExpectedOutput(CecOperand.SET_SYSTEM_AUDIO_MODE);
+        assertThat(CecMessage.getParams(message)).isEqualTo(OFF);
+    }
+
+    /**
+     * Test 11.2.15-6
+     * Tests that the device sends a <Set System Audio Mode> ["Off"]
+     * message before going into standby when System Audio Mode is on.
+     */
+    @Test
+    public void cect_11_2_15_6_SystemAudioModeOffBeforeStandby() throws Exception {
+        try {
+            getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP");
+            sendSystemAudioModeInitiation();
+            String message = hdmiCecClient.checkExpectedOutput(CecOperand.SET_SYSTEM_AUDIO_MODE);
+            assertThat(CecMessage.getParams(message)).isEqualTo(ON);
+            getDevice().executeShellCommand("input keyevent KEYCODE_SLEEP");
+            message = hdmiCecClient.checkExpectedOutput(CecOperand.SET_SYSTEM_AUDIO_MODE);
+            assertThat(CecMessage.getParams(message)).isEqualTo(OFF);
+        } finally {
+            getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        }
+    }
+
+    /**
+    * Test 11.2.15-7
+    * Tests that the device responds correctly to a <Give System Audio Mode Status>
+    * message when the System Audio Mode is "Off".
+    */
+   @Test
+    public void cect_11_2_15_7_SystemAudioModeStatusOff() throws Exception {
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, AUDIO_DEVICE,
+                CecOperand.SET_SYSTEM_AUDIO_MODE, CecMessage.formatParams(OFF));
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, AUDIO_DEVICE,
+                CecOperand.GIVE_SYSTEM_AUDIO_MODE_STATUS);
+        String message = hdmiCecClient.checkExpectedOutput(LogicalAddress.TV,
+                CecOperand.SYSTEM_AUDIO_MODE_STATUS);
+        assertThat(CecMessage.getParams(message)).isEqualTo(OFF);
+    }
+
+    /**
+     * Test 11.2.15-8
+     * Tests that the device handles <User Controlled Pressed> ["Mute"]
+     * correctly when System Audio Mode is "On".
+     */
+    @Test
+    public void cect_11_2_15_8_HandleUcpMute() throws Exception {
+        unmuteDevice();
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, AUDIO_DEVICE,
+                CecOperand.SYSTEM_AUDIO_MODE_REQUEST,
+                CecMessage.formatParams(HdmiCecConstants.TV_PHYSICAL_ADDRESS,
+                    HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH));
+        hdmiCecClient.sendUserControlPressAndRelease(LogicalAddress.TV, AUDIO_DEVICE,
+                HdmiCecConstants.CEC_CONTROL_MUTE, false);
+        assertWithMessage("Device is not muted").that(isDeviceMuted()).isTrue();
+    }
+
+    /**
+     * Test 11.2.15-9
+     * Tests that the DUT responds with a <Report Audio Status> message with correct parameters
+     * to a <Give Audio Status> message when volume is set to 0% and not muted.
+     */
+    @Test
+    public void cect_11_2_15_9_ReportAudioStatus_0_unmuted() throws Exception {
+        sendSystemAudioModeInitiation();
+        unmuteDevice();
+        setDeviceVolume(0);
+        int reportedVolume = getDutAudioStatus();
+        assertThat(reportedVolume).isEqualTo(0);
+    }
+
+    /**
+     * Test 11.2.15-9
+     * Tests that the DUT responds with a <Report Audio Status> message with correct parameters
+     * to a <Give Audio Status> message when volume is set to 50% and not muted.
+     */
+    @Test
+    public void cect_11_2_15_9_ReportAudioStatus_50_unmuted() throws Exception {
+        sendSystemAudioModeInitiation();
+        unmuteDevice();
+        setDeviceVolume(50);
+        int reportedVolume = getDutAudioStatus();
+        /* Allow for a range of volume, since the actual volume set will depend on the device's
+        volume resolution. */
+        assertThat(reportedVolume).isIn(Range.closed(46, 54));
+    }
+
+    /**
+     * Test 11.2.15-9
+     * Tests that the DUT responds with a <Report Audio Status> message with correct parameters
+     * to a <Give Audio Status> message when volume is set to 100% and not muted.
+     */
+    @Test
+    public void cect_11_2_15_9_ReportAudioStatus_100_unmuted() throws Exception {
+        sendSystemAudioModeInitiation();
+        unmuteDevice();
+        setDeviceVolume(100);
+        int reportedVolume = getDutAudioStatus();
+        assertThat(reportedVolume).isEqualTo(100);
+    }
+
+    /**
+     * Test 11.2.15-9
+     * Tests that the DUT responds with a <Report Audio Status> message with correct parameters
+     * to a <Give Audio Status> message when volume is muted.
+     */
+    @Test
+    public void cect_11_2_15_9_ReportAudioStatusMuted() throws Exception {
+        sendSystemAudioModeInitiation();
+        muteDevice();
+        int reportedVolume = getDutAudioStatus();
+        /* If device is muted, the 8th bit of CEC message parameters is set and the volume will
+        be greater than 127. */
+        assertWithMessage("Device not muted").that(reportedVolume).isGreaterThan(127);
+    }
+
+    /**
+     * Test 11.2.15-13
+     * Tests that the device responds to a <Request Short Audio Descriptor> message with a
+     * <Report Short Audio Descriptor> message with only those audio descriptors that are supported.
+     */
+    @Test
+    public void cect_11_2_15_13_ValidShortAudioDescriptor() throws Exception {
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, AUDIO_DEVICE,
+                CecOperand.REQUEST_SHORT_AUDIO_DESCRIPTOR, getRequestSadFormatsParams(true));
+        String message = hdmiCecClient.checkExpectedOutput(LogicalAddress.TV,
+                CecOperand.REPORT_SHORT_AUDIO_DESCRIPTOR);
+        /* Each Short Audio Descriptor is 3 bytes long. In the first byte of the params, bits 3-6
+         * will have the audio format. Bit 7 will always 0 for audio format defined in CEA-861-D.
+         * Bits 0-2 represent (Max number of channels - 1). Discard bits 0-2 and check for the
+         * format.
+         * Iterate the params by 3 bytes(6 nibbles) and extract only the first byte(2 nibbles).
+         */
+        for (int i = 0; i < Math.min(mSupportedAudioFormats.size(), MAX_VALID_AUDIO_FORMATS); i++) {
+            int audioFormat =
+                    CecMessage.getParams(message, 6 * i, 6 * i + 2) >>> 3;
+            assertWithMessage("Could not find audio format " + audioFormat)
+                    .that(mSupportedAudioFormats).contains(audioFormat);
+        }
+    }
+
+    /**
+     * Test 11.2.15-14
+     * Tests that the device responds to a <Request Short Audio Descriptor> message with a
+     * <Feature Abort> [“Invalid Operand”] when a <Request Short Audio Descriptor> message is
+     * received with a single [Audio Format ID][Audio Format Code] pair that is not supported.
+     */
+    @Test
+    public void cect_11_2_15_14_InvalidShortAudioDescriptor() throws Exception {
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, AUDIO_DEVICE,
+                CecOperand.REQUEST_SHORT_AUDIO_DESCRIPTOR, getRequestSadFormatsParams(false));
+        String message = hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.FEATURE_ABORT);
+        assertThat(CecOperand.getOperand(CecMessage.getParams(message, 2)))
+                .isEqualTo(CecOperand.REQUEST_SHORT_AUDIO_DESCRIPTOR);
+        assertThat(CecMessage.getParams(message, 2, 4))
+                .isEqualTo(HdmiCecConstants.ABORT_INVALID_OPERAND);
+    }
+
+    /**
+     * Test 11.2.15-16
+     * Tests that the device unmute its volume when it broadcasts a
+     * <Set System Audio Mode> ["On"] message
+     */
+    @Test
+    public void cect_11_2_15_16_UnmuteForSystemAudioRequestOn() throws Exception {
+        muteDevice();
+        sendSystemAudioModeTermination();
+        String message = hdmiCecClient.checkExpectedOutput(CecOperand.SET_SYSTEM_AUDIO_MODE);
+        assertThat(CecMessage.getParams(message)).isEqualTo(OFF);
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, AUDIO_DEVICE,
+                CecOperand.SYSTEM_AUDIO_MODE_REQUEST,
+                CecMessage.formatParams(HdmiCecConstants.TV_PHYSICAL_ADDRESS,
+                    HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH));
+        message = hdmiCecClient.checkExpectedOutput(CecOperand.SET_SYSTEM_AUDIO_MODE);
+        assertThat(CecMessage.getParams(message)).isEqualTo(ON);
+        assertWithMessage("Device muted").that(isDeviceMuted()).isFalse();
+    }
+
+    /**
+     * Test 11.2.15-17
+     * Tests that the device mute its volume when it broadcasts a
+     * <Set System Audio Mode> ["Off"] message
+     */
+    @Test
+    public void cect_11_2_15_17_MuteForSystemAudioRequestOff() throws Exception {
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, AUDIO_DEVICE,
+                CecOperand.SYSTEM_AUDIO_MODE_REQUEST,
+                CecMessage.formatParams(HdmiCecConstants.TV_PHYSICAL_ADDRESS,
+                    HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH));
+        String message = hdmiCecClient.checkExpectedOutput(CecOperand.SET_SYSTEM_AUDIO_MODE);
+        assertThat(CecMessage.getParams(message)).isEqualTo(ON);
+        sendSystemAudioModeTermination();
+        message = hdmiCecClient.checkExpectedOutput(CecOperand.SET_SYSTEM_AUDIO_MODE);
+        assertThat(CecMessage.getParams(message)).isEqualTo(OFF);
+        assertWithMessage("Device not muted").that(isDeviceMuted()).isTrue();
+    }
+
+    /**
+     * Test 11.2.15-18
+     * Tests that the device doesn't broadcast <Set System Audio Mode>
+     * messages if TV responds with a <Feature Abort> message to directly addressed
+     * <Set System Audio Mode> message within the required maximum response time of 1 second.
+     */
+    @Test
+    public void cect_11_2_15_18_SystemAudioModeWithFeatureAbortWithinTime() throws Exception {
+        initiateSystemAudioModeFromDut();
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, AUDIO_DEVICE, CecOperand.FEATURE_ABORT,
+                CecMessage.formatParams(CecOperand.SET_SYSTEM_AUDIO_MODE + "04"));
+        hdmiCecClient.checkOutputDoesNotContainMessage(LogicalAddress.BROADCAST,
+                CecOperand.SET_SYSTEM_AUDIO_MODE);
+    }
+
+    /**
+     * Test 11.2.15-19
+     * Tests that the device doesn't broadcast <Set System Audio Mode>
+     * messages if TV responds with a <Feature Abort> message to directly addressed
+     * <Set System Audio Mode> message within the required maximum response time of 1 second.
+     * The <Set System Audio Mode> message should be initiated from logical address 3.
+     */
+    @Test
+    public void cect_11_2_15_19_SystemAudioModeWithFeatureAbortWithinTime() throws Exception {
+        initateSystemAudioModeFromTuner();
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, AUDIO_DEVICE, CecOperand.FEATURE_ABORT,
+                CecMessage.formatParams(CecOperand.SET_SYSTEM_AUDIO_MODE + "04"));
+        hdmiCecClient.checkOutputDoesNotContainMessage(LogicalAddress.BROADCAST,
+            CecOperand.SET_SYSTEM_AUDIO_MODE);
     }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecDeviceTypeTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecDeviceTypeTest.java
new file mode 100644
index 0000000..4c716cc
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecDeviceTypeTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hdmicec.cts.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Scanner;
+
+/** Tests to see that a valid HDMI CEC device type is declared by the device. */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class HdmiCecDeviceTypeTest extends BaseHostJUnit4Test {
+
+    private static List<String> validTypes = new ArrayList<>(
+        Arrays.asList("", "0", "4", "4,5", "5,4"));
+    /**
+     * Tests that the device declares a valid HDMI CEC device type.
+     */
+    @Test
+    public void checkHdmiCecDeviceType() throws Exception {
+        ITestDevice device = getDevice();
+        String logs = device.executeShellCommand("cmd package list features");
+        Scanner in = new Scanner(logs);
+        while (in.hasNextLine()) {
+            String line = in.nextLine();
+            if (line.equals("feature:android.software.leanback")) {
+                // Remove "" as valid device type if android.software.leanback feature is supported
+                validTypes.remove("");
+            }
+        }
+        String deviceType = device.executeShellCommand("getprop ro.hdmi.device_type");
+        assertThat(deviceType.trim()).isIn(validTypes);
+    }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecDeviceOsdNameTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecDeviceOsdNameTest.java
index cda2951..ef10425 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecDeviceOsdNameTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecDeviceOsdNameTest.java
@@ -16,28 +16,42 @@
 
 package android.hdmicec.cts.playback;
 
-import static org.junit.Assert.assertEquals;
+import static com.google.common.truth.Truth.assertThat;
 
-import android.hdmicec.cts.CecDevice;
 import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
 import android.hdmicec.cts.HdmiCecClientWrapper;
+import android.hdmicec.cts.LogicalAddress;
+import android.hdmicec.cts.RequiredPropertyRule;
+import android.hdmicec.cts.RequiredFeatureRule;
 
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
 import org.junit.Rule;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 import org.junit.Test;
 
 /** HDMI CEC tests related to the device reporting the device OSD name (Section 11.2.11) */
 @RunWith(DeviceJUnit4ClassRunner.class)
 public final class HdmiCecDeviceOsdNameTest extends BaseHostJUnit4Test {
-    private static final CecDevice PLAYBACK_DEVICE = CecDevice.PLAYBACK_1;
+
+    private static final LogicalAddress PLAYBACK_DEVICE = LogicalAddress.PLAYBACK_1;
+
+    public HdmiCecClientWrapper hdmiCecClient = new HdmiCecClientWrapper(LogicalAddress.PLAYBACK_1);
 
     @Rule
-    public HdmiCecClientWrapper hdmiCecClient =
-        new HdmiCecClientWrapper(CecDevice.PLAYBACK_1, this);
+    public RuleChain ruleChain =
+        RuleChain
+            .outerRule(new RequiredFeatureRule(this, LogicalAddress.HDMI_CEC_FEATURE))
+            .around(new RequiredFeatureRule(this, LogicalAddress.LEANBACK_FEATURE))
+            .around(RequiredPropertyRule.asCsvContainsValue(
+                this,
+                LogicalAddress.HDMI_DEVICE_TYPE_PROPERTY,
+                LogicalAddress.PLAYBACK_1.getDeviceType()))
+            .around(hdmiCecClient);
 
     /**
      * Test 11.2.11-1a
@@ -53,9 +67,9 @@
         if (deviceName.length() > nameLength) {
             deviceName = deviceName.substring(0, nameLength).trim();
         }
-        hdmiCecClient.sendCecMessage(CecDevice.TV, CecMessage.GIVE_OSD_NAME);
-        String message = hdmiCecClient.checkExpectedOutput(CecDevice.TV, CecMessage.SET_OSD_NAME);
-        assertEquals(deviceName, hdmiCecClient.getAsciiStringFromMessage(message));
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, CecOperand.GIVE_OSD_NAME);
+        String message = hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.SET_OSD_NAME);
+        assertThat(CecMessage.getAsciiString(message)).isEqualTo(deviceName);
     }
 
     /**
@@ -70,10 +84,10 @@
         String originalName = device.executeShellCommand("settings get global device_name").trim();
         try {
             device.executeShellCommand("settings put global device_name '" + testName + "'");
-            hdmiCecClient.sendCecMessage(CecDevice.TV, CecMessage.GIVE_OSD_NAME);
-            String message = hdmiCecClient.checkExpectedOutput(CecDevice.TV,
-                    CecMessage.SET_OSD_NAME);
-            assertEquals(testName, hdmiCecClient.getAsciiStringFromMessage(message));
+            hdmiCecClient.sendCecMessage(LogicalAddress.TV, CecOperand.GIVE_OSD_NAME);
+            String message = hdmiCecClient.checkExpectedOutput(LogicalAddress.TV,
+                    CecOperand.SET_OSD_NAME);
+            assertThat(CecMessage.getAsciiString(message)).isEqualTo(testName);
         } finally {
             device.executeShellCommand("settings put global device_name '" + originalName + "'");
         }
@@ -85,11 +99,11 @@
      */
     @Test
     public void cect_11_2_11_2_UnregisteredDeviceGiveOsdNameTest() throws Exception {
-        hdmiCecClient.sendCecMessage(CecDevice.PLAYBACK_1, CecMessage.GIVE_OSD_NAME);
-        hdmiCecClient.checkOutputDoesNotContainMessage(CecDevice.PLAYBACK_1,
-                CecMessage.SET_OSD_NAME);
-        hdmiCecClient.sendCecMessage(CecDevice.BROADCAST, CecMessage.GIVE_OSD_NAME);
-        hdmiCecClient.checkOutputDoesNotContainMessage(CecDevice.BROADCAST,
-                CecMessage.SET_OSD_NAME);
+        hdmiCecClient.sendCecMessage(LogicalAddress.PLAYBACK_1, CecOperand.GIVE_OSD_NAME);
+        hdmiCecClient.checkOutputDoesNotContainMessage(LogicalAddress.PLAYBACK_1,
+                CecOperand.SET_OSD_NAME);
+        hdmiCecClient.sendCecMessage(LogicalAddress.BROADCAST, CecOperand.GIVE_OSD_NAME);
+        hdmiCecClient.checkOutputDoesNotContainMessage(LogicalAddress.BROADCAST,
+                CecOperand.SET_OSD_NAME);
     }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecLogicalAddressTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecLogicalAddressTest.java
index 83c9120..d48a6cd 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecLogicalAddressTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecLogicalAddressTest.java
@@ -16,29 +16,43 @@
 
 package android.hdmicec.cts.playback;
 
-import static org.junit.Assert.assertEquals;
+import static com.google.common.truth.Truth.assertThat;
 
-import android.hdmicec.cts.CecDevice;
 import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
 import android.hdmicec.cts.HdmiCecClientWrapper;
 import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogicalAddress;
+import android.hdmicec.cts.RequiredPropertyRule;
+import android.hdmicec.cts.RequiredFeatureRule;
 
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
 import org.junit.Rule;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 import org.junit.Test;
 
 /** HDMI CEC test to verify physical address after device reboot (Section 10.2.3) */
 @RunWith(DeviceJUnit4ClassRunner.class)
 public final class HdmiCecLogicalAddressTest extends BaseHostJUnit4Test {
-    private static final CecDevice PLAYBACK_DEVICE = CecDevice.PLAYBACK_1;
+
+    private static final LogicalAddress PLAYBACK_DEVICE = LogicalAddress.PLAYBACK_1;
+
+    public HdmiCecClientWrapper hdmiCecClient = new HdmiCecClientWrapper(LogicalAddress.PLAYBACK_1);
 
     @Rule
-    public HdmiCecClientWrapper hdmiCecClient =
-        new HdmiCecClientWrapper(CecDevice.PLAYBACK_1, this);
+    public RuleChain ruleChain =
+        RuleChain
+            .outerRule(new RequiredFeatureRule(this, LogicalAddress.HDMI_CEC_FEATURE))
+            .around(new RequiredFeatureRule(this, LogicalAddress.LEANBACK_FEATURE))
+            .around(RequiredPropertyRule.asCsvContainsValue(
+                this,
+                LogicalAddress.HDMI_DEVICE_TYPE_PROPERTY,
+                LogicalAddress.PLAYBACK_1.getDeviceType()))
+            .around(hdmiCecClient);
 
     /**
      * Test 10.2.3-1
@@ -50,7 +64,7 @@
         ITestDevice device = getDevice();
         device.executeShellCommand("reboot");
         device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
-        String message = hdmiCecClient.checkExpectedOutput(CecMessage.REPORT_PHYSICAL_ADDRESS);
-        assertEquals(PLAYBACK_DEVICE, hdmiCecClient.getSourceFromMessage(message));
+        String message = hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_PHYSICAL_ADDRESS);
+        assertThat(CecMessage.getSource(message)).isEqualTo(PLAYBACK_DEVICE);
     }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecOneTouchPlayTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecOneTouchPlayTest.java
index a4ca3d2..56a7df2 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecOneTouchPlayTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecOneTouchPlayTest.java
@@ -16,17 +16,21 @@
 
 package android.hdmicec.cts.playback;
 
-import static org.junit.Assert.assertEquals;
+import static com.google.common.truth.Truth.assertThat;
 
-import android.hdmicec.cts.CecDevice;
 import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
 import android.hdmicec.cts.HdmiCecClientWrapper;
+import android.hdmicec.cts.LogicalAddress;
+import android.hdmicec.cts.RequiredPropertyRule;
+import android.hdmicec.cts.RequiredFeatureRule;
 
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
 import org.junit.Rule;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 import org.junit.Test;
 
@@ -36,9 +40,32 @@
 
     private static final int PHYSICAL_ADDRESS = 0x1000;
 
+    /** Intent to launch the remote pairing activity */
+    private static final String ACTION_CONNECT_INPUT_NORMAL =
+            "com.google.android.intent.action.CONNECT_INPUT";
+
+    /** Package name of the Settings app */
+    private static final String SETTINGS_PACKAGE =
+            "com.android.tv.settings";
+
+    /** The command to broadcast an intent. */
+    private static final String START_COMMAND = "am start -a ";
+
+    /** The command to stop an app. */
+    private static final String FORCE_STOP_COMMAND = "am force-stop ";
+
+    public HdmiCecClientWrapper hdmiCecClient = new HdmiCecClientWrapper(LogicalAddress.PLAYBACK_1);
+
     @Rule
-    public HdmiCecClientWrapper hdmiCecClient =
-        new HdmiCecClientWrapper(CecDevice.PLAYBACK_1, this);
+    public RuleChain ruleChain =
+        RuleChain
+            .outerRule(new RequiredFeatureRule(this, LogicalAddress.HDMI_CEC_FEATURE))
+            .around(new RequiredFeatureRule(this, LogicalAddress.LEANBACK_FEATURE))
+            .around(RequiredPropertyRule.asCsvContainsValue(
+                this,
+                LogicalAddress.HDMI_DEVICE_TYPE_PROPERTY,
+                LogicalAddress.PLAYBACK_1.getDeviceType()))
+            .around(hdmiCecClient);
 
     /**
      * Test 11.2.1-1
@@ -48,9 +75,25 @@
     @Test
     public void cect_11_2_1_1_OneTouchPlay() throws Exception {
         ITestDevice device = getDevice();
+        device.reboot();
         device.executeShellCommand("input keyevent KEYCODE_HOME");
-        hdmiCecClient.checkExpectedOutput(CecDevice.TV, CecMessage.TEXT_VIEW_ON);
-        String message = hdmiCecClient.checkExpectedOutput(CecMessage.ACTIVE_SOURCE);
-        assertEquals(PHYSICAL_ADDRESS, hdmiCecClient.getParamsFromMessage(message));
+        hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.TEXT_VIEW_ON);
+        String message = hdmiCecClient.checkExpectedOutput(CecOperand.ACTIVE_SOURCE);
+        assertThat(CecMessage.getParams(message)).isEqualTo(PHYSICAL_ADDRESS);
+    }
+
+    /**
+     * Tests that the device sends a <TEXT_VIEW_ON> when the pairing activity is started on
+     * device, followed by a <ACTIVE_SOURCE> message.
+     */
+    @Test
+    public void cect_PairingActivity_OneTouchPlay() throws Exception {
+        ITestDevice device = getDevice();
+        device.reboot();
+        device.executeShellCommand(START_COMMAND + ACTION_CONNECT_INPUT_NORMAL);
+        hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.TEXT_VIEW_ON);
+        String message = hdmiCecClient.checkExpectedOutput(CecOperand.ACTIVE_SOURCE);
+        assertThat(CecMessage.getParams(message)).isEqualTo(PHYSICAL_ADDRESS);
+        device.executeShellCommand(FORCE_STOP_COMMAND + SETTINGS_PACKAGE);
     }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecPhysicalAddressTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecPhysicalAddressTest.java
index 91280e7..bae8490 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecPhysicalAddressTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecPhysicalAddressTest.java
@@ -16,18 +16,22 @@
 
 package android.hdmicec.cts.playback;
 
-import static org.junit.Assert.assertEquals;
+import static com.google.common.truth.Truth.assertThat;
 
-import android.hdmicec.cts.CecDevice;
 import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
 import android.hdmicec.cts.HdmiCecClientWrapper;
 import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogicalAddress;
+import android.hdmicec.cts.RequiredPropertyRule;
+import android.hdmicec.cts.RequiredFeatureRule;
 
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
 import org.junit.Rule;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 import org.junit.Test;
 
@@ -35,9 +39,18 @@
 @RunWith(DeviceJUnit4ClassRunner.class)
 public final class HdmiCecPhysicalAddressTest extends BaseHostJUnit4Test {
 
+    public HdmiCecClientWrapper hdmiCecClient = new HdmiCecClientWrapper(LogicalAddress.PLAYBACK_1);
+
     @Rule
-    public HdmiCecClientWrapper hdmiCecClient =
-        new HdmiCecClientWrapper(CecDevice.PLAYBACK_1, this);
+    public RuleChain ruleChain =
+        RuleChain
+            .outerRule(new RequiredFeatureRule(this, LogicalAddress.HDMI_CEC_FEATURE))
+            .around(new RequiredFeatureRule(this, LogicalAddress.LEANBACK_FEATURE))
+            .around(RequiredPropertyRule.asCsvContainsValue(
+                this,
+                LogicalAddress.HDMI_DEVICE_TYPE_PROPERTY,
+                LogicalAddress.PLAYBACK_1.getDeviceType()))
+            .around(hdmiCecClient);
 
     /**
      * Test 10.1.2-1
@@ -49,9 +62,9 @@
         ITestDevice device = getDevice();
         device.executeShellCommand("reboot");
         device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
-        String message = hdmiCecClient.checkExpectedOutput(CecMessage.REPORT_PHYSICAL_ADDRESS);
-        int physicalAddress = hdmiCecClient.getParamsFromMessage(message,
+        String message = hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_PHYSICAL_ADDRESS);
+        int physicalAddress = CecMessage.getParams(message,
             HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH);
-        assertEquals(HdmiCecConstants.PHYSICAL_ADDRESS, physicalAddress);
+        assertThat(HdmiCecConstants.PHYSICAL_ADDRESS).isEqualTo(physicalAddress);
     }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecPowerStatusTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecPowerStatusTest.java
index 911d233..2d8d3ac 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecPowerStatusTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecPowerStatusTest.java
@@ -16,18 +16,22 @@
 
 package android.hdmicec.cts.playback;
 
-import static org.junit.Assert.assertEquals;
+import static com.google.common.truth.Truth.assertThat;
 
-import android.hdmicec.cts.CecDevice;
 import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
 import android.hdmicec.cts.HdmiCecClientWrapper;
 import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogicalAddress;
+import android.hdmicec.cts.RequiredPropertyRule;
+import android.hdmicec.cts.RequiredFeatureRule;
 
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
 import org.junit.Rule;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 import org.junit.Test;
 
@@ -39,12 +43,24 @@
 
     private static final int ON = 0x0;
     private static final int OFF = 0x1;
+    private static final int IN_TRANSITION_TO_STANDBY = 0x3;
 
+    private static final int SLEEP_TIMESTEP_SECONDS = 1;
     private static final int WAIT_TIME = 5;
+    private static final int MAX_SLEEP_TIME = 8;
+
+    public HdmiCecClientWrapper hdmiCecClient = new HdmiCecClientWrapper(LogicalAddress.PLAYBACK_1);
 
     @Rule
-    public HdmiCecClientWrapper hdmiCecClient =
-        new HdmiCecClientWrapper(CecDevice.PLAYBACK_1, this);
+    public RuleChain ruleChain =
+        RuleChain
+            .outerRule(new RequiredFeatureRule(this, LogicalAddress.HDMI_CEC_FEATURE))
+            .around(new RequiredFeatureRule(this, LogicalAddress.LEANBACK_FEATURE))
+            .around(RequiredPropertyRule.asCsvContainsValue(
+                this,
+                LogicalAddress.HDMI_DEVICE_TYPE_PROPERTY,
+                LogicalAddress.PLAYBACK_1.getDeviceType()))
+            .around(hdmiCecClient);
 
     /**
      * Test 11.2.14-1
@@ -56,10 +72,10 @@
         ITestDevice device = getDevice();
         /* Make sure the device is not booting up/in standby */
         device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
-        hdmiCecClient.sendCecMessage(CecDevice.TV, CecMessage.GIVE_POWER_STATUS);
-        String message = hdmiCecClient.checkExpectedOutput(CecDevice.TV,
-                                                            CecMessage.REPORT_POWER_STATUS);
-        assertEquals(ON, hdmiCecClient.getParamsFromMessage(message));
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, CecOperand.GIVE_POWER_STATUS);
+        String message = hdmiCecClient.checkExpectedOutput(LogicalAddress.TV,
+            CecOperand.REPORT_POWER_STATUS);
+        assertThat(CecMessage.getParams(message)).isEqualTo(ON);
     }
 
     /**
@@ -73,12 +89,20 @@
         try {
             /* Make sure the device is not booting up/in standby */
             device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
+            /* Home Key to prevent device from going to deep suspend state */
+            device.executeShellCommand("input keyevent KEYCODE_HOME");
             device.executeShellCommand("input keyevent KEYCODE_SLEEP");
             TimeUnit.SECONDS.sleep(WAIT_TIME);
-            hdmiCecClient.sendCecMessage(CecDevice.TV, CecMessage.GIVE_POWER_STATUS);
-            String message = hdmiCecClient.checkExpectedOutput(CecDevice.TV,
-                                                              CecMessage.REPORT_POWER_STATUS);
-            assertEquals(OFF, hdmiCecClient.getParamsFromMessage(message));
+            int waitTimeSeconds = WAIT_TIME;
+            int powerStatus;
+            do {
+                TimeUnit.SECONDS.sleep(SLEEP_TIMESTEP_SECONDS);
+                waitTimeSeconds += SLEEP_TIMESTEP_SECONDS;
+                hdmiCecClient.sendCecMessage(LogicalAddress.TV, CecOperand.GIVE_POWER_STATUS);
+                powerStatus = CecMessage.getParams(hdmiCecClient.checkExpectedOutput(
+                        LogicalAddress.TV, CecOperand.REPORT_POWER_STATUS));
+            } while (powerStatus == IN_TRANSITION_TO_STANDBY && waitTimeSeconds <= MAX_SLEEP_TIME);
+            assertThat(powerStatus).isEqualTo(OFF);
         } finally {
             /* Wake up the device */
             device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRemoteControlPassThroughTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRemoteControlPassThroughTest.java
index 9ac7eab..b0d927d 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRemoteControlPassThroughTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRemoteControlPassThroughTest.java
@@ -16,23 +16,22 @@
 
 package android.hdmicec.cts.playback;
 
-import static org.junit.Assert.assertEquals;
-
-import android.hdmicec.cts.CecDevice;
 import android.hdmicec.cts.HdmiCecClientWrapper;
 import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogHelper;
+import android.hdmicec.cts.LogicalAddress;
+import android.hdmicec.cts.RequiredPropertyRule;
+import android.hdmicec.cts.RequiredFeatureRule;
 
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
 import org.junit.Rule;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 import org.junit.Test;
 
-import java.util.Scanner;
-import java.util.concurrent.TimeUnit;
-
 /** HDMI CEC test to check if the device reports power status correctly (Section 11.2.13) */
 @RunWith(DeviceJUnit4ClassRunner.class)
 public final class HdmiCecRemoteControlPassThroughTest extends BaseHostJUnit4Test {
@@ -55,29 +54,18 @@
      */
     private static final String CLEAR_COMMAND = String.format("pm clear %s", PACKAGE);
 
-    private static final int WAIT_TIME = 10;
+    public HdmiCecClientWrapper hdmiCecClient = new HdmiCecClientWrapper(LogicalAddress.PLAYBACK_1);
 
     @Rule
-    public HdmiCecClientWrapper hdmiCecClient =
-            new HdmiCecClientWrapper(CecDevice.PLAYBACK_1, this);
-
-    private void lookForLog(String expectedOut) throws Exception {
-        ITestDevice device = getDevice();
-        TimeUnit.SECONDS.sleep(WAIT_TIME);
-        String logs = device.executeAdbCommand("logcat", "-v", "brief", "-d", CLASS + ":I", "*:S");
-        // Search for string.
-        String testString = "";
-        Scanner in = new Scanner(logs);
-        while (in.hasNextLine()) {
-            String line = in.nextLine();
-            if(line.startsWith("I/" + CLASS)) {
-                testString = line.split(":")[1].trim();
-                break;
-            }
-        }
-        device.executeAdbCommand("logcat", "-c");
-        assertEquals(expectedOut, testString);
-    }
+    public RuleChain ruleChain =
+        RuleChain
+            .outerRule(new RequiredFeatureRule(this, LogicalAddress.HDMI_CEC_FEATURE))
+            .around(new RequiredFeatureRule(this, LogicalAddress.LEANBACK_FEATURE))
+            .around(RequiredPropertyRule.asCsvContainsValue(
+                this,
+                LogicalAddress.HDMI_DEVICE_TYPE_PROPERTY,
+                LogicalAddress.PLAYBACK_1.getDeviceType()))
+            .around(hdmiCecClient);
 
     /**
      * Test 11.2.13-1
@@ -93,24 +81,25 @@
         device.executeAdbCommand("logcat", "-c");
         // Start the APK and wait for it to complete.
         device.executeShellCommand(START_COMMAND);
-        hdmiCecClient.sendUserControlPressAndRelease(CecDevice.TV, CecDevice.PLAYBACK_1,
+        hdmiCecClient.sendUserControlPressAndRelease(LogicalAddress.TV, LogicalAddress.PLAYBACK_1,
                 HdmiCecConstants.CEC_CONTROL_UP, false);
-        lookForLog("Short press KEYCODE_DPAD_UP");
-        hdmiCecClient.sendUserControlPressAndRelease(CecDevice.TV, CecDevice.PLAYBACK_1,
+        LogHelper.assertLog(getDevice(), CLASS, "Short press KEYCODE_DPAD_UP");
+        hdmiCecClient.sendUserControlPressAndRelease(LogicalAddress.TV, LogicalAddress.PLAYBACK_1,
                 HdmiCecConstants.CEC_CONTROL_DOWN, false);
-        lookForLog("Short press KEYCODE_DPAD_DOWN");
-        hdmiCecClient.sendUserControlPressAndRelease(CecDevice.TV, CecDevice.PLAYBACK_1,
+        LogHelper.assertLog(getDevice(), CLASS, "Short press KEYCODE_DPAD_DOWN");
+        hdmiCecClient.sendUserControlPressAndRelease(LogicalAddress.TV, LogicalAddress.PLAYBACK_1,
                 HdmiCecConstants.CEC_CONTROL_LEFT, false);
-        lookForLog("Short press KEYCODE_DPAD_LEFT");
-        hdmiCecClient.sendUserControlPressAndRelease(CecDevice.TV, CecDevice.PLAYBACK_1,
+        LogHelper.assertLog(getDevice(), CLASS, "Short press KEYCODE_DPAD_LEFT");
+        hdmiCecClient.sendUserControlPressAndRelease(LogicalAddress.TV, LogicalAddress.PLAYBACK_1,
                 HdmiCecConstants.CEC_CONTROL_RIGHT, false);
-        lookForLog("Short press KEYCODE_DPAD_RIGHT");
-        hdmiCecClient.sendUserControlPressAndRelease(CecDevice.TV, CecDevice.PLAYBACK_1,
+        LogHelper.assertLog(getDevice(), CLASS, "Short press KEYCODE_DPAD_RIGHT");
+        hdmiCecClient.sendUserControlPressAndRelease(LogicalAddress.TV, LogicalAddress.PLAYBACK_1,
                 HdmiCecConstants.CEC_CONTROL_SELECT, false);
-        lookForLog("Short press KEYCODE_DPAD_CENTER");
-        hdmiCecClient.sendUserControlPressAndRelease(CecDevice.TV, CecDevice.PLAYBACK_1,
+        LogHelper.assertLog(getDevice(), CLASS,
+                "Short press KEYCODE_DPAD_CENTER", "Short press KEYCODE_ENTER");
+        hdmiCecClient.sendUserControlPressAndRelease(LogicalAddress.TV, LogicalAddress.PLAYBACK_1,
                 HdmiCecConstants.CEC_CONTROL_BACK, false);
-        lookForLog("Short press KEYCODE_BACK");
+        LogHelper.assertLog(getDevice(), CLASS, "Short press KEYCODE_BACK");
     }
 
     /**
@@ -127,23 +116,24 @@
         device.executeAdbCommand("logcat", "-c");
         // Start the APK and wait for it to complete.
         device.executeShellCommand(START_COMMAND);
-        hdmiCecClient.sendUserControlPressAndRelease(CecDevice.TV, CecDevice.PLAYBACK_1,
+        hdmiCecClient.sendUserControlPressAndRelease(LogicalAddress.TV, LogicalAddress.PLAYBACK_1,
                 HdmiCecConstants.CEC_CONTROL_UP, true);
-        lookForLog("Long press KEYCODE_DPAD_UP");
-        hdmiCecClient.sendUserControlPressAndRelease(CecDevice.TV, CecDevice.PLAYBACK_1,
+        LogHelper.assertLog(getDevice(), CLASS, "Long press KEYCODE_DPAD_UP");
+        hdmiCecClient.sendUserControlPressAndRelease(LogicalAddress.TV, LogicalAddress.PLAYBACK_1,
                 HdmiCecConstants.CEC_CONTROL_DOWN, true);
-        lookForLog("Long press KEYCODE_DPAD_DOWN");
-        hdmiCecClient.sendUserControlPressAndRelease(CecDevice.TV, CecDevice.PLAYBACK_1,
+        LogHelper.assertLog(getDevice(), CLASS, "Long press KEYCODE_DPAD_DOWN");
+        hdmiCecClient.sendUserControlPressAndRelease(LogicalAddress.TV, LogicalAddress.PLAYBACK_1,
                 HdmiCecConstants.CEC_CONTROL_LEFT, true);
-        lookForLog("Long press KEYCODE_DPAD_LEFT");
-        hdmiCecClient.sendUserControlPressAndRelease(CecDevice.TV, CecDevice.PLAYBACK_1,
+        LogHelper.assertLog(getDevice(), CLASS, "Long press KEYCODE_DPAD_LEFT");
+        hdmiCecClient.sendUserControlPressAndRelease(LogicalAddress.TV, LogicalAddress.PLAYBACK_1,
                 HdmiCecConstants.CEC_CONTROL_RIGHT, true);
-        lookForLog("Long press KEYCODE_DPAD_RIGHT");
-        hdmiCecClient.sendUserControlPressAndRelease(CecDevice.TV, CecDevice.PLAYBACK_1,
+        LogHelper.assertLog(getDevice(), CLASS, "Long press KEYCODE_DPAD_RIGHT");
+        hdmiCecClient.sendUserControlPressAndRelease(LogicalAddress.TV, LogicalAddress.PLAYBACK_1,
                 HdmiCecConstants.CEC_CONTROL_SELECT, true);
-        lookForLog("Long press KEYCODE_DPAD_CENTER");
-        hdmiCecClient.sendUserControlPressAndRelease(CecDevice.TV, CecDevice.PLAYBACK_1,
+        LogHelper.assertLog(getDevice(), CLASS,
+                "Long press KEYCODE_DPAD_CENTER", "Long press KEYCODE_ENTER");
+        hdmiCecClient.sendUserControlPressAndRelease(LogicalAddress.TV, LogicalAddress.PLAYBACK_1,
                 HdmiCecConstants.CEC_CONTROL_BACK, true);
-        lookForLog("Long press KEYCODE_BACK");
+        LogHelper.assertLog(getDevice(), CLASS, "Long press KEYCODE_BACK");
     }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRoutingControlTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRoutingControlTest.java
index 4fd2a89..8bec365 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRoutingControlTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRoutingControlTest.java
@@ -16,18 +16,22 @@
 
 package android.hdmicec.cts.playback;
 
-import static org.junit.Assert.assertEquals;
+import static com.google.common.truth.Truth.assertThat;
 
-import android.hdmicec.cts.CecDevice;
 import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
 import android.hdmicec.cts.HdmiCecClientWrapper;
 import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogicalAddress;
+import android.hdmicec.cts.RequiredPropertyRule;
+import android.hdmicec.cts.RequiredFeatureRule;
 
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
 import org.junit.Rule;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 import org.junit.Test;
 
@@ -39,9 +43,18 @@
 
     private static final int PHYSICAL_ADDRESS = 0x1000;
 
+    public HdmiCecClientWrapper hdmiCecClient = new HdmiCecClientWrapper(LogicalAddress.PLAYBACK_1);
+
     @Rule
-    public HdmiCecClientWrapper hdmiCecClient =
-            new HdmiCecClientWrapper(CecDevice.PLAYBACK_1, this);
+    public RuleChain ruleChain =
+        RuleChain
+            .outerRule(new RequiredFeatureRule(this, LogicalAddress.HDMI_CEC_FEATURE))
+            .around(new RequiredFeatureRule(this, LogicalAddress.LEANBACK_FEATURE))
+            .around(RequiredPropertyRule.asCsvContainsValue(
+                this,
+                LogicalAddress.HDMI_DEVICE_TYPE_PROPERTY,
+                LogicalAddress.PLAYBACK_1.getDeviceType()))
+            .around(hdmiCecClient);
 
     /**
      * Test 11.2.2-1
@@ -52,15 +65,14 @@
     public void cect_11_2_2_1_SetStreamPathToDut() throws Exception {
         final long hdmi2Address = 0x2000;
         /* Switch to HDMI2. Setup assumes DUT is connected to HDMI1. */
-        hdmiCecClient.sendCecMessage(CecDevice.PLAYBACK_2, CecDevice.BROADCAST,
-                CecMessage.ACTIVE_SOURCE, hdmiCecClient.formatParams(hdmi2Address));
+        hdmiCecClient.sendCecMessage(LogicalAddress.PLAYBACK_2, LogicalAddress.BROADCAST,
+                CecOperand.ACTIVE_SOURCE, CecMessage.formatParams(hdmi2Address));
         TimeUnit.SECONDS.sleep(3);
-        hdmiCecClient.sendCecMessage(CecDevice.TV, CecDevice.BROADCAST,
-                CecMessage.SET_STREAM_PATH,
-                hdmiCecClient.formatParams(HdmiCecConstants.PHYSICAL_ADDRESS));
-        String message = hdmiCecClient.checkExpectedOutput(CecMessage.ACTIVE_SOURCE);
-        assertEquals(HdmiCecConstants.PHYSICAL_ADDRESS,
-                hdmiCecClient.getParamsFromMessage(message));
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, LogicalAddress.BROADCAST,
+                CecOperand.SET_STREAM_PATH,
+                CecMessage.formatParams(HdmiCecConstants.PHYSICAL_ADDRESS));
+        String message = hdmiCecClient.checkExpectedOutput(CecOperand.ACTIVE_SOURCE);
+        assertThat(CecMessage.getParams(message)).isEqualTo(PHYSICAL_ADDRESS);
     }
 
     /**
@@ -72,10 +84,10 @@
     public void cect_11_2_2_2_RequestActiveSource() throws Exception {
         ITestDevice device = getDevice();
         device.executeShellCommand("input keyevent KEYCODE_HOME");
-        hdmiCecClient.sendCecMessage(CecDevice.TV, CecDevice.BROADCAST,
-            CecMessage.REQUEST_ACTIVE_SOURCE);
-        String message = hdmiCecClient.checkExpectedOutput(CecMessage.ACTIVE_SOURCE);
-        assertEquals(PHYSICAL_ADDRESS, hdmiCecClient.getParamsFromMessage(message));
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, LogicalAddress.BROADCAST,
+            CecOperand.REQUEST_ACTIVE_SOURCE);
+        String message = hdmiCecClient.checkExpectedOutput(CecOperand.ACTIVE_SOURCE);
+        assertThat(CecMessage.getParams(message)).isEqualTo(PHYSICAL_ADDRESS);
     }
 
     /**
@@ -89,10 +101,9 @@
         try {
             device.executeShellCommand("input keyevent KEYCODE_HOME");
             device.executeShellCommand("input keyevent KEYCODE_SLEEP");
-            String message = hdmiCecClient.checkExpectedOutput(CecDevice.TV,
-                    CecMessage.INACTIVE_SOURCE);
-            assertEquals(HdmiCecConstants.PHYSICAL_ADDRESS,
-                    hdmiCecClient.getParamsFromMessage(message));
+            String message = hdmiCecClient.checkExpectedOutput(LogicalAddress.TV,
+                    CecOperand.INACTIVE_SOURCE);
+            assertThat(CecMessage.getParams(message)).isEqualTo(PHYSICAL_ADDRESS);
         } finally {
             /* Wake up the device */
             device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecStartupTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecStartupTest.java
new file mode 100644
index 0000000..06b62f9
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecStartupTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hdmicec.cts.playback;
+
+import static com.google.common.collect.Iterables.filter;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assume.assumeTrue;
+
+import android.hdmicec.cts.CecOperand;
+import android.hdmicec.cts.HdmiCecClientWrapper;
+import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogicalAddress;
+import android.hdmicec.cts.RequiredPropertyRule;
+import android.hdmicec.cts.RequiredFeatureRule;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import javax.annotation.Nullable;
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+import java.util.List;
+
+/**
+ * HDMI CEC test to verify physical address after device reboot (Section 10.2.3)
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class HdmiCecStartupTest extends BaseHostJUnit4Test {
+
+  private static final LogicalAddress PLAYBACK_DEVICE = LogicalAddress.PLAYBACK_1;
+  private static final ImmutableList<CecOperand> necessaryMessages =
+      new ImmutableList.Builder<CecOperand>()
+          .add(CecOperand.REPORT_PHYSICAL_ADDRESS, CecOperand.CEC_VERSION,
+              CecOperand.DEVICE_VENDOR_ID, CecOperand.GIVE_POWER_STATUS).build();
+  private static final ImmutableList<CecOperand> permissibleMessages =
+      new ImmutableList.Builder<CecOperand>()
+          .add(CecOperand.VENDOR_COMMAND, CecOperand.GIVE_DEVICE_VENDOR_ID,
+              CecOperand.SET_OSD_NAME, CecOperand.GIVE_OSD_NAME).build();
+
+  public HdmiCecClientWrapper hdmiCecClient = new HdmiCecClientWrapper(LogicalAddress.PLAYBACK_1);
+
+  @Rule
+  public RuleChain ruleChain =
+      RuleChain
+          .outerRule(new RequiredFeatureRule(this, LogicalAddress.HDMI_CEC_FEATURE))
+          .around(new RequiredFeatureRule(this, LogicalAddress.LEANBACK_FEATURE))
+          .around(RequiredPropertyRule.asCsvContainsValue(
+              this,
+              LogicalAddress.HDMI_DEVICE_TYPE_PROPERTY,
+              PLAYBACK_DEVICE.getDeviceType()))
+          .around(hdmiCecClient);
+
+  /**
+   * Tests that the device sends all the messages that should be sent on startup. It also ensures
+   * that only the device only sends messages which are allowed by the spec.
+   */
+  @Test
+  public void cectVerifyStartupMessages() throws Exception {
+    ITestDevice device = getDevice();
+
+    /* Make sure device is playback only. Not applicable to playback/audio combinations */
+    String deviceTypeCsv = device.executeShellCommand("getprop ro.hdmi.device_type").trim();
+    assumeTrue(deviceTypeCsv.equals(LogicalAddress.PLAYBACK_1.getDeviceType()));
+
+    device.executeShellCommand("reboot");
+    device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
+    /* Monitor CEC messages for 20s after reboot */
+    final List<CecOperand> messagesReceived =
+        hdmiCecClient.getAllMessages(LogicalAddress.PLAYBACK_1, 20);
+
+    /* Predicate to apply on necessaryMessages to ensure that all necessaryMessages are received. */
+    final Predicate<CecOperand> notReceived = new Predicate<CecOperand>() {
+      @Override
+      public boolean apply(@Nullable CecOperand cecOperand) {
+        return !messagesReceived.contains(cecOperand);
+      }
+    };
+
+    /* Predicate to apply on messagesReceived to ensure all messages received are permissible. */
+    final Predicate<CecOperand> notAllowed = new Predicate<CecOperand>() {
+      @Override
+      public boolean apply(@Nullable CecOperand cecOperand) {
+        return !(permissibleMessages.contains(cecOperand) || necessaryMessages.contains(cecOperand));
+      }
+    };
+
+    assertWithMessage("Some necessary messages are missing").
+        that(filter(necessaryMessages, notReceived)).isEmpty();
+
+    assertWithMessage("Some non-permissible messages received").
+        that(filter(messagesReceived, notAllowed)).isEmpty();
+  }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemAudioControlTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemAudioControlTest.java
index 5162a27..229e807 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemAudioControlTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemAudioControlTest.java
@@ -16,31 +16,44 @@
 
 package android.hdmicec.cts.playback;
 
-import static org.junit.Assert.assertEquals;
+import static com.google.common.truth.Truth.assertThat;
 
-import android.hdmicec.cts.CecDevice;
 import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
 import android.hdmicec.cts.HdmiCecClientWrapper;
 import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogicalAddress;
+import android.hdmicec.cts.RequiredPropertyRule;
+import android.hdmicec.cts.RequiredFeatureRule;
 
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
 import org.junit.Rule;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 import org.junit.Test;
 
-import java.util.concurrent.TimeUnit;
-
 /** HDMI CEC test to verify system audio control commands (Section 11.2.15) */
 @RunWith(DeviceJUnit4ClassRunner.class)
 public final class HdmiCecSystemAudioControlTest extends BaseHostJUnit4Test {
-    private static final CecDevice PLAYBACK_DEVICE = CecDevice.PLAYBACK_1;
+
+    private static final LogicalAddress PLAYBACK_DEVICE = LogicalAddress.PLAYBACK_1;
+
+    public HdmiCecClientWrapper hdmiCecClient =
+        new HdmiCecClientWrapper(LogicalAddress.PLAYBACK_1, "-t", "a");
 
     @Rule
-    public HdmiCecClientWrapper hdmiCecClient =
-        new HdmiCecClientWrapper(CecDevice.PLAYBACK_1, this, "-t", "a");
+    public RuleChain ruleChain =
+        RuleChain
+            .outerRule(new RequiredFeatureRule(this, LogicalAddress.HDMI_CEC_FEATURE))
+            .around(new RequiredFeatureRule(this, LogicalAddress.LEANBACK_FEATURE))
+            .around(RequiredPropertyRule.asCsvContainsValue(
+                this,
+                LogicalAddress.HDMI_DEVICE_TYPE_PROPERTY,
+                PLAYBACK_DEVICE.getDeviceType()))
+            .around(hdmiCecClient);
 
     /**
      * Test 11.2.15-10
@@ -49,10 +62,12 @@
     @Test
     public void cect_11_2_15_10_GiveSystemAudioModeStatus() throws Exception {
         ITestDevice device = getDevice();
+        /* Home Key to prevent device from going to deep suspend state */
+        device.executeShellCommand("input keyevent KEYCODE_HOME");
         device.executeShellCommand("input keyevent KEYCODE_SLEEP");
         device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
-        hdmiCecClient.checkExpectedOutput(CecDevice.AUDIO_SYSTEM,
-                CecMessage.GIVE_SYSTEM_AUDIO_MODE_STATUS);
+        hdmiCecClient.checkExpectedOutput(LogicalAddress.AUDIO_SYSTEM,
+                CecOperand.GIVE_SYSTEM_AUDIO_MODE_STATUS);
     }
 
     /**
@@ -64,22 +79,22 @@
     @Test
     public void cect_11_2_15_11_VolumeUpDownUserControlPressed() throws Exception {
         ITestDevice device = getDevice();
-        hdmiCecClient.sendCecMessage(CecDevice.AUDIO_SYSTEM, CecDevice.BROADCAST,
-                CecMessage.SET_SYSTEM_AUDIO_MODE, CecMessage.formatParams(1));
+        hdmiCecClient.sendCecMessage(LogicalAddress.AUDIO_SYSTEM, LogicalAddress.BROADCAST,
+                CecOperand.SET_SYSTEM_AUDIO_MODE, CecMessage.formatParams(1));
         device.executeShellCommand("input keyevent KEYCODE_VOLUME_UP");
-        String message = hdmiCecClient.checkExpectedOutput(CecDevice.AUDIO_SYSTEM,
-                CecMessage.USER_CONTROL_PRESSED);
-        assertEquals(HdmiCecConstants.CEC_CONTROL_VOLUME_UP,
-                hdmiCecClient.getParamsFromMessage(message));
-        hdmiCecClient.checkExpectedOutput(CecDevice.AUDIO_SYSTEM, CecMessage.USER_CONTROL_RELEASED);
+        String message = hdmiCecClient.checkExpectedOutput(LogicalAddress.AUDIO_SYSTEM,
+                CecOperand.USER_CONTROL_PRESSED);
+        assertThat(CecMessage.getParams(message))
+                .isEqualTo(HdmiCecConstants.CEC_CONTROL_VOLUME_UP);
+        hdmiCecClient.checkExpectedOutput(LogicalAddress.AUDIO_SYSTEM, CecOperand.USER_CONTROL_RELEASED);
 
 
         device.executeShellCommand("input keyevent KEYCODE_VOLUME_DOWN");
-        message = hdmiCecClient.checkExpectedOutput(CecDevice.AUDIO_SYSTEM,
-                CecMessage.USER_CONTROL_PRESSED);
-        assertEquals(HdmiCecConstants.CEC_CONTROL_VOLUME_DOWN,
-                hdmiCecClient.getParamsFromMessage(message));
-        hdmiCecClient.checkExpectedOutput(CecDevice.AUDIO_SYSTEM, CecMessage.USER_CONTROL_RELEASED);
+        message = hdmiCecClient.checkExpectedOutput(LogicalAddress.AUDIO_SYSTEM,
+                CecOperand.USER_CONTROL_PRESSED);
+        assertThat(CecMessage.getParams(message))
+                .isEqualTo(HdmiCecConstants.CEC_CONTROL_VOLUME_DOWN);
+        hdmiCecClient.checkExpectedOutput(LogicalAddress.AUDIO_SYSTEM, CecOperand.USER_CONTROL_RELEASED);
     }
 
     /**
@@ -91,13 +106,12 @@
     @Test
     public void cect_11_2_15_12_MuteUserControlPressed() throws Exception {
         ITestDevice device = getDevice();
-        hdmiCecClient.sendCecMessage(CecDevice.AUDIO_SYSTEM, CecDevice.BROADCAST,
-                CecMessage.SET_SYSTEM_AUDIO_MODE, CecMessage.formatParams(1));
+        hdmiCecClient.sendCecMessage(LogicalAddress.AUDIO_SYSTEM, LogicalAddress.BROADCAST,
+                CecOperand.SET_SYSTEM_AUDIO_MODE, CecMessage.formatParams(1));
         device.executeShellCommand("input keyevent KEYCODE_VOLUME_MUTE");
-        String message = hdmiCecClient.checkExpectedOutput(CecDevice.AUDIO_SYSTEM,
-                CecMessage.USER_CONTROL_PRESSED);
-        assertEquals(HdmiCecConstants.CEC_CONTROL_MUTE,
-                hdmiCecClient.getParamsFromMessage(message));
-        hdmiCecClient.checkExpectedOutput(CecDevice.AUDIO_SYSTEM, CecMessage.USER_CONTROL_RELEASED);
+        String message = hdmiCecClient.checkExpectedOutput(LogicalAddress.AUDIO_SYSTEM,
+                CecOperand.USER_CONTROL_PRESSED);
+        assertThat(CecMessage.getParams(message)).isEqualTo(HdmiCecConstants.CEC_CONTROL_MUTE);
+        hdmiCecClient.checkExpectedOutput(LogicalAddress.AUDIO_SYSTEM, CecOperand.USER_CONTROL_RELEASED);
     }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemInformationTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemInformationTest.java
index c6df9f7..5e59a8f 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemInformationTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemInformationTest.java
@@ -16,20 +16,25 @@
 
 package android.hdmicec.cts.playback;
 
-import static org.junit.Assert.assertEquals;
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assume.assumeTrue;
 
 import android.hdmicec.cts.CecClientMessage;
-import android.hdmicec.cts.CecDevice;
 import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
 import android.hdmicec.cts.HdmiCecClientWrapper;
 import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogicalAddress;
+import android.hdmicec.cts.RequiredPropertyRule;
+import android.hdmicec.cts.RequiredFeatureRule;
 
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
 import org.junit.Rule;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 import org.junit.Test;
 
@@ -42,9 +47,18 @@
 
     private static final String PROPERTY_LOCALE = "persist.sys.locale";
 
+    public HdmiCecClientWrapper hdmiCecClient = new HdmiCecClientWrapper(LogicalAddress.PLAYBACK_1);
+
     @Rule
-    public HdmiCecClientWrapper hdmiCecClient =
-        new HdmiCecClientWrapper(CecDevice.PLAYBACK_1, this);
+    public RuleChain ruleChain =
+        RuleChain
+            .outerRule(new RequiredFeatureRule(this, LogicalAddress.HDMI_CEC_FEATURE))
+            .around(new RequiredFeatureRule(this, LogicalAddress.LEANBACK_FEATURE))
+            .around(RequiredPropertyRule.asCsvContainsValue(
+                this,
+                LogicalAddress.HDMI_DEVICE_TYPE_PROPERTY,
+                LogicalAddress.PLAYBACK_1.getDeviceType()))
+            .around(hdmiCecClient);
 
     /**
      * Test 11.2.6-1
@@ -52,7 +66,7 @@
      */
     @Test
     public void cect_11_2_6_1_Ack() throws Exception {
-        String command = CecClientMessage.POLL + " " + CecDevice.PLAYBACK_1;
+        String command = CecClientMessage.POLL + " " + LogicalAddress.PLAYBACK_1;
         String expectedOutput = "POLL sent";
         hdmiCecClient.sendConsoleMessage(command);
         if (!hdmiCecClient.checkConsoleOutput(expectedOutput)) {
@@ -67,13 +81,13 @@
      */
     @Test
     public void cect_11_2_6_2_GivePhysicalAddress() throws Exception {
-        hdmiCecClient.sendCecMessage(CecMessage.GIVE_PHYSICAL_ADDRESS);
-        String message = hdmiCecClient.checkExpectedOutput(CecMessage.REPORT_PHYSICAL_ADDRESS);
+        hdmiCecClient.sendCecMessage(CecOperand.GIVE_PHYSICAL_ADDRESS);
+        String message = hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_PHYSICAL_ADDRESS);
         /* The checkExpectedOutput has already verified the first 4 nibbles of the message. We
             * have to verify the last 6 nibbles */
-        int receivedParams = hdmiCecClient.getParamsFromMessage(message);
-        assertEquals(HdmiCecConstants.PHYSICAL_ADDRESS, receivedParams >> 8);
-        assertEquals(HdmiCecConstants.PLAYBACK_DEVICE_TYPE, receivedParams & 0xFF);
+        int receivedParams = CecMessage.getParams(message);
+        assertThat(HdmiCecConstants.PHYSICAL_ADDRESS).isEqualTo(receivedParams >> 8);
+        assertThat(HdmiCecConstants.PLAYBACK_DEVICE_TYPE).isEqualTo(receivedParams & 0xFF);
     }
 
     /**
@@ -82,11 +96,10 @@
      */
     @Test
     public void cect_11_2_6_6_GiveCecVersion() throws Exception {
-        hdmiCecClient.sendCecMessage(CecDevice.TV, CecMessage.GET_CEC_VERSION);
-        String message = hdmiCecClient.checkExpectedOutput(CecDevice.TV,
-                                                            CecMessage.CEC_VERSION);
-
-        assertEquals(CEC_VERSION_NUMBER, hdmiCecClient.getParamsFromMessage(message));
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, CecOperand.GET_CEC_VERSION);
+        String message = hdmiCecClient.checkExpectedOutput(LogicalAddress.TV,
+                                                            CecOperand.CEC_VERSION);
+        assertThat(CecMessage.getParams(message)).isEqualTo(CEC_VERSION_NUMBER);
     }
 
     /**
@@ -95,11 +108,11 @@
      */
     @Test
     public void cect_11_2_6_7_GetMenuLanguage() throws Exception {
-        hdmiCecClient.sendCecMessage(CecDevice.TV, CecMessage.GET_MENU_LANGUAGE);
-        String message = hdmiCecClient.checkExpectedOutput(CecDevice.TV, CecMessage.FEATURE_ABORT);
-        int abortedOpcode = hdmiCecClient.getParamsFromMessage(message,
-                CecMessage.GET_MENU_LANGUAGE.toString().length());
-        assertEquals(CecMessage.getMessage(abortedOpcode), CecMessage.GET_MENU_LANGUAGE);
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, CecOperand.GET_MENU_LANGUAGE);
+        String message = hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.FEATURE_ABORT);
+        int abortedOpcode = CecMessage.getParams(message,
+                CecOperand.GET_MENU_LANGUAGE.toString().length());
+        assertThat(CecOperand.getOperand(abortedOpcode)).isEqualTo(CecOperand.GET_MENU_LANGUAGE);
     }
 
     private String getSystemLocale() throws Exception {
@@ -133,9 +146,9 @@
         final String language = originalLanguage.equals("spa") ? "eng" : "spa";
         final String newLanguage = originalLanguage.equals("spa") ? "en" : "es";
         try {
-            hdmiCecClient.sendCecMessage(CecDevice.TV, CecDevice.BROADCAST,
-                    CecMessage.SET_MENU_LANGUAGE, hdmiCecClient.convertStringToHexParams(language));
-            assertEquals(newLanguage, extractLanguage(getSystemLocale()));
+            hdmiCecClient.sendCecMessage(LogicalAddress.TV, LogicalAddress.BROADCAST,
+                    CecOperand.SET_MENU_LANGUAGE, CecMessage.convertStringToHexParams(language));
+            assertThat(extractLanguage(getSystemLocale())).isEqualTo(newLanguage);
         } finally {
             setSystemLocale(locale);
         }
@@ -152,9 +165,9 @@
         final String originalLanguage = extractLanguage(locale);
         final String language = "spb";
         try {
-            hdmiCecClient.sendCecMessage(CecDevice.TV, CecDevice.BROADCAST,
-                    CecMessage.SET_MENU_LANGUAGE, hdmiCecClient.convertStringToHexParams(language));
-            assertEquals(originalLanguage, extractLanguage(getSystemLocale()));
+            hdmiCecClient.sendCecMessage(LogicalAddress.TV, LogicalAddress.BROADCAST,
+                    CecOperand.SET_MENU_LANGUAGE, CecMessage.convertStringToHexParams(language));
+            assertThat(extractLanguage(getSystemLocale())).isEqualTo(originalLanguage);
         } finally {
             setSystemLocale(locale);
         }
@@ -172,9 +185,9 @@
         final String originalLanguage = extractLanguage(locale);
         final String language = originalLanguage.equals("spa") ? "eng" : "spa";
         try {
-            hdmiCecClient.sendCecMessage(CecDevice.RECORDER_1, CecDevice.BROADCAST,
-                    CecMessage.SET_MENU_LANGUAGE, hdmiCecClient.convertStringToHexParams(language));
-            assertEquals(originalLanguage, extractLanguage(getSystemLocale()));
+            hdmiCecClient.sendCecMessage(LogicalAddress.RECORDER_1, LogicalAddress.BROADCAST,
+                    CecOperand.SET_MENU_LANGUAGE, CecMessage.convertStringToHexParams(language));
+            assertThat(extractLanguage(getSystemLocale())).isEqualTo(originalLanguage);
         } finally {
             setSystemLocale(locale);
         }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemStandbyTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemStandbyTest.java
index 1aa8fe4..369542e 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemStandbyTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemStandbyTest.java
@@ -16,20 +16,21 @@
 
 package android.hdmicec.cts.playback;
 
-import static org.junit.Assert.assertEquals;
+import static com.google.common.truth.Truth.assertThat;
 
-import android.hdmicec.cts.CecDevice;
-import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
 import android.hdmicec.cts.HdmiCecClientWrapper;
 import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogicalAddress;
+import android.hdmicec.cts.RequiredPropertyRule;
+import android.hdmicec.cts.RequiredFeatureRule;
 
 import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.device.TestDeviceState;
-import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
 import org.junit.Rule;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 import org.junit.Test;
 
@@ -38,14 +39,24 @@
 /** HDMI CEC test to verify the device handles standby correctly (Section 11.2.3) */
 @RunWith(DeviceJUnit4ClassRunner.class)
 public final class HdmiCecSystemStandbyTest extends BaseHostJUnit4Test {
-    private static final CecDevice PLAYBACK_DEVICE = CecDevice.PLAYBACK_1;
+
+    private static final LogicalAddress PLAYBACK_DEVICE = LogicalAddress.PLAYBACK_1;
 
     private static final String HDMI_CONTROL_DEVICE_AUTO_OFF =
             "hdmi_control_auto_device_off_enabled";
 
+    public HdmiCecClientWrapper hdmiCecClient = new HdmiCecClientWrapper(LogicalAddress.PLAYBACK_1);
+
     @Rule
-    public HdmiCecClientWrapper hdmiCecClient =
-        new HdmiCecClientWrapper(CecDevice.PLAYBACK_1, this);
+    public RuleChain ruleChain =
+        RuleChain
+            .outerRule(new RequiredFeatureRule(this, LogicalAddress.HDMI_CEC_FEATURE))
+            .around(new RequiredFeatureRule(this, LogicalAddress.LEANBACK_FEATURE))
+            .around(RequiredPropertyRule.asCsvContainsValue(
+                this,
+                LogicalAddress.HDMI_DEVICE_TYPE_PROPERTY,
+                PLAYBACK_DEVICE.getDeviceType()))
+            .around(hdmiCecClient);
 
     private boolean setHdmiControlDeviceAutoOff(boolean turnOn) throws Exception {
         ITestDevice device = getDevice();
@@ -58,16 +69,16 @@
         return val.equals("1") ? true : false;
     }
 
-    private void checkDeviceAsleepAfterStandbySent(CecDevice source, CecDevice destination)
+    private void checkDeviceAsleepAfterStandbySent(LogicalAddress source, LogicalAddress destination)
             throws Exception {
         ITestDevice device = getDevice();
         try {
             device.executeShellCommand("input keyevent KEYCODE_HOME");
             TimeUnit.SECONDS.sleep(5);
-            hdmiCecClient.sendCecMessage(source, destination, CecMessage.STANDBY);
+            hdmiCecClient.sendCecMessage(source, destination, CecOperand.STANDBY);
             TimeUnit.SECONDS.sleep(5);
             String wakeState = device.executeShellCommand("dumpsys power | grep mWakefulness=");
-            assertEquals("mWakefulness=Asleep", wakeState.trim());
+            assertThat(wakeState.trim()).isEqualTo("mWakefulness=Asleep");
         } finally {
             /* Wake up the device */
             device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
@@ -84,19 +95,19 @@
         getDevice().waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
         try {
             TimeUnit.SECONDS.sleep(5);
-            checkDeviceAsleepAfterStandbySent(CecDevice.TV, CecDevice.BROADCAST);
+            checkDeviceAsleepAfterStandbySent(LogicalAddress.TV, LogicalAddress.BROADCAST);
             /* Wake up the TV */
-            hdmiCecClient.sendConsoleMessage("on " + CecDevice.TV);
-            checkDeviceAsleepAfterStandbySent(CecDevice.RECORDER_1, CecDevice.BROADCAST);
+            hdmiCecClient.sendConsoleMessage("on " + LogicalAddress.TV);
+            checkDeviceAsleepAfterStandbySent(LogicalAddress.RECORDER_1, LogicalAddress.BROADCAST);
             /* Wake up the TV */
-            hdmiCecClient.sendConsoleMessage("on " + CecDevice.TV);
-            checkDeviceAsleepAfterStandbySent(CecDevice.AUDIO_SYSTEM, CecDevice.BROADCAST);
+            hdmiCecClient.sendConsoleMessage("on " + LogicalAddress.TV);
+            checkDeviceAsleepAfterStandbySent(LogicalAddress.AUDIO_SYSTEM, LogicalAddress.BROADCAST);
             /* Wake up the TV */
-            hdmiCecClient.sendConsoleMessage("on " + CecDevice.TV);
-            checkDeviceAsleepAfterStandbySent(CecDevice.PLAYBACK_2, CecDevice.BROADCAST);
+            hdmiCecClient.sendConsoleMessage("on " + LogicalAddress.TV);
+            checkDeviceAsleepAfterStandbySent(LogicalAddress.PLAYBACK_2, LogicalAddress.BROADCAST);
         } finally {
             /* Wake up the TV */
-            hdmiCecClient.sendConsoleMessage("on " + CecDevice.TV);
+            hdmiCecClient.sendConsoleMessage("on " + LogicalAddress.TV);
         }
     }
 
@@ -108,11 +119,11 @@
     public void cect_11_2_3_3_HandleAddressedStandby() throws Exception {
         getDevice().executeShellCommand("reboot");
         getDevice().waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
-        checkDeviceAsleepAfterStandbySent(CecDevice.TV, CecDevice.PLAYBACK_1);
-        checkDeviceAsleepAfterStandbySent(CecDevice.RECORDER_1, CecDevice.PLAYBACK_1);
-        checkDeviceAsleepAfterStandbySent(CecDevice.AUDIO_SYSTEM, CecDevice.PLAYBACK_1);
-        checkDeviceAsleepAfterStandbySent(CecDevice.PLAYBACK_2, CecDevice.PLAYBACK_1);
-        checkDeviceAsleepAfterStandbySent(CecDevice.BROADCAST, CecDevice.PLAYBACK_1);
+        checkDeviceAsleepAfterStandbySent(LogicalAddress.TV, LogicalAddress.PLAYBACK_1);
+        checkDeviceAsleepAfterStandbySent(LogicalAddress.RECORDER_1, LogicalAddress.PLAYBACK_1);
+        checkDeviceAsleepAfterStandbySent(LogicalAddress.AUDIO_SYSTEM, LogicalAddress.PLAYBACK_1);
+        checkDeviceAsleepAfterStandbySent(LogicalAddress.PLAYBACK_2, LogicalAddress.PLAYBACK_1);
+        checkDeviceAsleepAfterStandbySent(LogicalAddress.BROADCAST, LogicalAddress.PLAYBACK_1);
     }
 
     /**
@@ -125,7 +136,7 @@
         boolean wasOn = setHdmiControlDeviceAutoOff(false);
         try {
             device.executeShellCommand("input keyevent KEYCODE_SLEEP");
-            hdmiCecClient.checkOutputDoesNotContainMessage(CecDevice.BROADCAST, CecMessage.STANDBY);
+            hdmiCecClient.checkOutputDoesNotContainMessage(LogicalAddress.BROADCAST, CecOperand.STANDBY);
             device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
         } finally {
             setHdmiControlDeviceAutoOff(wasOn);
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecVendorCommandsTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecVendorCommandsTest.java
index df9bc5a..edff394 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecVendorCommandsTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecVendorCommandsTest.java
@@ -16,30 +16,44 @@
 
 package android.hdmicec.cts.playback;
 
-import static org.junit.Assert.assertNotEquals;
+import static com.google.common.truth.Truth.assertThat;
 
-import android.hdmicec.cts.CecDevice;
 import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
 import android.hdmicec.cts.HdmiCecClientWrapper;
 import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogicalAddress;
+import android.hdmicec.cts.RequiredPropertyRule;
+import android.hdmicec.cts.RequiredFeatureRule;
 
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
 import org.junit.Rule;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 import org.junit.Test;
 
 /** HDMI CEC test to verify device vendor specific commands (Section 11.2.9) */
 @RunWith(DeviceJUnit4ClassRunner.class)
 public final class HdmiCecVendorCommandsTest extends BaseHostJUnit4Test {
-    private static final CecDevice PLAYBACK_DEVICE = CecDevice.PLAYBACK_1;
+
+    private static final LogicalAddress PLAYBACK_DEVICE = LogicalAddress.PLAYBACK_1;
     private static final int INCORRECT_VENDOR_ID = 0x0;
 
+    public HdmiCecClientWrapper hdmiCecClient = new HdmiCecClientWrapper(LogicalAddress.PLAYBACK_1);
+
     @Rule
-    public HdmiCecClientWrapper hdmiCecClient =
-        new HdmiCecClientWrapper(CecDevice.PLAYBACK_1, this);
+    public RuleChain ruleChain =
+        RuleChain
+            .outerRule(new RequiredFeatureRule(this, LogicalAddress.HDMI_CEC_FEATURE))
+            .around(new RequiredFeatureRule(this, LogicalAddress.LEANBACK_FEATURE))
+            .around(RequiredPropertyRule.asCsvContainsValue(
+                this,
+                LogicalAddress.HDMI_DEVICE_TYPE_PROPERTY,
+                PLAYBACK_DEVICE.getDeviceType()))
+            .around(hdmiCecClient);
 
     /**
      * Test 11.2.9-1
@@ -48,10 +62,14 @@
      */
     @Test
     public void cect_11_2_9_1_GiveDeviceVendorId() throws Exception {
-        for (CecDevice cecDevice : CecDevice.values()) {
-            hdmiCecClient.sendCecMessage(cecDevice, CecMessage.GIVE_DEVICE_VENDOR_ID);
-            String message = hdmiCecClient.checkExpectedOutput(CecMessage.DEVICE_VENDOR_ID);
-            assertNotEquals(INCORRECT_VENDOR_ID, hdmiCecClient.getParamsFromMessage(message));
+        for (LogicalAddress logicalAddress : LogicalAddress.values()) {
+            // Skip the logical address of this device
+            if (logicalAddress == PLAYBACK_DEVICE) {
+                continue;
+            }
+            hdmiCecClient.sendCecMessage(logicalAddress, CecOperand.GIVE_DEVICE_VENDOR_ID);
+            String message = hdmiCecClient.checkExpectedOutput(CecOperand.DEVICE_VENDOR_ID);
+            assertThat(CecMessage.getParams(message)).isNotEqualTo(INCORRECT_VENDOR_ID);
         }
     }
 
@@ -65,7 +83,7 @@
         ITestDevice device = getDevice();
         device.executeShellCommand("reboot");
         device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
-        String message = hdmiCecClient.checkExpectedOutput(CecMessage.DEVICE_VENDOR_ID);
-        assertNotEquals(INCORRECT_VENDOR_ID, hdmiCecClient.getParamsFromMessage(message));
+        String message = hdmiCecClient.checkExpectedOutput(CecOperand.DEVICE_VENDOR_ID);
+        assertThat(CecMessage.getParams(message)).isNotEqualTo(INCORRECT_VENDOR_ID);
     }
 }