HdmiCecController.java revision b3ecb72af8cbbd4f6f8089d0dc22289f6e2588f6
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.getServiceLooper()); 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 nativeSetOption(mNativePtr, flag, value); 328 } 329 330 /** 331 * Configure ARC circuit in the hardware logic to start or stop the feature. 332 * 333 * @param enabled whether to enable/disable ARC 334 */ 335 @ServiceThreadOnly 336 void setAudioReturnChannel(boolean enabled) { 337 assertRunOnServiceThread(); 338 nativeSetAudioReturnChannel(mNativePtr, enabled); 339 } 340 341 /** 342 * Return the connection status of the specified port 343 * 344 * @param port port number to check connection status 345 * @return true if connected; otherwise, return false 346 */ 347 @ServiceThreadOnly 348 boolean isConnected(int port) { 349 assertRunOnServiceThread(); 350 return nativeIsConnected(mNativePtr, port); 351 } 352 353 /** 354 * Poll all remote devices. It sends <Polling Message> to all remote 355 * devices. 356 * 357 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 358 * 359 * @param callback an interface used to get a list of all remote devices' address 360 * @param sourceAddress a logical address of source device where sends polling message 361 * @param pickStrategy strategy how to pick polling candidates 362 * @param retryCount the number of retry used to send polling message to remote devices 363 */ 364 @ServiceThreadOnly 365 void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy, 366 int retryCount) { 367 assertRunOnServiceThread(); 368 369 // Extract polling candidates. No need to poll against local devices. 370 List<Integer> pollingCandidates = pickPollCandidates(pickStrategy); 371 ArrayList<Integer> allocated = new ArrayList<>(); 372 runDevicePolling(sourceAddress, pollingCandidates, retryCount, callback, allocated); 373 } 374 375 /** 376 * Return a list of all {@link HdmiCecLocalDevice}s. 377 * 378 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 379 */ 380 @ServiceThreadOnly 381 List<HdmiCecLocalDevice> getLocalDeviceList() { 382 assertRunOnServiceThread(); 383 return HdmiUtils.sparseArrayToList(mLocalDevices); 384 } 385 386 private List<Integer> pickPollCandidates(int pickStrategy) { 387 int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK; 388 Predicate<Integer> pickPredicate = null; 389 switch (strategy) { 390 case Constants.POLL_STRATEGY_SYSTEM_AUDIO: 391 pickPredicate = mSystemAudioAddressPredicate; 392 break; 393 case Constants.POLL_STRATEGY_REMOTES_DEVICES: 394 default: // The default is POLL_STRATEGY_REMOTES_DEVICES. 395 pickPredicate = mRemoteDeviceAddressPredicate; 396 break; 397 } 398 399 int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK; 400 LinkedList<Integer> pollingCandidates = new LinkedList<>(); 401 switch (iterationStrategy) { 402 case Constants.POLL_ITERATION_IN_ORDER: 403 for (int i = Constants.ADDR_TV; i <= Constants.ADDR_SPECIFIC_USE; ++i) { 404 if (pickPredicate.apply(i)) { 405 pollingCandidates.add(i); 406 } 407 } 408 break; 409 case Constants.POLL_ITERATION_REVERSE_ORDER: 410 default: // The default is reverse order. 411 for (int i = Constants.ADDR_SPECIFIC_USE; i >= Constants.ADDR_TV; --i) { 412 if (pickPredicate.apply(i)) { 413 pollingCandidates.add(i); 414 } 415 } 416 break; 417 } 418 return pollingCandidates; 419 } 420 421 @ServiceThreadOnly 422 private boolean isAllocatedLocalDeviceAddress(int address) { 423 assertRunOnServiceThread(); 424 for (int i = 0; i < mLocalDevices.size(); ++i) { 425 if (mLocalDevices.valueAt(i).isAddressOf(address)) { 426 return true; 427 } 428 } 429 return false; 430 } 431 432 @ServiceThreadOnly 433 private void runDevicePolling(final int sourceAddress, 434 final List<Integer> candidates, final int retryCount, 435 final DevicePollingCallback callback, final List<Integer> allocated) { 436 assertRunOnServiceThread(); 437 if (candidates.isEmpty()) { 438 if (callback != null) { 439 HdmiLogger.debug("[P]:AllocatedAddress=%s", allocated.toString()); 440 callback.onPollingFinished(allocated); 441 } 442 return; 443 } 444 445 final Integer candidate = candidates.remove(0); 446 // Proceed polling action for the next address once polling action for the 447 // previous address is done. 448 runOnIoThread(new Runnable() { 449 @Override 450 public void run() { 451 if (sendPollMessage(sourceAddress, candidate, retryCount)) { 452 allocated.add(candidate); 453 } 454 runOnServiceThread(new Runnable() { 455 @Override 456 public void run() { 457 runDevicePolling(sourceAddress, candidates, retryCount, callback, 458 allocated); 459 } 460 }); 461 } 462 }); 463 } 464 465 @IoThreadOnly 466 private boolean sendPollMessage(int sourceAddress, int destinationAddress, int retryCount) { 467 assertRunOnIoThread(); 468 for (int i = 0; i < retryCount; ++i) { 469 // <Polling Message> is a message which has empty body. 470 // If sending <Polling Message> failed (NAK), it becomes 471 // new logical address for the device because no device uses 472 // it as logical address of the device. 473 if (nativeSendCecCommand(mNativePtr, sourceAddress, destinationAddress, EMPTY_BODY) 474 == Constants.SEND_RESULT_SUCCESS) { 475 return true; 476 } 477 } 478 return false; 479 } 480 481 private void assertRunOnIoThread() { 482 if (Looper.myLooper() != mIoHandler.getLooper()) { 483 throw new IllegalStateException("Should run on io thread."); 484 } 485 } 486 487 private void assertRunOnServiceThread() { 488 if (Looper.myLooper() != mControlHandler.getLooper()) { 489 throw new IllegalStateException("Should run on service thread."); 490 } 491 } 492 493 // Run a Runnable on IO thread. 494 // It should be careful to access member variables on IO thread because 495 // it can be accessed from system thread as well. 496 private void runOnIoThread(Runnable runnable) { 497 mIoHandler.post(runnable); 498 } 499 500 private void runOnServiceThread(Runnable runnable) { 501 mControlHandler.post(runnable); 502 } 503 504 private boolean isAcceptableAddress(int address) { 505 // Can access command targeting devices available in local device or broadcast command. 506 if (address == Constants.ADDR_BROADCAST) { 507 return true; 508 } 509 return isAllocatedLocalDeviceAddress(address); 510 } 511 512 @ServiceThreadOnly 513 private void onReceiveCommand(HdmiCecMessage message) { 514 assertRunOnServiceThread(); 515 if (isAcceptableAddress(message.getDestination()) && mService.handleCecCommand(message)) { 516 return; 517 } 518 // Not handled message, so we will reply it with <Feature Abort>. 519 maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE); 520 } 521 522 @ServiceThreadOnly 523 void maySendFeatureAbortCommand(HdmiCecMessage message, int reason) { 524 assertRunOnServiceThread(); 525 // Swap the source and the destination. 526 int src = message.getDestination(); 527 int dest = message.getSource(); 528 if (src == Constants.ADDR_BROADCAST || dest == Constants.ADDR_UNREGISTERED) { 529 // Don't reply <Feature Abort> from the unregistered devices or for the broadcasted 530 // messages. See CEC 12.2 Protocol General Rules for detail. 531 return; 532 } 533 int originalOpcode = message.getOpcode(); 534 if (originalOpcode == Constants.MESSAGE_FEATURE_ABORT) { 535 return; 536 } 537 sendCommand( 538 HdmiCecMessageBuilder.buildFeatureAbortCommand(src, dest, originalOpcode, reason)); 539 } 540 541 @ServiceThreadOnly 542 void sendCommand(HdmiCecMessage cecMessage) { 543 assertRunOnServiceThread(); 544 sendCommand(cecMessage, null); 545 } 546 547 @ServiceThreadOnly 548 void sendCommand(final HdmiCecMessage cecMessage, 549 final HdmiControlService.SendMessageCallback callback) { 550 assertRunOnServiceThread(); 551 runOnIoThread(new Runnable() { 552 @Override 553 public void run() { 554 HdmiLogger.debug("[S]:" + cecMessage); 555 byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams()); 556 int i = 0; 557 int errorCode = Constants.SEND_RESULT_SUCCESS; 558 do { 559 errorCode = nativeSendCecCommand(mNativePtr, cecMessage.getSource(), 560 cecMessage.getDestination(), body); 561 if (errorCode == Constants.SEND_RESULT_SUCCESS) { 562 break; 563 } 564 } while (i++ < HdmiConfig.RETRANSMISSION_COUNT); 565 566 final int finalError = errorCode; 567 if (finalError != Constants.SEND_RESULT_SUCCESS) { 568 Slog.w(TAG, "Failed to send " + cecMessage); 569 } 570 if (callback != null) { 571 runOnServiceThread(new Runnable() { 572 @Override 573 public void run() { 574 callback.onSendCompleted(finalError); 575 } 576 }); 577 } 578 } 579 }); 580 } 581 582 /** 583 * Called by native when incoming CEC message arrived. 584 */ 585 @ServiceThreadOnly 586 private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) { 587 assertRunOnServiceThread(); 588 HdmiCecMessage command = HdmiCecMessageBuilder.of(srcAddress, dstAddress, body); 589 HdmiLogger.debug("[R]:" + command); 590 onReceiveCommand(command); 591 } 592 593 /** 594 * Called by native when a hotplug event issues. 595 */ 596 @ServiceThreadOnly 597 private void handleHotplug(int port, boolean connected) { 598 assertRunOnServiceThread(); 599 HdmiLogger.debug("Hotplug event:[port:%d, connected:%b]", port, connected); 600 mService.onHotplug(port, connected); 601 } 602 603 void dump(final IndentingPrintWriter pw) { 604 for (int i = 0; i < mLocalDevices.size(); ++i) { 605 pw.println("HdmiCecLocalDevice #" + i + ":"); 606 pw.increaseIndent(); 607 mLocalDevices.valueAt(i).dump(pw); 608 pw.decreaseIndent(); 609 } 610 } 611 612 private static native long nativeInit(HdmiCecController handler, MessageQueue messageQueue); 613 private static native int nativeSendCecCommand(long controllerPtr, int srcAddress, 614 int dstAddress, byte[] body); 615 private static native int nativeAddLogicalAddress(long controllerPtr, int logicalAddress); 616 private static native void nativeClearLogicalAddress(long controllerPtr); 617 private static native int nativeGetPhysicalAddress(long controllerPtr); 618 private static native int nativeGetVersion(long controllerPtr); 619 private static native int nativeGetVendorId(long controllerPtr); 620 private static native HdmiPortInfo[] nativeGetPortInfos(long controllerPtr); 621 private static native void nativeSetOption(long controllerPtr, int flag, int value); 622 private static native void nativeSetAudioReturnChannel(long controllerPtr, boolean flag); 623 private static native boolean nativeIsConnected(long controllerPtr, int port); 624} 625