[CSAI] handle media view

Bug: 320779253
Test: mmm
Change-Id: I4102d7b57cda36433aa7ef86b91f670bb9af4ff6
diff --git a/media/java/android/media/tv/ad/ITvAdManager.aidl b/media/java/android/media/tv/ad/ITvAdManager.aidl
index a747e49..c4806fb 100644
--- a/media/java/android/media/tv/ad/ITvAdManager.aidl
+++ b/media/java/android/media/tv/ad/ITvAdManager.aidl
@@ -16,6 +16,7 @@
 
 package android.media.tv.ad;
 
+import android.graphics.Rect;
 import android.media.tv.ad.ITvAdClient;
 import android.media.tv.ad.ITvAdManagerCallback;
 import android.media.tv.ad.TvAdServiceInfo;
@@ -37,4 +38,9 @@
 
     void registerCallback(in ITvAdManagerCallback callback, int userId);
     void unregisterCallback(in ITvAdManagerCallback callback, int userId);
+
+    void createMediaView(in IBinder sessionToken, in IBinder windowToken, in Rect frame,
+            int userId);
+    void relayoutMediaView(in IBinder sessionToken, in Rect frame, int userId);
+    void removeMediaView(in IBinder sessionToken, int userId);
 }
diff --git a/media/java/android/media/tv/ad/ITvAdSession.aidl b/media/java/android/media/tv/ad/ITvAdSession.aidl
index 751257c..3ca0198 100644
--- a/media/java/android/media/tv/ad/ITvAdSession.aidl
+++ b/media/java/android/media/tv/ad/ITvAdSession.aidl
@@ -16,6 +16,7 @@
 
 package android.media.tv.ad;
 
+import android.graphics.Rect;
 import android.view.Surface;
 
 /**
@@ -27,4 +28,8 @@
     void startAdService();
     void setSurface(in Surface surface);
     void dispatchSurfaceChanged(int format, int width, int height);
+
+    void createMediaView(in IBinder windowToken, in Rect frame);
+    void relayoutMediaView(in Rect frame);
+    void removeMediaView();
 }
diff --git a/media/java/android/media/tv/ad/ITvAdSessionWrapper.java b/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
index 4df2783..3d5bc89 100644
--- a/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
+++ b/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
@@ -17,6 +17,8 @@
 package android.media.tv.ad;
 
 import android.content.Context;
+import android.graphics.Rect;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
@@ -43,6 +45,9 @@
     private static final int DO_RELEASE = 1;
     private static final int DO_SET_SURFACE = 2;
     private static final int DO_DISPATCH_SURFACE_CHANGED = 3;
+    private static final int DO_CREATE_MEDIA_VIEW = 4;
+    private static final int DO_RELAYOUT_MEDIA_VIEW = 5;
+    private static final int DO_REMOVE_MEDIA_VIEW = 6;
 
     private final HandlerCaller mCaller;
     private TvAdService.Session mSessionImpl;
@@ -61,6 +66,7 @@
 
     @Override
     public void release() {
+        mSessionImpl.scheduleMediaViewCleanup();
         mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_RELEASE));
     }
 
@@ -97,6 +103,20 @@
                 args.recycle();
                 break;
             }
+            case DO_CREATE_MEDIA_VIEW: {
+                SomeArgs args = (SomeArgs) msg.obj;
+                mSessionImpl.createMediaView((IBinder) args.arg1, (Rect) args.arg2);
+                args.recycle();
+                break;
+            }
+            case DO_RELAYOUT_MEDIA_VIEW: {
+                mSessionImpl.relayoutMediaView((Rect) msg.obj);
+                break;
+            }
+            case DO_REMOVE_MEDIA_VIEW: {
+                mSessionImpl.removeMediaView(true);
+                break;
+            }
             default: {
                 Log.w(TAG, "Unhandled message code: " + msg.what);
                 break;
@@ -129,6 +149,22 @@
                 mCaller.obtainMessageIIII(DO_DISPATCH_SURFACE_CHANGED, format, width, height, 0));
     }
 
+    @Override
+    public void createMediaView(IBinder windowToken, Rect frame) {
+        mCaller.executeOrSendMessage(
+                mCaller.obtainMessageOO(DO_CREATE_MEDIA_VIEW, windowToken, frame));
+    }
+
+    @Override
+    public void relayoutMediaView(Rect frame) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_RELAYOUT_MEDIA_VIEW, frame));
+    }
+
+    @Override
+    public void removeMediaView() {
+        mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_MEDIA_VIEW));
+    }
+
     private final class TvAdEventReceiver extends InputEventReceiver {
         TvAdEventReceiver(InputChannel inputChannel, Looper looper) {
             super(inputChannel, looper);
diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java
index 9c75051..b2ea00d 100644
--- a/media/java/android/media/tv/ad/TvAdManager.java
+++ b/media/java/android/media/tv/ad/TvAdManager.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.annotation.SystemService;
 import android.content.Context;
+import android.graphics.Rect;
 import android.media.tv.TvInputManager;
 import android.media.tv.flags.Flags;
 import android.os.Handler;
@@ -35,6 +36,7 @@
 import android.view.InputEvent;
 import android.view.InputEventSender;
 import android.view.Surface;
+import android.view.View;
 
 import com.android.internal.util.Preconditions;
 
@@ -286,6 +288,67 @@
         }
 
         /**
+         * Creates a media view. Once the media view is created, {@link #relayoutMediaView}
+         * should be called whenever the layout of its containing view is changed.
+         * {@link #removeMediaView()} should be called to remove the media view.
+         * Since a session can have only one media view, this method should be called only once
+         * or it can be called again after calling {@link #removeMediaView()}.
+         *
+         * @param view A view for AD service.
+         * @param frame A position of the media view.
+         * @throws IllegalStateException if {@code view} is not attached to a window.
+         */
+        void createMediaView(@NonNull View view, @NonNull Rect frame) {
+            Preconditions.checkNotNull(view);
+            Preconditions.checkNotNull(frame);
+            if (view.getWindowToken() == null) {
+                throw new IllegalStateException("view must be attached to a window");
+            }
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.createMediaView(mToken, view.getWindowToken(), frame, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Relayouts the current media view.
+         *
+         * @param frame A new position of the media view.
+         */
+        void relayoutMediaView(@NonNull Rect frame) {
+            Preconditions.checkNotNull(frame);
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.relayoutMediaView(mToken, frame, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Removes the current media view.
+         */
+        void removeMediaView() {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.removeMediaView(mToken, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
          * Notifies of any structural changes (format or size) of the surface passed in
          * {@link #setSurface}.
          *
diff --git a/media/java/android/media/tv/ad/TvAdService.java b/media/java/android/media/tv/ad/TvAdService.java
index 6995703..6f7f67d 100644
--- a/media/java/android/media/tv/ad/TvAdService.java
+++ b/media/java/android/media/tv/ad/TvAdService.java
@@ -20,19 +20,25 @@
 import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.Px;
 import android.annotation.SdkConstant;
 import android.annotation.SuppressLint;
+import android.app.ActivityManager;
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
+import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.util.Log;
+import android.view.Gravity;
 import android.view.InputChannel;
 import android.view.InputDevice;
 import android.view.InputEvent;
@@ -42,6 +48,7 @@
 import android.view.Surface;
 import android.view.View;
 import android.view.WindowManager;
+import android.widget.FrameLayout;
 
 import com.android.internal.os.SomeArgs;
 
@@ -56,6 +63,8 @@
     private static final boolean DEBUG = false;
     private static final String TAG = "TvAdService";
 
+    private static final int DETACH_MEDIA_VIEW_TIMEOUT_MS = 5000;
+
     /**
      * Name under which a TvAdService component publishes information about itself. This meta-data
      * must reference an XML resource containing an
@@ -151,7 +160,14 @@
         private final Context mContext;
         final Handler mHandler;
         private final WindowManager mWindowManager;
+        private WindowManager.LayoutParams mWindowParams;
         private Surface mSurface;
+        private FrameLayout mMediaViewContainer;
+        private View mMediaView;
+        private MediaViewCleanUpTask mMediaViewCleanUpTask;
+        private boolean mMediaViewEnabled;
+        private IBinder mWindowToken;
+        private Rect mMediaFrame;
 
 
         /**
@@ -166,6 +182,48 @@
         }
 
         /**
+         * Enables or disables the media view.
+         *
+         * <p>By default, the media view is disabled. Must be called explicitly after the
+         * session is created to enable the media view.
+         *
+         * <p>The TV AD service can disable its media view when needed.
+         *
+         * @param enable {@code true} if you want to enable the media view. {@code false}
+         *            otherwise.
+         * @hide
+         */
+        @CallSuper
+        public void setMediaViewEnabled(final boolean enable) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    if (enable == mMediaViewEnabled) {
+                        return;
+                    }
+                    mMediaViewEnabled = enable;
+                    if (enable) {
+                        if (mWindowToken != null) {
+                            createMediaView(mWindowToken, mMediaFrame);
+                        }
+                    } else {
+                        removeMediaView(false);
+                    }
+                }
+            });
+        }
+
+        /**
+         * Returns {@code true} if media view is enabled, {@code false} otherwise.
+         *
+         * @see #setMediaViewEnabled(boolean)
+         * @hide
+         */
+        public boolean isMediaViewEnabled() {
+            return mMediaViewEnabled;
+        }
+
+        /**
          * Releases TvAdService session.
          */
         public abstract void onRelease();
@@ -180,6 +238,9 @@
                 mSessionCallback = null;
                 mPendingActions.clear();
             }
+            // Removes the media view lastly so that any hanging on the main thread can be handled
+            // in {@link #scheduleMediaViewCleanup}.
+            removeMediaView(true);
         }
 
         /**
@@ -307,6 +368,33 @@
         }
 
         /**
+         * Called when the size of the media view is changed by the application.
+         *
+         * <p>This is always called at least once when the session is created regardless of whether
+         * the media view is enabled or not. The media view container size is the same as the
+         * containing {@link TvAdView}. Note that the size of the underlying surface can
+         * be different if the surface was changed by calling {@link #layoutSurface}.
+         *
+         * @param width The width of the media view, in pixels.
+         * @param height The height of the media view, in pixels.
+         * @hide
+         */
+        public void onMediaViewSizeChanged(@Px int width, @Px int height) {
+        }
+
+        /**
+         * Called when the application requests to create a media view. Each session
+         * implementation can override this method and return its own view.
+         *
+         * @return a view attached to the media window. {@code null} if no media view is created.
+         * @hide
+         */
+        @Nullable
+        public View onCreateMediaView() {
+            return null;
+        }
+
+        /**
          * Takes care of dispatching incoming input events and tells whether the event was handled.
          */
         int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
@@ -388,6 +476,137 @@
                 }
             }
         }
+
+        /**
+         * Creates a media view. This calls {@link #onCreateMediaView} to get a view to attach
+         * to the media window.
+         *
+         * @param windowToken A window token of the application.
+         * @param frame A position of the media view.
+         */
+        void createMediaView(IBinder windowToken, Rect frame) {
+            if (mMediaViewContainer != null) {
+                removeMediaView(false);
+            }
+            if (DEBUG) Log.d(TAG, "create media view(" + frame + ")");
+            mWindowToken = windowToken;
+            mMediaFrame = frame;
+            onMediaViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
+            if (!mMediaViewEnabled) {
+                return;
+            }
+            mMediaView = onCreateMediaView();
+            if (mMediaView == null) {
+                return;
+            }
+            if (mMediaViewCleanUpTask != null) {
+                mMediaViewCleanUpTask.cancel(true);
+                mMediaViewCleanUpTask = null;
+            }
+            // Creates a container view to check hanging on the media view detaching.
+            // Adding/removing the media view to/from the container make the view attach/detach
+            // logic run on the main thread.
+            mMediaViewContainer = new FrameLayout(mContext.getApplicationContext());
+            mMediaViewContainer.addView(mMediaView);
+
+            int type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
+            // 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 flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+                    | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+            if (ActivityManager.isHighEndGfx()) {
+                flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+            }
+            mWindowParams = new WindowManager.LayoutParams(
+                    frame.right - frame.left, frame.bottom - frame.top,
+                    frame.left, frame.top, type, flags, PixelFormat.TRANSPARENT);
+            mWindowParams.privateFlags |=
+                    WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+            mWindowParams.gravity = Gravity.START | Gravity.TOP;
+            mWindowParams.token = windowToken;
+            mWindowManager.addView(mMediaViewContainer, mWindowParams);
+        }
+
+        /**
+         * Relayouts the current media view.
+         *
+         * @param frame A new position of the media view.
+         */
+        void relayoutMediaView(Rect frame) {
+            if (DEBUG) Log.d(TAG, "relayoutMediaView(" + frame + ")");
+            if (mMediaFrame == null || mMediaFrame.width() != frame.width()
+                    || mMediaFrame.height() != frame.height()) {
+                // Note: relayoutMediaView is called whenever TvAdView's layout is
+                // changed regardless of setMediaViewEnabled.
+                onMediaViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
+            }
+            mMediaFrame = frame;
+            if (!mMediaViewEnabled || mMediaViewContainer == null) {
+                return;
+            }
+            mWindowParams.x = frame.left;
+            mWindowParams.y = frame.top;
+            mWindowParams.width = frame.right - frame.left;
+            mWindowParams.height = frame.bottom - frame.top;
+            mWindowManager.updateViewLayout(mMediaViewContainer, mWindowParams);
+        }
+
+        /**
+         * Removes the current media view.
+         */
+        void removeMediaView(boolean clearWindowToken) {
+            if (DEBUG) Log.d(TAG, "removeMediaView(" + mMediaViewContainer + ")");
+            if (clearWindowToken) {
+                mWindowToken = null;
+                mMediaFrame = null;
+            }
+            if (mMediaViewContainer != null) {
+                // Removes the media view from the view hierarchy in advance so that it can be
+                // cleaned up in the {@link MediaViewCleanUpTask} if the remove process is
+                // hanging.
+                mMediaViewContainer.removeView(mMediaView);
+                mMediaView = null;
+                mWindowManager.removeView(mMediaViewContainer);
+                mMediaViewContainer = null;
+                mWindowParams = null;
+            }
+        }
+
+        /**
+         * Schedules a task which checks whether the media view is detached and kills the process
+         * if it is not. Note that this method is expected to be called in a non-main thread.
+         */
+        void scheduleMediaViewCleanup() {
+            View mediaViewParent = mMediaViewContainer;
+            if (mediaViewParent != null) {
+                mMediaViewCleanUpTask = new MediaViewCleanUpTask();
+                mMediaViewCleanUpTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
+                        mediaViewParent);
+            }
+        }
+    }
+
+    private static final class MediaViewCleanUpTask extends AsyncTask<View, Void, Void> {
+        @Override
+        protected Void doInBackground(View... views) {
+            View mediaViewParent = views[0];
+            try {
+                Thread.sleep(DETACH_MEDIA_VIEW_TIMEOUT_MS);
+            } catch (InterruptedException e) {
+                return null;
+            }
+            if (isCancelled()) {
+                return null;
+            }
+            if (mediaViewParent.isAttachedToWindow()) {
+                Log.e(TAG, "Time out on releasing media view. Killing "
+                        + mediaViewParent.getContext().getPackageName());
+                android.os.Process.killProcess(Process.myPid());
+            }
+            return null;
+        }
     }
 
 
diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java
index 5e67fe9..5e4a70b 100644
--- a/media/java/android/media/tv/ad/TvAdView.java
+++ b/media/java/android/media/tv/ad/TvAdView.java
@@ -22,6 +22,8 @@
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.RectF;
 import android.os.Handler;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -64,6 +66,9 @@
     private int mSurfaceViewTop;
     private int mSurfaceViewBottom;
 
+    private boolean mMediaViewCreated;
+    private Rect mMediaViewFrame;
+
 
 
     private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
@@ -121,6 +126,20 @@
         mTvAdManager = (TvAdManager) getContext().getSystemService(Context.TV_AD_SERVICE);
     }
 
+    /** @hide */
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        createSessionMediaView();
+    }
+
+    /** @hide */
+    @Override
+    public void onDetachedFromWindow() {
+        removeSessionMediaView();
+        super.onDetachedFromWindow();
+    }
+
     @Override
     public void onLayout(boolean changed, int left, int top, int right, int bottom) {
         if (DEBUG) {
@@ -150,6 +169,11 @@
     public void onVisibilityChanged(@NonNull View changedView, int visibility) {
         super.onVisibilityChanged(changedView, visibility);
         mSurfaceView.setVisibility(visibility);
+        if (visibility == View.VISIBLE) {
+            createSessionMediaView();
+        } else {
+            removeSessionMediaView();
+        }
     }
 
     private void resetSurfaceView() {
@@ -162,6 +186,7 @@
             @Override
             protected void updateSurface() {
                 super.updateSurface();
+                relayoutSessionMediaView();
             }};
         // The surface view's content should be treated as secure all the time.
         mSurfaceView.setSecure(true);
@@ -174,6 +199,69 @@
         addView(mSurfaceView);
     }
 
+    /**
+     * Resets this TvAdView to release its resources.
+     *
+     * <p>It can be reused by call {@link #prepareAdService(String, String)}.
+     * @hide
+     */
+    public void reset() {
+        if (DEBUG) Log.d(TAG, "reset()");
+        resetInternal();
+    }
+
+    private void resetInternal() {
+        mSessionCallback = null;
+        if (mSession != null) {
+            setSessionSurface(null);
+            removeSessionMediaView();
+            mUseRequestedSurfaceLayout = false;
+            mSession.release();
+            mSession = null;
+            resetSurfaceView();
+        }
+    }
+
+    private void createSessionMediaView() {
+        // TODO: handle z-order
+        if (mSession == null || !isAttachedToWindow() || mMediaViewCreated) {
+            return;
+        }
+        mMediaViewFrame = getViewFrameOnScreen();
+        mSession.createMediaView(this, mMediaViewFrame);
+        mMediaViewCreated = true;
+    }
+
+    private void removeSessionMediaView() {
+        if (mSession == null || !mMediaViewCreated) {
+            return;
+        }
+        mSession.removeMediaView();
+        mMediaViewCreated = false;
+        mMediaViewFrame = null;
+    }
+
+    private void relayoutSessionMediaView() {
+        if (mSession == null || !isAttachedToWindow() || !mMediaViewCreated) {
+            return;
+        }
+        Rect viewFrame = getViewFrameOnScreen();
+        if (viewFrame.equals(mMediaViewFrame)) {
+            return;
+        }
+        mSession.relayoutMediaView(viewFrame);
+        mMediaViewFrame = viewFrame;
+    }
+
+    private Rect getViewFrameOnScreen() {
+        Rect frame = new Rect();
+        getGlobalVisibleRect(frame);
+        RectF frameF = new RectF(frame);
+        getMatrix().mapRect(frameF);
+        frameF.round(frame);
+        return frame;
+    }
+
     private void setSessionSurface(Surface surface) {
         if (mSession == null) {
             return;
@@ -185,7 +273,7 @@
         if (mSession == null) {
             return;
         }
-        //mSession.dispatchSurfaceChanged(format, width, height);
+        mSession.dispatchSurfaceChanged(format, width, height);
     }
 
     /**
@@ -246,6 +334,7 @@
                         dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
                     }
                 }
+                createSessionMediaView();
             } else {
                 // Failed to create
                 // Todo: forward error to Tv App
@@ -262,6 +351,8 @@
                 Log.w(TAG, "onSessionReleased - session not created");
                 return;
             }
+            mMediaViewCreated = false;
+            mMediaViewFrame = null;
             mSessionCallback = null;
             mSession = null;
         }
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index 7ab075e..169d43d 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -1038,7 +1038,7 @@
                 }
             } finally {
                 if (surface != null) {
-                    // surface is not used in TvInteractiveAppManagerService.
+                    // surface is not used in TvAdManagerService.
                     surface.release();
                 }
                 Binder.restoreCallingIdentity(identity);
@@ -1106,6 +1106,67 @@
             }
         }
 
+        @Override
+        public void createMediaView(IBinder sessionToken, IBinder windowToken, Rect frame,
+                int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "createMediaView");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        getAdSessionLocked(sessionToken, callingUid, resolvedUserId)
+                                .createMediaView(windowToken, frame);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slog.e(TAG, "error in createMediaView", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void relayoutMediaView(IBinder sessionToken, Rect frame, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "relayoutMediaView");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        getAdSessionLocked(sessionToken, callingUid, resolvedUserId)
+                                .relayoutMediaView(frame);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slog.e(TAG, "error in relayoutMediaView", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void removeMediaView(IBinder sessionToken, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "removeMediaView");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        getAdSessionLocked(sessionToken, callingUid, resolvedUserId)
+                                .removeMediaView();
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slog.e(TAG, "error in removeMediaView", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
     }
 
     private final class BinderService extends ITvInteractiveAppManager.Stub {