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