HdmiCecController.java revision a9095ba488ea18aeafaf9c3a8258bf9f459bda71
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    // 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    /**
317     * Add a new logical address to the device. Device's HW should be notified
318     * when a new logical address is assigned to a device, so that it can accept
319     * a command having available destinations.
320     *
321     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
322     *
323     * @param newLogicalAddress a logical address to be added
324     * @return 0 on success. Otherwise, returns negative value
325     */
326    int addLogicalAddress(int newLogicalAddress) {
327        if (HdmiCec.isValidAddress(newLogicalAddress)) {
328            return nativeAddLogicalAddress(mNativePtr, newLogicalAddress);
329        } else {
330            return -1;
331        }
332    }
333
334    /**
335     * Clear all logical addresses registered in the device.
336     *
337     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
338     */
339    void clearLogicalAddress() {
340        nativeClearLogicalAddress(mNativePtr);
341    }
342
343    /**
344     * Return the physical address of the device.
345     *
346     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
347     *
348     * @return CEC physical address of the device. The range of success address
349     *         is between 0x0000 and 0xFFFF. If failed it returns -1
350     */
351    int getPhysicalAddress() {
352        return nativeGetPhysicalAddress(mNativePtr);
353    }
354
355    /**
356     * Return CEC version of the device.
357     *
358     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
359     */
360    int getVersion() {
361        return nativeGetVersion(mNativePtr);
362    }
363
364    /**
365     * Return vendor id of the device.
366     *
367     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
368     */
369    int getVendorId() {
370        return nativeGetVendorId(mNativePtr);
371    }
372
373    private void init(HdmiControlService service, long nativePtr) {
374        mIoHandler = new IoHandler(service.getServiceLooper());
375        mControlHandler = new ControlHandler(service.getServiceLooper());
376        mNativePtr = nativePtr;
377    }
378
379    private void onReceiveCommand(HdmiCecMessage message) {
380        // TODO: Handle message according to opcode type.
381
382        // TODO: Use device's source address for broadcast message.
383        int sourceAddress = message.getDestination() != HdmiCec.ADDR_BROADCAST ?
384                message.getDestination() : 0;
385        // Reply <Feature Abort> to initiator (source) for all requests.
386        sendFeatureAbort(sourceAddress, message.getSource(), message.getOpcode(),
387                ABORT_REFUSED);
388    }
389
390    private void sendFeatureAbort(int srcAddress, int destAddress, int originalOpcode,
391            int reason) {
392        byte[] params = new byte[2];
393        params[0] = (byte) originalOpcode;
394        params[1] = (byte) reason;
395
396        HdmiCecMessage cecMessage = new HdmiCecMessage(srcAddress, destAddress,
397                HdmiCec.MESSAGE_FEATURE_ABORT, params);
398        Message message = mIoHandler.obtainMessage(MSG_SEND_CEC_COMMAND, cecMessage);
399        mIoHandler.sendMessage(message);
400    }
401
402    /**
403     * Called by native when incoming CEC message arrived.
404     */
405    private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) {
406        byte opcode = body[0];
407        byte params[] = Arrays.copyOfRange(body, 1, body.length);
408        HdmiCecMessage cecMessage = new HdmiCecMessage(srcAddress, dstAddress, opcode, params);
409
410        // Delegate message to main handler so that it handles in main thread.
411        Message message = mControlHandler.obtainMessage(
412                MSG_RECEIVE_CEC_COMMAND, cecMessage);
413        mControlHandler.sendMessage(message);
414    }
415
416    /**
417     * Called by native when a hotplug event issues.
418     */
419    private void handleHotplug(boolean connected) {
420        // TODO: Delegate event to main message handler.
421    }
422
423    private static native long nativeInit(HdmiCecController handler);
424    private static native int nativeSendCecCommand(long controllerPtr, int srcAddress,
425            int dstAddress, byte[] body);
426    private static native int nativeAddLogicalAddress(long controllerPtr, int logicalAddress);
427    private static native void nativeClearLogicalAddress(long controllerPtr);
428    private static native int nativeGetPhysicalAddress(long controllerPtr);
429    private static native int nativeGetVersion(long controllerPtr);
430    private static native int nativeGetVendorId(long controllerPtr);
431}
432