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);