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