HdmiCecController.java revision 3f74ab0ee0ec27a6be31cdb5a4258f4f25909ba8
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 */ 43class 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 // TODO: move these values to HdmiCec.java once make it internal constant class. 59 // CEC's ABORT reason values. 60 private static final int ABORT_UNRECOGNIZED_MODE = 0; 61 private static final int ABORT_NOT_IN_CORRECT_MODE = 1; 62 private static final int ABORT_CANNOT_PROVIDE_SOURCE = 2; 63 private static final int ABORT_INVALID_OPERAND = 3; 64 private static final int ABORT_REFUSED = 4; 65 private static final int ABORT_UNABLE_TO_DETERMINE = 5; 66 67 private static final int NUM_LOGICAL_ADDRESS = 16; 68 69 // TODO: define other constants for errors. 70 private static final int ERROR_SUCCESS = 0; 71 72 // Handler instance to process synchronous I/O (mainly send) message. 73 private Handler mIoHandler; 74 75 // Handler instance to process various messages coming from other CEC 76 // device or issued by internal state change. 77 private Handler mControlHandler; 78 79 // Stores the pointer to the native implementation of the service that 80 // interacts with HAL. 81 private long mNativePtr; 82 83 // Map-like container of all cec devices. A logical address of device is 84 // used as key of container. 85 private final SparseArray<HdmiCecDeviceInfo> mDeviceInfos = 86 new SparseArray<HdmiCecDeviceInfo>(); 87 88 // Private constructor. Use HdmiCecController.create(). 89 private HdmiCecController() { 90 } 91 92 /** 93 * A factory method to get {@link HdmiCecController}. If it fails to initialize 94 * inner device or has no device it will return {@code null}. 95 * 96 * <p>Declared as package-private, accessed by {@link HdmiControlService} only. 97 * @param service {@link HdmiControlService} instance used to create internal handler 98 * and to pass callback for incoming message or event. 99 * @return {@link HdmiCecController} if device is initialized successfully. Otherwise, 100 * returns {@code null}. 101 */ 102 static HdmiCecController create(HdmiControlService service) { 103 HdmiCecController handler = new HdmiCecController(); 104 long nativePtr = nativeInit(handler); 105 if (nativePtr == 0L) { 106 handler = null; 107 return null; 108 } 109 110 handler.init(service, nativePtr); 111 return handler; 112 } 113 114 /** 115 * Interface to report allocated logical address. 116 */ 117 interface AllocateLogicalAddressCallback { 118 /** 119 * Called when a new logical address is allocated. 120 * 121 * @param deviceType requested device type to allocate logical address 122 * @param logicalAddress allocated logical address. If it is 123 * {@link HdmiCec#ADDR_UNREGISTERED}, it means that 124 * it failed to allocate logical address for the given device type 125 */ 126 void onAllocated(int deviceType, int logicalAddress); 127 } 128 129 /** 130 * Allocate a new logical address of the given device type. Allocated 131 * address will be reported through {@link AllocateLogicalAddressCallback}. 132 * 133 * <p> Declared as package-private, accessed by {@link HdmiControlService} only. 134 * 135 * @param deviceType type of device to used to determine logical address 136 * @param preferredAddress a logical address preferred to be allocated. 137 * If sets {@link HdmiCec#ADDR_UNREGISTERED}, scans 138 * the smallest logical address matched with the given device type. 139 * Otherwise, scan address will start from {@code preferredAddress} 140 * @param callback callback interface to report allocated logical address to caller 141 */ 142 void allocateLogicalAddress(int deviceType, int preferredAddress, 143 AllocateLogicalAddressCallback callback) { 144 Message msg = mIoHandler.obtainMessage(MSG_ALLOCATE_LOGICAL_ADDRESS); 145 msg.arg1 = deviceType; 146 msg.arg2 = preferredAddress; 147 msg.obj = callback; 148 mIoHandler.sendMessage(msg); 149 } 150 151 private static byte[] buildBody(int opcode, byte[] params) { 152 byte[] body = new byte[params.length + 1]; 153 body[0] = (byte) opcode; 154 System.arraycopy(params, 0, body, 1, params.length); 155 return body; 156 } 157 158 private final class IoHandler extends Handler { 159 private IoHandler(Looper looper) { 160 super(looper); 161 } 162 163 @Override 164 public void handleMessage(Message msg) { 165 switch (msg.what) { 166 case MSG_SEND_CEC_COMMAND: 167 HdmiCecMessage cecMessage = (HdmiCecMessage) msg.obj; 168 byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams()); 169 nativeSendCecCommand(mNativePtr, cecMessage.getSource(), 170 cecMessage.getDestination(), body); 171 break; 172 case MSG_ALLOCATE_LOGICAL_ADDRESS: 173 int deviceType = msg.arg1; 174 int preferredAddress = msg.arg2; 175 AllocateLogicalAddressCallback callback = 176 (AllocateLogicalAddressCallback) msg.obj; 177 handleAllocateLogicalAddress(deviceType, preferredAddress, callback); 178 break; 179 default: 180 Slog.w(TAG, "Unsupported CEC Io request:" + msg.what); 181 break; 182 } 183 } 184 185 private void handleAllocateLogicalAddress(int deviceType, int preferredAddress, 186 AllocateLogicalAddressCallback callback) { 187 int startAddress = preferredAddress; 188 // If preferred address is "unregistered", start_index will be the smallest 189 // address matched with the given device type. 190 if (preferredAddress == HdmiCec.ADDR_UNREGISTERED) { 191 for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) { 192 if (deviceType == HdmiCec.getTypeFromAddress(i)) { 193 startAddress = i; 194 break; 195 } 196 } 197 } 198 199 int logcialAddress = HdmiCec.ADDR_UNREGISTERED; 200 // Iterates all possible addresses which has the same device type. 201 for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) { 202 int curAddress = (startAddress + i) % NUM_LOGICAL_ADDRESS; 203 if (curAddress != HdmiCec.ADDR_UNREGISTERED 204 && deviceType == HdmiCec.getTypeFromAddress(i)) { 205 // <Polling Message> is a message which has empty body and 206 // uses same address for both source and destination address. 207 // If sending <Polling Message> failed (NAK), it becomes 208 // new logical address for the device because no device uses 209 // it as logical address of the device. 210 int error = nativeSendCecCommand(mNativePtr, curAddress, curAddress, 211 EMPTY_BODY); 212 if (error != ERROR_SUCCESS) { 213 logcialAddress = curAddress; 214 break; 215 } 216 } 217 } 218 219 Message msg = mControlHandler.obtainMessage(MSG_REPORT_LOGICAL_ADDRESS); 220 msg.arg1 = deviceType; 221 msg.arg2 = logcialAddress; 222 msg.obj = callback; 223 mControlHandler.sendMessage(msg); 224 } 225 } 226 227 private final class ControlHandler extends Handler { 228 private ControlHandler(Looper looper) { 229 super(looper); 230 } 231 232 @Override 233 public void handleMessage(Message msg) { 234 switch (msg.what) { 235 case MSG_RECEIVE_CEC_COMMAND: 236 // TODO: delegate it to HdmiControl service. 237 onReceiveCommand((HdmiCecMessage) msg.obj); 238 break; 239 case MSG_REPORT_LOGICAL_ADDRESS: 240 int deviceType = msg.arg1; 241 int logicalAddress = msg.arg2; 242 AllocateLogicalAddressCallback callback = 243 (AllocateLogicalAddressCallback) msg.obj; 244 callback.onAllocated(deviceType, logicalAddress); 245 break; 246 default: 247 Slog.i(TAG, "Unsupported message type:" + msg.what); 248 break; 249 } 250 } 251 } 252 253 /** 254 * Add a new {@link HdmiCecDeviceInfo}. It returns old device info which has the same 255 * logical address as new device info's. 256 * 257 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 258 * 259 * @param deviceInfo a new {@link HdmiCecDeviceInfo} to be added. 260 * @return {@code null} if it is new device. Otherwise, returns old {@HdmiCecDeviceInfo} 261 * that has the same logical address as new one has. 262 */ 263 HdmiCecDeviceInfo addDeviceInfo(HdmiCecDeviceInfo deviceInfo) { 264 HdmiCecDeviceInfo oldDeviceInfo = getDeviceInfo(deviceInfo.getLogicalAddress()); 265 if (oldDeviceInfo != null) { 266 removeDeviceInfo(deviceInfo.getLogicalAddress()); 267 } 268 mDeviceInfos.append(deviceInfo.getLogicalAddress(), deviceInfo); 269 return oldDeviceInfo; 270 } 271 272 /** 273 * Remove a device info corresponding to the given {@code logicalAddress}. 274 * It returns removed {@link HdmiCecDeviceInfo} if exists. 275 * 276 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 277 * 278 * @param logicalAddress logical address of device to be removed 279 * @return removed {@link HdmiCecDeviceInfo} it exists. Otherwise, returns {@code null} 280 */ 281 HdmiCecDeviceInfo removeDeviceInfo(int logicalAddress) { 282 HdmiCecDeviceInfo deviceInfo = mDeviceInfos.get(logicalAddress); 283 if (deviceInfo != null) { 284 mDeviceInfos.remove(logicalAddress); 285 } 286 return deviceInfo; 287 } 288 289 /** 290 * Return a list of all {@HdmiCecDeviceInfo}. 291 * 292 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 293 */ 294 List<HdmiCecDeviceInfo> getDeviceInfoList() { 295 List<HdmiCecDeviceInfo> deviceInfoList = new ArrayList<HdmiCecDeviceInfo>( 296 mDeviceInfos.size()); 297 for (int i = 0; i < mDeviceInfos.size(); ++i) { 298 deviceInfoList.add(mDeviceInfos.valueAt(i)); 299 } 300 return deviceInfoList; 301 } 302 303 /** 304 * Return a {@link HdmiCecDeviceInfo} corresponding to the given {@code logicalAddress}. 305 * 306 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 307 * 308 * @param logicalAddress logical address to be retrieved 309 * @return {@link HdmiCecDeviceInfo} matched with the given {@code logicalAddress}. 310 * Returns null if no logical address matched 311 */ 312 HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) { 313 return mDeviceInfos.get(logicalAddress); 314 } 315 316 private void init(HdmiControlService service, long nativePtr) { 317 mIoHandler = new IoHandler(service.getServiceLooper()); 318 mControlHandler = new ControlHandler(service.getServiceLooper()); 319 mNativePtr = nativePtr; 320 } 321 322 private void onReceiveCommand(HdmiCecMessage message) { 323 // TODO: Handle message according to opcode type. 324 325 // TODO: Use device's source address for broadcast message. 326 int sourceAddress = message.getDestination() != HdmiCec.ADDR_BROADCAST ? 327 message.getDestination() : 0; 328 // Reply <Feature Abort> to initiator (source) for all requests. 329 sendFeatureAbort(sourceAddress, message.getSource(), message.getOpcode(), 330 ABORT_REFUSED); 331 } 332 333 private void sendFeatureAbort(int srcAddress, int destAddress, int originalOpcode, 334 int reason) { 335 byte[] params = new byte[2]; 336 params[0] = (byte) originalOpcode; 337 params[1] = (byte) reason; 338 339 HdmiCecMessage cecMessage = new HdmiCecMessage(srcAddress, destAddress, 340 HdmiCec.MESSAGE_FEATURE_ABORT, params); 341 Message message = mIoHandler.obtainMessage(MSG_SEND_CEC_COMMAND, cecMessage); 342 mIoHandler.sendMessage(message); 343 } 344 345 /** 346 * Called by native when incoming CEC message arrived. 347 */ 348 private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) { 349 byte opcode = body[0]; 350 byte params[] = Arrays.copyOfRange(body, 1, body.length); 351 HdmiCecMessage cecMessage = new HdmiCecMessage(srcAddress, dstAddress, opcode, params); 352 353 // Delegate message to main handler so that it handles in main thread. 354 Message message = mControlHandler.obtainMessage( 355 MSG_RECEIVE_CEC_COMMAND, cecMessage); 356 mControlHandler.sendMessage(message); 357 } 358 359 /** 360 * Called by native when a hotplug event issues. 361 */ 362 private void handleHotplug(boolean connected) { 363 // TODO: Delegate event to main message handler. 364 } 365 366 private static native long nativeInit(HdmiCecController handler); 367 private static native int nativeSendCecCommand(long contollerPtr, int srcAddress, 368 int dstAddress, byte[] body); 369} 370