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