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.hardware.hdmi.HdmiPortInfo; 20 import android.hardware.tv.cec.V1_0.CecMessage; 21 import android.hardware.tv.cec.V1_0.HotplugEvent; 22 import android.hardware.tv.cec.V1_0.IHdmiCec; 23 import android.hardware.tv.cec.V1_0.IHdmiCec.getPhysicalAddressCallback; 24 import android.hardware.tv.cec.V1_0.IHdmiCecCallback; 25 import android.hardware.tv.cec.V1_0.Result; 26 import android.hardware.tv.cec.V1_0.SendMessageResult; 27 import android.os.Handler; 28 import android.os.IHwBinder; 29 import android.os.Looper; 30 import android.os.RemoteException; 31 import android.util.Slog; 32 import android.util.SparseArray; 33 34 import com.android.internal.util.IndentingPrintWriter; 35 import com.android.server.hdmi.HdmiAnnotations.IoThreadOnly; 36 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; 37 import com.android.server.hdmi.HdmiControlService.DevicePollingCallback; 38 39 import libcore.util.EmptyArray; 40 41 import java.text.SimpleDateFormat; 42 import java.util.ArrayList; 43 import java.util.Date; 44 import java.util.LinkedList; 45 import java.util.List; 46 import java.util.concurrent.ArrayBlockingQueue; 47 import java.util.function.Predicate; 48 49 import sun.util.locale.LanguageTag; 50 51 /** 52 * Manages HDMI-CEC command and behaviors. It converts user's command into CEC command 53 * and pass it to CEC HAL so that it sends message to other device. For incoming 54 * message it translates the message and delegates it to proper module. 55 * 56 * <p>It should be careful to access member variables on IO thread because 57 * it can be accessed from system thread as well. 58 * 59 * <p>It can be created only by {@link HdmiCecController#create} 60 * 61 * <p>Declared as package-private, accessed by {@link HdmiControlService} only. 62 */ 63 final class HdmiCecController { 64 private static final String TAG = "HdmiCecController"; 65 66 /** 67 * Interface to report allocated logical address. 68 */ 69 interface AllocateAddressCallback { 70 /** 71 * Called when a new logical address is allocated. 72 * 73 * @param deviceType requested device type to allocate logical address 74 * @param logicalAddress allocated logical address. If it is 75 * {@link Constants#ADDR_UNREGISTERED}, it means that 76 * it failed to allocate logical address for the given device type 77 */ onAllocated(int deviceType, int logicalAddress)78 void onAllocated(int deviceType, int logicalAddress); 79 } 80 81 private static final byte[] EMPTY_BODY = EmptyArray.BYTE; 82 83 private static final int NUM_LOGICAL_ADDRESS = 16; 84 85 private static final int MAX_CEC_MESSAGE_HISTORY = 200; 86 87 private static final int INVALID_PHYSICAL_ADDRESS = 0xFFFF; 88 89 /** Cookie for matching the right end point. */ 90 protected static final int HDMI_CEC_HAL_DEATH_COOKIE = 353; 91 92 // Predicate for whether the given logical address is remote device's one or not. 93 private final Predicate<Integer> mRemoteDeviceAddressPredicate = new Predicate<Integer>() { 94 @Override 95 public boolean test(Integer address) { 96 return !isAllocatedLocalDeviceAddress(address); 97 } 98 }; 99 100 // Predicate whether the given logical address is system audio's one or not 101 private final Predicate<Integer> mSystemAudioAddressPredicate = new Predicate<Integer>() { 102 @Override 103 public boolean test(Integer address) { 104 return HdmiUtils.getTypeFromAddress(address) == Constants.ADDR_AUDIO_SYSTEM; 105 } 106 }; 107 108 // Handler instance to process synchronous I/O (mainly send) message. 109 private Handler mIoHandler; 110 111 // Handler instance to process various messages coming from other CEC 112 // device or issued by internal state change. 113 private Handler mControlHandler; 114 115 private final HdmiControlService mService; 116 117 // Stores the local CEC devices in the system. Device type is used for key. 118 private final SparseArray<HdmiCecLocalDevice> mLocalDevices = new SparseArray<>(); 119 120 // Stores recent CEC messages history for debugging purpose. 121 private final ArrayBlockingQueue<MessageHistoryRecord> mMessageHistory = 122 new ArrayBlockingQueue<>(MAX_CEC_MESSAGE_HISTORY); 123 124 private final NativeWrapper mNativeWrapperImpl; 125 126 // Private constructor. Use HdmiCecController.create(). HdmiCecController(HdmiControlService service, NativeWrapper nativeWrapper)127 private HdmiCecController(HdmiControlService service, NativeWrapper nativeWrapper) { 128 mService = service; 129 mNativeWrapperImpl = nativeWrapper; 130 } 131 132 /** 133 * A factory method to get {@link HdmiCecController}. If it fails to initialize 134 * inner device or has no device it will return {@code null}. 135 * 136 * <p>Declared as package-private, accessed by {@link HdmiControlService} only. 137 * @param service {@link HdmiControlService} instance used to create internal handler 138 * and to pass callback for incoming message or event. 139 * @return {@link HdmiCecController} if device is initialized successfully. Otherwise, 140 * returns {@code null}. 141 */ create(HdmiControlService service)142 static HdmiCecController create(HdmiControlService service) { 143 return createWithNativeWrapper(service, new NativeWrapperImpl()); 144 } 145 146 /** 147 * A factory method with injection of native methods for testing. 148 */ createWithNativeWrapper( HdmiControlService service, NativeWrapper nativeWrapper)149 static HdmiCecController createWithNativeWrapper( 150 HdmiControlService service, NativeWrapper nativeWrapper) { 151 HdmiCecController controller = new HdmiCecController(service, nativeWrapper); 152 String nativePtr = nativeWrapper.nativeInit(); 153 if (nativePtr == null) { 154 HdmiLogger.warning("Couldn't get tv.cec service."); 155 return null; 156 } 157 controller.init(nativeWrapper); 158 return controller; 159 } 160 init(NativeWrapper nativeWrapper)161 private void init(NativeWrapper nativeWrapper) { 162 mIoHandler = new Handler(mService.getIoLooper()); 163 mControlHandler = new Handler(mService.getServiceLooper()); 164 nativeWrapper.setCallback(new HdmiCecCallback()); 165 } 166 167 @ServiceThreadOnly addLocalDevice(int deviceType, HdmiCecLocalDevice device)168 void addLocalDevice(int deviceType, HdmiCecLocalDevice device) { 169 assertRunOnServiceThread(); 170 mLocalDevices.put(deviceType, device); 171 } 172 173 /** 174 * Allocate a new logical address of the given device type. Allocated 175 * address will be reported through {@link AllocateAddressCallback}. 176 * 177 * <p> Declared as package-private, accessed by {@link HdmiControlService} only. 178 * 179 * @param deviceType type of device to used to determine logical address 180 * @param preferredAddress a logical address preferred to be allocated. 181 * If sets {@link Constants#ADDR_UNREGISTERED}, scans 182 * the smallest logical address matched with the given device type. 183 * Otherwise, scan address will start from {@code preferredAddress} 184 * @param callback callback interface to report allocated logical address to caller 185 */ 186 @ServiceThreadOnly allocateLogicalAddress(final int deviceType, final int preferredAddress, final AllocateAddressCallback callback)187 void allocateLogicalAddress(final int deviceType, final int preferredAddress, 188 final AllocateAddressCallback callback) { 189 assertRunOnServiceThread(); 190 191 runOnIoThread(new Runnable() { 192 @Override 193 public void run() { 194 handleAllocateLogicalAddress(deviceType, preferredAddress, callback); 195 } 196 }); 197 } 198 199 @IoThreadOnly handleAllocateLogicalAddress(final int deviceType, int preferredAddress, final AllocateAddressCallback callback)200 private void handleAllocateLogicalAddress(final int deviceType, int preferredAddress, 201 final AllocateAddressCallback callback) { 202 assertRunOnIoThread(); 203 int startAddress = preferredAddress; 204 // If preferred address is "unregistered", start address will be the smallest 205 // address matched with the given device type. 206 if (preferredAddress == Constants.ADDR_UNREGISTERED) { 207 for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) { 208 if (deviceType == HdmiUtils.getTypeFromAddress(i)) { 209 startAddress = i; 210 break; 211 } 212 } 213 } 214 215 int logicalAddress = Constants.ADDR_UNREGISTERED; 216 // Iterates all possible addresses which has the same device type. 217 for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) { 218 int curAddress = (startAddress + i) % NUM_LOGICAL_ADDRESS; 219 if (curAddress != Constants.ADDR_UNREGISTERED 220 && deviceType == HdmiUtils.getTypeFromAddress(curAddress)) { 221 boolean acked = false; 222 for (int j = 0; j < HdmiConfig.ADDRESS_ALLOCATION_RETRY; ++j) { 223 if (sendPollMessage(curAddress, curAddress, 1)) { 224 acked = true; 225 break; 226 } 227 } 228 // If sending <Polling Message> failed, it becomes new logical address for the 229 // device because no device uses it as logical address of the device. 230 if (!acked) { 231 logicalAddress = curAddress; 232 break; 233 } 234 } 235 } 236 237 final int assignedAddress = logicalAddress; 238 HdmiLogger.debug("New logical address for device [%d]: [preferred:%d, assigned:%d]", 239 deviceType, preferredAddress, assignedAddress); 240 if (callback != null) { 241 runOnServiceThread(new Runnable() { 242 @Override 243 public void run() { 244 callback.onAllocated(deviceType, assignedAddress); 245 } 246 }); 247 } 248 } 249 buildBody(int opcode, byte[] params)250 private static byte[] buildBody(int opcode, byte[] params) { 251 byte[] body = new byte[params.length + 1]; 252 body[0] = (byte) opcode; 253 System.arraycopy(params, 0, body, 1, params.length); 254 return body; 255 } 256 257 getPortInfos()258 HdmiPortInfo[] getPortInfos() { 259 return mNativeWrapperImpl.nativeGetPortInfos(); 260 } 261 262 /** 263 * Return the locally hosted logical device of a given type. 264 * 265 * @param deviceType logical device type 266 * @return {@link HdmiCecLocalDevice} instance if the instance of the type is available; 267 * otherwise null. 268 */ getLocalDevice(int deviceType)269 HdmiCecLocalDevice getLocalDevice(int deviceType) { 270 return mLocalDevices.get(deviceType); 271 } 272 273 /** 274 * Add a new logical address to the device. Device's HW should be notified 275 * when a new logical address is assigned to a device, so that it can accept 276 * a command having available destinations. 277 * 278 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 279 * 280 * @param newLogicalAddress a logical address to be added 281 * @return 0 on success. Otherwise, returns negative value 282 */ 283 @ServiceThreadOnly addLogicalAddress(int newLogicalAddress)284 int addLogicalAddress(int newLogicalAddress) { 285 assertRunOnServiceThread(); 286 if (HdmiUtils.isValidAddress(newLogicalAddress)) { 287 return mNativeWrapperImpl.nativeAddLogicalAddress(newLogicalAddress); 288 } else { 289 return Result.FAILURE_INVALID_ARGS; 290 } 291 } 292 293 /** 294 * Clear all logical addresses registered in the device. 295 * 296 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 297 */ 298 @ServiceThreadOnly clearLogicalAddress()299 void clearLogicalAddress() { 300 assertRunOnServiceThread(); 301 for (int i = 0; i < mLocalDevices.size(); ++i) { 302 mLocalDevices.valueAt(i).clearAddress(); 303 } 304 mNativeWrapperImpl.nativeClearLogicalAddress(); 305 } 306 307 @ServiceThreadOnly clearLocalDevices()308 void clearLocalDevices() { 309 assertRunOnServiceThread(); 310 mLocalDevices.clear(); 311 } 312 313 /** 314 * Return the physical address of the device. 315 * 316 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 317 * 318 * @return CEC physical address of the device. The range of success address 319 * is between 0x0000 and 0xFFFF. If failed it returns -1 320 */ 321 @ServiceThreadOnly getPhysicalAddress()322 int getPhysicalAddress() { 323 assertRunOnServiceThread(); 324 return mNativeWrapperImpl.nativeGetPhysicalAddress(); 325 } 326 327 /** 328 * Return CEC version of the device. 329 * 330 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 331 */ 332 @ServiceThreadOnly getVersion()333 int getVersion() { 334 assertRunOnServiceThread(); 335 return mNativeWrapperImpl.nativeGetVersion(); 336 } 337 338 /** 339 * Return vendor id of the device. 340 * 341 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 342 */ 343 @ServiceThreadOnly getVendorId()344 int getVendorId() { 345 assertRunOnServiceThread(); 346 return mNativeWrapperImpl.nativeGetVendorId(); 347 } 348 349 /** 350 * Set an option to CEC HAL. 351 * 352 * @param flag key of option 353 * @param enabled whether to enable/disable the given option. 354 */ 355 @ServiceThreadOnly setOption(int flag, boolean enabled)356 void setOption(int flag, boolean enabled) { 357 assertRunOnServiceThread(); 358 HdmiLogger.debug("setOption: [flag:%d, enabled:%b]", flag, enabled); 359 mNativeWrapperImpl.nativeSetOption(flag, enabled); 360 } 361 362 /** 363 * Informs CEC HAL about the current system language. 364 * 365 * @param language Three-letter code defined in ISO/FDIS 639-2. Must be lowercase letters. 366 */ 367 @ServiceThreadOnly setLanguage(String language)368 void setLanguage(String language) { 369 assertRunOnServiceThread(); 370 if (!LanguageTag.isLanguage(language)) { 371 return; 372 } 373 mNativeWrapperImpl.nativeSetLanguage(language); 374 } 375 376 /** 377 * Configure ARC circuit in the hardware logic to start or stop the feature. 378 * 379 * @param port ID of HDMI port to which AVR is connected 380 * @param enabled whether to enable/disable ARC 381 */ 382 @ServiceThreadOnly enableAudioReturnChannel(int port, boolean enabled)383 void enableAudioReturnChannel(int port, boolean enabled) { 384 assertRunOnServiceThread(); 385 mNativeWrapperImpl.nativeEnableAudioReturnChannel(port, enabled); 386 } 387 388 /** 389 * Return the connection status of the specified port 390 * 391 * @param port port number to check connection status 392 * @return true if connected; otherwise, return false 393 */ 394 @ServiceThreadOnly isConnected(int port)395 boolean isConnected(int port) { 396 assertRunOnServiceThread(); 397 return mNativeWrapperImpl.nativeIsConnected(port); 398 } 399 400 /** 401 * Poll all remote devices. It sends <Polling Message> to all remote 402 * devices. 403 * 404 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 405 * 406 * @param callback an interface used to get a list of all remote devices' address 407 * @param sourceAddress a logical address of source device where sends polling message 408 * @param pickStrategy strategy how to pick polling candidates 409 * @param retryCount the number of retry used to send polling message to remote devices 410 */ 411 @ServiceThreadOnly pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy, int retryCount)412 void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy, 413 int retryCount) { 414 assertRunOnServiceThread(); 415 416 // Extract polling candidates. No need to poll against local devices. 417 List<Integer> pollingCandidates = pickPollCandidates(pickStrategy); 418 ArrayList<Integer> allocated = new ArrayList<>(); 419 runDevicePolling(sourceAddress, pollingCandidates, retryCount, callback, allocated); 420 } 421 422 /** 423 * Return a list of all {@link HdmiCecLocalDevice}s. 424 * 425 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 426 */ 427 @ServiceThreadOnly getLocalDeviceList()428 List<HdmiCecLocalDevice> getLocalDeviceList() { 429 assertRunOnServiceThread(); 430 return HdmiUtils.sparseArrayToList(mLocalDevices); 431 } 432 pickPollCandidates(int pickStrategy)433 private List<Integer> pickPollCandidates(int pickStrategy) { 434 int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK; 435 Predicate<Integer> pickPredicate = null; 436 switch (strategy) { 437 case Constants.POLL_STRATEGY_SYSTEM_AUDIO: 438 pickPredicate = mSystemAudioAddressPredicate; 439 break; 440 case Constants.POLL_STRATEGY_REMOTES_DEVICES: 441 default: // The default is POLL_STRATEGY_REMOTES_DEVICES. 442 pickPredicate = mRemoteDeviceAddressPredicate; 443 break; 444 } 445 446 int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK; 447 LinkedList<Integer> pollingCandidates = new LinkedList<>(); 448 switch (iterationStrategy) { 449 case Constants.POLL_ITERATION_IN_ORDER: 450 for (int i = Constants.ADDR_TV; i <= Constants.ADDR_SPECIFIC_USE; ++i) { 451 if (pickPredicate.test(i)) { 452 pollingCandidates.add(i); 453 } 454 } 455 break; 456 case Constants.POLL_ITERATION_REVERSE_ORDER: 457 default: // The default is reverse order. 458 for (int i = Constants.ADDR_SPECIFIC_USE; i >= Constants.ADDR_TV; --i) { 459 if (pickPredicate.test(i)) { 460 pollingCandidates.add(i); 461 } 462 } 463 break; 464 } 465 return pollingCandidates; 466 } 467 468 @ServiceThreadOnly isAllocatedLocalDeviceAddress(int address)469 private boolean isAllocatedLocalDeviceAddress(int address) { 470 assertRunOnServiceThread(); 471 for (int i = 0; i < mLocalDevices.size(); ++i) { 472 if (mLocalDevices.valueAt(i).isAddressOf(address)) { 473 return true; 474 } 475 } 476 return false; 477 } 478 479 @ServiceThreadOnly runDevicePolling(final int sourceAddress, final List<Integer> candidates, final int retryCount, final DevicePollingCallback callback, final List<Integer> allocated)480 private void runDevicePolling(final int sourceAddress, 481 final List<Integer> candidates, final int retryCount, 482 final DevicePollingCallback callback, final List<Integer> allocated) { 483 assertRunOnServiceThread(); 484 if (candidates.isEmpty()) { 485 if (callback != null) { 486 HdmiLogger.debug("[P]:AllocatedAddress=%s", allocated.toString()); 487 callback.onPollingFinished(allocated); 488 } 489 return; 490 } 491 492 final Integer candidate = candidates.remove(0); 493 // Proceed polling action for the next address once polling action for the 494 // previous address is done. 495 runOnIoThread(new Runnable() { 496 @Override 497 public void run() { 498 if (sendPollMessage(sourceAddress, candidate, retryCount)) { 499 allocated.add(candidate); 500 } 501 runOnServiceThread(new Runnable() { 502 @Override 503 public void run() { 504 runDevicePolling(sourceAddress, candidates, retryCount, callback, 505 allocated); 506 } 507 }); 508 } 509 }); 510 } 511 512 @IoThreadOnly sendPollMessage(int sourceAddress, int destinationAddress, int retryCount)513 private boolean sendPollMessage(int sourceAddress, int destinationAddress, int retryCount) { 514 assertRunOnIoThread(); 515 for (int i = 0; i < retryCount; ++i) { 516 // <Polling Message> is a message which has empty body. 517 int ret = 518 mNativeWrapperImpl.nativeSendCecCommand( 519 sourceAddress, destinationAddress, EMPTY_BODY); 520 if (ret == SendMessageResult.SUCCESS) { 521 return true; 522 } else if (ret != SendMessageResult.NACK) { 523 // Unusual failure 524 HdmiLogger.warning("Failed to send a polling message(%d->%d) with return code %d", 525 sourceAddress, destinationAddress, ret); 526 } 527 } 528 return false; 529 } 530 assertRunOnIoThread()531 private void assertRunOnIoThread() { 532 if (Looper.myLooper() != mIoHandler.getLooper()) { 533 throw new IllegalStateException("Should run on io thread."); 534 } 535 } 536 assertRunOnServiceThread()537 private void assertRunOnServiceThread() { 538 if (Looper.myLooper() != mControlHandler.getLooper()) { 539 throw new IllegalStateException("Should run on service thread."); 540 } 541 } 542 543 // Run a Runnable on IO thread. 544 // It should be careful to access member variables on IO thread because 545 // it can be accessed from system thread as well. runOnIoThread(Runnable runnable)546 private void runOnIoThread(Runnable runnable) { 547 mIoHandler.post(runnable); 548 } 549 runOnServiceThread(Runnable runnable)550 private void runOnServiceThread(Runnable runnable) { 551 mControlHandler.post(runnable); 552 } 553 554 @ServiceThreadOnly flush(final Runnable runnable)555 void flush(final Runnable runnable) { 556 assertRunOnServiceThread(); 557 runOnIoThread(new Runnable() { 558 @Override 559 public void run() { 560 // This ensures the runnable for cleanup is performed after all the pending 561 // commands are processed by IO thread. 562 runOnServiceThread(runnable); 563 } 564 }); 565 } 566 isAcceptableAddress(int address)567 private boolean isAcceptableAddress(int address) { 568 // Can access command targeting devices available in local device or broadcast command. 569 if (address == Constants.ADDR_BROADCAST) { 570 return true; 571 } 572 return isAllocatedLocalDeviceAddress(address); 573 } 574 575 @ServiceThreadOnly onReceiveCommand(HdmiCecMessage message)576 private void onReceiveCommand(HdmiCecMessage message) { 577 assertRunOnServiceThread(); 578 if (isAcceptableAddress(message.getDestination()) && mService.handleCecCommand(message)) { 579 return; 580 } 581 // Not handled message, so we will reply it with <Feature Abort>. 582 maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE); 583 } 584 585 @ServiceThreadOnly maySendFeatureAbortCommand(HdmiCecMessage message, int reason)586 void maySendFeatureAbortCommand(HdmiCecMessage message, int reason) { 587 assertRunOnServiceThread(); 588 // Swap the source and the destination. 589 int src = message.getDestination(); 590 int dest = message.getSource(); 591 if (src == Constants.ADDR_BROADCAST || dest == Constants.ADDR_UNREGISTERED) { 592 // Don't reply <Feature Abort> from the unregistered devices or for the broadcasted 593 // messages. See CEC 12.2 Protocol General Rules for detail. 594 return; 595 } 596 int originalOpcode = message.getOpcode(); 597 if (originalOpcode == Constants.MESSAGE_FEATURE_ABORT) { 598 return; 599 } 600 sendCommand( 601 HdmiCecMessageBuilder.buildFeatureAbortCommand(src, dest, originalOpcode, reason)); 602 } 603 604 @ServiceThreadOnly sendCommand(HdmiCecMessage cecMessage)605 void sendCommand(HdmiCecMessage cecMessage) { 606 assertRunOnServiceThread(); 607 sendCommand(cecMessage, null); 608 } 609 610 @ServiceThreadOnly sendCommand(final HdmiCecMessage cecMessage, final HdmiControlService.SendMessageCallback callback)611 void sendCommand(final HdmiCecMessage cecMessage, 612 final HdmiControlService.SendMessageCallback callback) { 613 assertRunOnServiceThread(); 614 addMessageToHistory(false /* isReceived */, cecMessage); 615 runOnIoThread(new Runnable() { 616 @Override 617 public void run() { 618 HdmiLogger.debug("[S]:" + cecMessage); 619 byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams()); 620 int i = 0; 621 int errorCode = SendMessageResult.SUCCESS; 622 do { 623 errorCode = mNativeWrapperImpl.nativeSendCecCommand( 624 cecMessage.getSource(), cecMessage.getDestination(), body); 625 if (errorCode == SendMessageResult.SUCCESS) { 626 break; 627 } 628 } while (i++ < HdmiConfig.RETRANSMISSION_COUNT); 629 630 final int finalError = errorCode; 631 if (finalError != SendMessageResult.SUCCESS) { 632 Slog.w(TAG, "Failed to send " + cecMessage + " with errorCode=" + finalError); 633 } 634 if (callback != null) { 635 runOnServiceThread(new Runnable() { 636 @Override 637 public void run() { 638 callback.onSendCompleted(finalError); 639 } 640 }); 641 } 642 } 643 }); 644 } 645 646 /** 647 * Called when incoming CEC message arrived. 648 */ 649 @ServiceThreadOnly handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body)650 private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) { 651 assertRunOnServiceThread(); 652 HdmiCecMessage command = HdmiCecMessageBuilder.of(srcAddress, dstAddress, body); 653 HdmiLogger.debug("[R]:" + command); 654 addMessageToHistory(true /* isReceived */, command); 655 onReceiveCommand(command); 656 } 657 658 /** 659 * Called when a hotplug event issues. 660 */ 661 @ServiceThreadOnly handleHotplug(int port, boolean connected)662 private void handleHotplug(int port, boolean connected) { 663 assertRunOnServiceThread(); 664 HdmiLogger.debug("Hotplug event:[port:%d, connected:%b]", port, connected); 665 mService.onHotplug(port, connected); 666 } 667 668 @ServiceThreadOnly addMessageToHistory(boolean isReceived, HdmiCecMessage message)669 private void addMessageToHistory(boolean isReceived, HdmiCecMessage message) { 670 assertRunOnServiceThread(); 671 MessageHistoryRecord record = new MessageHistoryRecord(isReceived, message); 672 if (!mMessageHistory.offer(record)) { 673 mMessageHistory.poll(); 674 mMessageHistory.offer(record); 675 } 676 } 677 dump(final IndentingPrintWriter pw)678 void dump(final IndentingPrintWriter pw) { 679 for (int i = 0; i < mLocalDevices.size(); ++i) { 680 pw.println("HdmiCecLocalDevice #" + mLocalDevices.keyAt(i) + ":"); 681 pw.increaseIndent(); 682 mLocalDevices.valueAt(i).dump(pw); 683 pw.decreaseIndent(); 684 } 685 pw.println("CEC message history:"); 686 pw.increaseIndent(); 687 final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 688 for (MessageHistoryRecord record : mMessageHistory) { 689 record.dump(pw, sdf); 690 } 691 pw.decreaseIndent(); 692 } 693 694 protected interface NativeWrapper { nativeInit()695 String nativeInit(); setCallback(HdmiCecCallback callback)696 void setCallback(HdmiCecCallback callback); nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body)697 int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body); nativeAddLogicalAddress(int logicalAddress)698 int nativeAddLogicalAddress(int logicalAddress); nativeClearLogicalAddress()699 void nativeClearLogicalAddress(); nativeGetPhysicalAddress()700 int nativeGetPhysicalAddress(); nativeGetVersion()701 int nativeGetVersion(); nativeGetVendorId()702 int nativeGetVendorId(); nativeGetPortInfos()703 HdmiPortInfo[] nativeGetPortInfos(); nativeSetOption(int flag, boolean enabled)704 void nativeSetOption(int flag, boolean enabled); nativeSetLanguage(String language)705 void nativeSetLanguage(String language); nativeEnableAudioReturnChannel(int port, boolean flag)706 void nativeEnableAudioReturnChannel(int port, boolean flag); nativeIsConnected(int port)707 boolean nativeIsConnected(int port); 708 } 709 710 private static final class NativeWrapperImpl implements NativeWrapper, 711 IHwBinder.DeathRecipient, getPhysicalAddressCallback { 712 private IHdmiCec mHdmiCec; 713 private final Object mLock = new Object(); 714 private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS; 715 716 @Override nativeInit()717 public String nativeInit() { 718 return (connectToHal() ? mHdmiCec.toString() : null); 719 } 720 connectToHal()721 boolean connectToHal() { 722 try { 723 mHdmiCec = IHdmiCec.getService(); 724 try { 725 mHdmiCec.linkToDeath(this, HDMI_CEC_HAL_DEATH_COOKIE); 726 } catch (RemoteException e) { 727 HdmiLogger.error("Couldn't link to death : ", e); 728 } 729 } catch (RemoteException e) { 730 HdmiLogger.error("Couldn't get tv.cec service : ", e); 731 return false; 732 } 733 return true; 734 } 735 736 @Override setCallback(HdmiCecCallback callback)737 public void setCallback(HdmiCecCallback callback) { 738 try { 739 mHdmiCec.setCallback(callback); 740 } catch (RemoteException e) { 741 HdmiLogger.error("Couldn't initialise tv.cec callback : ", e); 742 } 743 } 744 745 @Override nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body)746 public int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body) { 747 CecMessage message = new CecMessage(); 748 message.initiator = srcAddress; 749 message.destination = dstAddress; 750 message.body = new ArrayList<>(body.length); 751 for (byte b : body) { 752 message.body.add(b); 753 } 754 try { 755 return mHdmiCec.sendMessage(message); 756 } catch (RemoteException e) { 757 HdmiLogger.error("Failed to send CEC message : ", e); 758 return SendMessageResult.FAIL; 759 } 760 } 761 762 @Override nativeAddLogicalAddress(int logicalAddress)763 public int nativeAddLogicalAddress(int logicalAddress) { 764 try { 765 return mHdmiCec.addLogicalAddress(logicalAddress); 766 } catch (RemoteException e) { 767 HdmiLogger.error("Failed to add a logical address : ", e); 768 return Result.FAILURE_INVALID_ARGS; 769 } 770 } 771 772 @Override nativeClearLogicalAddress()773 public void nativeClearLogicalAddress() { 774 try { 775 mHdmiCec.clearLogicalAddress(); 776 } catch (RemoteException e) { 777 HdmiLogger.error("Failed to clear logical address : ", e); 778 } 779 } 780 781 @Override nativeGetPhysicalAddress()782 public int nativeGetPhysicalAddress() { 783 try { 784 mHdmiCec.getPhysicalAddress(this); 785 return mPhysicalAddress; 786 } catch (RemoteException e) { 787 HdmiLogger.error("Failed to get physical address : ", e); 788 return INVALID_PHYSICAL_ADDRESS; 789 } 790 } 791 792 @Override nativeGetVersion()793 public int nativeGetVersion() { 794 try { 795 return mHdmiCec.getCecVersion(); 796 } catch (RemoteException e) { 797 HdmiLogger.error("Failed to get cec version : ", e); 798 return Result.FAILURE_UNKNOWN; 799 } 800 } 801 802 @Override nativeGetVendorId()803 public int nativeGetVendorId() { 804 try { 805 return mHdmiCec.getVendorId(); 806 } catch (RemoteException e) { 807 HdmiLogger.error("Failed to get vendor id : ", e); 808 return Result.FAILURE_UNKNOWN; 809 } 810 } 811 812 @Override nativeGetPortInfos()813 public HdmiPortInfo[] nativeGetPortInfos() { 814 try { 815 ArrayList<android.hardware.tv.cec.V1_0.HdmiPortInfo> hdmiPortInfos = 816 mHdmiCec.getPortInfo(); 817 HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[hdmiPortInfos.size()]; 818 int i = 0; 819 for (android.hardware.tv.cec.V1_0.HdmiPortInfo portInfo : hdmiPortInfos) { 820 hdmiPortInfo[i] = new HdmiPortInfo(portInfo.portId, 821 portInfo.type, 822 portInfo.physicalAddress, 823 portInfo.cecSupported, 824 false, 825 portInfo.arcSupported); 826 i++; 827 } 828 return hdmiPortInfo; 829 } catch (RemoteException e) { 830 HdmiLogger.error("Failed to get port information : ", e); 831 return null; 832 } 833 } 834 835 @Override nativeSetOption(int flag, boolean enabled)836 public void nativeSetOption(int flag, boolean enabled) { 837 try { 838 mHdmiCec.setOption(flag, enabled); 839 } catch (RemoteException e) { 840 HdmiLogger.error("Failed to set option : ", e); 841 } 842 } 843 844 @Override nativeSetLanguage(String language)845 public void nativeSetLanguage(String language) { 846 try { 847 mHdmiCec.setLanguage(language); 848 } catch (RemoteException e) { 849 HdmiLogger.error("Failed to set language : ", e); 850 } 851 } 852 853 @Override nativeEnableAudioReturnChannel(int port, boolean flag)854 public void nativeEnableAudioReturnChannel(int port, boolean flag) { 855 try { 856 mHdmiCec.enableAudioReturnChannel(port, flag); 857 } catch (RemoteException e) { 858 HdmiLogger.error("Failed to enable/disable ARC : ", e); 859 } 860 } 861 862 @Override nativeIsConnected(int port)863 public boolean nativeIsConnected(int port) { 864 try { 865 return mHdmiCec.isConnected(port); 866 } catch (RemoteException e) { 867 HdmiLogger.error("Failed to get connection info : ", e); 868 return false; 869 } 870 } 871 872 @Override serviceDied(long cookie)873 public void serviceDied(long cookie) { 874 if (cookie == HDMI_CEC_HAL_DEATH_COOKIE) { 875 HdmiLogger.error(TAG, "Service died cokkie : " + cookie + "; reconnecting"); 876 connectToHal(); 877 } 878 } 879 880 @Override onValues(int result, short addr)881 public void onValues(int result, short addr) { 882 if (result == Result.SUCCESS) { 883 synchronized (mLock) { 884 mPhysicalAddress = new Short(addr).intValue(); 885 } 886 } 887 } 888 } 889 890 final class HdmiCecCallback extends IHdmiCecCallback.Stub { 891 @Override onCecMessage(CecMessage message)892 public void onCecMessage(CecMessage message) throws RemoteException { 893 byte[] body = new byte[message.body.size()]; 894 for (int i = 0; i < message.body.size(); i++) { 895 body[i] = message.body.get(i); 896 } 897 runOnServiceThread( 898 () -> handleIncomingCecCommand(message.initiator, message.destination, body)); 899 } 900 901 @Override onHotplugEvent(HotplugEvent event)902 public void onHotplugEvent(HotplugEvent event) throws RemoteException { 903 runOnServiceThread(() -> handleHotplug(event.portId, event.connected)); 904 } 905 } 906 907 private final class MessageHistoryRecord { 908 private final long mTime; 909 private final boolean mIsReceived; // true if received message and false if sent message 910 private final HdmiCecMessage mMessage; 911 MessageHistoryRecord(boolean isReceived, HdmiCecMessage message)912 public MessageHistoryRecord(boolean isReceived, HdmiCecMessage message) { 913 mTime = System.currentTimeMillis(); 914 mIsReceived = isReceived; 915 mMessage = message; 916 } 917 dump(final IndentingPrintWriter pw, SimpleDateFormat sdf)918 void dump(final IndentingPrintWriter pw, SimpleDateFormat sdf) { 919 pw.print(mIsReceived ? "[R]" : "[S]"); 920 pw.print(" time="); 921 pw.print(sdf.format(new Date(mTime))); 922 pw.print(" message="); 923 pw.println(mMessage); 924 } 925 } 926 } 927