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