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