HdmiCecController.java revision 79c58a4b97f27ede6a1b680d2fece9c2a0edf7b7
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.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 HdmiCec#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 HdmiCec.getTypeFromAddress(address) == HdmiCec.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 void addLocalDevice(int deviceType, HdmiCecLocalDevice device) { 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 HdmiCec#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 void allocateLogicalAddress(final int deviceType, final int preferredAddress, 165 final AllocateAddressCallback callback) { 166 assertRunOnServiceThread(); 167 168 runOnIoThread(new Runnable() { 169 @Override 170 public void run() { 171 handleAllocateLogicalAddress(deviceType, preferredAddress, callback); 172 } 173 }); 174 } 175 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 == HdmiCec.ADDR_UNREGISTERED) { 183 for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) { 184 if (deviceType == HdmiCec.getTypeFromAddress(i)) { 185 startAddress = i; 186 break; 187 } 188 } 189 } 190 191 int logicalAddress = HdmiCec.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 != HdmiCec.ADDR_UNREGISTERED 196 && deviceType == HdmiCec.getTypeFromAddress(curAddress)) { 197 if (!sendPollMessage(curAddress, RETRY_COUNT_FOR_LOGICAL_ADDRESS_ALLOCATION)) { 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 int addLogicalAddress(int newLogicalAddress) { 249 assertRunOnServiceThread(); 250 if (HdmiCec.isValidAddress(newLogicalAddress)) { 251 return nativeAddLogicalAddress(mNativePtr, newLogicalAddress); 252 } else { 253 return -1; 254 } 255 } 256 257 /** 258 * Clear all logical addresses registered in the device. 259 * 260 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 261 */ 262 void clearLogicalAddress() { 263 assertRunOnServiceThread(); 264 // TODO: consider to backup logical address so that new logical address 265 // allocation can use it as preferred address. 266 for (int i = 0; i < mLocalDevices.size(); ++i) { 267 mLocalDevices.valueAt(i).clearAddress(); 268 } 269 nativeClearLogicalAddress(mNativePtr); 270 } 271 272 /** 273 * Return the physical address of the device. 274 * 275 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 276 * 277 * @return CEC physical address of the device. The range of success address 278 * is between 0x0000 and 0xFFFF. If failed it returns -1 279 */ 280 int getPhysicalAddress() { 281 assertRunOnServiceThread(); 282 return nativeGetPhysicalAddress(mNativePtr); 283 } 284 285 /** 286 * Return CEC version of the device. 287 * 288 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 289 */ 290 int getVersion() { 291 assertRunOnServiceThread(); 292 return nativeGetVersion(mNativePtr); 293 } 294 295 /** 296 * Return vendor id of the device. 297 * 298 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 299 */ 300 int getVendorId() { 301 assertRunOnServiceThread(); 302 return nativeGetVendorId(mNativePtr); 303 } 304 305 /** 306 * Pass a option to CEC HAL. 307 * 308 * @param flag a key of option. For more details, look at 309 * {@link HdmiConstants#FLAG_HDMI_OPTION_WAKEUP} to 310 * {@link HdmiConstants#FLAG_HDMI_OPTION_SYSTEM_CEC_CONTROL} 311 * @param value a value of option. Actual value varies flag. For more 312 * details, look at description of flags 313 */ 314 void setOption(int flag, int value) { 315 assertRunOnServiceThread(); 316 nativeSetOption(mNativePtr, flag, value); 317 } 318 319 /** 320 * Configure ARC circuit in the hardware logic to start or stop the feature. 321 * 322 * @param enabled whether to enable/disable ARC 323 */ 324 void setAudioReturnChannel(boolean enabled) { 325 assertRunOnServiceThread(); 326 nativeSetAudioReturnChannel(mNativePtr, enabled); 327 } 328 329 /** 330 * Return the connection status of the specified port 331 * 332 * @param port port number to check connection status 333 * @return true if connected; otherwise, return false 334 */ 335 boolean isConnected(int port) { 336 assertRunOnServiceThread(); 337 return nativeIsConnected(mNativePtr, port); 338 } 339 340 /** 341 * Poll all remote devices. It sends <Polling Message> to all remote 342 * devices. 343 * 344 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 345 * 346 * @param callback an interface used to get a list of all remote devices' address 347 * @param pickStrategy strategy how to pick polling candidates 348 * @param retryCount the number of retry used to send polling message to remote devices 349 */ 350 void pollDevices(DevicePollingCallback callback, int pickStrategy, int retryCount) { 351 assertRunOnServiceThread(); 352 353 // Extract polling candidates. No need to poll against local devices. 354 List<Integer> pollingCandidates = pickPollCandidates(pickStrategy); 355 runDevicePolling(pollingCandidates, retryCount, callback); 356 } 357 358 /** 359 * Return a list of all {@link HdmiCecLocalDevice}s. 360 * 361 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 362 */ 363 List<HdmiCecLocalDevice> getLocalDeviceList() { 364 assertRunOnServiceThread(); 365 return HdmiUtils.sparseArrayToList(mLocalDevices); 366 } 367 368 private List<Integer> pickPollCandidates(int pickStrategy) { 369 int strategy = pickStrategy & HdmiControlService.POLL_STRATEGY_MASK; 370 Predicate<Integer> pickPredicate = null; 371 switch (strategy) { 372 case HdmiControlService.POLL_STRATEGY_SYSTEM_AUDIO: 373 pickPredicate = mSystemAudioAddressPredicate; 374 break; 375 case HdmiControlService.POLL_STRATEGY_REMOTES_DEVICES: 376 default: // The default is POLL_STRATEGY_REMOTES_DEVICES. 377 pickPredicate = mRemoteDeviceAddressPredicate; 378 break; 379 } 380 381 int iterationStrategy = pickStrategy & HdmiControlService.POLL_ITERATION_STRATEGY_MASK; 382 ArrayList<Integer> pollingCandidates = new ArrayList<>(); 383 switch (iterationStrategy) { 384 case HdmiControlService.POLL_ITERATION_IN_ORDER: 385 for (int i = HdmiCec.ADDR_TV; i <= HdmiCec.ADDR_SPECIFIC_USE; ++i) { 386 if (pickPredicate.apply(i)) { 387 pollingCandidates.add(i); 388 } 389 } 390 break; 391 case HdmiControlService.POLL_ITERATION_REVERSE_ORDER: 392 default: // The default is reverse order. 393 for (int i = HdmiCec.ADDR_SPECIFIC_USE; i >= HdmiCec.ADDR_TV; --i) { 394 if (pickPredicate.apply(i)) { 395 pollingCandidates.add(i); 396 } 397 } 398 break; 399 } 400 return pollingCandidates; 401 } 402 403 private boolean isAllocatedLocalDeviceAddress(int address) { 404 for (int i = 0; i < mLocalDevices.size(); ++i) { 405 if (mLocalDevices.valueAt(i).isAddressOf(address)) { 406 return true; 407 } 408 } 409 return false; 410 } 411 412 private void runDevicePolling(final List<Integer> candidates, final int retryCount, 413 final DevicePollingCallback callback) { 414 assertRunOnServiceThread(); 415 runOnIoThread(new Runnable() { 416 @Override 417 public void run() { 418 final ArrayList<Integer> allocated = new ArrayList<>(); 419 for (Integer address : candidates) { 420 if (sendPollMessage(address, retryCount)) { 421 allocated.add(address); 422 } 423 } 424 if (callback != null) { 425 runOnServiceThread(new Runnable() { 426 @Override 427 public void run() { 428 callback.onPollingFinished(allocated); 429 } 430 }); 431 } 432 } 433 }); 434 } 435 436 private boolean sendPollMessage(int address, int retryCount) { 437 assertRunOnIoThread(); 438 for (int i = 0; i < retryCount; ++i) { 439 // <Polling Message> is a message which has empty body and 440 // uses same address for both source and destination address. 441 // If sending <Polling Message> failed (NAK), it becomes 442 // new logical address for the device because no device uses 443 // it as logical address of the device. 444 if (nativeSendCecCommand(mNativePtr, address, address, EMPTY_BODY) 445 == HdmiControlService.SEND_RESULT_SUCCESS) { 446 return true; 447 } 448 } 449 return false; 450 } 451 452 private void assertRunOnIoThread() { 453 if (Looper.myLooper() != mIoHandler.getLooper()) { 454 throw new IllegalStateException("Should run on io thread."); 455 } 456 } 457 458 private void assertRunOnServiceThread() { 459 if (Looper.myLooper() != mControlHandler.getLooper()) { 460 throw new IllegalStateException("Should run on service thread."); 461 } 462 } 463 464 // Run a Runnable on IO thread. 465 // It should be careful to access member variables on IO thread because 466 // it can be accessed from system thread as well. 467 private void runOnIoThread(Runnable runnable) { 468 mIoHandler.post(runnable); 469 } 470 471 private void runOnServiceThread(Runnable runnable) { 472 mControlHandler.post(runnable); 473 } 474 475 private boolean isAcceptableAddress(int address) { 476 // Can access command targeting devices available in local device or broadcast command. 477 if (address == HdmiCec.ADDR_BROADCAST) { 478 return true; 479 } 480 return isAllocatedLocalDeviceAddress(address); 481 } 482 483 private void onReceiveCommand(HdmiCecMessage message) { 484 assertRunOnServiceThread(); 485 if (isAcceptableAddress(message.getDestination()) 486 && mService.handleCecCommand(message)) { 487 return; 488 } 489 490 if (message.getDestination() != HdmiCec.ADDR_BROADCAST) { 491 int sourceAddress = message.getDestination(); 492 // Reply <Feature Abort> to initiator (source) for all requests. 493 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand( 494 sourceAddress, message.getSource(), message.getOpcode(), 495 HdmiConstants.ABORT_REFUSED); 496 sendCommand(cecMessage, null); 497 } 498 } 499 500 void sendCommand(HdmiCecMessage cecMessage) { 501 sendCommand(cecMessage, null); 502 } 503 504 void sendCommand(final HdmiCecMessage cecMessage, 505 final HdmiControlService.SendMessageCallback callback) { 506 runOnIoThread(new Runnable() { 507 @Override 508 public void run() { 509 byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams()); 510 final int error = nativeSendCecCommand(mNativePtr, cecMessage.getSource(), 511 cecMessage.getDestination(), body); 512 if (error != HdmiControlService.SEND_RESULT_SUCCESS) { 513 Slog.w(TAG, "Failed to send " + cecMessage); 514 } 515 if (callback != null) { 516 runOnServiceThread(new Runnable() { 517 @Override 518 public void run() { 519 callback.onSendCompleted(error); 520 } 521 }); 522 } 523 } 524 }); 525 } 526 527 /** 528 * Called by native when incoming CEC message arrived. 529 */ 530 private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) { 531 assertRunOnServiceThread(); 532 onReceiveCommand(HdmiCecMessageBuilder.of(srcAddress, dstAddress, body)); 533 } 534 535 /** 536 * Called by native when a hotplug event issues. 537 */ 538 private void handleHotplug(boolean connected) { 539 // TODO: once add port number to cec HAL interface, pass port number 540 // to the service. 541 mService.onHotplug(0, connected); 542 } 543 544 private static native long nativeInit(HdmiCecController handler, MessageQueue messageQueue); 545 private static native int nativeSendCecCommand(long controllerPtr, int srcAddress, 546 int dstAddress, byte[] body); 547 private static native int nativeAddLogicalAddress(long controllerPtr, int logicalAddress); 548 private static native void nativeClearLogicalAddress(long controllerPtr); 549 private static native int nativeGetPhysicalAddress(long controllerPtr); 550 private static native int nativeGetVersion(long controllerPtr); 551 private static native int nativeGetVendorId(long controllerPtr); 552 private static native HdmiPortInfo[] nativeGetPortInfos(long controllerPtr); 553 private static native void nativeSetOption(long controllerPtr, int flag, int value); 554 private static native void nativeSetAudioReturnChannel(long controllerPtr, boolean flag); 555 private static native boolean nativeIsConnected(long controllerPtr, int port); 556} 557