1 /*
2  * Copyright (C) 2014 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 com.android.server.hdmi;
18 
19 import static com.android.server.hdmi.HdmiCecMessageValidator.ValidationResult;
20 
21 import android.annotation.Nullable;
22 
23 import com.android.server.hdmi.Constants.FeatureOpcode;
24 
25 import libcore.util.EmptyArray;
26 
27 import java.util.Arrays;
28 import java.util.Objects;
29 
30 /**
31  * Encapsulates the data that defines an HDMI-CEC message: source and destination address,
32  * command (or opcode), and optional parameters. Also stores the result of validating the message.
33  *
34  * Subclasses of this class represent specific messages that have been validated, and expose their
35  * parsed parameters.
36  */
37 public class HdmiCecMessage {
38     public static final byte[] EMPTY_PARAM = EmptyArray.BYTE;
39 
40     private final int mSource;
41     private final int mDestination;
42 
43     private final int mOpcode;
44     private final byte[] mParams;
45 
46     private final int mValidationResult;
47 
48     /**
49      * Constructor that allows the caller to provide the validation result.
50      * Must only be called by subclasses; other callers should use {@link #build}.
51      */
HdmiCecMessage(int source, int destination, int opcode, byte[] params, @ValidationResult int validationResult)52     protected HdmiCecMessage(int source, int destination, int opcode, byte[] params,
53             @ValidationResult int validationResult) {
54         mSource = source;
55         mDestination = destination;
56         mOpcode = opcode & 0xFF;
57         mParams = Arrays.copyOf(params, params.length);
58         mValidationResult = validationResult;
59     }
60 
HdmiCecMessage(int source, int destination, int opcode, byte[] params)61     private HdmiCecMessage(int source, int destination, int opcode, byte[] params) {
62         this(source, destination, opcode, params,
63                 HdmiCecMessageValidator.validate(source, destination, opcode & 0xFF, params));
64     }
65 
66     /**
67      * Constructs and validates a message. The result of validation will be accessible via
68      * {@link #getValidationResult}.
69      *
70      * Intended for parsing incoming messages, as it takes raw bytes as message parameters.
71      *
72      * If the opcode has its own subclass of this one, this method will instead validate and build
73      * the message using the logic in that class. If successful, it will return a validated
74      * instance of that class that exposes parsed parameters.
75      */
build(int source, int destination, int opcode, byte[] params)76     static HdmiCecMessage build(int source, int destination, int opcode, byte[] params) {
77         switch (opcode & 0xFF) {
78             case Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL:
79                 return SetAudioVolumeLevelMessage.build(source, destination, params);
80             case Constants.MESSAGE_REPORT_FEATURES:
81                 return ReportFeaturesMessage.build(source, destination, params);
82             default:
83                 return new HdmiCecMessage(source, destination, opcode & 0xFF, params);
84         }
85     }
86 
build(int source, int destination, int opcode)87     static HdmiCecMessage build(int source, int destination, int opcode) {
88         return new HdmiCecMessage(source, destination, opcode, EMPTY_PARAM);
89     }
90 
91     @Override
equals(@ullable Object message)92     public boolean equals(@Nullable Object message) {
93         if (message instanceof HdmiCecMessage) {
94             HdmiCecMessage that = (HdmiCecMessage) message;
95             return this.mSource == that.getSource()
96                     && this.mDestination == that.getDestination()
97                     && this.mOpcode == that.getOpcode()
98                     && Arrays.equals(this.mParams, that.getParams())
99                     && this.mValidationResult == that.getValidationResult();
100         }
101         return false;
102     }
103 
104     @Override
hashCode()105     public int hashCode() {
106         return Objects.hash(
107             mSource,
108             mDestination,
109             mOpcode,
110             Arrays.hashCode(mParams));
111     }
112 
113     /**
114      * Return the source address field of the message. It is the logical address
115      * of the device which generated the message.
116      *
117      * @return source address
118      */
getSource()119     public int getSource() {
120         return mSource;
121     }
122 
123     /**
124      * Return the destination address field of the message. It is the logical address
125      * of the device to which the message is sent.
126      *
127      * @return destination address
128      */
getDestination()129     public int getDestination() {
130         return mDestination;
131     }
132 
133     /**
134      * Return the opcode field of the message. It is the type of the message that
135      * tells the destination device what to do.
136      *
137      * @return opcode
138      */
getOpcode()139     public int getOpcode() {
140         return mOpcode;
141     }
142 
143     /**
144      * Return the parameter field of the message. The contents of parameter varies
145      * from opcode to opcode, and is used together with opcode to describe
146      * the action for the destination device to take.
147      *
148      * @return parameter
149      */
getParams()150     public byte[] getParams() {
151         return mParams;
152     }
153 
154     /**
155      * Returns the validation result of the message.
156      */
getValidationResult()157     public int getValidationResult() {
158         return mValidationResult;
159     }
160 
161     @Override
toString()162     public String toString() {
163         StringBuilder s = new StringBuilder();
164         s.append(String.format("<%s> %X%X:%02X",
165                 opcodeToString(mOpcode), mSource, mDestination, mOpcode));
166         if (mParams.length > 0) {
167             if (filterMessageParameters(mOpcode)) {
168                 s.append(String.format(" <Redacted len=%d>", mParams.length));
169             } else if (isUserControlPressedMessage(mOpcode)) {
170                 s.append(
171                         String.format(
172                                 " <Keycode type = %s>", HdmiCecKeycode.getKeycodeType(mParams[0])));
173             } else {
174                 for (byte data : mParams) {
175                     s.append(String.format(":%02X", data));
176                 }
177             }
178         }
179         if (mValidationResult != HdmiCecMessageValidator.OK) {
180             s.append(String.format(" <Validation error: %s>",
181                     validationResultToString(mValidationResult)));
182         }
183         return s.toString();
184     }
185 
validationResultToString(@alidationResult int validationResult)186     private static String validationResultToString(@ValidationResult int validationResult) {
187         switch (validationResult) {
188             case HdmiCecMessageValidator.OK:
189                 return "ok";
190             case HdmiCecMessageValidator.ERROR_SOURCE:
191                 return "invalid source";
192             case HdmiCecMessageValidator.ERROR_DESTINATION:
193                 return "invalid destination";
194             case HdmiCecMessageValidator.ERROR_PARAMETER:
195                 return "invalid parameters";
196             case HdmiCecMessageValidator.ERROR_PARAMETER_SHORT:
197                 return "short parameters";
198             default:
199                 return "unknown error";
200         }
201     }
202 
opcodeToString(@eatureOpcode int opcode)203     private static String opcodeToString(@FeatureOpcode int opcode) {
204         switch (opcode) {
205             case Constants.MESSAGE_FEATURE_ABORT:
206                 return "Feature Abort";
207             case Constants.MESSAGE_IMAGE_VIEW_ON:
208                 return "Image View On";
209             case Constants.MESSAGE_TUNER_STEP_INCREMENT:
210                 return "Tuner Step Increment";
211             case Constants.MESSAGE_TUNER_STEP_DECREMENT:
212                 return "Tuner Step Decrement";
213             case Constants.MESSAGE_TUNER_DEVICE_STATUS:
214                 return "Tuner Device Status";
215             case Constants.MESSAGE_GIVE_TUNER_DEVICE_STATUS:
216                 return "Give Tuner Device Status";
217             case Constants.MESSAGE_RECORD_ON:
218                 return "Record On";
219             case Constants.MESSAGE_RECORD_STATUS:
220                 return "Record Status";
221             case Constants.MESSAGE_RECORD_OFF:
222                 return "Record Off";
223             case Constants.MESSAGE_TEXT_VIEW_ON:
224                 return "Text View On";
225             case Constants.MESSAGE_RECORD_TV_SCREEN:
226                 return "Record Tv Screen";
227             case Constants.MESSAGE_GIVE_DECK_STATUS:
228                 return "Give Deck Status";
229             case Constants.MESSAGE_DECK_STATUS:
230                 return "Deck Status";
231             case Constants.MESSAGE_SET_MENU_LANGUAGE:
232                 return "Set Menu Language";
233             case Constants.MESSAGE_CLEAR_ANALOG_TIMER:
234                 return "Clear Analog Timer";
235             case Constants.MESSAGE_SET_ANALOG_TIMER:
236                 return "Set Analog Timer";
237             case Constants.MESSAGE_TIMER_STATUS:
238                 return "Timer Status";
239             case Constants.MESSAGE_STANDBY:
240                 return "Standby";
241             case Constants.MESSAGE_PLAY:
242                 return "Play";
243             case Constants.MESSAGE_DECK_CONTROL:
244                 return "Deck Control";
245             case Constants.MESSAGE_TIMER_CLEARED_STATUS:
246                 return "Timer Cleared Status";
247             case Constants.MESSAGE_USER_CONTROL_PRESSED:
248                 return "User Control Pressed";
249             case Constants.MESSAGE_USER_CONTROL_RELEASED:
250                 return "User Control Release";
251             case Constants.MESSAGE_GIVE_OSD_NAME:
252                 return "Give Osd Name";
253             case Constants.MESSAGE_SET_OSD_NAME:
254                 return "Set Osd Name";
255             case Constants.MESSAGE_SET_OSD_STRING:
256                 return "Set Osd String";
257             case Constants.MESSAGE_SET_TIMER_PROGRAM_TITLE:
258                 return "Set Timer Program Title";
259             case Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST:
260                 return "System Audio Mode Request";
261             case Constants.MESSAGE_GIVE_AUDIO_STATUS:
262                 return "Give Audio Status";
263             case Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE:
264                 return "Set System Audio Mode";
265             case Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL:
266                 return "Set Audio Volume Level";
267             case Constants.MESSAGE_REPORT_AUDIO_STATUS:
268                 return "Report Audio Status";
269             case Constants.MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS:
270                 return "Give System Audio Mode Status";
271             case Constants.MESSAGE_SYSTEM_AUDIO_MODE_STATUS:
272                 return "System Audio Mode Status";
273             case Constants.MESSAGE_ROUTING_CHANGE:
274                 return "Routing Change";
275             case Constants.MESSAGE_ROUTING_INFORMATION:
276                 return "Routing Information";
277             case Constants.MESSAGE_ACTIVE_SOURCE:
278                 return "Active Source";
279             case Constants.MESSAGE_GIVE_PHYSICAL_ADDRESS:
280                 return "Give Physical Address";
281             case Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS:
282                 return "Report Physical Address";
283             case Constants.MESSAGE_REQUEST_ACTIVE_SOURCE:
284                 return "Request Active Source";
285             case Constants.MESSAGE_SET_STREAM_PATH:
286                 return "Set Stream Path";
287             case Constants.MESSAGE_DEVICE_VENDOR_ID:
288                 return "Device Vendor Id";
289             case Constants.MESSAGE_VENDOR_COMMAND:
290                 return "Vendor Command";
291             case Constants.MESSAGE_VENDOR_REMOTE_BUTTON_DOWN:
292                 return "Vendor Remote Button Down";
293             case Constants.MESSAGE_VENDOR_REMOTE_BUTTON_UP:
294                 return "Vendor Remote Button Up";
295             case Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID:
296                 return "Give Device Vendor Id";
297             case Constants.MESSAGE_MENU_REQUEST:
298                 return "Menu Request";
299             case Constants.MESSAGE_MENU_STATUS:
300                 return "Menu Status";
301             case Constants.MESSAGE_GIVE_DEVICE_POWER_STATUS:
302                 return "Give Device Power Status";
303             case Constants.MESSAGE_REPORT_POWER_STATUS:
304                 return "Report Power Status";
305             case Constants.MESSAGE_GET_MENU_LANGUAGE:
306                 return "Get Menu Language";
307             case Constants.MESSAGE_SELECT_ANALOG_SERVICE:
308                 return "Select Analog Service";
309             case Constants.MESSAGE_SELECT_DIGITAL_SERVICE:
310                 return "Select Digital Service";
311             case Constants.MESSAGE_SET_DIGITAL_TIMER:
312                 return "Set Digital Timer";
313             case Constants.MESSAGE_CLEAR_DIGITAL_TIMER:
314                 return "Clear Digital Timer";
315             case Constants.MESSAGE_SET_AUDIO_RATE:
316                 return "Set Audio Rate";
317             case Constants.MESSAGE_INACTIVE_SOURCE:
318                 return "InActive Source";
319             case Constants.MESSAGE_CEC_VERSION:
320                 return "Cec Version";
321             case Constants.MESSAGE_GET_CEC_VERSION:
322                 return "Get Cec Version";
323             case Constants.MESSAGE_VENDOR_COMMAND_WITH_ID:
324                 return "Vendor Command With Id";
325             case Constants.MESSAGE_CLEAR_EXTERNAL_TIMER:
326                 return "Clear External Timer";
327             case Constants.MESSAGE_SET_EXTERNAL_TIMER:
328                 return "Set External Timer";
329             case Constants.MESSAGE_REPORT_SHORT_AUDIO_DESCRIPTOR:
330                 return "Report Short Audio Descriptor";
331             case Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR:
332                 return "Request Short Audio Descriptor";
333             case Constants.MESSAGE_INITIATE_ARC:
334                 return "Initiate ARC";
335             case Constants.MESSAGE_REPORT_ARC_INITIATED:
336                 return "Report ARC Initiated";
337             case Constants.MESSAGE_REPORT_ARC_TERMINATED:
338                 return "Report ARC Terminated";
339             case Constants.MESSAGE_REQUEST_ARC_INITIATION:
340                 return "Request ARC Initiation";
341             case Constants.MESSAGE_REQUEST_ARC_TERMINATION:
342                 return "Request ARC Termination";
343             case Constants.MESSAGE_GIVE_FEATURES:
344                 return "Give Features";
345             case Constants.MESSAGE_REPORT_FEATURES:
346                 return "Report Features";
347             case Constants.MESSAGE_REQUEST_CURRENT_LATENCY:
348                 return "Request Current Latency";
349             case Constants.MESSAGE_REPORT_CURRENT_LATENCY:
350                 return "Report Current Latency";
351             case Constants.MESSAGE_TERMINATE_ARC:
352                 return "Terminate ARC";
353             case Constants.MESSAGE_CDC_MESSAGE:
354                 return "Cdc Message";
355             case Constants.MESSAGE_ABORT:
356                 return "Abort";
357             default:
358                 return String.format("Opcode: %02X", opcode);
359         }
360     }
361 
filterMessageParameters(int opcode)362     private static boolean filterMessageParameters(int opcode) {
363         switch (opcode) {
364             case Constants.MESSAGE_USER_CONTROL_RELEASED:
365             case Constants.MESSAGE_SET_OSD_NAME:
366             case Constants.MESSAGE_SET_OSD_STRING:
367             case Constants.MESSAGE_VENDOR_COMMAND:
368             case Constants.MESSAGE_VENDOR_REMOTE_BUTTON_DOWN:
369             case Constants.MESSAGE_VENDOR_REMOTE_BUTTON_UP:
370             case Constants.MESSAGE_VENDOR_COMMAND_WITH_ID:
371                 return true;
372             default:
373                 return false;
374         }
375     }
376 
isUserControlPressedMessage(int opcode)377     private static boolean isUserControlPressedMessage(int opcode) {
378         return Constants.MESSAGE_USER_CONTROL_PRESSED == opcode;
379     }
380 
isCecTransportMessage(int opcode)381     static boolean isCecTransportMessage(int opcode) {
382         switch (opcode) {
383             case Constants.MESSAGE_REQUEST_CURRENT_LATENCY:
384             case Constants.MESSAGE_REPORT_CURRENT_LATENCY:
385             case Constants.MESSAGE_CDC_MESSAGE:
386                 return true;
387             default:
388                 return false;
389         }
390     }
391 }
392 
393