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