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