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