HdmiControlService.java revision a1fa91fe263c483cf13066e2847a440de2cd52a5
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.HandlerThread;
25import android.os.Looper;
26import android.util.Slog;
27
28import com.android.server.SystemService;
29
30import java.util.Locale;
31
32/**
33 * Provides a service for sending and processing HDMI control messages,
34 * HDMI-CEC and MHL control command, and providing the information on both standard.
35 */
36public final class HdmiControlService extends SystemService {
37    private static final String TAG = "HdmiControlService";
38
39    // A thread to handle synchronous IO of CEC and MHL control service.
40    // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
41    // and sparse call it shares a thread to handle IO operations.
42    private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");
43
44    @Nullable
45    private HdmiCecController mCecController;
46
47    @Nullable
48    private HdmiMhlController mMhlController;
49
50    public HdmiControlService(Context context) {
51        super(context);
52    }
53
54    @Override
55    public void onStart() {
56        mCecController = HdmiCecController.create(this);
57        if (mCecController == null) {
58            Slog.i(TAG, "Device does not support HDMI-CEC.");
59        }
60
61        mMhlController = HdmiMhlController.create(this);
62        if (mMhlController == null) {
63            Slog.i(TAG, "Device does not support MHL-control.");
64        }
65    }
66
67    /**
68     * Returns {@link Looper} for IO operation.
69     *
70     * <p>Declared as package-private.
71     */
72    Looper getIoLooper() {
73        return mIoThread.getLooper();
74    }
75
76    /**
77     * Returns {@link Looper} of main thread. Use this {@link Looper} instance
78     * for tasks that are running on main service thread.
79     *
80     * <p>Declared as package-private.
81     */
82    Looper getServiceLooper() {
83        return Looper.myLooper();
84    }
85
86    /**
87     * Add a new {@link FeatureAction} to the action queue.
88     *
89     * @param action {@link FeatureAction} to add
90     */
91    void addAction(FeatureAction action) {
92        // TODO: Implement this.
93    }
94
95
96    /**
97     * Remove the given {@link FeatureAction} object from the action queue.
98     *
99     * @param action {@link FeatureAction} to add
100     */
101    void removeAction(FeatureAction action) {
102        // TODO: Implement this.
103    }
104
105    /**
106     * Transmit a CEC command to CEC bus.
107     *
108     * @param command CEC command to send out
109     */
110    void sendCecCommand(HdmiCecMessage command) {
111        mCecController.sendCommand(command);
112    }
113
114    /**
115     * Add a new {@link HdmiCecDeviceInfo} to controller.
116     *
117     * @param deviceInfo new device information object to add
118     */
119    void addDeviceInfo(HdmiCecDeviceInfo deviceInfo) {
120        // TODO: Implement this.
121    }
122
123    boolean handleCecCommand(HdmiCecMessage message) {
124        // Commands that queries system information replies directly instead
125        // of creating FeatureAction because they are state-less.
126        switch (message.getOpcode()) {
127            case HdmiCec.MESSAGE_GET_MENU_LANGUAGE:
128                handleGetMenuLanguage(message);
129                return true;
130            case HdmiCec.MESSAGE_GET_OSD_NAME:
131                handleGetOsdName(message);
132                return true;
133            case HdmiCec.MESSAGE_GIVE_PHYSICAL_ADDRESS:
134                handleGivePhysicalAddress(message);
135                return true;
136            case HdmiCec.MESSAGE_GIVE_DEVICE_VENDOR_ID:
137                handleGiveDeviceVendorId(message);
138                return true;
139            case HdmiCec.MESSAGE_GET_CEC_VERSION:
140                handleGetCecVersion(message);
141                return true;
142            // TODO: Add remaining system information query such as
143            // <Give Device Power Status> and <Request Active Source> handler.
144            default:
145                Slog.w(TAG, "Unsupported cec command:" + message.toString());
146                return false;
147        }
148    }
149
150    private void handleGetCecVersion(HdmiCecMessage message) {
151        int version = mCecController.getVersion();
152        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildCecVersion(message.getDestination(),
153                message.getSource(),
154                version);
155        sendCecCommand(cecMessage);
156    }
157
158    private void handleGiveDeviceVendorId(HdmiCecMessage message) {
159        int vendorId = mCecController.getVendorId();
160        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
161                message.getDestination(), vendorId);
162        sendCecCommand(cecMessage);
163    }
164
165    private void handleGivePhysicalAddress(HdmiCecMessage message) {
166        int physicalAddress = mCecController.getPhysicalAddress();
167        int deviceType = HdmiCec.getTypeFromAddress(message.getDestination());
168        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
169                message.getDestination(), physicalAddress, deviceType);
170        sendCecCommand(cecMessage);
171    }
172
173    private void handleGetOsdName(HdmiCecMessage message) {
174        // TODO: read device name from settings or property.
175        String name = HdmiCec.getDefaultDeviceName(message.getDestination());
176        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildSetOsdNameCommand(
177                message.getDestination(), message.getSource(), name);
178        if (cecMessage != null) {
179            sendCecCommand(cecMessage);
180        } else {
181            Slog.w(TAG, "Failed to build <Get Osd Name>:" + name);
182        }
183    }
184
185    private void handleGetMenuLanguage(HdmiCecMessage message) {
186        // Only 0 (TV), 14 (specific use) can answer.
187        if (message.getDestination() != HdmiCec.ADDR_TV
188                && message.getDestination() != HdmiCec.ADDR_SPECIFIC_USE) {
189            Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString());
190            sendCecCommand(
191                    HdmiCecMessageBuilder.buildFeatureAbortCommand(message.getDestination(),
192                            message.getSource(), HdmiCec.MESSAGE_GET_MENU_LANGUAGE,
193                            HdmiCecMessageBuilder.ABORT_UNRECOGNIZED_MODE));
194            return;
195        }
196
197        HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand(
198                message.getDestination(),
199                Locale.getDefault().getISO3Language());
200        // TODO: figure out how to handle failed to get language code.
201        if (command != null) {
202            sendCecCommand(command);
203        } else {
204            Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString());
205        }
206    }
207}
208