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