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