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