blob: e44f1d1522ec166e2d976f06cb348a4db853a347 [file] [log] [blame]
Yuncheol Heo63a2e062014-05-27 23:06:01 +09001/*
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
17package com.android.server.hdmi;
18
Amy6bcc5962018-11-28 17:46:44 -080019import android.annotation.Nullable;
Jungshik Jang61f4fbd2014-08-06 19:21:12 +090020import android.hardware.hdmi.HdmiDeviceInfo;
Yuncheol Heo63a2e062014-05-27 23:06:01 +090021import android.util.Slog;
Jungshik Jang79c58a42014-06-16 16:45:36 +090022import android.util.SparseArray;
Amy6bcc5962018-11-28 17:46:44 -080023import android.util.Xml;
Yuncheol Heo63a2e062014-05-27 23:06:01 +090024
Amy6bcc5962018-11-28 17:46:44 -080025import com.android.internal.util.HexDump;
Nick Chalkob9e48e22018-10-23 06:59:39 -070026import com.android.internal.util.IndentingPrintWriter;
Amy6bcc5962018-11-28 17:46:44 -080027import com.android.server.hdmi.Constants.AudioCodec;
Nick Chalkob9e48e22018-10-23 06:59:39 -070028
Amy6bcc5962018-11-28 17:46:44 -080029import org.xmlpull.v1.XmlPullParser;
30import org.xmlpull.v1.XmlPullParserException;
31
32import java.io.IOException;
33import java.io.InputStream;
Jinsuk Kim0340bbc2014-06-05 11:07:47 +090034import java.util.ArrayList;
Amy6bcc5962018-11-28 17:46:44 -080035import java.util.Arrays;
Jinsuk Kim0340bbc2014-06-05 11:07:47 +090036import java.util.Collections;
37import java.util.List;
Nick Chalkob9e48e22018-10-23 06:59:39 -070038import java.util.Map;
Amy6bcc5962018-11-28 17:46:44 -080039import java.util.Objects;
Jinsuk Kim0340bbc2014-06-05 11:07:47 +090040
Yuncheol Heo63a2e062014-05-27 23:06:01 +090041/**
42 * Various utilities to handle HDMI CEC messages.
43 */
44final class HdmiUtils {
45
Amy6bcc5962018-11-28 17:46:44 -080046 private static final String TAG = "HdmiUtils";
47
Jinsuk Kimc0c20d02014-07-04 14:34:31 +090048 private static final int[] ADDRESS_TO_TYPE = {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +090049 HdmiDeviceInfo.DEVICE_TV, // ADDR_TV
50 HdmiDeviceInfo.DEVICE_RECORDER, // ADDR_RECORDER_1
51 HdmiDeviceInfo.DEVICE_RECORDER, // ADDR_RECORDER_2
52 HdmiDeviceInfo.DEVICE_TUNER, // ADDR_TUNER_1
53 HdmiDeviceInfo.DEVICE_PLAYBACK, // ADDR_PLAYBACK_1
54 HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM, // ADDR_AUDIO_SYSTEM
55 HdmiDeviceInfo.DEVICE_TUNER, // ADDR_TUNER_2
56 HdmiDeviceInfo.DEVICE_TUNER, // ADDR_TUNER_3
57 HdmiDeviceInfo.DEVICE_PLAYBACK, // ADDR_PLAYBACK_2
58 HdmiDeviceInfo.DEVICE_RECORDER, // ADDR_RECORDER_3
59 HdmiDeviceInfo.DEVICE_TUNER, // ADDR_TUNER_4
60 HdmiDeviceInfo.DEVICE_PLAYBACK, // ADDR_PLAYBACK_3
61 HdmiDeviceInfo.DEVICE_RESERVED,
62 HdmiDeviceInfo.DEVICE_RESERVED,
63 HdmiDeviceInfo.DEVICE_TV, // ADDR_SPECIFIC_USE
Jinsuk Kimc0c20d02014-07-04 14:34:31 +090064 };
65
66 private static final String[] DEFAULT_NAMES = {
67 "TV",
68 "Recorder_1",
69 "Recorder_2",
70 "Tuner_1",
71 "Playback_1",
72 "AudioSystem",
73 "Tuner_2",
74 "Tuner_3",
75 "Playback_2",
76 "Recorder_3",
77 "Tuner_4",
78 "Playback_3",
79 "Reserved_1",
80 "Reserved_2",
81 "Secondary_TV",
82 };
83
Amyff115f12018-11-21 14:26:16 -080084 /**
85 * Return value of {@link #getLocalPortFromPhysicalAddress(int, int)}
86 */
87 static final int TARGET_NOT_UNDER_LOCAL_DEVICE = -1;
88 static final int TARGET_SAME_PHYSICAL_ADDRESS = 0;
89
Yuncheol Heo63a2e062014-05-27 23:06:01 +090090 private HdmiUtils() { /* cannot be instantiated */ }
91
92 /**
Jinsuk Kimc0c20d02014-07-04 14:34:31 +090093 * Check if the given logical address is valid. A logical address is valid
94 * if it is one allocated for an actual device which allows communication
95 * with other logical devices.
96 *
97 * @param address logical address
98 * @return true if the given address is valid
99 */
100 static boolean isValidAddress(int address) {
101 return (Constants.ADDR_TV <= address && address <= Constants.ADDR_SPECIFIC_USE);
102 }
103
104 /**
105 * Return the device type for the given logical address.
106 *
107 * @param address logical address
108 * @return device type for the given logical address; DEVICE_INACTIVE
109 * if the address is not valid.
110 */
111 static int getTypeFromAddress(int address) {
112 if (isValidAddress(address)) {
113 return ADDRESS_TO_TYPE[address];
114 }
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900115 return HdmiDeviceInfo.DEVICE_INACTIVE;
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900116 }
117
118 /**
119 * Return the default device name for a logical address. This is the name
120 * by which the logical device is known to others until a name is
121 * set explicitly using HdmiCecService.setOsdName.
122 *
123 * @param address logical address
124 * @return default device name; empty string if the address is not valid
125 */
126 static String getDefaultDeviceName(int address) {
127 if (isValidAddress(address)) {
128 return DEFAULT_NAMES[address];
129 }
130 return "";
131 }
132
133 /**
Yuncheol Heo63a2e062014-05-27 23:06:01 +0900134 * Verify if the given address is for the given device type. If not it will throw
135 * {@link IllegalArgumentException}.
136 *
137 * @param logicalAddress the logical address to verify
138 * @param deviceType the device type to check
Shubanga3f59502018-06-05 16:32:53 -0700139 * @throws IllegalArgumentException
Yuncheol Heo63a2e062014-05-27 23:06:01 +0900140 */
141 static void verifyAddressType(int logicalAddress, int deviceType) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900142 int actualDeviceType = getTypeFromAddress(logicalAddress);
Yuncheol Heo63a2e062014-05-27 23:06:01 +0900143 if (actualDeviceType != deviceType) {
144 throw new IllegalArgumentException("Device type missmatch:[Expected:" + deviceType
145 + ", Actual:" + actualDeviceType);
146 }
147 }
148
149 /**
150 * Check if the given CEC message come from the given address.
151 *
152 * @param cmd the CEC message to check
153 * @param expectedAddress the expected source address of the given message
154 * @param tag the tag of caller module (for log message)
155 * @return true if the CEC message comes from the given address
156 */
157 static boolean checkCommandSource(HdmiCecMessage cmd, int expectedAddress, String tag) {
158 int src = cmd.getSource();
159 if (src != expectedAddress) {
160 Slog.w(tag, "Invalid source [Expected:" + expectedAddress + ", Actual:" + src + "]");
161 return false;
162 }
163 return true;
164 }
165
166 /**
167 * Parse the parameter block of CEC message as [System Audio Status].
168 *
169 * @param cmd the CEC message to parse
170 * @return true if the given parameter has [ON] value
171 */
172 static boolean parseCommandParamSystemAudioStatus(HdmiCecMessage cmd) {
Yuncheol Heo75a77e72014-07-09 18:27:53 +0900173 return cmd.getParams()[0] == Constants.SYSTEM_AUDIO_STATUS_ON;
Yuncheol Heo63a2e062014-05-27 23:06:01 +0900174 }
Jungshik Jang092b4452014-06-11 15:19:17 +0900175
176 /**
Shuichi.Noguchifbb50bc2017-12-06 11:12:33 +0900177 * Parse the <Report Audio Status> message and check if it is mute
178 *
179 * @param cmd the CEC message to parse
180 * @return true if the given parameter has [MUTE]
181 */
182 static boolean isAudioStatusMute(HdmiCecMessage cmd) {
183 byte params[] = cmd.getParams();
184 return (params[0] & 0x80) == 0x80;
185 }
186
187 /**
188 * Parse the <Report Audio Status> message and extract the volume
189 *
190 * @param cmd the CEC message to parse
191 * @return device's volume. Constants.UNKNOWN_VOLUME in case it is out of range
192 */
193 static int getAudioStatusVolume(HdmiCecMessage cmd) {
194 byte params[] = cmd.getParams();
195 int volume = params[0] & 0x7F;
196 if (volume < 0x00 || 0x64 < volume) {
197 volume = Constants.UNKNOWN_VOLUME;
198 }
199 return volume;
200 }
201
202 /**
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900203 * Convert integer array to list of {@link Integer}.
204 *
205 * <p>The result is immutable.
206 *
207 * @param is integer array
208 * @return {@link List} instance containing the elements in the given array
209 */
210 static List<Integer> asImmutableList(final int[] is) {
211 ArrayList<Integer> list = new ArrayList<>(is.length);
212 for (int type : is) {
213 list.add(type);
214 }
215 return Collections.unmodifiableList(list);
216 }
217
218 /**
Jungshik Jang092b4452014-06-11 15:19:17 +0900219 * Assemble two bytes into single integer value.
220 *
221 * @param data to be assembled
222 * @return assembled value
223 */
224 static int twoBytesToInt(byte[] data) {
225 return ((data[0] & 0xFF) << 8) | (data[1] & 0xFF);
226 }
227
228 /**
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900229 * Assemble two bytes into single integer value.
230 *
231 * @param data to be assembled
232 * @param offset offset to the data to convert in the array
233 * @return assembled value
234 */
235 static int twoBytesToInt(byte[] data, int offset) {
236 return ((data[offset] & 0xFF) << 8) | (data[offset + 1] & 0xFF);
237 }
238
239 /**
Jungshik Jang092b4452014-06-11 15:19:17 +0900240 * Assemble three bytes into single integer value.
241 *
242 * @param data to be assembled
243 * @return assembled value
244 */
245 static int threeBytesToInt(byte[] data) {
246 return ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
247 }
Jungshik Jang79c58a42014-06-16 16:45:36 +0900248
249 static <T> List<T> sparseArrayToList(SparseArray<T> array) {
250 ArrayList<T> list = new ArrayList<>();
251 for (int i = 0; i < array.size(); ++i) {
252 list.add(array.valueAt(i));
253 }
254 return list;
255 }
256
Jinsuk Kimed086452014-08-18 15:01:53 +0900257 static <T> List<T> mergeToUnmodifiableList(List<T> a, List<T> b) {
258 if (a.isEmpty() && b.isEmpty()) {
259 return Collections.emptyList();
260 }
261 if (a.isEmpty()) {
262 return Collections.unmodifiableList(b);
263 }
264 if (b.isEmpty()) {
265 return Collections.unmodifiableList(a);
266 }
267 List<T> newList = new ArrayList<>();
268 newList.addAll(a);
269 newList.addAll(b);
270 return Collections.unmodifiableList(newList);
271 }
272
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900273 /**
274 * See if the new path is affecting the active path.
275 *
276 * @param activePath current active path
277 * @param newPath new path
278 * @return true if the new path changes the current active path
279 */
280 static boolean isAffectingActiveRoutingPath(int activePath, int newPath) {
281 // The new path affects the current active path if the parent of the new path
282 // is an ancestor of the active path.
283 // (1.1.0.0, 2.0.0.0) -> true, new path alters the parent
284 // (1.1.0.0, 1.2.0.0) -> true, new path is a sibling
285 // (1.1.0.0, 1.2.1.0) -> false, new path is a descendant of a sibling
286 // (1.0.0.0, 3.2.0.0) -> false, in a completely different path
287
288 // Get the parent of the new path by clearing the least significant
289 // non-zero nibble.
290 for (int i = 0; i <= 12; i += 4) {
291 int nibble = (newPath >> i) & 0xF;
292 if (nibble != 0) {
293 int mask = 0xFFF0 << i;
294 newPath &= mask;
295 break;
296 }
297 }
298 if (newPath == 0x0000) {
299 return true; // Top path always affects the active path
300 }
301 return isInActiveRoutingPath(activePath, newPath);
302 }
303
304 /**
305 * See if the new path is in the active path.
306 *
307 * @param activePath current active path
308 * @param newPath new path
309 * @return true if the new path in the active routing path
310 */
311 static boolean isInActiveRoutingPath(int activePath, int newPath) {
312 // Check each nibble of the currently active path and the new path till the position
313 // where the active nibble is not zero. For (activePath, newPath),
314 // (1.1.0.0, 1.0.0.0) -> true, new path is a parent
315 // (1.2.1.0, 1.2.1.2) -> true, new path is a descendant
316 // (1.1.0.0, 1.2.0.0) -> false, new path is a sibling
317 // (1.0.0.0, 2.0.0.0) -> false, in a completely different path
318 for (int i = 12; i >= 0; i -= 4) {
319 int nibbleActive = (activePath >> i) & 0xF;
320 if (nibbleActive == 0) {
321 break;
322 }
323 int nibbleNew = (newPath >> i) & 0xF;
324 if (nibbleNew == 0) {
325 break;
326 }
327 if (nibbleActive != nibbleNew) {
328 return false;
329 }
330 }
331 return true;
332 }
Jungshik Jang410ca9c2014-08-07 18:04:14 +0900333
334 /**
335 * Clone {@link HdmiDeviceInfo} with new power status.
336 */
337 static HdmiDeviceInfo cloneHdmiDeviceInfo(HdmiDeviceInfo info, int newPowerStatus) {
338 return new HdmiDeviceInfo(info.getLogicalAddress(),
339 info.getPhysicalAddress(), info.getPortId(), info.getDeviceType(),
340 info.getVendorId(), info.getDisplayName(), newPowerStatus);
341 }
Nick Chalkob9e48e22018-10-23 06:59:39 -0700342
343 /**
344 * Dump a {@link SparseArray} to the print writer.
345 *
346 * <p>The dump is formatted:
347 * <pre>
348 * name:
349 * key = value
350 * key = value
351 * ...
352 * </pre>
353 */
354 static <T> void dumpSparseArray(IndentingPrintWriter pw, String name,
355 SparseArray<T> sparseArray) {
356 printWithTrailingColon(pw, name);
357 pw.increaseIndent();
358 int size = sparseArray.size();
359 for (int i = 0; i < size; i++) {
360 int key = sparseArray.keyAt(i);
361 T value = sparseArray.get(key);
362 pw.printPair(Integer.toString(key), value);
363 pw.println();
364 }
365 pw.decreaseIndent();
366 }
367
368 private static void printWithTrailingColon(IndentingPrintWriter pw, String name) {
369 pw.println(name.endsWith(":") ? name : name.concat(":"));
370 }
371
372 /**
373 * Dump a {@link Map} to the print writer.
374 *
375 * <p>The dump is formatted:
376 * <pre>
377 * name:
378 * key = value
379 * key = value
380 * ...
381 * </pre>
382 */
383 static <K, V> void dumpMap(IndentingPrintWriter pw, String name, Map<K, V> map) {
384 printWithTrailingColon(pw, name);
385 pw.increaseIndent();
386 for (Map.Entry<K, V> entry: map.entrySet()) {
387 pw.printPair(entry.getKey().toString(), entry.getValue());
388 pw.println();
389 }
390 pw.decreaseIndent();
391 }
392
393 /**
394 * Dump a {@link Map} to the print writer.
395 *
396 * <p>The dump is formatted:
397 * <pre>
398 * name:
399 * value
400 * value
401 * ...
402 * </pre>
403 */
404 static <T> void dumpIterable(IndentingPrintWriter pw, String name, Iterable<T> values) {
405 printWithTrailingColon(pw, name);
406 pw.increaseIndent();
407 for (T value : values) {
408 pw.println(value);
409 }
410 pw.decreaseIndent();
411 }
Amydbed4e52018-11-28 17:22:20 -0800412
Amyff115f12018-11-21 14:26:16 -0800413 /**
414 * Method to parse target physical address to the port number on the current device.
415 *
416 * <p>This check assumes target address is valid.
417 *
418 * @param targetPhysicalAddress is the physical address of the target device
419 * @param myPhysicalAddress is the physical address of the current device
420 * @return
421 * If the target device is under the current device, return the port number of current device
422 * that the target device is connected to. This also applies to the devices that are indirectly
423 * connected to the current device.
424 *
425 * <p>If the target device has the same physical address as the current device, return
426 * {@link #TARGET_SAME_PHYSICAL_ADDRESS}.
427 *
428 * <p>If the target device is not under the current device, return
429 * {@link #TARGET_NOT_UNDER_LOCAL_DEVICE}.
430 */
431 public static int getLocalPortFromPhysicalAddress(
432 int targetPhysicalAddress, int myPhysicalAddress) {
433 if (myPhysicalAddress == targetPhysicalAddress) {
434 return TARGET_SAME_PHYSICAL_ADDRESS;
435 }
436
437 int mask = 0xF000;
438 int finalMask = 0xF000;
439 int maskedAddress = myPhysicalAddress;
440
441 while (maskedAddress != 0) {
442 maskedAddress = myPhysicalAddress & mask;
443 finalMask |= mask;
444 mask >>= 4;
445 }
446
447 int portAddress = targetPhysicalAddress & finalMask;
448 if ((portAddress & (finalMask << 4)) != myPhysicalAddress) {
449 return TARGET_NOT_UNDER_LOCAL_DEVICE;
450 }
451
452 mask <<= 4;
453 int port = portAddress & mask;
454 while ((port >> 4) != 0) {
455 port >>= 4;
456 }
457 return port;
458 }
459
Amy6bcc5962018-11-28 17:46:44 -0800460 public static class ShortAudioDescriptorXmlParser {
461 // We don't use namespaces
462 private static final String NS = null;
463
464 // return a list of devices config
465 public static List<DeviceConfig> parse(InputStream in)
466 throws XmlPullParserException, IOException {
467 XmlPullParser parser = Xml.newPullParser();
468 parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
469 parser.setInput(in, null);
470 parser.nextTag();
471 return readDevices(parser);
472 }
473
474 private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
475 if (parser.getEventType() != XmlPullParser.START_TAG) {
476 throw new IllegalStateException();
477 }
478 int depth = 1;
479 while (depth != 0) {
480 switch (parser.next()) {
481 case XmlPullParser.END_TAG:
482 depth--;
483 break;
484 case XmlPullParser.START_TAG:
485 depth++;
486 break;
487 }
488 }
489 }
490
491 private static List<DeviceConfig> readDevices(XmlPullParser parser)
492 throws XmlPullParserException, IOException {
493 List<DeviceConfig> devices = new ArrayList<>();
494
495 parser.require(XmlPullParser.START_TAG, NS, "config");
496 while (parser.next() != XmlPullParser.END_TAG) {
497 if (parser.getEventType() != XmlPullParser.START_TAG) {
498 continue;
499 }
500 String name = parser.getName();
501 // Starts by looking for the device tag
502 if (name.equals("device")) {
503 String deviceType = parser.getAttributeValue(null, "type");
504 DeviceConfig config = null;
505 if (deviceType != null) {
506 config = readDeviceConfig(parser, deviceType);
507 }
508 if (config != null) {
509 devices.add(config);
510 }
511 } else {
512 skip(parser);
513 }
514 }
515 return devices;
516 }
517
518 // Processes device tags in the config.
519 @Nullable
520 private static DeviceConfig readDeviceConfig(XmlPullParser parser, String deviceType)
521 throws XmlPullParserException, IOException {
522 List<CodecSad> codecSads = new ArrayList<>();
523 int format;
524 byte[] descriptor;
525
526 parser.require(XmlPullParser.START_TAG, NS, "device");
527 while (parser.next() != XmlPullParser.END_TAG) {
528 if (parser.getEventType() != XmlPullParser.START_TAG) {
529 continue;
530 }
531 String tagName = parser.getName();
532
533 // Starts by looking for the supportedFormat tag
534 if (tagName.equals("supportedFormat")) {
535 String codecAttriValue = parser.getAttributeValue(null, "format");
536 String sadAttriValue = parser.getAttributeValue(null, "descriptor");
537 format = (codecAttriValue) == null
538 ? Constants.AUDIO_CODEC_NONE : formatNameToNum(codecAttriValue);
539 descriptor = readSad(sadAttriValue);
540 if (format != Constants.AUDIO_CODEC_NONE && descriptor != null) {
541 codecSads.add(new CodecSad(format, descriptor));
542 }
543 parser.nextTag();
544 parser.require(XmlPullParser.END_TAG, NS, "supportedFormat");
545 } else {
546 skip(parser);
547 }
548 }
549 if (codecSads.size() == 0) {
550 return null;
551 }
552 return new DeviceConfig(deviceType, codecSads);
553 }
554
555 // Processes sad attribute in the supportedFormat.
556 @Nullable
557 private static byte[] readSad(String sad) {
558 if (sad == null || sad.length() == 0) {
559 return null;
560 }
561 byte[] sadBytes = HexDump.hexStringToByteArray(sad);
562 if (sadBytes.length != 3) {
563 Slog.w(TAG, "SAD byte array length is not 3. Length = " + sadBytes.length);
564 return null;
565 }
566 return sadBytes;
567 }
568
569 @AudioCodec
570 private static int formatNameToNum(String codecAttriValue) {
571 switch (codecAttriValue) {
572 case "AUDIO_FORMAT_NONE":
573 return Constants.AUDIO_CODEC_NONE;
574 case "AUDIO_FORMAT_LPCM":
575 return Constants.AUDIO_CODEC_LPCM;
576 case "AUDIO_FORMAT_DD":
577 return Constants.AUDIO_CODEC_DD;
578 case "AUDIO_FORMAT_MPEG1":
579 return Constants.AUDIO_CODEC_MPEG1;
580 case "AUDIO_FORMAT_MP3":
581 return Constants.AUDIO_CODEC_MP3;
582 case "AUDIO_FORMAT_MPEG2":
583 return Constants.AUDIO_CODEC_MPEG2;
584 case "AUDIO_FORMAT_AAC":
585 return Constants.AUDIO_CODEC_AAC;
586 case "AUDIO_FORMAT_DTS":
587 return Constants.AUDIO_CODEC_DTS;
588 case "AUDIO_FORMAT_ATRAC":
589 return Constants.AUDIO_CODEC_ATRAC;
590 case "AUDIO_FORMAT_ONEBITAUDIO":
591 return Constants.AUDIO_CODEC_ONEBITAUDIO;
592 case "AUDIO_FORMAT_DDP":
593 return Constants.AUDIO_CODEC_DDP;
594 case "AUDIO_FORMAT_DTSHD":
595 return Constants.AUDIO_CODEC_DTSHD;
596 case "AUDIO_FORMAT_TRUEHD":
597 return Constants.AUDIO_CODEC_TRUEHD;
598 case "AUDIO_FORMAT_DST":
599 return Constants.AUDIO_CODEC_DST;
600 case "AUDIO_FORMAT_WMAPRO":
601 return Constants.AUDIO_CODEC_WMAPRO;
602 case "AUDIO_FORMAT_MAX":
603 return Constants.AUDIO_CODEC_MAX;
604 default:
605 return Constants.AUDIO_CODEC_NONE;
606 }
607 }
608 }
609
Amydbed4e52018-11-28 17:22:20 -0800610 // Device configuration of its supported Codecs and their Short Audio Descriptors.
611 public static class DeviceConfig {
612 /** Name of the device. Should be {@link Constants.AudioDevice}. **/
613 public final String name;
614 /** List of a {@link CodecSad}. **/
615 public final List<CodecSad> supportedCodecs;
616
Amy6bcc5962018-11-28 17:46:44 -0800617 public DeviceConfig(String name, List<CodecSad> supportedCodecs) {
Amydbed4e52018-11-28 17:22:20 -0800618 this.name = name;
619 this.supportedCodecs = supportedCodecs;
620 }
Amy6bcc5962018-11-28 17:46:44 -0800621
622 @Override
623 public boolean equals(Object obj) {
624 if (obj instanceof DeviceConfig) {
625 DeviceConfig that = (DeviceConfig) obj;
626 return that.name.equals(this.name)
627 && that.supportedCodecs.equals(this.supportedCodecs);
628 }
629 return false;
630 }
631
632 @Override
633 public int hashCode() {
634 return Objects.hash(
635 name,
636 supportedCodecs.hashCode());
637 }
Amydbed4e52018-11-28 17:22:20 -0800638 }
639
640 // Short Audio Descriptor of a specific Codec
641 public static class CodecSad {
642 /** Audio Codec. Should be {@link Constants.AudioCodec}. **/
643 public final int audioCodec;
644 /**
645 * Three-byte Short Audio Descriptor. See HDMI Specification 1.4b CEC 13.15.3 and
646 * ANSI-CTA-861-F-FINAL 7.5.2 Audio Data Block for more details.
647 */
648 public final byte[] sad;
649
650 public CodecSad(int audioCodec, byte[] sad) {
651 this.audioCodec = audioCodec;
652 this.sad = sad;
653 }
Amy6bcc5962018-11-28 17:46:44 -0800654
655 public CodecSad(int audioCodec, String sad) {
656 this.audioCodec = audioCodec;
657 this.sad = HexDump.hexStringToByteArray(sad);
658 }
659
660 @Override
661 public boolean equals(Object obj) {
662 if (obj instanceof CodecSad) {
663 CodecSad that = (CodecSad) obj;
664 return that.audioCodec == this.audioCodec
665 && Arrays.equals(that.sad, this.sad);
666 }
667 return false;
668 }
669
670 @Override
671 public int hashCode() {
672 return Objects.hash(
673 audioCodec,
674 Arrays.hashCode(sad));
675 }
Amydbed4e52018-11-28 17:22:20 -0800676 }
Yuncheol Heo63a2e062014-05-27 23:06:01 +0900677}