HdmiCecController.java revision 67ea521d14f366fe5aac09e512865d31bfa0ee53
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.Message; 25import android.util.Slog; 26import android.util.SparseArray; 27import android.util.SparseIntArray; 28 29import libcore.util.EmptyArray; 30 31import java.util.ArrayList; 32import java.util.Arrays; 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 can be created only by {@link HdmiCecController#create} 41 * 42 * <p>Declared as package-private, accessed by {@link HdmiControlService} only. 43 */ 44final class HdmiCecController { 45 private static final String TAG = "HdmiCecController"; 46 47 private static final byte[] EMPTY_BODY = EmptyArray.BYTE; 48 49 // A message to pass cec send command to IO looper. 50 private static final int MSG_SEND_CEC_COMMAND = 1; 51 // A message to delegate logical allocation to IO looper. 52 private static final int MSG_ALLOCATE_LOGICAL_ADDRESS = 2; 53 54 // Message types to handle incoming message in main service looper. 55 private final static int MSG_RECEIVE_CEC_COMMAND = 1; 56 // A message to report allocated logical address to main control looper. 57 private final static int MSG_REPORT_LOGICAL_ADDRESS = 2; 58 59 private static final int NUM_LOGICAL_ADDRESS = 16; 60 61 // TODO: define other constants for errors. 62 private static final int ERROR_SUCCESS = 0; 63 64 // Handler instance to process synchronous I/O (mainly send) message. 65 private Handler mIoHandler; 66 67 // Handler instance to process various messages coming from other CEC 68 // device or issued by internal state change. 69 private Handler mControlHandler; 70 71 // Stores the pointer to the native implementation of the service that 72 // interacts with HAL. 73 private long mNativePtr; 74 75 private HdmiControlService mService; 76 77 // Map-like container of all cec devices. A logical address of device is 78 // used as key of container. 79 private final SparseArray<HdmiCecDeviceInfo> mDeviceInfos = 80 new SparseArray<HdmiCecDeviceInfo>(); 81 // Set-like container for all local devices' logical address. 82 // Key and value are same. 83 private final SparseIntArray mLocalAddresses = new SparseIntArray(); 84 85 // Private constructor. Use HdmiCecController.create(). 86 private HdmiCecController() { 87 // TODO: Consider restoring the local device addresses from persistent storage 88 // to allocate the same addresses again if possible. 89 } 90 91 /** 92 * A factory method to get {@link HdmiCecController}. If it fails to initialize 93 * inner device or has no device it will return {@code null}. 94 * 95 * <p>Declared as package-private, accessed by {@link HdmiControlService} only. 96 * @param service {@link HdmiControlService} instance used to create internal handler 97 * and to pass callback for incoming message or event. 98 * @return {@link HdmiCecController} if device is initialized successfully. Otherwise, 99 * returns {@code null}. 100 */ 101 static HdmiCecController create(HdmiControlService service) { 102 HdmiCecController handler = new HdmiCecController(); 103 long nativePtr = nativeInit(handler); 104 if (nativePtr == 0L) { 105 handler = null; 106 return null; 107 } 108 109 handler.init(service, nativePtr); 110 return handler; 111 } 112 113 /** 114 * Initialize {@link #mLocalAddresses} by allocating logical addresses for each hosted type. 115 * 116 * @param deviceTypes local device types 117 */ 118 void initializeLocalDevices(int[] deviceTypes) { 119 for (int deviceType : deviceTypes) { 120 int preferred = getPreferredAddress(deviceType); 121 allocateLogicalAddress(deviceType, preferred, new AllocateLogicalAddressCallback() { 122 @Override 123 public void onAllocated(int deviceType, int logicalAddress) { 124 addLogicalAddress(logicalAddress); 125 } 126 }); 127 } 128 } 129 130 /** 131 * Get the preferred address for a given type. 132 * 133 * @param deviceType logical device type to get the address for 134 * @return preferred address; {@link HdmiCec#ADDR_UNREGISTERED} if not available. 135 */ 136 private int getPreferredAddress(int deviceType) { 137 // Uses the data restored from persistent memory at boot up if they are available. 138 // Otherwise we return UNREGISTERED indicating there is no preferred address. 139 // Note that for address SPECIFIC_USE(14), HdmiCec.getTypeFromAddress() returns DEVICE_TV, 140 // meaning that we do not support device type video processor yet. 141 for (int i = 0; i < mLocalAddresses.size(); ++i) { 142 int address = mLocalAddresses.keyAt(i); 143 int type = HdmiCec.getTypeFromAddress(address); 144 if (type == deviceType) { 145 return address; 146 } 147 } 148 return HdmiCec.ADDR_UNREGISTERED; 149 } 150 151 /** 152 * Interface to report allocated logical address. 153 */ 154 interface AllocateLogicalAddressCallback { 155 /** 156 * Called when a new logical address is allocated. 157 * 158 * @param deviceType requested device type to allocate logical address 159 * @param logicalAddress allocated logical address. If it is 160 * {@link HdmiCec#ADDR_UNREGISTERED}, it means that 161 * it failed to allocate logical address for the given device type 162 */ 163 void onAllocated(int deviceType, int logicalAddress); 164 } 165 166 /** 167 * Allocate a new logical address of the given device type. Allocated 168 * address will be reported through {@link AllocateLogicalAddressCallback}. 169 * 170 * <p> Declared as package-private, accessed by {@link HdmiControlService} only. 171 * 172 * @param deviceType type of device to used to determine logical address 173 * @param preferredAddress a logical address preferred to be allocated. 174 * If sets {@link HdmiCec#ADDR_UNREGISTERED}, scans 175 * the smallest logical address matched with the given device type. 176 * Otherwise, scan address will start from {@code preferredAddress} 177 * @param callback callback interface to report allocated logical address to caller 178 */ 179 void allocateLogicalAddress(int deviceType, int preferredAddress, 180 AllocateLogicalAddressCallback callback) { 181 Message msg = mIoHandler.obtainMessage(MSG_ALLOCATE_LOGICAL_ADDRESS); 182 msg.arg1 = deviceType; 183 msg.arg2 = preferredAddress; 184 msg.obj = callback; 185 mIoHandler.sendMessage(msg); 186 } 187 188 private static byte[] buildBody(int opcode, byte[] params) { 189 byte[] body = new byte[params.length + 1]; 190 body[0] = (byte) opcode; 191 System.arraycopy(params, 0, body, 1, params.length); 192 return body; 193 } 194 195 private final class IoHandler extends Handler { 196 private IoHandler(Looper looper) { 197 super(looper); 198 } 199 200 @Override 201 public void handleMessage(Message msg) { 202 switch (msg.what) { 203 case MSG_SEND_CEC_COMMAND: 204 HdmiCecMessage cecMessage = (HdmiCecMessage) msg.obj; 205 byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams()); 206 nativeSendCecCommand(mNativePtr, cecMessage.getSource(), 207 cecMessage.getDestination(), body); 208 break; 209 case MSG_ALLOCATE_LOGICAL_ADDRESS: 210 int deviceType = msg.arg1; 211 int preferredAddress = msg.arg2; 212 AllocateLogicalAddressCallback callback = 213 (AllocateLogicalAddressCallback) msg.obj; 214 handleAllocateLogicalAddress(deviceType, preferredAddress, callback); 215 break; 216 default: 217 Slog.w(TAG, "Unsupported CEC Io request:" + msg.what); 218 break; 219 } 220 } 221 222 private void handleAllocateLogicalAddress(int deviceType, int preferredAddress, 223 AllocateLogicalAddressCallback callback) { 224 int startAddress = preferredAddress; 225 // If preferred address is "unregistered", start_index will be the smallest 226 // address matched with the given device type. 227 if (preferredAddress == HdmiCec.ADDR_UNREGISTERED) { 228 for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) { 229 if (deviceType == HdmiCec.getTypeFromAddress(i)) { 230 startAddress = i; 231 break; 232 } 233 } 234 } 235 236 int logcialAddress = HdmiCec.ADDR_UNREGISTERED; 237 // Iterates all possible addresses which has the same device type. 238 for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) { 239 int curAddress = (startAddress + i) % NUM_LOGICAL_ADDRESS; 240 if (curAddress != HdmiCec.ADDR_UNREGISTERED 241 && deviceType == HdmiCec.getTypeFromAddress(i)) { 242 // <Polling Message> is a message which has empty body and 243 // uses same address for both source and destination address. 244 // If sending <Polling Message> failed (NAK), it becomes 245 // new logical address for the device because no device uses 246 // it as logical address of the device. 247 int error = nativeSendCecCommand(mNativePtr, curAddress, curAddress, 248 EMPTY_BODY); 249 if (error != ERROR_SUCCESS) { 250 logcialAddress = curAddress; 251 break; 252 } 253 } 254 } 255 256 Message msg = mControlHandler.obtainMessage(MSG_REPORT_LOGICAL_ADDRESS); 257 msg.arg1 = deviceType; 258 msg.arg2 = logcialAddress; 259 msg.obj = callback; 260 mControlHandler.sendMessage(msg); 261 } 262 } 263 264 private final class ControlHandler extends Handler { 265 private ControlHandler(Looper looper) { 266 super(looper); 267 } 268 269 @Override 270 public void handleMessage(Message msg) { 271 switch (msg.what) { 272 case MSG_RECEIVE_CEC_COMMAND: 273 // TODO: delegate it to HdmiControl service. 274 onReceiveCommand((HdmiCecMessage) msg.obj); 275 break; 276 case MSG_REPORT_LOGICAL_ADDRESS: 277 int deviceType = msg.arg1; 278 int logicalAddress = msg.arg2; 279 AllocateLogicalAddressCallback callback = 280 (AllocateLogicalAddressCallback) msg.obj; 281 callback.onAllocated(deviceType, logicalAddress); 282 break; 283 default: 284 Slog.i(TAG, "Unsupported message type:" + msg.what); 285 break; 286 } 287 } 288 } 289 290 /** 291 * Add a new {@link HdmiCecDeviceInfo}. It returns old device info which has the same 292 * logical address as new device info's. 293 * 294 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 295 * 296 * @param deviceInfo a new {@link HdmiCecDeviceInfo} to be added. 297 * @return {@code null} if it is new device. Otherwise, returns old {@HdmiCecDeviceInfo} 298 * that has the same logical address as new one has. 299 */ 300 HdmiCecDeviceInfo addDeviceInfo(HdmiCecDeviceInfo deviceInfo) { 301 HdmiCecDeviceInfo oldDeviceInfo = getDeviceInfo(deviceInfo.getLogicalAddress()); 302 if (oldDeviceInfo != null) { 303 removeDeviceInfo(deviceInfo.getLogicalAddress()); 304 } 305 mDeviceInfos.append(deviceInfo.getLogicalAddress(), deviceInfo); 306 return oldDeviceInfo; 307 } 308 309 /** 310 * Remove a device info corresponding to the given {@code logicalAddress}. 311 * It returns removed {@link HdmiCecDeviceInfo} if exists. 312 * 313 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 314 * 315 * @param logicalAddress logical address of device to be removed 316 * @return removed {@link HdmiCecDeviceInfo} it exists. Otherwise, returns {@code null} 317 */ 318 HdmiCecDeviceInfo removeDeviceInfo(int logicalAddress) { 319 HdmiCecDeviceInfo deviceInfo = mDeviceInfos.get(logicalAddress); 320 if (deviceInfo != null) { 321 mDeviceInfos.remove(logicalAddress); 322 } 323 return deviceInfo; 324 } 325 326 /** 327 * Return a list of all {@HdmiCecDeviceInfo}. 328 * 329 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 330 */ 331 List<HdmiCecDeviceInfo> getDeviceInfoList() { 332 List<HdmiCecDeviceInfo> deviceInfoList = new ArrayList<HdmiCecDeviceInfo>( 333 mDeviceInfos.size()); 334 for (int i = 0; i < mDeviceInfos.size(); ++i) { 335 deviceInfoList.add(mDeviceInfos.valueAt(i)); 336 } 337 return deviceInfoList; 338 } 339 340 /** 341 * Return a {@link HdmiCecDeviceInfo} corresponding to the given {@code logicalAddress}. 342 * 343 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 344 * 345 * @param logicalAddress logical address to be retrieved 346 * @return {@link HdmiCecDeviceInfo} matched with the given {@code logicalAddress}. 347 * Returns null if no logical address matched 348 */ 349 HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) { 350 return mDeviceInfos.get(logicalAddress); 351 } 352 353 /** 354 * Add a new logical address to the device. Device's HW should be notified 355 * when a new logical address is assigned to a device, so that it can accept 356 * a command having available destinations. 357 * 358 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 359 * 360 * @param newLogicalAddress a logical address to be added 361 * @return 0 on success. Otherwise, returns negative value 362 */ 363 int addLogicalAddress(int newLogicalAddress) { 364 if (HdmiCec.isValidAddress(newLogicalAddress)) { 365 mLocalAddresses.put(newLogicalAddress, newLogicalAddress); 366 return nativeAddLogicalAddress(mNativePtr, newLogicalAddress); 367 } else { 368 return -1; 369 } 370 } 371 372 /** 373 * Clear all logical addresses registered in the device. 374 * 375 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 376 */ 377 void clearLogicalAddress() { 378 // TODO: consider to backup logical address so that new logical address 379 // allocation can use it as preferred address. 380 mLocalAddresses.clear(); 381 nativeClearLogicalAddress(mNativePtr); 382 } 383 384 /** 385 * Return the physical address of the device. 386 * 387 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 388 * 389 * @return CEC physical address of the device. The range of success address 390 * is between 0x0000 and 0xFFFF. If failed it returns -1 391 */ 392 int getPhysicalAddress() { 393 return nativeGetPhysicalAddress(mNativePtr); 394 } 395 396 /** 397 * Return CEC version of the device. 398 * 399 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 400 */ 401 int getVersion() { 402 return nativeGetVersion(mNativePtr); 403 } 404 405 /** 406 * Return vendor id of the device. 407 * 408 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 409 */ 410 int getVendorId() { 411 return nativeGetVendorId(mNativePtr); 412 } 413 414 private void init(HdmiControlService service, long nativePtr) { 415 mService = service; 416 mIoHandler = new IoHandler(service.getServiceLooper()); 417 mControlHandler = new ControlHandler(service.getServiceLooper()); 418 mNativePtr = nativePtr; 419 } 420 421 private boolean isAcceptableAddress(int address) { 422 // Can access command targeting devices available in local device or 423 // broadcast command. 424 return address == HdmiCec.ADDR_BROADCAST 425 || mLocalAddresses.indexOfKey(address) < 0; 426 } 427 428 private void onReceiveCommand(HdmiCecMessage message) { 429 if (isAcceptableAddress(message.getDestination()) && 430 mService.handleCecCommand(message)) { 431 return; 432 } 433 434 // TODO: Use device's source address for broadcast message. 435 int sourceAddress = message.getDestination() != HdmiCec.ADDR_BROADCAST ? 436 message.getDestination() : 0; 437 // Reply <Feature Abort> to initiator (source) for all requests. 438 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand 439 (sourceAddress, message.getSource(), message.getOpcode(), 440 HdmiCecMessageBuilder.ABORT_REFUSED); 441 sendCommand(cecMessage); 442 443 } 444 445 boolean sendCommand(HdmiCecMessage cecMessage) { 446 Message message = mIoHandler.obtainMessage(MSG_SEND_CEC_COMMAND, cecMessage); 447 return mIoHandler.sendMessage(message); 448 } 449 450 /** 451 * Called by native when incoming CEC message arrived. 452 */ 453 private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) { 454 byte opcode = body[0]; 455 byte params[] = Arrays.copyOfRange(body, 1, body.length); 456 HdmiCecMessage cecMessage = new HdmiCecMessage(srcAddress, dstAddress, opcode, params); 457 458 // Delegate message to main handler so that it handles in main thread. 459 Message message = mControlHandler.obtainMessage( 460 MSG_RECEIVE_CEC_COMMAND, cecMessage); 461 mControlHandler.sendMessage(message); 462 } 463 464 /** 465 * Called by native when a hotplug event issues. 466 */ 467 private void handleHotplug(boolean connected) { 468 // TODO: once add port number to cec HAL interface, pass port number 469 // to the service. 470 mService.onHotplug(0, connected); 471 } 472 473 private static native long nativeInit(HdmiCecController handler); 474 private static native int nativeSendCecCommand(long controllerPtr, int srcAddress, 475 int dstAddress, byte[] body); 476 private static native int nativeAddLogicalAddress(long controllerPtr, int logicalAddress); 477 private static native void nativeClearLogicalAddress(long controllerPtr); 478 private static native int nativeGetPhysicalAddress(long controllerPtr); 479 private static native int nativeGetVersion(long controllerPtr); 480 private static native int nativeGetVendorId(long controllerPtr); 481} 482