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