1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.hdmicec.cts.tv;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static com.google.common.truth.Truth.assertWithMessage;
21 
22 import android.hdmicec.cts.BaseHdmiCecCtsTest;
23 import android.hdmicec.cts.CecMessage;
24 import android.hdmicec.cts.CecOperand;
25 import android.hdmicec.cts.HdmiCecConstants;
26 import android.hdmicec.cts.LogicalAddress;
27 import android.hdmicec.cts.error.CecClientWrapperException;
28 import android.hdmicec.cts.error.ErrorCodes;
29 
30 import com.android.tradefed.device.ITestDevice;
31 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
32 
33 import org.junit.Before;
34 import org.junit.Rule;
35 import org.junit.Test;
36 import org.junit.rules.RuleChain;
37 import org.junit.runner.RunWith;
38 
39 import java.util.ArrayList;
40 import java.util.Arrays;
41 import java.util.HashMap;
42 import java.util.List;
43 import java.util.concurrent.TimeUnit;
44 
45 /** HDMI CEC test to check Remote Control Pass Through behaviour (Sections 11.1.13) */
46 @RunWith(DeviceJUnit4ClassRunner.class)
47 public final class HdmiCecRemoteControlPassThroughTest extends BaseHdmiCecCtsTest {
48 
49     private static final int WAIT_TIME_MS = 1000;
50 
51     private HashMap<String, Integer> remoteControlKeys = new HashMap<String, Integer>();
52     private HashMap<String, Integer> remoteControlAudioKeys = new HashMap<String, Integer>();
53 
54     @Rule
55     public RuleChain ruleChain =
56             RuleChain.outerRule(CecRules.requiresCec(this))
57                     .around(CecRules.requiresLeanback(this))
58                     .around(CecRules.requiresPhysicalDevice(this))
59                     .around(CecRules.requiresDeviceType(this, HdmiCecConstants.CEC_DEVICE_TYPE_TV))
60                     .around(hdmiCecClient);
61 
HdmiCecRemoteControlPassThroughTest()62     public HdmiCecRemoteControlPassThroughTest() {
63         super(HdmiCecConstants.CEC_DEVICE_TYPE_TV, "-t", "r", "-t", "p", "-t", "t", "-t", "a");
64         mapRemoteControlKeys();
65     }
66 
67     @Before
checkForInitialActiveSourceMessage()68     public void checkForInitialActiveSourceMessage() throws CecClientWrapperException {
69         try {
70             /*
71              * Check for the broadcasted <ACTIVE_SOURCE> message from Recorder_1, which was sent as
72              * a response to <SET_STREAM_PATH> message from the TV.
73              */
74             String message =
75                     hdmiCecClient.checkExpectedMessageFromClient(
76                             LogicalAddress.RECORDER_1, CecOperand.ACTIVE_SOURCE);
77         } catch (CecClientWrapperException e) {
78             if (e.getErrorCode() != ErrorCodes.CecMessageNotFound) {
79                 throw e;
80             } else {
81                 /*
82                  * In case the TV does not send <Set Stream Path> to CEC adapter, or the client does
83                  * not make recorder active source, broadcast an <Active Source> message from the
84                  * adapter.
85                  */
86                 hdmiCecClient.broadcastActiveSource(
87                         LogicalAddress.RECORDER_1, hdmiCecClient.getPhysicalAddress());
88                 try {
89                     TimeUnit.MILLISECONDS.sleep(WAIT_TIME_MS);
90                 } catch (InterruptedException ex) {
91                     // Do nothing
92                 }
93             }
94         }
95     }
96 
97     /**
98      * Test 11.1.13-1
99      *
100      * <p>Tests that the DUT sends the appropriate messages for remote control pass through to a
101      * Recording Device.
102      */
103     @Test
cect_11_1_13_1_RemoteControlMessagesToRecorder()104     public void cect_11_1_13_1_RemoteControlMessagesToRecorder() throws Exception {
105         hdmiCecClient.broadcastActiveSource(
106                 LogicalAddress.RECORDER_1, hdmiCecClient.getPhysicalAddress());
107         TimeUnit.MILLISECONDS.sleep(WAIT_TIME_MS);
108         validateKeyeventToUserControlPress(LogicalAddress.RECORDER_1, remoteControlKeys);
109     }
110 
111     /**
112      * Test 11.1.13-2
113      *
114      * <p>Tests that the DUT sends the appropriate messages for remote control pass through to a
115      * Playback Device.
116      */
117     @Test
cect_11_1_13_2_RemoteControlMessagesToPlayback()118     public void cect_11_1_13_2_RemoteControlMessagesToPlayback() throws Exception {
119         hdmiCecClient.broadcastActiveSource(
120                 LogicalAddress.PLAYBACK_1, hdmiCecClient.getPhysicalAddress());
121         TimeUnit.MILLISECONDS.sleep(WAIT_TIME_MS);
122         validateKeyeventToUserControlPress(LogicalAddress.PLAYBACK_1, remoteControlKeys);
123     }
124 
125     /**
126      * Test 11.1.13-3
127      *
128      * <p>Tests that the DUT sends the appropriate messages for remote control pass through to a
129      * Tuner Device.
130      */
131     @Test
cect_11_1_13_3_RemoteControlMessagesToTuner()132     public void cect_11_1_13_3_RemoteControlMessagesToTuner() throws Exception {
133         hdmiCecClient.broadcastActiveSource(
134                 LogicalAddress.TUNER_1, hdmiCecClient.getPhysicalAddress());
135         TimeUnit.MILLISECONDS.sleep(WAIT_TIME_MS);
136         validateKeyeventToUserControlPress(LogicalAddress.TUNER_1, remoteControlKeys);
137     }
138 
139     /**
140      * Test 11.1.13-4
141      *
142      * <p>Tests that the DUT sends the appropriate messages for remote control pass through to an
143      * Audio System.
144      */
145     @Test
cect_11_1_13_4_RemoteControlMessagesToAudioSystem()146     public void cect_11_1_13_4_RemoteControlMessagesToAudioSystem() throws Exception {
147         hdmiCecClient.broadcastActiveSource(
148                 LogicalAddress.AUDIO_SYSTEM, hdmiCecClient.getPhysicalAddress());
149         TimeUnit.MILLISECONDS.sleep(WAIT_TIME_MS);
150         validateKeyeventToUserControlPress(LogicalAddress.AUDIO_SYSTEM, remoteControlAudioKeys);
151     }
152 
153     /**
154      * Test 11.1.13-5
155      *
156      * <p>Tests that the DUT behaves sensibly when the remote control pass through feature is
157      * invoked in a system with multiple devices of the same type.
158      */
159     @Test
cect_11_1_13_5_RemoteControlPassthroughWithMultipleDevices()160     public void cect_11_1_13_5_RemoteControlPassthroughWithMultipleDevices() throws Exception {
161         hdmiCecClient.broadcastReportPhysicalAddress(LogicalAddress.RECORDER_1);
162         hdmiCecClient.broadcastReportPhysicalAddress(LogicalAddress.RECORDER_2, 0x2100);
163         validateMultipleKeyeventToUserControlPress(
164                 LogicalAddress.RECORDER_1, LogicalAddress.RECORDER_2);
165     }
166 
mapRemoteControlKeys()167     private void mapRemoteControlKeys() {
168         remoteControlKeys.put("DPAD_UP", HdmiCecConstants.CEC_KEYCODE_UP);
169         remoteControlKeys.put("DPAD_DOWN", HdmiCecConstants.CEC_KEYCODE_DOWN);
170         remoteControlKeys.put("DPAD_LEFT", HdmiCecConstants.CEC_KEYCODE_LEFT);
171         remoteControlKeys.put("DPAD_RIGHT", HdmiCecConstants.CEC_KEYCODE_RIGHT);
172         remoteControlAudioKeys.put("VOLUME_UP", HdmiCecConstants.CEC_KEYCODE_VOLUME_UP);
173         remoteControlAudioKeys.put("VOLUME_DOWN", HdmiCecConstants.CEC_KEYCODE_VOLUME_DOWN);
174         remoteControlAudioKeys.put("VOLUME_MUTE", HdmiCecConstants.CEC_KEYCODE_MUTE);
175     }
176 
validateKeyeventToUserControlPress(LogicalAddress toDevice , HashMap<String, Integer> keyMaps)177     private void validateKeyeventToUserControlPress(LogicalAddress toDevice
178             , HashMap<String, Integer> keyMaps) throws Exception {
179         ITestDevice device = getDevice();
180         for (String remoteKey : keyMaps.keySet()) {
181             device.executeShellCommand("input keyevent KEYCODE_" + remoteKey);
182             String message =
183                     hdmiCecClient.checkExpectedOutput(toDevice, CecOperand.USER_CONTROL_PRESSED);
184             assertThat(CecMessage.getParams(message)).isEqualTo(keyMaps.get(remoteKey));
185             hdmiCecClient.checkExpectedOutput(toDevice, CecOperand.USER_CONTROL_RELEASED);
186         }
187     }
188 
validateMultipleKeyeventToUserControlPress( LogicalAddress device1, LogicalAddress device2)189     private void validateMultipleKeyeventToUserControlPress(
190             LogicalAddress device1, LogicalAddress device2) throws Exception {
191         ITestDevice device = getDevice();
192         for (String remoteKey : remoteControlKeys.keySet()) {
193             List<LogicalAddress> destinationAddresses = new ArrayList<>();
194             device.executeShellCommand("input keyevent KEYCODE_" + remoteKey);
195             destinationAddresses =
196                     hdmiCecClient.getAllDestLogicalAddresses(
197                             CecOperand.USER_CONTROL_PRESSED,
198                             CecMessage.formatParams(remoteControlKeys.get(remoteKey)),
199                             4);
200             assertWithMessage("UCP message forwarded to more than one device.")
201                     .that(destinationAddresses.containsAll(Arrays.asList(device1, device2)))
202                     .isFalse();
203             assertWithMessage("UCP message was not forwarded to any of the device.")
204                     .that(destinationAddresses)
205                     .containsAnyIn(Arrays.asList(device1, device2));
206         }
207     }
208 }
209