HdmiCecController.java revision 7d9a843af83dbc75b1d170c743603198ede5a10f
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 java.util.ArrayList;
29import java.util.Arrays;
30import java.util.List;
31
32/**
33 * Manages HDMI-CEC command and behaviors. It converts user's command into CEC command
34 * and pass it to CEC HAL so that it sends message to other device. For incoming
35 * message it translates the message and delegates it to proper module.
36 *
37 * <p>It can be created only by {@link HdmiCecController#create}
38 *
39 * <p>Declared as package-private, accessed by {@link HdmiControlService} only.
40 */
41class HdmiCecController {
42    private static final String TAG = "HdmiCecController";
43
44    // A message to pass cec send command to IO looper.
45    private static final int MSG_SEND_CEC_COMMAND = 1;
46
47    // Message types to handle incoming message in main service looper.
48    private final static int MSG_RECEIVE_CEC_COMMAND = 1;
49
50    // TODO: move these values to HdmiCec.java once make it internal constant class.
51    // CEC's ABORT reason values.
52    private static final int ABORT_UNRECOGNIZED_MODE = 0;
53    private static final int ABORT_NOT_IN_CORRECT_MODE = 1;
54    private static final int ABORT_CANNOT_PROVIDE_SOURCE = 2;
55    private static final int ABORT_INVALID_OPERAND = 3;
56    private static final int ABORT_REFUSED = 4;
57    private static final int ABORT_UNABLE_TO_DETERMINE = 5;
58
59    // 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.
64    private Handler mControlHandler;
65
66    // Stores the pointer to the native implementation of the service that
67    // interacts with HAL.
68    private long mNativePtr;
69
70    // Map-like container of all cec devices. A logical address of device is
71    // used as key of container.
72    private final SparseArray<HdmiCecDeviceInfo> mDeviceInfos =
73            new SparseArray<HdmiCecDeviceInfo>();
74
75    // Private constructor.  Use HdmiCecController.create().
76    private HdmiCecController() {
77    }
78
79    /**
80     * A factory method to get {@link HdmiCecController}. If it fails to initialize
81     * inner device or has no device it will return {@code null}.
82     *
83     * <p>Declared as package-private, accessed by {@link HdmiControlService} only.
84     * @param service {@link HdmiControlService} instance used to create internal handler
85     *                and to pass callback for incoming message or event.
86     * @return {@link HdmiCecController} if device is initialized successfully. Otherwise,
87     *         returns {@code null}.
88     */
89    static HdmiCecController create(HdmiControlService service) {
90        HdmiCecController handler = new HdmiCecController();
91        long nativePtr = nativeInit(handler);
92        if (nativePtr == 0L) {
93            handler = null;
94            return null;
95        }
96
97        handler.init(service, nativePtr);
98        return handler;
99    }
100
101    private static byte[] buildBody(int opcode, byte[] params) {
102        byte[] body = new byte[params.length + 1];
103        body[0] = (byte) opcode;
104        System.arraycopy(params, 0, body, 1, params.length);
105        return body;
106    }
107
108    private final class IoHandler extends Handler {
109        private IoHandler(Looper looper) {
110            super(looper);
111        }
112
113        @Override
114        public void handleMessage(Message msg) {
115            switch (msg.what) {
116                case MSG_SEND_CEC_COMMAND:
117                    HdmiCecMessage cecMessage = (HdmiCecMessage) msg.obj;
118                    byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams());
119                    nativeSendCecCommand(mNativePtr, cecMessage.getSource(),
120                            cecMessage.getDestination(), body);
121                    break;
122                default:
123                    Slog.w(TAG, "Unsupported CEC Io request:" + msg.what);
124                    break;
125            }
126        }
127    }
128
129    private final class ControlHandler extends Handler {
130        private ControlHandler(Looper looper) {
131            super(looper);
132        }
133
134        @Override
135        public void handleMessage(Message msg) {
136            switch (msg.what) {
137                case MSG_RECEIVE_CEC_COMMAND:
138                    // TODO: delegate it to HdmiControl service.
139                    onReceiveCommand((HdmiCecMessage) msg.obj);
140                    break;
141                default:
142                    Slog.i(TAG, "Unsupported message type:" + msg.what);
143                    break;
144            }
145        }
146    }
147
148    /**
149     * Add a new {@link HdmiCecDeviceInfo}. It returns old device info which has the same
150     * logical address as new device info's.
151     *
152     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
153     *
154     * @param deviceInfo a new {@link HdmiCecDeviceInfo} to be added.
155     * @return {@code null} if it is new device. Otherwise, returns old {@HdmiCecDeviceInfo}
156     *         that has the same logical address as new one has.
157     */
158    HdmiCecDeviceInfo addDeviceInfo(HdmiCecDeviceInfo deviceInfo) {
159        HdmiCecDeviceInfo oldDeviceInfo = getDeviceInfo(deviceInfo.getLogicalAddress());
160        if (oldDeviceInfo != null) {
161            removeDeviceInfo(deviceInfo.getLogicalAddress());
162        }
163        mDeviceInfos.append(deviceInfo.getLogicalAddress(), deviceInfo);
164        return oldDeviceInfo;
165    }
166
167    /**
168     * Remove a device info corresponding to the given {@code logicalAddress}.
169     * It returns removed {@link HdmiCecDeviceInfo} if exists.
170     *
171     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
172     *
173     * @param logicalAddress logical address of device to be removed
174     * @return removed {@link HdmiCecDeviceInfo} it exists. Otherwise, returns {@code null}
175     */
176    HdmiCecDeviceInfo removeDeviceInfo(int logicalAddress) {
177        HdmiCecDeviceInfo deviceInfo = mDeviceInfos.get(logicalAddress);
178        if (deviceInfo != null) {
179            mDeviceInfos.remove(logicalAddress);
180        }
181        return deviceInfo;
182    }
183
184    /**
185     * Return a list of all {@HdmiCecDeviceInfo}.
186     *
187     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
188     */
189    List<HdmiCecDeviceInfo> getDeviceInfoList() {
190        List<HdmiCecDeviceInfo> deviceInfoList = new ArrayList<HdmiCecDeviceInfo>(
191                mDeviceInfos.size());
192        for (int i = 0; i < mDeviceInfos.size(); ++i) {
193            deviceInfoList.add(mDeviceInfos.valueAt(i));
194        }
195        return deviceInfoList;
196    }
197
198    /**
199     * Return a {@link HdmiCecDeviceInfo} corresponding to the given {@code logicalAddress}.
200     *
201     * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
202     *
203     * @param logicalAddress logical address to be retrieved
204     * @return {@link HdmiCecDeviceInfo} matched with the given {@code logicalAddress}.
205     *         Returns null if no logical address matched
206     */
207    HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) {
208        return mDeviceInfos.get(logicalAddress);
209    }
210
211    private void init(HdmiControlService service, long nativePtr) {
212        mIoHandler = new IoHandler(service.getServiceLooper());
213        mControlHandler = new ControlHandler(service.getServiceLooper());
214        mNativePtr = nativePtr;
215    }
216
217    private void onReceiveCommand(HdmiCecMessage message) {
218        // TODO: Handle message according to opcode type.
219
220        // TODO: Use device's source address for broadcast message.
221        int sourceAddress = message.getDestination() != HdmiCec.ADDR_BROADCAST ?
222                message.getDestination() : 0;
223        // Reply <Feature Abort> to initiator (source) for all requests.
224        sendFeatureAbort(sourceAddress, message.getSource(), message.getOpcode(),
225                ABORT_REFUSED);
226    }
227
228    private void sendFeatureAbort(int srcAddress, int destAddress, int originalOpcode,
229            int reason) {
230        byte[] params = new byte[2];
231        params[0] = (byte) originalOpcode;
232        params[1] = (byte) reason;
233
234        HdmiCecMessage cecMessage = new HdmiCecMessage(srcAddress, destAddress,
235                HdmiCec.MESSAGE_FEATURE_ABORT, params);
236        Message message = mIoHandler.obtainMessage(MSG_SEND_CEC_COMMAND, cecMessage);
237        mIoHandler.sendMessage(message);
238    }
239
240    /**
241     * Called by native when incoming CEC message arrived.
242     */
243    private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) {
244        byte opcode = body[0];
245        byte params[] = Arrays.copyOfRange(body, 1, body.length);
246        HdmiCecMessage cecMessage = new HdmiCecMessage(srcAddress, dstAddress, opcode, params);
247
248        // Delegate message to main handler so that it handles in main thread.
249        Message message = mControlHandler.obtainMessage(
250                MSG_RECEIVE_CEC_COMMAND, cecMessage);
251        mControlHandler.sendMessage(message);
252    }
253
254    /**
255     * Called by native when a hotplug event issues.
256     */
257    private void handleHotplug(boolean connected) {
258        // TODO: Delegate event to main message handler.
259    }
260
261    private static native long nativeInit(HdmiCecController handler);
262    private static native int nativeSendCecCommand(long contollerPtr, int srcAddress,
263            int dstAddress, byte[] body);
264}
265