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