Initial round of Television Input Framework

This provides APIs to control and create individual television inputs on
the system which will later be hosted by television applications.

Change-Id: I6866d28e78175a1bff2c32a85c5d77e94d0cd60c
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 7149ab9..e3b8d5c 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -103,6 +103,8 @@
 import android.print.IPrintManager;
 import android.print.PrintManager;
 import android.telephony.TelephonyManager;
+import android.tv.ITvInputManager;
+import android.tv.TvInputManager;
 import android.content.ClipboardManager;
 import android.util.AndroidRuntimeException;
 import android.util.ArrayMap;
@@ -629,6 +631,13 @@
                 return new TrustManager(b);
             }
         });
+
+        registerService(TV_INPUT_SERVICE, new ServiceFetcher() {
+            public Object createService(ContextImpl ctx) {
+                IBinder iBinder = ServiceManager.getService(TV_INPUT_SERVICE);
+                ITvInputManager service = ITvInputManager.Stub.asInterface(iBinder);
+                return new TvInputManager(service, UserHandle.myUserId());
+            }});
     }
 
     static ContextImpl getImpl(Context context) {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 27e526b..17b57bb 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2609,6 +2609,16 @@
     public static final String TRUST_SERVICE = "trust";
 
     /**
+     * Use with {@link #getSystemService} to retrieve a
+     * {@link android.tv.TvInputManager} for interacting with TV inputs on the
+     * device.
+     *
+     * @see #getSystemService
+     * @see android.tv.TvInputManager
+     */
+    public static final String TV_INPUT_SERVICE = "tv_input";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
diff --git a/core/java/android/tv/ITvInputClient.aidl b/core/java/android/tv/ITvInputClient.aidl
new file mode 100644
index 0000000..43be6f0
--- /dev/null
+++ b/core/java/android/tv/ITvInputClient.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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.tv;
+
+import android.content.ComponentName;
+import android.tv.ITvInputSession;
+
+/**
+ * Interface a client of the ITvInputManager implements, to identify itself and receive information
+ * about changes to the state of each TV input service.
+ * @hide
+ */
+oneway interface ITvInputClient {
+    void onSessionCreated(in ComponentName name, IBinder token, int seq);
+    void onAvailabilityChanged(in ComponentName name, boolean isAvailable);
+}
diff --git a/core/java/android/tv/ITvInputManager.aidl b/core/java/android/tv/ITvInputManager.aidl
new file mode 100644
index 0000000..a927dc9
--- /dev/null
+++ b/core/java/android/tv/ITvInputManager.aidl
@@ -0,0 +1,43 @@
+/*
+ * 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.tv;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.tv.ITvInputClient;
+import android.tv.TvInputInfo;
+import android.view.Surface;
+
+/**
+ * Interface to the TV input manager service.
+ * @hide
+ */
+interface ITvInputManager {
+    List<TvInputInfo> getTvInputList(int userId);
+
+    boolean getAvailability(in ITvInputClient client, in ComponentName name, int userId);
+
+    void registerCallback(in ITvInputClient client, in ComponentName name, int userId);
+    void unregisterCallback(in ITvInputClient client, in ComponentName name, int userId);
+
+    void createSession(in ITvInputClient client, in ComponentName name, int seq, int userId);
+    void releaseSession(in IBinder sessionToken, int userId);
+
+    void setSurface(in IBinder sessionToken, in Surface surface, int userId);
+    void setVolume(in IBinder sessionToken, float volume, int userId);
+    void tune(in IBinder sessionToken, in Uri channelUri, int userId);
+}
diff --git a/core/java/android/tv/ITvInputService.aidl b/core/java/android/tv/ITvInputService.aidl
new file mode 100644
index 0000000..d80f286
--- /dev/null
+++ b/core/java/android/tv/ITvInputService.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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.tv;
+
+import android.tv.ITvInputServiceCallback;
+import android.tv.ITvInputSession;
+import android.tv.ITvInputSessionCallback;
+
+/**
+ * Top-level interface to a TV input component (implemented in a Service).
+ * @hide
+ */
+oneway interface ITvInputService {
+    void registerCallback(ITvInputServiceCallback callback);
+    void unregisterCallback(in ITvInputServiceCallback callback);
+    void createSession(ITvInputSessionCallback callback);
+}
diff --git a/core/java/android/tv/ITvInputServiceCallback.aidl b/core/java/android/tv/ITvInputServiceCallback.aidl
new file mode 100644
index 0000000..e535c81
--- /dev/null
+++ b/core/java/android/tv/ITvInputServiceCallback.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.tv;
+
+import android.content.ComponentName;
+
+/**
+ * Helper interface for ITvInputService to allow the TV input to notify the client when its status
+ * has been changed.
+ * @hide
+ */
+oneway interface ITvInputServiceCallback {
+    void onAvailabilityChanged(in ComponentName name, boolean isAvailable);
+}
diff --git a/core/java/android/tv/ITvInputSession.aidl b/core/java/android/tv/ITvInputSession.aidl
new file mode 100644
index 0000000..d379d2d
--- /dev/null
+++ b/core/java/android/tv/ITvInputSession.aidl
@@ -0,0 +1,34 @@
+/*
+ * 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.tv;
+
+import android.net.Uri;
+import android.view.Surface;
+
+/**
+ * Sub-interface of ITvInputService which is created per session and has its own context.
+ * @hide
+ */
+oneway interface ITvInputSession {
+    void release();
+
+    void setSurface(in Surface surface);
+    // TODO: Remove this once it becomes irrelevant for applications to handle audio focus. The plan
+    // is to introduce some new concepts that will solve a number of problems in audio policy today.
+    void setVolume(float volume);
+    void tune(in Uri channelUri);
+}
diff --git a/core/java/android/tv/ITvInputSessionCallback.aidl b/core/java/android/tv/ITvInputSessionCallback.aidl
new file mode 100644
index 0000000..a2bd0d7
--- /dev/null
+++ b/core/java/android/tv/ITvInputSessionCallback.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.tv;
+
+import android.tv.ITvInputSession;
+
+/**
+ * Helper interface for ITvInputSession to allow the TV input to notify the system service when a
+ * new session has been created.
+ * @hide
+ */
+oneway interface ITvInputSessionCallback {
+    void onSessionCreated(ITvInputSession session);
+}
diff --git a/core/java/android/tv/ITvInputSessionWrapper.java b/core/java/android/tv/ITvInputSessionWrapper.java
new file mode 100644
index 0000000..fd4e1e3
--- /dev/null
+++ b/core/java/android/tv/ITvInputSessionWrapper.java
@@ -0,0 +1,99 @@
+/*
+ * 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.tv;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Message;
+import android.util.Log;
+import android.view.Surface;
+
+import com.android.internal.os.HandlerCaller;
+
+/**
+ * Implements the internal ITvInputSession interface to convert incoming calls on to it back to
+ * calls on the public TvInputSession interface, scheduling them on the main thread of the process.
+ *
+ * @hide
+ */
+public class ITvInputSessionWrapper extends ITvInputSession.Stub implements HandlerCaller.Callback {
+    private static final String TAG = "TvInputSessionWrapper";
+
+    private static final int DO_RELEASE = 1;
+    private static final int DO_SET_SURFACE = 2;
+    private static final int DO_SET_VOLUME = 3;
+    private static final int DO_TUNE = 4;
+
+    private TvInputSession mTvInputSession;
+    private final HandlerCaller mCaller;
+
+    public ITvInputSessionWrapper(Context context, TvInputSession session) {
+        mCaller = new HandlerCaller(context, null, this, true /* asyncHandler */);
+        mTvInputSession = session;
+    }
+
+    @Override
+    public void executeMessage(Message msg) {
+        if (mTvInputSession == null) {
+            return;
+        }
+
+        switch (msg.what) {
+            case DO_RELEASE: {
+                mTvInputSession.release();
+                mTvInputSession = null;
+                return;
+            }
+            case DO_SET_SURFACE: {
+                mTvInputSession.setSurface((Surface) msg.obj);
+                return;
+            }
+            case DO_SET_VOLUME: {
+                mTvInputSession.setVolume((Float) msg.obj);
+                return;
+            }
+            case DO_TUNE: {
+                mTvInputSession.tune((Uri) msg.obj);
+                return;
+            }
+            default: {
+                Log.w(TAG, "Unhandled message code: " + msg.what);
+                return;
+            }
+        }
+    }
+
+    @Override
+    public void release() {
+        mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_RELEASE));
+    }
+
+    @Override
+    public void setSurface(Surface surface) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_SURFACE, surface));
+    }
+
+    @Override
+    public final void setVolume(float volume) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_VOLUME, volume));
+    }
+
+    @Override
+    public void tune(Uri channelUri) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_TUNE, channelUri));
+    }
+}
diff --git a/core/java/android/tv/TvInputInfo.aidl b/core/java/android/tv/TvInputInfo.aidl
new file mode 100644
index 0000000..abc4b47
--- /dev/null
+++ b/core/java/android/tv/TvInputInfo.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.tv;
+
+parcelable TvInputInfo;
diff --git a/core/java/android/tv/TvInputInfo.java b/core/java/android/tv/TvInputInfo.java
new file mode 100644
index 0000000..90625d8
--- /dev/null
+++ b/core/java/android/tv/TvInputInfo.java
@@ -0,0 +1,124 @@
+/*
+ * 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.tv;
+
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class is used to specify meta information of a TV input.
+ */
+public final class TvInputInfo implements Parcelable {
+    private final ResolveInfo mService;
+    private final String mId;
+
+    /**
+     * Constructor.
+     *
+     * @param service The ResolveInfo returned from the package manager about this TV input service.
+     * @hide
+     */
+    public TvInputInfo(ResolveInfo service) {
+        mService = service;
+        ServiceInfo si = service.serviceInfo;
+        mId = new ComponentName(si.packageName, si.name).flattenToShortString();
+    }
+
+    /**
+     * Returns a unique ID for this TV input. The ID is generated from the package and class name
+     * implementing the TV input service.
+     */
+    public String getId() {
+        return mId;
+    }
+
+    /**
+     * Returns the .apk package that implements this TV input service.
+     */
+    public String getPackageName() {
+        return mService.serviceInfo.packageName;
+    }
+
+    /**
+     * Returns the class name of the service component that implements this TV input service.
+     */
+    public String getServiceName() {
+        return mService.serviceInfo.name;
+    }
+
+    /**
+     * Returns the component of the service that implements this TV input.
+     */
+    public ComponentName getComponent() {
+        return new ComponentName(mService.serviceInfo.packageName, mService.serviceInfo.name);
+    }
+
+    /**
+     * Loads the user-displayed label for this TV input service.
+     *
+     * @param pm Supplies a PackageManager used to load the TV input's resources.
+     * @return Returns a CharSequence containing the TV input's label. If the TV input does not have
+     *         a label, its name is returned.
+     */
+    public CharSequence loadLabel(PackageManager pm) {
+        return mService.loadLabel(pm);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Used to package this object into a {@link Parcel}.
+     *
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mId);
+        mService.writeToParcel(dest, flags);
+    }
+
+    /**
+     * Used to make this class parcelable.
+     *
+     * @hide
+     */
+    public static final Parcelable.Creator<TvInputInfo> CREATOR =
+            new Parcelable.Creator<TvInputInfo>() {
+        @Override
+        public TvInputInfo createFromParcel(Parcel in) {
+            return new TvInputInfo(in);
+        }
+
+        @Override
+        public TvInputInfo[] newArray(int size) {
+            return new TvInputInfo[size];
+        }
+    };
+
+    private TvInputInfo(Parcel in) {
+        mId = in.readString();
+        mService = ResolveInfo.CREATOR.createFromParcel(in);
+    }
+}
diff --git a/core/java/android/tv/TvInputManager.java b/core/java/android/tv/TvInputManager.java
new file mode 100644
index 0000000..0b6ab64
--- /dev/null
+++ b/core/java/android/tv/TvInputManager.java
@@ -0,0 +1,392 @@
+/*
+ * 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.tv;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Surface;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Central system API to the overall TV input framework (TIF) architecture, which arbitrates
+ * interaction between applications and the selected TV inputs.
+ */
+public final class TvInputManager {
+    private static final String TAG = "TvInputManager";
+
+    private final ITvInputManager mService;
+
+    // A mapping from an input to the list of its TvInputListenerRecords.
+    private final Map<ComponentName, List<TvInputListenerRecord>> mTvInputListenerRecordsMap =
+            new HashMap<ComponentName, List<TvInputListenerRecord>>();
+
+    // A mapping from the sequence number of a session to its SessionCreateCallbackRecord.
+    private final SparseArray<SessionCreateCallbackRecord> mSessionCreateCallbackRecordMap =
+            new SparseArray<SessionCreateCallbackRecord>();
+
+    // A sequence number for the next session to be created. Should be protected by a lock
+    // {@code mSessionCreateCallbackRecordMap}.
+    private int mNextSeq;
+
+    private final ITvInputClient mClient;
+
+    private final int mUserId;
+
+    /**
+     * Interface used to receive the created session.
+     */
+    public interface SessionCreateCallback {
+        /**
+         * This is called after {@link TvInputManager#createSession} has been processed.
+         *
+         * @param session A {@link TvInputManager.Session} instance created. This can be
+         *            {@code null} if the creation request failed.
+         */
+        void onSessionCreated(Session session);
+    }
+
+    private static final class SessionCreateCallbackRecord {
+        private final SessionCreateCallback mSessionCreateCallback;
+        private final Handler mHandler;
+
+        public SessionCreateCallbackRecord(SessionCreateCallback sessionCreateCallback,
+                Handler handler) {
+            mSessionCreateCallback = sessionCreateCallback;
+            mHandler = handler;
+        }
+
+        public void postSessionCreated(final Session session) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCreateCallback.onSessionCreated(session);
+                }
+            });
+        }
+    }
+
+    /**
+     * Interface used to monitor status of the TV input.
+     */
+    public abstract static class TvInputListener {
+        /**
+         * This is called when the availability status of a given TV input is changed.
+         *
+         * @param name {@link ComponentName} of {@link android.app.Service} that implements the
+         *            given TV input.
+         * @param isAvailable {@code true} if the given TV input is available to show TV programs.
+         *            {@code false} otherwise.
+         */
+        public void onAvailabilityChanged(ComponentName name, boolean isAvailable) {
+        }
+    }
+
+    private static final class TvInputListenerRecord {
+        private final TvInputListener mListener;
+        private final Handler mHandler;
+
+        public TvInputListenerRecord(TvInputListener listener, Handler handler) {
+            mListener = listener;
+            mHandler = handler;
+        }
+
+        public TvInputListener getListener() {
+            return mListener;
+        }
+
+        public void postAvailabilityChanged(final ComponentName name, final boolean isAvailable) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mListener.onAvailabilityChanged(name, isAvailable);
+                }
+            });
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public TvInputManager(ITvInputManager service, int userId) {
+        mService = service;
+        mUserId = userId;
+        mClient = new ITvInputClient.Stub() {
+            @Override
+            public void onSessionCreated(ComponentName name, IBinder token, int seq) {
+                synchronized (mSessionCreateCallbackRecordMap) {
+                    SessionCreateCallbackRecord record = mSessionCreateCallbackRecordMap.get(seq);
+                    mSessionCreateCallbackRecordMap.delete(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for " + token);
+                        return;
+                    }
+                    Session session = null;
+                    if (token != null) {
+                        session = new Session(name, token, mService, mUserId);
+                    }
+                    record.postSessionCreated(session);
+                }
+            }
+
+            @Override
+            public void onAvailabilityChanged(ComponentName name, boolean isAvailable) {
+                synchronized (mTvInputListenerRecordsMap) {
+                    List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(name);
+                    if (records == null) {
+                        // Silently ignore - no listener is registered yet.
+                        return;
+                    }
+                    int recordsCount = records.size();
+                    for (int i = 0; i < recordsCount; i++) {
+                        records.get(i).postAvailabilityChanged(name, isAvailable);
+                    }
+                }
+            }
+        };
+    }
+
+    /**
+     * Returns the complete list of TV inputs on the system.
+     *
+     * @return List of {@link TvInputInfo} for each TV input that describes its meta information.
+     */
+    public List<TvInputInfo> getTvInputList() {
+        try {
+            return mService.getTvInputList(mUserId);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Returns the availability of a given TV input.
+     *
+     * @param name {@link ComponentName} of {@link android.app.Service} that implements the given TV
+     *            input.
+     * @throws IllegalArgumentException if the argument is {@code null}.
+     * @throws IllegalStateException If there is no {@link TvInputListener} registered on the given
+     *             TV input.
+     */
+    public boolean getAvailability(ComponentName name) {
+        if (name == null) {
+            throw new IllegalArgumentException("name cannot be null");
+        }
+        synchronized (mTvInputListenerRecordsMap) {
+            List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(name);
+            if (records == null || records.size() == 0) {
+                throw new IllegalStateException("At least one listener should be registered.");
+            }
+        }
+        try {
+            return mService.getAvailability(mClient, name, mUserId);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Registers a {@link TvInputListener} for a given TV input.
+     *
+     * @param name {@link ComponentName} of {@link android.app.Service} that implements the given TV
+     *            input.
+     * @param listener a listener used to monitor status of the given TV input.
+     * @param handler a {@link Handler} that the status change will be delivered to.
+     * @throws IllegalArgumentException if any of the arguments is {@code null}.
+     */
+    public void registerListener(ComponentName name, TvInputListener listener, Handler handler) {
+        if (name == null) {
+            throw new IllegalArgumentException("name cannot be null");
+        }
+        if (listener == null) {
+            throw new IllegalArgumentException("listener cannot be null");
+        }
+        if (handler == null) {
+            throw new IllegalArgumentException("handler cannot be null");
+        }
+        synchronized (mTvInputListenerRecordsMap) {
+            List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(name);
+            if (records == null) {
+                records = new ArrayList<TvInputListenerRecord>();
+                mTvInputListenerRecordsMap.put(name, records);
+                try {
+                    mService.registerCallback(mClient, name, mUserId);
+                } catch (RemoteException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+            records.add(new TvInputListenerRecord(listener, handler));
+        }
+    }
+
+    /**
+     * Unregisters the existing {@link TvInputListener} for a given TV input.
+     *
+     * @param name {@link ComponentName} of {@link android.app.Service} that implements the given TV
+     *            input.
+     * @param listener the existing listener to remove for the given TV input.
+     * @throws IllegalArgumentException if any of the arguments is {@code null}.
+     */
+    public void unregisterListener(ComponentName name, final TvInputListener listener) {
+        if (name == null) {
+            throw new IllegalArgumentException("name cannot be null");
+        }
+        if (listener == null) {
+            throw new IllegalArgumentException("listener cannot be null");
+        }
+        synchronized (mTvInputListenerRecordsMap) {
+            List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(name);
+            if (records == null) {
+                Log.e(TAG, "No listener found for " + name.getClassName());
+                return;
+            }
+            for (Iterator<TvInputListenerRecord> it = records.iterator(); it.hasNext();) {
+                TvInputListenerRecord record = it.next();
+                if (record.getListener() == listener) {
+                    it.remove();
+                }
+            }
+            if (records.isEmpty()) {
+                try {
+                    mService.unregisterCallback(mClient, name, mUserId);
+                } catch (RemoteException e) {
+                    throw new RuntimeException(e);
+                } finally {
+                    mTvInputListenerRecordsMap.remove(name);
+                }
+            }
+        }
+    }
+
+    /**
+     * Creates a {@link TvInputSession} interface for a given TV input.
+     * <p>
+     * The number of sessions that can be created at the same time is limited by the capability of
+     * the given TV input.
+     * </p>
+     *
+     * @param name {@link ComponentName} of {@link android.app.Service} that implements the given TV
+     *            input.
+     * @param callback a callback used to receive the created session.
+     * @param handler a {@link Handler} that the session creation will be delivered to.
+     * @throws IllegalArgumentException if any of the arguments is {@code null}.
+     */
+    public void createSession(ComponentName name, final SessionCreateCallback callback,
+            Handler handler) {
+        if (name == null) {
+            throw new IllegalArgumentException("name cannot be null");
+        }
+        if (callback == null) {
+            throw new IllegalArgumentException("callback cannot be null");
+        }
+        if (handler == null) {
+            throw new IllegalArgumentException("handler cannot be null");
+        }
+        SessionCreateCallbackRecord record = new SessionCreateCallbackRecord(callback, handler);
+        synchronized (mSessionCreateCallbackRecordMap) {
+            int seq = mNextSeq++;
+            mSessionCreateCallbackRecordMap.put(seq, record);
+            try {
+                mService.createSession(mClient, name, seq, mUserId);
+            } catch (RemoteException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    /** The Session provides the per-session functionality of TV inputs. */
+    public static final class Session {
+        private final ITvInputManager mService;
+        private final IBinder mToken;
+        private final int mUserId;
+
+        /** @hide */
+        private Session(ComponentName name, IBinder token, ITvInputManager service, int userId) {
+            mToken = token;
+            mService = service;
+            mUserId = userId;
+        }
+
+        /**
+         * Releases this session.
+         */
+        public void release() {
+            try {
+                mService.releaseSession(mToken, mUserId);
+            } catch (RemoteException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        /**
+         * Sets the {@link android.view.Surface} for this session.
+         *
+         * @param surface A {@link android.view.Surface} used to render video.
+         */
+        public void setSurface(Surface surface) {
+            // surface can be null.
+            try {
+                mService.setSurface(mToken, surface, mUserId);
+            } catch (RemoteException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        /**
+         * Sets the relative volume of this session to handle a change of audio focus.
+         *
+         * @param volume A volume value between 0.0f to 1.0f.
+         * @throws IllegalArgumentException if the volume value is out of range.
+         */
+        public void setVolume(float volume) {
+            try {
+                if (volume < 0.0f || volume > 1.0f) {
+                    throw new IllegalArgumentException("volume should be between 0.0f and 1.0f");
+                }
+                mService.setVolume(mToken, volume, mUserId);
+            } catch (RemoteException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        /**
+         * Tunes to a given channel.
+         *
+         * @param channelUri The URI of a channel.
+         * @throws IllegalArgumentException if the argument is {@code null}.
+         */
+        public void tune(Uri channelUri) {
+            if (channelUri == null) {
+                throw new IllegalArgumentException("channelUri cannot be null");
+            }
+            try {
+                mService.tune(mToken, channelUri, mUserId);
+            } catch (RemoteException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+}
diff --git a/core/java/android/tv/TvInputService.java b/core/java/android/tv/TvInputService.java
new file mode 100644
index 0000000..e43cc95
--- /dev/null
+++ b/core/java/android/tv/TvInputService.java
@@ -0,0 +1,254 @@
+/*
+ * 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.tv;
+
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.Surface;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * A base class for implementing television input service.
+ */
+public abstract class TvInputService extends Service {
+    // STOPSHIP: Turn debugging off.
+    private static final boolean DEBUG = true;
+    private static final String TAG = "TvInputService";
+
+    /**
+     * This is the interface name that a service implementing a TV input should say that it support
+     * -- that is, this is the action it uses for its intent filter. To be supported, the service
+     * must also require the {@link android.Manifest.permission#BIND_TV_INPUT} permission so that
+     * other applications cannot abuse it.
+     */
+    public static final String SERVICE_INTERFACE = "android.tv.TvInputService";
+
+    private ComponentName mComponentName;
+    private final Handler mHandler = new ServiceHandler();
+    private final RemoteCallbackList<ITvInputServiceCallback> mCallbacks =
+            new RemoteCallbackList<ITvInputServiceCallback>();
+    private boolean mAvailable;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mComponentName = new ComponentName(getPackageName(), getClass().getName());
+    }
+
+    @Override
+    public final IBinder onBind(Intent intent) {
+        return new ITvInputService.Stub() {
+            @Override
+            public void registerCallback(ITvInputServiceCallback cb) {
+                if (cb != null) {
+                    mCallbacks.register(cb);
+                    // The first time a callback is registered, the service needs to report its
+                    // availability status so that the system can know its initial value.
+                    try {
+                        cb.onAvailabilityChanged(mComponentName, mAvailable);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "error in onAvailabilityChanged", e);
+                    }
+                }
+            }
+
+            @Override
+            public void unregisterCallback(ITvInputServiceCallback cb) {
+                if (cb != null) {
+                    mCallbacks.unregister(cb);
+                }
+            }
+
+            @Override
+            public void createSession(ITvInputSessionCallback cb) {
+                if (cb != null) {
+                    mHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, cb).sendToTarget();
+                }
+            }
+        };
+    }
+
+    /**
+     * Convenience method to notify an availability change of this TV input service.
+     *
+     * @param available {@code true} if the input service is available to show TV programs.
+     */
+    public final void setAvailable(boolean available) {
+        if (available != mAvailable) {
+            mAvailable = available;
+            mHandler.obtainMessage(ServiceHandler.DO_BROADCAST_AVAILABILITY_CHANGE, available)
+                    .sendToTarget();
+        }
+    }
+
+    /**
+     * Get the number of callbacks that are registered.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public final int getRegisteredCallbackCount() {
+        return mCallbacks.getRegisteredCallbackCount();
+    }
+
+    /**
+     * Returns a concrete implementation of {@link TvInputSessionImpl}.
+     * <p>
+     * May return {@code null} if this TV input service fails to create a session for some reason.
+     * </p>
+     */
+    public abstract TvInputSessionImpl onCreateSession();
+
+    /**
+     * Base class for derived classes to implement to provide {@link TvInputSession}.
+     */
+    public abstract static class TvInputSessionImpl {
+        /**
+         * Called when the session is released.
+         */
+        public abstract void onRelease();
+
+        /**
+         * Sets the {@link Surface} for the current input session on which the TV input renders
+         * video.
+         *
+         * @param surface {@link Surface} an application passes to this TV input session.
+         * @return {@code true} if the surface was set, {@code false} otherwise.
+         */
+        public abstract boolean onSetSurface(Surface surface);
+
+        /**
+         * Sets the relative volume of the current TV input session to handle the change of audio
+         * focus by setting.
+         *
+         * @param volume Volume scale from 0.0 to 1.0.
+         */
+        public abstract void onSetVolume(float volume);
+
+        /**
+         * Tunes to a given channel.
+         *
+         * @param channelUri The URI of the channel.
+         * @return {@code true} the tuning was successful, {@code false} otherwise.
+         */
+        public abstract boolean onTune(Uri channelUri);
+    }
+
+    /**
+     * Internal implementation of {@link TvInputSession}. This takes care of basic maintenance of
+     * the TV input session but most behavior must be implemented in {@link TvInputSessionImpl}
+     * returned by {@link TvInputService#onCreateSession}.
+     */
+    private static class TvInputSessionImplInternal extends TvInputSession {
+        private final TvInputSessionImpl mSessionImpl;
+
+        public TvInputSessionImplInternal(TvInputSessionImpl sessionImpl) {
+            mSessionImpl = sessionImpl;
+        }
+
+        /**
+         * This method is called when the application would like to stop using the current input
+         * session.
+         */
+        @Override
+        public final void release() {
+            mSessionImpl.onRelease();
+        }
+
+        /**
+         * Calls {@link TvInputSessionImpl#onSetSurface}.
+         */
+        @Override
+        public final void setSurface(Surface surface) {
+            mSessionImpl.onSetSurface(surface);
+            // TODO: Handle failure.
+        }
+
+        /**
+         * Calls {@link TvInputSessionImpl#onSetVolume}.
+         */
+        @Override
+        public final void setVolume(float volume) {
+            mSessionImpl.onSetVolume(volume);
+        }
+
+        /**
+         * Calls {@link TvInputSessionImpl#onTune}.
+         */
+        @Override
+        public final void tune(Uri channelUri) {
+            mSessionImpl.onTune(channelUri);
+            // TODO: Handle failure.
+        }
+    }
+
+    private final class ServiceHandler extends Handler {
+        private static final int DO_CREATE_SESSION = 1;
+        private static final int DO_BROADCAST_AVAILABILITY_CHANGE = 2;
+
+        @Override
+        public final void handleMessage(Message msg) {
+            switch (msg.what) {
+                case DO_CREATE_SESSION: {
+                    ITvInputSessionCallback cb = (ITvInputSessionCallback) msg.obj;
+                    try {
+                        TvInputSessionImpl sessionImpl = onCreateSession();
+                        if (sessionImpl == null) {
+                            // Failed to create a session.
+                            cb.onSessionCreated(null);
+                            return;
+                        }
+                        ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this,
+                                new TvInputSessionImplInternal(sessionImpl));
+                        cb.onSessionCreated(stub);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "error in onSessionCreated");
+                    }
+                    return;
+                }
+                case DO_BROADCAST_AVAILABILITY_CHANGE: {
+                    boolean isAvailable = (Boolean) msg.obj;
+                    int n = mCallbacks.beginBroadcast();
+                    try {
+                        for (int i = 0; i < n; i++) {
+                            mCallbacks.getBroadcastItem(i).onAvailabilityChanged(mComponentName,
+                                    isAvailable);
+                        }
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Unexpected exception", e);
+                    } finally {
+                        mCallbacks.finishBroadcast();
+                    }
+                    return;
+                }
+                default: {
+                    Log.w(TAG, "Unhandled message code: " + msg.what);
+                    return;
+                }
+            }
+        }
+    }
+}
diff --git a/core/java/android/tv/TvInputSession.java b/core/java/android/tv/TvInputSession.java
new file mode 100644
index 0000000..5b70a0b
--- /dev/null
+++ b/core/java/android/tv/TvInputSession.java
@@ -0,0 +1,57 @@
+/*
+ * 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.tv;
+
+import android.net.Uri;
+import android.view.Surface;
+
+/**
+ * The TvInputSession provides the per-session functionality of TvInputService.
+ *
+ * @hide
+ */
+public abstract class TvInputSession {
+    /**
+     * This method is called when the application would like to stop using the current input
+     * session.
+     */
+    public void release() { }
+
+    /**
+     * Sets the {@link Surface} for the current input session on which the TV input renders video.
+     *
+     * @param surface {@link Surface} to be used for the video playback of this session.
+     */
+    public void setSurface(Surface surface) { }
+
+    /**
+     * This method is called when the application needs to handle the change of audio focus by
+     * setting the relative volume of the current TV input service session.
+     *
+     * @param volume Volume scale from 0.0 to 1.0.
+     */
+    // TODO: Remove this once it becomes irrelevant for applications to handle audio focus. The plan
+    // is to introduce some new concepts that will solve a number of problems in audio policy today.
+    public void setVolume(float volume) { }
+
+    /**
+     * Tunes to a given channel.
+     *
+     * @param channelUri The URI of the channel.
+     */
+    public void tune(Uri channelUri) { }
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3857cd1..0f772f1 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2036,6 +2036,13 @@
         android:description="@string/permdesc_bindRemoteDisplay"
         android:protectionLevel="signature" />
 
+    <!-- Must be required by a {@link android.tv.TvInputService}
+         to ensure that only the system can bind to it. -->
+    <permission android:name="android.permission.BIND_TV_INPUT"
+        android:label="@string/permlab_bindTvInput"
+        android:description="@string/permdesc_bindTvInput"
+        android:protectionLevel="signature|system" />
+
     <!-- Must be required by device administration receiver, to ensure that only the
          system can interact with it. -->
     <permission android:name="android.permission.BIND_DEVICE_ADMIN"
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 902aea8..4a121d1 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1056,6 +1056,12 @@
         a device administrator. Should never be needed for normal apps.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_bindTvInput">bind to a TV input</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_bindTvInput">Allows the holder to bind to the top-level
+        interface of a TV input. Should never be needed for normal apps.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_manageDeviceAdmins">add or remove a device admin</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_manageDeviceAdmins">Allows the holder to add or remove active device