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