diff options
author | Jinsuk Kim <jinsukkim@google.com> | 2014-03-21 02:37:00 +0000 |
---|---|---|
committer | Android Git Automerger <android-git-automerger@android.com> | 2014-03-21 02:37:00 +0000 |
commit | f2fecf48f740a079cf9015ea8c2695039ca11d81 (patch) | |
tree | 587908d20a49ea3fb1aa692dc64047dfc26ad899 | |
parent | a5853b177c80052246c361e0bc2b0e5809cfeef2 (diff) | |
parent | c61f4dd744b1b0396703aa58eb22fc31d2b7050e (diff) | |
download | frameworks_base-f2fecf48f740a079cf9015ea8c2695039ca11d81.zip frameworks_base-f2fecf48f740a079cf9015ea8c2695039ca11d81.tar.gz frameworks_base-f2fecf48f740a079cf9015ea8c2695039ca11d81.tar.bz2 |
am c61f4dd7: Merge "Add HDMI-CEC service" into klp-modular-dev
* commit 'c61f4dd744b1b0396703aa58eb22fc31d2b7050e':
Add HDMI-CEC service
-rw-r--r-- | Android.mk | 2 | ||||
-rw-r--r-- | api/current.txt | 48 | ||||
-rw-r--r-- | core/java/android/hardware/hdmi/HdmiCec.java | 190 | ||||
-rw-r--r-- | core/java/android/hardware/hdmi/HdmiCecMessage.aidl | 19 | ||||
-rw-r--r-- | core/java/android/hardware/hdmi/HdmiCecMessage.java | 145 | ||||
-rw-r--r-- | core/java/android/hardware/hdmi/IHdmiCecListener.aidl | 29 | ||||
-rw-r--r-- | core/java/android/hardware/hdmi/IHdmiCecService.aidl | 40 | ||||
-rw-r--r-- | core/res/AndroidManifest.xml | 6 | ||||
-rw-r--r-- | services/core/java/com/android/server/hdmi/HdmiCecDevice.java | 171 | ||||
-rw-r--r-- | services/core/java/com/android/server/hdmi/HdmiCecService.java | 394 | ||||
-rw-r--r-- | services/core/jni/Android.mk | 9 | ||||
-rw-r--r-- | services/core/jni/com_android_server_hdmi_HdmiCecService.cpp | 689 | ||||
-rw-r--r-- | services/core/jni/onload.cpp | 6 | ||||
-rw-r--r-- | services/java/com/android/server/SystemServer.java | 8 |
14 files changed, 1751 insertions, 5 deletions
@@ -130,6 +130,8 @@ LOCAL_SRC_FILES += \ core/java/android/hardware/ISerialManager.aidl \ core/java/android/hardware/display/IDisplayManager.aidl \ core/java/android/hardware/display/IDisplayManagerCallback.aidl \ + core/java/android/hardware/hdmi/IHdmiCecListener.aidl \ + core/java/android/hardware/hdmi/IHdmiCecService.aidl \ core/java/android/hardware/input/IInputManager.aidl \ core/java/android/hardware/input/IInputDevicesChangedListener.aidl \ core/java/android/hardware/location/IFusedLocationHardware.aidl \ diff --git a/api/current.txt b/api/current.txt index bb5e0fa..d6a0dbb 100644 --- a/api/current.txt +++ b/api/current.txt @@ -10908,6 +10908,54 @@ package android.hardware.display { } +package android.hardware.hdmi { + + public final class HdmiCec { + method public static java.lang.String getDefaultDeviceName(int); + method public static int getTypeFromAddress(int); + method public static boolean isValidAddress(int); + method public static boolean isValidType(int); + field public static final int ADDR_AUDIO_SYSTEM = 5; // 0x5 + field public static final int ADDR_BROADCAST = 15; // 0xf + field public static final int ADDR_FREE_USE = 14; // 0xe + field public static final int ADDR_INVALID = -1; // 0xffffffff + field public static final int ADDR_PLAYBACK_1 = 4; // 0x4 + field public static final int ADDR_PLAYBACK_2 = 8; // 0x8 + field public static final int ADDR_PLAYBACK_3 = 11; // 0xb + field public static final int ADDR_RECORDER_1 = 1; // 0x1 + field public static final int ADDR_RECORDER_2 = 2; // 0x2 + field public static final int ADDR_RECORDER_3 = 9; // 0x9 + field public static final int ADDR_RESERVED_1 = 12; // 0xc + field public static final int ADDR_RESERVED_2 = 13; // 0xd + field public static final int ADDR_TUNER_1 = 3; // 0x3 + field public static final int ADDR_TUNER_2 = 6; // 0x6 + field public static final int ADDR_TUNER_3 = 7; // 0x7 + field public static final int ADDR_TUNER_4 = 10; // 0xa + field public static final int ADDR_TV = 0; // 0x0 + field public static final int ADDR_UNREGISTERED = 15; // 0xf + field public static final int DEVICE_AUDIO_SYSTEM = 5; // 0x5 + field public static final int DEVICE_INACTIVE = -1; // 0xffffffff + field public static final int DEVICE_PLAYBACK = 4; // 0x4 + field public static final int DEVICE_RECORDER = 1; // 0x1 + field public static final int DEVICE_RESERVED = 2; // 0x2 + field public static final int DEVICE_TUNER = 3; // 0x3 + field public static final int DEVICE_TV = 0; // 0x0 + field public static final int MESSAGE_ACTIVE_SOURCE = 157; // 0x9d + } + + public final class HdmiCecMessage implements android.os.Parcelable { + ctor public HdmiCecMessage(int, int, int, byte[]); + method public int describeContents(); + method public int getDestination(); + method public int getOpcode(); + method public byte[] getParams(); + method public int getSource(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + +} + package android.hardware.input { public final class InputManager { diff --git a/core/java/android/hardware/hdmi/HdmiCec.java b/core/java/android/hardware/hdmi/HdmiCec.java new file mode 100644 index 0000000..c95431a --- /dev/null +++ b/core/java/android/hardware/hdmi/HdmiCec.java @@ -0,0 +1,190 @@ +/* + * 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 android.hardware.hdmi; + +/** + * Defines constants and utility methods related to HDMI-CEC protocol. + */ +public final class HdmiCec { + + /** TV device type. */ + public static final int DEVICE_TV = 0; + + /** Recording device type. */ + public static final int DEVICE_RECORDER = 1; + + /** Device type reserved for future usage. */ + public static final int DEVICE_RESERVED = 2; + + /** Tuner device type. */ + public static final int DEVICE_TUNER = 3; + + /** Playback device type. */ + public static final int DEVICE_PLAYBACK = 4; + + /** Audio system device type. */ + public static final int DEVICE_AUDIO_SYSTEM = 5; + + // Value indicating the device is not an active source. + public static final int DEVICE_INACTIVE = -1; + + /** Logical address for TV */ + public static final int ADDR_TV = 0; + + /** Logical address for recorder 1 */ + public static final int ADDR_RECORDER_1 = 1; + + /** Logical address for recorder 2 */ + public static final int ADDR_RECORDER_2 = 2; + + /** Logical address for tuner 1 */ + public static final int ADDR_TUNER_1 = 3; + + /** Logical address for playback 1 */ + public static final int ADDR_PLAYBACK_1 = 4; + + /** Logical address for audio system */ + public static final int ADDR_AUDIO_SYSTEM = 5; + + /** Logical address for tuner 2 */ + public static final int ADDR_TUNER_2 = 6; + + /** Logical address for tuner 3 */ + public static final int ADDR_TUNER_3 = 7; + + /** Logical address for playback 2 */ + public static final int ADDR_PLAYBACK_2 = 8; + + /** Logical address for recorder 3 */ + public static final int ADDR_RECORDER_3 = 9; + + /** Logical address for tuner 4 */ + public static final int ADDR_TUNER_4 = 10; + + /** Logical address for playback 3 */ + public static final int ADDR_PLAYBACK_3 = 11; + + /** Logical address reserved for future usage */ + public static final int ADDR_RESERVED_1 = 12; + + /** Logical address reserved for future usage */ + public static final int ADDR_RESERVED_2 = 13; + + /** Logical address for TV other than the one assigned with {@link #ADDR_TV} */ + public static final int ADDR_FREE_USE = 14; + + /** Logical address for devices to which address cannot be allocated */ + public static final int ADDR_UNREGISTERED = 15; + + /** Logical address used in the destination address field for broadcast messages */ + public static final int ADDR_BROADCAST = 15; + + /** Logical address used to indicate it is not initialized or invalid. */ + public static final int ADDR_INVALID = -1; + + // TODO: Complete the list of CEC messages definition. + public static final int MESSAGE_ACTIVE_SOURCE = 0x9D; + + private static final int[] ADDRESS_TO_TYPE = { + DEVICE_TV, // ADDR_TV + DEVICE_RECORDER, // ADDR_RECORDER_1 + DEVICE_RECORDER, // ADDR_RECORDER_2 + DEVICE_TUNER, // ADDR_TUNER_1 + DEVICE_PLAYBACK, // ADDR_PLAYBACK_1 + DEVICE_AUDIO_SYSTEM, // ADDR_AUDIO_SYSTEM + DEVICE_TUNER, // ADDR_TUNER_2 + DEVICE_TUNER, // ADDR_TUNER_3 + DEVICE_PLAYBACK, // ADDR_PLAYBACK_2 + DEVICE_RECORDER, // ADDR_RECORDER_3 + DEVICE_TUNER, // ADDR_TUNER_4 + DEVICE_PLAYBACK, // ADDR_PLAYBACK_3 + }; + + private static final String[] DEFAULT_NAMES = { + "TV", + "Recorder_1", + "Recorder_2", + "Tuner_1", + "Playback_1", + "AudioSystem", + "Tuner_2", + "Tuner_3", + "Playback_2", + "Recorder_3", + "Tuner_4", + "Playback_3", + }; + + private HdmiCec() { } // Prevents instantiation. + + /** + * Check if the given type is valid. A valid type is one of the actual + * logical device types defined in the standard ({@link #DEVICE_TV}, + * {@link #DEVICE_PLAYBACK}, {@link #DEVICE_TUNER}, {@link #DEVICE_RECORDER}, + * and {@link #DEVICE_AUDIO_SYSTEM}). + * + * @param type device type + * @return true if the given type is valid + */ + public static boolean isValidType(int type) { + return (DEVICE_TV <= type && type <= DEVICE_AUDIO_SYSTEM) + && type != DEVICE_RESERVED; + } + + /** + * Check if the given logical address is valid. A logical address is valid + * if it is one allocated for an actual device which allows communication + * with other logical devices. + * + * @param address logical address + * @return true if the given address is valid + */ + public static boolean isValidAddress(int address) { + // TODO: We leave out the address 'free use(14)' for now. Check this later + // again to make sure it is a valid address for communication. + return (ADDR_TV <= address && address <= ADDR_PLAYBACK_3); + } + + /** + * Return the device type for the given logical address. + * + * @param address logical address + * @return device type for the given logical address; DEVICE_INACTIVE + * if the address is not valid. + */ + public static int getTypeFromAddress(int address) { + if (isValidAddress(address)) { + return ADDRESS_TO_TYPE[address]; + } + return DEVICE_INACTIVE; + } + + /** + * Return the default device name for a logical address. This is the name + * by which the logical device is known to others until a name is + * set explicitly using HdmiCecService.setOsdName. + * + * @param address logical address + * @return default device name; empty string if the address is not valid + */ + public static String getDefaultDeviceName(int address) { + if (isValidAddress(address)) { + return DEFAULT_NAMES[address]; + } + return ""; + } +} diff --git a/core/java/android/hardware/hdmi/HdmiCecMessage.aidl b/core/java/android/hardware/hdmi/HdmiCecMessage.aidl new file mode 100644 index 0000000..6687ba4 --- /dev/null +++ b/core/java/android/hardware/hdmi/HdmiCecMessage.aidl @@ -0,0 +1,19 @@ +/* + * 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 android.hardware.hdmi; + +parcelable HdmiCecMessage; diff --git a/core/java/android/hardware/hdmi/HdmiCecMessage.java b/core/java/android/hardware/hdmi/HdmiCecMessage.java new file mode 100644 index 0000000..be94d97 --- /dev/null +++ b/core/java/android/hardware/hdmi/HdmiCecMessage.java @@ -0,0 +1,145 @@ +/* + * 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 android.hardware.hdmi; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Arrays; + +/** + * A class to encapsulate HDMI-CEC message used for the devices connected via + * HDMI cable to communicate with one another. A message is defined by its + * source and destination address, command (or opcode), and optional parameters. + */ +public final class HdmiCecMessage implements Parcelable { + + private static final int MAX_MESSAGE_LENGTH = 16; + + private final int mSource; + private final int mDestination; + + private final int mOpcode; + private final byte[] mParams; + + /** + * Constructor. + */ + public HdmiCecMessage(int source, int destination, int opcode, byte[] params) { + mSource = source; + mDestination = destination; + mOpcode = opcode; + mParams = Arrays.copyOf(params, params.length); + } + + /** + * Return the source address field of the message. It is the logical address + * of the device which generated the message. + * + * @return source address + */ + public int getSource() { + return mSource; + } + + /** + * Return the destination address field of the message. It is the logical address + * of the device to which the message is sent. + * + * @return destination address + */ + public int getDestination() { + return mDestination; + } + + /** + * Return the opcode field of the message. It is the type of the message that + * tells the destination device what to do. + * + * @return opcode + */ + public int getOpcode() { + return mOpcode; + } + + /** + * Return the parameter field of the message. The contents of parameter varies + * from opcode to opcode, and is used together with opcode to describe + * the action for the destination device to take. + * + * @return parameter + */ + public byte[] getParams() { + return mParams; + } + + /** + * Describe the kinds of special objects contained in this Parcelable's + * marshalled representation. + */ + @Override + public int describeContents() { + return 0; + } + + /** + * Flatten this object in to a Parcel. + * + * @param dest The Parcel in which the object should be written. + * @param flags Additional flags about how the object should be written. + * May be 0 or {@link Parcelable#PARCELABLE_WRITE_RETURN_VALUE}. + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mSource); + dest.writeInt(mDestination); + dest.writeInt(mOpcode); + dest.writeInt(mParams.length); + dest.writeByteArray(mParams); + } + + public static final Parcelable.Creator<HdmiCecMessage> CREATOR + = new Parcelable.Creator<HdmiCecMessage>() { + /** + * Rebuild a HdmiCecMessage previously stored with writeToParcel(). + * @param p HdmiCecMessage object to read the Rating from + * @return a new HdmiCecMessage created from the data in the parcel + */ + public HdmiCecMessage createFromParcel(Parcel p) { + int source = p.readInt(); + int destination = p.readInt(); + int opcode = p.readInt(); + byte[] params = new byte[p.readInt()]; + p.readByteArray(params); + return new HdmiCecMessage(source, destination, opcode, params); + } + public HdmiCecMessage[] newArray(int size) { + return new HdmiCecMessage[size]; + } + }; + + @Override + public String toString() { + StringBuffer s = new StringBuffer(); + s.append(String.format("src: %d dst: %d op: %2X params: ", mSource, mDestination, mOpcode)); + for (byte data : mParams) { + s.append(String.format("%02X ", data)); + } + return s.toString(); + } +} + diff --git a/core/java/android/hardware/hdmi/IHdmiCecListener.aidl b/core/java/android/hardware/hdmi/IHdmiCecListener.aidl new file mode 100644 index 0000000..d281ce6 --- /dev/null +++ b/core/java/android/hardware/hdmi/IHdmiCecListener.aidl @@ -0,0 +1,29 @@ +/* + * 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 android.hardware.hdmi; + +import android.hardware.hdmi.HdmiCecMessage; + +/** + * Interface definition for HdmiCecService to do interprocess communcation. + * + * @hide + */ +oneway interface IHdmiCecListener { + void onMessageReceived(in HdmiCecMessage message); + void onCableStatusChanged(in boolean connected); +} diff --git a/core/java/android/hardware/hdmi/IHdmiCecService.aidl b/core/java/android/hardware/hdmi/IHdmiCecService.aidl new file mode 100644 index 0000000..6fefcf8 --- /dev/null +++ b/core/java/android/hardware/hdmi/IHdmiCecService.aidl @@ -0,0 +1,40 @@ +/* + * 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 android.hardware.hdmi; + +import android.hardware.hdmi.HdmiCecMessage; +import android.hardware.hdmi.IHdmiCecListener; +import android.os.IBinder; + +/** + * Binder interface that components running in the appplication process + * will use to enable HDMI-CEC protocol exchange with other devices. + * + * @hide + */ +interface IHdmiCecService { + IBinder allocateLogicalDevice(int type, IHdmiCecListener listener); + void removeServiceListener(IBinder b, IHdmiCecListener listener); + void setOsdName(IBinder b, String name); + void sendActiveSource(IBinder b); + void sendInactiveSource(IBinder b); + void sendImageViewOn(IBinder b); + void sendTextViewOn(IBinder b); + void sendGiveDevicePowerStatus(IBinder b, int address); + void sendMessage(IBinder b, in HdmiCecMessage message); +} + diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index eaf9318..0a1fb84 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -682,6 +682,12 @@ android:label="@string/permlab_installLocationProvider" android:description="@string/permdesc_installLocationProvider" /> + <!-- Allows HDMI-CEC service to access device and configuration files. + @hide This should only be used by HDMI-CEC service. + --> + <permission android:name="android.permission.HDMI_CEC" + android:protectionLevel="signatureOrSystem" /> + <!-- Allows an application to use location features in hardware, such as the geofencing api. <p>Not for use by third-party applications. --> 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_REL_DIR := core/jni 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_InputManager(JNIEnv* env); 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 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) 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 b3f22b1..f80db48 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -115,6 +115,8 @@ public final class SystemServer { "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; @@ -888,6 +890,12 @@ public final class SystemServer { 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"); |