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