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