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