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