Add HDMI-CEC service
This CL adds a system service handling HDMI-CEC protocol. The service
is equipped with the capability sending/receiving HDMI-CEC messages
Not all the messages are in place. Currently it has messages to support
a few features only, as follows:
- One touch play
- System information
- Routing control (partially - active source status maintenance only)
- Device OSD transfer
- Power status
It will be extended to cover the wider usages in the follow up CLs.
The CEC standard version referenced in the implementation is 1.3a.
Change-Id: Ifed0b02f52ebf098eddb3bd0987efbf353b7e8fe
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecDevice.java
new file mode 100644
index 0000000..d30edf5
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiCecDevice.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.HdmiCecMessage;
+import android.hardware.hdmi.IHdmiCecDevice;
+import android.hardware.hdmi.IHdmiCecListener;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * CecDevice class represents a CEC logical device characterized
+ * by its device type. A physical device can contain the functions of
+ * more than one logical device, in which case it should create
+ * as many logical devices as necessary.
+ *
+ * <p>Note that if a physical device has multiple instances of a particular
+ * functionality, it should advertize only one instance. For instance, if
+ * a device has multiple tuners, it should only expose one for control
+ * via CEC. In this case, it is up to the device itself to manage multiple tuners.
+ *
+ * <p>The version of HDMI-CEC protocol supported in this class is 1.3a.
+ *
+ * <p>Declared as package-private, accessed by HdmiCecService only.
+ */
+final class HdmiCecDevice {
+ private static final String TAG = "HdmiCecDevice";
+
+ private final int mType;
+
+ // List of listeners to the message/event coming to the device.
+ private final List<IHdmiCecListener> mListeners = new ArrayList<IHdmiCecListener>();
+ private final Binder mBinder = new Binder();
+
+ private String mName;
+ private boolean mIsActiveSource;
+
+ /**
+ * Constructor.
+ */
+ public HdmiCecDevice(int type) {
+ mType = type;
+ mIsActiveSource = false;
+ }
+
+ /**
+ * Return the binder token that identifies this instance.
+ */
+ public Binder getToken() {
+ return mBinder;
+ }
+
+ /**
+ * Return the type of this device.
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Set the name of the device. The name will be transferred via the message
+ * <Set OSD Name> to other HDMI-CEC devices connected through HDMI
+ * cables and shown on TV screen to identify the devicie.
+ *
+ * @param name name of the device
+ */
+ public void setName(String name) {
+ mName = name;
+ }
+
+ /**
+ * Return the name of this device.
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Register a listener to be invoked when events occur.
+ *
+ * @param listener the listern that will run
+ */
+ public void addListener(IHdmiCecListener listener) {
+ mListeners.add(listener);
+ }
+
+ /**
+ * Remove the listener that was previously registered.
+ *
+ * @param listener IHdmiCecListener instance to be removed
+ */
+ public void removeListener(IHdmiCecListener listener) {
+ mListeners.remove(listener);
+ }
+
+ /**
+ * Indicate if the device has listeners.
+ *
+ * @return true if there are listener instances for this device
+ */
+ public boolean hasListener() {
+ return !mListeners.isEmpty();
+ }
+
+ /**
+ * Handle HDMI-CEC message coming to the device by invoking the registered
+ * listeners.
+ */
+ public void handleMessage(int srcAddress, int dstAddress, int opcode, byte[] params) {
+ if (opcode == HdmiCec.MESSAGE_ACTIVE_SOURCE) {
+ mIsActiveSource = false;
+ }
+ if (mListeners.size() == 0) {
+ return;
+ }
+ HdmiCecMessage message = new HdmiCecMessage(srcAddress, dstAddress, opcode, params);
+ for (IHdmiCecListener listener : mListeners) {
+ try {
+ listener.onMessageReceived(message);
+ } catch (RemoteException e) {
+ Log.e(TAG, "listener.onMessageReceived failed.");
+ }
+ }
+ }
+
+ public void handleHotplug(boolean connected) {
+ for (IHdmiCecListener listener : mListeners) {
+ try {
+ listener.onCableStatusChanged(connected);
+ } catch (RemoteException e) {
+ Log.e(TAG, "listener.onCableStatusChanged failed.");
+ }
+ }
+ }
+
+ /**
+ * Return the active status of the device.
+ *
+ * @return true if the device is the active source among the connected
+ * HDMI-CEC-enabled devices; otherwise false.
+ */
+ public boolean isActiveSource() {
+ return mIsActiveSource;
+ }
+
+ /**
+ * Update the active source state of the device.
+ */
+ public void setIsActiveSource(boolean state) {
+ mIsActiveSource = state;
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecService.java b/services/core/java/com/android/server/hdmi/HdmiCecService.java
new file mode 100644
index 0000000..01f2ec3
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiCecService.java
@@ -0,0 +1,394 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.HdmiCecMessage;
+import android.hardware.hdmi.IHdmiCecListener;
+import android.hardware.hdmi.IHdmiCecService;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.server.SystemService;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Locale;
+
+/**
+ * Provides a service for sending and processing HDMI-CEC messages, and providing
+ * the information on HDMI settings in general.
+ */
+public final class HdmiCecService extends SystemService {
+ private static final String TAG = "HdmiCecService";
+
+ // Maintains the allocated logical devices. Device type, not logical address,
+ // is used for key as logical address is likely to change over time while
+ // device type is permanent. Type-address mapping is maintained only at
+ // native level.
+ private final SparseArray<HdmiCecDevice> mLogicalDevices = new SparseArray<HdmiCecDevice>();
+
+ // List of IBinder.DeathRecipient instances to handle dead IHdmiCecListener
+ // objects.
+ private final ArrayList<ListenerRecord> mListenerRecords = new ArrayList<ListenerRecord>();
+
+ // Used to synchronize the access to the service.
+ private final Object mLock = new Object();
+
+ // Stores the pointer to the native implementation of the service that
+ // interacts with HAL.
+ private long mNativePtr;
+
+ private static final String PERMISSION = "android.permission.HDMI_CEC";
+
+ // Service name under which it is registered to service manager.
+ // TODO: Move this to Context once HdmiCecManager is introduced.
+ private static final String HDMI_CEC_SERVICE = "hdmi_cec";
+
+ public HdmiCecService(Context context) {
+ super(context);
+ }
+
+ private static native long nativeInit(HdmiCecService service);
+
+ @Override
+ public void onStart() {
+ mNativePtr = nativeInit(this);
+ publishBinderService(HDMI_CEC_SERVICE, new BinderService());
+ }
+
+ /**
+ * Called by native when an HDMI-CEC message arrived. Invokes the registered
+ * listeners to handle the message.
+ */
+ private void handleMessage(int srcAddress, int dstAddress, int opcode, byte[] params) {
+ // TODO: Messages like <Standby> may not need be passed to listener
+ // but better be handled in service by turning off the screen
+ // or putting the device into suspend mode. List up such messages
+ // and handle them here.
+ int type = HdmiCec.getTypeFromAddress(dstAddress);
+ synchronized (mLock) {
+ if (dstAddress == HdmiCec.ADDR_BROADCAST) {
+ for (int i = 0; i < mLogicalDevices.size(); ++i) {
+ mLogicalDevices.valueAt(i).handleMessage(srcAddress, dstAddress, opcode,
+ params);
+ }
+ } else {
+ HdmiCecDevice device = mLogicalDevices.get(type);
+ if (device == null) {
+ Log.w(TAG, "logical device not found. type: " + type);
+ return;
+ }
+ device.handleMessage(srcAddress, dstAddress, opcode, params);
+ }
+ }
+ }
+
+ /**
+ * Called by native when internal HDMI hotplug event occurs. Invokes the registered
+ * listeners to handle the event.
+ */
+ private void handleHotplug(boolean connected) {
+ synchronized(mLock) {
+ for (int i = 0; i < mLogicalDevices.size(); ++i) {
+ mLogicalDevices.valueAt(i).handleHotplug(connected);
+ }
+ }
+ }
+
+ /**
+ * Called by native when it needs to know whether we have an active source.
+ * The native part uses the return value to respond to <Request Active
+ * Source >.
+ *
+ * @return type of the device which is active; DEVICE_INACTIVE if there is
+ * no active logical device in the system.
+ */
+ private int getActiveSource() {
+ synchronized(mLock) {
+ for (int i = 0; i < mLogicalDevices.size(); ++i) {
+ if (mLogicalDevices.valueAt(i).isActiveSource()) {
+ return mLogicalDevices.keyAt(i);
+ }
+ }
+ }
+ return HdmiCec.DEVICE_INACTIVE;
+ }
+
+ /**
+ * Called by native when a request for the device OSD name was received.
+ * The native part uses the return value to generate the message
+ * <Set Osd Name> in response.
+ */
+ private byte[] getOsdName(int type) {
+ synchronized (mLock) {
+ HdmiCecDevice device = mLogicalDevices.get(type);
+ if (device != null) {
+ return device.getName().getBytes(Charset.forName("US-ASCII"));
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Called by native when a request for the menu language of the device was
+ * received. The native part uses the return value to generate the message
+ * <Set Menu Language> in response. The language should be of
+ * the 3-letter format as defined in ISO/FDIS 639-2. We use system default
+ * locale.
+ */
+ private String getLanguage(int type) {
+ return Locale.getDefault().getISO3Language();
+ }
+
+ private void enforceAccessPermission() {
+ getContext().enforceCallingOrSelfPermission(PERMISSION, "HdmiCecService");
+ }
+
+ private void dumpInternal(PrintWriter pw) {
+ pw.println("HdmiCecService (dumpsys hdmi_cec)");
+ pw.println("");
+ synchronized (mLock) {
+ for (int i = 0; i < mLogicalDevices.size(); ++i) {
+ HdmiCecDevice device = mLogicalDevices.valueAt(i);
+ pw.println("Device: name=" + device.getName() +
+ ", type=" + device.getType() +
+ ", active=" + device.isActiveSource());
+ }
+ }
+ }
+
+ // Remove logical device of a given type.
+ private void removeLogicalDeviceLocked(int type) {
+ ensureValidType(type);
+ mLogicalDevices.remove(type);
+ nativeRemoveLogicalAddress(mNativePtr, type);
+ }
+
+ private static void ensureValidType(int type) {
+ if (!HdmiCec.isValidType(type)) {
+ throw new IllegalArgumentException("invalid type: " + type);
+ }
+ }
+
+ // Return the logical device identified by the given binder token.
+ private HdmiCecDevice getLogicalDeviceLocked(IBinder b) {
+ for (int i = 0; i < mLogicalDevices.size(); ++i) {
+ HdmiCecDevice device = mLogicalDevices.valueAt(i);
+ if (device.getToken() == b) {
+ return device;
+ }
+ }
+ throw new IllegalArgumentException("Device not found");
+ }
+
+ private final class ListenerRecord implements IBinder.DeathRecipient {
+ private final IHdmiCecListener mListener;
+ private final int mType;
+
+ public ListenerRecord(IHdmiCecListener listener, int type) {
+ mListener = listener;
+ mType = type;
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (mLock) {
+ mListenerRecords.remove(this);
+ HdmiCecDevice device = mLogicalDevices.get(mType);
+ if (device != null) {
+ device.removeListener(mListener);
+ if (!device.hasListener()) {
+ removeLogicalDeviceLocked(mType);
+ }
+ }
+ }
+ }
+ }
+
+ private final class BinderService extends IHdmiCecService.Stub {
+
+ @Override
+ public IBinder allocateLogicalDevice(int type, IHdmiCecListener listener) {
+ enforceAccessPermission();
+ ensureValidType(type);
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+ synchronized (mLock) {
+ HdmiCecDevice device = mLogicalDevices.get(type);
+ if (device != null) {
+ Log.v(TAG, "Logical address already allocated. Adding listener only.");
+ } else {
+ int address = nativeAllocateLogicalAddress(mNativePtr, type);
+ if (!HdmiCec.isValidAddress(address)) {
+ Log.e(TAG, "Logical address was not allocated");
+ return null;
+ } else {
+ device = new HdmiCecDevice(type);
+ device.setName(HdmiCec.getDefaultDeviceName(address));
+ mLogicalDevices.put(type, device);
+ }
+ }
+
+ // Adds the listener and its monitor
+ ListenerRecord record = new ListenerRecord(listener, type);
+ try {
+ listener.asBinder().linkToDeath(record, 0);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Listener already died");
+ if (!device.hasListener()) {
+ removeLogicalDeviceLocked(type);
+ }
+ return null;
+ }
+ mListenerRecords.add(record);
+ device.addListener(listener);
+ return device.getToken();
+ }
+ }
+
+ @Override
+ public void setOsdName(IBinder b, String name) {
+ enforceAccessPermission();
+ if (TextUtils.isEmpty(name)) {
+ throw new IllegalArgumentException("name must not be null");
+ }
+ synchronized (mLock) {
+ HdmiCecDevice device = getLogicalDeviceLocked(b);
+ device.setName(name);
+ }
+ }
+
+ @Override
+ public void sendActiveSource(IBinder b) {
+ enforceAccessPermission();
+ synchronized (mLock) {
+ HdmiCecDevice device = getLogicalDeviceLocked(b);
+ device.setIsActiveSource(true);
+ nativeSendActiveSource(mNativePtr, device.getType());
+ }
+ }
+
+ @Override
+ public void sendInactiveSource(IBinder b) {
+ enforceAccessPermission();
+ synchronized (mLock) {
+ HdmiCecDevice device = getLogicalDeviceLocked(b);
+ device.setIsActiveSource(false);
+ nativeSendInactiveSource(mNativePtr, device.getType());
+ }
+ }
+
+ @Override
+ public void sendImageViewOn(IBinder b) {
+ enforceAccessPermission();
+ synchronized (mLock) {
+ HdmiCecDevice device = getLogicalDeviceLocked(b);
+ nativeSendImageViewOn(mNativePtr, device.getType());
+ }
+ }
+
+ @Override
+ public void sendTextViewOn(IBinder b) {
+ enforceAccessPermission();
+ synchronized (mLock) {
+ HdmiCecDevice device = getLogicalDeviceLocked(b);
+ nativeSendTextViewOn(mNativePtr, device.getType());
+ }
+ }
+
+ @Override
+ public void sendGiveDevicePowerStatus(IBinder b, int address) {
+ enforceAccessPermission();
+ synchronized (mLock) {
+ HdmiCecDevice device = getLogicalDeviceLocked(b);
+ nativeSendGiveDevicePowerStatus(mNativePtr, device.getType(), address);
+ }
+ }
+
+ @Override
+ public void removeServiceListener(IBinder b, IHdmiCecListener listener) {
+ enforceAccessPermission();
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+ synchronized (mLock) {
+ HdmiCecDevice device = getLogicalDeviceLocked(b);
+ for (ListenerRecord record : mListenerRecords) {
+ if (record.mType == device.getType()
+ && record.mListener.asBinder() == listener.asBinder()) {
+ mListenerRecords.remove(record);
+ device.removeListener(record.mListener);
+ if (!device.hasListener()) {
+ removeLogicalDeviceLocked(record.mType);
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ @Override
+ public void sendMessage(IBinder b, HdmiCecMessage message) {
+ enforceAccessPermission();
+ if (message == null) {
+ throw new IllegalArgumentException("message must not be null");
+ }
+ synchronized (mLock) {
+ HdmiCecDevice device = getLogicalDeviceLocked(b);
+ nativeSendMessage(mNativePtr, device.getType(), message.getDestination(),
+ message.getOpcode(), message.getParams());
+ }
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission denial: can't dump HdmiCecService from pid="
+ + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ dumpInternal(pw);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
+ private static native int nativeAllocateLogicalAddress(long handler, int deviceType);
+ private static native void nativeRemoveLogicalAddress(long handler, int deviceType);
+ private static native void nativeSendMessage(long handler, int deviceType, int destination,
+ int opcode, byte[] params);
+ private static native void nativeSendActiveSource(long handler, int deviceType);
+ private static native void nativeSendInactiveSource(long handler, int deviceType);
+ private static native void nativeSendImageViewOn(long handler, int deviceType);
+ private static native void nativeSendTextViewOn(long handler, int deviceType);
+ private static native void nativeSendGiveDevicePowerStatus(long handler, int deviceType,
+ int address);
+}
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index d1cfff4..0843e94 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -5,23 +5,26 @@
LOCAL_SRC_FILES += \
$(LOCAL_REL_DIR)/com_android_server_AlarmManagerService.cpp \
$(LOCAL_REL_DIR)/com_android_server_AssetAtlasService.cpp \
+ $(LOCAL_REL_DIR)/com_android_server_connectivity_Vpn.cpp \
$(LOCAL_REL_DIR)/com_android_server_ConsumerIrService.cpp \
$(LOCAL_REL_DIR)/com_android_server_dreams_McuHal.cpp \
+ $(LOCAL_REL_DIR)/com_android_server_hdmi_HdmiCecService.cpp \
$(LOCAL_REL_DIR)/com_android_server_input_InputApplicationHandle.cpp \
$(LOCAL_REL_DIR)/com_android_server_input_InputManagerService.cpp \
$(LOCAL_REL_DIR)/com_android_server_input_InputWindowHandle.cpp \
$(LOCAL_REL_DIR)/com_android_server_lights_LightsService.cpp \
+ $(LOCAL_REL_DIR)/com_android_server_location_GpsLocationProvider.cpp \
+ $(LOCAL_REL_DIR)/com_android_server_location_FlpHardwareProvider.cpp \
$(LOCAL_REL_DIR)/com_android_server_power_PowerManagerService.cpp \
$(LOCAL_REL_DIR)/com_android_server_SerialService.cpp \
$(LOCAL_REL_DIR)/com_android_server_SystemServer.cpp \
$(LOCAL_REL_DIR)/com_android_server_UsbDeviceManager.cpp \
$(LOCAL_REL_DIR)/com_android_server_UsbHostManager.cpp \
$(LOCAL_REL_DIR)/com_android_server_VibratorService.cpp \
- $(LOCAL_REL_DIR)/com_android_server_location_GpsLocationProvider.cpp \
- $(LOCAL_REL_DIR)/com_android_server_location_FlpHardwareProvider.cpp \
- $(LOCAL_REL_DIR)/com_android_server_connectivity_Vpn.cpp \
$(LOCAL_REL_DIR)/onload.cpp
+include external/stlport/libstlport.mk
+
LOCAL_C_INCLUDES += \
$(JNI_H_INCLUDE) \
frameworks/base/services \
diff --git a/services/core/jni/com_android_server_hdmi_HdmiCecService.cpp b/services/core/jni/com_android_server_hdmi_HdmiCecService.cpp
new file mode 100644
index 0000000..6170c09
--- /dev/null
+++ b/services/core/jni/com_android_server_hdmi_HdmiCecService.cpp
@@ -0,0 +1,689 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "HdmiCecJni"
+
+#define LOG_NDEBUG 1
+
+#include "ScopedPrimitiveArray.h"
+
+#include <cstring>
+#include <deque>
+#include <map>
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+#include <hardware/hdmi_cec.h>
+
+namespace android {
+
+static struct {
+ jmethodID handleMessage;
+ jmethodID handleHotplug;
+ jmethodID getActiveSource;
+ jmethodID getOsdName;
+ jmethodID getLanguage;
+} gHdmiCecServiceClassInfo;
+
+#ifndef min
+#define min(a, b) ((a) > (b) ? (b) : (a))
+#endif
+
+class HdmiCecHandler {
+public:
+ enum HdmiCecError {
+ SUCCESS = 0,
+ FAILED = -1
+ };
+
+ // Data type to hold a CEC message or internal event data.
+ typedef union {
+ cec_message_t cec;
+ hotplug_event_t hotplug;
+ } queue_item_t;
+
+ // Entry used for message queue.
+ typedef std::pair<int, const queue_item_t> MessageEntry;
+
+ HdmiCecHandler(hdmi_cec_device_t* device, jobject callbacksObj);
+
+ void initialize();
+
+ // initialize individual logical device.
+ int initLogicalDevice(int type);
+ void releaseLogicalDevice(int type);
+
+ cec_logical_address_t getLogicalAddress(int deviceType);
+ int getDeviceType(cec_logical_address_t addr);
+ void queueMessage(const MessageEntry& message);
+ void queueOutgoingMessage(const cec_message_t& message);
+ void sendReportPhysicalAddress();
+ void sendActiveSource(cec_logical_address_t srcAddr);
+ void sendInactiveSource(cec_logical_address_t srcAddr);
+ void sendImageViewOn(cec_logical_address_t srcAddr);
+ void sendTextViewOn(cec_logical_address_t srcAddr);
+ void sendGiveDevicePowerStatus(cec_logical_address_t srcAddr, cec_logical_address_t dstAddr);
+ void sendFeatureAbort(cec_logical_address_t srcAddr, cec_logical_address_t dstAddr,
+ int opcode, int reason);
+ void sendCecVersion(cec_logical_address_t srcAddr, cec_logical_address_t dstAddr,
+ int version);
+ void sendDeviceVendorID(cec_logical_address_t srcAddr, cec_logical_address_t dstAddr);
+ void sendGiveDeviceVendorID(cec_logical_address_t srcAddr, cec_logical_address_t dstAddr);
+ void sendSetOsdName(cec_logical_address_t srcAddr, cec_logical_address_t dstAddr,
+ const char* name, size_t len);
+ void sendSetMenuLanguage(cec_logical_address_t srcAddr, cec_logical_address_t dstAddr);
+
+ void sendCecMessage(const cec_message_t& message);
+
+private:
+ enum {
+ EVENT_TYPE_RX,
+ EVENT_TYPE_TX,
+ EVENT_TYPE_HOTPLUG,
+ EVENT_TYPE_STANDBY
+ };
+
+ static const unsigned int MAX_BUFFER_SIZE = 256;
+ static const uint16_t INVALID_PHYSICAL_ADDRESS = 0xFFFF;
+ static const int INACTIVE_DEVICE_TYPE = -1;
+
+ static void onReceived(const hdmi_event_t* event, void* arg);
+ static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);
+
+ void updatePhysicalAddress();
+ void dispatchMessage(const MessageEntry& message);
+ void processIncomingMessage(const cec_message_t& msg);
+
+ // Check the message before we pass it up to framework. If true, we proceed.
+ // otherwise do not propagate it.
+ bool precheckMessage(const cec_message_t& msg);
+
+ // Propagate the message up to Java layer.
+ void propagateMessage(const cec_message_t& msg);
+ void propagateHotplug(bool connected);
+
+ // Handles incoming <Request Active Source> message. If one of logical
+ // devices is active, it should reply with <Active Source> message.
+ void handleRequestActiveSource();
+ void handleGetOsdName(const cec_message_t& msg);
+ void handleGiveDeviceVendorID(const cec_message_t& msg);
+ void handleGetCECVersion(const cec_message_t& msg);
+ void handleGetMenuLanguage(const cec_message_t& msg);
+
+ // Internal thread for message queue handler
+ class HdmiThread : public Thread {
+ public:
+ HdmiThread(HdmiCecHandler* hdmiCecHandler, bool canCallJava) :
+ Thread(canCallJava),
+ mHdmiCecHandler(hdmiCecHandler) {
+ }
+ private:
+ virtual bool threadLoop() {
+ ALOGV("HdmiThread started");
+ AutoMutex _l(mHdmiCecHandler->mMessageQueueLock);
+ mHdmiCecHandler->mMessageQueueCondition.wait(mHdmiCecHandler->mMessageQueueLock);
+ /* Process all messages in the queue */
+ while (mHdmiCecHandler->mMessageQueue.size() > 0) {
+ MessageEntry entry = mHdmiCecHandler->mMessageQueue.front();
+ mHdmiCecHandler->dispatchMessage(entry);
+ }
+ return true;
+ }
+
+ HdmiCecHandler* mHdmiCecHandler;
+ };
+
+ // device type -> logical address mapping
+ std::map<int, cec_logical_address_t> mLogicalDevices;
+
+ hdmi_cec_device_t* mDevice;
+ jobject mCallbacksObj;
+ Mutex mLock;
+ Mutex mMessageQueueLock;
+ Condition mMessageQueueCondition;
+ sp<HdmiThread> mMessageQueueHandler;
+
+ std::deque<MessageEntry> mMessageQueue;
+ uint16_t mPhysicalAddress;
+};
+
+
+HdmiCecHandler::HdmiCecHandler(hdmi_cec_device_t* device, jobject callbacksObj) :
+ mDevice(device),
+ mCallbacksObj(callbacksObj) {
+}
+
+void HdmiCecHandler::initialize() {
+ mDevice->register_event_callback(mDevice, HdmiCecHandler::onReceived, this);
+ mMessageQueueHandler = new HdmiThread(this, true /* canCallJava */);
+ mMessageQueueHandler->run("MessageHandler");
+ updatePhysicalAddress();
+}
+
+void HdmiCecHandler::updatePhysicalAddress() {
+ uint16_t addr;
+ if (!mDevice->get_physical_address(mDevice, &addr)) {
+ mPhysicalAddress = addr;
+ } else {
+ mPhysicalAddress = INVALID_PHYSICAL_ADDRESS;
+ }
+}
+
+int HdmiCecHandler::initLogicalDevice(int type) {
+ cec_logical_address_t addr;
+ int res = mDevice->allocate_logical_address(mDevice, type, &addr);
+
+ if (res != 0) {
+ ALOGE("Logical Address Allocation failed: %d", res);
+ } else {
+ ALOGV("Logical Address Allocation success: %d", addr);
+ mLogicalDevices.insert(std::pair<int, cec_logical_address_t>(type, addr));
+ }
+ return addr;
+}
+
+void HdmiCecHandler::releaseLogicalDevice(int type) {
+ std::map<int, cec_logical_address_t>::iterator it = mLogicalDevices.find(type);
+ if (it != mLogicalDevices.end()) {
+ mLogicalDevices.erase(it);
+ }
+ // TODO: remove the address monitored in HAL as well.
+}
+
+cec_logical_address_t HdmiCecHandler::getLogicalAddress(int mDevicetype) {
+ std::map<int, cec_logical_address_t>::iterator it = mLogicalDevices.find(mDevicetype);
+ if (it != mLogicalDevices.end()) {
+ return it->second;
+ }
+ return CEC_ADDR_UNREGISTERED;
+}
+
+int HdmiCecHandler::getDeviceType(cec_logical_address_t addr) {
+ std::map<int, cec_logical_address_t>::iterator it = mLogicalDevices.begin();
+ for (; it != mLogicalDevices.end(); ++it) {
+ if (it->second == addr) {
+ return it->first;
+ }
+ }
+ return INACTIVE_DEVICE_TYPE;
+}
+
+void HdmiCecHandler::queueMessage(const MessageEntry& entry) {
+ AutoMutex _l(mMessageQueueLock);
+ if (mMessageQueue.size() <= MAX_BUFFER_SIZE) {
+ mMessageQueue.push_back(entry);
+ mMessageQueueCondition.signal();
+ } else {
+ ALOGW("Queue is full! Message dropped.");
+ }
+}
+
+void HdmiCecHandler::queueOutgoingMessage(const cec_message_t& message) {
+ queue_item_t item;
+ item.cec = message;
+ MessageEntry entry = std::make_pair(EVENT_TYPE_TX, item);
+ queueMessage(entry);
+}
+
+void HdmiCecHandler::sendReportPhysicalAddress() {
+ if (mPhysicalAddress == INVALID_PHYSICAL_ADDRESS) {
+ ALOGE("Invalid physical address.");
+ return;
+ }
+
+ // Report physical address for each logical one hosted in it.
+ std::map<int, cec_logical_address_t>::iterator it = mLogicalDevices.begin();
+ while (it != mLogicalDevices.end()) {
+ cec_message_t msg;
+ msg.initiator = it->second; // logical address
+ msg.destination = CEC_ADDR_BROADCAST;
+ msg.length = 4;
+ msg.body[0] = CEC_MESSAGE_REPORT_PHYSICAL_ADDRESS;
+ std::memcpy(msg.body + 1, &mPhysicalAddress, 2);
+ msg.body[3] = it->first; // device type
+ queueOutgoingMessage(msg);
+ ++it;
+ }
+}
+
+void HdmiCecHandler::sendActiveSource(cec_logical_address_t srcAddr) {
+ if (mPhysicalAddress == INVALID_PHYSICAL_ADDRESS) {
+ ALOGE("Error getting physical address.");
+ return;
+ }
+ cec_message_t msg;
+ msg.initiator = srcAddr;
+ msg.destination = CEC_ADDR_BROADCAST;
+ msg.length = 3;
+ msg.body[0] = CEC_MESSAGE_ACTIVE_SOURCE;
+ std::memcpy(msg.body + 1, &mPhysicalAddress, 2);
+ queueOutgoingMessage(msg);
+}
+
+void HdmiCecHandler::sendInactiveSource(cec_logical_address_t srcAddr) {
+ cec_message_t msg;
+ msg.initiator = srcAddr;
+ msg.destination = CEC_ADDR_TV;
+ msg.length = 3;
+ msg.body[0] = CEC_MESSAGE_INACTIVE_SOURCE;
+ if (mPhysicalAddress != INVALID_PHYSICAL_ADDRESS) {
+ std::memcpy(msg.body + 1, &mPhysicalAddress, 2);
+ queueOutgoingMessage(msg);
+ }
+}
+
+void HdmiCecHandler::sendImageViewOn(cec_logical_address_t srcAddr) {
+ cec_message_t msg;
+ msg.initiator = srcAddr;
+ msg.destination = CEC_ADDR_TV;
+ msg.length = 1;
+ msg.body[0] = CEC_MESSAGE_IMAGE_VIEW_ON;
+ queueOutgoingMessage(msg);
+}
+
+void HdmiCecHandler::sendTextViewOn(cec_logical_address_t srcAddr) {
+ cec_message_t msg;
+ msg.initiator = srcAddr;
+ msg.destination = CEC_ADDR_TV;
+ msg.length = 1;
+ msg.body[0] = CEC_MESSAGE_TEXT_VIEW_ON;
+ queueOutgoingMessage(msg);
+}
+
+void HdmiCecHandler::sendGiveDevicePowerStatus(cec_logical_address_t srcAddr,
+ cec_logical_address_t dstAddr) {
+ cec_message_t msg;
+ msg.initiator = srcAddr;
+ msg.destination = dstAddr;
+ msg.length = 1;
+ msg.body[0] = CEC_MESSAGE_GIVE_DEVICE_POWER_STATUS;
+ queueOutgoingMessage(msg);
+}
+
+void HdmiCecHandler::sendFeatureAbort(cec_logical_address_t srcAddr,
+ cec_logical_address_t dstAddr, int opcode, int reason) {
+ cec_message_t msg;
+ msg.initiator = srcAddr;
+ msg.destination = dstAddr;
+ msg.length = 3;
+ msg.body[0] = CEC_MESSAGE_FEATURE_ABORT;
+ msg.body[1] = opcode;
+ msg.body[2] = reason;
+ queueOutgoingMessage(msg);
+}
+
+void HdmiCecHandler::sendCecVersion(cec_logical_address_t srcAddr,
+ cec_logical_address_t dstAddr, int version) {
+ cec_message_t msg;
+ msg.initiator = srcAddr;
+ msg.destination = dstAddr;
+ msg.length = 2;
+ msg.body[0] = CEC_MESSAGE_CEC_VERSION;
+ msg.body[1] = version;
+ queueOutgoingMessage(msg);
+}
+
+void HdmiCecHandler::sendGiveDeviceVendorID(cec_logical_address_t srcAddr,
+ cec_logical_address_t dstAddr) {
+ cec_message_t msg;
+ msg.initiator = srcAddr;
+ msg.destination = dstAddr;
+ msg.length = 1;
+ msg.body[0] = CEC_MESSAGE_GIVE_DEVICE_VENDOR_ID;
+ queueOutgoingMessage(msg);
+}
+
+void HdmiCecHandler::sendDeviceVendorID(cec_logical_address_t srcAddr,
+ cec_logical_address_t dstAddr) {
+ cec_message_t msg;
+ msg.initiator = srcAddr;
+ msg.destination = dstAddr;
+ msg.length = 4;
+ msg.body[0] = CEC_MESSAGE_DEVICE_VENDOR_ID;
+ uint32_t vendor_id;
+ mDevice->get_vendor_id(mDevice, &vendor_id);
+ std::memcpy(msg.body + 1, &vendor_id, 3);
+ queueOutgoingMessage(msg);
+}
+
+void HdmiCecHandler::sendSetOsdName(cec_logical_address_t srcAddr, cec_logical_address_t dstAddr,
+ const char* name, size_t len) {
+ cec_message_t msg;
+ msg.initiator = srcAddr;
+ msg.destination = dstAddr;
+ msg.body[0] = CEC_MESSAGE_SET_OSD_NAME;
+ msg.length = min(len + 1, CEC_MESSAGE_BODY_MAX_LENGTH);
+ std::memcpy(msg.body + 1, name, msg.length - 1);
+ queueOutgoingMessage(msg);
+}
+
+void HdmiCecHandler::sendSetMenuLanguage(cec_logical_address_t srcAddr,
+ cec_logical_address_t dstAddr) {
+ char lang[4]; // buffer for 3-letter language code
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ jstring res = (jstring) env->CallObjectMethod(mCallbacksObj,
+ gHdmiCecServiceClassInfo.getLanguage,
+ getDeviceType(srcAddr));
+ const char *clang = env->GetStringUTFChars(res, NULL);
+ strlcpy(lang, clang, sizeof(lang));
+ env->ReleaseStringUTFChars(res, clang);
+
+ cec_message_t msg;
+ msg.initiator = srcAddr;
+ msg.destination = dstAddr;
+ msg.length = 4; // opcode (1) + language code (3)
+ msg.body[0] = CEC_MESSAGE_SET_MENU_LANGUAGE;
+ std::memcpy(msg.body + 1, lang, 3);
+ queueOutgoingMessage(msg);
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+}
+
+void HdmiCecHandler::sendCecMessage(const cec_message_t& message) {
+ AutoMutex _l(mLock);
+ ALOGV("sendCecMessage");
+ mDevice->send_message(mDevice, &message);
+}
+
+// static
+void HdmiCecHandler::onReceived(const hdmi_event_t* event, void* arg) {
+ HdmiCecHandler* handler = static_cast<HdmiCecHandler*>(arg);
+ if (handler == NULL) {
+ return;
+ }
+ queue_item_t item;
+ if (event->type == HDMI_EVENT_CEC_MESSAGE) {
+ item.cec = event->cec;
+ MessageEntry entry = std::make_pair<int, const queue_item_t>(EVENT_TYPE_RX, item);
+ handler->queueMessage(entry);
+ } else if (event->type == HDMI_EVENT_HOT_PLUG) {
+ item.hotplug = event->hotplug;
+ MessageEntry entry = std::make_pair<int, const queue_item_t>(EVENT_TYPE_HOTPLUG, item);
+ handler->queueMessage(entry);
+ }
+}
+
+// static
+void HdmiCecHandler::checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
+ if (env->ExceptionCheck()) {
+ ALOGE("An exception was thrown by callback '%s'.", methodName);
+ LOGE_EX(env);
+ env->ExceptionClear();
+ }
+}
+
+void HdmiCecHandler::dispatchMessage(const MessageEntry& entry) {
+ int type = entry.first;
+ mMessageQueueLock.unlock();
+ if (type == EVENT_TYPE_RX) {
+ mMessageQueue.pop_front();
+ processIncomingMessage(entry.second.cec);
+ } else if (type == EVENT_TYPE_TX) {
+ sendCecMessage(entry.second.cec);
+ mMessageQueue.pop_front();
+ } else if (type == EVENT_TYPE_HOTPLUG) {
+ mMessageQueue.pop_front();
+ bool connected = entry.second.hotplug.connected;
+ if (connected) {
+ // TODO: Update logical addresses as well, since they also could have
+ // changed while the cable was disconnected.
+ updatePhysicalAddress();
+ }
+ propagateHotplug(connected);
+ }
+ mMessageQueueLock.lock();
+}
+
+void HdmiCecHandler::processIncomingMessage(const cec_message_t& msg) {
+ int opcode = msg.body[0];
+ if (opcode == CEC_MESSAGE_GIVE_PHYSICAL_ADDRESS) {
+ sendReportPhysicalAddress();
+ } else if (opcode == CEC_MESSAGE_REQUEST_ACTIVE_SOURCE) {
+ handleRequestActiveSource();
+ } else if (opcode == CEC_MESSAGE_GET_OSD_NAME) {
+ handleGetOsdName(msg);
+ } else if (opcode == CEC_MESSAGE_GIVE_DEVICE_VENDOR_ID) {
+ handleGiveDeviceVendorID(msg);
+ } else if (opcode == CEC_MESSAGE_GET_CEC_VERSION) {
+ handleGetCECVersion(msg);
+ } else if (opcode == CEC_MESSAGE_GET_MENU_LANGUAGE) {
+ handleGetMenuLanguage(msg);
+ } else {
+ if (precheckMessage(msg)) {
+ propagateMessage(msg);
+ }
+ }
+}
+
+bool HdmiCecHandler::precheckMessage(const cec_message_t& msg) {
+ // Check if this is the broadcast message coming to itself, which need not be passed
+ // back to framework. This happens because CEC spec specifies that a physical device
+ // may host multiple logical devices. A broadcast message sent by one of them therefore
+ // should be able to reach the others by the loopback mechanism.
+ //
+ // Currently we don't deal with multiple logical devices, so this is not necessary.
+ // It should be revisited once we support hosting multiple logical devices.
+ int opcode = msg.body[0];
+ if (msg.destination == CEC_ADDR_BROADCAST &&
+ (opcode == CEC_MESSAGE_ACTIVE_SOURCE ||
+ opcode == CEC_MESSAGE_SET_STREAM_PATH ||
+ opcode == CEC_MESSAGE_INACTIVE_SOURCE)) {
+ uint16_t senderAddr;
+ std::memcpy(&senderAddr, &msg.body[1], 2);
+ if (senderAddr == mPhysicalAddress) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void HdmiCecHandler::propagateMessage(const cec_message_t& msg) {
+ int paramLen = msg.length - 1;
+ jint srcAddr = msg.initiator;
+ jint dstAddr = msg.destination;
+ jint opcode = msg.body[0];
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ jbyteArray params = env->NewByteArray(paramLen);
+ const jbyte* body = reinterpret_cast<const jbyte *>(msg.body + 1);
+ if (paramLen > 0) {
+ env->SetByteArrayRegion(params, 0, paramLen, body);
+ }
+ env->CallVoidMethod(mCallbacksObj,
+ gHdmiCecServiceClassInfo.handleMessage,
+ srcAddr, dstAddr, opcode, params);
+ env->DeleteLocalRef(params);
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+}
+
+void HdmiCecHandler::propagateHotplug(bool connected) {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->CallVoidMethod(mCallbacksObj,
+ gHdmiCecServiceClassInfo.handleHotplug,
+ connected);
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+}
+
+
+void HdmiCecHandler::handleRequestActiveSource() {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ jint activeDeviceType = env->CallIntMethod(mCallbacksObj,
+ gHdmiCecServiceClassInfo.getActiveSource);
+ if (activeDeviceType != INACTIVE_DEVICE_TYPE) {
+ sendActiveSource(getLogicalAddress(activeDeviceType));
+ }
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+}
+
+void HdmiCecHandler::handleGetOsdName(const cec_message_t& msg) {
+ cec_logical_address_t addr = msg.destination;
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ jbyteArray res = (jbyteArray) env->CallObjectMethod(mCallbacksObj,
+ gHdmiCecServiceClassInfo.getOsdName,
+ getDeviceType(addr));
+ jbyte *name = env->GetByteArrayElements(res, NULL);
+ if (name != NULL) {
+ sendSetOsdName(addr, msg.initiator, reinterpret_cast<const char *>(name),
+ env->GetArrayLength(res));
+ env->ReleaseByteArrayElements(res, name, JNI_ABORT);
+ }
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+}
+
+void HdmiCecHandler::handleGiveDeviceVendorID(const cec_message_t& msg) {
+ sendDeviceVendorID(msg.destination, msg.initiator);
+}
+
+void HdmiCecHandler::handleGetCECVersion(const cec_message_t& msg) {
+ int version;
+ mDevice->get_version(mDevice, &version);
+ sendCecVersion(msg.destination, msg.initiator, version);
+}
+
+void HdmiCecHandler::handleGetMenuLanguage(const cec_message_t& msg) {
+ sendSetMenuLanguage(msg.destination, msg.initiator);
+}
+
+//------------------------------------------------------------------------------
+
+#define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \
+ var = env->GetMethodID(clazz, methodName, methodDescriptor); \
+ LOG_FATAL_IF(! var, "Unable to find method " methodName);
+
+static jlong nativeInit(JNIEnv* env, jclass clazz, jobject callbacksObj) {
+ int err;
+ hw_module_t* module;
+ err = hw_get_module(HDMI_CEC_HARDWARE_MODULE_ID, const_cast<const hw_module_t **>(&module));
+ if (err != 0) {
+ ALOGE("Error acquiring hardware module: %d", err);
+ return 0;
+ }
+ hw_device_t* device;
+ err = module->methods->open(module, HDMI_CEC_HARDWARE_INTERFACE, &device);
+ if (err != 0) {
+ ALOGE("Error opening hardware module: %d", err);
+ return 0;
+ }
+ HdmiCecHandler *handler = new HdmiCecHandler(reinterpret_cast<hdmi_cec_device *>(device),
+ env->NewGlobalRef(callbacksObj));
+ handler->initialize();
+
+ GET_METHOD_ID(gHdmiCecServiceClassInfo.handleMessage, clazz,
+ "handleMessage", "(III[B)V");
+ GET_METHOD_ID(gHdmiCecServiceClassInfo.handleHotplug, clazz,
+ "handleHotplug", "(Z)V");
+ GET_METHOD_ID(gHdmiCecServiceClassInfo.getActiveSource, clazz,
+ "getActiveSource", "()I");
+ GET_METHOD_ID(gHdmiCecServiceClassInfo.getOsdName, clazz,
+ "getOsdName", "(I)[B");
+ GET_METHOD_ID(gHdmiCecServiceClassInfo.getLanguage, clazz,
+ "getLanguage", "(I)Ljava/lang/String;");
+
+ return reinterpret_cast<jlong>(handler);
+}
+
+static void nativeSendMessage(JNIEnv* env, jclass clazz, jlong handlerPtr, jint deviceType,
+ jint dstAddr, jint opcode, jbyteArray params) {
+ HdmiCecHandler *handler = reinterpret_cast<HdmiCecHandler *>(handlerPtr);
+ cec_logical_address_t srcAddr = handler->getLogicalAddress(deviceType);
+ jsize len = env->GetArrayLength(params);
+ ScopedByteArrayRO paramsPtr(env, params);
+ cec_message_t message;
+ message.initiator = srcAddr;
+ message.destination = static_cast<cec_logical_address_t>(dstAddr);
+ message.length = len + 1;
+ message.body[0] = opcode;
+ std::memcpy(message.body + 1, paramsPtr.get(), len);
+ handler->sendCecMessage(message);
+}
+
+static int nativeAllocateLogicalAddress(JNIEnv* env, jclass clazz, jlong handlerPtr,
+ jint deviceType) {
+ HdmiCecHandler *handler = reinterpret_cast<HdmiCecHandler *>(handlerPtr);
+ return handler->initLogicalDevice(deviceType);
+}
+
+static void nativeRemoveLogicalAddress(JNIEnv* env, jclass clazz, jlong handlerPtr,
+ jint deviceType) {
+ HdmiCecHandler *handler = reinterpret_cast<HdmiCecHandler *>(handlerPtr);
+ return handler->releaseLogicalDevice(deviceType);
+}
+
+static void nativeSendActiveSource(JNIEnv* env, jclass clazz, jlong handlerPtr,
+ jint deviceType) {
+ HdmiCecHandler *handler = reinterpret_cast<HdmiCecHandler *>(handlerPtr);
+ cec_logical_address_t srcAddr = handler->getLogicalAddress(deviceType);
+ handler->sendActiveSource(srcAddr);
+}
+
+static void nativeSendInactiveSource(JNIEnv* env, jclass clazz, jlong handlerPtr,
+ jint deviceType) {
+ HdmiCecHandler *handler = reinterpret_cast<HdmiCecHandler *>(handlerPtr);
+ cec_logical_address_t srcAddr = handler->getLogicalAddress(deviceType);
+ handler->sendInactiveSource(srcAddr);
+}
+
+static void nativeSendImageViewOn(JNIEnv* env, jclass clazz, jlong handlerPtr,
+ jint deviceType) {
+ HdmiCecHandler *handler = reinterpret_cast<HdmiCecHandler *>(handlerPtr);
+ cec_logical_address_t srcAddr = handler->getLogicalAddress(deviceType);
+ handler->sendImageViewOn(srcAddr);
+}
+
+static void nativeSendTextViewOn(JNIEnv* env, jclass clazz, jlong handlerPtr,
+ jint deviceType) {
+ HdmiCecHandler *handler = reinterpret_cast<HdmiCecHandler *>(handlerPtr);
+ cec_logical_address_t srcAddr = handler->getLogicalAddress(deviceType);
+ handler->sendTextViewOn(srcAddr);
+}
+
+static void nativeSendGiveDevicePowerStatus(JNIEnv* env, jclass clazz, jlong handlerPtr,
+ jint deviceType, jint destination) {
+ HdmiCecHandler *handler = reinterpret_cast<HdmiCecHandler *>(handlerPtr);
+ cec_logical_address_t srcAddr = handler->getLogicalAddress(deviceType);
+ cec_logical_address_t dstAddr = static_cast<cec_logical_address_t>(destination);
+ handler->sendGiveDevicePowerStatus(srcAddr, dstAddr);
+}
+
+static JNINativeMethod sMethods[] = {
+ /* name, signature, funcPtr */
+ { "nativeInit", "(Lcom/android/server/hdmi/HdmiCecService;)J",
+ (void *)nativeInit },
+ { "nativeSendMessage", "(JIII[B)V",
+ (void *)nativeSendMessage },
+ { "nativeAllocateLogicalAddress", "(JI)I",
+ (void *)nativeAllocateLogicalAddress },
+ { "nativeRemoveLogicalAddress", "(JI)V",
+ (void *)nativeRemoveLogicalAddress },
+ { "nativeSendActiveSource", "(JI)V",
+ (void *)nativeSendActiveSource },
+ { "nativeSendInactiveSource", "(JI)V",
+ (void *)nativeSendInactiveSource },
+ { "nativeSendImageViewOn", "(JI)V",
+ (void *)nativeSendImageViewOn },
+ { "nativeSendTextViewOn", "(JI)V",
+ (void *)nativeSendTextViewOn },
+ { "nativeSendGiveDevicePowerStatus", "(JII)V",
+ (void *)nativeSendGiveDevicePowerStatus }
+};
+
+#define CLASS_PATH "com/android/server/hdmi/HdmiCecService"
+
+int register_android_server_hdmi_HdmiCecService(JNIEnv* env) {
+ int res = jniRegisterNativeMethods(env, CLASS_PATH, sMethods, NELEM(sMethods));
+ LOG_FATAL_IF(res < 0, "Unable to register native methods.");
+ return 0;
+}
+
+} /* namespace android */
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 00986d5..904966a 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -21,6 +21,7 @@
namespace android {
int register_android_server_AlarmManagerService(JNIEnv* env);
+int register_android_server_AssetAtlasService(JNIEnv* env);
int register_android_server_ConsumerIrService(JNIEnv *env);
int register_android_server_InputApplicationHandle(JNIEnv* env);
int register_android_server_InputWindowHandle(JNIEnv* env);
@@ -28,15 +29,15 @@
int register_android_server_LightsService(JNIEnv* env);
int register_android_server_PowerManagerService(JNIEnv* env);
int register_android_server_SerialService(JNIEnv* env);
+int register_android_server_SystemServer(JNIEnv* env);
int register_android_server_UsbDeviceManager(JNIEnv* env);
int register_android_server_UsbHostManager(JNIEnv* env);
int register_android_server_VibratorService(JNIEnv* env);
-int register_android_server_SystemServer(JNIEnv* env);
int register_android_server_location_GpsLocationProvider(JNIEnv* env);
int register_android_server_location_FlpHardwareProvider(JNIEnv* env);
int register_android_server_connectivity_Vpn(JNIEnv* env);
-int register_android_server_AssetAtlasService(JNIEnv* env);
int register_android_server_dreams_McuHal(JNIEnv* env);
+int register_android_server_hdmi_HdmiCecService(JNIEnv* env);
};
using namespace android;
@@ -69,6 +70,7 @@
register_android_server_AssetAtlasService(env);
register_android_server_ConsumerIrService(env);
register_android_server_dreams_McuHal(env);
+ register_android_server_hdmi_HdmiCecService(env);
return JNI_VERSION_1_4;
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index db40cbe..7fb591b 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -114,6 +114,8 @@
"com.android.server.print.PrintManagerService";
private static final String USB_SERVICE_CLASS =
"com.android.server.usb.UsbService$Lifecycle";
+ private static final String HDMI_CEC_SERVICE_CLASS =
+ "com.android.server.hdmi.HdmiCecService";
private final int mFactoryTestMode;
private Timer mProfilerSnapshotTimer;
@@ -887,6 +889,12 @@
reportWtf("starting Print Service", e);
}
+ try {
+ mSystemServiceManager.startService(HDMI_CEC_SERVICE_CLASS);
+ } catch (Throwable e) {
+ reportWtf("starting HdmiCec Service", e);
+ }
+
if (!disableNonCoreServices) {
try {
Slog.i(TAG, "Media Router Service");