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.common;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.junit.Assume.assumeTrue;
22 
23 import android.hdmicec.cts.BaseHdmiCecCtsTest;
24 import android.hdmicec.cts.CecMessage;
25 import android.hdmicec.cts.CecOperand;
26 import android.hdmicec.cts.HdmiCecConstants;
27 import android.hdmicec.cts.LogHelper;
28 import android.hdmicec.cts.LogicalAddress;
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.concurrent.TimeUnit;
40 
41 /** HDMI CEC test to verify that device ignores invalid messages (Section 12) */
42 @RunWith(DeviceJUnit4ClassRunner.class)
43 public final class HdmiCecInvalidMessagesTest extends BaseHdmiCecCtsTest {
44 
45     /** The package name of the APK. */
46     private static final String PACKAGE = "android.hdmicec.app";
47 
48     /** The class name of the main activity in the APK. */
49     private static final String CLASS = "HdmiCecKeyEventCapture";
50 
51     /** The command to launch the main activity. */
52     private static final String START_COMMAND =
53             String.format(
54                     "am start -W -a android.intent.action.MAIN -n %s/%s.%s",
55                     PACKAGE, PACKAGE, CLASS);
56 
57     /** The command to clear the main activity. */
58     private static final String CLEAR_COMMAND = String.format("pm clear %s", PACKAGE);
59 
60     private LogicalAddress source;
61     private LogicalAddress targetLogicalAddress;
62     private LogicalAddress mNonLocalPlaybackAddress;
63 
64     @Rule
65     public RuleChain ruleChain =
66             RuleChain.outerRule(CecRules.requiresCec(this))
67                     .around(CecRules.requiresLeanback(this))
68                     .around(CecRules.requiresPhysicalDevice(this))
69                     .around(hdmiCecClient);
70 
71     @Before
setupLogicalAddresses()72     public void setupLogicalAddresses() throws Exception {
73         source = (hasDeviceType(HdmiCecConstants.CEC_DEVICE_TYPE_TV)) ? LogicalAddress.RECORDER_1
74                                                                       : LogicalAddress.TV;
75         targetLogicalAddress = getTargetLogicalAddress();
76         mNonLocalPlaybackAddress =
77                 (targetLogicalAddress == LogicalAddress.PLAYBACK_1)
78                         ? LogicalAddress.PLAYBACK_2
79                         : LogicalAddress.PLAYBACK_1;
80     }
81 
getUnusedPhysicalAddress(int usedValue)82     private int getUnusedPhysicalAddress(int usedValue) {
83         return (usedValue == 0x2000) ? 0x3000 : 0x2000;
84     }
85 
reportPhysicalAddress(LogicalAddress logicalAddress, int physicalAddress, int deviceType)86     private void reportPhysicalAddress(LogicalAddress logicalAddress, int physicalAddress,
87             int deviceType) throws Exception {
88         String formattedPhysicalAddress = CecMessage.formatParams(physicalAddress,
89                 HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH);
90         String formattedDeviceType = CecMessage.formatParams(deviceType);
91         hdmiCecClient.sendCecMessage(
92                 logicalAddress,
93                 LogicalAddress.BROADCAST,
94                 CecOperand.REPORT_PHYSICAL_ADDRESS,
95                 formattedPhysicalAddress + formattedDeviceType
96         );
97     }
98 
99     /**
100      * Test 12-1
101      *
102      * <p>Tests that the device ignores every broadcast only message that is received as directly
103      * addressed.
104      */
105     @Test
cect_12_1_BroadcastReceivedAsDirectlyAddressed()106     public void cect_12_1_BroadcastReceivedAsDirectlyAddressed() throws Exception {
107         /* <Set Menu Language> */
108         assumeTrue("Language should be editable for this test", isLanguageEditable());
109         final String locale = getSystemLocale();
110         final String originalLanguage = extractLanguage(locale);
111         final String language = originalLanguage.equals("spa") ? "eng" : "spa";
112         try {
113             hdmiCecClient.sendCecMessage(
114                     source,
115                     CecOperand.SET_MENU_LANGUAGE,
116                     CecMessage.convertStringToHexParams(language));
117             assertThat(originalLanguage).isEqualTo(extractLanguage(getSystemLocale()));
118         } finally {
119             // If the language was incorrectly changed during the test, restore it.
120             setSystemLocale(locale);
121         }
122     }
123 
124     /**
125      * Test 12-2
126      *
127      * <p>Tests that the device ignores directly addressed message {@code <GET_CEC_VERSION>} if
128      * received as a broadcast message
129      */
130     @Test
cect_12_2_DirectlyAddressedReceivedAsBroadcast_getCecVersion()131     public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_getCecVersion() throws Exception {
132         hdmiCecClient.sendCecMessage(source, LogicalAddress.BROADCAST, CecOperand.GET_CEC_VERSION);
133         hdmiCecClient.checkOutputDoesNotContainMessage(source, CecOperand.CEC_VERSION);
134     }
135 
136     /**
137      * Test 12-2
138      *
139      * <p>Tests that the device ignores directly addressed message {@code <GIVE_PHYSICAL_ADDRESS>}
140      * if received as a broadcast message
141      */
142     @Test
cect_12_2_DirectlyAddressedReceivedAsBroadcast_givePhysicalAddress()143     public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_givePhysicalAddress()
144             throws Exception {
145         hdmiCecClient.sendCecMessage(
146                 source, LogicalAddress.BROADCAST, CecOperand.GIVE_PHYSICAL_ADDRESS);
147         hdmiCecClient.checkOutputDoesNotContainMessage(
148                 LogicalAddress.BROADCAST, CecOperand.REPORT_PHYSICAL_ADDRESS);
149     }
150 
151     /**
152      * Test 12-2
153      *
154      * <p>Tests that the device ignores directly addressed message {@code <GIVE_POWER_STATUS>} if
155      * received as a broadcast message
156      */
157     @Test
cect_12_2_DirectlyAddressedReceivedAsBroadcast_givePowerStatus()158     public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_givePowerStatus() throws Exception {
159         hdmiCecClient.sendCecMessage(
160                 source, LogicalAddress.BROADCAST, CecOperand.GIVE_POWER_STATUS);
161         hdmiCecClient.checkOutputDoesNotContainMessage(source, CecOperand.REPORT_POWER_STATUS);
162     }
163 
164     /**
165      * Test 12-2
166      *
167      * <p>Tests that the device ignores directly addressed message {@code <GIVE_DEVICE_VENDOR_ID>}
168      * if received as a broadcast message
169      */
170     @Test
cect_12_2_DirectlyAddressedReceivedAsBroadcast_giveDeviceVendorId()171     public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_giveDeviceVendorId()
172             throws Exception {
173         hdmiCecClient.sendCecMessage(
174                 source, LogicalAddress.BROADCAST, CecOperand.GIVE_DEVICE_VENDOR_ID);
175         hdmiCecClient.checkOutputDoesNotContainMessage(
176                 LogicalAddress.BROADCAST, CecOperand.DEVICE_VENDOR_ID);
177     }
178 
179     /**
180      * Test 12-2
181      *
182      * <p>Tests that the device ignores directly addressed message {@code <GIVE_OSD_NAME>} if
183      * received as a broadcast message
184      */
185     @Test
cect_12_2_DirectlyAddressedReceivedAsBroadcast_giveOsdName()186     public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_giveOsdName() throws Exception {
187         hdmiCecClient.sendCecMessage(source, LogicalAddress.BROADCAST, CecOperand.GIVE_OSD_NAME);
188         hdmiCecClient.checkOutputDoesNotContainMessage(source, CecOperand.SET_OSD_NAME);
189     }
190 
191     /**
192      * Test 12-2
193      *
194      * <p>Tests that the device ignores directly addressed message {@code <USER_CONTROL_PRESSED>} if
195      * received as a broadcast message
196      */
197     @Test
cect_12_2_DirectlyAddressedReceivedAsBroadcast_userControlPressed()198     public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_userControlPressed()
199             throws Exception {
200         ITestDevice device = getDevice();
201         // Clear activity
202         device.executeShellCommand(CLEAR_COMMAND);
203         // Clear logcat.
204         device.executeAdbCommand("logcat", "-c");
205         // Start the APK and wait for it to complete.
206         device.executeShellCommand(START_COMMAND);
207         hdmiCecClient.sendUserControlPressAndRelease(
208                 source, LogicalAddress.BROADCAST, HdmiCecConstants.CEC_KEYCODE_UP, false);
209         LogHelper.assertLogDoesNotContain(getDevice(), CLASS, "Short press KEYCODE_DPAD_UP");
210     }
211 
212     /**
213      * <p>Tests that the device ignores a directly addressed message {@code <GIVE_PHYSICAL_ADDRESS>}
214      * if received as a broadcast message and its source is the device's logical address
215      */
216     @Test
cect_IgnoreDirectlyAddressedFromSameSource()217     public void cect_IgnoreDirectlyAddressedFromSameSource()
218             throws Exception {
219         hdmiCecClient.sendCecMessage(
220                 targetLogicalAddress, targetLogicalAddress, CecOperand.GIVE_PHYSICAL_ADDRESS);
221         hdmiCecClient.checkOutputDoesNotContainMessage(
222                 targetLogicalAddress, CecOperand.REPORT_PHYSICAL_ADDRESS);
223     }
224 
225     /**
226      * <p>Tests that the device ignores a broadcasted message {@code <REQUEST_ACTIVE_SOURCE>} if its
227      * source has the logical address equal to device's logical address
228      * Change the active source to another device (a new Playback) first.
229      */
230     @Test
cect_IgnoreBroadcastedFromSameSource()231     public void cect_IgnoreBroadcastedFromSameSource()
232             throws Exception {
233         String previousPowerStateChange = setPowerStateChangeOnActiveSourceLost(
234                 HdmiCecConstants.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_NONE);
235         try {
236             int dumpsysPhysicalAddress = getDumpsysPhysicalAddress();
237             // Add a new playback device in the network.
238             int playbackPhysicalAddress = getUnusedPhysicalAddress(dumpsysPhysicalAddress);
239             reportPhysicalAddress(
240                     mNonLocalPlaybackAddress,
241                     playbackPhysicalAddress,
242                     HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE);
243             // Make the new Playback the active source.
244             hdmiCecClient.broadcastActiveSource(mNonLocalPlaybackAddress, playbackPhysicalAddress);
245             // Wait for the <Active Source> message to be processed by the DUT.
246             TimeUnit.SECONDS.sleep(HdmiCecConstants.DEVICE_WAIT_TIME_SECONDS);
247 
248             // Press Home key and the DUT shall broadcast an <Active Source> message.
249             ITestDevice device = getDevice();
250             device.executeShellCommand("input keyevent KEYCODE_HOME");
251             hdmiCecClient.checkExpectedOutput(
252                     LogicalAddress.BROADCAST, CecOperand.ACTIVE_SOURCE);
253             // The DUT shouldn't send <Active Source> again.
254             hdmiCecClient.sendCecMessage(
255                     targetLogicalAddress, LogicalAddress.BROADCAST, CecOperand.REQUEST_ACTIVE_SOURCE);
256             hdmiCecClient.checkOutputDoesNotContainMessage(
257                     LogicalAddress.BROADCAST, CecOperand.ACTIVE_SOURCE);
258         } finally {
259             // Restore the previous power state change.
260             setPowerStateChangeOnActiveSourceLost(previousPowerStateChange);
261         }
262     }
263 
264     /**
265      * <p>Tests that the device ignores a directly addressed message {@code <GIVE_POWER_STATUS>} if
266      * coming from the unregistered address F. This message should only be sent from a device with
267      * an allocated logical address
268      */
269     @Test
cect_IgnoreDirectlyAddressedFromUnknownAddress_giveDevicePowerStatus()270     public void cect_IgnoreDirectlyAddressedFromUnknownAddress_giveDevicePowerStatus()
271             throws Exception {
272         hdmiCecClient.sendCecMessage(
273                 LogicalAddress.UNKNOWN, targetLogicalAddress, CecOperand.GIVE_POWER_STATUS);
274         hdmiCecClient.checkOutputDoesNotContainMessage(
275                 LogicalAddress.UNKNOWN, CecOperand.REPORT_POWER_STATUS);
276     }
277 
278     /**
279      * <p>Tests that the device process a directly addressed message {@code <GIVE_PHYSICAL_ADDRESS>}
280      * if coming from the unregistered address F
281      */
282     @Test
cect_ProcessAddressedFromUnknownAddress_givePhysicalAddress()283     public void cect_ProcessAddressedFromUnknownAddress_givePhysicalAddress()
284             throws Exception {
285         hdmiCecClient.sendCecMessage(
286                 LogicalAddress.UNKNOWN, targetLogicalAddress, CecOperand.GIVE_PHYSICAL_ADDRESS);
287         hdmiCecClient.checkExpectedOutput(
288                 LogicalAddress.UNKNOWN, CecOperand.REPORT_PHYSICAL_ADDRESS);
289     }
290 }
291