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