blob: c87fc99807f507bf1a62e99783cd177290b7265f [file] [log] [blame]
Jungshik Jang0792d372014-04-23 17:57:26 +09001/*
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
Jungshik Jange9c77c82014-04-24 20:30:09 +090019import android.hardware.hdmi.HdmiCec;
Jungshik Jang7d9a8432014-04-29 15:12:43 +090020import android.hardware.hdmi.HdmiCecDeviceInfo;
Jungshik Jange9c77c82014-04-24 20:30:09 +090021import android.hardware.hdmi.HdmiCecMessage;
Jungshik Jang0792d372014-04-23 17:57:26 +090022import android.os.Handler;
23import android.os.Looper;
24import android.os.Message;
Jungshik Jange9c77c82014-04-24 20:30:09 +090025import android.util.Slog;
Jungshik Jang7d9a8432014-04-29 15:12:43 +090026import android.util.SparseArray;
Jungshik Jange9c77c82014-04-24 20:30:09 +090027
Jungshik Jang3f74ab02014-04-30 14:31:02 +090028import libcore.util.EmptyArray;
29
Jungshik Jang7d9a8432014-04-29 15:12:43 +090030import java.util.ArrayList;
Jungshik Jange9c77c82014-04-24 20:30:09 +090031import java.util.Arrays;
Jungshik Jang7d9a8432014-04-29 15:12:43 +090032import java.util.List;
Jungshik Jang0792d372014-04-23 17:57:26 +090033
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
Jungshik Jang3f74ab02014-04-30 14:31:02 +090046 private static final byte[] EMPTY_BODY = EmptyArray.BYTE;
47
Jungshik Jange9c77c82014-04-24 20:30:09 +090048 // A message to pass cec send command to IO looper.
49 private static final int MSG_SEND_CEC_COMMAND = 1;
Jungshik Jang3f74ab02014-04-30 14:31:02 +090050 // A message to delegate logical allocation to IO looper.
51 private static final int MSG_ALLOCATE_LOGICAL_ADDRESS = 2;
Jungshik Jange9c77c82014-04-24 20:30:09 +090052
53 // Message types to handle incoming message in main service looper.
54 private final static int MSG_RECEIVE_CEC_COMMAND = 1;
Jungshik Jang3f74ab02014-04-30 14:31:02 +090055 // A message to report allocated logical address to main control looper.
56 private final static int MSG_REPORT_LOGICAL_ADDRESS = 2;
Jungshik Jange9c77c82014-04-24 20:30:09 +090057
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
Jungshik Jang3f74ab02014-04-30 14:31:02 +090067 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
Jungshik Jang0792d372014-04-23 17:57:26 +090072 // 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.
Jungshik Jange9c77c82014-04-24 20:30:09 +090077 private Handler mControlHandler;
Jungshik Jang0792d372014-04-23 17:57:26 +090078
79 // Stores the pointer to the native implementation of the service that
80 // interacts with HAL.
81 private long mNativePtr;
82
Jungshik Jang7d9a8432014-04-29 15:12:43 +090083 // 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
Jungshik Jang0792d372014-04-23 17:57:26 +090088 // 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.
Jungshik Jange9c77c82014-04-24 20:30:09 +090097 * @param service {@link HdmiControlService} instance used to create internal handler
98 * and to pass callback for incoming message or event.
Jungshik Jang0792d372014-04-23 17:57:26 +090099 * @return {@link HdmiCecController} if device is initialized successfully. Otherwise,
100 * returns {@code null}.
101 */
Jungshik Jange9c77c82014-04-24 20:30:09 +0900102 static HdmiCecController create(HdmiControlService service) {
Jungshik Jang0792d372014-04-23 17:57:26 +0900103 HdmiCecController handler = new HdmiCecController();
104 long nativePtr = nativeInit(handler);
105 if (nativePtr == 0L) {
106 handler = null;
107 return null;
108 }
109
Jungshik Jange9c77c82014-04-24 20:30:09 +0900110 handler.init(service, nativePtr);
Jungshik Jang0792d372014-04-23 17:57:26 +0900111 return handler;
112 }
113
Jungshik Jang3f74ab02014-04-30 14:31:02 +0900114 /**
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
Jungshik Jange9c77c82014-04-24 20:30:09 +0900151 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 }
Jungshik Jang0792d372014-04-23 17:57:26 +0900157
Jungshik Jange9c77c82014-04-24 20:30:09 +0900158 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;
Jungshik Jang3f74ab02014-04-30 14:31:02 +0900172 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;
Jungshik Jange9c77c82014-04-24 20:30:09 +0900179 default:
180 Slog.w(TAG, "Unsupported CEC Io request:" + msg.what);
181 break;
182 }
183 }
Jungshik Jang3f74ab02014-04-30 14:31:02 +0900184
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 }
Jungshik Jange9c77c82014-04-24 20:30:09 +0900225 }
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;
Jungshik Jang3f74ab02014-04-30 14:31:02 +0900239 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;
Jungshik Jange9c77c82014-04-24 20:30:09 +0900246 default:
247 Slog.i(TAG, "Unsupported message type:" + msg.what);
248 break;
249 }
250 }
251 }
252
Jungshik Jang7d9a8432014-04-29 15:12:43 +0900253 /**
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
Jungshik Jange9c77c82014-04-24 20:30:09 +0900316 private void init(HdmiControlService service, long nativePtr) {
317 mIoHandler = new IoHandler(service.getServiceLooper());
318 mControlHandler = new ControlHandler(service.getServiceLooper());
Jungshik Jang0792d372014-04-23 17:57:26 +0900319 mNativePtr = nativePtr;
320 }
321
Jungshik Jange9c77c82014-04-24 20:30:09 +0900322 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
Jungshik Jang0792d372014-04-23 17:57:26 +0900345 /**
Jungshik Jange9c77c82014-04-24 20:30:09 +0900346 * Called by native when incoming CEC message arrived.
Jungshik Jang0792d372014-04-23 17:57:26 +0900347 */
Jungshik Jange9c77c82014-04-24 20:30:09 +0900348 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.
Jungshik Jang0792d372014-04-23 17:57:26 +0900364 }
365
366 private static native long nativeInit(HdmiCecController handler);
Jungshik Jange9c77c82014-04-24 20:30:09 +0900367 private static native int nativeSendCecCommand(long contollerPtr, int srcAddress,
368 int dstAddress, byte[] body);
Jungshik Jang0792d372014-04-23 17:57:26 +0900369}