Add overlay view in Tv Input Framework

A TvInputService app developers sometimes want to draw UI above a surface
playing TV. For this purpose, we add a window in TIS and allow developers to
attach their customized view on the TV surface.

Change-Id: I65c3dffa17580b8d4c42fac58bbfc8dad338c185
diff --git a/api/current.txt b/api/current.txt
index 42b1e18..61c17f1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -27822,7 +27822,6 @@
 
   public static final class TvInputManager.Session {
     method public void release();
-    method public void setSurface(android.view.Surface);
     method public void setVolume(float);
     method public void tune(android.net.Uri);
   }
@@ -27844,12 +27843,22 @@
     field public static final java.lang.String SERVICE_INTERFACE = "android.tv.TvInputService";
   }
 
-  public static abstract class TvInputService.TvInputSessionImpl {
+  public abstract class TvInputService.TvInputSessionImpl {
     ctor public TvInputService.TvInputSessionImpl();
+    method public android.view.View onCreateOverlayView();
     method public abstract void onRelease();
     method public abstract boolean onSetSurface(android.view.Surface);
     method public abstract void onSetVolume(float);
     method public abstract boolean onTune(android.net.Uri);
+    method public void setOverlayViewEnabled(boolean);
+  }
+
+  public class TvView extends android.view.SurfaceView {
+    ctor public TvView(android.content.Context);
+    ctor public TvView(android.content.Context, android.util.AttributeSet);
+    ctor public TvView(android.content.Context, android.util.AttributeSet, int);
+    method public void bindTvInput(android.content.ComponentName, android.tv.TvInputManager.SessionCreateCallback);
+    method public void unbindTvInput();
   }
 
 }
diff --git a/core/java/android/tv/ITvInputManager.aidl b/core/java/android/tv/ITvInputManager.aidl
index a927dc9..a4c99e4 100644
--- a/core/java/android/tv/ITvInputManager.aidl
+++ b/core/java/android/tv/ITvInputManager.aidl
@@ -17,6 +17,7 @@
 package android.tv;
 
 import android.content.ComponentName;
+import android.graphics.Rect;
 import android.net.Uri;
 import android.tv.ITvInputClient;
 import android.tv.TvInputInfo;
@@ -40,4 +41,9 @@
     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);
+
+    void createOverlayView(in IBinder sessionToken, in IBinder windowToken, in Rect frame,
+            int userId);
+    void relayoutOverlayView(in IBinder sessionToken, in Rect frame, int userId);
+    void removeOverlayView(in IBinder sessionToken, int userId);
 }
diff --git a/core/java/android/tv/ITvInputService.aidl b/core/java/android/tv/ITvInputService.aidl
index d80f286..672784f 100644
--- a/core/java/android/tv/ITvInputService.aidl
+++ b/core/java/android/tv/ITvInputService.aidl
@@ -17,7 +17,6 @@
 package android.tv;
 
 import android.tv.ITvInputServiceCallback;
-import android.tv.ITvInputSession;
 import android.tv.ITvInputSessionCallback;
 
 /**
diff --git a/core/java/android/tv/ITvInputSession.aidl b/core/java/android/tv/ITvInputSession.aidl
index d379d2d..32fee4b 100644
--- a/core/java/android/tv/ITvInputSession.aidl
+++ b/core/java/android/tv/ITvInputSession.aidl
@@ -16,6 +16,7 @@
 
 package android.tv;
 
+import android.graphics.Rect;
 import android.net.Uri;
 import android.view.Surface;
 
@@ -31,4 +32,8 @@
     // 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);
+
+    void createOverlayView(in IBinder windowToken, in Rect frame);
+    void relayoutOverlayView(in Rect frame);
+    void removeOverlayView();
 }
diff --git a/core/java/android/tv/ITvInputSessionWrapper.java b/core/java/android/tv/ITvInputSessionWrapper.java
index 66fe5e1..a6e0877 100644
--- a/core/java/android/tv/ITvInputSessionWrapper.java
+++ b/core/java/android/tv/ITvInputSessionWrapper.java
@@ -17,13 +17,16 @@
 package android.tv;
 
 import android.content.Context;
+import android.graphics.Rect;
 import android.net.Uri;
+import android.os.IBinder;
 import android.os.Message;
 import android.tv.TvInputService.TvInputSessionImpl;
 import android.util.Log;
 import android.view.Surface;
 
 import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
 
 /**
  * Implements the internal ITvInputSession interface to convert incoming calls on to it back to
@@ -38,6 +41,9 @@
     private static final int DO_SET_SURFACE = 2;
     private static final int DO_SET_VOLUME = 3;
     private static final int DO_TUNE = 4;
+    private static final int DO_CREATE_OVERLAY_VIEW = 5;
+    private static final int DO_RELAYOUT_OVERLAY_VIEW = 6;
+    private static final int DO_REMOVE_OVERLAY_VIEW = 7;
 
     private TvInputSessionImpl mTvInputSession;
     private final HandlerCaller mCaller;
@@ -71,6 +77,20 @@
                 mTvInputSession.tune((Uri) msg.obj);
                 return;
             }
+            case DO_CREATE_OVERLAY_VIEW: {
+                SomeArgs args = (SomeArgs) msg.obj;
+                mTvInputSession.createOverlayView((IBinder) args.arg1, (Rect) args.arg2);
+                args.recycle();
+                return;
+            }
+            case DO_RELAYOUT_OVERLAY_VIEW: {
+                mTvInputSession.relayoutOverlayView((Rect) msg.obj);
+                return;
+            }
+            case DO_REMOVE_OVERLAY_VIEW: {
+                mTvInputSession.removeOverlayView(true);
+                return;
+            }
             default: {
                 Log.w(TAG, "Unhandled message code: " + msg.what);
                 return;
@@ -97,4 +117,20 @@
     public void tune(Uri channelUri) {
         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_TUNE, channelUri));
     }
+
+    @Override
+    public void createOverlayView(IBinder windowToken, Rect frame) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_CREATE_OVERLAY_VIEW, windowToken,
+                frame));
+    }
+
+    @Override
+    public void relayoutOverlayView(Rect frame) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_RELAYOUT_OVERLAY_VIEW, frame));
+    }
+
+    @Override
+    public void removeOverlayView() {
+        mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_OVERLAY_VIEW));
+    }
 }
diff --git a/core/java/android/tv/TvInputManager.java b/core/java/android/tv/TvInputManager.java
index 4cf2b35..05f0b9c 100644
--- a/core/java/android/tv/TvInputManager.java
+++ b/core/java/android/tv/TvInputManager.java
@@ -17,6 +17,7 @@
 package android.tv;
 
 import android.content.ComponentName;
+import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.IBinder;
@@ -24,6 +25,7 @@
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.Surface;
+import android.view.View;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -320,8 +322,8 @@
     /** 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;
+        private IBinder mToken;
 
         /** @hide */
         private Session(ComponentName name, IBinder token, ITvInputManager service, int userId) {
@@ -332,10 +334,16 @@
 
         /**
          * Releases this session.
+         *
+         * @throws IllegalStateException if the session has been already released.
          */
         public void release() {
+            if (mToken == null) {
+                throw new IllegalStateException("the session has been already released");
+            }
             try {
                 mService.releaseSession(mToken, mUserId);
+                mToken = null;
             } catch (RemoteException e) {
                 throw new RuntimeException(e);
             }
@@ -345,8 +353,12 @@
          * Sets the {@link android.view.Surface} for this session.
          *
          * @param surface A {@link android.view.Surface} used to render video.
+         * @throws IllegalStateException if the session has been already released.
          */
-        public void setSurface(Surface surface) {
+        void setSurface(Surface surface) {
+            if (mToken == null) {
+                throw new IllegalStateException("the session has been already released");
+            }
             // surface can be null.
             try {
                 mService.setSurface(mToken, surface, mUserId);
@@ -360,8 +372,12 @@
          *
          * @param volume A volume value between 0.0f to 1.0f.
          * @throws IllegalArgumentException if the volume value is out of range.
+         * @throws IllegalStateException if the session has been already released.
          */
         public void setVolume(float volume) {
+            if (mToken == null) {
+                throw new IllegalStateException("the session has been already released");
+            }
             try {
                 if (volume < 0.0f || volume > 1.0f) {
                     throw new IllegalArgumentException("volume should be between 0.0f and 1.0f");
@@ -377,16 +393,90 @@
          *
          * @param channelUri The URI of a channel.
          * @throws IllegalArgumentException if the argument is {@code null}.
+         * @throws IllegalStateException if the session has been already released.
          */
         public void tune(Uri channelUri) {
             if (channelUri == null) {
                 throw new IllegalArgumentException("channelUri cannot be null");
             }
+            if (mToken == null) {
+                throw new IllegalStateException("the session has been already released");
+            }
             try {
                 mService.tune(mToken, channelUri, mUserId);
             } catch (RemoteException e) {
                 throw new RuntimeException(e);
             }
         }
+
+        /**
+         * Creates an overlay view. Once the overlay view is created, {@link #relayoutOverlayView}
+         * should be called whenever the layout of its containing view is changed.
+         * {@link #removeOverlayView()} should be called to remove the overlay view.
+         * Since a session can have only one overlay view, this method should be called only once
+         * or it can be called again after calling {@link #removeOverlayView()}.
+         *
+         * @param view A view playing TV.
+         * @param frame A position of the overlay view.
+         * @throws IllegalArgumentException if any of the arguments is {@code null}.
+         * @throws IllegalStateException if {@code view} is not attached to a window or
+         *         if the session has been already released.
+         */
+        void createOverlayView(View view, Rect frame) {
+            if (view == null) {
+                throw new IllegalArgumentException("view cannot be null");
+            }
+            if (frame == null) {
+                throw new IllegalArgumentException("frame cannot be null");
+            }
+            if (view.getWindowToken() == null) {
+                throw new IllegalStateException("view must be attached to a window");
+            }
+            if (mToken == null) {
+                throw new IllegalStateException("the session has been already released");
+            }
+            try {
+                mService.createOverlayView(mToken, view.getWindowToken(), frame, mUserId);
+            } catch (RemoteException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        /**
+         * Relayouts the current overlay view.
+         *
+         * @param frame A new position of the overlay view.
+         * @throws IllegalArgumentException if the arguments is {@code null}.
+         * @throws IllegalStateException if the session has been already released.
+         */
+        void relayoutOverlayView(Rect frame) {
+            if (frame == null) {
+                throw new IllegalArgumentException("frame cannot be null");
+            }
+            if (mToken == null) {
+                throw new IllegalStateException("the session has been already released");
+            }
+            try {
+                mService.relayoutOverlayView(mToken, frame, mUserId);
+            } catch (RemoteException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        /**
+         * Removes the current overlay view.
+         *
+         * @throws IllegalStateException if the session has been already released.
+         */
+        void removeOverlayView() {
+            if (mToken == null) {
+                throw new IllegalStateException("the session has been already released");
+            }
+            try {
+                mService.removeOverlayView(mToken, mUserId);
+            } catch (RemoteException e) {
+                throw new RuntimeException(e);
+            }
+        }
     }
 }
diff --git a/core/java/android/tv/TvInputService.java b/core/java/android/tv/TvInputService.java
index d7f6c32..80eb407 100644
--- a/core/java/android/tv/TvInputService.java
+++ b/core/java/android/tv/TvInputService.java
@@ -18,7 +18,10 @@
 
 import android.app.Service;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.IBinder;
@@ -26,7 +29,10 @@
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.util.Log;
+import android.view.Gravity;
 import android.view.Surface;
+import android.view.View;
+import android.view.WindowManager;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -125,7 +131,37 @@
     /**
      * Base class for derived classes to implement to provide {@link TvInputManager.Session}.
      */
-    public abstract static class TvInputSessionImpl {
+    public abstract class TvInputSessionImpl {
+        private final WindowManager mWindowManager;
+        private WindowManager.LayoutParams mWindowParams;
+        private View mOverlayView;
+        private boolean mOverlayViewEnabled;
+        private IBinder mWindowToken;
+        private Rect mOverlayFrame;
+
+        public TvInputSessionImpl() {
+            mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
+        }
+
+        public void setOverlayViewEnabled(final boolean enable) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    if (enable == mOverlayViewEnabled) {
+                        return;
+                    }
+                    mOverlayViewEnabled = enable;
+                    if (enable) {
+                        if (mWindowToken != null) {
+                            createOverlayView(mWindowToken, mOverlayFrame);
+                        }
+                    } else {
+                        removeOverlayView(false);
+                    }
+                }
+            });
+        }
+
         /**
          * Called when the session is released.
          */
@@ -157,11 +193,22 @@
         public abstract boolean onTune(Uri channelUri);
 
         /**
+         * Called when an application requests to create an overlay view. Each session
+         * implementation can override this method and return its own view.
+         *
+         * @return a view attached to the overlay window
+         */
+        public View onCreateOverlayView() {
+            return null;
+        }
+
+        /**
          * This method is called when the application would like to stop using the current input
          * session.
          */
         void release() {
             onRelease();
+            removeOverlayView(true);
         }
 
         /**
@@ -186,6 +233,87 @@
             onTune(channelUri);
             // TODO: Handle failure.
         }
+
+        /**
+         * Creates an overlay view. This calls {@link onCreateOverlayView} to get
+         * a view to attach to the overlay window.
+         *
+         * @param windowToken A window token of an application.
+         * @param frame A position of the overlay view.
+         */
+        void createOverlayView(IBinder windowToken, Rect frame) {
+            if (mOverlayView != null) {
+                mWindowManager.removeView(mOverlayView);
+                mOverlayView = null;
+            }
+            if (DEBUG) {
+                Log.d(TAG, "create overlay view(" + frame + ")");
+            }
+            mWindowToken = windowToken;
+            mOverlayFrame = frame;
+            if (!mOverlayViewEnabled) {
+                return;
+            }
+            mOverlayView = onCreateOverlayView();
+            if (mOverlayView == null) {
+                return;
+            }
+            // TvView's window type is TYPE_APPLICATION_MEDIA and we want to create
+            // an overlay window above the media window but below the application window.
+            int type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
+            // We make the overlay view non-focusable and non-touchable so that
+            // the application that owns the window token can decide whether to consume or
+            // dispatch the input events.
+            int flag = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                    | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+            mWindowParams = new WindowManager.LayoutParams(
+                    frame.right - frame.left, frame.bottom - frame.top,
+                    frame.left, frame.top, type, flag, PixelFormat.TRANSPARENT);
+            mWindowParams.privateFlags |=
+                    WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+            mWindowParams.gravity = Gravity.START | Gravity.TOP;
+            mWindowParams.token = windowToken;
+            mWindowManager.addView(mOverlayView, mWindowParams);
+        }
+
+        /**
+         * Relayouts the current overlay view.
+         *
+         * @param frame A new position of the overlay view.
+         */
+        void relayoutOverlayView(Rect frame) {
+            if (DEBUG) {
+                Log.d(TAG, "relayout overlay view(" + frame + ")");
+            }
+            mOverlayFrame = frame;
+            if (!mOverlayViewEnabled || mOverlayView == null) {
+                return;
+            }
+            mWindowParams.x = frame.left;
+            mWindowParams.y = frame.top;
+            mWindowParams.width = frame.right - frame.left;
+            mWindowParams.height = frame.bottom - frame.top;
+            mWindowManager.updateViewLayout(mOverlayView, mWindowParams);
+        }
+
+        /**
+         * Removes the current overlay view.
+         */
+        void removeOverlayView(boolean clearWindowToken) {
+            if (DEBUG) {
+                Log.d(TAG, "remove overlay view(" + mOverlayView + ")");
+            }
+            if (clearWindowToken) {
+                mWindowToken = null;
+                mOverlayFrame = null;
+            }
+            if (mOverlayView != null) {
+                mWindowManager.removeView(mOverlayView);
+                mOverlayView = null;
+                mWindowParams = null;
+            }
+        }
     }
 
     private final class ServiceHandler extends Handler {
diff --git a/core/java/android/tv/TvView.java b/core/java/android/tv/TvView.java
new file mode 100644
index 0000000..325950d
--- /dev/null
+++ b/core/java/android/tv/TvView.java
@@ -0,0 +1,228 @@
+/*
+ * 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.Context;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.tv.TvInputManager;
+import android.tv.TvInputManager.Session;
+import android.tv.TvInputManager.SessionCreateCallback;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.ViewTreeObserver;
+
+/**
+ * View playing TV
+ */
+public class TvView extends SurfaceView {
+    private static final String TAG = "TvView";
+
+    private final Handler mHandler = new Handler();
+    private TvInputManager.Session mSession;
+    private Surface mSurface;
+    private boolean mOverlayViewCreated;
+    private Rect mOverlayViewFrame;
+    private boolean mGlobalListenersAdded;
+    private TvInputManager mTvInputManager;
+    private SessionCreateCallback mSessionCreateCallback;
+
+    private SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
+        @Override
+        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+            Log.d(TAG, "surfaceChanged(holder=" + holder + ", format=" + format + ", width=" + width
+                    + ", height=" + height + ")");
+            if (holder.getSurface() == mSurface) {
+                return;
+            }
+            mSurface = holder.getSurface();
+            setSessionSurface(mSurface);
+        }
+
+        @Override
+        public void surfaceCreated(SurfaceHolder holder) {
+            mSurface = holder.getSurface();
+            setSessionSurface(mSurface);
+        }
+
+        @Override
+        public void surfaceDestroyed(SurfaceHolder holder) {
+            mSurface = null;
+            setSessionSurface(null);
+        }
+    };
+
+    public TvView(Context context) {
+        this(context, null, 0);
+    }
+
+    public TvView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public TvView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        getHolder().addCallback(mSurfaceHolderCallback);
+        mTvInputManager = (TvInputManager) getContext().getSystemService(Context.TV_INPUT_SERVICE);
+    }
+
+    /**
+     * Binds a TV input to this view. {@link SessionCreateCallback#onSessionCreated} will be
+     * called to send the result of this binding with {@link TvInputManager.Session}.
+     * If a TV input is already bound, the input will be unbound from this view and its session
+     * will be released.
+     *
+     * @param name TV input name will be bound to this view.
+     * @param callback called when TV input is bound. The callback sends
+     *        {@link TvInputManager.Session}
+     * @throws IllegalArgumentException if any of the arguments is {@code null}.
+     */
+    public void bindTvInput(ComponentName name, SessionCreateCallback callback) {
+        if (name == null) {
+            throw new IllegalArgumentException("name cannot be null");
+        }
+        if (callback == null) {
+            throw new IllegalArgumentException("callback cannot be null");
+        }
+        if (mSession != null) {
+            release();
+        }
+        // When bindTvInput is called multiple times before the callback is called,
+        // only the callback of the last bindTvInput call will be actually called back.
+        // The previous callbacks will be ignored. For the logic, mSessionCreateCallback
+        // is newly assigned for every bindTvInput call and compared with
+        // MySessionCreateCallback.this.
+        mSessionCreateCallback = new MySessionCreateCallback(callback);
+        mTvInputManager.createSession(name, mSessionCreateCallback, mHandler);
+    }
+
+    /**
+     * Unbinds a TV input currently bound. Its corresponding {@link TvInputManager.Session}
+     * is released.
+     */
+    public void unbindTvInput() {
+        if (mSession != null) {
+            release();
+        }
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        createSessionOverlayView();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        removeSessionOverlayView();
+        super.onDetachedFromWindow();
+    }
+
+    /** @hide */
+    @Override
+    protected void updateWindow(boolean force, boolean redrawNeeded) {
+        super.updateWindow(force, redrawNeeded);
+        relayoutSessionOverlayView();
+    }
+
+    private void release() {
+        setSessionSurface(null);
+        removeSessionOverlayView();
+        mSession.release();
+        mSession = null;
+    }
+
+    private void setSessionSurface(Surface surface) {
+        if (mSession == null) {
+            return;
+        }
+        mSession.setSurface(surface);
+    }
+
+    private void createSessionOverlayView() {
+        if (mSession == null || !isAttachedToWindow()
+                || mOverlayViewCreated) {
+            return;
+        }
+        mOverlayViewFrame = getViewFrameOnScreen();
+        mSession.createOverlayView(this, mOverlayViewFrame);
+        mOverlayViewCreated = true;
+    }
+
+    private void removeSessionOverlayView() {
+        if (mSession == null || !mOverlayViewCreated) {
+            return;
+        }
+        mSession.removeOverlayView();
+        mOverlayViewCreated = false;
+        mOverlayViewFrame = null;
+    }
+
+    private void relayoutSessionOverlayView() {
+        if (mSession == null || !isAttachedToWindow()
+                || !mOverlayViewCreated) {
+            return;
+        }
+        Rect viewFrame = getViewFrameOnScreen();
+        if (viewFrame.equals(mOverlayViewFrame)) {
+            return;
+        }
+        mSession.relayoutOverlayView(viewFrame);
+        mOverlayViewFrame = viewFrame;
+    }
+
+    private Rect getViewFrameOnScreen() {
+        int[] location = new int[2];
+        getLocationOnScreen(location);
+        return new Rect(location[0], location[1],
+                location[0] + getWidth(), location[1] + getHeight());
+    }
+
+    private class MySessionCreateCallback implements SessionCreateCallback {
+        final SessionCreateCallback mExternalCallback;
+
+        MySessionCreateCallback(SessionCreateCallback externalCallback) {
+            mExternalCallback = externalCallback;
+        }
+
+        @Override
+        public void onSessionCreated(Session session) {
+            if (this != mSessionCreateCallback) {
+                // This callback is obsolete.
+                session.release();
+                return;
+            }
+            mSession = session;
+            if (session != null) {
+                // mSurface may not be ready yet as soon as starting an application.
+                // In the case, we don't send Session.setSurface(null) unnecessarily.
+                // setSessionSurface will be called in surfaceCreated.
+                if (mSurface != null) {
+                    setSessionSurface(mSurface);
+                }
+                createSessionOverlayView();
+            }
+            if (mExternalCallback != null) {
+                mExternalCallback.onSessionCreated(session);
+            }
+        }
+    }
+}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 23123dd..4a2cc1a 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -422,7 +422,8 @@
         mWindowType = type;
     }
 
-    private void updateWindow(boolean force, boolean redrawNeeded) {
+    /** @hide */
+    protected void updateWindow(boolean force, boolean redrawNeeded) {
         if (!mHaveFrame) {
             return;
         }
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 16ed7fe..a72d4c6 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -26,6 +26,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.IBinder;
@@ -42,6 +43,7 @@
 import android.tv.TvInputService;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.Slog;
 import android.util.SparseArray;
 import android.view.Surface;
 
@@ -116,17 +118,19 @@
         UserState userState = getUserStateLocked(userId);
         userState.inputList.clear();
 
+        if (DEBUG) Slog.d(TAG, "buildTvInputList");
         PackageManager pm = mContext.getPackageManager();
         List<ResolveInfo> services = pm.queryIntentServices(
                 new Intent(TvInputService.SERVICE_INTERFACE), PackageManager.GET_SERVICES);
         for (ResolveInfo ri : services) {
             ServiceInfo si = ri.serviceInfo;
             if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
-                Log.w(TAG, "Skipping TV input " + si.name + ": it does not require the permission "
+                Slog.w(TAG, "Skipping TV input " + si.name + ": it does not require the permission "
                         + android.Manifest.permission.BIND_TV_INPUT);
                 continue;
             }
             TvInputInfo info = new TvInputInfo(ri);
+            if (DEBUG) Slog.d(TAG, "add " + info.getId());
             userState.inputList.add(info);
         }
     }
@@ -161,7 +165,7 @@
                     try {
                         state.session.release();
                     } catch (RemoteException e) {
-                        Log.e(TAG, "error in release", e);
+                        Slog.e(TAG, "error in release", e);
                     }
                 }
             }
@@ -173,7 +177,7 @@
                     try {
                         serviceState.service.unregisterCallback(serviceState.callback);
                     } catch (RemoteException e) {
-                        Log.e(TAG, "error in unregisterCallback", e);
+                        Slog.e(TAG, "error in unregisterCallback", e);
                     }
                 }
                 serviceState.clients.clear();
@@ -244,7 +248,7 @@
                 return;
             }
             if (DEBUG) {
-                Log.i(TAG, "bindServiceAsUser(name=" + name.getClassName() + ", userId=" + userId
+                Slog.d(TAG, "bindServiceAsUser(name=" + name.getClassName() + ", userId=" + userId
                         + ")");
             }
             Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(name);
@@ -255,7 +259,7 @@
             // This means that the service is already connected but its state indicates that we have
             // nothing to do with it. Then, disconnect the service.
             if (DEBUG) {
-                Log.i(TAG, "unbindService(name=" + name.getClassName() + ")");
+                Slog.d(TAG, "unbindService(name=" + name.getClassName() + ")");
             }
             mContext.unbindService(serviceState.connection);
             userState.serviceStateMap.remove(name);
@@ -265,7 +269,7 @@
     private void createSessionInternalLocked(ITvInputService service, final IBinder sessionToken,
             final SessionState sessionState, final int userId) {
         if (DEBUG) {
-            Log.d(TAG, "createSessionInternalLocked(name=" + sessionState.name.getClassName()
+            Slog.d(TAG, "createSessionInternalLocked(name=" + sessionState.name.getClassName()
                     + ")");
         }
         // Set up a callback to send the session token.
@@ -273,7 +277,7 @@
             @Override
             public void onSessionCreated(ITvInputSession session) {
                 if (DEBUG) {
-                    Log.d(TAG, "onSessionCreated(name=" + sessionState.name.getClassName() + ")");
+                    Slog.d(TAG, "onSessionCreated(name=" + sessionState.name.getClassName() + ")");
                 }
                 synchronized (mLock) {
                     sessionState.session = session;
@@ -293,7 +297,7 @@
         try {
             service.createSession(callback);
         } catch (RemoteException e) {
-            Log.e(TAG, "error in createSession", e);
+            Slog.e(TAG, "error in createSession", e);
             removeSessionStateLocked(sessionToken, userId);
             sendSessionTokenToClientLocked(sessionState.client, sessionState.name, null,
                     sessionState.seq, userId);
@@ -305,7 +309,7 @@
         try {
             client.onSessionCreated(name, sessionToken, seq);
         } catch (RemoteException exception) {
-            Log.e(TAG, "error in onSessionCreated", exception);
+            Slog.e(TAG, "error in onSessionCreated", exception);
         }
 
         if (sessionToken == null) {
@@ -394,7 +398,7 @@
                         try {
                             serviceState.service.registerCallback(serviceState.callback);
                         } catch (RemoteException e) {
-                            Log.e(TAG, "error in registerCallback", e);
+                            Slog.e(TAG, "error in registerCallback", e);
                         }
                     } else {
                         updateServiceConnectionLocked(name, resolvedUserId);
@@ -430,7 +434,7 @@
                     try {
                         serviceState.service.unregisterCallback(serviceState.callback);
                     } catch (RemoteException e) {
-                        Log.e(TAG, "error in unregisterCallback", e);
+                        Slog.e(TAG, "error in unregisterCallback", e);
                     } finally {
                         serviceState.callback = null;
                         updateServiceConnectionLocked(name, resolvedUserId);
@@ -491,7 +495,7 @@
                     try {
                         getSessionLocked(sessionToken, callingUid, resolvedUserId).release();
                     } catch (RemoteException e) {
-                        Log.e(TAG, "error in release", e);
+                        Slog.e(TAG, "error in release", e);
                     }
 
                     removeSessionStateLocked(sessionToken, resolvedUserId);
@@ -513,7 +517,7 @@
                         getSessionLocked(sessionToken, callingUid, resolvedUserId).setSurface(
                                 surface);
                     } catch (RemoteException e) {
-                        Log.e(TAG, "error in setSurface", e);
+                        Slog.e(TAG, "error in setSurface", e);
                     }
                 }
             } finally {
@@ -533,7 +537,7 @@
                         getSessionLocked(sessionToken, callingUid, resolvedUserId).setVolume(
                                 volume);
                     } catch (RemoteException e) {
-                        Log.e(TAG, "error in setVolume", e);
+                        Slog.e(TAG, "error in setVolume", e);
                     }
                 }
             } finally {
@@ -549,13 +553,10 @@
             final long identity = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
-                    SessionState sessionState = getUserStateLocked(resolvedUserId)
-                            .sessionStateMap.get(sessionToken);
-                    final String serviceName = sessionState.name.getClassName();
                     try {
                         getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(channelUri);
                     } catch (RemoteException e) {
-                        Log.e(TAG, "error in tune", e);
+                        Slog.e(TAG, "error in tune", e);
                         return;
                     }
                 }
@@ -563,6 +564,67 @@
                 Binder.restoreCallingIdentity(identity);
             }
         }
+
+        @Override
+        public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame,
+                int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "createOverlayView");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
+                                .createOverlayView(windowToken, frame);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "error in createOverlayView", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void relayoutOverlayView(IBinder sessionToken, Rect frame, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "relayoutOverlayView");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
+                                .relayoutOverlayView(frame);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "error in relayoutOverlayView", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void removeOverlayView(IBinder sessionToken, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "removeOverlayView");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
+                                .removeOverlayView();
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "error in removeOverlayView", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
     }
 
     private static final class UserState {
@@ -620,7 +682,7 @@
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
             if (DEBUG) {
-                Log.d(TAG, "onServiceConnected(name=" + name.getClassName() + ")");
+                Slog.d(TAG, "onServiceConnected(name=" + name.getClassName() + ")");
             }
             synchronized (mLock) {
                 ServiceState serviceState = getServiceStateLocked(name, mUserId);
@@ -632,7 +694,7 @@
                     try {
                         serviceState.service.registerCallback(serviceState.callback);
                     } catch (RemoteException e) {
-                        Log.e(TAG, "error in registerCallback", e);
+                        Slog.e(TAG, "error in registerCallback", e);
                     }
                 }
 
@@ -648,7 +710,7 @@
         @Override
         public void onServiceDisconnected(ComponentName name) {
             if (DEBUG) {
-                Log.d(TAG, "onServiceDisconnected(name=" + name.getClassName() + ")");
+                Slog.d(TAG, "onServiceDisconnected(name=" + name.getClassName() + ")");
             }
         }
     }
@@ -664,7 +726,7 @@
         public void onAvailabilityChanged(ComponentName name, boolean isAvailable)
                 throws RemoteException {
             if (DEBUG) {
-                Log.d(TAG, "onAvailabilityChanged(name=" + name.getClassName() + ", isAvailable="
+                Slog.d(TAG, "onAvailabilityChanged(name=" + name.getClassName() + ", isAvailable="
                         + isAvailable + ")");
             }
             synchronized (mLock) {