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 %d, signal %d", 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 %d ", 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 | NullPointerException 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 | NullPointerException 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 | NullPointerException 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 | NullPointerException 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 | NullPointerException 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 synchronized (mLock) { 1211 if (result == Result.SUCCESS) { 1212 mPhysicalAddress = Short.toUnsignedInt(addr); 1213 } else { 1214 mPhysicalAddress = INVALID_PHYSICAL_ADDRESS; 1215 } 1216 } 1217 } 1218 1219 @Override serviceDied(long cookie)1220 public void serviceDied(long cookie) { 1221 if (cookie == HDMI_CEC_HAL_DEATH_COOKIE) { 1222 HdmiLogger.error("Service died cookie : " + cookie + "; reconnecting"); 1223 connectToHal(); 1224 // Reconnect the callback 1225 if (mCallback != null) { 1226 setCallback(mCallback); 1227 } 1228 } 1229 } 1230 1231 @Override setCallback(HdmiCecCallback callback)1232 public void setCallback(HdmiCecCallback callback) { 1233 mCallback = callback; 1234 try { 1235 mHdmiCec.setCallback_1_1(new HdmiCecCallback11(callback)); 1236 } catch (RemoteException e) { 1237 HdmiLogger.error("Couldn't initialise tv.cec callback : ", e); 1238 } 1239 } 1240 1241 @Override nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body)1242 public int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body) { 1243 android.hardware.tv.cec.V1_1.CecMessage message = 1244 new android.hardware.tv.cec.V1_1.CecMessage(); 1245 message.initiator = srcAddress; 1246 message.destination = dstAddress; 1247 message.body = new ArrayList<>(body.length); 1248 for (byte b : body) { 1249 message.body.add(b); 1250 } 1251 try { 1252 return mHdmiCec.sendMessage_1_1(message); 1253 } catch (RemoteException e) { 1254 HdmiLogger.error("Failed to send CEC message : ", e); 1255 return SendMessageResult.FAIL; 1256 } 1257 } 1258 1259 @Override nativeAddLogicalAddress(int logicalAddress)1260 public int nativeAddLogicalAddress(int logicalAddress) { 1261 try { 1262 return mHdmiCec.addLogicalAddress_1_1(logicalAddress); 1263 } catch (RemoteException e) { 1264 HdmiLogger.error("Failed to add a logical address : ", e); 1265 return Result.FAILURE_INVALID_ARGS; 1266 } 1267 } 1268 1269 @Override nativeClearLogicalAddress()1270 public void nativeClearLogicalAddress() { 1271 try { 1272 mHdmiCec.clearLogicalAddress(); 1273 } catch (RemoteException e) { 1274 HdmiLogger.error("Failed to clear logical address : ", e); 1275 } 1276 } 1277 1278 @Override nativeGetPhysicalAddress()1279 public int nativeGetPhysicalAddress() { 1280 try { 1281 mHdmiCec.getPhysicalAddress(this); 1282 return mPhysicalAddress; 1283 } catch (RemoteException e) { 1284 HdmiLogger.error("Failed to get physical address : ", e); 1285 return INVALID_PHYSICAL_ADDRESS; 1286 } 1287 } 1288 1289 @Override nativeGetVersion()1290 public int nativeGetVersion() { 1291 try { 1292 return mHdmiCec.getCecVersion(); 1293 } catch (RemoteException e) { 1294 HdmiLogger.error("Failed to get cec version : ", e); 1295 return Result.FAILURE_UNKNOWN; 1296 } 1297 } 1298 1299 @Override nativeGetVendorId()1300 public int nativeGetVendorId() { 1301 try { 1302 return mHdmiCec.getVendorId(); 1303 } catch (RemoteException e) { 1304 HdmiLogger.error("Failed to get vendor id : ", e); 1305 return Result.FAILURE_UNKNOWN; 1306 } 1307 } 1308 1309 @Override nativeGetPortInfos()1310 public HdmiPortInfo[] nativeGetPortInfos() { 1311 try { 1312 ArrayList<android.hardware.tv.cec.V1_0.HdmiPortInfo> hdmiPortInfos = 1313 mHdmiCec.getPortInfo(); 1314 HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[hdmiPortInfos.size()]; 1315 int i = 0; 1316 for (android.hardware.tv.cec.V1_0.HdmiPortInfo portInfo : hdmiPortInfos) { 1317 hdmiPortInfo[i] = new HdmiPortInfo.Builder( 1318 portInfo.portId, 1319 portInfo.type, 1320 Short.toUnsignedInt(portInfo.physicalAddress)) 1321 .setCecSupported(portInfo.cecSupported) 1322 .setMhlSupported(false) 1323 .setArcSupported(portInfo.arcSupported) 1324 .setEarcSupported(false) 1325 .build(); 1326 i++; 1327 } 1328 return hdmiPortInfo; 1329 } catch (RemoteException e) { 1330 HdmiLogger.error("Failed to get port information : ", e); 1331 return null; 1332 } 1333 } 1334 nativeSetOption(int flag, boolean enabled)1335 private void nativeSetOption(int flag, boolean enabled) { 1336 try { 1337 mHdmiCec.setOption(flag, enabled); 1338 } catch (RemoteException e) { 1339 HdmiLogger.error("Failed to set option : ", e); 1340 } 1341 } 1342 1343 @Override enableWakeupByOtp(boolean enabled)1344 public void enableWakeupByOtp(boolean enabled) { 1345 nativeSetOption(OptionKey.WAKEUP, enabled); 1346 } 1347 1348 @Override enableCec(boolean enabled)1349 public void enableCec(boolean enabled) { 1350 nativeSetOption(OptionKey.ENABLE_CEC, enabled); 1351 } 1352 1353 @Override enableSystemCecControl(boolean enabled)1354 public void enableSystemCecControl(boolean enabled) { 1355 nativeSetOption(OptionKey.SYSTEM_CEC_CONTROL, enabled); 1356 } 1357 1358 @Override nativeSetLanguage(String language)1359 public void nativeSetLanguage(String language) { 1360 try { 1361 mHdmiCec.setLanguage(language); 1362 } catch (RemoteException e) { 1363 HdmiLogger.error("Failed to set language : ", e); 1364 } 1365 } 1366 1367 @Override nativeEnableAudioReturnChannel(int port, boolean flag)1368 public void nativeEnableAudioReturnChannel(int port, boolean flag) { 1369 try { 1370 mHdmiCec.enableAudioReturnChannel(port, flag); 1371 } catch (RemoteException e) { 1372 HdmiLogger.error("Failed to enable/disable ARC : ", e); 1373 } 1374 } 1375 1376 @Override nativeIsConnected(int port)1377 public boolean nativeIsConnected(int port) { 1378 try { 1379 return mHdmiCec.isConnected(port); 1380 } catch (RemoteException e) { 1381 HdmiLogger.error("Failed to get connection info : ", e); 1382 return false; 1383 } 1384 } 1385 1386 @Override nativeSetHpdSignalType(int signal, int portId)1387 public void nativeSetHpdSignalType(int signal, int portId) { 1388 HdmiLogger.error( 1389 "Failed to set HPD signal type: not supported by HAL."); 1390 } 1391 1392 @Override nativeGetHpdSignalType(int portId)1393 public int nativeGetHpdSignalType(int portId) { 1394 HdmiLogger.error( 1395 "Failed to get HPD signal type: not supported by HAL."); 1396 return Constants.HDMI_HPD_TYPE_PHYSICAL; 1397 } 1398 } 1399 1400 private static final class NativeWrapperImpl implements NativeWrapper, 1401 IHwBinder.DeathRecipient, getPhysicalAddressCallback { 1402 private android.hardware.tv.cec.V1_0.IHdmiCec mHdmiCec; 1403 @Nullable private HdmiCecCallback mCallback; 1404 1405 private final Object mLock = new Object(); 1406 private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS; 1407 1408 @Override nativeInit()1409 public String nativeInit() { 1410 return (connectToHal() ? mHdmiCec.toString() : null); 1411 } 1412 connectToHal()1413 boolean connectToHal() { 1414 try { 1415 mHdmiCec = android.hardware.tv.cec.V1_0.IHdmiCec.getService(true); 1416 try { 1417 mHdmiCec.linkToDeath(this, HDMI_CEC_HAL_DEATH_COOKIE); 1418 } catch (RemoteException e) { 1419 HdmiLogger.error("Couldn't link to death : ", e); 1420 } 1421 } catch (RemoteException | NoSuchElementException e) { 1422 HdmiLogger.error("Couldn't connect to cec@1.0", e); 1423 return false; 1424 } 1425 return true; 1426 } 1427 1428 @Override setCallback(@onNull HdmiCecCallback callback)1429 public void setCallback(@NonNull HdmiCecCallback callback) { 1430 mCallback = callback; 1431 try { 1432 mHdmiCec.setCallback(new HdmiCecCallback10(callback)); 1433 } catch (RemoteException e) { 1434 HdmiLogger.error("Couldn't initialise tv.cec callback : ", e); 1435 } 1436 } 1437 1438 @Override nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body)1439 public int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body) { 1440 android.hardware.tv.cec.V1_0.CecMessage message = 1441 new android.hardware.tv.cec.V1_0.CecMessage(); 1442 message.initiator = srcAddress; 1443 message.destination = dstAddress; 1444 message.body = new ArrayList<>(body.length); 1445 for (byte b : body) { 1446 message.body.add(b); 1447 } 1448 try { 1449 return mHdmiCec.sendMessage(message); 1450 } catch (RemoteException e) { 1451 HdmiLogger.error("Failed to send CEC message : ", e); 1452 return SendMessageResult.FAIL; 1453 } 1454 } 1455 1456 @Override nativeAddLogicalAddress(int logicalAddress)1457 public int nativeAddLogicalAddress(int logicalAddress) { 1458 try { 1459 return mHdmiCec.addLogicalAddress(logicalAddress); 1460 } catch (RemoteException e) { 1461 HdmiLogger.error("Failed to add a logical address : ", e); 1462 return Result.FAILURE_INVALID_ARGS; 1463 } 1464 } 1465 1466 @Override nativeClearLogicalAddress()1467 public void nativeClearLogicalAddress() { 1468 try { 1469 mHdmiCec.clearLogicalAddress(); 1470 } catch (RemoteException e) { 1471 HdmiLogger.error("Failed to clear logical address : ", e); 1472 } 1473 } 1474 1475 @Override nativeGetPhysicalAddress()1476 public int nativeGetPhysicalAddress() { 1477 try { 1478 mHdmiCec.getPhysicalAddress(this); 1479 return mPhysicalAddress; 1480 } catch (RemoteException e) { 1481 HdmiLogger.error("Failed to get physical address : ", e); 1482 return INVALID_PHYSICAL_ADDRESS; 1483 } 1484 } 1485 1486 @Override nativeGetVersion()1487 public int nativeGetVersion() { 1488 try { 1489 return mHdmiCec.getCecVersion(); 1490 } catch (RemoteException e) { 1491 HdmiLogger.error("Failed to get cec version : ", e); 1492 return Result.FAILURE_UNKNOWN; 1493 } 1494 } 1495 1496 @Override nativeGetVendorId()1497 public int nativeGetVendorId() { 1498 try { 1499 return mHdmiCec.getVendorId(); 1500 } catch (RemoteException e) { 1501 HdmiLogger.error("Failed to get vendor id : ", e); 1502 return Result.FAILURE_UNKNOWN; 1503 } 1504 } 1505 1506 @Override nativeGetPortInfos()1507 public HdmiPortInfo[] nativeGetPortInfos() { 1508 try { 1509 ArrayList<android.hardware.tv.cec.V1_0.HdmiPortInfo> hdmiPortInfos = 1510 mHdmiCec.getPortInfo(); 1511 HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[hdmiPortInfos.size()]; 1512 int i = 0; 1513 for (android.hardware.tv.cec.V1_0.HdmiPortInfo portInfo : hdmiPortInfos) { 1514 hdmiPortInfo[i] = new HdmiPortInfo.Builder( 1515 portInfo.portId, 1516 portInfo.type, 1517 Short.toUnsignedInt(portInfo.physicalAddress)) 1518 .setCecSupported(portInfo.cecSupported) 1519 .setMhlSupported(false) 1520 .setArcSupported(portInfo.arcSupported) 1521 .setEarcSupported(false) 1522 .build(); 1523 i++; 1524 } 1525 return hdmiPortInfo; 1526 } catch (RemoteException e) { 1527 HdmiLogger.error("Failed to get port information : ", e); 1528 return null; 1529 } 1530 } 1531 nativeSetOption(int flag, boolean enabled)1532 private void nativeSetOption(int flag, boolean enabled) { 1533 try { 1534 mHdmiCec.setOption(flag, enabled); 1535 } catch (RemoteException e) { 1536 HdmiLogger.error("Failed to set option : ", e); 1537 } 1538 } 1539 1540 @Override enableWakeupByOtp(boolean enabled)1541 public void enableWakeupByOtp(boolean enabled) { 1542 nativeSetOption(OptionKey.WAKEUP, enabled); 1543 } 1544 1545 @Override enableCec(boolean enabled)1546 public void enableCec(boolean enabled) { 1547 nativeSetOption(OptionKey.ENABLE_CEC, enabled); 1548 } 1549 1550 @Override enableSystemCecControl(boolean enabled)1551 public void enableSystemCecControl(boolean enabled) { 1552 nativeSetOption(OptionKey.SYSTEM_CEC_CONTROL, enabled); 1553 } 1554 1555 @Override nativeSetLanguage(String language)1556 public void nativeSetLanguage(String language) { 1557 try { 1558 mHdmiCec.setLanguage(language); 1559 } catch (RemoteException e) { 1560 HdmiLogger.error("Failed to set language : ", e); 1561 } 1562 } 1563 1564 @Override nativeEnableAudioReturnChannel(int port, boolean flag)1565 public void nativeEnableAudioReturnChannel(int port, boolean flag) { 1566 try { 1567 mHdmiCec.enableAudioReturnChannel(port, flag); 1568 } catch (RemoteException e) { 1569 HdmiLogger.error("Failed to enable/disable ARC : ", e); 1570 } 1571 } 1572 1573 @Override nativeIsConnected(int port)1574 public boolean nativeIsConnected(int port) { 1575 try { 1576 return mHdmiCec.isConnected(port); 1577 } catch (RemoteException e) { 1578 HdmiLogger.error("Failed to get connection info : ", e); 1579 return false; 1580 } 1581 } 1582 1583 @Override nativeSetHpdSignalType(int signal, int portId)1584 public void nativeSetHpdSignalType(int signal, int portId) { 1585 HdmiLogger.error( 1586 "Failed to set HPD signal type: not supported by HAL."); 1587 } 1588 1589 @Override nativeGetHpdSignalType(int portId)1590 public int nativeGetHpdSignalType(int portId) { 1591 HdmiLogger.error( 1592 "Failed to get HPD signal type: not supported by HAL."); 1593 return Constants.HDMI_HPD_TYPE_PHYSICAL; 1594 } 1595 1596 @Override serviceDied(long cookie)1597 public void serviceDied(long cookie) { 1598 if (cookie == HDMI_CEC_HAL_DEATH_COOKIE) { 1599 HdmiLogger.error("Service died cookie : " + cookie + "; reconnecting"); 1600 connectToHal(); 1601 // Reconnect the callback 1602 if (mCallback != null) { 1603 setCallback(mCallback); 1604 } 1605 } 1606 } 1607 1608 @Override onValues(int result, short addr)1609 public void onValues(int result, short addr) { 1610 synchronized (mLock) { 1611 if (result == Result.SUCCESS) { 1612 mPhysicalAddress = Short.toUnsignedInt(addr); 1613 } else { 1614 mPhysicalAddress = INVALID_PHYSICAL_ADDRESS; 1615 } 1616 } 1617 } 1618 } 1619 1620 final class HdmiCecCallback { 1621 @VisibleForTesting onCecMessage(int initiator, int destination, byte[] body)1622 public void onCecMessage(int initiator, int destination, byte[] body) { 1623 runOnServiceThread( 1624 () -> handleIncomingCecCommand(initiator, destination, body)); 1625 } 1626 1627 @VisibleForTesting onHotplugEvent(int portId, boolean connected)1628 public void onHotplugEvent(int portId, boolean connected) { 1629 runOnServiceThread(() -> handleHotplug(portId, connected)); 1630 } 1631 } 1632 1633 private static final class HdmiCecCallback10 1634 extends android.hardware.tv.cec.V1_0.IHdmiCecCallback.Stub { 1635 private final HdmiCecCallback mHdmiCecCallback; 1636 HdmiCecCallback10(HdmiCecCallback hdmiCecCallback)1637 HdmiCecCallback10(HdmiCecCallback hdmiCecCallback) { 1638 mHdmiCecCallback = hdmiCecCallback; 1639 } 1640 1641 @Override onCecMessage(android.hardware.tv.cec.V1_0.CecMessage message)1642 public void onCecMessage(android.hardware.tv.cec.V1_0.CecMessage message) 1643 throws RemoteException { 1644 byte[] body = new byte[message.body.size()]; 1645 for (int i = 0; i < message.body.size(); i++) { 1646 body[i] = message.body.get(i); 1647 } 1648 mHdmiCecCallback.onCecMessage(message.initiator, message.destination, body); 1649 } 1650 1651 @Override onHotplugEvent(HotplugEvent event)1652 public void onHotplugEvent(HotplugEvent event) throws RemoteException { 1653 mHdmiCecCallback.onHotplugEvent(event.portId, event.connected); 1654 } 1655 } 1656 1657 private static final class HdmiCecCallback11 1658 extends android.hardware.tv.cec.V1_1.IHdmiCecCallback.Stub { 1659 private final HdmiCecCallback mHdmiCecCallback; 1660 HdmiCecCallback11(HdmiCecCallback hdmiCecCallback)1661 HdmiCecCallback11(HdmiCecCallback hdmiCecCallback) { 1662 mHdmiCecCallback = hdmiCecCallback; 1663 } 1664 1665 @Override onCecMessage_1_1(android.hardware.tv.cec.V1_1.CecMessage message)1666 public void onCecMessage_1_1(android.hardware.tv.cec.V1_1.CecMessage message) 1667 throws RemoteException { 1668 byte[] body = new byte[message.body.size()]; 1669 for (int i = 0; i < message.body.size(); i++) { 1670 body[i] = message.body.get(i); 1671 } 1672 mHdmiCecCallback.onCecMessage(message.initiator, message.destination, body); 1673 } 1674 1675 @Override onCecMessage(android.hardware.tv.cec.V1_0.CecMessage message)1676 public void onCecMessage(android.hardware.tv.cec.V1_0.CecMessage message) 1677 throws RemoteException { 1678 byte[] body = new byte[message.body.size()]; 1679 for (int i = 0; i < message.body.size(); i++) { 1680 body[i] = message.body.get(i); 1681 } 1682 mHdmiCecCallback.onCecMessage(message.initiator, message.destination, body); 1683 } 1684 1685 @Override onHotplugEvent(HotplugEvent event)1686 public void onHotplugEvent(HotplugEvent event) throws RemoteException { 1687 mHdmiCecCallback.onHotplugEvent(event.portId, event.connected); 1688 } 1689 } 1690 1691 private static final class HdmiCecCallbackAidl extends IHdmiCecCallback.Stub { 1692 private final HdmiCecCallback mHdmiCecCallback; 1693 HdmiCecCallbackAidl(HdmiCecCallback hdmiCecCallback)1694 HdmiCecCallbackAidl(HdmiCecCallback hdmiCecCallback) { 1695 mHdmiCecCallback = hdmiCecCallback; 1696 } 1697 1698 @Override onCecMessage(CecMessage message)1699 public void onCecMessage(CecMessage message) throws RemoteException { 1700 mHdmiCecCallback.onCecMessage(message.initiator, message.destination, message.body); 1701 } 1702 1703 @Override getInterfaceHash()1704 public synchronized String getInterfaceHash() throws android.os.RemoteException { 1705 return IHdmiCecCallback.Stub.HASH; 1706 } 1707 1708 @Override getInterfaceVersion()1709 public int getInterfaceVersion() throws android.os.RemoteException { 1710 return IHdmiCecCallback.Stub.VERSION; 1711 } 1712 } 1713 1714 private static final class HdmiConnectionCallbackAidl extends IHdmiConnectionCallback.Stub { 1715 private final HdmiCecCallback mHdmiCecCallback; 1716 HdmiConnectionCallbackAidl(HdmiCecCallback hdmiCecCallback)1717 HdmiConnectionCallbackAidl(HdmiCecCallback hdmiCecCallback) { 1718 mHdmiCecCallback = hdmiCecCallback; 1719 } 1720 1721 @Override onHotplugEvent(boolean connected, int portId)1722 public void onHotplugEvent(boolean connected, int portId) throws RemoteException { 1723 mHdmiCecCallback.onHotplugEvent(portId, connected); 1724 } 1725 1726 @Override getInterfaceHash()1727 public synchronized String getInterfaceHash() throws android.os.RemoteException { 1728 return IHdmiConnectionCallback.Stub.HASH; 1729 } 1730 1731 @Override getInterfaceVersion()1732 public int getInterfaceVersion() throws android.os.RemoteException { 1733 return IHdmiConnectionCallback.Stub.VERSION; 1734 } 1735 } 1736 1737 public abstract static class Dumpable { 1738 protected final long mTime; 1739 Dumpable()1740 Dumpable() { 1741 mTime = System.currentTimeMillis(); 1742 } 1743 dump(IndentingPrintWriter pw, SimpleDateFormat sdf)1744 abstract void dump(IndentingPrintWriter pw, SimpleDateFormat sdf); 1745 } 1746 1747 private static final class MessageHistoryRecord extends Dumpable { 1748 private final boolean mIsReceived; // true if received message and false if sent message 1749 private final HdmiCecMessage mMessage; 1750 private final List<String> mSendResults; 1751 MessageHistoryRecord(boolean isReceived, HdmiCecMessage message, List<String> sendResults)1752 MessageHistoryRecord(boolean isReceived, HdmiCecMessage message, List<String> sendResults) { 1753 super(); 1754 mIsReceived = isReceived; 1755 mMessage = message; 1756 mSendResults = sendResults; 1757 } 1758 1759 @Override dump(final IndentingPrintWriter pw, SimpleDateFormat sdf)1760 void dump(final IndentingPrintWriter pw, SimpleDateFormat sdf) { 1761 pw.print(mIsReceived ? "[R]" : "[S]"); 1762 pw.print(" time="); 1763 pw.print(sdf.format(new Date(mTime))); 1764 pw.print(" message="); 1765 pw.print(mMessage); 1766 1767 StringBuilder results = new StringBuilder(); 1768 if (!mIsReceived && mSendResults != null) { 1769 results.append(" ("); 1770 results.append(String.join(", ", mSendResults)); 1771 results.append(")"); 1772 } 1773 1774 pw.println(results); 1775 } 1776 } 1777 1778 private static final class HotplugHistoryRecord extends Dumpable { 1779 private final int mPort; 1780 private final boolean mConnected; 1781 HotplugHistoryRecord(int port, boolean connected)1782 HotplugHistoryRecord(int port, boolean connected) { 1783 super(); 1784 mPort = port; 1785 mConnected = connected; 1786 } 1787 1788 @Override dump(final IndentingPrintWriter pw, SimpleDateFormat sdf)1789 void dump(final IndentingPrintWriter pw, SimpleDateFormat sdf) { 1790 pw.print("[H]"); 1791 pw.print(" time="); 1792 pw.print(sdf.format(new Date(mTime))); 1793 pw.print(" hotplug port="); 1794 pw.print(mPort); 1795 pw.print(" connected="); 1796 pw.println(mConnected); 1797 } 1798 } 1799 } 1800