blob: 5327ef43aaaa9c73db33b789bcc03ea3a8d82958 [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 */
Jungshik Janga9095ba2014-05-02 13:06:22 +090043final class HdmiCecController {
Jungshik Jang0792d372014-04-23 17:57:26 +090044 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
Jungshik Jang3f74ab02014-04-30 14:31:02 +090058 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
Jungshik Jang0792d372014-04-23 17:57:26 +090063 // 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.
Jungshik Jange9c77c82014-04-24 20:30:09 +090068 private Handler mControlHandler;
Jungshik Jang0792d372014-04-23 17:57:26 +090069
70 // Stores the pointer to the native implementation of the service that
71 // interacts with HAL.
72 private long mNativePtr;
73
Jungshik Janga1fa91f2014-05-08 20:56:41 +090074 private HdmiControlService mService;
75
Jungshik Jang7d9a8432014-04-29 15:12:43 +090076 // 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>();
Jungshik Janga1fa91f2014-05-08 20:56:41 +090080 // 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>();
Jungshik Jang7d9a8432014-04-29 15:12:43 +090084
Jungshik Jang0792d372014-04-23 17:57:26 +090085 // 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.
Jungshik Jange9c77c82014-04-24 20:30:09 +090094 * @param service {@link HdmiControlService} instance used to create internal handler
95 * and to pass callback for incoming message or event.
Jungshik Jang0792d372014-04-23 17:57:26 +090096 * @return {@link HdmiCecController} if device is initialized successfully. Otherwise,
97 * returns {@code null}.
98 */
Jungshik Jange9c77c82014-04-24 20:30:09 +090099 static HdmiCecController create(HdmiControlService service) {
Jungshik Jang0792d372014-04-23 17:57:26 +0900100 HdmiCecController handler = new HdmiCecController();
101 long nativePtr = nativeInit(handler);
102 if (nativePtr == 0L) {
103 handler = null;
104 return null;
105 }
106
Jungshik Jange9c77c82014-04-24 20:30:09 +0900107 handler.init(service, nativePtr);
Jungshik Jang0792d372014-04-23 17:57:26 +0900108 return handler;
109 }
110
Jungshik Jang3f74ab02014-04-30 14:31:02 +0900111 /**
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
Jungshik Jange9c77c82014-04-24 20:30:09 +0900148 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 }
Jungshik Jang0792d372014-04-23 17:57:26 +0900154
Jungshik Jange9c77c82014-04-24 20:30:09 +0900155 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;
Jungshik Jang3f74ab02014-04-30 14:31:02 +0900169 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;
Jungshik Jange9c77c82014-04-24 20:30:09 +0900176 default:
177 Slog.w(TAG, "Unsupported CEC Io request:" + msg.what);
178 break;
179 }
180 }
Jungshik Jang3f74ab02014-04-30 14:31:02 +0900181
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 }
Jungshik Jange9c77c82014-04-24 20:30:09 +0900222 }
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;
Jungshik Jang3f74ab02014-04-30 14:31:02 +0900236 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;
Jungshik Jange9c77c82014-04-24 20:30:09 +0900243 default:
244 Slog.i(TAG, "Unsupported message type:" + msg.what);
245 break;
246 }
247 }
248 }
249
Jungshik Jang7d9a8432014-04-29 15:12:43 +0900250 /**
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
Jungshik Janga9095ba2014-05-02 13:06:22 +0900313 /**
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)) {
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900325 mLocalLogicalAddresses.append(newLogicalAddress, newLogicalAddress);
Jungshik Janga9095ba2014-05-02 13:06:22 +0900326 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() {
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900338 // TODO: consider to backup logical address so that new logical address
339 // allocation can use it as preferred address.
340 mLocalLogicalAddresses.clear();
Jungshik Janga9095ba2014-05-02 13:06:22 +0900341 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
Jungshik Jange9c77c82014-04-24 20:30:09 +0900374 private void init(HdmiControlService service, long nativePtr) {
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900375 mService = service;
Jungshik Jange9c77c82014-04-24 20:30:09 +0900376 mIoHandler = new IoHandler(service.getServiceLooper());
377 mControlHandler = new ControlHandler(service.getServiceLooper());
Jungshik Jang0792d372014-04-23 17:57:26 +0900378 mNativePtr = nativePtr;
379 }
380
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900381 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
Jungshik Jange9c77c82014-04-24 20:30:09 +0900388 private void onReceiveCommand(HdmiCecMessage message) {
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900389 if (isAcceptableAddress(message.getDestination()) &&
390 mService.handleCecCommand(message)) {
391 return;
392 }
Jungshik Jange9c77c82014-04-24 20:30:09 +0900393
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.
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900398 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand
399 (sourceAddress, message.getSource(), message.getOpcode(),
400 HdmiCecMessageBuilder.ABORT_REFUSED);
401 sendCommand(cecMessage);
402
Jungshik Jange9c77c82014-04-24 20:30:09 +0900403 }
404
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900405 void sendCommand(HdmiCecMessage cecMessage) {
Jungshik Jange9c77c82014-04-24 20:30:09 +0900406 Message message = mIoHandler.obtainMessage(MSG_SEND_CEC_COMMAND, cecMessage);
407 mIoHandler.sendMessage(message);
408 }
409
Jungshik Jang0792d372014-04-23 17:57:26 +0900410 /**
Jungshik Jange9c77c82014-04-24 20:30:09 +0900411 * Called by native when incoming CEC message arrived.
Jungshik Jang0792d372014-04-23 17:57:26 +0900412 */
Jungshik Jange9c77c82014-04-24 20:30:09 +0900413 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.
Jungshik Jang0792d372014-04-23 17:57:26 +0900429 }
430
431 private static native long nativeInit(HdmiCecController handler);
Jungshik Janga9095ba2014-05-02 13:06:22 +0900432 private static native int nativeSendCecCommand(long controllerPtr, int srcAddress,
Jungshik Jange9c77c82014-04-24 20:30:09 +0900433 int dstAddress, byte[] body);
Jungshik Janga9095ba2014-05-02 13:06:22 +0900434 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);
Jungshik Jang0792d372014-04-23 17:57:26 +0900439}