Merge changes Iae777a8c,Ic21728e2,Iea41f56f into main

* changes:
  CSAI: handle TvAdServiceInfo and callback
  CSAI: handle surface
  CSAI: handle session
diff --git a/media/java/android/media/tv/ad/ITvAdClient.aidl b/media/java/android/media/tv/ad/ITvAdClient.aidl
new file mode 100644
index 0000000..34d96b3
--- /dev/null
+++ b/media/java/android/media/tv/ad/ITvAdClient.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 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.media.tv.ad;
+
+import android.view.InputChannel;
+
+/**
+ * Interface a client of the ITvAdManager implements, to identify itself and receive
+ * information about changes to the state of each TV AD service.
+ * @hide
+ */
+oneway interface ITvAdClient {
+    void onSessionCreated(in String serviceId, IBinder token, in InputChannel channel, int seq);
+    void onSessionReleased(int seq);
+    void onLayoutSurface(int left, int top, int right, int bottom, int seq);
+}
\ No newline at end of file
diff --git a/media/java/android/media/tv/ad/ITvAdManager.aidl b/media/java/android/media/tv/ad/ITvAdManager.aidl
index 92cc923..a747e49 100644
--- a/media/java/android/media/tv/ad/ITvAdManager.aidl
+++ b/media/java/android/media/tv/ad/ITvAdManager.aidl
@@ -16,10 +16,25 @@
 
 package android.media.tv.ad;
 
+import android.media.tv.ad.ITvAdClient;
+import android.media.tv.ad.ITvAdManagerCallback;
+import android.media.tv.ad.TvAdServiceInfo;
+import android.view.Surface;
+
 /**
  * Interface to the TV AD service.
  * @hide
  */
 interface ITvAdManager {
+    List<TvAdServiceInfo> getTvAdServiceList(int userId);
+    void createSession(
+            in ITvAdClient client, in String serviceId, in String type, int seq, int userId);
+    void releaseSession(in IBinder sessionToken, int userId);
     void startAdService(in IBinder sessionToken, int userId);
+    void setSurface(in IBinder sessionToken, in Surface surface, int userId);
+    void dispatchSurfaceChanged(in IBinder sessionToken, int format, int width, int height,
+            int userId);
+
+    void registerCallback(in ITvAdManagerCallback callback, int userId);
+    void unregisterCallback(in ITvAdManagerCallback callback, int userId);
 }
diff --git a/media/java/android/media/tv/ad/ITvAdManagerCallback.aidl b/media/java/android/media/tv/ad/ITvAdManagerCallback.aidl
new file mode 100644
index 0000000..f55f67e
--- /dev/null
+++ b/media/java/android/media/tv/ad/ITvAdManagerCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 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.media.tv.ad;
+
+/**
+ * Interface to receive callbacks from ITvAdManager regardless of sessions.
+ * @hide
+ */
+oneway interface ITvAdManagerCallback {
+    void onAdServiceAdded(in String serviceId);
+    void onAdServiceRemoved(in String serviceId);
+    void onAdServiceUpdated(in String serviceId);
+}
\ No newline at end of file
diff --git a/media/java/android/media/tv/ad/ITvAdService.aidl b/media/java/android/media/tv/ad/ITvAdService.aidl
new file mode 100644
index 0000000..3bb0409
--- /dev/null
+++ b/media/java/android/media/tv/ad/ITvAdService.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 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.media.tv.ad;
+
+import android.media.tv.ad.ITvAdServiceCallback;
+import android.media.tv.ad.ITvAdSessionCallback;
+import android.os.Bundle;
+import android.view.InputChannel;
+
+/**
+ * Top-level interface to a TV AD component (implemented in a Service). It's used for
+ * TvAdManagerService to communicate with TvAdService.
+ * @hide
+ */
+oneway interface ITvAdService {
+    void registerCallback(in ITvAdServiceCallback callback);
+    void unregisterCallback(in ITvAdServiceCallback callback);
+    void createSession(in InputChannel channel, in ITvAdSessionCallback callback,
+            in String serviceId, in String type);
+    void sendAppLinkCommand(in Bundle command);
+}
\ No newline at end of file
diff --git a/media/java/android/media/tv/ad/ITvAdServiceCallback.aidl b/media/java/android/media/tv/ad/ITvAdServiceCallback.aidl
new file mode 100644
index 0000000..a087181
--- /dev/null
+++ b/media/java/android/media/tv/ad/ITvAdServiceCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2023 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.media.tv.ad;
+
+/**
+ * Helper interface for ITvAdService to allow the TvAdService to notify the TvAdManagerService.
+ * @hide
+ */
+oneway interface ITvAdServiceCallback {
+}
\ No newline at end of file
diff --git a/media/java/android/media/tv/ad/ITvAdSession.aidl b/media/java/android/media/tv/ad/ITvAdSession.aidl
index b834f1b9..751257c 100644
--- a/media/java/android/media/tv/ad/ITvAdSession.aidl
+++ b/media/java/android/media/tv/ad/ITvAdSession.aidl
@@ -16,10 +16,15 @@
 
 package android.media.tv.ad;
 
+import android.view.Surface;
+
 /**
- * Sub-interface of ITvAdService which is created per session and has its own context.
+ * Sub-interface of ITvAdService.aidl which is created per session and has its own context.
  * @hide
  */
 oneway interface ITvAdSession {
+    void release();
     void startAdService();
+    void setSurface(in Surface surface);
+    void dispatchSurfaceChanged(int format, int width, int height);
 }
diff --git a/media/java/android/media/tv/ad/ITvAdSessionCallback.aidl b/media/java/android/media/tv/ad/ITvAdSessionCallback.aidl
new file mode 100644
index 0000000..f21ef19
--- /dev/null
+++ b/media/java/android/media/tv/ad/ITvAdSessionCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 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.media.tv.ad;
+
+import android.media.tv.ad.ITvAdSession;
+
+/**
+ * Helper interface for ITvAdSession to allow TvAdService to notify the system service when there is
+ * a related event.
+ * @hide
+ */
+oneway interface ITvAdSessionCallback {
+    void onSessionCreated(in ITvAdSession session);
+    void onLayoutSurface(int left, int top, int right, int bottom);
+}
\ No newline at end of file
diff --git a/media/java/android/media/tv/ad/ITvAdSessionWrapper.java b/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
new file mode 100644
index 0000000..4df2783
--- /dev/null
+++ b/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2023 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.media.tv.ad;
+
+import android.content.Context;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.Surface;
+
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
+
+/**
+ * Implements the internal ITvAdSession interface.
+ * @hide
+ */
+public class ITvAdSessionWrapper
+        extends ITvAdSession.Stub implements HandlerCaller.Callback {
+
+    private static final String TAG = "ITvAdSessionWrapper";
+
+    private static final int EXECUTE_MESSAGE_TIMEOUT_SHORT_MILLIS = 1000;
+    private static final int EXECUTE_MESSAGE_TIMEOUT_LONG_MILLIS = 5 * 1000;
+    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 final HandlerCaller mCaller;
+    private TvAdService.Session mSessionImpl;
+    private InputChannel mChannel;
+    private TvAdEventReceiver mReceiver;
+
+    public ITvAdSessionWrapper(
+            Context context, TvAdService.Session mSessionImpl, InputChannel channel) {
+        this.mSessionImpl = mSessionImpl;
+        mCaller = new HandlerCaller(context, null, this, true /* asyncHandler */);
+        mChannel = channel;
+        if (channel != null) {
+            mReceiver = new TvAdEventReceiver(channel, context.getMainLooper());
+        }
+    }
+
+    @Override
+    public void release() {
+        mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_RELEASE));
+    }
+
+
+    @Override
+    public void executeMessage(Message msg) {
+        if (mSessionImpl == null) {
+            return;
+        }
+
+        long startTime = System.nanoTime();
+        switch (msg.what) {
+            case DO_RELEASE: {
+                mSessionImpl.release();
+                mSessionImpl = null;
+                if (mReceiver != null) {
+                    mReceiver.dispose();
+                    mReceiver = null;
+                }
+                if (mChannel != null) {
+                    mChannel.dispose();
+                    mChannel = null;
+                }
+                break;
+            }
+            case DO_SET_SURFACE: {
+                mSessionImpl.setSurface((Surface) msg.obj);
+                break;
+            }
+            case DO_DISPATCH_SURFACE_CHANGED: {
+                SomeArgs args = (SomeArgs) msg.obj;
+                mSessionImpl.dispatchSurfaceChanged(
+                        (Integer) args.argi1, (Integer) args.argi2, (Integer) args.argi3);
+                args.recycle();
+                break;
+            }
+            default: {
+                Log.w(TAG, "Unhandled message code: " + msg.what);
+                break;
+            }
+        }
+        long durationMs = (System.nanoTime() - startTime) / (1000 * 1000);
+        if (durationMs > EXECUTE_MESSAGE_TIMEOUT_SHORT_MILLIS) {
+            Log.w(TAG, "Handling message (" + msg.what + ") took too long time (duration="
+                    + durationMs + "ms)");
+            if (durationMs > EXECUTE_MESSAGE_TIMEOUT_LONG_MILLIS) {
+                // TODO: handle timeout
+            }
+        }
+
+    }
+
+    @Override
+    public void startAdService() throws RemoteException {
+
+    }
+
+    @Override
+    public void setSurface(Surface surface) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_SURFACE, surface));
+    }
+
+    @Override
+    public void dispatchSurfaceChanged(int format, int width, int height) {
+        mCaller.executeOrSendMessage(
+                mCaller.obtainMessageIIII(DO_DISPATCH_SURFACE_CHANGED, format, width, height, 0));
+    }
+
+    private final class TvAdEventReceiver extends InputEventReceiver {
+        TvAdEventReceiver(InputChannel inputChannel, Looper looper) {
+            super(inputChannel, looper);
+        }
+
+        @Override
+        public void onInputEvent(InputEvent event) {
+            if (mSessionImpl == null) {
+                // The session has been finished.
+                finishInputEvent(event, false);
+                return;
+            }
+
+            int handled = mSessionImpl.dispatchInputEvent(event, this);
+            if (handled != TvAdManager.Session.DISPATCH_IN_PROGRESS) {
+                finishInputEvent(
+                        event, handled == TvAdManager.Session.DISPATCH_HANDLED);
+            }
+        }
+    }
+}
diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java
index 2b52c4b..9c75051 100644
--- a/media/java/android/media/tv/ad/TvAdManager.java
+++ b/media/java/android/media/tv/ad/TvAdManager.java
@@ -17,12 +17,30 @@
 package android.media.tv.ad;
 
 import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemService;
 import android.content.Context;
+import android.media.tv.TvInputManager;
 import android.media.tv.flags.Flags;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
 import android.os.RemoteException;
 import android.util.Log;
+import android.util.Pools;
+import android.util.SparseArray;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventSender;
+import android.view.Surface;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
  * Central system API to the overall client-side TV AD architecture, which arbitrates interaction
@@ -37,10 +55,163 @@
     private final ITvAdManager mService;
     private final int mUserId;
 
+    // A mapping from the sequence number of a session to its SessionCallbackRecord.
+    private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap =
+            new SparseArray<>();
+
+    // @GuardedBy("mLock")
+    private final List<TvAdServiceCallbackRecord> mCallbackRecords = new ArrayList<>();
+
+    // A sequence number for the next session to be created. Should be protected by a lock
+    // {@code mSessionCallbackRecordMap}.
+    private int mNextSeq;
+
+    private final Object mLock = new Object();
+    private final ITvAdClient mClient;
+
     /** @hide */
     public TvAdManager(ITvAdManager service, int userId) {
         mService = service;
         mUserId = userId;
+        mClient = new ITvAdClient.Stub() {
+            @Override
+            public void onSessionCreated(String serviceId, IBinder token, InputChannel channel,
+                    int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for " + token);
+                        return;
+                    }
+                    Session session = null;
+                    if (token != null) {
+                        session = new Session(token, channel, mService, mUserId, seq,
+                                mSessionCallbackRecordMap);
+                    } else {
+                        mSessionCallbackRecordMap.delete(seq);
+                    }
+                    record.postSessionCreated(session);
+                }
+            }
+
+            @Override
+            public void onSessionReleased(int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    mSessionCallbackRecordMap.delete(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq:" + seq);
+                        return;
+                    }
+                    record.mSession.releaseInternal();
+                    record.postSessionReleased();
+                }
+            }
+
+            @Override
+            public void onLayoutSurface(int left, int top, int right, int bottom, int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postLayoutSurface(left, top, right, bottom);
+                }
+            }
+
+        };
+
+        ITvAdManagerCallback managerCallback =
+                new ITvAdManagerCallback.Stub() {
+                    @Override
+                    public void onAdServiceAdded(String serviceId) {
+                        synchronized (mLock) {
+                            for (TvAdServiceCallbackRecord record : mCallbackRecords) {
+                                record.postAdServiceAdded(serviceId);
+                            }
+                        }
+                    }
+
+                    @Override
+                    public void onAdServiceRemoved(String serviceId) {
+                        synchronized (mLock) {
+                            for (TvAdServiceCallbackRecord record : mCallbackRecords) {
+                                record.postAdServiceRemoved(serviceId);
+                            }
+                        }
+                    }
+
+                    @Override
+                    public void onAdServiceUpdated(String serviceId) {
+                        synchronized (mLock) {
+                            for (TvAdServiceCallbackRecord record : mCallbackRecords) {
+                                record.postAdServiceUpdated(serviceId);
+                            }
+                        }
+                    }
+                };
+        try {
+            if (mService != null) {
+                mService.registerCallback(managerCallback, mUserId);
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the complete list of TV AD service on the system.
+     *
+     * @return List of {@link TvAdServiceInfo} for each TV AD service that describes its meta
+     * information.
+     * @hide
+     */
+    @NonNull
+    public List<TvAdServiceInfo> getTvAdServiceList() {
+        try {
+            return mService.getTvAdServiceList(mUserId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Creates a {@link Session} for a given TV AD service.
+     *
+     * <p>The number of sessions that can be created at the same time is limited by the capability
+     * of the given AD service.
+     *
+     * @param serviceId The ID of the AD service.
+     * @param callback A callback used to receive the created session.
+     * @param handler A {@link Handler} that the session creation will be delivered to.
+     * @hide
+     */
+    public void createSession(
+            @NonNull String serviceId,
+            @NonNull String type,
+            @NonNull final TvAdManager.SessionCallback callback,
+            @NonNull Handler handler) {
+        createSessionInternal(serviceId, type, callback, handler);
+    }
+
+    private void createSessionInternal(String serviceId, String type,
+            TvAdManager.SessionCallback callback, Handler handler) {
+        Preconditions.checkNotNull(serviceId);
+        Preconditions.checkNotNull(type);
+        Preconditions.checkNotNull(callback);
+        Preconditions.checkNotNull(handler);
+        TvAdManager.SessionCallbackRecord
+                record = new TvAdManager.SessionCallbackRecord(callback, handler);
+        synchronized (mSessionCallbackRecordMap) {
+            int seq = mNextSeq++;
+            mSessionCallbackRecordMap.put(seq, record);
+            try {
+                mService.createSession(mClient, serviceId, type, seq, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
     }
 
     /**
@@ -48,14 +219,121 @@
      * @hide
      */
     public static final class Session {
-        private final IBinder mToken;
+        static final int DISPATCH_IN_PROGRESS = -1;
+        static final int DISPATCH_NOT_HANDLED = 0;
+        static final int DISPATCH_HANDLED = 1;
+
+        private static final long INPUT_SESSION_NOT_RESPONDING_TIMEOUT = 2500;
         private final ITvAdManager mService;
         private final int mUserId;
+        private final int mSeq;
+        private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap;
 
-        private Session(IBinder token, ITvAdManager service, int userId) {
+        // For scheduling input event handling on the main thread. This also serves as a lock to
+        // protect pending input events and the input channel.
+        private final InputEventHandler mHandler = new InputEventHandler(Looper.getMainLooper());
+
+        private TvInputManager.Session mInputSession;
+        private final Pools.Pool<PendingEvent> mPendingEventPool = new Pools.SimplePool<>(20);
+        private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<>(20);
+        private TvInputEventSender mSender;
+        private InputChannel mInputChannel;
+        private IBinder mToken;
+
+        private Session(IBinder token, InputChannel channel, ITvAdManager service, int userId,
+                int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) {
             mToken = token;
+            mInputChannel = channel;
             mService = service;
             mUserId = userId;
+            mSeq = seq;
+            mSessionCallbackRecordMap = sessionCallbackRecordMap;
+        }
+
+        /**
+         * Releases this session.
+         */
+        public void release() {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.releaseSession(mToken, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+
+            releaseInternal();
+        }
+
+        /**
+         * Sets the {@link android.view.Surface} for this session.
+         *
+         * @param surface A {@link android.view.Surface} used to render AD.
+         */
+        public void setSurface(Surface surface) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            // surface can be null.
+            try {
+                mService.setSurface(mToken, surface, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Notifies of any structural changes (format or size) of the surface passed in
+         * {@link #setSurface}.
+         *
+         * @param format The new PixelFormat of the surface.
+         * @param width The new width of the surface.
+         * @param height The new height of the surface.
+         */
+        public void dispatchSurfaceChanged(int format, int width, int height) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.dispatchSurfaceChanged(mToken, format, width, height, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        private void flushPendingEventsLocked() {
+            mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT);
+
+            final int count = mPendingEvents.size();
+            for (int i = 0; i < count; i++) {
+                int seq = mPendingEvents.keyAt(i);
+                Message msg = mHandler.obtainMessage(
+                        InputEventHandler.MSG_FLUSH_INPUT_EVENT, seq, 0);
+                msg.setAsynchronous(true);
+                msg.sendToTarget();
+            }
+        }
+
+        private void releaseInternal() {
+            mToken = null;
+            synchronized (mHandler) {
+                if (mInputChannel != null) {
+                    if (mSender != null) {
+                        flushPendingEventsLocked();
+                        mSender.dispose();
+                        mSender = null;
+                    }
+                    mInputChannel.dispose();
+                    mInputChannel = null;
+                }
+            }
+            synchronized (mSessionCallbackRecordMap) {
+                mSessionCallbackRecordMap.delete(mSeq);
+            }
         }
 
         void startAdService() {
@@ -69,5 +347,324 @@
                 throw e.rethrowFromSystemServer();
             }
         }
+
+        private final class InputEventHandler extends Handler {
+            public static final int MSG_SEND_INPUT_EVENT = 1;
+            public static final int MSG_TIMEOUT_INPUT_EVENT = 2;
+            public static final int MSG_FLUSH_INPUT_EVENT = 3;
+
+            InputEventHandler(Looper looper) {
+                super(looper, null, true);
+            }
+
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case MSG_SEND_INPUT_EVENT: {
+                        sendInputEventAndReportResultOnMainLooper((PendingEvent) msg.obj);
+                        return;
+                    }
+                    case MSG_TIMEOUT_INPUT_EVENT: {
+                        finishedInputEvent(msg.arg1, false, true);
+                        return;
+                    }
+                    case MSG_FLUSH_INPUT_EVENT: {
+                        finishedInputEvent(msg.arg1, false, false);
+                        return;
+                    }
+                }
+            }
+        }
+
+        // Assumes the event has already been removed from the queue.
+        void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) {
+            p.mHandled = handled;
+            if (p.mEventHandler.getLooper().isCurrentThread()) {
+                // Already running on the callback handler thread so we can send the callback
+                // immediately.
+                p.run();
+            } else {
+                // Post the event to the callback handler thread.
+                // In this case, the callback will be responsible for recycling the event.
+                Message msg = Message.obtain(p.mEventHandler, p);
+                msg.setAsynchronous(true);
+                msg.sendToTarget();
+            }
+        }
+
+        // Must be called on the main looper
+        private void sendInputEventAndReportResultOnMainLooper(PendingEvent p) {
+            synchronized (mHandler) {
+                int result = sendInputEventOnMainLooperLocked(p);
+                if (result == DISPATCH_IN_PROGRESS) {
+                    return;
+                }
+            }
+
+            invokeFinishedInputEventCallback(p, false);
+        }
+
+        private int sendInputEventOnMainLooperLocked(PendingEvent p) {
+            if (mInputChannel != null) {
+                if (mSender == null) {
+                    mSender = new TvInputEventSender(mInputChannel, mHandler.getLooper());
+                }
+
+                final InputEvent event = p.mEvent;
+                final int seq = event.getSequenceNumber();
+                if (mSender.sendInputEvent(seq, event)) {
+                    mPendingEvents.put(seq, p);
+                    Message msg = mHandler.obtainMessage(
+                            InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
+                    msg.setAsynchronous(true);
+                    mHandler.sendMessageDelayed(msg, INPUT_SESSION_NOT_RESPONDING_TIMEOUT);
+                    return DISPATCH_IN_PROGRESS;
+                }
+
+                Log.w(TAG, "Unable to send input event to session: " + mToken + " dropping:"
+                        + event);
+            }
+            return DISPATCH_NOT_HANDLED;
+        }
+
+        void finishedInputEvent(int seq, boolean handled, boolean timeout) {
+            final PendingEvent p;
+            synchronized (mHandler) {
+                int index = mPendingEvents.indexOfKey(seq);
+                if (index < 0) {
+                    return; // spurious, event already finished or timed out
+                }
+
+                p = mPendingEvents.valueAt(index);
+                mPendingEvents.removeAt(index);
+
+                if (timeout) {
+                    Log.w(TAG, "Timeout waiting for session to handle input event after "
+                            + INPUT_SESSION_NOT_RESPONDING_TIMEOUT + " ms: " + mToken);
+                } else {
+                    mHandler.removeMessages(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
+                }
+            }
+
+            invokeFinishedInputEventCallback(p, handled);
+        }
+
+        private void recyclePendingEventLocked(PendingEvent p) {
+            p.recycle();
+            mPendingEventPool.release(p);
+        }
+
+        /**
+         * Callback that is invoked when an input event that was dispatched to this session has been
+         * finished.
+         *
+         * @hide
+         */
+        public interface FinishedInputEventCallback {
+            /**
+             * Called when the dispatched input event is finished.
+             *
+             * @param token A token passed to {@link #dispatchInputEvent}.
+             * @param handled {@code true} if the dispatched input event was handled properly.
+             *            {@code false} otherwise.
+             */
+            void onFinishedInputEvent(Object token, boolean handled);
+        }
+
+        private final class TvInputEventSender extends InputEventSender {
+            TvInputEventSender(InputChannel inputChannel, Looper looper) {
+                super(inputChannel, looper);
+            }
+
+            @Override
+            public void onInputEventFinished(int seq, boolean handled) {
+                finishedInputEvent(seq, handled, false);
+            }
+        }
+
+        private final class PendingEvent implements Runnable {
+            public InputEvent mEvent;
+            public Object mEventToken;
+            public Session.FinishedInputEventCallback mCallback;
+            public Handler mEventHandler;
+            public boolean mHandled;
+
+            public void recycle() {
+                mEvent = null;
+                mEventToken = null;
+                mCallback = null;
+                mEventHandler = null;
+                mHandled = false;
+            }
+
+            @Override
+            public void run() {
+                mCallback.onFinishedInputEvent(mEventToken, mHandled);
+
+                synchronized (mEventHandler) {
+                    recyclePendingEventLocked(this);
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Interface used to receive the created session.
+     * @hide
+     */
+    public abstract static class SessionCallback {
+        /**
+         * This is called after {@link TvAdManager#createSession} has been processed.
+         *
+         * @param session A {@link TvAdManager.Session} instance created. This can be
+         *                {@code null} if the creation request failed.
+         */
+        public void onSessionCreated(@Nullable Session session) {
+        }
+
+        /**
+         * This is called when {@link TvAdManager.Session} is released.
+         * This typically happens when the process hosting the session has crashed or been killed.
+         *
+         * @param session the {@link TvAdManager.Session} instance released.
+         */
+        public void onSessionReleased(@NonNull Session session) {
+        }
+
+        /**
+         * This is called when {@link TvAdService.Session#layoutSurface} is called to
+         * change the layout of surface.
+         *
+         * @param session A {@link TvAdManager.Session} associated with this callback.
+         * @param left Left position.
+         * @param top Top position.
+         * @param right Right position.
+         * @param bottom Bottom position.
+         */
+        public void onLayoutSurface(Session session, int left, int top, int right, int bottom) {
+        }
+
+    }
+
+    /**
+     * Callback used to monitor status of the TV AD service.
+     * @hide
+     */
+    public abstract static class TvAdServiceCallback {
+        /**
+         * This is called when a TV AD service is added to the system.
+         *
+         * <p>Normally it happens when the user installs a new TV AD service package that implements
+         * {@link TvAdService} interface.
+         *
+         * @param serviceId The ID of the TV AD service.
+         */
+        public void onAdServiceAdded(@NonNull String serviceId) {
+        }
+
+        /**
+         * This is called when a TV AD service is removed from the system.
+         *
+         * <p>Normally it happens when the user uninstalls the previously installed TV AD service
+         * package.
+         *
+         * @param serviceId The ID of the TV AD service.
+         */
+        public void onAdServiceRemoved(@NonNull String serviceId) {
+        }
+
+        /**
+         * This is called when a TV AD service is updated on the system.
+         *
+         * <p>Normally it happens when a previously installed TV AD service package is re-installed
+         * or a newer version of the package exists becomes available/unavailable.
+         *
+         * @param serviceId The ID of the TV AD service.
+         */
+        public void onAdServiceUpdated(@NonNull String serviceId) {
+        }
+
+    }
+
+    private static final class SessionCallbackRecord {
+        private final SessionCallback mSessionCallback;
+        private final Handler mHandler;
+        private Session mSession;
+
+        SessionCallbackRecord(SessionCallback sessionCallback, Handler handler) {
+            mSessionCallback = sessionCallback;
+            mHandler = handler;
+        }
+
+        void postSessionCreated(final Session session) {
+            mSession = session;
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onSessionCreated(session);
+                }
+            });
+        }
+
+        void postSessionReleased() {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onSessionReleased(mSession);
+                }
+            });
+        }
+
+        void postLayoutSurface(final int left, final int top, final int right,
+                final int bottom) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onLayoutSurface(mSession, left, top, right, bottom);
+                }
+            });
+        }
+    }
+
+    private static final class TvAdServiceCallbackRecord {
+        private final TvAdServiceCallback mCallback;
+        private final Executor mExecutor;
+
+        TvAdServiceCallbackRecord(TvAdServiceCallback callback, Executor executor) {
+            mCallback = callback;
+            mExecutor = executor;
+        }
+
+        public TvAdServiceCallback getCallback() {
+            return mCallback;
+        }
+
+        public void postAdServiceAdded(final String serviceId) {
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onAdServiceAdded(serviceId);
+                }
+            });
+        }
+
+        public void postAdServiceRemoved(final String serviceId) {
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onAdServiceRemoved(serviceId);
+                }
+            });
+        }
+
+        public void postAdServiceUpdated(final String serviceId) {
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onAdServiceUpdated(serviceId);
+                }
+            });
+        }
     }
 }
diff --git a/media/java/android/media/tv/ad/TvAdService.java b/media/java/android/media/tv/ad/TvAdService.java
index 6897a78..6995703 100644
--- a/media/java/android/media/tv/ad/TvAdService.java
+++ b/media/java/android/media/tv/ad/TvAdService.java
@@ -16,8 +16,37 @@
 
 package android.media.tv.ad;
 
+import android.annotation.CallSuper;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
 import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.os.Bundle;
+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.InputChannel;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
 import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.internal.os.SomeArgs;
+
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * The TvAdService class represents a TV client-side advertisement service.
@@ -36,9 +65,123 @@
     public static final String SERVICE_META_DATA = "android.media.tv.ad.service";
 
     /**
+     * This is the interface name that a service implementing a TV AD service should
+     * say that it supports -- that is, this is the action it uses for its intent filter. To be
+     * supported, the service must also require the
+     * android.Manifest.permission#BIND_TV_AD_SERVICE permission so that other
+     * applications cannot abuse it.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+    public static final String SERVICE_INTERFACE = "android.media.tv.ad.TvAdService";
+
+    private final Handler mServiceHandler = new ServiceHandler();
+    private final RemoteCallbackList<ITvAdServiceCallback> mCallbacks = new RemoteCallbackList<>();
+
+    @Override
+    @Nullable
+    public final IBinder onBind(@NonNull Intent intent) {
+        ITvAdService.Stub tvAdServiceBinder = new ITvAdService.Stub() {
+            @Override
+            public void registerCallback(ITvAdServiceCallback cb) {
+                if (cb != null) {
+                    mCallbacks.register(cb);
+                }
+            }
+
+            @Override
+            public void unregisterCallback(ITvAdServiceCallback cb) {
+                if (cb != null) {
+                    mCallbacks.unregister(cb);
+                }
+            }
+
+            @Override
+            public void createSession(InputChannel channel, ITvAdSessionCallback cb,
+                    String serviceId, String type) {
+                if (cb == null) {
+                    return;
+                }
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = channel;
+                args.arg2 = cb;
+                args.arg3 = serviceId;
+                args.arg4 = type;
+                mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args)
+                        .sendToTarget();
+            }
+
+            @Override
+            public void sendAppLinkCommand(Bundle command) {
+                onAppLinkCommand(command);
+            }
+        };
+        return tvAdServiceBinder;
+    }
+
+    /**
+     * Called when app link command is received.
+     */
+    public void onAppLinkCommand(@NonNull Bundle command) {
+    }
+
+
+    /**
+     * Returns a concrete implementation of {@link Session}.
+     *
+     * <p>May return {@code null} if this TV AD service fails to create a session for some
+     * reason.
+     *
+     * @param serviceId The ID of the TV AD associated with the session.
+     * @param type The type of the TV AD associated with the session.
+     */
+    @Nullable
+    public abstract Session onCreateSession(@NonNull String serviceId, @NonNull String type);
+
+    /**
      * Base class for derived classes to implement to provide a TV AD session.
      */
     public abstract static class Session implements KeyEvent.Callback {
+        private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState();
+
+        private final Object mLock = new Object();
+        // @GuardedBy("mLock")
+        private ITvAdSessionCallback mSessionCallback;
+        // @GuardedBy("mLock")
+        private final List<Runnable> mPendingActions = new ArrayList<>();
+        private final Context mContext;
+        final Handler mHandler;
+        private final WindowManager mWindowManager;
+        private Surface mSurface;
+
+
+        /**
+         * Creates a new Session.
+         *
+         * @param context The context of the application
+         */
+        public Session(@NonNull Context context) {
+            mContext = context;
+            mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+            mHandler = new Handler(context.getMainLooper());
+        }
+
+        /**
+         * Releases TvAdService session.
+         */
+        public abstract void onRelease();
+
+        void release() {
+            onRelease();
+            if (mSurface != null) {
+                mSurface.release();
+                mSurface = null;
+            }
+            synchronized (mLock) {
+                mSessionCallback = null;
+                mPendingActions.clear();
+            }
+        }
+
         /**
          * Starts TvAdService session.
          */
@@ -48,21 +191,264 @@
         void startAdService() {
             onStartAdService();
         }
-    }
 
-    /**
-     * Implements the internal ITvAdService interface.
-     */
-    public static class ITvAdSessionWrapper extends ITvAdSession.Stub {
-        private final Session mSessionImpl;
-
-        public ITvAdSessionWrapper(Session mSessionImpl) {
-            this.mSessionImpl = mSessionImpl;
+        @Override
+        public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
+            return false;
         }
 
         @Override
-        public void startAdService() {
-            mSessionImpl.startAdService();
+        public boolean onKeyLongPress(int keyCode, @NonNull KeyEvent event) {
+            return false;
         }
+
+        @Override
+        public boolean onKeyMultiple(int keyCode, int count, @NonNull KeyEvent event) {
+            return false;
+        }
+
+        @Override
+        public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
+            return false;
+        }
+
+        /**
+         * Implement this method to handle touch screen motion events on the current session.
+         *
+         * @param event The motion event being received.
+         * @return If you handled the event, return {@code true}. If you want to allow the event to
+         *         be handled by the next receiver, return {@code false}.
+         * @see View#onTouchEvent
+         */
+        public boolean onTouchEvent(@NonNull MotionEvent event) {
+            return false;
+        }
+
+        /**
+         * Implement this method to handle trackball events on the current session.
+         *
+         * @param event The motion event being received.
+         * @return If you handled the event, return {@code true}. If you want to allow the event to
+         *         be handled by the next receiver, return {@code false}.
+         * @see View#onTrackballEvent
+         */
+        public boolean onTrackballEvent(@NonNull MotionEvent event) {
+            return false;
+        }
+
+        /**
+         * Implement this method to handle generic motion events on the current session.
+         *
+         * @param event The motion event being received.
+         * @return If you handled the event, return {@code true}. If you want to allow the event to
+         *         be handled by the next receiver, return {@code false}.
+         * @see View#onGenericMotionEvent
+         */
+        public boolean onGenericMotionEvent(@NonNull MotionEvent event) {
+            return false;
+        }
+
+        /**
+         * Assigns a size and position to the surface passed in {@link #onSetSurface}. The position
+         * is relative to the overlay view that sits on top of this surface.
+         *
+         * @param left Left position in pixels, relative to the overlay view.
+         * @param top Top position in pixels, relative to the overlay view.
+         * @param right Right position in pixels, relative to the overlay view.
+         * @param bottom Bottom position in pixels, relative to the overlay view.
+         */
+        @CallSuper
+        public void layoutSurface(final int left, final int top, final int right,
+                final int bottom) {
+            if (left > right || top > bottom) {
+                throw new IllegalArgumentException("Invalid parameter");
+            }
+            executeOrPostRunnableOnMainThread(new Runnable() {
+                @MainThread
+                @Override
+                public void run() {
+                    try {
+                        if (DEBUG) {
+                            Log.d(TAG, "layoutSurface (l=" + left + ", t=" + top
+                                    + ", r=" + right + ", b=" + bottom + ",)");
+                        }
+                        if (mSessionCallback != null) {
+                            mSessionCallback.onLayoutSurface(left, top, right, bottom);
+                        }
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "error in layoutSurface", e);
+                    }
+                }
+            });
+        }
+
+        /**
+         * Called when the application sets the surface.
+         *
+         * <p>The TV AD service should render AD UI onto the given surface. When called with
+         * {@code null}, the AD service should immediately free any references to the currently set
+         * surface and stop using it.
+         *
+         * @param surface The surface to be used for AD UI rendering. Can be {@code null}.
+         * @return {@code true} if the surface was set successfully, {@code false} otherwise.
+         */
+        public abstract boolean onSetSurface(@Nullable Surface surface);
+
+        /**
+         * Called after any structural changes (format or size) have been made to the surface passed
+         * in {@link #onSetSurface}. This method is always called at least once, after
+         * {@link #onSetSurface} is called with non-null surface.
+         *
+         * @param format The new {@link PixelFormat} of the surface.
+         * @param width The new width of the surface.
+         * @param height The new height of the surface.
+         */
+        public void onSurfaceChanged(@PixelFormat.Format int format, int width, int height) {
+        }
+
+        /**
+         * Takes care of dispatching incoming input events and tells whether the event was handled.
+         */
+        int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
+            if (DEBUG) Log.d(TAG, "dispatchInputEvent(" + event + ")");
+            if (event instanceof KeyEvent) {
+                KeyEvent keyEvent = (KeyEvent) event;
+                if (keyEvent.dispatch(this, mDispatcherState, this)) {
+                    return TvAdManager.Session.DISPATCH_HANDLED;
+                }
+
+                // TODO: special handlings of navigation keys and media keys
+            } else if (event instanceof MotionEvent) {
+                MotionEvent motionEvent = (MotionEvent) event;
+                final int source = motionEvent.getSource();
+                if (motionEvent.isTouchEvent()) {
+                    if (onTouchEvent(motionEvent)) {
+                        return TvAdManager.Session.DISPATCH_HANDLED;
+                    }
+                } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+                    if (onTrackballEvent(motionEvent)) {
+                        return TvAdManager.Session.DISPATCH_HANDLED;
+                    }
+                } else {
+                    if (onGenericMotionEvent(motionEvent)) {
+                        return TvAdManager.Session.DISPATCH_HANDLED;
+                    }
+                }
+            }
+            // TODO: handle overlay view
+            return TvAdManager.Session.DISPATCH_NOT_HANDLED;
+        }
+
+
+        private void initialize(ITvAdSessionCallback callback) {
+            synchronized (mLock) {
+                mSessionCallback = callback;
+                for (Runnable runnable : mPendingActions) {
+                    runnable.run();
+                }
+                mPendingActions.clear();
+            }
+        }
+
+        /**
+         * Calls {@link #onSetSurface}.
+         */
+        void setSurface(Surface surface) {
+            onSetSurface(surface);
+            if (mSurface != null) {
+                mSurface.release();
+            }
+            mSurface = surface;
+            // TODO: Handle failure.
+        }
+
+        /**
+         * Calls {@link #onSurfaceChanged}.
+         */
+        void dispatchSurfaceChanged(int format, int width, int height) {
+            if (DEBUG) {
+                Log.d(TAG, "dispatchSurfaceChanged(format=" + format + ", width=" + width
+                        + ", height=" + height + ")");
+            }
+            onSurfaceChanged(format, width, height);
+        }
+
+        private void executeOrPostRunnableOnMainThread(Runnable action) {
+            synchronized (mLock) {
+                if (mSessionCallback == null) {
+                    // The session is not initialized yet.
+                    mPendingActions.add(action);
+                } else {
+                    if (mHandler.getLooper().isCurrentThread()) {
+                        action.run();
+                    } else {
+                        // Posts the runnable if this is not called from the main thread
+                        mHandler.post(action);
+                    }
+                }
+            }
+        }
+    }
+
+
+    @SuppressLint("HandlerLeak")
+    private final class ServiceHandler extends Handler {
+        private static final int DO_CREATE_SESSION = 1;
+        private static final int DO_NOTIFY_SESSION_CREATED = 2;
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case DO_CREATE_SESSION: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    InputChannel channel = (InputChannel) args.arg1;
+                    ITvAdSessionCallback cb = (ITvAdSessionCallback) args.arg2;
+                    String serviceId = (String) args.arg3;
+                    String type = (String) args.arg4;
+                    args.recycle();
+                    TvAdService.Session sessionImpl = onCreateSession(serviceId, type);
+                    if (sessionImpl == null) {
+                        try {
+                            // Failed to create a session.
+                            cb.onSessionCreated(null);
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "error in onSessionCreated", e);
+                        }
+                        return;
+                    }
+                    ITvAdSession stub =
+                            new ITvAdSessionWrapper(TvAdService.this, sessionImpl, channel);
+
+                    SomeArgs someArgs = SomeArgs.obtain();
+                    someArgs.arg1 = sessionImpl;
+                    someArgs.arg2 = stub;
+                    someArgs.arg3 = cb;
+                    mServiceHandler.obtainMessage(
+                            DO_NOTIFY_SESSION_CREATED, someArgs).sendToTarget();
+                    return;
+                }
+                case DO_NOTIFY_SESSION_CREATED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    Session sessionImpl = (Session) args.arg1;
+                    ITvAdSession stub = (ITvAdSession) args.arg2;
+                    ITvAdSessionCallback cb = (ITvAdSessionCallback) args.arg3;
+                    try {
+                        cb.onSessionCreated(stub);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "error in onSessionCreated", e);
+                    }
+                    if (sessionImpl != null) {
+                        sessionImpl.initialize(cb);
+                    }
+                    args.recycle();
+                    return;
+                }
+                default: {
+                    Log.w(TAG, "Unhandled message code: " + msg.what);
+                    return;
+                }
+            }
+        }
+
     }
 }
diff --git a/media/java/android/media/tv/ad/TvAdServiceInfo.java b/media/java/android/media/tv/ad/TvAdServiceInfo.java
index ed04f1f..45dc89d 100644
--- a/media/java/android/media/tv/ad/TvAdServiceInfo.java
+++ b/media/java/android/media/tv/ad/TvAdServiceInfo.java
@@ -24,6 +24,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -63,8 +64,7 @@
         if (context == null) {
             throw new IllegalArgumentException("context cannot be null.");
         }
-        // TODO: use a constant
-        Intent intent = new Intent("android.media.tv.ad.TvAdService").setComponent(component);
+        Intent intent = new Intent(TvAdService.SERVICE_INTERFACE).setComponent(component);
         ResolveInfo resolveInfo = context.getPackageManager().resolveService(
                 intent, PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
         if (resolveInfo == null) {
@@ -80,6 +80,7 @@
 
         mService = resolveInfo;
         mId = id;
+        mTypes.addAll(types);
     }
 
     private TvAdServiceInfo(ResolveInfo service, String id, List<String> types) {
@@ -147,9 +148,8 @@
             ResolveInfo resolveInfo, Context context, List<String> types) {
         ServiceInfo serviceInfo = resolveInfo.serviceInfo;
         PackageManager pm = context.getPackageManager();
-        // TODO: use constant for the metadata
         try (XmlResourceParser parser =
-                     serviceInfo.loadXmlMetaData(pm, "android.media.tv.ad.service")) {
+                     serviceInfo.loadXmlMetaData(pm, TvAdService.SERVICE_META_DATA)) {
             if (parser == null) {
                 throw new IllegalStateException(
                         "No " + "android.media.tv.ad.service"
@@ -171,7 +171,15 @@
                         + XML_START_TAG_NAME + " tag for " + serviceInfo.name);
             }
 
-            // TODO: parse attributes
+            TypedArray sa = resources.obtainAttributes(attrs,
+                    com.android.internal.R.styleable.TvAdService);
+            CharSequence[] textArr = sa.getTextArray(
+                    com.android.internal.R.styleable.TvAdService_adServiceTypes);
+            for (CharSequence cs : textArr) {
+                types.add(cs.toString().toLowerCase());
+            }
+
+            sa.recycle();
         } catch (IOException | XmlPullParserException e) {
             throw new IllegalStateException(
                     "Failed reading meta-data for " + serviceInfo.packageName, e);
diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java
index 1a3771a..5e67fe9 100644
--- a/media/java/android/media/tv/ad/TvAdView.java
+++ b/media/java/android/media/tv/ad/TvAdView.java
@@ -16,8 +16,20 @@
 
 package android.media.tv.ad;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.graphics.PixelFormat;
+import android.os.Handler;
+import android.util.AttributeSet;
 import android.util.Log;
+import android.util.Xml;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
 import android.view.ViewGroup;
 
 /**
@@ -28,18 +40,166 @@
     private static final String TAG = "TvAdView";
     private static final boolean DEBUG = false;
 
-    // TODO: create session
-    private TvAdManager.Session mSession;
+    private final TvAdManager mTvAdManager;
 
-    public TvAdView(Context context) {
-        super(context, /* attrs = */null, /* defStyleAttr = */0);
+    private final Handler mHandler = new Handler();
+    private TvAdManager.Session mSession;
+    private MySessionCallback mSessionCallback;
+
+    private final AttributeSet mAttrs;
+    private final int mDefStyleAttr;
+    private final XmlResourceParser mParser;
+
+    private SurfaceView mSurfaceView;
+    private Surface mSurface;
+
+    private boolean mSurfaceChanged;
+    private int mSurfaceFormat;
+    private int mSurfaceWidth;
+    private int mSurfaceHeight;
+
+    private boolean mUseRequestedSurfaceLayout;
+    private int mSurfaceViewLeft;
+    private int mSurfaceViewRight;
+    private int mSurfaceViewTop;
+    private int mSurfaceViewBottom;
+
+
+
+    private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
+        @Override
+        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+            if (DEBUG) {
+                Log.d(TAG, "surfaceChanged(holder=" + holder + ", format=" + format
+                        + ", width=" + width + ", height=" + height + ")");
+            }
+            mSurfaceFormat = format;
+            mSurfaceWidth = width;
+            mSurfaceHeight = height;
+            mSurfaceChanged = true;
+            dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
+        }
+
+        @Override
+        public void surfaceCreated(SurfaceHolder holder) {
+            mSurface = holder.getSurface();
+            setSessionSurface(mSurface);
+        }
+
+        @Override
+        public void surfaceDestroyed(SurfaceHolder holder) {
+            mSurface = null;
+            mSurfaceChanged = false;
+            setSessionSurface(null);
+        }
+    };
+
+
+    public TvAdView(@NonNull Context context) {
+        this(context, null, 0);
+    }
+
+    public TvAdView(@NonNull Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public TvAdView(@NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        int sourceResId = Resources.getAttributeSetSourceResId(attrs);
+        if (sourceResId != Resources.ID_NULL) {
+            Log.d(TAG, "Build local AttributeSet");
+            mParser  = context.getResources().getXml(sourceResId);
+            mAttrs = Xml.asAttributeSet(mParser);
+        } else {
+            Log.d(TAG, "Use passed in AttributeSet");
+            mParser = null;
+            mAttrs = attrs;
+        }
+        mDefStyleAttr = defStyleAttr;
+        resetSurfaceView();
+        mTvAdManager = (TvAdManager) getContext().getSystemService(Context.TV_AD_SERVICE);
     }
 
     @Override
-    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+    public void onLayout(boolean changed, int left, int top, int right, int bottom) {
         if (DEBUG) {
-            Log.d(TAG,
-                    "onLayout (left=" + l + ", top=" + t + ", right=" + r + ", bottom=" + b + ",)");
+            Log.d(TAG, "onLayout (left=" + left + ", top=" + top + ", right=" + right
+                    + ", bottom=" + bottom + ",)");
+        }
+        if (mUseRequestedSurfaceLayout) {
+            mSurfaceView.layout(mSurfaceViewLeft, mSurfaceViewTop, mSurfaceViewRight,
+                    mSurfaceViewBottom);
+        } else {
+            mSurfaceView.layout(0, 0, right - left, bottom - top);
+        }
+    }
+
+    @Override
+    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        mSurfaceView.measure(widthMeasureSpec, heightMeasureSpec);
+        int width = mSurfaceView.getMeasuredWidth();
+        int height = mSurfaceView.getMeasuredHeight();
+        int childState = mSurfaceView.getMeasuredState();
+        setMeasuredDimension(resolveSizeAndState(width, widthMeasureSpec, childState),
+                resolveSizeAndState(height, heightMeasureSpec,
+                        childState << MEASURED_HEIGHT_STATE_SHIFT));
+    }
+
+    @Override
+    public void onVisibilityChanged(@NonNull View changedView, int visibility) {
+        super.onVisibilityChanged(changedView, visibility);
+        mSurfaceView.setVisibility(visibility);
+    }
+
+    private void resetSurfaceView() {
+        if (mSurfaceView != null) {
+            mSurfaceView.getHolder().removeCallback(mSurfaceHolderCallback);
+            removeView(mSurfaceView);
+        }
+        mSurface = null;
+        mSurfaceView = new SurfaceView(getContext(), mAttrs, mDefStyleAttr) {
+            @Override
+            protected void updateSurface() {
+                super.updateSurface();
+            }};
+        // The surface view's content should be treated as secure all the time.
+        mSurfaceView.setSecure(true);
+        mSurfaceView.getHolder().addCallback(mSurfaceHolderCallback);
+        mSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
+
+        mSurfaceView.setZOrderOnTop(false);
+        mSurfaceView.setZOrderMediaOverlay(true);
+
+        addView(mSurfaceView);
+    }
+
+    private void setSessionSurface(Surface surface) {
+        if (mSession == null) {
+            return;
+        }
+        mSession.setSurface(surface);
+    }
+
+    private void dispatchSurfaceChanged(int format, int width, int height) {
+        if (mSession == null) {
+            return;
+        }
+        //mSession.dispatchSurfaceChanged(format, width, height);
+    }
+
+    /**
+     * Prepares the AD service of corresponding {@link TvAdService}.
+     *
+     * @param serviceId the AD service ID, which can be found in TvAdServiceInfo#getId().
+     */
+    public void prepareAdService(@NonNull String serviceId, @NonNull String type) {
+        if (DEBUG) {
+            Log.d(TAG, "prepareAdService");
+        }
+        mSessionCallback = new TvAdView.MySessionCallback(serviceId);
+        if (mTvAdManager != null) {
+            mTvAdManager.createSession(serviceId, type, mSessionCallback, mHandler);
         }
     }
 
@@ -54,4 +214,75 @@
             mSession.startAdService();
         }
     }
+
+    private class MySessionCallback extends TvAdManager.SessionCallback {
+        final String mServiceId;
+
+        MySessionCallback(String serviceId) {
+            mServiceId = serviceId;
+        }
+
+        @Override
+        public void onSessionCreated(TvAdManager.Session session) {
+            if (DEBUG) {
+                Log.d(TAG, "onSessionCreated()");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onSessionCreated - session already created");
+                // This callback is obsolete.
+                if (session != null) {
+                    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);
+                    if (mSurfaceChanged) {
+                        dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
+                    }
+                }
+            } else {
+                // Failed to create
+                // Todo: forward error to Tv App
+                mSessionCallback = null;
+            }
+        }
+
+        @Override
+        public void onSessionReleased(TvAdManager.Session session) {
+            if (DEBUG) {
+                Log.d(TAG, "onSessionReleased()");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onSessionReleased - session not created");
+                return;
+            }
+            mSessionCallback = null;
+            mSession = null;
+        }
+
+        @Override
+        public void onLayoutSurface(
+                TvAdManager.Session session, int left, int top, int right, int bottom) {
+            if (DEBUG) {
+                Log.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top + ", right="
+                        + right + ", bottom=" + bottom + ",)");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onLayoutSurface - session not created");
+                return;
+            }
+            mSurfaceViewLeft = left;
+            mSurfaceViewTop = top;
+            mSurfaceViewRight = right;
+            mSurfaceViewBottom = bottom;
+            mUseRequestedSurfaceLayout = true;
+            requestLayout();
+        }
+    }
 }
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 0467d0c..51a0553 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -38,7 +38,16 @@
 import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.TvRecordingInfo;
 import android.media.tv.TvTrackInfo;
+import android.media.tv.ad.ITvAdClient;
 import android.media.tv.ad.ITvAdManager;
+import android.media.tv.ad.ITvAdManagerCallback;
+import android.media.tv.ad.ITvAdService;
+import android.media.tv.ad.ITvAdServiceCallback;
+import android.media.tv.ad.ITvAdSession;
+import android.media.tv.ad.ITvAdSessionCallback;
+import android.media.tv.ad.TvAdService;
+import android.media.tv.ad.TvAdServiceInfo;
+import android.media.tv.flags.Flags;
 import android.media.tv.interactive.AppLinkInfo;
 import android.media.tv.interactive.ITvInteractiveAppClient;
 import android.media.tv.interactive.ITvInteractiveAppManager;
@@ -110,6 +119,8 @@
     @GuardedBy("mLock")
     private boolean mGetServiceListCalled = false;
     @GuardedBy("mLock")
+    private boolean mGetAdServiceListCalled = false;
+    @GuardedBy("mLock")
     private boolean mGetAppLinkInfoListCalled = false;
 
     private final UserManager mUserManager;
@@ -256,6 +267,141 @@
     }
 
     @GuardedBy("mLock")
+    private void buildTvAdServiceListLocked(int userId, String[] updatedPackages) {
+        if (!Flags.enableAdServiceFw()) {
+            return;
+        }
+        UserState userState = getOrCreateUserStateLocked(userId);
+        userState.mPackageSet.clear();
+
+        if (DEBUG) {
+            Slogf.d(TAG, "buildTvAdServiceListLocked");
+        }
+        PackageManager pm = mContext.getPackageManager();
+        List<ResolveInfo> services = pm.queryIntentServicesAsUser(
+                new Intent(TvAdService.SERVICE_INTERFACE),
+                PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
+                userId);
+        List<TvAdServiceInfo> serviceList = new ArrayList<>();
+
+        for (ResolveInfo ri : services) {
+            ServiceInfo si = ri.serviceInfo;
+            if (!android.Manifest.permission.BIND_TV_AD_SERVICE.equals(si.permission)) {
+                Slog.w(TAG, "Skipping TV AD service " + si.name
+                        + ": it does not require the permission "
+                        + android.Manifest.permission.BIND_TV_AD_SERVICE);
+                continue;
+            }
+
+            ComponentName component = new ComponentName(si.packageName, si.name);
+            try {
+                TvAdServiceInfo info = new TvAdServiceInfo(mContext, component);
+                serviceList.add(info);
+            } catch (Exception e) {
+                Slogf.e(TAG, "failed to load TV AD service " + si.name, e);
+                continue;
+            }
+            userState.mPackageSet.add(si.packageName);
+        }
+
+        // sort the service list by service id
+        Collections.sort(serviceList, Comparator.comparing(TvAdServiceInfo::getId));
+        Map<String, TvAdServiceState> adServiceMap = new HashMap<>();
+        for (TvAdServiceInfo info : serviceList) {
+            String serviceId = info.getId();
+            if (DEBUG) {
+                Slogf.d(TAG, "add " + serviceId);
+            }
+            TvAdServiceState adServiceState = userState.mAdServiceMap.get(serviceId);
+            if (adServiceState == null) {
+                adServiceState = new TvAdServiceState();
+            }
+            adServiceState.mInfo = info;
+            adServiceState.mUid = getAdServiceUid(info);
+            adServiceState.mComponentName = info.getComponent();
+            adServiceMap.put(serviceId, adServiceState);
+        }
+
+        for (String serviceId : adServiceMap.keySet()) {
+            if (!userState.mAdServiceMap.containsKey(serviceId)) {
+                notifyAdServiceAddedLocked(userState, serviceId);
+            } else if (updatedPackages != null) {
+                // Notify the package updates
+                ComponentName component = adServiceMap.get(serviceId).mInfo.getComponent();
+                for (String updatedPackage : updatedPackages) {
+                    if (component.getPackageName().equals(updatedPackage)) {
+                        updateAdServiceConnectionLocked(component, userId);
+                        notifyAdServiceUpdatedLocked(userState, serviceId);
+                        break;
+                    }
+                }
+            }
+        }
+
+        for (String serviceId : userState.mAdServiceMap.keySet()) {
+            if (!adServiceMap.containsKey(serviceId)) {
+                TvAdServiceInfo info = userState.mAdServiceMap.get(serviceId).mInfo;
+                AdServiceState serviceState = userState.mAdServiceStateMap.get(info.getComponent());
+                if (serviceState != null) {
+                    abortPendingCreateAdSessionRequestsLocked(serviceState, serviceId, userId);
+                }
+                notifyAdServiceRemovedLocked(userState, serviceId);
+            }
+        }
+
+        userState.mIAppMap.clear();
+        userState.mAdServiceMap = adServiceMap;
+    }
+
+    @GuardedBy("mLock")
+    private void notifyAdServiceAddedLocked(UserState userState, String serviceId) {
+        if (DEBUG) {
+            Slog.d(TAG, "notifyAdServiceAddedLocked(serviceId=" + serviceId + ")");
+        }
+        int n = userState.mAdCallbacks.beginBroadcast();
+        for (int i = 0; i < n; ++i) {
+            try {
+                userState.mAdCallbacks.getBroadcastItem(i).onAdServiceAdded(serviceId);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "failed to report added AD service to callback", e);
+            }
+        }
+        userState.mAdCallbacks.finishBroadcast();
+    }
+
+    @GuardedBy("mLock")
+    private void notifyAdServiceRemovedLocked(UserState userState, String serviceId) {
+        if (DEBUG) {
+            Slog.d(TAG, "notifyAdServiceRemovedLocked(serviceId=" + serviceId + ")");
+        }
+        int n = userState.mAdCallbacks.beginBroadcast();
+        for (int i = 0; i < n; ++i) {
+            try {
+                userState.mAdCallbacks.getBroadcastItem(i).onAdServiceRemoved(serviceId);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "failed to report removed AD service to callback", e);
+            }
+        }
+        userState.mAdCallbacks.finishBroadcast();
+    }
+
+    @GuardedBy("mLock")
+    private void notifyAdServiceUpdatedLocked(UserState userState, String serviceId) {
+        if (DEBUG) {
+            Slog.d(TAG, "notifyAdServiceUpdatedLocked(serviceId=" + serviceId + ")");
+        }
+        int n = userState.mAdCallbacks.beginBroadcast();
+        for (int i = 0; i < n; ++i) {
+            try {
+                userState.mAdCallbacks.getBroadcastItem(i).onAdServiceUpdated(serviceId);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "failed to report updated AD service to callback", e);
+            }
+        }
+        userState.mAdCallbacks.finishBroadcast();
+    }
+
+    @GuardedBy("mLock")
     private void notifyInteractiveAppServiceAddedLocked(UserState userState, String iAppServiceId) {
         if (DEBUG) {
             Slog.d(TAG, "notifyInteractiveAppServiceAddedLocked(iAppServiceId="
@@ -340,6 +486,16 @@
         }
     }
 
+    private int getAdServiceUid(TvAdServiceInfo info) {
+        try {
+            return getContext().getPackageManager().getApplicationInfo(
+                    info.getServiceInfo().packageName, 0).uid;
+        } catch (PackageManager.NameNotFoundException e) {
+            Slogf.w(TAG, "Unable to get UID for  " + info, e);
+            return Process.INVALID_UID;
+        }
+    }
+
     @Override
     public void onStart() {
         if (DEBUG) {
@@ -357,6 +513,7 @@
             synchronized (mLock) {
                 buildTvInteractiveAppServiceListLocked(mCurrentUserId, null);
                 buildAppLinkInfoLocked(mCurrentUserId);
+                buildTvAdServiceListLocked(mCurrentUserId, null);
             }
         }
     }
@@ -372,6 +529,14 @@
                     }
                 }
             }
+            private void buildTvAdServiceList(String[] packages) {
+                int userId = getChangingUserId();
+                synchronized (mLock) {
+                    if (mCurrentUserId == userId || mRunningProfiles.contains(userId)) {
+                        buildTvAdServiceListLocked(userId, packages);
+                    }
+                }
+            }
 
             @Override
             public void onPackageUpdateFinished(String packageName, int uid) {
@@ -379,6 +544,7 @@
                 // This callback is invoked when the TV interactive App service is reinstalled.
                 // In this case, isReplacing() always returns true.
                 buildTvInteractiveAppServiceList(new String[] { packageName });
+                buildTvAdServiceList(new String[] { packageName });
             }
 
             @Override
@@ -390,6 +556,7 @@
                 // available.
                 if (isReplacing()) {
                     buildTvInteractiveAppServiceList(packages);
+                    buildTvAdServiceList(packages);
                 }
             }
 
@@ -403,6 +570,7 @@
                 }
                 if (isReplacing()) {
                     buildTvInteractiveAppServiceList(packages);
+                    buildTvAdServiceList(packages);
                 }
             }
 
@@ -418,6 +586,7 @@
                     return;
                 }
                 buildTvInteractiveAppServiceList(null);
+                buildTvAdServiceList(null);
             }
 
             @Override
@@ -476,6 +645,7 @@
             mCurrentUserId = userId;
             buildTvInteractiveAppServiceListLocked(userId, null);
             buildAppLinkInfoLocked(userId);
+            buildTvAdServiceListLocked(userId, null);
         }
     }
 
@@ -562,6 +732,7 @@
         mRunningProfiles.add(userId);
         buildTvInteractiveAppServiceListLocked(userId, null);
         buildAppLinkInfoLocked(userId);
+        buildTvAdServiceListLocked(userId, null);
     }
 
     @GuardedBy("mLock")
@@ -619,7 +790,19 @@
                 Slog.e(TAG, "error in onSessionReleased", e);
             }
         }
-        removeSessionStateLocked(state.mSessionToken, state.mUserId);
+        removeAdSessionStateLocked(state.mSessionToken, state.mUserId);
+    }
+
+    @GuardedBy("mLock")
+    private void clearAdSessionAndNotifyClientLocked(AdSessionState state) {
+        if (state.mClient != null) {
+            try {
+                state.mClient.onSessionReleased(state.mSeq);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "error in onSessionReleased", e);
+            }
+        }
+        removeAdSessionStateLocked(state.mSessionToken, state.mUserId);
     }
 
     private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId,
@@ -655,6 +838,44 @@
     }
 
     @GuardedBy("mLock")
+    private AdSessionState getAdSessionStateLocked(
+            IBinder sessionToken, int callingUid, int userId) {
+        UserState userState = getOrCreateUserStateLocked(userId);
+        return getAdSessionStateLocked(sessionToken, callingUid, userState);
+    }
+
+    @GuardedBy("mLock")
+    private AdSessionState getAdSessionStateLocked(IBinder sessionToken, int callingUid,
+            UserState userState) {
+        AdSessionState sessionState = userState.mAdSessionStateMap.get(sessionToken);
+        if (sessionState == null) {
+            throw new SessionNotFoundException("Session state not found for token " + sessionToken);
+        }
+        // Only the application that requested this session or the system can access it.
+        if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.mCallingUid) {
+            throw new SecurityException("Illegal access to the session with token " + sessionToken
+                    + " from uid " + callingUid);
+        }
+        return sessionState;
+    }
+
+    @GuardedBy("mLock")
+    private ITvAdSession getAdSessionLocked(
+            IBinder sessionToken, int callingUid, int userId) {
+        return getAdSessionLocked(getAdSessionStateLocked(sessionToken, callingUid, userId));
+    }
+
+    @GuardedBy("mLock")
+    private ITvAdSession getAdSessionLocked(AdSessionState sessionState) {
+        ITvAdSession session = sessionState.mSession;
+        if (session == null) {
+            throw new IllegalStateException("Session not yet created for token "
+                    + sessionState.mSessionToken);
+        }
+        return session;
+    }
+
+    @GuardedBy("mLock")
     private SessionState getSessionStateLocked(IBinder sessionToken, int callingUid, int userId) {
         UserState userState = getOrCreateUserStateLocked(userId);
         return getSessionStateLocked(sessionToken, callingUid, userState);
@@ -691,10 +912,200 @@
         return session;
     }
     private final class TvAdBinderService extends ITvAdManager.Stub {
+
+        @Override
+        public List<TvAdServiceInfo> getTvAdServiceList(int userId) {
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+                    Binder.getCallingUid(), userId, "getTvAdServiceList");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    if (!mGetAdServiceListCalled) {
+                        buildTvAdServiceListLocked(userId, null);
+                        mGetAdServiceListCalled = true;
+                    }
+                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+                    List<TvAdServiceInfo> adServiceList = new ArrayList<>();
+                    for (TvAdServiceState state : userState.mAdServiceMap.values()) {
+                        adServiceList.add(state.mInfo);
+                    }
+                    return adServiceList;
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void createSession(final ITvAdClient client, final String serviceId, String type,
+                int seq, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int callingPid = Binder.getCallingPid();
+            final int resolvedUserId = resolveCallingUserId(callingPid, callingUid,
+                    userId, "createSession");
+            final long identity = Binder.clearCallingIdentity();
+
+            try {
+                synchronized (mLock) {
+                    if (userId != mCurrentUserId && !mRunningProfiles.contains(userId)) {
+                        // Only current user and its running profiles can create sessions.
+                        // Let the client get onConnectionFailed callback for this case.
+                        sendAdSessionTokenToClientLocked(client, serviceId, null, null, seq);
+                        return;
+                    }
+                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+                    TvAdServiceState adState = userState.mAdMap.get(serviceId);
+                    if (adState == null) {
+                        Slogf.w(TAG, "Failed to find state for serviceId=" + serviceId);
+                        sendAdSessionTokenToClientLocked(client, serviceId, null, null, seq);
+                        return;
+                    }
+                    AdServiceState serviceState =
+                            userState.mAdServiceStateMap.get(adState.mComponentName);
+                    if (serviceState == null) {
+                        int tasUid = PackageManager.getApplicationInfoAsUserCached(
+                                adState.mComponentName.getPackageName(), 0, resolvedUserId).uid;
+                        serviceState = new AdServiceState(
+                                adState.mComponentName, serviceId, resolvedUserId);
+                        userState.mAdServiceStateMap.put(adState.mComponentName, serviceState);
+                    }
+                    // Send a null token immediately while reconnecting.
+                    if (serviceState.mReconnecting) {
+                        sendAdSessionTokenToClientLocked(client, serviceId, null, null, seq);
+                        return;
+                    }
+
+                    // Create a new session token and a session state.
+                    IBinder sessionToken = new Binder();
+                    AdSessionState sessionState = new AdSessionState(sessionToken, serviceId, type,
+                            adState.mComponentName, client, seq, callingUid,
+                            callingPid, resolvedUserId);
+
+                    // Add them to the global session state map of the current user.
+                    userState.mAdSessionStateMap.put(sessionToken, sessionState);
+
+                    // Also, add them to the session state map of the current service.
+                    serviceState.mSessionTokens.add(sessionToken);
+
+                    if (serviceState.mService != null) {
+                        if (!createAdSessionInternalLocked(serviceState.mService, sessionToken,
+                                resolvedUserId)) {
+                            removeAdSessionStateLocked(sessionToken, resolvedUserId);
+                        }
+                    } else {
+                        updateAdServiceConnectionLocked(adState.mComponentName, resolvedUserId);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void releaseSession(IBinder sessionToken, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "releaseSession(sessionToken=" + sessionToken + ")");
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "releaseSession");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void setSurface(IBinder sessionToken, Surface surface, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "setSurface");
+            AdSessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState = getAdSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getAdSessionLocked(sessionState).setSurface(surface);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in setSurface", e);
+                    }
+                }
+            } finally {
+                if (surface != null) {
+                    // surface is not used in TvInteractiveAppManagerService.
+                    surface.release();
+                }
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void dispatchSurfaceChanged(IBinder sessionToken, int format, int width,
+                int height, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "dispatchSurfaceChanged");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        AdSessionState sessionState = getAdSessionStateLocked(
+                                sessionToken, callingUid, resolvedUserId);
+                        getAdSessionLocked(sessionState).dispatchSurfaceChanged(format, width,
+                                height);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in dispatchSurfaceChanged", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
         @Override
         public void startAdService(IBinder sessionToken, int userId) {
         }
 
+        @Override
+        public void registerCallback(final ITvAdManagerCallback callback, int userId) {
+            int callingPid = Binder.getCallingPid();
+            int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+                    "registerCallback");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    final UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+                    if (!userState.mAdCallbacks.register(callback)) {
+                        Slog.e(TAG, "client process has already died");
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void unregisterCallback(ITvAdManagerCallback callback, int userId) {
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+                    Binder.getCallingUid(), userId, "unregisterCallback");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+                    userState.mAdCallbacks.unregister(callback);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
     }
 
     private final class BinderService extends ITvInteractiveAppManager.Stub {
@@ -927,7 +1338,7 @@
             final long identity = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
-                    releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
+                    releaseAdSessionLocked(sessionToken, callingUid, resolvedUserId);
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -2134,6 +2545,17 @@
     }
 
     @GuardedBy("mLock")
+    private void sendAdSessionTokenToClientLocked(
+            ITvAdClient client, String serviceId, IBinder sessionToken,
+            InputChannel channel, int seq) {
+        try {
+            client.onSessionCreated(serviceId, sessionToken, channel, seq);
+        } catch (RemoteException e) {
+            Slogf.e(TAG, "error in onSessionCreated", e);
+        }
+    }
+
+    @GuardedBy("mLock")
     private boolean createSessionInternalLocked(
             ITvInteractiveAppService service, IBinder sessionToken, int userId) {
         UserState userState = getOrCreateUserStateLocked(userId);
@@ -2163,6 +2585,58 @@
     }
 
     @GuardedBy("mLock")
+    private boolean createAdSessionInternalLocked(
+            ITvAdService service, IBinder sessionToken, int userId) {
+        UserState userState = getOrCreateUserStateLocked(userId);
+        AdSessionState sessionState = userState.mAdSessionStateMap.get(sessionToken);
+        if (DEBUG) {
+            Slogf.d(TAG, "createAdSessionInternalLocked(iAppServiceId="
+                    + sessionState.mAdServiceId + ")");
+        }
+        InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
+
+        // Set up a callback to send the session token.
+        ITvAdSessionCallback callback = new AdSessionCallback(sessionState, channels);
+
+        boolean created = true;
+        // Create a session. When failed, send a null token immediately.
+        try {
+            service.createSession(
+                    channels[1], callback, sessionState.mAdServiceId, sessionState.mType);
+        } catch (RemoteException e) {
+            Slogf.e(TAG, "error in createSession", e);
+            sendAdSessionTokenToClientLocked(sessionState.mClient, sessionState.mAdServiceId, null,
+                    null, sessionState.mSeq);
+            created = false;
+        }
+        channels[1].dispose();
+        return created;
+    }
+
+    @GuardedBy("mLock")
+    @Nullable
+    private AdSessionState releaseAdSessionLocked(
+            IBinder sessionToken, int callingUid, int userId) {
+        AdSessionState sessionState = null;
+        try {
+            sessionState = getAdSessionStateLocked(sessionToken, callingUid, userId);
+            UserState userState = getOrCreateUserStateLocked(userId);
+            if (sessionState.mSession != null) {
+                sessionState.mSession.asBinder().unlinkToDeath(sessionState, 0);
+                sessionState.mSession.release();
+            }
+        } catch (RemoteException | SessionNotFoundException e) {
+            Slogf.e(TAG, "error in releaseSession", e);
+        } finally {
+            if (sessionState != null) {
+                sessionState.mSession = null;
+            }
+        }
+        removeAdSessionStateLocked(sessionToken, userId);
+        return sessionState;
+    }
+
+    @GuardedBy("mLock")
     @Nullable
     private SessionState releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
         SessionState sessionState = null;
@@ -2215,6 +2689,36 @@
     }
 
     @GuardedBy("mLock")
+    private void removeAdSessionStateLocked(IBinder sessionToken, int userId) {
+        UserState userState = getOrCreateUserStateLocked(userId);
+
+        // Remove the session state from the global session state map of the current user.
+        AdSessionState sessionState = userState.mAdSessionStateMap.remove(sessionToken);
+
+        if (sessionState == null) {
+            Slogf.e(TAG, "sessionState null, no more remove session action!");
+            return;
+        }
+
+        // Also remove the session token from the session token list of the current client and
+        // service.
+        ClientState clientState = userState.mClientStateMap.get(sessionState.mClient.asBinder());
+        if (clientState != null) {
+            clientState.mSessionTokens.remove(sessionToken);
+            if (clientState.isEmpty()) {
+                userState.mClientStateMap.remove(sessionState.mClient.asBinder());
+                sessionState.mClient.asBinder().unlinkToDeath(clientState, 0);
+            }
+        }
+
+        AdServiceState serviceState = userState.mAdServiceStateMap.get(sessionState.mComponent);
+        if (serviceState != null) {
+            serviceState.mSessionTokens.remove(sessionToken);
+        }
+        updateAdServiceConnectionLocked(sessionState.mComponent, userId);
+    }
+
+    @GuardedBy("mLock")
     private void abortPendingCreateSessionRequestsLocked(ServiceState serviceState,
             String iAppServiceId, int userId) {
         // Let clients know the create session requests are failed.
@@ -2237,6 +2741,28 @@
     }
 
     @GuardedBy("mLock")
+    private void abortPendingCreateAdSessionRequestsLocked(AdServiceState serviceState,
+            String serviceId, int userId) {
+        // Let clients know the create session requests are failed.
+        UserState userState = getOrCreateUserStateLocked(userId);
+        List<AdSessionState> sessionsToAbort = new ArrayList<>();
+        for (IBinder sessionToken : serviceState.mSessionTokens) {
+            AdSessionState sessionState = userState.mAdSessionStateMap.get(sessionToken);
+            if (sessionState.mSession == null
+                    && (serviceState == null
+                    || sessionState.mAdServiceId.equals(serviceId))) {
+                sessionsToAbort.add(sessionState);
+            }
+        }
+        for (AdSessionState sessionState : sessionsToAbort) {
+            removeAdSessionStateLocked(sessionState.mSessionToken, sessionState.mUserId);
+            sendAdSessionTokenToClientLocked(sessionState.mClient,
+                    sessionState.mAdServiceId, null, null, sessionState.mSeq);
+        }
+        updateAdServiceConnectionLocked(serviceState.mComponent, userId);
+    }
+
+    @GuardedBy("mLock")
     private void updateServiceConnectionLocked(ComponentName component, int userId) {
         UserState userState = getOrCreateUserStateLocked(userId);
         ServiceState serviceState = userState.mServiceStateMap.get(component);
@@ -2284,10 +2810,64 @@
         }
     }
 
+    @GuardedBy("mLock")
+    private void updateAdServiceConnectionLocked(ComponentName component, int userId) {
+        UserState userState = getOrCreateUserStateLocked(userId);
+        AdServiceState serviceState = userState.mAdServiceStateMap.get(component);
+        if (serviceState == null) {
+            return;
+        }
+        if (serviceState.mReconnecting) {
+            if (!serviceState.mSessionTokens.isEmpty()) {
+                // wait until all the sessions are removed.
+                return;
+            }
+            serviceState.mReconnecting = false;
+        }
+
+        boolean shouldBind = (!serviceState.mSessionTokens.isEmpty())
+                || (!serviceState.mPendingAppLinkCommand.isEmpty());
+
+        if (serviceState.mService == null && shouldBind) {
+            // This means that the service is not yet connected but its state indicates that we
+            // have pending requests. Then, connect the service.
+            if (serviceState.mBound) {
+                // We have already bound to the service so we don't try to bind again until after we
+                // unbind later on.
+                return;
+            }
+            if (DEBUG) {
+                Slogf.d(TAG, "bindServiceAsUser(service=" + component + ", userId=" + userId + ")");
+            }
+
+            Intent i = new Intent(TvAdService.SERVICE_INTERFACE).setComponent(component);
+            serviceState.mBound = mContext.bindServiceAsUser(
+                    i, serviceState.mConnection,
+                    Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
+                    new UserHandle(userId));
+        } else if (serviceState.mService != null && !shouldBind) {
+            // 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) {
+                Slogf.d(TAG, "unbindService(service=" + component + ")");
+            }
+            mContext.unbindService(serviceState.mConnection);
+            userState.mAdServiceStateMap.remove(component);
+        }
+    }
+
     private static final class UserState {
         private final int mUserId;
+        // A mapping from the TV AD service ID to its TvAdServiceState.
+        private Map<String, TvAdServiceState> mAdMap = new HashMap<>();
+        // A mapping from the name of a TV Interactive App service to its state.
+        private final Map<ComponentName, AdServiceState> mAdServiceStateMap = new HashMap<>();
+        // A mapping from the token of a TV Interactive App session to its state.
+        private final Map<IBinder, AdSessionState> mAdSessionStateMap = new HashMap<>();
         // A mapping from the TV Interactive App ID to its TvInteractiveAppState.
         private Map<String, TvInteractiveAppState> mIAppMap = new HashMap<>();
+        // A mapping from the TV AD service ID to its TvAdServiceState.
+        private Map<String, TvAdServiceState> mAdServiceMap = new HashMap<>();
         // A mapping from the token of a client to its state.
         private final Map<IBinder, ClientState> mClientStateMap = new HashMap<>();
         // A mapping from the name of a TV Interactive App service to its state.
@@ -2299,6 +2879,8 @@
         private final Set<String> mPackageSet = new HashSet<>();
         // A list of all app link infos.
         private final List<AppLinkInfo> mAppLinkInfoList = new ArrayList<>();
+        private final RemoteCallbackList<ITvAdManagerCallback> mAdCallbacks =
+                new RemoteCallbackList<>();
 
         // A list of callbacks.
         private final RemoteCallbackList<ITvInteractiveAppManagerCallback> mCallbacks =
@@ -2317,7 +2899,16 @@
         private int mIAppNumber;
     }
 
+    private static final class TvAdServiceState {
+        private String mAdServiceId;
+        private ComponentName mComponentName;
+        private TvAdServiceInfo mInfo;
+        private int mUid;
+        private int mAdNumber;
+    }
+
     private final class SessionState implements IBinder.DeathRecipient {
+        // TODO: rename SessionState and reorganize classes / methods of this file
         private final IBinder mSessionToken;
         private ITvInteractiveAppSession mSession;
         private final String mIAppServiceId;
@@ -2359,6 +2950,49 @@
         }
     }
 
+    private final class AdSessionState implements IBinder.DeathRecipient {
+        private final IBinder mSessionToken;
+        private ITvAdSession mSession;
+        private final String mAdServiceId;
+
+        private final String mType;
+        private final ITvAdClient mClient;
+        private final int mSeq;
+        private final ComponentName mComponent;
+
+        // The UID of the application that created the session.
+        // The application is usually the TV app.
+        private final int mCallingUid;
+
+        // The PID of the application that created the session.
+        // The application is usually the TV app.
+        private final int mCallingPid;
+
+        private final int mUserId;
+
+        private AdSessionState(IBinder sessionToken, String serviceId, String type,
+                ComponentName componentName, ITvAdClient client, int seq,
+                int callingUid, int callingPid, int userId) {
+            mSessionToken = sessionToken;
+            mAdServiceId = serviceId;
+            mType = type;
+            mComponent = componentName;
+            mClient = client;
+            mSeq = seq;
+            mCallingUid = callingUid;
+            mCallingPid = callingPid;
+            mUserId = userId;
+        }
+
+        @Override
+        public void binderDied() {
+            synchronized (mLock) {
+                mSession = null;
+                clearAdSessionAndNotifyClientLocked(this);
+            }
+        }
+    }
+
     private final class ClientState implements IBinder.DeathRecipient {
         private final List<IBinder> mSessionTokens = new ArrayList<>();
 
@@ -2429,6 +3063,29 @@
         }
     }
 
+    private final class AdServiceState {
+        private final List<IBinder> mSessionTokens = new ArrayList<>();
+        private final ServiceConnection mConnection;
+        private final ComponentName mComponent;
+        private final String mAdServiceId;
+        private final List<Bundle> mPendingAppLinkCommand = new ArrayList<>();
+
+        private ITvAdService mService;
+        private AdServiceCallback mCallback;
+        private boolean mBound;
+        private boolean mReconnecting;
+
+        private AdServiceState(ComponentName component, String tasId, int userId) {
+            mComponent = component;
+            mConnection = new AdServiceConnection(component, userId);
+            mAdServiceId = tasId;
+        }
+
+        private void addPendingAppLinkCommand(Bundle command) {
+            mPendingAppLinkCommand.add(command);
+        }
+    }
+
     private final class InteractiveAppServiceConnection implements ServiceConnection {
         private final ComponentName mComponent;
         private final int mUserId;
@@ -2542,6 +3199,98 @@
         }
     }
 
+    private final class AdServiceConnection implements ServiceConnection {
+        private final ComponentName mComponent;
+        private final int mUserId;
+
+        private AdServiceConnection(ComponentName component, int userId) {
+            mComponent = component;
+            mUserId = userId;
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName component, IBinder service) {
+            if (DEBUG) {
+                Slogf.d(TAG, "onServiceConnected(component=" + component + ")");
+            }
+            synchronized (mLock) {
+                UserState userState = getUserStateLocked(mUserId);
+                if (userState == null) {
+                    // The user was removed while connecting.
+                    mContext.unbindService(this);
+                    return;
+                }
+                AdServiceState serviceState = userState.mAdServiceStateMap.get(mComponent);
+                serviceState.mService = ITvAdService.Stub.asInterface(service);
+
+                // Register a callback, if we need to.
+                if (serviceState.mCallback == null) {
+                    serviceState.mCallback = new AdServiceCallback(mComponent, mUserId);
+                    try {
+                        serviceState.mService.registerCallback(serviceState.mCallback);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "error in registerCallback", e);
+                    }
+                }
+
+                if (!serviceState.mPendingAppLinkCommand.isEmpty()) {
+                    for (Iterator<Bundle> it = serviceState.mPendingAppLinkCommand.iterator();
+                            it.hasNext(); ) {
+                        Bundle command = it.next();
+                        final long identity = Binder.clearCallingIdentity();
+                        try {
+                            serviceState.mService.sendAppLinkCommand(command);
+                            it.remove();
+                        } catch (RemoteException e) {
+                            Slogf.e(TAG, "error in sendAppLinkCommand(" + command
+                                    + ") when onServiceConnected", e);
+                        } finally {
+                            Binder.restoreCallingIdentity(identity);
+                        }
+                    }
+                }
+
+                List<IBinder> tokensToBeRemoved = new ArrayList<>();
+
+                // And create sessions, if any.
+                for (IBinder sessionToken : serviceState.mSessionTokens) {
+                    if (!createAdSessionInternalLocked(
+                            serviceState.mService, sessionToken, mUserId)) {
+                        tokensToBeRemoved.add(sessionToken);
+                    }
+                }
+
+                for (IBinder sessionToken : tokensToBeRemoved) {
+                    removeAdSessionStateLocked(sessionToken, mUserId);
+                }
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName component) {
+            if (DEBUG) {
+                Slogf.d(TAG, "onServiceDisconnected(component=" + component + ")");
+            }
+            if (!mComponent.equals(component)) {
+                throw new IllegalArgumentException("Mismatched ComponentName: "
+                        + mComponent + " (expected), " + component + " (actual).");
+            }
+            synchronized (mLock) {
+                UserState userState = getOrCreateUserStateLocked(mUserId);
+                AdServiceState serviceState = userState.mAdServiceStateMap.get(mComponent);
+                if (serviceState != null) {
+                    serviceState.mReconnecting = true;
+                    serviceState.mBound = false;
+                    serviceState.mService = null;
+                    serviceState.mCallback = null;
+
+                    abortPendingCreateAdSessionRequestsLocked(serviceState, null, mUserId);
+                }
+            }
+        }
+    }
+
+
     private final class ServiceCallback extends ITvInteractiveAppServiceCallback.Stub {
         private final ComponentName mComponent;
         private final int mUserId;
@@ -2567,6 +3316,17 @@
         }
     }
 
+
+    private final class AdServiceCallback extends ITvAdServiceCallback.Stub {
+        private final ComponentName mComponent;
+        private final int mUserId;
+
+        AdServiceCallback(ComponentName component, int userId) {
+            mComponent = component;
+            mUserId = userId;
+        }
+    }
+
     private final class SessionCallback extends ITvInteractiveAppSessionCallback.Stub {
         private final SessionState mSessionState;
         private final InputChannel[] mInputChannels;
@@ -3110,6 +3870,85 @@
         }
     }
 
+    private final class AdSessionCallback extends ITvAdSessionCallback.Stub {
+        private final AdSessionState mSessionState;
+        private final InputChannel[] mInputChannels;
+
+        AdSessionCallback(AdSessionState sessionState, InputChannel[] channels) {
+            mSessionState = sessionState;
+            mInputChannels = channels;
+        }
+
+        @Override
+        public void onSessionCreated(ITvAdSession session) {
+            if (DEBUG) {
+                Slogf.d(TAG, "onSessionCreated(adServiceId="
+                        + mSessionState.mAdServiceId + ")");
+            }
+            synchronized (mLock) {
+                mSessionState.mSession = session;
+                if (session != null && addAdSessionTokenToClientStateLocked(session)) {
+                    sendAdSessionTokenToClientLocked(
+                            mSessionState.mClient,
+                            mSessionState.mAdServiceId,
+                            mSessionState.mSessionToken,
+                            mInputChannels[0],
+                            mSessionState.mSeq);
+                } else {
+                    removeAdSessionStateLocked(mSessionState.mSessionToken, mSessionState.mUserId);
+                    sendAdSessionTokenToClientLocked(mSessionState.mClient,
+                            mSessionState.mAdServiceId, null, null, mSessionState.mSeq);
+                }
+                mInputChannels[0].dispose();
+            }
+        }
+
+        @Override
+        public void onLayoutSurface(int left, int top, int right, int bottom) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slogf.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top
+                            + ", right=" + right + ", bottom=" + bottom + ",)");
+                }
+                if (mSessionState.mSession == null || mSessionState.mClient == null) {
+                    return;
+                }
+                try {
+                    mSessionState.mClient.onLayoutSurface(left, top, right, bottom,
+                            mSessionState.mSeq);
+                } catch (RemoteException e) {
+                    Slogf.e(TAG, "error in onLayoutSurface", e);
+                }
+            }
+        }
+
+        @GuardedBy("mLock")
+        private boolean addAdSessionTokenToClientStateLocked(ITvAdSession session) {
+            try {
+                session.asBinder().linkToDeath(mSessionState, 0);
+            } catch (RemoteException e) {
+                Slogf.e(TAG, "session process has already died", e);
+                return false;
+            }
+
+            IBinder clientToken = mSessionState.mClient.asBinder();
+            UserState userState = getOrCreateUserStateLocked(mSessionState.mUserId);
+            ClientState clientState = userState.mClientStateMap.get(clientToken);
+            if (clientState == null) {
+                clientState = new ClientState(clientToken, mSessionState.mUserId);
+                try {
+                    clientToken.linkToDeath(clientState, 0);
+                } catch (RemoteException e) {
+                    Slogf.e(TAG, "client process has already died", e);
+                    return false;
+                }
+                userState.mClientStateMap.put(clientToken, clientState);
+            }
+            clientState.mSessionTokens.add(mSessionState.mSessionToken);
+            return true;
+        }
+    }
+
     private static class SessionNotFoundException extends IllegalArgumentException {
         SessionNotFoundException(String name) {
             super(name);