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