HdmiCecController.java revision e9c77c88ea34a66f83a94f960547275c0ff6bd07
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.HdmiCecMessage;
21import android.os.Handler;
22import android.os.Looper;
23import android.os.Message;
24import android.util.Slog;
25
26import java.util.Arrays;
27
28/**
29 * Manages HDMI-CEC command and behaviors. It converts user's command into CEC command
30 * and pass it to CEC HAL so that it sends message to other device. For incoming
31 * message it translates the message and delegates it to proper module.
32 *
33 * <p>It can be created only by {@link HdmiCecController#create}
34 *
35 * <p>Declared as package-private, accessed by {@link HdmiControlService} only.
36 */
37class HdmiCecController {
38    private static final String TAG = "HdmiCecController";
39
40    // A message to pass cec send command to IO looper.
41    private static final int MSG_SEND_CEC_COMMAND = 1;
42
43    // Message types to handle incoming message in main service looper.
44    private final static int MSG_RECEIVE_CEC_COMMAND = 1;
45
46    // TODO: move these values to HdmiCec.java once make it internal constant class.
47    // CEC's ABORT reason values.
48    private static final int ABORT_UNRECOGNIZED_MODE = 0;
49    private static final int ABORT_NOT_IN_CORRECT_MODE = 1;
50    private static final int ABORT_CANNOT_PROVIDE_SOURCE = 2;
51    private static final int ABORT_INVALID_OPERAND = 3;
52    private static final int ABORT_REFUSED = 4;
53    private static final int ABORT_UNABLE_TO_DETERMINE = 5;
54
55    // Handler instance to process synchronous I/O (mainly send) message.
56    private Handler mIoHandler;
57
58    // Handler instance to process various messages coming from other CEC
59    // device or issued by internal state change.
60    private Handler mControlHandler;
61
62    // Stores the pointer to the native implementation of the service that
63    // interacts with HAL.
64    private long mNativePtr;
65
66    // Private constructor.  Use HdmiCecController.create().
67    private HdmiCecController() {
68    }
69
70    /**
71     * A factory method to get {@link HdmiCecController}. If it fails to initialize
72     * inner device or has no device it will return {@code null}.
73     *
74     * <p>Declared as package-private, accessed by {@link HdmiControlService} only.
75     * @param service {@link HdmiControlService} instance used to create internal handler
76     *                and to pass callback for incoming message or event.
77     * @return {@link HdmiCecController} if device is initialized successfully. Otherwise,
78     *         returns {@code null}.
79     */
80    static HdmiCecController create(HdmiControlService service) {
81        HdmiCecController handler = new HdmiCecController();
82        long nativePtr = nativeInit(handler);
83        if (nativePtr == 0L) {
84            handler = null;
85            return null;
86        }
87
88        handler.init(service, nativePtr);
89        return handler;
90    }
91
92    private static byte[] buildBody(int opcode, byte[] params) {
93        byte[] body = new byte[params.length + 1];
94        body[0] = (byte) opcode;
95        System.arraycopy(params, 0, body, 1, params.length);
96        return body;
97    }
98
99    private final class IoHandler extends Handler {
100        private IoHandler(Looper looper) {
101            super(looper);
102        }
103
104        @Override
105        public void handleMessage(Message msg) {
106            switch (msg.what) {
107                case MSG_SEND_CEC_COMMAND:
108                    HdmiCecMessage cecMessage = (HdmiCecMessage) msg.obj;
109                    byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams());
110                    nativeSendCecCommand(mNativePtr, cecMessage.getSource(),
111                            cecMessage.getDestination(), body);
112                    break;
113                default:
114                    Slog.w(TAG, "Unsupported CEC Io request:" + msg.what);
115                    break;
116            }
117        }
118    }
119
120    private final class ControlHandler extends Handler {
121        private ControlHandler(Looper looper) {
122            super(looper);
123        }
124
125        @Override
126        public void handleMessage(Message msg) {
127            switch (msg.what) {
128                case MSG_RECEIVE_CEC_COMMAND:
129                    // TODO: delegate it to HdmiControl service.
130                    onReceiveCommand((HdmiCecMessage) msg.obj);
131                    break;
132                default:
133                    Slog.i(TAG, "Unsupported message type:" + msg.what);
134                    break;
135            }
136        }
137    }
138
139    private void init(HdmiControlService service, long nativePtr) {
140        mIoHandler = new IoHandler(service.getServiceLooper());
141        mControlHandler = new ControlHandler(service.getServiceLooper());
142        mNativePtr = nativePtr;
143    }
144
145    private void onReceiveCommand(HdmiCecMessage message) {
146        // TODO: Handle message according to opcode type.
147
148        // TODO: Use device's source address for broadcast message.
149        int sourceAddress = message.getDestination() != HdmiCec.ADDR_BROADCAST ?
150                message.getDestination() : 0;
151        // Reply <Feature Abort> to initiator (source) for all requests.
152        sendFeatureAbort(sourceAddress, message.getSource(), message.getOpcode(),
153                ABORT_REFUSED);
154    }
155
156    private void sendFeatureAbort(int srcAddress, int destAddress, int originalOpcode,
157            int reason) {
158        byte[] params = new byte[2];
159        params[0] = (byte) originalOpcode;
160        params[1] = (byte) reason;
161
162        HdmiCecMessage cecMessage = new HdmiCecMessage(srcAddress, destAddress,
163                HdmiCec.MESSAGE_FEATURE_ABORT, params);
164        Message message = mIoHandler.obtainMessage(MSG_SEND_CEC_COMMAND, cecMessage);
165        mIoHandler.sendMessage(message);
166    }
167
168    /**
169     * Called by native when incoming CEC message arrived.
170     */
171    private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) {
172        byte opcode = body[0];
173        byte params[] = Arrays.copyOfRange(body, 1, body.length);
174        HdmiCecMessage cecMessage = new HdmiCecMessage(srcAddress, dstAddress, opcode, params);
175
176        // Delegate message to main handler so that it handles in main thread.
177        Message message = mControlHandler.obtainMessage(
178                MSG_RECEIVE_CEC_COMMAND, cecMessage);
179        mControlHandler.sendMessage(message);
180    }
181
182    /**
183     * Called by native when a hotplug event issues.
184     */
185    private void handleHotplug(boolean connected) {
186        // TODO: Delegate event to main message handler.
187    }
188
189    private static native long nativeInit(HdmiCecController handler);
190    private static native int nativeSendCecCommand(long contollerPtr, int srcAddress,
191            int dstAddress, byte[] body);
192}
193