Revert "Revert "Add physical TV input handling to TvInputManagerService""

This reverts commit 1940e197a8de186df5edf0b78e0907ae539bd215.

Bug: 14118245, Bug: 15197740
Change-Id: Ia308f16d2ed8ec55112a4d21c180ccb97e8d7c6a
diff --git a/services/core/java/com/android/server/tv/TvInputHal.java b/services/core/java/com/android/server/tv/TvInputHal.java
new file mode 100644
index 0000000..4bdd2be
--- /dev/null
+++ b/services/core/java/com/android/server/tv/TvInputHal.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 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.tv;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.tv.TvInputHardwareInfo;
+import android.tv.TvStreamConfig;
+import android.view.Surface;
+
+/**
+ * Provides access to the low-level TV input hardware abstraction layer.
+ */
+final class TvInputHal {
+    public final static int SUCCESS = 0;
+    public final static int ERROR_NO_INIT = -1;
+    public final static int ERROR_STALE_CONFIG = -2;
+    public final static int ERROR_UNKNOWN = -3;
+
+    public static final int TYPE_HDMI = 1;
+    public static final int TYPE_BUILT_IN_TUNER = 2;
+    public static final int TYPE_PASSTHROUGH = 3;
+
+    public interface Callback {
+        public void onDeviceAvailable(
+                TvInputHardwareInfo info, TvStreamConfig[] configs);
+        public void onDeviceUnavailable(int deviceId);
+        public void onStreamConfigurationChanged(int deviceId, TvStreamConfig[] configs);
+    }
+
+    private native long nativeOpen();
+
+    private static native int nativeSetSurface(long ptr, int deviceId, int streamId,
+            Surface surface);
+    private static native TvStreamConfig[] nativeGetStreamConfigs(long ptr, int deviceId,
+            int generation);
+    private static native void nativeClose(long ptr);
+
+    private long mPtr = 0l;
+    private final Callback mCallback;
+    private final HandlerThread mThread = new HandlerThread("TV input HAL event thread");
+    private final Handler mHandler;
+    private int mStreamConfigGeneration = 0;
+    private TvStreamConfig[] mStreamConfigs;
+
+    public TvInputHal(Callback callback) {
+        mCallback = callback;
+        mThread.start();
+        mHandler = new Handler(mThread.getLooper());
+    }
+
+    public void init() {
+        mPtr = nativeOpen();
+    }
+
+    public int setSurface(int deviceId, Surface surface, TvStreamConfig streamConfig) {
+        if (mPtr == 0) {
+            return ERROR_NO_INIT;
+        }
+        if (mStreamConfigGeneration != streamConfig.getGeneration()) {
+            return ERROR_STALE_CONFIG;
+        }
+        if (nativeSetSurface(mPtr, deviceId, streamConfig.getStreamId(), surface) == 0) {
+            return SUCCESS;
+        } else {
+            return ERROR_UNKNOWN;
+        }
+    }
+
+    public void close() {
+        if (mPtr != 0l) {
+            nativeClose(mPtr);
+            mThread.quitSafely();
+        }
+    }
+
+    private synchronized void retrieveStreamConfigs(int deviceId) {
+        ++mStreamConfigGeneration;
+        mStreamConfigs = nativeGetStreamConfigs(mPtr, deviceId, mStreamConfigGeneration);
+    }
+
+    // Called from native
+    private void deviceAvailableFromNative(int deviceId, int type) {
+        final TvInputHardwareInfo info = new TvInputHardwareInfo(deviceId, type);
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                retrieveStreamConfigs(info.getDeviceId());
+                mCallback.onDeviceAvailable(info, mStreamConfigs);
+            }
+        });
+    }
+
+    private void deviceUnavailableFromNative(int deviceId) {
+        final int id = deviceId;
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mCallback.onDeviceUnavailable(id);
+            }
+        });
+    }
+
+    private void streamConfigsChangedFromNative(int deviceId) {
+        final int id = deviceId;
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                retrieveStreamConfigs(id);
+                mCallback.onStreamConfigurationChanged(id, mStreamConfigs);
+            }
+        });
+    }
+}
diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
new file mode 100644
index 0000000..b95b0f0
--- /dev/null
+++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
@@ -0,0 +1,308 @@
+/*
+ * 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.tv;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.tv.ITvInputHardware;
+import android.tv.ITvInputHardwareCallback;
+import android.tv.TvInputHardwareInfo;
+import android.tv.TvStreamConfig;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.KeyEvent;
+import android.view.Surface;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A helper class for TvInputManagerService to handle TV input hardware.
+ *
+ * This class does a basic connection management and forwarding calls to TvInputHal which eventually
+ * calls to tv_input HAL module.
+ *
+ * @hide
+ */
+class TvInputHardwareManager implements TvInputHal.Callback {
+    private static final String TAG = TvInputHardwareManager.class.getSimpleName();
+    private final TvInputHal mHal = new TvInputHal(this);
+    private final SparseArray<Connection> mConnections = new SparseArray<Connection>();
+    private final List<TvInputHardwareInfo> mInfoList = new ArrayList<TvInputHardwareInfo>();
+    private final Context mContext;
+    private final Set<Integer> mActiveHdmiSources = new HashSet<Integer>();
+
+    private final Object mLock = new Object();
+
+    public TvInputHardwareManager(Context context) {
+        mContext = context;
+        // TODO(hdmi): mHdmiManager = mContext.getSystemService(...);
+        // TODO(hdmi): mHdmiClient = mHdmiManager.getTvClient();
+        mHal.init();
+    }
+
+    @Override
+    public void onDeviceAvailable(
+            TvInputHardwareInfo info, TvStreamConfig[] configs) {
+        synchronized (mLock) {
+            Connection connection = new Connection(info);
+            connection.updateConfigsLocked(configs);
+            mConnections.put(info.getDeviceId(), connection);
+            buildInfoListLocked();
+            // TODO: notify if necessary
+        }
+    }
+
+    private void buildInfoListLocked() {
+        mInfoList.clear();
+        for (int i = 0; i < mConnections.size(); ++i) {
+            mInfoList.add(mConnections.valueAt(i).getInfoLocked());
+        }
+    }
+
+    @Override
+    public void onDeviceUnavailable(int deviceId) {
+        synchronized (mLock) {
+            Connection connection = mConnections.get(deviceId);
+            if (connection == null) {
+                Slog.e(TAG, "onDeviceUnavailable: Cannot find a connection with " + deviceId);
+                return;
+            }
+            connection.resetLocked(null, null, null, null);
+            mConnections.remove(deviceId);
+            buildInfoListLocked();
+            // TODO: notify if necessary
+        }
+    }
+
+    @Override
+    public void onStreamConfigurationChanged(int deviceId, TvStreamConfig[] configs) {
+        synchronized (mLock) {
+            Connection connection = mConnections.get(deviceId);
+            if (connection == null) {
+                Slog.e(TAG, "StreamConfigurationChanged: Cannot find a connection with "
+                        + deviceId);
+                return;
+            }
+            connection.updateConfigsLocked(configs);
+            try {
+                connection.getCallbackLocked().onStreamConfigChanged(configs);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "onStreamConfigurationChanged: " + e);
+            }
+        }
+    }
+
+    public List<TvInputHardwareInfo> getHardwareList() {
+        synchronized (mLock) {
+            return mInfoList;
+        }
+    }
+
+    /**
+     * Create a TvInputHardware object with a specific deviceId. One service at a time can access
+     * the object, and if more than one process attempts to create hardware with the same deviceId,
+     * the latest service will get the object and all the other hardware are released. The
+     * release is notified via ITvInputHardwareCallback.onReleased().
+     */
+    public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback,
+            int callingUid, int resolvedUserId) {
+        if (callback == null) {
+            throw new NullPointerException();
+        }
+        synchronized (mLock) {
+            Connection connection = mConnections.get(deviceId);
+            if (connection == null) {
+                Slog.e(TAG, "Invalid deviceId : " + deviceId);
+                return null;
+            }
+            if (connection.getCallingUidLocked() != callingUid
+                    || connection.getResolvedUserIdLocked() != resolvedUserId) {
+                TvInputHardwareImpl hardware = new TvInputHardwareImpl(connection.getInfoLocked());
+                try {
+                    callback.asBinder().linkToDeath(connection, 0);
+                } catch (RemoteException e) {
+                    hardware.release();
+                    return null;
+                }
+                connection.resetLocked(hardware, callback, callingUid, resolvedUserId);
+            }
+            return connection.getHardwareLocked();
+        }
+    }
+
+    /**
+     * Release the specified hardware.
+     */
+    public void releaseHardware(int deviceId, ITvInputHardware hardware, int callingUid,
+            int resolvedUserId) {
+        synchronized (mLock) {
+            Connection connection = mConnections.get(deviceId);
+            if (connection == null) {
+                Slog.e(TAG, "Invalid deviceId : " + deviceId);
+                return;
+            }
+            if (connection.getHardwareLocked() != hardware
+                    || connection.getCallingUidLocked() != callingUid
+                    || connection.getResolvedUserIdLocked() != resolvedUserId) {
+                return;
+            }
+            connection.resetLocked(null, null, null, null);
+        }
+    }
+
+    private class Connection implements IBinder.DeathRecipient {
+        private final TvInputHardwareInfo mInfo;
+        private TvInputHardwareImpl mHardware = null;
+        private ITvInputHardwareCallback mCallback;
+        private TvStreamConfig[] mConfigs = null;
+        private Integer mCallingUid = null;
+        private Integer mResolvedUserId = null;
+
+        public Connection(TvInputHardwareInfo info) {
+            mInfo = info;
+        }
+
+        // *Locked methods assume TvInputHardwareManager.mLock is held.
+
+        public void resetLocked(TvInputHardwareImpl hardware,
+                ITvInputHardwareCallback callback, Integer callingUid, Integer resolvedUserId) {
+            if (mHardware != null) {
+                try {
+                    mCallback.onReleased();
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Connection::resetHardware: " + e);
+                }
+                mHardware.release();
+            }
+            mHardware = hardware;
+            mCallback = callback;
+            mCallingUid = callingUid;
+            mResolvedUserId = resolvedUserId;
+
+            if (mHardware != null && mCallback != null) {
+                try {
+                    mCallback.onStreamConfigChanged(getConfigsLocked());
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Connection::resetHardware: " + e);
+                }
+            }
+        }
+
+        public void updateConfigsLocked(TvStreamConfig[] configs) {
+            mConfigs = configs;
+        }
+
+        public TvInputHardwareInfo getInfoLocked() {
+            return mInfo;
+        }
+
+        public ITvInputHardware getHardwareLocked() {
+            return mHardware;
+        }
+
+        public ITvInputHardwareCallback getCallbackLocked() {
+            return mCallback;
+        }
+
+        public TvStreamConfig[] getConfigsLocked() {
+            return mConfigs;
+        }
+
+        public int getCallingUidLocked() {
+            return mCallingUid;
+        }
+
+        public int getResolvedUserIdLocked() {
+            return mResolvedUserId;
+        }
+
+        @Override
+        public void binderDied() {
+            synchronized (mLock) {
+                resetLocked(null, null, null, null);
+            }
+        }
+    }
+
+    private class TvInputHardwareImpl extends ITvInputHardware.Stub {
+        private final TvInputHardwareInfo mInfo;
+        private boolean mReleased = false;
+        private final Object mImplLock = new Object();
+
+        public TvInputHardwareImpl(TvInputHardwareInfo info) {
+            mInfo = info;
+        }
+
+        public void release() {
+            synchronized (mImplLock) {
+                mReleased = true;
+            }
+        }
+
+        @Override
+        public boolean setSurface(Surface surface, TvStreamConfig config)
+                throws RemoteException {
+            synchronized (mImplLock) {
+                if (mReleased) {
+                    throw new IllegalStateException("Device already released.");
+                }
+                if (mInfo.getType() == TvInputHal.TYPE_HDMI) {
+                    if (surface != null) {
+                        // Set "Active Source" for HDMI.
+                        // TODO(hdmi): mHdmiClient.deviceSelect(...);
+                        mActiveHdmiSources.add(mInfo.getDeviceId());
+                    } else {
+                        mActiveHdmiSources.remove(mInfo.getDeviceId());
+                        if (mActiveHdmiSources.size() == 0) {
+                            // Tell HDMI that no HDMI source is active
+                            // TODO(hdmi): mHdmiClient.portSelect(null);
+                        }
+                    }
+                }
+                return mHal.setSurface(mInfo.getDeviceId(), surface, config) == TvInputHal.SUCCESS;
+            }
+        }
+
+        @Override
+        public void setVolume(float volume) throws RemoteException {
+            synchronized (mImplLock) {
+                if (mReleased) {
+                    throw new IllegalStateException("Device already released.");
+                }
+            }
+            // TODO
+        }
+
+        @Override
+        public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException {
+            synchronized (mImplLock) {
+                if (mReleased) {
+                    throw new IllegalStateException("Device already released.");
+                }
+            }
+            if (mInfo.getType() != TvInputHal.TYPE_HDMI) {
+                return false;
+            }
+            // TODO(hdmi): mHdmiClient.sendKeyEvent(event);
+            return false;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 8ad7fff..6c38a4c 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -42,11 +42,14 @@
 import android.os.UserHandle;
 import android.provider.TvContract;
 import android.tv.ITvInputClient;
+import android.tv.ITvInputHardware;
+import android.tv.ITvInputHardwareCallback;
 import android.tv.ITvInputManager;
 import android.tv.ITvInputService;
 import android.tv.ITvInputServiceCallback;
 import android.tv.ITvInputSession;
 import android.tv.ITvInputSessionCallback;
+import android.tv.TvInputHardwareInfo;
 import android.tv.TvInputInfo;
 import android.tv.TvInputService;
 import android.util.Slog;
@@ -71,6 +74,7 @@
     private static final String TAG = "TvInputManagerService";
 
     private final Context mContext;
+    private final TvInputHardwareManager mTvInputHardwareManager;
 
     private final ContentResolver mContentResolver;
 
@@ -92,6 +96,7 @@
         mContentResolver = context.getContentResolver();
         mLogHandler = new LogHandler(IoThread.get().getLooper());
 
+        mTvInputHardwareManager = new TvInputHardwareManager(context);
         registerBroadcastReceivers();
 
         synchronized (mLock) {
@@ -730,6 +735,64 @@
                 Binder.restoreCallingIdentity(identity);
             }
         }
+
+        @Override
+        public List<TvInputHardwareInfo> getHardwareList() throws RemoteException {
+            if (mContext.checkCallingPermission(
+                    android.Manifest.permission.TV_INPUT_HARDWARE)
+                    != PackageManager.PERMISSION_GRANTED) {
+                return null;
+            }
+
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                return mTvInputHardwareManager.getHardwareList();
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public ITvInputHardware acquireTvInputHardware(int deviceId,
+                ITvInputHardwareCallback callback, int userId) throws RemoteException {
+            if (mContext.checkCallingPermission(
+                    android.Manifest.permission.TV_INPUT_HARDWARE)
+                    != PackageManager.PERMISSION_GRANTED) {
+                return null;
+            }
+
+            final long identity = Binder.clearCallingIdentity();
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "acquireTvInputHardware");
+            try {
+                return mTvInputHardwareManager.acquireHardware(
+                        deviceId, callback, callingUid, resolvedUserId);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void releaseTvInputHardware(int deviceId, ITvInputHardware hardware, int userId)
+                throws RemoteException {
+            if (mContext.checkCallingPermission(
+                    android.Manifest.permission.TV_INPUT_HARDWARE)
+                    != PackageManager.PERMISSION_GRANTED) {
+                return;
+            }
+
+            final long identity = Binder.clearCallingIdentity();
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "releaseTvInputHardware");
+            try {
+                mTvInputHardwareManager.releaseHardware(
+                        deviceId, hardware, callingUid, resolvedUserId);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
     }
 
     private static final class UserState {
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index 51583a5..3cfb45b 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -23,6 +23,7 @@
     $(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_tv_TvInputHal.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 \
diff --git a/services/core/jni/com_android_server_tv_TvInputHal.cpp b/services/core/jni/com_android_server_tv_TvInputHal.cpp
new file mode 100644
index 0000000..f0c4f3a
--- /dev/null
+++ b/services/core/jni/com_android_server_tv_TvInputHal.cpp
@@ -0,0 +1,388 @@
+/*
+ * Copyright 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 "TvInputHal"
+
+//#define LOG_NDEBUG 0
+
+#include "android_runtime/AndroidRuntime.h"
+#include "android_runtime/android_view_Surface.h"
+#include "JNIHelp.h"
+#include "jni.h"
+
+#include <gui/Surface.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/Log.h>
+#include <utils/NativeHandle.h>
+#include <hardware/tv_input.h>
+
+namespace android {
+
+static struct {
+    jmethodID deviceAvailable;
+    jmethodID deviceUnavailable;
+    jmethodID streamConfigsChanged;
+} gTvInputHalClassInfo;
+
+static struct {
+    jclass clazz;
+} gTvStreamConfigClassInfo;
+
+static struct {
+    jclass clazz;
+
+    jmethodID constructor;
+    jmethodID streamId;
+    jmethodID type;
+    jmethodID maxWidth;
+    jmethodID maxHeight;
+    jmethodID generation;
+    jmethodID build;
+} gTvStreamConfigBuilderClassInfo;
+
+////////////////////////////////////////////////////////////////////////////////
+
+class JTvInputHal {
+public:
+    ~JTvInputHal();
+
+    static JTvInputHal* createInstance(JNIEnv* env, jobject thiz);
+
+    int setSurface(int deviceId, int streamId, const sp<Surface>& surface);
+    void getStreamConfigs(int deviceId, jobjectArray* array);
+    const tv_stream_config_t* getStreamConfigs(int deviceId, int* numConfigs);
+
+private:
+    class Connection {
+    public:
+        Connection() : mStreamId(0) {}
+
+        sp<Surface> mSurface;
+        sp<NativeHandle> mSourceHandle;
+        int mStreamId;
+    };
+
+    JTvInputHal(JNIEnv* env, jobject thiz, tv_input_device_t* dev);
+
+    static void notify(
+            tv_input_device_t* dev,tv_input_event_t* event, void* data);
+
+    void onDeviceAvailable(const tv_input_device_info_t& info);
+    void onDeviceUnavailable(int deviceId);
+    void onStreamConfigurationsChanged(int deviceId);
+
+    jweak mThiz;
+    tv_input_device_t* mDevice;
+    tv_input_callback_ops_t mCallback;
+
+    KeyedVector<int, Connection> mConnections;
+};
+
+JTvInputHal::JTvInputHal(JNIEnv* env, jobject thiz, tv_input_device_t* device) {
+    mThiz = env->NewWeakGlobalRef(thiz);
+    mDevice = device;
+    mCallback.notify = &JTvInputHal::notify;
+
+    mDevice->initialize(mDevice, &mCallback, this);
+}
+
+JTvInputHal::~JTvInputHal() {
+    mDevice->common.close((hw_device_t*)mDevice);
+
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    env->DeleteWeakGlobalRef(mThiz);
+    mThiz = NULL;
+}
+
+JTvInputHal* JTvInputHal::createInstance(JNIEnv* env, jobject thiz) {
+    tv_input_module_t* module = NULL;
+    status_t err = hw_get_module(TV_INPUT_HARDWARE_MODULE_ID,
+            (hw_module_t const**)&module);
+    if (err) {
+        ALOGE("Couldn't load %s module (%s)",
+                TV_INPUT_HARDWARE_MODULE_ID, strerror(-err));
+        return 0;
+    }
+
+    tv_input_device_t* device = NULL;
+    err = module->common.methods->open(
+            (hw_module_t*)module,
+            TV_INPUT_DEFAULT_DEVICE,
+            (hw_device_t**)&device);
+    if (err) {
+        ALOGE("Couldn't open %s device (%s)",
+                TV_INPUT_DEFAULT_DEVICE, strerror(-err));
+        return 0;
+    }
+
+    return new JTvInputHal(env, thiz, device);
+}
+
+int JTvInputHal::setSurface(int deviceId, int streamId, const sp<Surface>& surface) {
+    Connection& connection = mConnections.editValueFor(deviceId);
+    if (connection.mStreamId == streamId && connection.mSurface == surface) {
+        // Nothing to do
+        return NO_ERROR;
+    }
+    if (Surface::isValid(connection.mSurface)) {
+        connection.mSurface.clear();
+    }
+    if (surface == NULL) {
+        if (connection.mSurface != NULL) {
+            connection.mSurface->setSidebandStream(NULL);
+            connection.mSurface.clear();
+        }
+        if (connection.mSourceHandle != NULL) {
+            // Need to reset streams
+            if (mDevice->close_stream(
+                    mDevice, deviceId, connection.mStreamId) != 0) {
+                ALOGE("Couldn't remove stream");
+                return BAD_VALUE;
+            }
+            connection.mSourceHandle.clear();
+        }
+        return NO_ERROR;
+    }
+    connection.mSurface = surface;
+    if (connection.mSourceHandle == NULL) {
+        // Need to configure stream
+        int numConfigs = 0;
+        const tv_stream_config_t* configs = NULL;
+        if (mDevice->get_stream_configurations(
+                mDevice, deviceId, &numConfigs, &configs) != 0) {
+            ALOGE("Couldn't get stream configs");
+            return UNKNOWN_ERROR;
+        }
+        int configIndex = -1;
+        for (int i = 0; i < numConfigs; ++i) {
+            if (configs[i].stream_id == streamId) {
+                configIndex = i;
+                break;
+            }
+        }
+        if (configIndex == -1) {
+            ALOGE("Cannot find a config with given stream ID: %d", streamId);
+            return BAD_VALUE;
+        }
+        // TODO: handle buffer producer profile.
+        if (configs[configIndex].type !=
+                TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE) {
+            ALOGE("Profiles other than independent video source is not yet "
+                  "supported : type = %d", configs[configIndex].type);
+            return INVALID_OPERATION;
+        }
+        tv_stream_t stream;
+        stream.stream_id = configs[configIndex].stream_id;
+        if (mDevice->open_stream(mDevice, deviceId, &stream) != 0) {
+            ALOGE("Couldn't add stream");
+            return UNKNOWN_ERROR;
+        }
+        connection.mSourceHandle = NativeHandle::create(
+                stream.sideband_stream_source_handle, false);
+        connection.mStreamId = stream.stream_id;
+        connection.mSurface->setSidebandStream(connection.mSourceHandle);
+    }
+    return NO_ERROR;
+}
+
+const tv_stream_config_t* JTvInputHal::getStreamConfigs(int deviceId, int* numConfigs) {
+    const tv_stream_config_t* configs = NULL;
+    if (mDevice->get_stream_configurations(
+            mDevice, deviceId, numConfigs, &configs) != 0) {
+        ALOGE("Couldn't get stream configs");
+        return NULL;
+    }
+    return configs;
+}
+
+
+// static
+void JTvInputHal::notify(
+        tv_input_device_t* dev, tv_input_event_t* event, void* data) {
+    JTvInputHal* thiz = (JTvInputHal*)data;
+    switch (event->type) {
+        case TV_INPUT_EVENT_DEVICE_AVAILABLE: {
+            thiz->onDeviceAvailable(event->device_info);
+        } break;
+        case TV_INPUT_EVENT_DEVICE_UNAVAILABLE: {
+            thiz->onDeviceUnavailable(event->device_info.device_id);
+        } break;
+        case TV_INPUT_EVENT_STREAM_CONFIGURATIONS_CHANGED: {
+            thiz->onStreamConfigurationsChanged(event->device_info.device_id);
+        } break;
+        default:
+            ALOGE("Unrecognizable event");
+    }
+}
+
+void JTvInputHal::onDeviceAvailable(const tv_input_device_info_t& info) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    mConnections.add(info.device_id, Connection());
+    env->CallVoidMethod(
+            mThiz,
+            gTvInputHalClassInfo.deviceAvailable,
+            info.device_id,
+            info.type);
+}
+
+void JTvInputHal::onDeviceUnavailable(int deviceId) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    mConnections.removeItem(deviceId);
+    env->CallVoidMethod(
+            mThiz,
+            gTvInputHalClassInfo.deviceUnavailable,
+            deviceId);
+}
+
+void JTvInputHal::onStreamConfigurationsChanged(int deviceId) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    mConnections.removeItem(deviceId);
+    env->CallVoidMethod(
+            mThiz,
+            gTvInputHalClassInfo.streamConfigsChanged,
+            deviceId);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+static jlong nativeOpen(JNIEnv* env, jobject thiz) {
+    return (jlong)JTvInputHal::createInstance(env, thiz);
+}
+
+static int nativeSetSurface(JNIEnv* env, jclass clazz,
+        jlong ptr, jint deviceId, jint streamId, jobject jsurface) {
+    JTvInputHal* tvInputHal = (JTvInputHal*)ptr;
+    sp<Surface> surface(
+            jsurface
+            ? android_view_Surface_getSurface(env, jsurface)
+            : NULL);
+    return tvInputHal->setSurface(deviceId, streamId, surface);
+}
+
+static jobjectArray nativeGetStreamConfigs(JNIEnv* env, jclass clazz,
+        jlong ptr, jint deviceId, jint generation) {
+    JTvInputHal* tvInputHal = (JTvInputHal*)ptr;
+    int numConfigs = 0;
+    const tv_stream_config_t* configs = tvInputHal->getStreamConfigs(deviceId, &numConfigs);
+
+    jobjectArray result = env->NewObjectArray(numConfigs, gTvStreamConfigClassInfo.clazz, NULL);
+    for (int i = 0; i < numConfigs; ++i) {
+        jobject builder = env->NewObject(
+                gTvStreamConfigBuilderClassInfo.clazz,
+                gTvStreamConfigBuilderClassInfo.constructor);
+        env->CallObjectMethod(
+                builder, gTvStreamConfigBuilderClassInfo.streamId, configs[i].stream_id);
+        env->CallObjectMethod(
+                builder, gTvStreamConfigBuilderClassInfo.type, configs[i].type);
+        env->CallObjectMethod(
+                builder, gTvStreamConfigBuilderClassInfo.maxWidth, configs[i].max_video_width);
+        env->CallObjectMethod(
+                builder, gTvStreamConfigBuilderClassInfo.maxHeight, configs[i].max_video_height);
+        env->CallObjectMethod(
+                builder, gTvStreamConfigBuilderClassInfo.generation, generation);
+
+        jobject config = env->CallObjectMethod(builder, gTvStreamConfigBuilderClassInfo.build);
+
+        env->SetObjectArrayElement(result, i, config);
+
+        env->DeleteLocalRef(config);
+        env->DeleteLocalRef(builder);
+    }
+    return result;
+}
+
+static void nativeClose(JNIEnv* env, jclass clazz, jlong ptr) {
+    JTvInputHal* tvInputHal = (JTvInputHal*)ptr;
+    delete tvInputHal;
+}
+
+static JNINativeMethod gTvInputHalMethods[] = {
+    /* name, signature, funcPtr */
+    { "nativeOpen", "()J",
+            (void*) nativeOpen },
+    { "nativeSetSurface", "(JIILandroid/view/Surface;)I",
+            (void*) nativeSetSurface },
+    { "nativeGetStreamConfigs", "(JII)[Landroid/tv/TvStreamConfig;",
+            (void*) nativeGetStreamConfigs },
+    { "nativeClose", "(J)V",
+            (void*) nativeClose },
+};
+
+#define FIND_CLASS(var, className) \
+        var = env->FindClass(className); \
+        LOG_FATAL_IF(! var, "Unable to find class " className)
+
+#define GET_METHOD_ID(var, clazz, methodName, fieldDescriptor) \
+        var = env->GetMethodID(clazz, methodName, fieldDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find method" methodName)
+
+int register_android_server_tv_TvInputHal(JNIEnv* env) {
+    int res = jniRegisterNativeMethods(env, "com/android/server/tv/TvInputHal",
+            gTvInputHalMethods, NELEM(gTvInputHalMethods));
+    LOG_FATAL_IF(res < 0, "Unable to register native methods.");
+
+    jclass clazz;
+    FIND_CLASS(clazz, "com/android/server/tv/TvInputHal");
+
+    GET_METHOD_ID(
+            gTvInputHalClassInfo.deviceAvailable, clazz, "deviceAvailableFromNative", "(II)V");
+    GET_METHOD_ID(
+            gTvInputHalClassInfo.deviceUnavailable, clazz, "deviceUnavailableFromNative", "(I)V");
+    GET_METHOD_ID(
+            gTvInputHalClassInfo.streamConfigsChanged, clazz,
+            "streamConfigsChangedFromNative", "(I)V");
+
+    FIND_CLASS(gTvStreamConfigClassInfo.clazz, "android/tv/TvStreamConfig");
+    gTvStreamConfigClassInfo.clazz = jclass(env->NewGlobalRef(gTvStreamConfigClassInfo.clazz));
+
+    FIND_CLASS(gTvStreamConfigBuilderClassInfo.clazz, "android/tv/TvStreamConfig$Builder");
+    gTvStreamConfigBuilderClassInfo.clazz =
+            jclass(env->NewGlobalRef(gTvStreamConfigBuilderClassInfo.clazz));
+
+    GET_METHOD_ID(
+            gTvStreamConfigBuilderClassInfo.constructor,
+            gTvStreamConfigBuilderClassInfo.clazz,
+            "<init>", "()V");
+    GET_METHOD_ID(
+            gTvStreamConfigBuilderClassInfo.streamId,
+            gTvStreamConfigBuilderClassInfo.clazz,
+            "streamId", "(I)Landroid/tv/TvStreamConfig$Builder;");
+    GET_METHOD_ID(
+            gTvStreamConfigBuilderClassInfo.type,
+            gTvStreamConfigBuilderClassInfo.clazz,
+            "type", "(I)Landroid/tv/TvStreamConfig$Builder;");
+    GET_METHOD_ID(
+            gTvStreamConfigBuilderClassInfo.maxWidth,
+            gTvStreamConfigBuilderClassInfo.clazz,
+            "maxWidth", "(I)Landroid/tv/TvStreamConfig$Builder;");
+    GET_METHOD_ID(
+            gTvStreamConfigBuilderClassInfo.maxHeight,
+            gTvStreamConfigBuilderClassInfo.clazz,
+            "maxHeight", "(I)Landroid/tv/TvStreamConfig$Builder;");
+    GET_METHOD_ID(
+            gTvStreamConfigBuilderClassInfo.generation,
+            gTvStreamConfigBuilderClassInfo.clazz,
+            "generation", "(I)Landroid/tv/TvStreamConfig$Builder;");
+    GET_METHOD_ID(
+            gTvStreamConfigBuilderClassInfo.build,
+            gTvStreamConfigBuilderClassInfo.clazz,
+            "build", "()Landroid/tv/TvStreamConfig;");
+
+    return 0;
+}
+
+} /* namespace android */
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 1feb325..bfa8286 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -41,6 +41,7 @@
 int register_android_server_hdmi_HdmiCecController(JNIEnv* env);
 int register_android_server_hdmi_HdmiCecService(JNIEnv* env);
 int register_android_server_hdmi_HdmiMhlController(JNIEnv* env);
+int register_android_server_tv_TvInputHal(JNIEnv* env);
 };
 
 using namespace android;
@@ -78,6 +79,7 @@
     // TODO: remove this once replaces HdmiCecService with HdmiControlService.
     register_android_server_hdmi_HdmiCecService(env);
     register_android_server_hdmi_HdmiMhlController(env);
+    register_android_server_tv_TvInputHal(env);
 
     return JNI_VERSION_1_4;
 }