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.Constants.ADDR_BACKUP_1; 20 import static com.android.server.hdmi.Constants.ADDR_BACKUP_2; 21 import static com.android.server.hdmi.Constants.ADDR_TV; 22 23 import android.annotation.Nullable; 24 import android.hardware.hdmi.HdmiControlManager; 25 import android.hardware.hdmi.HdmiDeviceInfo; 26 import android.util.Slog; 27 import android.util.SparseArray; 28 import android.util.TypedXmlPullParser; 29 import android.util.Xml; 30 31 import com.android.internal.util.HexDump; 32 import com.android.internal.util.IndentingPrintWriter; 33 import com.android.server.hdmi.Constants.AbortReason; 34 import com.android.server.hdmi.Constants.AudioCodec; 35 import com.android.server.hdmi.Constants.FeatureOpcode; 36 import com.android.server.hdmi.Constants.PathRelationship; 37 38 import com.google.android.collect.Lists; 39 40 import org.xmlpull.v1.XmlPullParser; 41 import org.xmlpull.v1.XmlPullParserException; 42 43 import java.io.IOException; 44 import java.io.InputStream; 45 import java.util.ArrayList; 46 import java.util.Arrays; 47 import java.util.Collections; 48 import java.util.HashMap; 49 import java.util.List; 50 import java.util.Map; 51 import java.util.Objects; 52 53 /** 54 * Various utilities to handle HDMI CEC messages. 55 */ 56 final class HdmiUtils { 57 58 private static final String TAG = "HdmiUtils"; 59 60 private static final Map<Integer, List<Integer>> ADDRESS_TO_TYPE = 61 new HashMap<Integer, List<Integer>>() { 62 { 63 put(Constants.ADDR_TV, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV)); 64 put(Constants.ADDR_RECORDER_1, 65 Lists.newArrayList(HdmiDeviceInfo.DEVICE_RECORDER)); 66 put(Constants.ADDR_RECORDER_2, 67 Lists.newArrayList(HdmiDeviceInfo.DEVICE_RECORDER)); 68 put(Constants.ADDR_TUNER_1, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER)); 69 put(Constants.ADDR_PLAYBACK_1, 70 Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK)); 71 put(Constants.ADDR_AUDIO_SYSTEM, 72 Lists.newArrayList(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)); 73 put(Constants.ADDR_TUNER_2, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER)); 74 put(Constants.ADDR_TUNER_3, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER)); 75 put(Constants.ADDR_PLAYBACK_2, 76 Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK)); 77 put(Constants.ADDR_RECORDER_3, 78 Lists.newArrayList(HdmiDeviceInfo.DEVICE_RECORDER)); 79 put(Constants.ADDR_TUNER_4, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER)); 80 put(Constants.ADDR_PLAYBACK_3, 81 Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK)); 82 put(Constants.ADDR_BACKUP_1, Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK, 83 HdmiDeviceInfo.DEVICE_RECORDER, HdmiDeviceInfo.DEVICE_TUNER, 84 HdmiDeviceInfo.DEVICE_VIDEO_PROCESSOR)); 85 put(Constants.ADDR_BACKUP_2, Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK, 86 HdmiDeviceInfo.DEVICE_RECORDER, HdmiDeviceInfo.DEVICE_TUNER, 87 HdmiDeviceInfo.DEVICE_VIDEO_PROCESSOR)); 88 put(Constants.ADDR_SPECIFIC_USE, Lists.newArrayList(ADDR_TV)); 89 put(Constants.ADDR_UNREGISTERED, Collections.emptyList()); 90 } 91 }; 92 93 private static final String[] DEFAULT_NAMES = { 94 "TV", 95 "Recorder_1", 96 "Recorder_2", 97 "Tuner_1", 98 "Playback_1", 99 "AudioSystem", 100 "Tuner_2", 101 "Tuner_3", 102 "Playback_2", 103 "Recorder_3", 104 "Tuner_4", 105 "Playback_3", 106 "Backup_1", 107 "Backup_2", 108 "Secondary_TV", 109 }; 110 111 /** 112 * Return value of {@link #getLocalPortFromPhysicalAddress(int, int)} 113 */ 114 static final int TARGET_NOT_UNDER_LOCAL_DEVICE = -1; 115 static final int TARGET_SAME_PHYSICAL_ADDRESS = 0; 116 HdmiUtils()117 private HdmiUtils() { /* cannot be instantiated */ } 118 119 /** 120 * Check if the given logical address is valid. A logical address is valid 121 * if it is one allocated for an actual device which allows communication 122 * with other logical devices. 123 * 124 * @param address logical address 125 * @return true if the given address is valid 126 */ isValidAddress(int address)127 static boolean isValidAddress(int address) { 128 return (ADDR_TV <= address && address <= Constants.ADDR_SPECIFIC_USE); 129 } 130 isEligibleAddressForDevice(int deviceType, int logicalAddress)131 static boolean isEligibleAddressForDevice(int deviceType, int logicalAddress) { 132 return isValidAddress(logicalAddress) 133 && ADDRESS_TO_TYPE.get(logicalAddress).contains(deviceType); 134 } 135 isEligibleAddressForCecVersion(int cecVersion, int logicalAddress)136 static boolean isEligibleAddressForCecVersion(int cecVersion, int logicalAddress) { 137 if (isValidAddress(logicalAddress)) { 138 if (logicalAddress == ADDR_BACKUP_1 || logicalAddress == ADDR_BACKUP_2) { 139 return cecVersion >= HdmiControlManager.HDMI_CEC_VERSION_2_0; 140 } 141 return true; 142 } 143 return false; 144 } 145 146 /** 147 * Return the device type for the given logical address. 148 * 149 * @param logicalAddress logical address 150 * @return device type for the given logical address; DEVICE_INACTIVE 151 * if the address is not valid. 152 */ getTypeFromAddress(int logicalAddress)153 static List<Integer> getTypeFromAddress(int logicalAddress) { 154 if (isValidAddress(logicalAddress)) { 155 return ADDRESS_TO_TYPE.get(logicalAddress); 156 } 157 return Lists.newArrayList(HdmiDeviceInfo.DEVICE_INACTIVE); 158 } 159 160 /** 161 * Return the default device name for a logical address. This is the name 162 * by which the logical device is known to others until a name is 163 * set explicitly using HdmiCecService.setOsdName. 164 * 165 * @param address logical address 166 * @return default device name; empty string if the address is not valid 167 */ getDefaultDeviceName(int address)168 static String getDefaultDeviceName(int address) { 169 if (isValidAddress(address)) { 170 return DEFAULT_NAMES[address]; 171 } 172 return ""; 173 } 174 175 /** 176 * Verify if the given address is for the given device type. If not it will throw 177 * {@link IllegalArgumentException}. 178 * 179 * @param logicalAddress the logical address to verify 180 * @param deviceType the device type to check 181 * @throws IllegalArgumentException 182 */ verifyAddressType(int logicalAddress, int deviceType)183 static void verifyAddressType(int logicalAddress, int deviceType) { 184 List<Integer> actualDeviceTypes = getTypeFromAddress(logicalAddress); 185 if (!actualDeviceTypes.contains(deviceType)) { 186 throw new IllegalArgumentException("Device type missmatch:[Expected:" + deviceType 187 + ", Actual:" + actualDeviceTypes); 188 } 189 } 190 191 /** 192 * Check if the given CEC message come from the given address. 193 * 194 * @param cmd the CEC message to check 195 * @param expectedAddress the expected source address of the given message 196 * @param tag the tag of caller module (for log message) 197 * @return true if the CEC message comes from the given address 198 */ checkCommandSource(HdmiCecMessage cmd, int expectedAddress, String tag)199 static boolean checkCommandSource(HdmiCecMessage cmd, int expectedAddress, String tag) { 200 int src = cmd.getSource(); 201 if (src != expectedAddress) { 202 Slog.w(tag, "Invalid source [Expected:" + expectedAddress + ", Actual:" + src + "]"); 203 return false; 204 } 205 return true; 206 } 207 208 /** 209 * Parse the parameter block of CEC message as [System Audio Status]. 210 * 211 * @param cmd the CEC message to parse 212 * @return true if the given parameter has [ON] value 213 */ parseCommandParamSystemAudioStatus(HdmiCecMessage cmd)214 static boolean parseCommandParamSystemAudioStatus(HdmiCecMessage cmd) { 215 return cmd.getParams()[0] == Constants.SYSTEM_AUDIO_STATUS_ON; 216 } 217 218 /** 219 * Parse the <Report Audio Status> message and check if it is mute 220 * 221 * @param cmd the CEC message to parse 222 * @return true if the given parameter has [MUTE] 223 */ isAudioStatusMute(HdmiCecMessage cmd)224 static boolean isAudioStatusMute(HdmiCecMessage cmd) { 225 byte params[] = cmd.getParams(); 226 return (params[0] & 0x80) == 0x80; 227 } 228 229 /** 230 * Parse the <Report Audio Status> message and extract the volume 231 * 232 * @param cmd the CEC message to parse 233 * @return device's volume. Constants.UNKNOWN_VOLUME in case it is out of range 234 */ getAudioStatusVolume(HdmiCecMessage cmd)235 static int getAudioStatusVolume(HdmiCecMessage cmd) { 236 byte params[] = cmd.getParams(); 237 int volume = params[0] & 0x7F; 238 if (volume < 0x00 || 0x64 < volume) { 239 volume = Constants.UNKNOWN_VOLUME; 240 } 241 return volume; 242 } 243 244 /** 245 * Convert integer array to list of {@link Integer}. 246 * 247 * <p>The result is immutable. 248 * 249 * @param is integer array 250 * @return {@link List} instance containing the elements in the given array 251 */ asImmutableList(final int[] is)252 static List<Integer> asImmutableList(final int[] is) { 253 ArrayList<Integer> list = new ArrayList<>(is.length); 254 for (int type : is) { 255 list.add(type); 256 } 257 return Collections.unmodifiableList(list); 258 } 259 260 /** 261 * Assemble two bytes into single integer value. 262 * 263 * @param data to be assembled 264 * @return assembled value 265 */ twoBytesToInt(byte[] data)266 static int twoBytesToInt(byte[] data) { 267 return ((data[0] & 0xFF) << 8) | (data[1] & 0xFF); 268 } 269 270 /** 271 * Assemble two bytes into single integer value. 272 * 273 * @param data to be assembled 274 * @param offset offset to the data to convert in the array 275 * @return assembled value 276 */ twoBytesToInt(byte[] data, int offset)277 static int twoBytesToInt(byte[] data, int offset) { 278 return ((data[offset] & 0xFF) << 8) | (data[offset + 1] & 0xFF); 279 } 280 281 /** 282 * Assemble three bytes into single integer value. 283 * 284 * @param data to be assembled 285 * @return assembled value 286 */ threeBytesToInt(byte[] data)287 static int threeBytesToInt(byte[] data) { 288 return ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF); 289 } 290 sparseArrayToList(SparseArray<T> array)291 static <T> List<T> sparseArrayToList(SparseArray<T> array) { 292 ArrayList<T> list = new ArrayList<>(); 293 for (int i = 0; i < array.size(); ++i) { 294 list.add(array.valueAt(i)); 295 } 296 return list; 297 } 298 mergeToUnmodifiableList(List<T> a, List<T> b)299 static <T> List<T> mergeToUnmodifiableList(List<T> a, List<T> b) { 300 if (a.isEmpty() && b.isEmpty()) { 301 return Collections.emptyList(); 302 } 303 if (a.isEmpty()) { 304 return Collections.unmodifiableList(b); 305 } 306 if (b.isEmpty()) { 307 return Collections.unmodifiableList(a); 308 } 309 List<T> newList = new ArrayList<>(); 310 newList.addAll(a); 311 newList.addAll(b); 312 return Collections.unmodifiableList(newList); 313 } 314 315 /** 316 * See if the new path is affecting the active path. 317 * 318 * @param activePath current active path 319 * @param newPath new path 320 * @return true if the new path changes the current active path 321 */ isAffectingActiveRoutingPath(int activePath, int newPath)322 static boolean isAffectingActiveRoutingPath(int activePath, int newPath) { 323 // The new path affects the current active path if the parent of the new path 324 // is an ancestor of the active path. 325 // (1.1.0.0, 2.0.0.0) -> true, new path alters the parent 326 // (1.1.0.0, 1.2.0.0) -> true, new path is a sibling 327 // (1.1.0.0, 1.2.1.0) -> false, new path is a descendant of a sibling 328 // (1.0.0.0, 3.2.0.0) -> false, in a completely different path 329 330 // Get the parent of the new path by clearing the least significant 331 // non-zero nibble. 332 for (int i = 0; i <= 12; i += 4) { 333 int nibble = (newPath >> i) & 0xF; 334 if (nibble != 0) { 335 int mask = 0xFFF0 << i; 336 newPath &= mask; 337 break; 338 } 339 } 340 if (newPath == 0x0000) { 341 return true; // Top path always affects the active path 342 } 343 return isInActiveRoutingPath(activePath, newPath); 344 } 345 346 /** 347 * See if the new path is in the active path. 348 * 349 * @param activePath current active path 350 * @param newPath new path 351 * @return true if the new path in the active routing path 352 */ isInActiveRoutingPath(int activePath, int newPath)353 static boolean isInActiveRoutingPath(int activePath, int newPath) { 354 @PathRelationship int pathRelationship = pathRelationship(newPath, activePath); 355 return (pathRelationship == Constants.PATH_RELATIONSHIP_ANCESTOR 356 || pathRelationship == Constants.PATH_RELATIONSHIP_DESCENDANT 357 || pathRelationship == Constants.PATH_RELATIONSHIP_SAME); 358 } 359 360 /** 361 * Computes the relationship from the first path to the second path. 362 */ pathRelationship(int firstPath, int secondPath)363 static @PathRelationship int pathRelationship(int firstPath, int secondPath) { 364 if (firstPath == Constants.INVALID_PHYSICAL_ADDRESS 365 || secondPath == Constants.INVALID_PHYSICAL_ADDRESS) { 366 return Constants.PATH_RELATIONSHIP_UNKNOWN; 367 } 368 // Loop forwards through both paths, looking for the first nibble where the paths differ. 369 // Checking this nibble and the next one distinguishes between most possible relationships. 370 for (int nibbleIndex = 0; nibbleIndex <= 3; nibbleIndex++) { 371 int shift = 12 - nibbleIndex * 4; 372 int firstPathNibble = (firstPath >> shift) & 0xF; 373 int secondPathNibble = (secondPath >> shift) & 0xF; 374 // Found the first nibble where the paths differ. 375 if (firstPathNibble != secondPathNibble) { 376 int firstPathNextNibble = (firstPath >> (shift - 4)) & 0xF; 377 int secondPathNextNibble = (secondPath >> (shift - 4)) & 0xF; 378 if (firstPathNibble == 0) { 379 return Constants.PATH_RELATIONSHIP_ANCESTOR; 380 } else if (secondPathNibble == 0) { 381 return Constants.PATH_RELATIONSHIP_DESCENDANT; 382 } else if (nibbleIndex == 3 383 || (firstPathNextNibble == 0 && secondPathNextNibble == 0)) { 384 return Constants.PATH_RELATIONSHIP_SIBLING; 385 } else { 386 return Constants.PATH_RELATIONSHIP_DIFFERENT_BRANCH; 387 } 388 } 389 } 390 return Constants.PATH_RELATIONSHIP_SAME; 391 } 392 393 /** 394 * Dump a {@link SparseArray} to the print writer. 395 * 396 * <p>The dump is formatted: 397 * <pre> 398 * name: 399 * key = value 400 * key = value 401 * ... 402 * </pre> 403 */ dumpSparseArray(IndentingPrintWriter pw, String name, SparseArray<T> sparseArray)404 static <T> void dumpSparseArray(IndentingPrintWriter pw, String name, 405 SparseArray<T> sparseArray) { 406 printWithTrailingColon(pw, name); 407 pw.increaseIndent(); 408 int size = sparseArray.size(); 409 for (int i = 0; i < size; i++) { 410 int key = sparseArray.keyAt(i); 411 T value = sparseArray.get(key); 412 pw.printPair(Integer.toString(key), value); 413 pw.println(); 414 } 415 pw.decreaseIndent(); 416 } 417 printWithTrailingColon(IndentingPrintWriter pw, String name)418 private static void printWithTrailingColon(IndentingPrintWriter pw, String name) { 419 pw.println(name.endsWith(":") ? name : name.concat(":")); 420 } 421 422 /** 423 * Dump a {@link Map} to the print writer. 424 * 425 * <p>The dump is formatted: 426 * <pre> 427 * name: 428 * key = value 429 * key = value 430 * ... 431 * </pre> 432 */ dumpMap(IndentingPrintWriter pw, String name, Map<K, V> map)433 static <K, V> void dumpMap(IndentingPrintWriter pw, String name, Map<K, V> map) { 434 printWithTrailingColon(pw, name); 435 pw.increaseIndent(); 436 for (Map.Entry<K, V> entry: map.entrySet()) { 437 pw.printPair(entry.getKey().toString(), entry.getValue()); 438 pw.println(); 439 } 440 pw.decreaseIndent(); 441 } 442 443 /** 444 * Dump a {@link Map} to the print writer. 445 * 446 * <p>The dump is formatted: 447 * <pre> 448 * name: 449 * value 450 * value 451 * ... 452 * </pre> 453 */ dumpIterable(IndentingPrintWriter pw, String name, Iterable<T> values)454 static <T> void dumpIterable(IndentingPrintWriter pw, String name, Iterable<T> values) { 455 printWithTrailingColon(pw, name); 456 pw.increaseIndent(); 457 for (T value : values) { 458 pw.println(value); 459 } 460 pw.decreaseIndent(); 461 } 462 463 /** 464 * Method to build target physical address to the port number on the current device. 465 * 466 * <p>This check assumes target address is valid. 467 * 468 * @param targetPhysicalAddress is the physical address of the target device 469 * @param myPhysicalAddress is the physical address of the current device 470 * @return 471 * If the target device is under the current device, return the port number of current device 472 * that the target device is connected to. This also applies to the devices that are indirectly 473 * connected to the current device. 474 * 475 * <p>If the target device has the same physical address as the current device, return 476 * {@link #TARGET_SAME_PHYSICAL_ADDRESS}. 477 * 478 * <p>If the target device is not under the current device, return 479 * {@link #TARGET_NOT_UNDER_LOCAL_DEVICE}. 480 */ getLocalPortFromPhysicalAddress( int targetPhysicalAddress, int myPhysicalAddress)481 public static int getLocalPortFromPhysicalAddress( 482 int targetPhysicalAddress, int myPhysicalAddress) { 483 if (myPhysicalAddress == targetPhysicalAddress) { 484 return TARGET_SAME_PHYSICAL_ADDRESS; 485 } 486 487 int mask = 0xF000; 488 int finalMask = 0xF000; 489 int maskedAddress = myPhysicalAddress; 490 491 while (maskedAddress != 0) { 492 maskedAddress = myPhysicalAddress & mask; 493 finalMask |= mask; 494 mask >>= 4; 495 } 496 497 int portAddress = targetPhysicalAddress & finalMask; 498 if ((portAddress & (finalMask << 4)) != myPhysicalAddress) { 499 return TARGET_NOT_UNDER_LOCAL_DEVICE; 500 } 501 502 mask <<= 4; 503 int port = portAddress & mask; 504 while ((port >> 4) != 0) { 505 port >>= 4; 506 } 507 return port; 508 } 509 510 /** 511 * Parse the Feature Abort CEC message parameter into a [Feature Opcode]. 512 * 513 * @param cmd the CEC message to parse 514 * @return the original opcode of the cec message that got aborted. 515 */ 516 @FeatureOpcode getAbortFeatureOpcode(HdmiCecMessage cmd)517 static int getAbortFeatureOpcode(HdmiCecMessage cmd) { 518 return cmd.getParams()[0] & 0xFF; 519 } 520 521 /** 522 * Parse the Feature Abort CEC message parameter into an [Abort Reason]. 523 * 524 * @param cmd the CEC message to parse 525 * @return The reason to abort the feature. 526 */ 527 @AbortReason getAbortReason(HdmiCecMessage cmd)528 static int getAbortReason(HdmiCecMessage cmd) { 529 return cmd.getParams()[1]; 530 } 531 532 /** 533 * Build a CEC message from a hex byte string with bytes separated by {@code :}. 534 * 535 * <p>This format is used by both cec-client and www.cec-o-matic.com 536 */ buildMessage(String message)537 public static HdmiCecMessage buildMessage(String message) { 538 String[] parts = message.split(":"); 539 540 if (parts.length < 2) { 541 throw new IllegalArgumentException("Message is too short"); 542 } 543 for (String part : parts) { 544 if (part.length() != 2) { 545 throw new IllegalArgumentException("Malformatted CEC message: " + message); 546 } 547 } 548 549 int src = Integer.parseInt(parts[0].substring(0, 1), 16); 550 int dest = Integer.parseInt(parts[0].substring(1, 2), 16); 551 int opcode = Integer.parseInt(parts[1], 16); 552 byte[] params = new byte[parts.length - 2]; 553 for (int i = 0; i < params.length; i++) { 554 params[i] = (byte) Integer.parseInt(parts[i + 2], 16); 555 } 556 return HdmiCecMessage.build(src, dest, opcode, params); 557 } 558 559 /** 560 * Some operands in the CEC spec consist of a variable number of bytes, where each byte except 561 * the last one has bit 7 set to 1. 562 * Given the index of a byte in such an operand, this method returns the index of the last byte 563 * in the operand, or -1 if the input is invalid (e.g. operand not terminated properly). 564 * @param params Byte array representing a CEC message's parameters 565 * @param offset Index of a byte in the operand to find the end of 566 */ getEndOfSequence(byte[] params, int offset)567 public static int getEndOfSequence(byte[] params, int offset) { 568 if (offset < 0) { 569 return -1; 570 } 571 while (offset < params.length && ((params[offset] >> 7) & 1) == 1) { 572 offset++; 573 } 574 if (offset >= params.length) { 575 return -1; 576 } 577 return offset; 578 } 579 580 public static class ShortAudioDescriptorXmlParser { 581 // We don't use namespaces 582 private static final String NS = null; 583 584 // return a list of devices config parse(InputStream in)585 public static List<DeviceConfig> parse(InputStream in) 586 throws XmlPullParserException, IOException { 587 TypedXmlPullParser parser = Xml.resolvePullParser(in); 588 parser.nextTag(); 589 return readDevices(parser); 590 } 591 skip(TypedXmlPullParser parser)592 private static void skip(TypedXmlPullParser parser) 593 throws XmlPullParserException, IOException { 594 if (parser.getEventType() != XmlPullParser.START_TAG) { 595 throw new IllegalStateException(); 596 } 597 int depth = 1; 598 while (depth != 0) { 599 switch (parser.next()) { 600 case XmlPullParser.END_TAG: 601 depth--; 602 break; 603 case XmlPullParser.START_TAG: 604 depth++; 605 break; 606 } 607 } 608 } 609 readDevices(TypedXmlPullParser parser)610 private static List<DeviceConfig> readDevices(TypedXmlPullParser parser) 611 throws XmlPullParserException, IOException { 612 List<DeviceConfig> devices = new ArrayList<>(); 613 614 parser.require(XmlPullParser.START_TAG, NS, "config"); 615 while (parser.next() != XmlPullParser.END_TAG) { 616 if (parser.getEventType() != XmlPullParser.START_TAG) { 617 continue; 618 } 619 String name = parser.getName(); 620 // Starts by looking for the device tag 621 if (name.equals("device")) { 622 String deviceType = parser.getAttributeValue(null, "type"); 623 DeviceConfig config = null; 624 if (deviceType != null) { 625 config = readDeviceConfig(parser, deviceType); 626 } 627 if (config != null) { 628 devices.add(config); 629 } 630 } else { 631 skip(parser); 632 } 633 } 634 return devices; 635 } 636 637 // Processes device tags in the config. 638 @Nullable readDeviceConfig(TypedXmlPullParser parser, String deviceType)639 private static DeviceConfig readDeviceConfig(TypedXmlPullParser parser, String deviceType) 640 throws XmlPullParserException, IOException { 641 List<CodecSad> codecSads = new ArrayList<>(); 642 int format; 643 byte[] descriptor; 644 645 parser.require(XmlPullParser.START_TAG, NS, "device"); 646 while (parser.next() != XmlPullParser.END_TAG) { 647 if (parser.getEventType() != XmlPullParser.START_TAG) { 648 continue; 649 } 650 String tagName = parser.getName(); 651 652 // Starts by looking for the supportedFormat tag 653 if (tagName.equals("supportedFormat")) { 654 String codecAttriValue = parser.getAttributeValue(null, "format"); 655 String sadAttriValue = parser.getAttributeValue(null, "descriptor"); 656 format = (codecAttriValue) == null 657 ? Constants.AUDIO_CODEC_NONE : formatNameToNum(codecAttriValue); 658 descriptor = readSad(sadAttriValue); 659 if (format != Constants.AUDIO_CODEC_NONE && descriptor != null) { 660 codecSads.add(new CodecSad(format, descriptor)); 661 } 662 parser.nextTag(); 663 parser.require(XmlPullParser.END_TAG, NS, "supportedFormat"); 664 } else { 665 skip(parser); 666 } 667 } 668 if (codecSads.size() == 0) { 669 return null; 670 } 671 return new DeviceConfig(deviceType, codecSads); 672 } 673 674 // Processes sad attribute in the supportedFormat. 675 @Nullable readSad(String sad)676 private static byte[] readSad(String sad) { 677 if (sad == null || sad.length() == 0) { 678 return null; 679 } 680 byte[] sadBytes = HexDump.hexStringToByteArray(sad); 681 if (sadBytes.length != 3) { 682 Slog.w(TAG, "SAD byte array length is not 3. Length = " + sadBytes.length); 683 return null; 684 } 685 return sadBytes; 686 } 687 688 @AudioCodec formatNameToNum(String codecAttriValue)689 private static int formatNameToNum(String codecAttriValue) { 690 switch (codecAttriValue) { 691 case "AUDIO_FORMAT_NONE": 692 return Constants.AUDIO_CODEC_NONE; 693 case "AUDIO_FORMAT_LPCM": 694 return Constants.AUDIO_CODEC_LPCM; 695 case "AUDIO_FORMAT_DD": 696 return Constants.AUDIO_CODEC_DD; 697 case "AUDIO_FORMAT_MPEG1": 698 return Constants.AUDIO_CODEC_MPEG1; 699 case "AUDIO_FORMAT_MP3": 700 return Constants.AUDIO_CODEC_MP3; 701 case "AUDIO_FORMAT_MPEG2": 702 return Constants.AUDIO_CODEC_MPEG2; 703 case "AUDIO_FORMAT_AAC": 704 return Constants.AUDIO_CODEC_AAC; 705 case "AUDIO_FORMAT_DTS": 706 return Constants.AUDIO_CODEC_DTS; 707 case "AUDIO_FORMAT_ATRAC": 708 return Constants.AUDIO_CODEC_ATRAC; 709 case "AUDIO_FORMAT_ONEBITAUDIO": 710 return Constants.AUDIO_CODEC_ONEBITAUDIO; 711 case "AUDIO_FORMAT_DDP": 712 return Constants.AUDIO_CODEC_DDP; 713 case "AUDIO_FORMAT_DTSHD": 714 return Constants.AUDIO_CODEC_DTSHD; 715 case "AUDIO_FORMAT_TRUEHD": 716 return Constants.AUDIO_CODEC_TRUEHD; 717 case "AUDIO_FORMAT_DST": 718 return Constants.AUDIO_CODEC_DST; 719 case "AUDIO_FORMAT_WMAPRO": 720 return Constants.AUDIO_CODEC_WMAPRO; 721 case "AUDIO_FORMAT_MAX": 722 return Constants.AUDIO_CODEC_MAX; 723 default: 724 return Constants.AUDIO_CODEC_NONE; 725 } 726 } 727 } 728 729 // Device configuration of its supported Codecs and their Short Audio Descriptors. 730 public static class DeviceConfig { 731 /** Name of the device. Should be {@link Constants.AudioDevice}. **/ 732 public final String name; 733 /** List of a {@link CodecSad}. **/ 734 public final List<CodecSad> supportedCodecs; 735 DeviceConfig(String name, List<CodecSad> supportedCodecs)736 public DeviceConfig(String name, List<CodecSad> supportedCodecs) { 737 this.name = name; 738 this.supportedCodecs = supportedCodecs; 739 } 740 741 @Override equals(Object obj)742 public boolean equals(Object obj) { 743 if (obj instanceof DeviceConfig) { 744 DeviceConfig that = (DeviceConfig) obj; 745 return that.name.equals(this.name) 746 && that.supportedCodecs.equals(this.supportedCodecs); 747 } 748 return false; 749 } 750 751 @Override hashCode()752 public int hashCode() { 753 return Objects.hash( 754 name, 755 supportedCodecs.hashCode()); 756 } 757 } 758 759 // Short Audio Descriptor of a specific Codec 760 public static class CodecSad { 761 /** Audio Codec. Should be {@link Constants.AudioCodec}. **/ 762 public final int audioCodec; 763 /** 764 * Three-byte Short Audio Descriptor. See HDMI Specification 1.4b CEC 13.15.3 and 765 * ANSI-CTA-861-F-FINAL 7.5.2 Audio Data Block for more details. 766 */ 767 public final byte[] sad; 768 CodecSad(int audioCodec, byte[] sad)769 public CodecSad(int audioCodec, byte[] sad) { 770 this.audioCodec = audioCodec; 771 this.sad = sad; 772 } 773 CodecSad(int audioCodec, String sad)774 public CodecSad(int audioCodec, String sad) { 775 this.audioCodec = audioCodec; 776 this.sad = HexDump.hexStringToByteArray(sad); 777 } 778 779 @Override equals(Object obj)780 public boolean equals(Object obj) { 781 if (obj instanceof CodecSad) { 782 CodecSad that = (CodecSad) obj; 783 return that.audioCodec == this.audioCodec 784 && Arrays.equals(that.sad, this.sad); 785 } 786 return false; 787 } 788 789 @Override hashCode()790 public int hashCode() { 791 return Objects.hash( 792 audioCodec, 793 Arrays.hashCode(sad)); 794 } 795 } 796 } 797