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.HotplugEvent; 23 import android.hardware.tv.cec.V1_0.IHdmiCec.getPhysicalAddressCallback; 24 import android.hardware.tv.cec.V1_0.OptionKey; 25 import android.hardware.tv.cec.V1_0.Result; 26 import android.hardware.tv.cec.V1_0.SendMessageResult; 27 import android.hardware.tv.hdmi.cec.CecMessage; 28 import android.hardware.tv.hdmi.cec.IHdmiCec; 29 import android.hardware.tv.hdmi.cec.IHdmiCecCallback; 30 import android.hardware.tv.hdmi.connection.IHdmiConnection; 31 import android.hardware.tv.hdmi.connection.IHdmiConnectionCallback; 32 import android.icu.util.IllformedLocaleException; 33 import android.icu.util.ULocale; 34 import android.os.Binder; 35 import android.os.Handler; 36 import android.os.IBinder; 37 import android.os.IHwBinder; 38 import android.os.Looper; 39 import android.os.RemoteException; 40 import android.os.ServiceManager; 41 import android.os.ServiceSpecificException; 42 import android.stats.hdmi.HdmiStatsEnums; 43 import android.util.Slog; 44 45 import com.android.internal.annotations.VisibleForTesting; 46 import com.android.internal.util.FrameworkStatsLog; 47 import com.android.internal.util.IndentingPrintWriter; 48 import com.android.server.hdmi.HdmiAnnotations.IoThreadOnly; 49 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; 50 import com.android.server.hdmi.HdmiControlService.DevicePollingCallback; 51 52 import libcore.util.EmptyArray; 53 54 import java.text.SimpleDateFormat; 55 import java.util.ArrayList; 56 import java.util.Arrays; 57 import java.util.Date; 58 import java.util.List; 59 import java.util.NoSuchElementException; 60 import java.util.concurrent.ArrayBlockingQueue; 61 import java.util.function.Predicate; 62 63 /** 64 * Manages HDMI-CEC command and behaviors. It converts user's command into CEC command 65 * and pass it to CEC HAL so that it sends message to other device. For incoming 66 * message it translates the message and delegates it to proper module. 67 * 68 * <p>It should be careful to access member variables on IO thread because 69 * it can be accessed from system thread as well. 70 * 71 * <p>It can be created only by {@link HdmiCecController#create} 72 * 73 * <p>Declared as package-private, accessed by {@link HdmiControlService} only. 74 * 75 * <p>Also manages HDMI HAL methods that are shared between CEC and eARC. To make eARC 76 * fully independent of the presence of a CEC HAL, we should split this class into HdmiCecController 77 * and HdmiController TODO(b/255751565). 78 */ 79 final class HdmiCecController { 80 private static final String TAG = "HdmiCecController"; 81 82 /** 83 * Interface to report allocated logical address. 84 */ 85 interface AllocateAddressCallback { 86 /** 87 * Called when a new logical address is allocated. 88 * 89 * @param deviceType requested device type to allocate logical address 90 * @param logicalAddress allocated logical address. If it is 91 * {@link Constants#ADDR_UNREGISTERED}, it means that 92 * it failed to allocate logical address for the given device type 93 */ onAllocated(int deviceType, int logicalAddress)94 void onAllocated(int deviceType, int logicalAddress); 95 } 96 97 private static final byte[] EMPTY_BODY = EmptyArray.BYTE; 98 99 private static final int NUM_LOGICAL_ADDRESS = 16; 100 101 private static final int MAX_DEDICATED_ADDRESS = 11; 102 103 private static final int INITIAL_HDMI_MESSAGE_HISTORY_SIZE = 250; 104 105 private static final int INVALID_PHYSICAL_ADDRESS = 0xFFFF; 106 107 /* 108 * The three flags below determine the action when a message is received. If CEC_DISABLED_IGNORE 109 * bit is set in ACTION_ON_RECEIVE_MSG, then the message is forwarded irrespective of whether 110 * CEC is enabled or disabled. The other flags/bits are also ignored. 111 */ 112 private static final int CEC_DISABLED_IGNORE = 1 << 0; 113 114 /* If CEC_DISABLED_LOG_WARNING bit is set, a warning message is printed if CEC is disabled. */ 115 private static final int CEC_DISABLED_LOG_WARNING = 1 << 1; 116 117 /* If CEC_DISABLED_DROP_MSG bit is set, the message is dropped if CEC is disabled. */ 118 private static final int CEC_DISABLED_DROP_MSG = 1 << 2; 119 120 private static final int ACTION_ON_RECEIVE_MSG = CEC_DISABLED_LOG_WARNING; 121 122 /** Cookie for matching the right end point. */ 123 protected static final int HDMI_CEC_HAL_DEATH_COOKIE = 353; 124 125 // Predicate for whether the given logical address is remote device's one or not. 126 private final Predicate<Integer> mRemoteDeviceAddressPredicate = new Predicate<Integer>() { 127 @Override 128 public boolean test(Integer address) { 129 return !mService.getHdmiCecNetwork().isAllocatedLocalDeviceAddress(address); 130 } 131 }; 132 133 // Predicate whether the given logical address is system audio's one or not 134 private final Predicate<Integer> mSystemAudioAddressPredicate = new Predicate<Integer>() { 135 @Override 136 public boolean test(Integer address) { 137 return HdmiUtils.isEligibleAddressForDevice(Constants.ADDR_AUDIO_SYSTEM, address); 138 } 139 }; 140 141 // Handler instance to process synchronous I/O (mainly send) message. 142 private Handler mIoHandler; 143 144 // Handler instance to process various messages coming from other CEC 145 // device or issued by internal state change. 146 private Handler mControlHandler; 147 148 private final HdmiControlService mService; 149 150 // Stores recent CEC messages and HDMI Hotplug event history for debugging purpose. 151 private ArrayBlockingQueue<Dumpable> mMessageHistory = 152 new ArrayBlockingQueue<>(INITIAL_HDMI_MESSAGE_HISTORY_SIZE); 153 154 private final Object mMessageHistoryLock = new Object(); 155 156 private final NativeWrapper mNativeWrapperImpl; 157 158 private final HdmiCecAtomWriter mHdmiCecAtomWriter; 159 160 // This variable is used for testing, in order to delay the logical address allocation. 161 private long mLogicalAddressAllocationDelay = 0; 162 163 // Private constructor. Use HdmiCecController.create(). HdmiCecController( HdmiControlService service, NativeWrapper nativeWrapper, HdmiCecAtomWriter atomWriter)164 private HdmiCecController( 165 HdmiControlService service, NativeWrapper nativeWrapper, HdmiCecAtomWriter atomWriter) { 166 mService = service; 167 mNativeWrapperImpl = nativeWrapper; 168 mHdmiCecAtomWriter = atomWriter; 169 } 170 171 /** 172 * A factory method to get {@link HdmiCecController}. If it fails to initialize 173 * inner device or has no device it will return {@code null}. 174 * 175 * <p>Declared as package-private, accessed by {@link HdmiControlService} only. 176 * @param service {@link HdmiControlService} instance used to create internal handler 177 * and to pass callback for incoming message or event. 178 * @param atomWriter {@link HdmiCecAtomWriter} instance for writing atoms for metrics. 179 * @return {@link HdmiCecController} if device is initialized successfully. Otherwise, 180 * returns {@code null}. 181 */ create(HdmiControlService service, HdmiCecAtomWriter atomWriter)182 static HdmiCecController create(HdmiControlService service, HdmiCecAtomWriter atomWriter) { 183 HdmiCecController controller = 184 createWithNativeWrapper(service, new NativeWrapperImplAidl(), atomWriter); 185 if (controller != null) { 186 return controller; 187 } 188 HdmiLogger.warning("Unable to use CEC and HDMI Connection AIDL HALs"); 189 190 controller = createWithNativeWrapper(service, new NativeWrapperImpl11(), atomWriter); 191 if (controller != null) { 192 return controller; 193 } 194 HdmiLogger.warning("Unable to use cec@1.1"); 195 return createWithNativeWrapper(service, new NativeWrapperImpl(), atomWriter); 196 } 197 198 /** 199 * A factory method with injection of native methods for testing. 200 */ createWithNativeWrapper( HdmiControlService service, NativeWrapper nativeWrapper, HdmiCecAtomWriter atomWriter)201 static HdmiCecController createWithNativeWrapper( 202 HdmiControlService service, NativeWrapper nativeWrapper, HdmiCecAtomWriter atomWriter) { 203 HdmiCecController controller = new HdmiCecController(service, nativeWrapper, atomWriter); 204 String nativePtr = nativeWrapper.nativeInit(); 205 if (nativePtr == null) { 206 HdmiLogger.warning("Couldn't get tv.cec service."); 207 return null; 208 } 209 controller.init(nativeWrapper); 210 return controller; 211 } 212 init(NativeWrapper nativeWrapper)213 private void init(NativeWrapper nativeWrapper) { 214 mIoHandler = new Handler(mService.getIoLooper()); 215 mControlHandler = new Handler(mService.getServiceLooper()); 216 nativeWrapper.setCallback(new HdmiCecCallback()); 217 } 218 219 /** 220 * Allocate a new logical address of the given device type. Allocated 221 * address will be reported through {@link AllocateAddressCallback}. 222 * 223 * <p> Declared as package-private, accessed by {@link HdmiControlService} only. 224 * 225 * @param deviceType type of device to used to determine logical address 226 * @param preferredAddress a logical address preferred to be allocated. 227 * If sets {@link Constants#ADDR_UNREGISTERED}, scans 228 * the smallest logical address matched with the given device type. 229 * Otherwise, scan address will start from {@code preferredAddress} 230 * @param callback callback interface to report allocated logical address to caller 231 */ 232 @ServiceThreadOnly allocateLogicalAddress(final int deviceType, final int preferredAddress, final AllocateAddressCallback callback)233 void allocateLogicalAddress(final int deviceType, final int preferredAddress, 234 final AllocateAddressCallback callback) { 235 assertRunOnServiceThread(); 236 237 mIoHandler.postDelayed(new Runnable() { 238 @Override 239 public void run() { 240 handleAllocateLogicalAddress(deviceType, preferredAddress, callback); 241 } 242 }, mLogicalAddressAllocationDelay); 243 } 244 245 /** 246 * Address allocation will check the following addresses (in order): 247 * <ul> 248 * <li>Given preferred logical address (if the address is valid for the given device 249 * type)</li> 250 * <li>All dedicated logical addresses for the given device type</li> 251 * <li>Backup addresses, if valid for the given device type</li> 252 * </ul> 253 */ 254 @IoThreadOnly handleAllocateLogicalAddress(final int deviceType, int preferredAddress, final AllocateAddressCallback callback)255 private void handleAllocateLogicalAddress(final int deviceType, int preferredAddress, 256 final AllocateAddressCallback callback) { 257 assertRunOnIoThread(); 258 List<Integer> logicalAddressesToPoll = new ArrayList<>(); 259 if (HdmiUtils.isEligibleAddressForDevice(deviceType, preferredAddress)) { 260 logicalAddressesToPoll.add(preferredAddress); 261 } 262 for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) { 263 if (!logicalAddressesToPoll.contains(i) && HdmiUtils.isEligibleAddressForDevice( 264 deviceType, i) && HdmiUtils.isEligibleAddressForCecVersion( 265 mService.getCecVersion(), i)) { 266 logicalAddressesToPoll.add(i); 267 } 268 } 269 270 int logicalAddress = Constants.ADDR_UNREGISTERED; 271 for (Integer logicalAddressToPoll : logicalAddressesToPoll) { 272 boolean acked = false; 273 for (int j = 0; j < HdmiConfig.ADDRESS_ALLOCATION_RETRY; ++j) { 274 if (sendPollMessage(logicalAddressToPoll, logicalAddressToPoll, 1)) { 275 acked = true; 276 break; 277 } 278 } 279 // If sending <Polling Message> failed, it becomes new logical address for the 280 // device because no device uses it as logical address of the device. 281 if (!acked) { 282 logicalAddress = logicalAddressToPoll; 283 break; 284 } 285 } 286 287 final int assignedAddress = logicalAddress; 288 HdmiLogger.debug("New logical address for device [%d]: [preferred:%d, assigned:%d]", 289 deviceType, preferredAddress, assignedAddress); 290 if (callback != null) { 291 runOnServiceThread(new Runnable() { 292 @Override 293 public void run() { 294 callback.onAllocated(deviceType, assignedAddress); 295 } 296 }); 297 } 298 } 299 buildBody(int opcode, byte[] params)300 private static byte[] buildBody(int opcode, byte[] params) { 301 byte[] body = new byte[params.length + 1]; 302 body[0] = (byte) opcode; 303 System.arraycopy(params, 0, body, 1, params.length); 304 return body; 305 } 306 307 getPortInfos()308 HdmiPortInfo[] getPortInfos() { 309 return mNativeWrapperImpl.nativeGetPortInfos(); 310 } 311 312 /** 313 * Add a new logical address to the device. Device's HW should be notified 314 * when a new logical address is assigned to a device, so that it can accept 315 * a command having available destinations. 316 * 317 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 318 * 319 * @param newLogicalAddress a logical address to be added 320 * @return 0 on success. Otherwise, returns negative value 321 */ 322 @ServiceThreadOnly addLogicalAddress(int newLogicalAddress)323 int addLogicalAddress(int newLogicalAddress) { 324 assertRunOnServiceThread(); 325 if (HdmiUtils.isValidAddress(newLogicalAddress)) { 326 return mNativeWrapperImpl.nativeAddLogicalAddress(newLogicalAddress); 327 } else { 328 return Result.FAILURE_INVALID_ARGS; 329 } 330 } 331 332 /** 333 * Clear all logical addresses registered in the device. 334 * 335 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 336 */ 337 @ServiceThreadOnly clearLogicalAddress()338 void clearLogicalAddress() { 339 assertRunOnServiceThread(); 340 mNativeWrapperImpl.nativeClearLogicalAddress(); 341 } 342 343 /** 344 * Return the physical address of the device. 345 * 346 * <p>Declared as package-private. accessed by {@link HdmiControlService} and 347 * {@link HdmiCecNetwork} only. 348 * 349 * @return CEC physical address of the device. The range of success address 350 * is between 0x0000 and 0xFFFF. If failed it returns -1 351 */ 352 @ServiceThreadOnly getPhysicalAddress()353 int getPhysicalAddress() { 354 assertRunOnServiceThread(); 355 return mNativeWrapperImpl.nativeGetPhysicalAddress(); 356 } 357 358 /** 359 * Return highest CEC version supported by this device. 360 * 361 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 362 */ 363 @ServiceThreadOnly getVersion()364 int getVersion() { 365 assertRunOnServiceThread(); 366 return mNativeWrapperImpl.nativeGetVersion(); 367 } 368 369 /** 370 * Return vendor id of the device. 371 * 372 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 373 */ 374 @ServiceThreadOnly getVendorId()375 int getVendorId() { 376 assertRunOnServiceThread(); 377 return mNativeWrapperImpl.nativeGetVendorId(); 378 } 379 380 /** 381 * Configures the TV panel device wakeup behaviour in standby mode when it receives an OTP 382 * (One Touch Play) from a source device. 383 * 384 * @param enabled If true, the TV device will wake up when OTP is received and if false, the TV 385 * device will not wake up for an OTP. 386 */ 387 @ServiceThreadOnly enableWakeupByOtp(boolean enabled)388 void enableWakeupByOtp(boolean enabled) { 389 assertRunOnServiceThread(); 390 HdmiLogger.debug("enableWakeupByOtp: %b", enabled); 391 mNativeWrapperImpl.enableWakeupByOtp(enabled); 392 } 393 394 /** 395 * Switch to enable or disable CEC on the device. 396 * 397 * @param enabled If true, the device will have all CEC functionalities and if false, the device 398 * will not perform any CEC functions. 399 */ 400 @ServiceThreadOnly enableCec(boolean enabled)401 void enableCec(boolean enabled) { 402 assertRunOnServiceThread(); 403 HdmiLogger.debug("enableCec: %b", enabled); 404 mNativeWrapperImpl.enableCec(enabled); 405 } 406 407 /** 408 * Configures the module that processes CEC messages - the Android framework or the HAL. 409 * 410 * @param enabled If true, the Android framework will actively process CEC messages. 411 * If false, only the HAL will process the CEC messages. 412 */ 413 @ServiceThreadOnly enableSystemCecControl(boolean enabled)414 void enableSystemCecControl(boolean enabled) { 415 assertRunOnServiceThread(); 416 HdmiLogger.debug("enableSystemCecControl: %b", enabled); 417 mNativeWrapperImpl.enableSystemCecControl(enabled); 418 } 419 420 /** 421 * Configures the type of HDP signal that the driver and HAL use for actions other than eARC, 422 * such as signaling EDID updates. 423 */ 424 @ServiceThreadOnly setHpdSignalType(@onstants.HpdSignalType int signal, int portId)425 void setHpdSignalType(@Constants.HpdSignalType int signal, int portId) { 426 assertRunOnServiceThread(); 427 HdmiLogger.debug("setHpdSignalType: portId %b, signal %b", portId, signal); 428 mNativeWrapperImpl.nativeSetHpdSignalType(signal, portId); 429 } 430 431 /** 432 * Gets the type of the HDP signal that the driver and HAL use for actions other than eARC, 433 * such as signaling EDID updates. 434 */ 435 @ServiceThreadOnly 436 @Constants.HpdSignalType getHpdSignalType(int portId)437 int getHpdSignalType(int portId) { 438 assertRunOnServiceThread(); 439 HdmiLogger.debug("getHpdSignalType: portId %b ", portId); 440 return mNativeWrapperImpl.nativeGetHpdSignalType(portId); 441 } 442 443 /** 444 * Informs CEC HAL about the current system language. 445 * 446 * @param language Three-letter code defined in ISO/FDIS 639-2. Must be lowercase letters. 447 */ 448 @ServiceThreadOnly setLanguage(String language)449 void setLanguage(String language) { 450 assertRunOnServiceThread(); 451 if (!isLanguage(language)) { 452 return; 453 } 454 mNativeWrapperImpl.nativeSetLanguage(language); 455 } 456 457 /** 458 * This method is used for testing, in order to delay the logical address allocation. 459 */ 460 @VisibleForTesting setLogicalAddressAllocationDelay(long delay)461 void setLogicalAddressAllocationDelay(long delay) { 462 mLogicalAddressAllocationDelay = delay; 463 } 464 465 /** 466 * Returns true if the language code is well-formed. 467 */ isLanguage(String language)468 @VisibleForTesting static boolean isLanguage(String language) { 469 // Handle null and empty string because because ULocale.Builder#setLanguage accepts them. 470 if (language == null || language.isEmpty()) { 471 return false; 472 } 473 474 ULocale.Builder builder = new ULocale.Builder(); 475 try { 476 builder.setLanguage(language); 477 return true; 478 } catch (IllformedLocaleException e) { 479 return false; 480 } 481 } 482 483 /** 484 * Configure ARC circuit in the hardware logic to start or stop the feature. 485 * 486 * @param port ID of HDMI port to which AVR is connected 487 * @param enabled whether to enable/disable ARC 488 */ 489 @ServiceThreadOnly enableAudioReturnChannel(int port, boolean enabled)490 void enableAudioReturnChannel(int port, boolean enabled) { 491 assertRunOnServiceThread(); 492 mNativeWrapperImpl.nativeEnableAudioReturnChannel(port, enabled); 493 } 494 495 /** 496 * Return the connection status of the specified port 497 * 498 * @param port port number to check connection status 499 * @return true if connected; otherwise, return false 500 */ 501 @ServiceThreadOnly isConnected(int port)502 boolean isConnected(int port) { 503 assertRunOnServiceThread(); 504 return mNativeWrapperImpl.nativeIsConnected(port); 505 } 506 507 /** 508 * Poll all remote devices. It sends <Polling Message> to all remote 509 * devices. 510 * 511 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 512 * 513 * @param callback an interface used to get a list of all remote devices' address 514 * @param sourceAddress a logical address of source device where sends polling message 515 * @param pickStrategy strategy how to pick polling candidates 516 * @param retryCount the number of retry used to send polling message to remote devices 517 */ 518 @ServiceThreadOnly pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy, int retryCount)519 void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy, 520 int retryCount) { 521 assertRunOnServiceThread(); 522 523 // Extract polling candidates. No need to poll against local devices. 524 List<Integer> pollingCandidates = pickPollCandidates(pickStrategy); 525 ArrayList<Integer> allocated = new ArrayList<>(); 526 runDevicePolling(sourceAddress, pollingCandidates, retryCount, callback, allocated); 527 } 528 pickPollCandidates(int pickStrategy)529 private List<Integer> pickPollCandidates(int pickStrategy) { 530 int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK; 531 Predicate<Integer> pickPredicate = null; 532 switch (strategy) { 533 case Constants.POLL_STRATEGY_SYSTEM_AUDIO: 534 pickPredicate = mSystemAudioAddressPredicate; 535 break; 536 case Constants.POLL_STRATEGY_REMOTES_DEVICES: 537 default: // The default is POLL_STRATEGY_REMOTES_DEVICES. 538 pickPredicate = mRemoteDeviceAddressPredicate; 539 break; 540 } 541 542 int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK; 543 ArrayList<Integer> pollingCandidates = new ArrayList<>(); 544 switch (iterationStrategy) { 545 case Constants.POLL_ITERATION_IN_ORDER: 546 for (int i = Constants.ADDR_TV; i <= Constants.ADDR_SPECIFIC_USE; ++i) { 547 if (pickPredicate.test(i)) { 548 pollingCandidates.add(i); 549 } 550 } 551 break; 552 case Constants.POLL_ITERATION_REVERSE_ORDER: 553 default: // The default is reverse order. 554 for (int i = Constants.ADDR_SPECIFIC_USE; i >= Constants.ADDR_TV; --i) { 555 if (pickPredicate.test(i)) { 556 pollingCandidates.add(i); 557 } 558 } 559 break; 560 } 561 return pollingCandidates; 562 } 563 564 @ServiceThreadOnly runDevicePolling(final int sourceAddress, final List<Integer> candidates, final int retryCount, final DevicePollingCallback callback, final List<Integer> allocated)565 private void runDevicePolling(final int sourceAddress, 566 final List<Integer> candidates, final int retryCount, 567 final DevicePollingCallback callback, final List<Integer> allocated) { 568 assertRunOnServiceThread(); 569 if (candidates.isEmpty()) { 570 if (callback != null) { 571 HdmiLogger.debug("[P]:AllocatedAddress=%s", allocated.toString()); 572 callback.onPollingFinished(allocated); 573 } 574 return; 575 } 576 577 final Integer candidate = candidates.remove(0); 578 // Proceed polling action for the next address once polling action for the 579 // previous address is done. 580 runOnIoThread(new Runnable() { 581 @Override 582 public void run() { 583 if (sendPollMessage(sourceAddress, candidate, retryCount)) { 584 allocated.add(candidate); 585 } 586 runOnServiceThread(new Runnable() { 587 @Override 588 public void run() { 589 runDevicePolling(sourceAddress, candidates, retryCount, callback, 590 allocated); 591 } 592 }); 593 } 594 }); 595 } 596 597 @IoThreadOnly sendPollMessage(int sourceAddress, int destinationAddress, int retryCount)598 private boolean sendPollMessage(int sourceAddress, int destinationAddress, int retryCount) { 599 assertRunOnIoThread(); 600 for (int i = 0; i < retryCount; ++i) { 601 // <Polling Message> is a message which has empty body. 602 int ret = 603 mNativeWrapperImpl.nativeSendCecCommand( 604 sourceAddress, destinationAddress, EMPTY_BODY); 605 if (ret == SendMessageResult.SUCCESS) { 606 return true; 607 } else if (ret != SendMessageResult.NACK) { 608 // Unusual failure 609 HdmiLogger.warning("Failed to send a polling message(%d->%d) with return code %d", 610 sourceAddress, destinationAddress, ret); 611 } 612 } 613 return false; 614 } 615 assertRunOnIoThread()616 private void assertRunOnIoThread() { 617 if (Looper.myLooper() != mIoHandler.getLooper()) { 618 throw new IllegalStateException("Should run on io thread."); 619 } 620 } 621 assertRunOnServiceThread()622 private void assertRunOnServiceThread() { 623 if (Looper.myLooper() != mControlHandler.getLooper()) { 624 throw new IllegalStateException("Should run on service thread."); 625 } 626 } 627 628 // Run a Runnable on IO thread. 629 // It should be careful to access member variables on IO thread because 630 // it can be accessed from system thread as well. 631 @VisibleForTesting runOnIoThread(Runnable runnable)632 void runOnIoThread(Runnable runnable) { 633 mIoHandler.post(new WorkSourceUidPreservingRunnable(runnable)); 634 } 635 636 @VisibleForTesting runOnServiceThread(Runnable runnable)637 void runOnServiceThread(Runnable runnable) { 638 mControlHandler.post(new WorkSourceUidPreservingRunnable(runnable)); 639 } 640 641 @ServiceThreadOnly flush(final Runnable runnable)642 void flush(final Runnable runnable) { 643 assertRunOnServiceThread(); 644 runOnIoThread(new Runnable() { 645 @Override 646 public void run() { 647 // This ensures the runnable for cleanup is performed after all the pending 648 // commands are processed by IO thread. 649 runOnServiceThread(runnable); 650 } 651 }); 652 } 653 isAcceptableAddress(int address)654 private boolean isAcceptableAddress(int address) { 655 // Can access command targeting devices available in local device or broadcast command. 656 if (address == Constants.ADDR_BROADCAST) { 657 return true; 658 } 659 return mService.getHdmiCecNetwork().isAllocatedLocalDeviceAddress(address); 660 } 661 662 @ServiceThreadOnly 663 @VisibleForTesting onReceiveCommand(HdmiCecMessage message)664 void onReceiveCommand(HdmiCecMessage message) { 665 assertRunOnServiceThread(); 666 if (((ACTION_ON_RECEIVE_MSG & CEC_DISABLED_IGNORE) == 0) 667 && !mService.isCecControlEnabled() 668 && !HdmiCecMessage.isCecTransportMessage(message.getOpcode())) { 669 if ((ACTION_ON_RECEIVE_MSG & CEC_DISABLED_LOG_WARNING) != 0) { 670 HdmiLogger.warning("Message " + message + " received when cec disabled"); 671 } 672 if ((ACTION_ON_RECEIVE_MSG & CEC_DISABLED_DROP_MSG) != 0) { 673 return; 674 } 675 } 676 if (mService.isAddressAllocated() && !isAcceptableAddress(message.getDestination())) { 677 return; 678 } 679 @Constants.HandleMessageResult int messageState = mService.handleCecCommand(message); 680 if (messageState == Constants.NOT_HANDLED) { 681 // Message was not handled 682 maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE); 683 } else if (messageState != Constants.HANDLED) { 684 // Message handler wants to send a feature abort 685 maySendFeatureAbortCommand(message, messageState); 686 } 687 } 688 689 @ServiceThreadOnly maySendFeatureAbortCommand(HdmiCecMessage message, @Constants.AbortReason int reason)690 void maySendFeatureAbortCommand(HdmiCecMessage message, @Constants.AbortReason int reason) { 691 assertRunOnServiceThread(); 692 // Swap the source and the destination. 693 int src = message.getDestination(); 694 int dest = message.getSource(); 695 if (src == Constants.ADDR_BROADCAST || dest == Constants.ADDR_UNREGISTERED) { 696 // Don't reply <Feature Abort> from the unregistered devices or for the broadcasted 697 // messages. See CEC 12.2 Protocol General Rules for detail. 698 return; 699 } 700 int originalOpcode = message.getOpcode(); 701 if (originalOpcode == Constants.MESSAGE_FEATURE_ABORT) { 702 return; 703 } 704 sendCommand( 705 HdmiCecMessageBuilder.buildFeatureAbortCommand(src, dest, originalOpcode, reason)); 706 } 707 708 @ServiceThreadOnly sendCommand(HdmiCecMessage cecMessage)709 void sendCommand(HdmiCecMessage cecMessage) { 710 assertRunOnServiceThread(); 711 sendCommand(cecMessage, null); 712 } 713 714 /** 715 * Returns the calling UID of the original Binder call that triggered this code. 716 * If this code was not triggered by a Binder call, returns the UID of this process. 717 */ getCallingUid()718 private int getCallingUid() { 719 int workSourceUid = Binder.getCallingWorkSourceUid(); 720 if (workSourceUid == -1) { 721 return Binder.getCallingUid(); 722 } 723 return workSourceUid; 724 } 725 726 @ServiceThreadOnly sendCommand(final HdmiCecMessage cecMessage, final HdmiControlService.SendMessageCallback callback)727 void sendCommand(final HdmiCecMessage cecMessage, 728 final HdmiControlService.SendMessageCallback callback) { 729 assertRunOnServiceThread(); 730 List<String> sendResults = new ArrayList<>(); 731 runOnIoThread(new Runnable() { 732 @Override 733 public void run() { 734 HdmiLogger.debug("[S]:" + cecMessage); 735 byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams()); 736 int retransmissionCount = 0; 737 int errorCode = SendMessageResult.SUCCESS; 738 do { 739 errorCode = mNativeWrapperImpl.nativeSendCecCommand( 740 cecMessage.getSource(), cecMessage.getDestination(), body); 741 switch (errorCode) { 742 case SendMessageResult.SUCCESS: sendResults.add("ACK"); break; 743 case SendMessageResult.FAIL: sendResults.add("FAIL"); break; 744 case SendMessageResult.NACK: sendResults.add("NACK"); break; 745 case SendMessageResult.BUSY: sendResults.add("BUSY"); break; 746 } 747 if (errorCode == SendMessageResult.SUCCESS) { 748 break; 749 } 750 } while (retransmissionCount++ < HdmiConfig.RETRANSMISSION_COUNT); 751 752 final int finalError = errorCode; 753 if (finalError != SendMessageResult.SUCCESS) { 754 Slog.w(TAG, "Failed to send " + cecMessage + " with errorCode=" + finalError); 755 } 756 runOnServiceThread(new Runnable() { 757 @Override 758 public void run() { 759 mHdmiCecAtomWriter.messageReported( 760 cecMessage, 761 FrameworkStatsLog.HDMI_CEC_MESSAGE_REPORTED__DIRECTION__OUTGOING, 762 getCallingUid(), 763 finalError 764 ); 765 if (callback != null) { 766 callback.onSendCompleted(finalError); 767 } 768 } 769 }); 770 } 771 }); 772 773 addCecMessageToHistory(false /* isReceived */, cecMessage, sendResults); 774 } 775 776 /** 777 * Called when incoming CEC message arrived. 778 */ 779 @ServiceThreadOnly handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body)780 private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) { 781 assertRunOnServiceThread(); 782 783 if (body.length == 0) { 784 Slog.e(TAG, "Message with empty body received."); 785 return; 786 } 787 788 HdmiCecMessage command = HdmiCecMessage.build(srcAddress, dstAddress, body[0], 789 Arrays.copyOfRange(body, 1, body.length)); 790 791 if (command.getValidationResult() != HdmiCecMessageValidator.OK) { 792 Slog.e(TAG, "Invalid message received: " + command); 793 } 794 795 HdmiLogger.debug("[R]:" + command); 796 addCecMessageToHistory(true /* isReceived */, command, null); 797 798 mHdmiCecAtomWriter.messageReported(command, 799 incomingMessageDirection(srcAddress, dstAddress), getCallingUid()); 800 801 onReceiveCommand(command); 802 } 803 804 /** 805 * Computes the direction of an incoming message, as implied by the source and 806 * destination addresses. This will usually return INCOMING; if not, it can indicate a bug. 807 */ incomingMessageDirection(int srcAddress, int dstAddress)808 private int incomingMessageDirection(int srcAddress, int dstAddress) { 809 boolean sourceIsLocal = false; 810 boolean destinationIsLocal = dstAddress == Constants.ADDR_BROADCAST; 811 for (HdmiCecLocalDevice localDevice : mService.getHdmiCecNetwork().getLocalDeviceList()) { 812 int logicalAddress = localDevice.getDeviceInfo().getLogicalAddress(); 813 if (logicalAddress == srcAddress) { 814 sourceIsLocal = true; 815 } 816 if (logicalAddress == dstAddress) { 817 destinationIsLocal = true; 818 } 819 } 820 821 if (!sourceIsLocal && destinationIsLocal) { 822 return HdmiStatsEnums.INCOMING; 823 } else if (sourceIsLocal && destinationIsLocal) { 824 return HdmiStatsEnums.TO_SELF; 825 } 826 return HdmiStatsEnums.MESSAGE_DIRECTION_OTHER; 827 } 828 829 /** 830 * Called when a hotplug event issues. 831 */ 832 @ServiceThreadOnly handleHotplug(int port, boolean connected)833 private void handleHotplug(int port, boolean connected) { 834 assertRunOnServiceThread(); 835 HdmiLogger.debug("Hotplug event:[port:%d, connected:%b]", port, connected); 836 addHotplugEventToHistory(port, connected); 837 mService.onHotplug(port, connected); 838 } 839 840 @ServiceThreadOnly addHotplugEventToHistory(int port, boolean connected)841 private void addHotplugEventToHistory(int port, boolean connected) { 842 assertRunOnServiceThread(); 843 addEventToHistory(new HotplugHistoryRecord(port, connected)); 844 } 845 846 @ServiceThreadOnly addCecMessageToHistory(boolean isReceived, HdmiCecMessage message, List<String> sendResults)847 private void addCecMessageToHistory(boolean isReceived, HdmiCecMessage message, 848 List<String> sendResults) { 849 assertRunOnServiceThread(); 850 addEventToHistory(new MessageHistoryRecord(isReceived, message, sendResults)); 851 } 852 addEventToHistory(Dumpable event)853 private void addEventToHistory(Dumpable event) { 854 synchronized (mMessageHistoryLock) { 855 if (!mMessageHistory.offer(event)) { 856 mMessageHistory.poll(); 857 mMessageHistory.offer(event); 858 } 859 } 860 } 861 getMessageHistorySize()862 int getMessageHistorySize() { 863 synchronized (mMessageHistoryLock) { 864 return mMessageHistory.size() + mMessageHistory.remainingCapacity(); 865 } 866 } 867 setMessageHistorySize(int newSize)868 boolean setMessageHistorySize(int newSize) { 869 if (newSize < INITIAL_HDMI_MESSAGE_HISTORY_SIZE) { 870 return false; 871 } 872 ArrayBlockingQueue<Dumpable> newMessageHistory = new ArrayBlockingQueue<>(newSize); 873 874 synchronized (mMessageHistoryLock) { 875 if (newSize < mMessageHistory.size()) { 876 for (int i = 0; i < mMessageHistory.size() - newSize; i++) { 877 mMessageHistory.poll(); 878 } 879 } 880 881 newMessageHistory.addAll(mMessageHistory); 882 mMessageHistory = newMessageHistory; 883 } 884 return true; 885 } 886 dump(final IndentingPrintWriter pw)887 void dump(final IndentingPrintWriter pw) { 888 pw.println("CEC message history:"); 889 pw.increaseIndent(); 890 final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 891 for (Dumpable record : mMessageHistory) { 892 record.dump(pw, sdf); 893 } 894 pw.decreaseIndent(); 895 } 896 897 protected interface NativeWrapper { nativeInit()898 String nativeInit(); setCallback(HdmiCecCallback callback)899 void setCallback(HdmiCecCallback callback); nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body)900 int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body); nativeAddLogicalAddress(int logicalAddress)901 int nativeAddLogicalAddress(int logicalAddress); nativeClearLogicalAddress()902 void nativeClearLogicalAddress(); nativeGetPhysicalAddress()903 int nativeGetPhysicalAddress(); nativeGetVersion()904 int nativeGetVersion(); nativeGetVendorId()905 int nativeGetVendorId(); nativeGetPortInfos()906 HdmiPortInfo[] nativeGetPortInfos(); 907 enableWakeupByOtp(boolean enabled)908 void enableWakeupByOtp(boolean enabled); 909 enableCec(boolean enabled)910 void enableCec(boolean enabled); 911 enableSystemCecControl(boolean enabled)912 void enableSystemCecControl(boolean enabled); 913 nativeSetLanguage(String language)914 void nativeSetLanguage(String language); nativeEnableAudioReturnChannel(int port, boolean flag)915 void nativeEnableAudioReturnChannel(int port, boolean flag); nativeIsConnected(int port)916 boolean nativeIsConnected(int port); nativeSetHpdSignalType(int signal, int portId)917 void nativeSetHpdSignalType(int signal, int portId); nativeGetHpdSignalType(int portId)918 int nativeGetHpdSignalType(int portId); 919 } 920 921 private static final class NativeWrapperImplAidl 922 implements NativeWrapper, IBinder.DeathRecipient { 923 private IHdmiCec mHdmiCec; 924 private IHdmiConnection mHdmiConnection; 925 @Nullable private HdmiCecCallback mCallback; 926 927 private final Object mLock = new Object(); 928 929 @Override nativeInit()930 public String nativeInit() { 931 return connectToHal() ? mHdmiCec.toString() + " " + mHdmiConnection.toString() : null; 932 } 933 connectToHal()934 boolean connectToHal() { 935 mHdmiCec = 936 IHdmiCec.Stub.asInterface( 937 ServiceManager.getService(IHdmiCec.DESCRIPTOR + "/default")); 938 if (mHdmiCec == null) { 939 HdmiLogger.error("Could not initialize HDMI CEC AIDL HAL"); 940 return false; 941 } 942 try { 943 mHdmiCec.asBinder().linkToDeath(this, 0); 944 } catch (RemoteException e) { 945 HdmiLogger.error("Couldn't link to death : ", e); 946 } 947 948 mHdmiConnection = 949 IHdmiConnection.Stub.asInterface( 950 ServiceManager.getService(IHdmiConnection.DESCRIPTOR + "/default")); 951 if (mHdmiConnection == null) { 952 HdmiLogger.error("Could not initialize HDMI Connection AIDL HAL"); 953 return false; 954 } 955 try { 956 mHdmiConnection.asBinder().linkToDeath(this, 0); 957 } catch (RemoteException e) { 958 HdmiLogger.error("Couldn't link to death : ", e); 959 } 960 return true; 961 } 962 963 @Override binderDied()964 public void binderDied() { 965 // One of the services died, try to reconnect to both. 966 mHdmiCec.asBinder().unlinkToDeath(this, 0); 967 mHdmiConnection.asBinder().unlinkToDeath(this, 0); 968 HdmiLogger.error("HDMI Connection or CEC service died, reconnecting"); 969 connectToHal(); 970 // Reconnect the callback 971 if (mCallback != null) { 972 setCallback(mCallback); 973 } 974 } 975 976 @Override setCallback(HdmiCecCallback callback)977 public void setCallback(HdmiCecCallback callback) { 978 mCallback = callback; 979 try { 980 // Create an AIDL callback that can callback onCecMessage 981 mHdmiCec.setCallback(new HdmiCecCallbackAidl(callback)); 982 } catch (RemoteException e) { 983 HdmiLogger.error("Couldn't initialise tv.cec callback : ", e); 984 } 985 try { 986 // Create an AIDL callback that can callback onHotplugEvent 987 mHdmiConnection.setCallback(new HdmiConnectionCallbackAidl(callback)); 988 } catch (RemoteException e) { 989 HdmiLogger.error("Couldn't initialise tv.hdmi callback : ", e); 990 } 991 } 992 993 @Override nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body)994 public int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body) { 995 CecMessage message = new CecMessage(); 996 message.initiator = (byte) (srcAddress & 0xF); 997 message.destination = (byte) (dstAddress & 0xF); 998 message.body = body; 999 try { 1000 return mHdmiCec.sendMessage(message); 1001 } catch (RemoteException e) { 1002 HdmiLogger.error("Failed to send CEC message : ", e); 1003 return SendMessageResult.FAIL; 1004 } 1005 } 1006 1007 @Override nativeAddLogicalAddress(int logicalAddress)1008 public int nativeAddLogicalAddress(int logicalAddress) { 1009 try { 1010 return mHdmiCec.addLogicalAddress((byte) logicalAddress); 1011 } catch (RemoteException e) { 1012 HdmiLogger.error("Failed to add a logical address : ", e); 1013 return Result.FAILURE_INVALID_ARGS; 1014 } 1015 } 1016 1017 @Override nativeClearLogicalAddress()1018 public void nativeClearLogicalAddress() { 1019 try { 1020 mHdmiCec.clearLogicalAddress(); 1021 } catch (RemoteException e) { 1022 HdmiLogger.error("Failed to clear logical address : ", e); 1023 } 1024 } 1025 1026 @Override nativeGetPhysicalAddress()1027 public int nativeGetPhysicalAddress() { 1028 try { 1029 return mHdmiCec.getPhysicalAddress(); 1030 } catch (RemoteException e) { 1031 HdmiLogger.error("Failed to get physical address : ", e); 1032 return INVALID_PHYSICAL_ADDRESS; 1033 } 1034 } 1035 1036 @Override nativeGetVersion()1037 public int nativeGetVersion() { 1038 try { 1039 return mHdmiCec.getCecVersion(); 1040 } catch (RemoteException e) { 1041 HdmiLogger.error("Failed to get cec version : ", e); 1042 return Result.FAILURE_UNKNOWN; 1043 } 1044 } 1045 1046 @Override nativeGetVendorId()1047 public int nativeGetVendorId() { 1048 try { 1049 return mHdmiCec.getVendorId(); 1050 } catch (RemoteException e) { 1051 HdmiLogger.error("Failed to get vendor id : ", e); 1052 return Result.FAILURE_UNKNOWN; 1053 } 1054 } 1055 1056 @Override enableWakeupByOtp(boolean enabled)1057 public void enableWakeupByOtp(boolean enabled) { 1058 try { 1059 mHdmiCec.enableWakeupByOtp(enabled); 1060 } catch (RemoteException e) { 1061 HdmiLogger.error("Failed call to enableWakeupByOtp : ", e); 1062 } 1063 } 1064 1065 @Override enableCec(boolean enabled)1066 public void enableCec(boolean enabled) { 1067 try { 1068 mHdmiCec.enableCec(enabled); 1069 } catch (RemoteException e) { 1070 HdmiLogger.error("Failed call to enableCec : ", e); 1071 } 1072 } 1073 1074 @Override enableSystemCecControl(boolean enabled)1075 public void enableSystemCecControl(boolean enabled) { 1076 try { 1077 mHdmiCec.enableSystemCecControl(enabled); 1078 } catch (RemoteException e) { 1079 HdmiLogger.error("Failed call to enableSystemCecControl : ", e); 1080 } 1081 } 1082 1083 @Override nativeSetLanguage(String language)1084 public void nativeSetLanguage(String language) { 1085 try { 1086 mHdmiCec.setLanguage(language); 1087 } catch (RemoteException e) { 1088 HdmiLogger.error("Failed to set language : ", e); 1089 } 1090 } 1091 1092 @Override nativeEnableAudioReturnChannel(int port, boolean flag)1093 public void nativeEnableAudioReturnChannel(int port, boolean flag) { 1094 try { 1095 mHdmiCec.enableAudioReturnChannel(port, flag); 1096 } catch (RemoteException e) { 1097 HdmiLogger.error("Failed to enable/disable ARC : ", e); 1098 } 1099 } 1100 1101 @Override nativeGetPortInfos()1102 public HdmiPortInfo[] nativeGetPortInfos() { 1103 try { 1104 android.hardware.tv.hdmi.connection.HdmiPortInfo[] hdmiPortInfos = 1105 mHdmiConnection.getPortInfo(); 1106 HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[hdmiPortInfos.length]; 1107 int i = 0; 1108 for (android.hardware.tv.hdmi.connection.HdmiPortInfo portInfo : hdmiPortInfos) { 1109 hdmiPortInfo[i] = new HdmiPortInfo.Builder( 1110 portInfo.portId, 1111 portInfo.type, 1112 portInfo.physicalAddress) 1113 .setCecSupported(portInfo.cecSupported) 1114 .setMhlSupported(false) 1115 .setArcSupported(portInfo.arcSupported) 1116 .setEarcSupported(portInfo.eArcSupported) 1117 .build(); 1118 i++; 1119 } 1120 return hdmiPortInfo; 1121 } catch (RemoteException e) { 1122 HdmiLogger.error("Failed to get port information : ", e); 1123 return null; 1124 } 1125 } 1126 1127 @Override nativeIsConnected(int port)1128 public boolean nativeIsConnected(int port) { 1129 try { 1130 return mHdmiConnection.isConnected(port); 1131 } catch (RemoteException e) { 1132 HdmiLogger.error("Failed to get connection info : ", e); 1133 return false; 1134 } 1135 } 1136 1137 @Override nativeSetHpdSignalType(int signal, int portId)1138 public void nativeSetHpdSignalType(int signal, int portId) { 1139 try { 1140 mHdmiConnection.setHpdSignal((byte) signal, portId); 1141 } catch (ServiceSpecificException sse) { 1142 HdmiLogger.error( 1143 "Could not set HPD signal type for portId " + portId + " to " + signal 1144 + ". Error: ", sse.errorCode); 1145 } catch (RemoteException e) { 1146 HdmiLogger.error( 1147 "Could not set HPD signal type for portId " + portId + " to " + signal 1148 + ". Exception: ", e); 1149 } 1150 } 1151 1152 @Override nativeGetHpdSignalType(int portId)1153 public int nativeGetHpdSignalType(int portId) { 1154 try { 1155 return mHdmiConnection.getHpdSignal(portId); 1156 } catch (RemoteException e) { 1157 HdmiLogger.error( 1158 "Could not get HPD signal type for portId " + portId + ". Exception: ", e); 1159 return Constants.HDMI_HPD_TYPE_PHYSICAL; 1160 } 1161 } 1162 } 1163 1164 private static final class NativeWrapperImpl11 implements NativeWrapper, 1165 IHwBinder.DeathRecipient, getPhysicalAddressCallback { 1166 private android.hardware.tv.cec.V1_1.IHdmiCec mHdmiCec; 1167 @Nullable private HdmiCecCallback mCallback; 1168 1169 private final Object mLock = new Object(); 1170 private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS; 1171 1172 @Override nativeInit()1173 public String nativeInit() { 1174 return (connectToHal() ? mHdmiCec.toString() : null); 1175 } 1176 connectToHal()1177 boolean connectToHal() { 1178 try { 1179 mHdmiCec = android.hardware.tv.cec.V1_1.IHdmiCec.getService(true); 1180 try { 1181 mHdmiCec.linkToDeath(this, HDMI_CEC_HAL_DEATH_COOKIE); 1182 } catch (RemoteException e) { 1183 HdmiLogger.error("Couldn't link to death : ", e); 1184 } 1185 } catch (RemoteException | NoSuchElementException e) { 1186 HdmiLogger.error("Couldn't connect to cec@1.1", e); 1187 return false; 1188 } 1189 return true; 1190 } 1191 1192 @Override onValues(int result, short addr)1193 public void onValues(int result, short addr) { 1194 if (result == Result.SUCCESS) { 1195 synchronized (mLock) { 1196 mPhysicalAddress = new Short(addr).intValue(); 1197 } 1198 } 1199 } 1200 1201 @Override serviceDied(long cookie)1202 public void serviceDied(long cookie) { 1203 if (cookie == HDMI_CEC_HAL_DEATH_COOKIE) { 1204 HdmiLogger.error("Service died cookie : " + cookie + "; reconnecting"); 1205 connectToHal(); 1206 // Reconnect the callback 1207 if (mCallback != null) { 1208 setCallback(mCallback); 1209 } 1210 } 1211 } 1212 1213 @Override setCallback(HdmiCecCallback callback)1214 public void setCallback(HdmiCecCallback callback) { 1215 mCallback = callback; 1216 try { 1217 mHdmiCec.setCallback_1_1(new HdmiCecCallback11(callback)); 1218 } catch (RemoteException e) { 1219 HdmiLogger.error("Couldn't initialise tv.cec callback : ", e); 1220 } 1221 } 1222 1223 @Override nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body)1224 public int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body) { 1225 android.hardware.tv.cec.V1_1.CecMessage message = 1226 new android.hardware.tv.cec.V1_1.CecMessage(); 1227 message.initiator = srcAddress; 1228 message.destination = dstAddress; 1229 message.body = new ArrayList<>(body.length); 1230 for (byte b : body) { 1231 message.body.add(b); 1232 } 1233 try { 1234 return mHdmiCec.sendMessage_1_1(message); 1235 } catch (RemoteException e) { 1236 HdmiLogger.error("Failed to send CEC message : ", e); 1237 return SendMessageResult.FAIL; 1238 } 1239 } 1240 1241 @Override nativeAddLogicalAddress(int logicalAddress)1242 public int nativeAddLogicalAddress(int logicalAddress) { 1243 try { 1244 return mHdmiCec.addLogicalAddress_1_1(logicalAddress); 1245 } catch (RemoteException e) { 1246 HdmiLogger.error("Failed to add a logical address : ", e); 1247 return Result.FAILURE_INVALID_ARGS; 1248 } 1249 } 1250 1251 @Override nativeClearLogicalAddress()1252 public void nativeClearLogicalAddress() { 1253 try { 1254 mHdmiCec.clearLogicalAddress(); 1255 } catch (RemoteException e) { 1256 HdmiLogger.error("Failed to clear logical address : ", e); 1257 } 1258 } 1259 1260 @Override nativeGetPhysicalAddress()1261 public int nativeGetPhysicalAddress() { 1262 try { 1263 mHdmiCec.getPhysicalAddress(this); 1264 return mPhysicalAddress; 1265 } catch (RemoteException e) { 1266 HdmiLogger.error("Failed to get physical address : ", e); 1267 return INVALID_PHYSICAL_ADDRESS; 1268 } 1269 } 1270 1271 @Override nativeGetVersion()1272 public int nativeGetVersion() { 1273 try { 1274 return mHdmiCec.getCecVersion(); 1275 } catch (RemoteException e) { 1276 HdmiLogger.error("Failed to get cec version : ", e); 1277 return Result.FAILURE_UNKNOWN; 1278 } 1279 } 1280 1281 @Override nativeGetVendorId()1282 public int nativeGetVendorId() { 1283 try { 1284 return mHdmiCec.getVendorId(); 1285 } catch (RemoteException e) { 1286 HdmiLogger.error("Failed to get vendor id : ", e); 1287 return Result.FAILURE_UNKNOWN; 1288 } 1289 } 1290 1291 @Override nativeGetPortInfos()1292 public HdmiPortInfo[] nativeGetPortInfos() { 1293 try { 1294 ArrayList<android.hardware.tv.cec.V1_0.HdmiPortInfo> hdmiPortInfos = 1295 mHdmiCec.getPortInfo(); 1296 HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[hdmiPortInfos.size()]; 1297 int i = 0; 1298 for (android.hardware.tv.cec.V1_0.HdmiPortInfo portInfo : hdmiPortInfos) { 1299 hdmiPortInfo[i] = new HdmiPortInfo.Builder( 1300 portInfo.portId, 1301 portInfo.type, 1302 portInfo.physicalAddress) 1303 .setCecSupported(portInfo.cecSupported) 1304 .setMhlSupported(false) 1305 .setArcSupported(portInfo.arcSupported) 1306 .setEarcSupported(false) 1307 .build(); 1308 i++; 1309 } 1310 return hdmiPortInfo; 1311 } catch (RemoteException e) { 1312 HdmiLogger.error("Failed to get port information : ", e); 1313 return null; 1314 } 1315 } 1316 nativeSetOption(int flag, boolean enabled)1317 private void nativeSetOption(int flag, boolean enabled) { 1318 try { 1319 mHdmiCec.setOption(flag, enabled); 1320 } catch (RemoteException e) { 1321 HdmiLogger.error("Failed to set option : ", e); 1322 } 1323 } 1324 1325 @Override enableWakeupByOtp(boolean enabled)1326 public void enableWakeupByOtp(boolean enabled) { 1327 nativeSetOption(OptionKey.WAKEUP, enabled); 1328 } 1329 1330 @Override enableCec(boolean enabled)1331 public void enableCec(boolean enabled) { 1332 nativeSetOption(OptionKey.ENABLE_CEC, enabled); 1333 } 1334 1335 @Override enableSystemCecControl(boolean enabled)1336 public void enableSystemCecControl(boolean enabled) { 1337 nativeSetOption(OptionKey.SYSTEM_CEC_CONTROL, enabled); 1338 } 1339 1340 @Override nativeSetLanguage(String language)1341 public void nativeSetLanguage(String language) { 1342 try { 1343 mHdmiCec.setLanguage(language); 1344 } catch (RemoteException e) { 1345 HdmiLogger.error("Failed to set language : ", e); 1346 } 1347 } 1348 1349 @Override nativeEnableAudioReturnChannel(int port, boolean flag)1350 public void nativeEnableAudioReturnChannel(int port, boolean flag) { 1351 try { 1352 mHdmiCec.enableAudioReturnChannel(port, flag); 1353 } catch (RemoteException e) { 1354 HdmiLogger.error("Failed to enable/disable ARC : ", e); 1355 } 1356 } 1357 1358 @Override nativeIsConnected(int port)1359 public boolean nativeIsConnected(int port) { 1360 try { 1361 return mHdmiCec.isConnected(port); 1362 } catch (RemoteException e) { 1363 HdmiLogger.error("Failed to get connection info : ", e); 1364 return false; 1365 } 1366 } 1367 1368 @Override nativeSetHpdSignalType(int signal, int portId)1369 public void nativeSetHpdSignalType(int signal, int portId) { 1370 HdmiLogger.error( 1371 "Failed to set HPD signal type: not supported by HAL."); 1372 } 1373 1374 @Override nativeGetHpdSignalType(int portId)1375 public int nativeGetHpdSignalType(int portId) { 1376 HdmiLogger.error( 1377 "Failed to get HPD signal type: not supported by HAL."); 1378 return Constants.HDMI_HPD_TYPE_PHYSICAL; 1379 } 1380 } 1381 1382 private static final class NativeWrapperImpl implements NativeWrapper, 1383 IHwBinder.DeathRecipient, getPhysicalAddressCallback { 1384 private android.hardware.tv.cec.V1_0.IHdmiCec mHdmiCec; 1385 @Nullable private HdmiCecCallback mCallback; 1386 1387 private final Object mLock = new Object(); 1388 private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS; 1389 1390 @Override nativeInit()1391 public String nativeInit() { 1392 return (connectToHal() ? mHdmiCec.toString() : null); 1393 } 1394 connectToHal()1395 boolean connectToHal() { 1396 try { 1397 mHdmiCec = android.hardware.tv.cec.V1_0.IHdmiCec.getService(true); 1398 try { 1399 mHdmiCec.linkToDeath(this, HDMI_CEC_HAL_DEATH_COOKIE); 1400 } catch (RemoteException e) { 1401 HdmiLogger.error("Couldn't link to death : ", e); 1402 } 1403 } catch (RemoteException | NoSuchElementException e) { 1404 HdmiLogger.error("Couldn't connect to cec@1.0", e); 1405 return false; 1406 } 1407 return true; 1408 } 1409 1410 @Override setCallback(@onNull HdmiCecCallback callback)1411 public void setCallback(@NonNull HdmiCecCallback callback) { 1412 mCallback = callback; 1413 try { 1414 mHdmiCec.setCallback(new HdmiCecCallback10(callback)); 1415 } catch (RemoteException e) { 1416 HdmiLogger.error("Couldn't initialise tv.cec callback : ", e); 1417 } 1418 } 1419 1420 @Override nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body)1421 public int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body) { 1422 android.hardware.tv.cec.V1_0.CecMessage message = 1423 new android.hardware.tv.cec.V1_0.CecMessage(); 1424 message.initiator = srcAddress; 1425 message.destination = dstAddress; 1426 message.body = new ArrayList<>(body.length); 1427 for (byte b : body) { 1428 message.body.add(b); 1429 } 1430 try { 1431 return mHdmiCec.sendMessage(message); 1432 } catch (RemoteException e) { 1433 HdmiLogger.error("Failed to send CEC message : ", e); 1434 return SendMessageResult.FAIL; 1435 } 1436 } 1437 1438 @Override nativeAddLogicalAddress(int logicalAddress)1439 public int nativeAddLogicalAddress(int logicalAddress) { 1440 try { 1441 return mHdmiCec.addLogicalAddress(logicalAddress); 1442 } catch (RemoteException e) { 1443 HdmiLogger.error("Failed to add a logical address : ", e); 1444 return Result.FAILURE_INVALID_ARGS; 1445 } 1446 } 1447 1448 @Override nativeClearLogicalAddress()1449 public void nativeClearLogicalAddress() { 1450 try { 1451 mHdmiCec.clearLogicalAddress(); 1452 } catch (RemoteException e) { 1453 HdmiLogger.error("Failed to clear logical address : ", e); 1454 } 1455 } 1456 1457 @Override nativeGetPhysicalAddress()1458 public int nativeGetPhysicalAddress() { 1459 try { 1460 mHdmiCec.getPhysicalAddress(this); 1461 return mPhysicalAddress; 1462 } catch (RemoteException e) { 1463 HdmiLogger.error("Failed to get physical address : ", e); 1464 return INVALID_PHYSICAL_ADDRESS; 1465 } 1466 } 1467 1468 @Override nativeGetVersion()1469 public int nativeGetVersion() { 1470 try { 1471 return mHdmiCec.getCecVersion(); 1472 } catch (RemoteException e) { 1473 HdmiLogger.error("Failed to get cec version : ", e); 1474 return Result.FAILURE_UNKNOWN; 1475 } 1476 } 1477 1478 @Override nativeGetVendorId()1479 public int nativeGetVendorId() { 1480 try { 1481 return mHdmiCec.getVendorId(); 1482 } catch (RemoteException e) { 1483 HdmiLogger.error("Failed to get vendor id : ", e); 1484 return Result.FAILURE_UNKNOWN; 1485 } 1486 } 1487 1488 @Override nativeGetPortInfos()1489 public HdmiPortInfo[] nativeGetPortInfos() { 1490 try { 1491 ArrayList<android.hardware.tv.cec.V1_0.HdmiPortInfo> hdmiPortInfos = 1492 mHdmiCec.getPortInfo(); 1493 HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[hdmiPortInfos.size()]; 1494 int i = 0; 1495 for (android.hardware.tv.cec.V1_0.HdmiPortInfo portInfo : hdmiPortInfos) { 1496 hdmiPortInfo[i] = new HdmiPortInfo.Builder( 1497 portInfo.portId, 1498 portInfo.type, 1499 portInfo.physicalAddress) 1500 .setCecSupported(portInfo.cecSupported) 1501 .setMhlSupported(false) 1502 .setArcSupported(portInfo.arcSupported) 1503 .setEarcSupported(false) 1504 .build(); 1505 i++; 1506 } 1507 return hdmiPortInfo; 1508 } catch (RemoteException e) { 1509 HdmiLogger.error("Failed to get port information : ", e); 1510 return null; 1511 } 1512 } 1513 nativeSetOption(int flag, boolean enabled)1514 private void nativeSetOption(int flag, boolean enabled) { 1515 try { 1516 mHdmiCec.setOption(flag, enabled); 1517 } catch (RemoteException e) { 1518 HdmiLogger.error("Failed to set option : ", e); 1519 } 1520 } 1521 1522 @Override enableWakeupByOtp(boolean enabled)1523 public void enableWakeupByOtp(boolean enabled) { 1524 nativeSetOption(OptionKey.WAKEUP, enabled); 1525 } 1526 1527 @Override enableCec(boolean enabled)1528 public void enableCec(boolean enabled) { 1529 nativeSetOption(OptionKey.ENABLE_CEC, enabled); 1530 } 1531 1532 @Override enableSystemCecControl(boolean enabled)1533 public void enableSystemCecControl(boolean enabled) { 1534 nativeSetOption(OptionKey.SYSTEM_CEC_CONTROL, enabled); 1535 } 1536 1537 @Override nativeSetLanguage(String language)1538 public void nativeSetLanguage(String language) { 1539 try { 1540 mHdmiCec.setLanguage(language); 1541 } catch (RemoteException e) { 1542 HdmiLogger.error("Failed to set language : ", e); 1543 } 1544 } 1545 1546 @Override nativeEnableAudioReturnChannel(int port, boolean flag)1547 public void nativeEnableAudioReturnChannel(int port, boolean flag) { 1548 try { 1549 mHdmiCec.enableAudioReturnChannel(port, flag); 1550 } catch (RemoteException e) { 1551 HdmiLogger.error("Failed to enable/disable ARC : ", e); 1552 } 1553 } 1554 1555 @Override nativeIsConnected(int port)1556 public boolean nativeIsConnected(int port) { 1557 try { 1558 return mHdmiCec.isConnected(port); 1559 } catch (RemoteException e) { 1560 HdmiLogger.error("Failed to get connection info : ", e); 1561 return false; 1562 } 1563 } 1564 1565 @Override nativeSetHpdSignalType(int signal, int portId)1566 public void nativeSetHpdSignalType(int signal, int portId) { 1567 HdmiLogger.error( 1568 "Failed to set HPD signal type: not supported by HAL."); 1569 } 1570 1571 @Override nativeGetHpdSignalType(int portId)1572 public int nativeGetHpdSignalType(int portId) { 1573 HdmiLogger.error( 1574 "Failed to get HPD signal type: not supported by HAL."); 1575 return Constants.HDMI_HPD_TYPE_PHYSICAL; 1576 } 1577 1578 @Override serviceDied(long cookie)1579 public void serviceDied(long cookie) { 1580 if (cookie == HDMI_CEC_HAL_DEATH_COOKIE) { 1581 HdmiLogger.error("Service died cookie : " + cookie + "; reconnecting"); 1582 connectToHal(); 1583 // Reconnect the callback 1584 if (mCallback != null) { 1585 setCallback(mCallback); 1586 } 1587 } 1588 } 1589 1590 @Override onValues(int result, short addr)1591 public void onValues(int result, short addr) { 1592 if (result == Result.SUCCESS) { 1593 synchronized (mLock) { 1594 mPhysicalAddress = new Short(addr).intValue(); 1595 } 1596 } 1597 } 1598 } 1599 1600 final class HdmiCecCallback { 1601 @VisibleForTesting onCecMessage(int initiator, int destination, byte[] body)1602 public void onCecMessage(int initiator, int destination, byte[] body) { 1603 runOnServiceThread( 1604 () -> handleIncomingCecCommand(initiator, destination, body)); 1605 } 1606 1607 @VisibleForTesting onHotplugEvent(int portId, boolean connected)1608 public void onHotplugEvent(int portId, boolean connected) { 1609 runOnServiceThread(() -> handleHotplug(portId, connected)); 1610 } 1611 } 1612 1613 private static final class HdmiCecCallback10 1614 extends android.hardware.tv.cec.V1_0.IHdmiCecCallback.Stub { 1615 private final HdmiCecCallback mHdmiCecCallback; 1616 HdmiCecCallback10(HdmiCecCallback hdmiCecCallback)1617 HdmiCecCallback10(HdmiCecCallback hdmiCecCallback) { 1618 mHdmiCecCallback = hdmiCecCallback; 1619 } 1620 1621 @Override onCecMessage(android.hardware.tv.cec.V1_0.CecMessage message)1622 public void onCecMessage(android.hardware.tv.cec.V1_0.CecMessage message) 1623 throws RemoteException { 1624 byte[] body = new byte[message.body.size()]; 1625 for (int i = 0; i < message.body.size(); i++) { 1626 body[i] = message.body.get(i); 1627 } 1628 mHdmiCecCallback.onCecMessage(message.initiator, message.destination, body); 1629 } 1630 1631 @Override onHotplugEvent(HotplugEvent event)1632 public void onHotplugEvent(HotplugEvent event) throws RemoteException { 1633 mHdmiCecCallback.onHotplugEvent(event.portId, event.connected); 1634 } 1635 } 1636 1637 private static final class HdmiCecCallback11 1638 extends android.hardware.tv.cec.V1_1.IHdmiCecCallback.Stub { 1639 private final HdmiCecCallback mHdmiCecCallback; 1640 HdmiCecCallback11(HdmiCecCallback hdmiCecCallback)1641 HdmiCecCallback11(HdmiCecCallback hdmiCecCallback) { 1642 mHdmiCecCallback = hdmiCecCallback; 1643 } 1644 1645 @Override onCecMessage_1_1(android.hardware.tv.cec.V1_1.CecMessage message)1646 public void onCecMessage_1_1(android.hardware.tv.cec.V1_1.CecMessage message) 1647 throws RemoteException { 1648 byte[] body = new byte[message.body.size()]; 1649 for (int i = 0; i < message.body.size(); i++) { 1650 body[i] = message.body.get(i); 1651 } 1652 mHdmiCecCallback.onCecMessage(message.initiator, message.destination, body); 1653 } 1654 1655 @Override onCecMessage(android.hardware.tv.cec.V1_0.CecMessage message)1656 public void onCecMessage(android.hardware.tv.cec.V1_0.CecMessage message) 1657 throws RemoteException { 1658 byte[] body = new byte[message.body.size()]; 1659 for (int i = 0; i < message.body.size(); i++) { 1660 body[i] = message.body.get(i); 1661 } 1662 mHdmiCecCallback.onCecMessage(message.initiator, message.destination, body); 1663 } 1664 1665 @Override onHotplugEvent(HotplugEvent event)1666 public void onHotplugEvent(HotplugEvent event) throws RemoteException { 1667 mHdmiCecCallback.onHotplugEvent(event.portId, event.connected); 1668 } 1669 } 1670 1671 private static final class HdmiCecCallbackAidl extends IHdmiCecCallback.Stub { 1672 private final HdmiCecCallback mHdmiCecCallback; 1673 HdmiCecCallbackAidl(HdmiCecCallback hdmiCecCallback)1674 HdmiCecCallbackAidl(HdmiCecCallback hdmiCecCallback) { 1675 mHdmiCecCallback = hdmiCecCallback; 1676 } 1677 1678 @Override onCecMessage(CecMessage message)1679 public void onCecMessage(CecMessage message) throws RemoteException { 1680 mHdmiCecCallback.onCecMessage(message.initiator, message.destination, message.body); 1681 } 1682 1683 @Override getInterfaceHash()1684 public synchronized String getInterfaceHash() throws android.os.RemoteException { 1685 return IHdmiCecCallback.Stub.HASH; 1686 } 1687 1688 @Override getInterfaceVersion()1689 public int getInterfaceVersion() throws android.os.RemoteException { 1690 return IHdmiCecCallback.Stub.VERSION; 1691 } 1692 } 1693 1694 private static final class HdmiConnectionCallbackAidl extends IHdmiConnectionCallback.Stub { 1695 private final HdmiCecCallback mHdmiCecCallback; 1696 HdmiConnectionCallbackAidl(HdmiCecCallback hdmiCecCallback)1697 HdmiConnectionCallbackAidl(HdmiCecCallback hdmiCecCallback) { 1698 mHdmiCecCallback = hdmiCecCallback; 1699 } 1700 1701 @Override onHotplugEvent(boolean connected, int portId)1702 public void onHotplugEvent(boolean connected, int portId) throws RemoteException { 1703 mHdmiCecCallback.onHotplugEvent(portId, connected); 1704 } 1705 1706 @Override getInterfaceHash()1707 public synchronized String getInterfaceHash() throws android.os.RemoteException { 1708 return IHdmiConnectionCallback.Stub.HASH; 1709 } 1710 1711 @Override getInterfaceVersion()1712 public int getInterfaceVersion() throws android.os.RemoteException { 1713 return IHdmiConnectionCallback.Stub.VERSION; 1714 } 1715 } 1716 1717 public abstract static class Dumpable { 1718 protected final long mTime; 1719 Dumpable()1720 Dumpable() { 1721 mTime = System.currentTimeMillis(); 1722 } 1723 dump(IndentingPrintWriter pw, SimpleDateFormat sdf)1724 abstract void dump(IndentingPrintWriter pw, SimpleDateFormat sdf); 1725 } 1726 1727 private static final class MessageHistoryRecord extends Dumpable { 1728 private final boolean mIsReceived; // true if received message and false if sent message 1729 private final HdmiCecMessage mMessage; 1730 private final List<String> mSendResults; 1731 MessageHistoryRecord(boolean isReceived, HdmiCecMessage message, List<String> sendResults)1732 MessageHistoryRecord(boolean isReceived, HdmiCecMessage message, List<String> sendResults) { 1733 super(); 1734 mIsReceived = isReceived; 1735 mMessage = message; 1736 mSendResults = sendResults; 1737 } 1738 1739 @Override dump(final IndentingPrintWriter pw, SimpleDateFormat sdf)1740 void dump(final IndentingPrintWriter pw, SimpleDateFormat sdf) { 1741 pw.print(mIsReceived ? "[R]" : "[S]"); 1742 pw.print(" time="); 1743 pw.print(sdf.format(new Date(mTime))); 1744 pw.print(" message="); 1745 pw.print(mMessage); 1746 1747 StringBuilder results = new StringBuilder(); 1748 if (!mIsReceived && mSendResults != null) { 1749 results.append(" ("); 1750 results.append(String.join(", ", mSendResults)); 1751 results.append(")"); 1752 } 1753 1754 pw.println(results); 1755 } 1756 } 1757 1758 private static final class HotplugHistoryRecord extends Dumpable { 1759 private final int mPort; 1760 private final boolean mConnected; 1761 HotplugHistoryRecord(int port, boolean connected)1762 HotplugHistoryRecord(int port, boolean connected) { 1763 super(); 1764 mPort = port; 1765 mConnected = connected; 1766 } 1767 1768 @Override dump(final IndentingPrintWriter pw, SimpleDateFormat sdf)1769 void dump(final IndentingPrintWriter pw, SimpleDateFormat sdf) { 1770 pw.print("[H]"); 1771 pw.print(" time="); 1772 pw.print(sdf.format(new Date(mTime))); 1773 pw.print(" hotplug port="); 1774 pw.print(mPort); 1775 pw.print(" connected="); 1776 pw.println(mConnected); 1777 } 1778 } 1779 } 1780