HdmiControlService.java revision 67ea521d14f366fe5aac09e512865d31bfa0ee53
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.annotation.Nullable;
20import android.content.Context;
21import android.hardware.hdmi.HdmiCec;
22import android.hardware.hdmi.HdmiCecDeviceInfo;
23import android.hardware.hdmi.HdmiCecMessage;
24import android.os.Handler;
25import android.os.HandlerThread;
26import android.os.Looper;
27import android.util.Slog;
28
29import com.android.server.SystemService;
30
31import java.util.Iterator;
32import java.util.LinkedList;
33import java.util.Locale;
34
35/**
36 * Provides a service for sending and processing HDMI control messages,
37 * HDMI-CEC and MHL control command, and providing the information on both standard.
38 */
39public final class HdmiControlService extends SystemService {
40    private static final String TAG = "HdmiControlService";
41
42    // A thread to handle synchronous IO of CEC and MHL control service.
43    // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
44    // and sparse call it shares a thread to handle IO operations.
45    private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");
46
47    // A collection of FeatureAction.
48    // Note that access to this collection should happen in service thread.
49    private final LinkedList<FeatureAction> mActions = new LinkedList<>();
50
51    @Nullable
52    private HdmiCecController mCecController;
53
54    @Nullable
55    private HdmiMhlController mMhlController;
56
57    // Whether ARC is "enabled" or not.
58    // TODO: it may need to hold lock if it's accessed from others.
59    private boolean mArcStatusEnabled = false;
60
61    // Handler running on service thread. It's used to run a task in service thread.
62    private Handler mHandler = new Handler();
63
64    public HdmiControlService(Context context) {
65        super(context);
66    }
67
68    @Override
69    public void onStart() {
70        mCecController = HdmiCecController.create(this);
71        if (mCecController != null) {
72            mCecController.initializeLocalDevices(getContext().getResources()
73                    .getIntArray(com.android.internal.R.array.config_hdmiCecLogicalDeviceType));
74        } else {
75            Slog.i(TAG, "Device does not support HDMI-CEC.");
76        }
77
78        mMhlController = HdmiMhlController.create(this);
79        if (mMhlController == null) {
80            Slog.i(TAG, "Device does not support MHL-control.");
81        }
82    }
83
84    /**
85     * Returns {@link Looper} for IO operation.
86     *
87     * <p>Declared as package-private.
88     */
89    Looper getIoLooper() {
90        return mIoThread.getLooper();
91    }
92
93    /**
94     * Returns {@link Looper} of main thread. Use this {@link Looper} instance
95     * for tasks that are running on main service thread.
96     *
97     * <p>Declared as package-private.
98     */
99    Looper getServiceLooper() {
100        return mHandler.getLooper();
101    }
102
103    /**
104     * Add and start a new {@link FeatureAction} to the action queue.
105     *
106     * @param action {@link FeatureAction} to add and start
107     */
108    void addAndStartAction(final FeatureAction action) {
109        // TODO: may need to check the number of stale actions.
110        runOnServiceThread(new Runnable() {
111            @Override
112            public void run() {
113                mActions.add(action);
114                action.start();
115            }
116        });
117    }
118
119    /**
120     * Remove the given {@link FeatureAction} object from the action queue.
121     *
122     * @param action {@link FeatureAction} to remove
123     */
124    void removeAction(final FeatureAction action) {
125        runOnServiceThread(new Runnable() {
126            @Override
127            public void run() {
128                mActions.remove(action);
129            }
130        });
131    }
132
133    // Remove all actions matched with the given Class type.
134    private <T extends FeatureAction> void removeAction(final Class<T> clazz) {
135        runOnServiceThread(new Runnable() {
136            @Override
137            public void run() {
138                Iterator<FeatureAction> iter = mActions.iterator();
139                while (iter.hasNext()) {
140                    FeatureAction action = iter.next();
141                    if (action.getClass().equals(clazz)) {
142                        action.clear();
143                        mActions.remove(action);
144                    }
145                }
146            }
147        });
148    }
149
150    private void runOnServiceThread(Runnable runnable) {
151        mHandler.post(runnable);
152    }
153
154    /**
155     * Change ARC status into the given {@code enabled} status.
156     *
157     * @return {@code true} if ARC was in "Enabled" status
158     */
159    boolean setArcStatus(boolean enabled) {
160        boolean oldStatus = mArcStatusEnabled;
161        // 1. Enable/disable ARC circuit.
162        // TODO: call set_audio_return_channel of hal interface.
163
164        // 2. Update arc status;
165        mArcStatusEnabled = enabled;
166        return oldStatus;
167    }
168
169    /**
170     * Transmit a CEC command to CEC bus.
171     *
172     * @param command CEC command to send out
173     * @return {@code true} if succeeds to send command
174     */
175    boolean sendCecCommand(HdmiCecMessage command) {
176        return mCecController.sendCommand(command);
177    }
178
179    /**
180     * Add a new {@link HdmiCecDeviceInfo} to controller.
181     *
182     * @param deviceInfo new device information object to add
183     */
184    void addDeviceInfo(HdmiCecDeviceInfo deviceInfo) {
185        // TODO: Implement this.
186    }
187
188    boolean handleCecCommand(HdmiCecMessage message) {
189        // Commands that queries system information replies directly instead
190        // of creating FeatureAction because they are state-less.
191        switch (message.getOpcode()) {
192            case HdmiCec.MESSAGE_GET_MENU_LANGUAGE:
193                handleGetMenuLanguage(message);
194                return true;
195            case HdmiCec.MESSAGE_GIVE_OSD_NAME:
196                handleGiveOsdName(message);
197                return true;
198            case HdmiCec.MESSAGE_GIVE_PHYSICAL_ADDRESS:
199                handleGivePhysicalAddress(message);
200                return true;
201            case HdmiCec.MESSAGE_GIVE_DEVICE_VENDOR_ID:
202                handleGiveDeviceVendorId(message);
203                return true;
204            case HdmiCec.MESSAGE_GET_CEC_VERSION:
205                handleGetCecVersion(message);
206                return true;
207            case HdmiCec.MESSAGE_INITIATE_ARC:
208                handleInitiateArc(message);
209                return true;
210            case HdmiCec.MESSAGE_TERMINATE_ARC:
211                handleTerminateArc(message);
212                return true;
213            // TODO: Add remaining system information query such as
214            // <Give Device Power Status> and <Request Active Source> handler.
215            default:
216                Slog.w(TAG, "Unsupported cec command:" + message.toString());
217                return false;
218        }
219    }
220
221    /**
222     * Called when a new hotplug event is issued.
223     *
224     * @param port hdmi port number where hot plug event issued.
225     * @param connected whether to be plugged in or not
226     */
227    void onHotplug(int portNo, boolean connected) {
228        // TODO: Start "RequestArcInitiationAction" if ARC port.
229    }
230
231    private void handleInitiateArc(HdmiCecMessage message){
232        // In case where <Initiate Arc> is started by <Request ARC Initiation>
233        // need to clean up RequestArcInitiationAction.
234        removeAction(RequestArcInitiationAction.class);
235        SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
236                message.getDestination(), message.getSource(), true);
237        addAndStartAction(action);
238    }
239
240    private void handleTerminateArc(HdmiCecMessage message) {
241        // In case where <Terminate Arc> is started by <Request ARC Termination>
242        // need to clean up RequestArcInitiationAction.
243        // TODO: check conditions of power status by calling is_connected api
244        // to be added soon.
245        removeAction(RequestArcTerminationAction.class);
246        SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
247                message.getDestination(), message.getSource(), false);
248        addAndStartAction(action);
249    }
250
251    private void handleGetCecVersion(HdmiCecMessage message) {
252        int version = mCecController.getVersion();
253        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildCecVersion(message.getDestination(),
254                message.getSource(),
255                version);
256        sendCecCommand(cecMessage);
257    }
258
259    private void handleGiveDeviceVendorId(HdmiCecMessage message) {
260        int vendorId = mCecController.getVendorId();
261        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
262                message.getDestination(), vendorId);
263        sendCecCommand(cecMessage);
264    }
265
266    private void handleGivePhysicalAddress(HdmiCecMessage message) {
267        int physicalAddress = mCecController.getPhysicalAddress();
268        int deviceType = HdmiCec.getTypeFromAddress(message.getDestination());
269        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
270                message.getDestination(), physicalAddress, deviceType);
271        sendCecCommand(cecMessage);
272    }
273
274    private void handleGiveOsdName(HdmiCecMessage message) {
275        // TODO: read device name from settings or property.
276        String name = HdmiCec.getDefaultDeviceName(message.getDestination());
277        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildSetOsdNameCommand(
278                message.getDestination(), message.getSource(), name);
279        if (cecMessage != null) {
280            sendCecCommand(cecMessage);
281        } else {
282            Slog.w(TAG, "Failed to build <Get Osd Name>:" + name);
283        }
284    }
285
286    private void handleGetMenuLanguage(HdmiCecMessage message) {
287        // Only 0 (TV), 14 (specific use) can answer.
288        if (message.getDestination() != HdmiCec.ADDR_TV
289                && message.getDestination() != HdmiCec.ADDR_SPECIFIC_USE) {
290            Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString());
291            sendCecCommand(
292                    HdmiCecMessageBuilder.buildFeatureAbortCommand(message.getDestination(),
293                            message.getSource(), HdmiCec.MESSAGE_GET_MENU_LANGUAGE,
294                            HdmiCecMessageBuilder.ABORT_UNRECOGNIZED_MODE));
295            return;
296        }
297
298        HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand(
299                message.getDestination(),
300                Locale.getDefault().getISO3Language());
301        // TODO: figure out how to handle failed to get language code.
302        if (command != null) {
303            sendCecCommand(command);
304        } else {
305            Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString());
306        }
307    }
308}
309