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