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 android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.hardware.hdmi.HdmiPortInfo; 22 import android.hardware.tv.cec.V1_0.CecMessage; 23 import android.hardware.tv.cec.V1_0.HotplugEvent; 24 import android.hardware.tv.cec.V1_0.IHdmiCec; 25 import android.hardware.tv.cec.V1_0.IHdmiCec.getPhysicalAddressCallback; 26 import android.hardware.tv.cec.V1_0.IHdmiCecCallback; 27 import android.hardware.tv.cec.V1_0.Result; 28 import android.hardware.tv.cec.V1_0.SendMessageResult; 29 import android.icu.util.IllformedLocaleException; 30 import android.icu.util.ULocale; 31 import android.os.Binder; 32 import android.os.Handler; 33 import android.os.IHwBinder; 34 import android.os.Looper; 35 import android.os.RemoteException; 36 import android.stats.hdmi.HdmiStatsEnums; 37 import android.util.Slog; 38 39 import com.android.internal.annotations.VisibleForTesting; 40 import com.android.internal.util.FrameworkStatsLog; 41 import com.android.internal.util.IndentingPrintWriter; 42 import com.android.server.hdmi.HdmiAnnotations.IoThreadOnly; 43 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; 44 import com.android.server.hdmi.HdmiControlService.DevicePollingCallback; 45 46 import libcore.util.EmptyArray; 47 48 import java.text.SimpleDateFormat; 49 import java.util.ArrayList; 50 import java.util.Arrays; 51 import java.util.Date; 52 import java.util.LinkedList; 53 import java.util.List; 54 import java.util.NoSuchElementException; 55 import java.util.concurrent.ArrayBlockingQueue; 56 import java.util.function.Predicate; 57 58 /** 59 * Manages HDMI-CEC command and behaviors. It converts user's command into CEC command 60 * and pass it to CEC HAL so that it sends message to other device. For incoming 61 * message it translates the message and delegates it to proper module. 62 * 63 * <p>It should be careful to access member variables on IO thread because 64 * it can be accessed from system thread as well. 65 * 66 * <p>It can be created only by {@link HdmiCecController#create} 67 * 68 * <p>Declared as package-private, accessed by {@link HdmiControlService} only. 69 */ 70 final class HdmiCecController { 71 private static final String TAG = "HdmiCecController"; 72 73 /** 74 * Interface to report allocated logical address. 75 */ 76 interface AllocateAddressCallback { 77 /** 78 * Called when a new logical address is allocated. 79 * 80 * @param deviceType requested device type to allocate logical address 81 * @param logicalAddress allocated logical address. If it is 82 * {@link Constants#ADDR_UNREGISTERED}, it means that 83 * it failed to allocate logical address for the given device type 84 */ onAllocated(int deviceType, int logicalAddress)85 void onAllocated(int deviceType, int logicalAddress); 86 } 87 88 private static final byte[] EMPTY_BODY = EmptyArray.BYTE; 89 90 private static final int NUM_LOGICAL_ADDRESS = 16; 91 92 private static final int MAX_DEDICATED_ADDRESS = 11; 93 94 private static final int INITIAL_HDMI_MESSAGE_HISTORY_SIZE = 250; 95 96 private static final int INVALID_PHYSICAL_ADDRESS = 0xFFFF; 97 98 /* 99 * The three flags below determine the action when a message is received. If CEC_DISABLED_IGNORE 100 * bit is set in ACTION_ON_RECEIVE_MSG, then the message is forwarded irrespective of whether 101 * CEC is enabled or disabled. The other flags/bits are also ignored. 102 */ 103 private static final int CEC_DISABLED_IGNORE = 1 << 0; 104 105 /* If CEC_DISABLED_LOG_WARNING bit is set, a warning message is printed if CEC is disabled. */ 106 private static final int CEC_DISABLED_LOG_WARNING = 1 << 1; 107 108 /* If CEC_DISABLED_DROP_MSG bit is set, the message is dropped if CEC is disabled. */ 109 private static final int CEC_DISABLED_DROP_MSG = 1 << 2; 110 111 private static final int ACTION_ON_RECEIVE_MSG = CEC_DISABLED_LOG_WARNING; 112 113 /** Cookie for matching the right end point. */ 114 protected static final int HDMI_CEC_HAL_DEATH_COOKIE = 353; 115 116 // Predicate for whether the given logical address is remote device's one or not. 117 private final Predicate<Integer> mRemoteDeviceAddressPredicate = new Predicate<Integer>() { 118 @Override 119 public boolean test(Integer address) { 120 return !mService.getHdmiCecNetwork().isAllocatedLocalDeviceAddress(address); 121 } 122 }; 123 124 // Predicate whether the given logical address is system audio's one or not 125 private final Predicate<Integer> mSystemAudioAddressPredicate = new Predicate<Integer>() { 126 @Override 127 public boolean test(Integer address) { 128 return HdmiUtils.isEligibleAddressForDevice(Constants.ADDR_AUDIO_SYSTEM, address); 129 } 130 }; 131 132 // Handler instance to process synchronous I/O (mainly send) message. 133 private Handler mIoHandler; 134 135 // Handler instance to process various messages coming from other CEC 136 // device or issued by internal state change. 137 private Handler mControlHandler; 138 139 private final HdmiControlService mService; 140 141 // Stores recent CEC messages and HDMI Hotplug event history for debugging purpose. 142 private ArrayBlockingQueue<Dumpable> mMessageHistory = 143 new ArrayBlockingQueue<>(INITIAL_HDMI_MESSAGE_HISTORY_SIZE); 144 145 private final Object mMessageHistoryLock = new Object(); 146 147 private final NativeWrapper mNativeWrapperImpl; 148 149 private final HdmiCecAtomWriter mHdmiCecAtomWriter; 150 151 // Private constructor. Use HdmiCecController.create(). HdmiCecController( HdmiControlService service, NativeWrapper nativeWrapper, HdmiCecAtomWriter atomWriter)152 private HdmiCecController( 153 HdmiControlService service, NativeWrapper nativeWrapper, HdmiCecAtomWriter atomWriter) { 154 mService = service; 155 mNativeWrapperImpl = nativeWrapper; 156 mHdmiCecAtomWriter = atomWriter; 157 } 158 159 /** 160 * A factory method to get {@link HdmiCecController}. If it fails to initialize 161 * inner device or has no device it will return {@code null}. 162 * 163 * <p>Declared as package-private, accessed by {@link HdmiControlService} only. 164 * @param service {@link HdmiControlService} instance used to create internal handler 165 * and to pass callback for incoming message or event. 166 * @param atomWriter {@link HdmiCecAtomWriter} instance for writing atoms for metrics. 167 * @return {@link HdmiCecController} if device is initialized successfully. Otherwise, 168 * returns {@code null}. 169 */ create(HdmiControlService service, HdmiCecAtomWriter atomWriter)170 static HdmiCecController create(HdmiControlService service, HdmiCecAtomWriter atomWriter) { 171 HdmiCecController controller = createWithNativeWrapper(service, new NativeWrapperImpl11(), 172 atomWriter); 173 if (controller != null) { 174 return controller; 175 } 176 HdmiLogger.warning("Unable to use cec@1.1"); 177 return createWithNativeWrapper(service, new NativeWrapperImpl(), atomWriter); 178 } 179 180 /** 181 * A factory method with injection of native methods for testing. 182 */ createWithNativeWrapper( HdmiControlService service, NativeWrapper nativeWrapper, HdmiCecAtomWriter atomWriter)183 static HdmiCecController createWithNativeWrapper( 184 HdmiControlService service, NativeWrapper nativeWrapper, HdmiCecAtomWriter atomWriter) { 185 HdmiCecController controller = new HdmiCecController(service, nativeWrapper, atomWriter); 186 String nativePtr = nativeWrapper.nativeInit(); 187 if (nativePtr == null) { 188 HdmiLogger.warning("Couldn't get tv.cec service."); 189 return null; 190 } 191 controller.init(nativeWrapper); 192 return controller; 193 } 194 init(NativeWrapper nativeWrapper)195 private void init(NativeWrapper nativeWrapper) { 196 mIoHandler = new Handler(mService.getIoLooper()); 197 mControlHandler = new Handler(mService.getServiceLooper()); 198 nativeWrapper.setCallback(new HdmiCecCallback()); 199 } 200 201 /** 202 * Allocate a new logical address of the given device type. Allocated 203 * address will be reported through {@link AllocateAddressCallback}. 204 * 205 * <p> Declared as package-private, accessed by {@link HdmiControlService} only. 206 * 207 * @param deviceType type of device to used to determine logical address 208 * @param preferredAddress a logical address preferred to be allocated. 209 * If sets {@link Constants#ADDR_UNREGISTERED}, scans 210 * the smallest logical address matched with the given device type. 211 * Otherwise, scan address will start from {@code preferredAddress} 212 * @param callback callback interface to report allocated logical address to caller 213 */ 214 @ServiceThreadOnly allocateLogicalAddress(final int deviceType, final int preferredAddress, final AllocateAddressCallback callback)215 void allocateLogicalAddress(final int deviceType, final int preferredAddress, 216 final AllocateAddressCallback callback) { 217 assertRunOnServiceThread(); 218 219 runOnIoThread(new Runnable() { 220 @Override 221 public void run() { 222 handleAllocateLogicalAddress(deviceType, preferredAddress, callback); 223 } 224 }); 225 } 226 227 /** 228 * Address allocation will check the following addresses (in order): 229 * <ul> 230 * <li>Given preferred logical address (if the address is valid for the given device 231 * type)</li> 232 * <li>All dedicated logical addresses for the given device type</li> 233 * <li>Backup addresses, if valid for the given device type</li> 234 * </ul> 235 */ 236 @IoThreadOnly handleAllocateLogicalAddress(final int deviceType, int preferredAddress, final AllocateAddressCallback callback)237 private void handleAllocateLogicalAddress(final int deviceType, int preferredAddress, 238 final AllocateAddressCallback callback) { 239 assertRunOnIoThread(); 240 List<Integer> logicalAddressesToPoll = new ArrayList<>(); 241 if (HdmiUtils.isEligibleAddressForDevice(deviceType, preferredAddress)) { 242 logicalAddressesToPoll.add(preferredAddress); 243 } 244 for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) { 245 if (!logicalAddressesToPoll.contains(i) && HdmiUtils.isEligibleAddressForDevice( 246 deviceType, i) && HdmiUtils.isEligibleAddressForCecVersion( 247 mService.getCecVersion(), i)) { 248 logicalAddressesToPoll.add(i); 249 } 250 } 251 252 int logicalAddress = Constants.ADDR_UNREGISTERED; 253 for (Integer logicalAddressToPoll : logicalAddressesToPoll) { 254 boolean acked = false; 255 for (int j = 0; j < HdmiConfig.ADDRESS_ALLOCATION_RETRY; ++j) { 256 if (sendPollMessage(logicalAddressToPoll, logicalAddressToPoll, 1)) { 257 acked = true; 258 break; 259 } 260 } 261 // If sending <Polling Message> failed, it becomes new logical address for the 262 // device because no device uses it as logical address of the device. 263 if (!acked) { 264 logicalAddress = logicalAddressToPoll; 265 break; 266 } 267 } 268 269 final int assignedAddress = logicalAddress; 270 HdmiLogger.debug("New logical address for device [%d]: [preferred:%d, assigned:%d]", 271 deviceType, preferredAddress, assignedAddress); 272 if (callback != null) { 273 runOnServiceThread(new Runnable() { 274 @Override 275 public void run() { 276 callback.onAllocated(deviceType, assignedAddress); 277 } 278 }); 279 } 280 } 281 buildBody(int opcode, byte[] params)282 private static byte[] buildBody(int opcode, byte[] params) { 283 byte[] body = new byte[params.length + 1]; 284 body[0] = (byte) opcode; 285 System.arraycopy(params, 0, body, 1, params.length); 286 return body; 287 } 288 289 getPortInfos()290 HdmiPortInfo[] getPortInfos() { 291 return mNativeWrapperImpl.nativeGetPortInfos(); 292 } 293 294 /** 295 * Add a new logical address to the device. Device's HW should be notified 296 * when a new logical address is assigned to a device, so that it can accept 297 * a command having available destinations. 298 * 299 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 300 * 301 * @param newLogicalAddress a logical address to be added 302 * @return 0 on success. Otherwise, returns negative value 303 */ 304 @ServiceThreadOnly addLogicalAddress(int newLogicalAddress)305 int addLogicalAddress(int newLogicalAddress) { 306 assertRunOnServiceThread(); 307 if (HdmiUtils.isValidAddress(newLogicalAddress)) { 308 return mNativeWrapperImpl.nativeAddLogicalAddress(newLogicalAddress); 309 } else { 310 return Result.FAILURE_INVALID_ARGS; 311 } 312 } 313 314 /** 315 * Clear all logical addresses registered in the device. 316 * 317 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 318 */ 319 @ServiceThreadOnly clearLogicalAddress()320 void clearLogicalAddress() { 321 assertRunOnServiceThread(); 322 mNativeWrapperImpl.nativeClearLogicalAddress(); 323 } 324 325 /** 326 * Return the physical address of the device. 327 * 328 * <p>Declared as package-private. accessed by {@link HdmiControlService} and 329 * {@link HdmiCecNetwork} only. 330 * 331 * @return CEC physical address of the device. The range of success address 332 * is between 0x0000 and 0xFFFF. If failed it returns -1 333 */ 334 @ServiceThreadOnly getPhysicalAddress()335 int getPhysicalAddress() { 336 assertRunOnServiceThread(); 337 return mNativeWrapperImpl.nativeGetPhysicalAddress(); 338 } 339 340 /** 341 * Return highest CEC version supported by this device. 342 * 343 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 344 */ 345 @ServiceThreadOnly getVersion()346 int getVersion() { 347 assertRunOnServiceThread(); 348 return mNativeWrapperImpl.nativeGetVersion(); 349 } 350 351 /** 352 * Return vendor id of the device. 353 * 354 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 355 */ 356 @ServiceThreadOnly getVendorId()357 int getVendorId() { 358 assertRunOnServiceThread(); 359 return mNativeWrapperImpl.nativeGetVendorId(); 360 } 361 362 /** 363 * Set an option to CEC HAL. 364 * 365 * @param flag key of option 366 * @param enabled whether to enable/disable the given option. 367 */ 368 @ServiceThreadOnly setOption(int flag, boolean enabled)369 void setOption(int flag, boolean enabled) { 370 assertRunOnServiceThread(); 371 HdmiLogger.debug("setOption: [flag:%d, enabled:%b]", flag, enabled); 372 mNativeWrapperImpl.nativeSetOption(flag, enabled); 373 } 374 375 /** 376 * Informs CEC HAL about the current system language. 377 * 378 * @param language Three-letter code defined in ISO/FDIS 639-2. Must be lowercase letters. 379 */ 380 @ServiceThreadOnly setLanguage(String language)381 void setLanguage(String language) { 382 assertRunOnServiceThread(); 383 if (!isLanguage(language)) { 384 return; 385 } 386 mNativeWrapperImpl.nativeSetLanguage(language); 387 } 388 389 /** 390 * Returns true if the language code is well-formed. 391 */ isLanguage(String language)392 @VisibleForTesting static boolean isLanguage(String language) { 393 // Handle null and empty string because because ULocale.Builder#setLanguage accepts them. 394 if (language == null || language.isEmpty()) { 395 return false; 396 } 397 398 ULocale.Builder builder = new ULocale.Builder(); 399 try { 400 builder.setLanguage(language); 401 return true; 402 } catch (IllformedLocaleException e) { 403 return false; 404 } 405 } 406 407 /** 408 * Configure ARC circuit in the hardware logic to start or stop the feature. 409 * 410 * @param port ID of HDMI port to which AVR is connected 411 * @param enabled whether to enable/disable ARC 412 */ 413 @ServiceThreadOnly enableAudioReturnChannel(int port, boolean enabled)414 void enableAudioReturnChannel(int port, boolean enabled) { 415 assertRunOnServiceThread(); 416 mNativeWrapperImpl.nativeEnableAudioReturnChannel(port, enabled); 417 } 418 419 /** 420 * Return the connection status of the specified port 421 * 422 * @param port port number to check connection status 423 * @return true if connected; otherwise, return false 424 */ 425 @ServiceThreadOnly isConnected(int port)426 boolean isConnected(int port) { 427 assertRunOnServiceThread(); 428 return mNativeWrapperImpl.nativeIsConnected(port); 429 } 430 431 /** 432 * Poll all remote devices. It sends <Polling Message> to all remote 433 * devices. 434 * 435 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 436 * 437 * @param callback an interface used to get a list of all remote devices' address 438 * @param sourceAddress a logical address of source device where sends polling message 439 * @param pickStrategy strategy how to pick polling candidates 440 * @param retryCount the number of retry used to send polling message to remote devices 441 */ 442 @ServiceThreadOnly pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy, int retryCount)443 void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy, 444 int retryCount) { 445 assertRunOnServiceThread(); 446 447 // Extract polling candidates. No need to poll against local devices. 448 List<Integer> pollingCandidates = pickPollCandidates(pickStrategy); 449 ArrayList<Integer> allocated = new ArrayList<>(); 450 runDevicePolling(sourceAddress, pollingCandidates, retryCount, callback, allocated); 451 } 452 pickPollCandidates(int pickStrategy)453 private List<Integer> pickPollCandidates(int pickStrategy) { 454 int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK; 455 Predicate<Integer> pickPredicate = null; 456 switch (strategy) { 457 case Constants.POLL_STRATEGY_SYSTEM_AUDIO: 458 pickPredicate = mSystemAudioAddressPredicate; 459 break; 460 case Constants.POLL_STRATEGY_REMOTES_DEVICES: 461 default: // The default is POLL_STRATEGY_REMOTES_DEVICES. 462 pickPredicate = mRemoteDeviceAddressPredicate; 463 break; 464 } 465 466 int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK; 467 LinkedList<Integer> pollingCandidates = new LinkedList<>(); 468 switch (iterationStrategy) { 469 case Constants.POLL_ITERATION_IN_ORDER: 470 for (int i = Constants.ADDR_TV; i <= Constants.ADDR_SPECIFIC_USE; ++i) { 471 if (pickPredicate.test(i)) { 472 pollingCandidates.add(i); 473 } 474 } 475 break; 476 case Constants.POLL_ITERATION_REVERSE_ORDER: 477 default: // The default is reverse order. 478 for (int i = Constants.ADDR_SPECIFIC_USE; i >= Constants.ADDR_TV; --i) { 479 if (pickPredicate.test(i)) { 480 pollingCandidates.add(i); 481 } 482 } 483 break; 484 } 485 return pollingCandidates; 486 } 487 488 @ServiceThreadOnly runDevicePolling(final int sourceAddress, final List<Integer> candidates, final int retryCount, final DevicePollingCallback callback, final List<Integer> allocated)489 private void runDevicePolling(final int sourceAddress, 490 final List<Integer> candidates, final int retryCount, 491 final DevicePollingCallback callback, final List<Integer> allocated) { 492 assertRunOnServiceThread(); 493 if (candidates.isEmpty()) { 494 if (callback != null) { 495 HdmiLogger.debug("[P]:AllocatedAddress=%s", allocated.toString()); 496 callback.onPollingFinished(allocated); 497 } 498 return; 499 } 500 501 final Integer candidate = candidates.remove(0); 502 // Proceed polling action for the next address once polling action for the 503 // previous address is done. 504 runOnIoThread(new Runnable() { 505 @Override 506 public void run() { 507 if (sendPollMessage(sourceAddress, candidate, retryCount)) { 508 allocated.add(candidate); 509 } 510 runOnServiceThread(new Runnable() { 511 @Override 512 public void run() { 513 runDevicePolling(sourceAddress, candidates, retryCount, callback, 514 allocated); 515 } 516 }); 517 } 518 }); 519 } 520 521 @IoThreadOnly sendPollMessage(int sourceAddress, int destinationAddress, int retryCount)522 private boolean sendPollMessage(int sourceAddress, int destinationAddress, int retryCount) { 523 assertRunOnIoThread(); 524 for (int i = 0; i < retryCount; ++i) { 525 // <Polling Message> is a message which has empty body. 526 int ret = 527 mNativeWrapperImpl.nativeSendCecCommand( 528 sourceAddress, destinationAddress, EMPTY_BODY); 529 if (ret == SendMessageResult.SUCCESS) { 530 return true; 531 } else if (ret != SendMessageResult.NACK) { 532 // Unusual failure 533 HdmiLogger.warning("Failed to send a polling message(%d->%d) with return code %d", 534 sourceAddress, destinationAddress, ret); 535 } 536 } 537 return false; 538 } 539 assertRunOnIoThread()540 private void assertRunOnIoThread() { 541 if (Looper.myLooper() != mIoHandler.getLooper()) { 542 throw new IllegalStateException("Should run on io thread."); 543 } 544 } 545 assertRunOnServiceThread()546 private void assertRunOnServiceThread() { 547 if (Looper.myLooper() != mControlHandler.getLooper()) { 548 throw new IllegalStateException("Should run on service thread."); 549 } 550 } 551 552 // Run a Runnable on IO thread. 553 // It should be careful to access member variables on IO thread because 554 // it can be accessed from system thread as well. 555 @VisibleForTesting runOnIoThread(Runnable runnable)556 void runOnIoThread(Runnable runnable) { 557 mIoHandler.post(new WorkSourceUidPreservingRunnable(runnable)); 558 } 559 560 @VisibleForTesting runOnServiceThread(Runnable runnable)561 void runOnServiceThread(Runnable runnable) { 562 mControlHandler.post(new WorkSourceUidPreservingRunnable(runnable)); 563 } 564 565 @ServiceThreadOnly flush(final Runnable runnable)566 void flush(final Runnable runnable) { 567 assertRunOnServiceThread(); 568 runOnIoThread(new Runnable() { 569 @Override 570 public void run() { 571 // This ensures the runnable for cleanup is performed after all the pending 572 // commands are processed by IO thread. 573 runOnServiceThread(runnable); 574 } 575 }); 576 } 577 isAcceptableAddress(int address)578 private boolean isAcceptableAddress(int address) { 579 // Can access command targeting devices available in local device or broadcast command. 580 if (address == Constants.ADDR_BROADCAST) { 581 return true; 582 } 583 return mService.getHdmiCecNetwork().isAllocatedLocalDeviceAddress(address); 584 } 585 586 @ServiceThreadOnly 587 @VisibleForTesting onReceiveCommand(HdmiCecMessage message)588 void onReceiveCommand(HdmiCecMessage message) { 589 assertRunOnServiceThread(); 590 if (((ACTION_ON_RECEIVE_MSG & CEC_DISABLED_IGNORE) == 0) 591 && !mService.isControlEnabled() 592 && !HdmiCecMessage.isCecTransportMessage(message.getOpcode())) { 593 if ((ACTION_ON_RECEIVE_MSG & CEC_DISABLED_LOG_WARNING) != 0) { 594 HdmiLogger.warning("Message " + message + " received when cec disabled"); 595 } 596 if ((ACTION_ON_RECEIVE_MSG & CEC_DISABLED_DROP_MSG) != 0) { 597 return; 598 } 599 } 600 if (mService.isAddressAllocated() && !isAcceptableAddress(message.getDestination())) { 601 return; 602 } 603 @Constants.HandleMessageResult int messageState = mService.handleCecCommand(message); 604 if (messageState == Constants.NOT_HANDLED) { 605 // Message was not handled 606 maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE); 607 } else if (messageState != Constants.HANDLED) { 608 // Message handler wants to send a feature abort 609 maySendFeatureAbortCommand(message, messageState); 610 } 611 } 612 613 @ServiceThreadOnly maySendFeatureAbortCommand(HdmiCecMessage message, @Constants.AbortReason int reason)614 void maySendFeatureAbortCommand(HdmiCecMessage message, @Constants.AbortReason int reason) { 615 assertRunOnServiceThread(); 616 // Swap the source and the destination. 617 int src = message.getDestination(); 618 int dest = message.getSource(); 619 if (src == Constants.ADDR_BROADCAST || dest == Constants.ADDR_UNREGISTERED) { 620 // Don't reply <Feature Abort> from the unregistered devices or for the broadcasted 621 // messages. See CEC 12.2 Protocol General Rules for detail. 622 return; 623 } 624 int originalOpcode = message.getOpcode(); 625 if (originalOpcode == Constants.MESSAGE_FEATURE_ABORT) { 626 return; 627 } 628 sendCommand( 629 HdmiCecMessageBuilder.buildFeatureAbortCommand(src, dest, originalOpcode, reason)); 630 } 631 632 @ServiceThreadOnly sendCommand(HdmiCecMessage cecMessage)633 void sendCommand(HdmiCecMessage cecMessage) { 634 assertRunOnServiceThread(); 635 sendCommand(cecMessage, null); 636 } 637 638 /** 639 * Returns the calling UID of the original Binder call that triggered this code. 640 * If this code was not triggered by a Binder call, returns the UID of this process. 641 */ getCallingUid()642 private int getCallingUid() { 643 int workSourceUid = Binder.getCallingWorkSourceUid(); 644 if (workSourceUid == -1) { 645 return Binder.getCallingUid(); 646 } 647 return workSourceUid; 648 } 649 650 @ServiceThreadOnly sendCommand(final HdmiCecMessage cecMessage, final HdmiControlService.SendMessageCallback callback)651 void sendCommand(final HdmiCecMessage cecMessage, 652 final HdmiControlService.SendMessageCallback callback) { 653 assertRunOnServiceThread(); 654 addCecMessageToHistory(false /* isReceived */, cecMessage); 655 runOnIoThread(new Runnable() { 656 @Override 657 public void run() { 658 HdmiLogger.debug("[S]:" + cecMessage); 659 byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams()); 660 int retransmissionCount = 0; 661 int errorCode = SendMessageResult.SUCCESS; 662 do { 663 errorCode = mNativeWrapperImpl.nativeSendCecCommand( 664 cecMessage.getSource(), cecMessage.getDestination(), body); 665 if (errorCode == SendMessageResult.SUCCESS) { 666 break; 667 } 668 } while (retransmissionCount++ < HdmiConfig.RETRANSMISSION_COUNT); 669 670 final int finalError = errorCode; 671 if (finalError != SendMessageResult.SUCCESS) { 672 Slog.w(TAG, "Failed to send " + cecMessage + " with errorCode=" + finalError); 673 } 674 runOnServiceThread(new Runnable() { 675 @Override 676 public void run() { 677 mHdmiCecAtomWriter.messageReported( 678 cecMessage, 679 FrameworkStatsLog.HDMI_CEC_MESSAGE_REPORTED__DIRECTION__OUTGOING, 680 getCallingUid(), 681 finalError 682 ); 683 if (callback != null) { 684 callback.onSendCompleted(finalError); 685 } 686 } 687 }); 688 } 689 }); 690 } 691 692 /** 693 * Called when incoming CEC message arrived. 694 */ 695 @ServiceThreadOnly handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body)696 private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) { 697 assertRunOnServiceThread(); 698 699 if (body.length == 0) { 700 Slog.e(TAG, "Message with empty body received."); 701 return; 702 } 703 704 HdmiCecMessage command = HdmiCecMessage.build(srcAddress, dstAddress, body[0], 705 Arrays.copyOfRange(body, 1, body.length)); 706 707 if (command.getValidationResult() != HdmiCecMessageValidator.OK) { 708 Slog.e(TAG, "Invalid message received: " + command); 709 } 710 711 HdmiLogger.debug("[R]:" + command); 712 addCecMessageToHistory(true /* isReceived */, command); 713 714 mHdmiCecAtomWriter.messageReported(command, 715 incomingMessageDirection(srcAddress, dstAddress), getCallingUid()); 716 717 onReceiveCommand(command); 718 } 719 720 /** 721 * Computes the direction of an incoming message, as implied by the source and 722 * destination addresses. This will usually return INCOMING; if not, it can indicate a bug. 723 */ incomingMessageDirection(int srcAddress, int dstAddress)724 private int incomingMessageDirection(int srcAddress, int dstAddress) { 725 boolean sourceIsLocal = false; 726 boolean destinationIsLocal = dstAddress == Constants.ADDR_BROADCAST; 727 for (HdmiCecLocalDevice localDevice : mService.getHdmiCecNetwork().getLocalDeviceList()) { 728 int logicalAddress = localDevice.getDeviceInfo().getLogicalAddress(); 729 if (logicalAddress == srcAddress) { 730 sourceIsLocal = true; 731 } 732 if (logicalAddress == dstAddress) { 733 destinationIsLocal = true; 734 } 735 } 736 737 if (!sourceIsLocal && destinationIsLocal) { 738 return HdmiStatsEnums.INCOMING; 739 } else if (sourceIsLocal && destinationIsLocal) { 740 return HdmiStatsEnums.TO_SELF; 741 } 742 return HdmiStatsEnums.MESSAGE_DIRECTION_OTHER; 743 } 744 745 /** 746 * Called when a hotplug event issues. 747 */ 748 @ServiceThreadOnly handleHotplug(int port, boolean connected)749 private void handleHotplug(int port, boolean connected) { 750 assertRunOnServiceThread(); 751 HdmiLogger.debug("Hotplug event:[port:%d, connected:%b]", port, connected); 752 addHotplugEventToHistory(port, connected); 753 mService.onHotplug(port, connected); 754 } 755 756 @ServiceThreadOnly addHotplugEventToHistory(int port, boolean connected)757 private void addHotplugEventToHistory(int port, boolean connected) { 758 assertRunOnServiceThread(); 759 addEventToHistory(new HotplugHistoryRecord(port, connected)); 760 } 761 762 @ServiceThreadOnly addCecMessageToHistory(boolean isReceived, HdmiCecMessage message)763 private void addCecMessageToHistory(boolean isReceived, HdmiCecMessage message) { 764 assertRunOnServiceThread(); 765 addEventToHistory(new MessageHistoryRecord(isReceived, message)); 766 } 767 addEventToHistory(Dumpable event)768 private void addEventToHistory(Dumpable event) { 769 synchronized (mMessageHistoryLock) { 770 if (!mMessageHistory.offer(event)) { 771 mMessageHistory.poll(); 772 mMessageHistory.offer(event); 773 } 774 } 775 } 776 getMessageHistorySize()777 int getMessageHistorySize() { 778 synchronized (mMessageHistoryLock) { 779 return mMessageHistory.size() + mMessageHistory.remainingCapacity(); 780 } 781 } 782 setMessageHistorySize(int newSize)783 boolean setMessageHistorySize(int newSize) { 784 if (newSize < INITIAL_HDMI_MESSAGE_HISTORY_SIZE) { 785 return false; 786 } 787 ArrayBlockingQueue<Dumpable> newMessageHistory = new ArrayBlockingQueue<>(newSize); 788 789 synchronized (mMessageHistoryLock) { 790 if (newSize < mMessageHistory.size()) { 791 for (int i = 0; i < mMessageHistory.size() - newSize; i++) { 792 mMessageHistory.poll(); 793 } 794 } 795 796 newMessageHistory.addAll(mMessageHistory); 797 mMessageHistory = newMessageHistory; 798 } 799 return true; 800 } 801 dump(final IndentingPrintWriter pw)802 void dump(final IndentingPrintWriter pw) { 803 pw.println("CEC message history:"); 804 pw.increaseIndent(); 805 final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 806 for (Dumpable record : mMessageHistory) { 807 record.dump(pw, sdf); 808 } 809 pw.decreaseIndent(); 810 } 811 812 protected interface NativeWrapper { nativeInit()813 String nativeInit(); setCallback(HdmiCecCallback callback)814 void setCallback(HdmiCecCallback callback); nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body)815 int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body); nativeAddLogicalAddress(int logicalAddress)816 int nativeAddLogicalAddress(int logicalAddress); nativeClearLogicalAddress()817 void nativeClearLogicalAddress(); nativeGetPhysicalAddress()818 int nativeGetPhysicalAddress(); nativeGetVersion()819 int nativeGetVersion(); nativeGetVendorId()820 int nativeGetVendorId(); nativeGetPortInfos()821 HdmiPortInfo[] nativeGetPortInfos(); nativeSetOption(int flag, boolean enabled)822 void nativeSetOption(int flag, boolean enabled); nativeSetLanguage(String language)823 void nativeSetLanguage(String language); nativeEnableAudioReturnChannel(int port, boolean flag)824 void nativeEnableAudioReturnChannel(int port, boolean flag); nativeIsConnected(int port)825 boolean nativeIsConnected(int port); 826 } 827 828 private static final class NativeWrapperImpl11 implements NativeWrapper, 829 IHwBinder.DeathRecipient, getPhysicalAddressCallback { 830 private android.hardware.tv.cec.V1_1.IHdmiCec mHdmiCec; 831 @Nullable private HdmiCecCallback mCallback; 832 833 private final Object mLock = new Object(); 834 private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS; 835 836 @Override nativeInit()837 public String nativeInit() { 838 return (connectToHal() ? mHdmiCec.toString() : null); 839 } 840 connectToHal()841 boolean connectToHal() { 842 try { 843 mHdmiCec = android.hardware.tv.cec.V1_1.IHdmiCec.getService(true); 844 try { 845 mHdmiCec.linkToDeath(this, HDMI_CEC_HAL_DEATH_COOKIE); 846 } catch (RemoteException e) { 847 HdmiLogger.error("Couldn't link to death : ", e); 848 } 849 } catch (RemoteException | NoSuchElementException e) { 850 HdmiLogger.error("Couldn't connect to cec@1.1", e); 851 return false; 852 } 853 return true; 854 } 855 856 @Override onValues(int result, short addr)857 public void onValues(int result, short addr) { 858 if (result == Result.SUCCESS) { 859 synchronized (mLock) { 860 mPhysicalAddress = new Short(addr).intValue(); 861 } 862 } 863 } 864 865 @Override serviceDied(long cookie)866 public void serviceDied(long cookie) { 867 if (cookie == HDMI_CEC_HAL_DEATH_COOKIE) { 868 HdmiLogger.error("Service died cookie : " + cookie + "; reconnecting"); 869 connectToHal(); 870 // Reconnect the callback 871 if (mCallback != null) { 872 setCallback(mCallback); 873 } 874 } 875 } 876 877 @Override setCallback(HdmiCecCallback callback)878 public void setCallback(HdmiCecCallback callback) { 879 mCallback = callback; 880 try { 881 mHdmiCec.setCallback_1_1(new HdmiCecCallback11(callback)); 882 } catch (RemoteException e) { 883 HdmiLogger.error("Couldn't initialise tv.cec callback : ", e); 884 } 885 } 886 887 @Override nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body)888 public int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body) { 889 android.hardware.tv.cec.V1_1.CecMessage message = 890 new android.hardware.tv.cec.V1_1.CecMessage(); 891 message.initiator = srcAddress; 892 message.destination = dstAddress; 893 message.body = new ArrayList<>(body.length); 894 for (byte b : body) { 895 message.body.add(b); 896 } 897 try { 898 return mHdmiCec.sendMessage_1_1(message); 899 } catch (RemoteException e) { 900 HdmiLogger.error("Failed to send CEC message : ", e); 901 return SendMessageResult.FAIL; 902 } 903 } 904 905 @Override nativeAddLogicalAddress(int logicalAddress)906 public int nativeAddLogicalAddress(int logicalAddress) { 907 try { 908 return mHdmiCec.addLogicalAddress_1_1(logicalAddress); 909 } catch (RemoteException e) { 910 HdmiLogger.error("Failed to add a logical address : ", e); 911 return Result.FAILURE_INVALID_ARGS; 912 } 913 } 914 915 @Override nativeClearLogicalAddress()916 public void nativeClearLogicalAddress() { 917 try { 918 mHdmiCec.clearLogicalAddress(); 919 } catch (RemoteException e) { 920 HdmiLogger.error("Failed to clear logical address : ", e); 921 } 922 } 923 924 @Override nativeGetPhysicalAddress()925 public int nativeGetPhysicalAddress() { 926 try { 927 mHdmiCec.getPhysicalAddress(this); 928 return mPhysicalAddress; 929 } catch (RemoteException e) { 930 HdmiLogger.error("Failed to get physical address : ", e); 931 return INVALID_PHYSICAL_ADDRESS; 932 } 933 } 934 935 @Override nativeGetVersion()936 public int nativeGetVersion() { 937 try { 938 return mHdmiCec.getCecVersion(); 939 } catch (RemoteException e) { 940 HdmiLogger.error("Failed to get cec version : ", e); 941 return Result.FAILURE_UNKNOWN; 942 } 943 } 944 945 @Override nativeGetVendorId()946 public int nativeGetVendorId() { 947 try { 948 return mHdmiCec.getVendorId(); 949 } catch (RemoteException e) { 950 HdmiLogger.error("Failed to get vendor id : ", e); 951 return Result.FAILURE_UNKNOWN; 952 } 953 } 954 955 @Override nativeGetPortInfos()956 public HdmiPortInfo[] nativeGetPortInfos() { 957 try { 958 ArrayList<android.hardware.tv.cec.V1_0.HdmiPortInfo> hdmiPortInfos = 959 mHdmiCec.getPortInfo(); 960 HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[hdmiPortInfos.size()]; 961 int i = 0; 962 for (android.hardware.tv.cec.V1_0.HdmiPortInfo portInfo : hdmiPortInfos) { 963 hdmiPortInfo[i] = new HdmiPortInfo(portInfo.portId, 964 portInfo.type, 965 portInfo.physicalAddress, 966 portInfo.cecSupported, 967 false, 968 portInfo.arcSupported); 969 i++; 970 } 971 return hdmiPortInfo; 972 } catch (RemoteException e) { 973 HdmiLogger.error("Failed to get port information : ", e); 974 return null; 975 } 976 } 977 978 @Override nativeSetOption(int flag, boolean enabled)979 public void nativeSetOption(int flag, boolean enabled) { 980 try { 981 mHdmiCec.setOption(flag, enabled); 982 } catch (RemoteException e) { 983 HdmiLogger.error("Failed to set option : ", e); 984 } 985 } 986 987 @Override nativeSetLanguage(String language)988 public void nativeSetLanguage(String language) { 989 try { 990 mHdmiCec.setLanguage(language); 991 } catch (RemoteException e) { 992 HdmiLogger.error("Failed to set language : ", e); 993 } 994 } 995 996 @Override nativeEnableAudioReturnChannel(int port, boolean flag)997 public void nativeEnableAudioReturnChannel(int port, boolean flag) { 998 try { 999 mHdmiCec.enableAudioReturnChannel(port, flag); 1000 } catch (RemoteException e) { 1001 HdmiLogger.error("Failed to enable/disable ARC : ", e); 1002 } 1003 } 1004 1005 @Override nativeIsConnected(int port)1006 public boolean nativeIsConnected(int port) { 1007 try { 1008 return mHdmiCec.isConnected(port); 1009 } catch (RemoteException e) { 1010 HdmiLogger.error("Failed to get connection info : ", e); 1011 return false; 1012 } 1013 } 1014 } 1015 1016 private static final class NativeWrapperImpl implements NativeWrapper, 1017 IHwBinder.DeathRecipient, getPhysicalAddressCallback { 1018 private android.hardware.tv.cec.V1_0.IHdmiCec mHdmiCec; 1019 @Nullable private HdmiCecCallback mCallback; 1020 1021 private final Object mLock = new Object(); 1022 private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS; 1023 1024 @Override nativeInit()1025 public String nativeInit() { 1026 return (connectToHal() ? mHdmiCec.toString() : null); 1027 } 1028 connectToHal()1029 boolean connectToHal() { 1030 try { 1031 mHdmiCec = IHdmiCec.getService(true); 1032 try { 1033 mHdmiCec.linkToDeath(this, HDMI_CEC_HAL_DEATH_COOKIE); 1034 } catch (RemoteException e) { 1035 HdmiLogger.error("Couldn't link to death : ", e); 1036 } 1037 } catch (RemoteException | NoSuchElementException e) { 1038 HdmiLogger.error("Couldn't connect to cec@1.0", e); 1039 return false; 1040 } 1041 return true; 1042 } 1043 1044 @Override setCallback(@onNull HdmiCecCallback callback)1045 public void setCallback(@NonNull HdmiCecCallback callback) { 1046 mCallback = callback; 1047 try { 1048 mHdmiCec.setCallback(new HdmiCecCallback10(callback)); 1049 } catch (RemoteException e) { 1050 HdmiLogger.error("Couldn't initialise tv.cec callback : ", e); 1051 } 1052 } 1053 1054 @Override nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body)1055 public int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body) { 1056 CecMessage message = new CecMessage(); 1057 message.initiator = srcAddress; 1058 message.destination = dstAddress; 1059 message.body = new ArrayList<>(body.length); 1060 for (byte b : body) { 1061 message.body.add(b); 1062 } 1063 try { 1064 return mHdmiCec.sendMessage(message); 1065 } catch (RemoteException e) { 1066 HdmiLogger.error("Failed to send CEC message : ", e); 1067 return SendMessageResult.FAIL; 1068 } 1069 } 1070 1071 @Override nativeAddLogicalAddress(int logicalAddress)1072 public int nativeAddLogicalAddress(int logicalAddress) { 1073 try { 1074 return mHdmiCec.addLogicalAddress(logicalAddress); 1075 } catch (RemoteException e) { 1076 HdmiLogger.error("Failed to add a logical address : ", e); 1077 return Result.FAILURE_INVALID_ARGS; 1078 } 1079 } 1080 1081 @Override nativeClearLogicalAddress()1082 public void nativeClearLogicalAddress() { 1083 try { 1084 mHdmiCec.clearLogicalAddress(); 1085 } catch (RemoteException e) { 1086 HdmiLogger.error("Failed to clear logical address : ", e); 1087 } 1088 } 1089 1090 @Override nativeGetPhysicalAddress()1091 public int nativeGetPhysicalAddress() { 1092 try { 1093 mHdmiCec.getPhysicalAddress(this); 1094 return mPhysicalAddress; 1095 } catch (RemoteException e) { 1096 HdmiLogger.error("Failed to get physical address : ", e); 1097 return INVALID_PHYSICAL_ADDRESS; 1098 } 1099 } 1100 1101 @Override nativeGetVersion()1102 public int nativeGetVersion() { 1103 try { 1104 return mHdmiCec.getCecVersion(); 1105 } catch (RemoteException e) { 1106 HdmiLogger.error("Failed to get cec version : ", e); 1107 return Result.FAILURE_UNKNOWN; 1108 } 1109 } 1110 1111 @Override nativeGetVendorId()1112 public int nativeGetVendorId() { 1113 try { 1114 return mHdmiCec.getVendorId(); 1115 } catch (RemoteException e) { 1116 HdmiLogger.error("Failed to get vendor id : ", e); 1117 return Result.FAILURE_UNKNOWN; 1118 } 1119 } 1120 1121 @Override nativeGetPortInfos()1122 public HdmiPortInfo[] nativeGetPortInfos() { 1123 try { 1124 ArrayList<android.hardware.tv.cec.V1_0.HdmiPortInfo> hdmiPortInfos = 1125 mHdmiCec.getPortInfo(); 1126 HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[hdmiPortInfos.size()]; 1127 int i = 0; 1128 for (android.hardware.tv.cec.V1_0.HdmiPortInfo portInfo : hdmiPortInfos) { 1129 hdmiPortInfo[i] = new HdmiPortInfo(portInfo.portId, 1130 portInfo.type, 1131 portInfo.physicalAddress, 1132 portInfo.cecSupported, 1133 false, 1134 portInfo.arcSupported); 1135 i++; 1136 } 1137 return hdmiPortInfo; 1138 } catch (RemoteException e) { 1139 HdmiLogger.error("Failed to get port information : ", e); 1140 return null; 1141 } 1142 } 1143 1144 @Override nativeSetOption(int flag, boolean enabled)1145 public void nativeSetOption(int flag, boolean enabled) { 1146 try { 1147 mHdmiCec.setOption(flag, enabled); 1148 } catch (RemoteException e) { 1149 HdmiLogger.error("Failed to set option : ", e); 1150 } 1151 } 1152 1153 @Override nativeSetLanguage(String language)1154 public void nativeSetLanguage(String language) { 1155 try { 1156 mHdmiCec.setLanguage(language); 1157 } catch (RemoteException e) { 1158 HdmiLogger.error("Failed to set language : ", e); 1159 } 1160 } 1161 1162 @Override nativeEnableAudioReturnChannel(int port, boolean flag)1163 public void nativeEnableAudioReturnChannel(int port, boolean flag) { 1164 try { 1165 mHdmiCec.enableAudioReturnChannel(port, flag); 1166 } catch (RemoteException e) { 1167 HdmiLogger.error("Failed to enable/disable ARC : ", e); 1168 } 1169 } 1170 1171 @Override nativeIsConnected(int port)1172 public boolean nativeIsConnected(int port) { 1173 try { 1174 return mHdmiCec.isConnected(port); 1175 } catch (RemoteException e) { 1176 HdmiLogger.error("Failed to get connection info : ", e); 1177 return false; 1178 } 1179 } 1180 1181 @Override serviceDied(long cookie)1182 public void serviceDied(long cookie) { 1183 if (cookie == HDMI_CEC_HAL_DEATH_COOKIE) { 1184 HdmiLogger.error("Service died cookie : " + cookie + "; reconnecting"); 1185 connectToHal(); 1186 // Reconnect the callback 1187 if (mCallback != null) { 1188 setCallback(mCallback); 1189 } 1190 } 1191 } 1192 1193 @Override onValues(int result, short addr)1194 public void onValues(int result, short addr) { 1195 if (result == Result.SUCCESS) { 1196 synchronized (mLock) { 1197 mPhysicalAddress = new Short(addr).intValue(); 1198 } 1199 } 1200 } 1201 } 1202 1203 final class HdmiCecCallback { onCecMessage(int initiator, int destination, byte[] body)1204 public void onCecMessage(int initiator, int destination, byte[] body) { 1205 runOnServiceThread( 1206 () -> handleIncomingCecCommand(initiator, destination, body)); 1207 } 1208 onHotplugEvent(int portId, boolean connected)1209 public void onHotplugEvent(int portId, boolean connected) { 1210 runOnServiceThread(() -> handleHotplug(portId, connected)); 1211 } 1212 } 1213 1214 private static final class HdmiCecCallback10 extends IHdmiCecCallback.Stub { 1215 private final HdmiCecCallback mHdmiCecCallback; 1216 HdmiCecCallback10(HdmiCecCallback hdmiCecCallback)1217 HdmiCecCallback10(HdmiCecCallback hdmiCecCallback) { 1218 mHdmiCecCallback = hdmiCecCallback; 1219 } 1220 1221 @Override onCecMessage(CecMessage message)1222 public void onCecMessage(CecMessage message) throws RemoteException { 1223 byte[] body = new byte[message.body.size()]; 1224 for (int i = 0; i < message.body.size(); i++) { 1225 body[i] = message.body.get(i); 1226 } 1227 mHdmiCecCallback.onCecMessage(message.initiator, message.destination, body); 1228 } 1229 1230 @Override onHotplugEvent(HotplugEvent event)1231 public void onHotplugEvent(HotplugEvent event) throws RemoteException { 1232 mHdmiCecCallback.onHotplugEvent(event.portId, event.connected); 1233 } 1234 } 1235 1236 private static final class HdmiCecCallback11 1237 extends android.hardware.tv.cec.V1_1.IHdmiCecCallback.Stub { 1238 private final HdmiCecCallback mHdmiCecCallback; 1239 HdmiCecCallback11(HdmiCecCallback hdmiCecCallback)1240 HdmiCecCallback11(HdmiCecCallback hdmiCecCallback) { 1241 mHdmiCecCallback = hdmiCecCallback; 1242 } 1243 1244 @Override onCecMessage_1_1(android.hardware.tv.cec.V1_1.CecMessage message)1245 public void onCecMessage_1_1(android.hardware.tv.cec.V1_1.CecMessage message) 1246 throws RemoteException { 1247 byte[] body = new byte[message.body.size()]; 1248 for (int i = 0; i < message.body.size(); i++) { 1249 body[i] = message.body.get(i); 1250 } 1251 mHdmiCecCallback.onCecMessage(message.initiator, message.destination, body); 1252 } 1253 1254 @Override onCecMessage(CecMessage message)1255 public void onCecMessage(CecMessage message) throws RemoteException { 1256 byte[] body = new byte[message.body.size()]; 1257 for (int i = 0; i < message.body.size(); i++) { 1258 body[i] = message.body.get(i); 1259 } 1260 mHdmiCecCallback.onCecMessage(message.initiator, message.destination, body); 1261 } 1262 1263 @Override onHotplugEvent(HotplugEvent event)1264 public void onHotplugEvent(HotplugEvent event) throws RemoteException { 1265 mHdmiCecCallback.onHotplugEvent(event.portId, event.connected); 1266 } 1267 } 1268 1269 public abstract static class Dumpable { 1270 protected final long mTime; 1271 Dumpable()1272 Dumpable() { 1273 mTime = System.currentTimeMillis(); 1274 } 1275 dump(IndentingPrintWriter pw, SimpleDateFormat sdf)1276 abstract void dump(IndentingPrintWriter pw, SimpleDateFormat sdf); 1277 } 1278 1279 private static final class MessageHistoryRecord extends Dumpable { 1280 private final boolean mIsReceived; // true if received message and false if sent message 1281 private final HdmiCecMessage mMessage; 1282 MessageHistoryRecord(boolean isReceived, HdmiCecMessage message)1283 MessageHistoryRecord(boolean isReceived, HdmiCecMessage message) { 1284 super(); 1285 mIsReceived = isReceived; 1286 mMessage = message; 1287 } 1288 1289 @Override dump(final IndentingPrintWriter pw, SimpleDateFormat sdf)1290 void dump(final IndentingPrintWriter pw, SimpleDateFormat sdf) { 1291 pw.print(mIsReceived ? "[R]" : "[S]"); 1292 pw.print(" time="); 1293 pw.print(sdf.format(new Date(mTime))); 1294 pw.print(" message="); 1295 pw.println(mMessage); 1296 } 1297 } 1298 1299 private static final class HotplugHistoryRecord extends Dumpable { 1300 private final int mPort; 1301 private final boolean mConnected; 1302 HotplugHistoryRecord(int port, boolean connected)1303 HotplugHistoryRecord(int port, boolean connected) { 1304 super(); 1305 mPort = port; 1306 mConnected = connected; 1307 } 1308 1309 @Override dump(final IndentingPrintWriter pw, SimpleDateFormat sdf)1310 void dump(final IndentingPrintWriter pw, SimpleDateFormat sdf) { 1311 pw.print("[H]"); 1312 pw.print(" time="); 1313 pw.print(sdf.format(new Date(mTime))); 1314 pw.print(" hotplug port="); 1315 pw.print(mPort); 1316 pw.print(" connected="); 1317 pw.println(mConnected); 1318 } 1319 } 1320 } 1321