blob: 4792c55cf74632465ba1ede3c8c62fef5921bf92 [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;
Yuncheol Heoece603b2014-05-23 20:10:19 +090023import android.util.Slog;
Jungshik Jang7d9a8432014-04-29 15:12:43 +090024import android.util.SparseArray;
Jinsuk Kima8a5e502014-05-15 16:51:49 +090025import android.util.SparseIntArray;
Jungshik Jange9c77c82014-04-24 20:30:09 +090026
Jungshik Jang3f74ab02014-04-30 14:31:02 +090027import libcore.util.EmptyArray;
28
Jungshik Jang7d9a8432014-04-29 15:12:43 +090029import java.util.ArrayList;
Jungshik Jange9c77c82014-04-24 20:30:09 +090030import java.util.Arrays;
Jungshik Jang7d9a8432014-04-29 15:12:43 +090031import java.util.List;
Jungshik Jang0792d372014-04-23 17:57:26 +090032
33/**
34 * Manages HDMI-CEC command and behaviors. It converts user's command into CEC command
35 * and pass it to CEC HAL so that it sends message to other device. For incoming
36 * message it translates the message and delegates it to proper module.
37 *
38 * <p>It can be created only by {@link HdmiCecController#create}
39 *
40 * <p>Declared as package-private, accessed by {@link HdmiControlService} only.
41 */
Jungshik Janga9095ba2014-05-02 13:06:22 +090042final class HdmiCecController {
Jungshik Jang0792d372014-04-23 17:57:26 +090043 private static final String TAG = "HdmiCecController";
44
Jungshik Jang3f74ab02014-04-30 14:31:02 +090045 private static final byte[] EMPTY_BODY = EmptyArray.BYTE;
46
Jungshik Jange9c77c82014-04-24 20:30:09 +090047 // A message to pass cec send command to IO looper.
48 private static final int MSG_SEND_CEC_COMMAND = 1;
Jungshik Jang3f74ab02014-04-30 14:31:02 +090049 // A message to delegate logical allocation to IO looper.
50 private static final int MSG_ALLOCATE_LOGICAL_ADDRESS = 2;
Jungshik Jange9c77c82014-04-24 20:30:09 +090051
52 // Message types to handle incoming message in main service looper.
53 private final static int MSG_RECEIVE_CEC_COMMAND = 1;
Jungshik Jang3f74ab02014-04-30 14:31:02 +090054 // A message to report allocated logical address to main control looper.
55 private final static int MSG_REPORT_LOGICAL_ADDRESS = 2;
Jungshik Jange9c77c82014-04-24 20:30:09 +090056
Jungshik Jang3f74ab02014-04-30 14:31:02 +090057 private static final int NUM_LOGICAL_ADDRESS = 16;
58
Jungshik Jang0792d372014-04-23 17:57:26 +090059 // Handler instance to process synchronous I/O (mainly send) message.
60 private Handler mIoHandler;
61
62 // Handler instance to process various messages coming from other CEC
63 // device or issued by internal state change.
Jungshik Jange9c77c82014-04-24 20:30:09 +090064 private Handler mControlHandler;
Jungshik Jang0792d372014-04-23 17:57:26 +090065
66 // Stores the pointer to the native implementation of the service that
67 // interacts with HAL.
68 private long mNativePtr;
69
Jungshik Janga1fa91f2014-05-08 20:56:41 +090070 private HdmiControlService mService;
71
Jungshik Jang7d9a8432014-04-29 15:12:43 +090072 // Map-like container of all cec devices. A logical address of device is
73 // used as key of container.
74 private final SparseArray<HdmiCecDeviceInfo> mDeviceInfos =
75 new SparseArray<HdmiCecDeviceInfo>();
Jungshik Janga1fa91f2014-05-08 20:56:41 +090076 // Set-like container for all local devices' logical address.
77 // Key and value are same.
Jinsuk Kima8a5e502014-05-15 16:51:49 +090078 private final SparseIntArray mLocalAddresses = new SparseIntArray();
Jungshik Jang7d9a8432014-04-29 15:12:43 +090079
Jungshik Jang0792d372014-04-23 17:57:26 +090080 // Private constructor. Use HdmiCecController.create().
81 private HdmiCecController() {
Jinsuk Kima8a5e502014-05-15 16:51:49 +090082 // TODO: Consider restoring the local device addresses from persistent storage
83 // to allocate the same addresses again if possible.
Jungshik Jang0792d372014-04-23 17:57:26 +090084 }
85
86 /**
87 * A factory method to get {@link HdmiCecController}. If it fails to initialize
88 * inner device or has no device it will return {@code null}.
89 *
90 * <p>Declared as package-private, accessed by {@link HdmiControlService} only.
Jungshik Jange9c77c82014-04-24 20:30:09 +090091 * @param service {@link HdmiControlService} instance used to create internal handler
92 * and to pass callback for incoming message or event.
Jungshik Jang0792d372014-04-23 17:57:26 +090093 * @return {@link HdmiCecController} if device is initialized successfully. Otherwise,
94 * returns {@code null}.
95 */
Jungshik Jange9c77c82014-04-24 20:30:09 +090096 static HdmiCecController create(HdmiControlService service) {
Jungshik Jang0792d372014-04-23 17:57:26 +090097 HdmiCecController handler = new HdmiCecController();
98 long nativePtr = nativeInit(handler);
99 if (nativePtr == 0L) {
100 handler = null;
101 return null;
102 }
103
Jungshik Jange9c77c82014-04-24 20:30:09 +0900104 handler.init(service, nativePtr);
Jungshik Jang0792d372014-04-23 17:57:26 +0900105 return handler;
106 }
107
Jungshik Jang3f74ab02014-04-30 14:31:02 +0900108 /**
Jinsuk Kima8a5e502014-05-15 16:51:49 +0900109 * Initialize {@link #mLocalAddresses} by allocating logical addresses for each hosted type.
110 *
111 * @param deviceTypes local device types
112 */
113 void initializeLocalDevices(int[] deviceTypes) {
114 for (int deviceType : deviceTypes) {
115 int preferred = getPreferredAddress(deviceType);
116 allocateLogicalAddress(deviceType, preferred, new AllocateLogicalAddressCallback() {
117 @Override
118 public void onAllocated(int deviceType, int logicalAddress) {
119 addLogicalAddress(logicalAddress);
120 }
121 });
122 }
123 }
124
125 /**
126 * Get the preferred address for a given type.
127 *
128 * @param deviceType logical device type to get the address for
129 * @return preferred address; {@link HdmiCec#ADDR_UNREGISTERED} if not available.
130 */
131 private int getPreferredAddress(int deviceType) {
132 // Uses the data restored from persistent memory at boot up if they are available.
133 // Otherwise we return UNREGISTERED indicating there is no preferred address.
134 // Note that for address SPECIFIC_USE(14), HdmiCec.getTypeFromAddress() returns DEVICE_TV,
135 // meaning that we do not support device type video processor yet.
136 for (int i = 0; i < mLocalAddresses.size(); ++i) {
137 int address = mLocalAddresses.keyAt(i);
138 int type = HdmiCec.getTypeFromAddress(address);
139 if (type == deviceType) {
140 return address;
141 }
142 }
143 return HdmiCec.ADDR_UNREGISTERED;
144 }
145
146 /**
Jungshik Jang3f74ab02014-04-30 14:31:02 +0900147 * Interface to report allocated logical address.
148 */
149 interface AllocateLogicalAddressCallback {
150 /**
151 * Called when a new logical address is allocated.
152 *
153 * @param deviceType requested device type to allocate logical address
154 * @param logicalAddress allocated logical address. If it is
155 * {@link HdmiCec#ADDR_UNREGISTERED}, it means that
156 * it failed to allocate logical address for the given device type
157 */
158 void onAllocated(int deviceType, int logicalAddress);
159 }
160
161 /**
162 * Allocate a new logical address of the given device type. Allocated
163 * address will be reported through {@link AllocateLogicalAddressCallback}.
164 *
165 * <p> Declared as package-private, accessed by {@link HdmiControlService} only.
166 *
167 * @param deviceType type of device to used to determine logical address
168 * @param preferredAddress a logical address preferred to be allocated.
169 * If sets {@link HdmiCec#ADDR_UNREGISTERED}, scans
170 * the smallest logical address matched with the given device type.
171 * Otherwise, scan address will start from {@code preferredAddress}
172 * @param callback callback interface to report allocated logical address to caller
173 */
Jungshik Jangd643f762014-05-22 19:28:09 +0900174 void allocateLogicalAddress(final int deviceType, final int preferredAddress,
175 final AllocateLogicalAddressCallback callback) {
176 runOnIoThread(new Runnable() {
177 @Override
178 public void run() {
179 handleAllocateLogicalAddress(deviceType, preferredAddress, callback);
180 }
181 });
182 }
183
184 private void handleAllocateLogicalAddress(final int deviceType, int preferredAddress,
185 final AllocateLogicalAddressCallback callback) {
186 int startAddress = preferredAddress;
187 // If preferred address is "unregistered", start address will be the smallest
188 // address matched with the given device type.
189 if (preferredAddress == HdmiCec.ADDR_UNREGISTERED) {
190 for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
191 if (deviceType == HdmiCec.getTypeFromAddress(i)) {
192 startAddress = i;
193 break;
194 }
195 }
196 }
197
198 int logicalAddress = HdmiCec.ADDR_UNREGISTERED;
199 // Iterates all possible addresses which has the same device type.
200 for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
201 int curAddress = (startAddress + i) % NUM_LOGICAL_ADDRESS;
202 if (curAddress != HdmiCec.ADDR_UNREGISTERED
203 && deviceType == HdmiCec.getTypeFromAddress(i)) {
204 // <Polling Message> is a message which has empty body and
205 // uses same address for both source and destination address.
206 // If sending <Polling Message> failed (NAK), it becomes
207 // new logical address for the device because no device uses
208 // it as logical address of the device.
209 int error = nativeSendCecCommand(mNativePtr, curAddress, curAddress,
210 EMPTY_BODY);
Yuncheol Heoece603b2014-05-23 20:10:19 +0900211 if (error != HdmiControlService.SEND_RESULT_SUCCESS) {
Jungshik Jangd643f762014-05-22 19:28:09 +0900212 logicalAddress = curAddress;
213 break;
214 }
215 }
216 }
217
218 final int assignedAddress = logicalAddress;
219 if (callback != null) {
220 runOnServiceThread(new Runnable() {
221 @Override
222 public void run() {
223 callback.onAllocated(deviceType, assignedAddress);
224 }
225 });
226 }
Jungshik Jang3f74ab02014-04-30 14:31:02 +0900227 }
228
Jungshik Jange9c77c82014-04-24 20:30:09 +0900229 private static byte[] buildBody(int opcode, byte[] params) {
230 byte[] body = new byte[params.length + 1];
231 body[0] = (byte) opcode;
232 System.arraycopy(params, 0, body, 1, params.length);
233 return body;
234 }
Jungshik Jang0792d372014-04-23 17:57:26 +0900235
Jungshik Jang7d9a8432014-04-29 15:12:43 +0900236 /**
237 * Add a new {@link HdmiCecDeviceInfo}. It returns old device info which has the same
238 * logical address as new device info's.
239 *
240 * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
241 *
242 * @param deviceInfo a new {@link HdmiCecDeviceInfo} to be added.
243 * @return {@code null} if it is new device. Otherwise, returns old {@HdmiCecDeviceInfo}
244 * that has the same logical address as new one has.
245 */
246 HdmiCecDeviceInfo addDeviceInfo(HdmiCecDeviceInfo deviceInfo) {
247 HdmiCecDeviceInfo oldDeviceInfo = getDeviceInfo(deviceInfo.getLogicalAddress());
248 if (oldDeviceInfo != null) {
249 removeDeviceInfo(deviceInfo.getLogicalAddress());
250 }
251 mDeviceInfos.append(deviceInfo.getLogicalAddress(), deviceInfo);
252 return oldDeviceInfo;
253 }
254
255 /**
256 * Remove a device info corresponding to the given {@code logicalAddress}.
257 * It returns removed {@link HdmiCecDeviceInfo} if exists.
258 *
259 * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
260 *
261 * @param logicalAddress logical address of device to be removed
262 * @return removed {@link HdmiCecDeviceInfo} it exists. Otherwise, returns {@code null}
263 */
264 HdmiCecDeviceInfo removeDeviceInfo(int logicalAddress) {
265 HdmiCecDeviceInfo deviceInfo = mDeviceInfos.get(logicalAddress);
266 if (deviceInfo != null) {
267 mDeviceInfos.remove(logicalAddress);
268 }
269 return deviceInfo;
270 }
271
272 /**
273 * Return a list of all {@HdmiCecDeviceInfo}.
274 *
275 * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
276 */
277 List<HdmiCecDeviceInfo> getDeviceInfoList() {
278 List<HdmiCecDeviceInfo> deviceInfoList = new ArrayList<HdmiCecDeviceInfo>(
279 mDeviceInfos.size());
280 for (int i = 0; i < mDeviceInfos.size(); ++i) {
281 deviceInfoList.add(mDeviceInfos.valueAt(i));
282 }
283 return deviceInfoList;
284 }
285
286 /**
287 * Return a {@link HdmiCecDeviceInfo} corresponding to the given {@code logicalAddress}.
288 *
289 * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
290 *
291 * @param logicalAddress logical address to be retrieved
292 * @return {@link HdmiCecDeviceInfo} matched with the given {@code logicalAddress}.
293 * Returns null if no logical address matched
294 */
295 HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) {
296 return mDeviceInfos.get(logicalAddress);
297 }
298
Jungshik Janga9095ba2014-05-02 13:06:22 +0900299 /**
300 * Add a new logical address to the device. Device's HW should be notified
301 * when a new logical address is assigned to a device, so that it can accept
302 * a command having available destinations.
303 *
304 * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
305 *
306 * @param newLogicalAddress a logical address to be added
307 * @return 0 on success. Otherwise, returns negative value
308 */
309 int addLogicalAddress(int newLogicalAddress) {
310 if (HdmiCec.isValidAddress(newLogicalAddress)) {
Jinsuk Kima8a5e502014-05-15 16:51:49 +0900311 mLocalAddresses.put(newLogicalAddress, newLogicalAddress);
Jungshik Janga9095ba2014-05-02 13:06:22 +0900312 return nativeAddLogicalAddress(mNativePtr, newLogicalAddress);
313 } else {
314 return -1;
315 }
316 }
317
318 /**
319 * Clear all logical addresses registered in the device.
320 *
321 * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
322 */
323 void clearLogicalAddress() {
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900324 // TODO: consider to backup logical address so that new logical address
325 // allocation can use it as preferred address.
Jinsuk Kima8a5e502014-05-15 16:51:49 +0900326 mLocalAddresses.clear();
Jungshik Janga9095ba2014-05-02 13:06:22 +0900327 nativeClearLogicalAddress(mNativePtr);
328 }
329
330 /**
331 * Return the physical address of the device.
332 *
333 * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
334 *
335 * @return CEC physical address of the device. The range of success address
336 * is between 0x0000 and 0xFFFF. If failed it returns -1
337 */
338 int getPhysicalAddress() {
339 return nativeGetPhysicalAddress(mNativePtr);
340 }
341
342 /**
343 * Return CEC version of the device.
344 *
345 * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
346 */
347 int getVersion() {
348 return nativeGetVersion(mNativePtr);
349 }
350
351 /**
352 * Return vendor id of the device.
353 *
354 * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
355 */
356 int getVendorId() {
357 return nativeGetVendorId(mNativePtr);
358 }
359
Jungshik Jangd643f762014-05-22 19:28:09 +0900360 private void runOnIoThread(Runnable runnable) {
361 mIoHandler.post(runnable);
362 }
363
364 private void runOnServiceThread(Runnable runnable) {
365 mControlHandler.post(runnable);
366 }
367
Jungshik Jange9c77c82014-04-24 20:30:09 +0900368 private void init(HdmiControlService service, long nativePtr) {
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900369 mService = service;
Jungshik Jangd643f762014-05-22 19:28:09 +0900370 mIoHandler = new Handler(service.getServiceLooper());
371 mControlHandler = new Handler(service.getServiceLooper());
Jungshik Jang0792d372014-04-23 17:57:26 +0900372 mNativePtr = nativePtr;
373 }
374
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900375 private boolean isAcceptableAddress(int address) {
376 // Can access command targeting devices available in local device or
377 // broadcast command.
378 return address == HdmiCec.ADDR_BROADCAST
Jinsuk Kima8a5e502014-05-15 16:51:49 +0900379 || mLocalAddresses.indexOfKey(address) < 0;
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900380 }
381
Jungshik Jange9c77c82014-04-24 20:30:09 +0900382 private void onReceiveCommand(HdmiCecMessage message) {
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900383 if (isAcceptableAddress(message.getDestination()) &&
384 mService.handleCecCommand(message)) {
385 return;
386 }
Jungshik Jange9c77c82014-04-24 20:30:09 +0900387
388 // TODO: Use device's source address for broadcast message.
389 int sourceAddress = message.getDestination() != HdmiCec.ADDR_BROADCAST ?
390 message.getDestination() : 0;
391 // Reply <Feature Abort> to initiator (source) for all requests.
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900392 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand
393 (sourceAddress, message.getSource(), message.getOpcode(),
394 HdmiCecMessageBuilder.ABORT_REFUSED);
Jungshik Jangd643f762014-05-22 19:28:09 +0900395 sendCommand(cecMessage, null);
Jungshik Jange9c77c82014-04-24 20:30:09 +0900396 }
397
Jungshik Jangd643f762014-05-22 19:28:09 +0900398 void sendCommand(final HdmiCecMessage cecMessage,
399 final HdmiControlService.SendMessageCallback callback) {
400 runOnIoThread(new Runnable() {
401 @Override
402 public void run() {
403 byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams());
404 final int error = nativeSendCecCommand(mNativePtr, cecMessage.getSource(),
405 cecMessage.getDestination(), body);
Yuncheol Heoece603b2014-05-23 20:10:19 +0900406 if (error != HdmiControlService.SEND_RESULT_SUCCESS) {
407 Slog.w(TAG, "Failed to send " + cecMessage);
408 }
Jungshik Jangd643f762014-05-22 19:28:09 +0900409 if (callback != null) {
410 runOnServiceThread(new Runnable() {
411 @Override
412 public void run() {
413 callback.onSendCompleted(error);
414 }
415 });
416 }
417 }
418 });
Jungshik Jange9c77c82014-04-24 20:30:09 +0900419 }
420
Jungshik Jang0792d372014-04-23 17:57:26 +0900421 /**
Jungshik Jange9c77c82014-04-24 20:30:09 +0900422 * Called by native when incoming CEC message arrived.
Jungshik Jang0792d372014-04-23 17:57:26 +0900423 */
Jungshik Jange9c77c82014-04-24 20:30:09 +0900424 private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) {
425 byte opcode = body[0];
426 byte params[] = Arrays.copyOfRange(body, 1, body.length);
Jungshik Jangd643f762014-05-22 19:28:09 +0900427 final HdmiCecMessage cecMessage = new HdmiCecMessage(srcAddress, dstAddress, opcode, params);
Jungshik Jange9c77c82014-04-24 20:30:09 +0900428
429 // Delegate message to main handler so that it handles in main thread.
Jungshik Jangd643f762014-05-22 19:28:09 +0900430 runOnServiceThread(new Runnable() {
431 @Override
432 public void run() {
433 onReceiveCommand(cecMessage);
434 }
435 });
Jungshik Jange9c77c82014-04-24 20:30:09 +0900436 }
437
438 /**
439 * Called by native when a hotplug event issues.
440 */
441 private void handleHotplug(boolean connected) {
Jungshik Jang67ea5212014-05-15 14:05:24 +0900442 // TODO: once add port number to cec HAL interface, pass port number
443 // to the service.
444 mService.onHotplug(0, connected);
Jungshik Jang0792d372014-04-23 17:57:26 +0900445 }
446
447 private static native long nativeInit(HdmiCecController handler);
Jungshik Janga9095ba2014-05-02 13:06:22 +0900448 private static native int nativeSendCecCommand(long controllerPtr, int srcAddress,
Jungshik Jange9c77c82014-04-24 20:30:09 +0900449 int dstAddress, byte[] body);
Jungshik Janga9095ba2014-05-02 13:06:22 +0900450 private static native int nativeAddLogicalAddress(long controllerPtr, int logicalAddress);
451 private static native void nativeClearLogicalAddress(long controllerPtr);
452 private static native int nativeGetPhysicalAddress(long controllerPtr);
453 private static native int nativeGetVersion(long controllerPtr);
454 private static native int nativeGetVendorId(long controllerPtr);
Jungshik Jang0792d372014-04-23 17:57:26 +0900455}