From 8428f19e11fb19c0d5b5d4707e8b52b8eeec3220 Mon Sep 17 00:00:00 2001 From: shubang Date: Wed, 14 Dec 2022 15:44:38 -0800 Subject: [PATCH] Add APIs for streaming AD insertion Call sequence diagram can be found in the Bug below. Bug: 262620108 Test: mmm Change-Id: Ife8e19330f905bcfd82c4b54616f7849a7889025 --- media/java/android/media/tv/AdBuffer.aidl | 19 ++ media/java/android/media/tv/AdBuffer.java | 163 ++++++++++++++++++ media/java/android/media/tv/AdRequest.java | 44 ++++- media/java/android/media/tv/AdResponse.java | 5 +- .../java/android/media/tv/ITvInputClient.aidl | 2 + .../android/media/tv/ITvInputManager.aidl | 2 + .../android/media/tv/ITvInputSession.aidl | 2 + .../media/tv/ITvInputSessionCallback.aidl | 2 + .../media/tv/ITvInputSessionWrapper.java | 11 ++ .../java/android/media/tv/TvInputManager.java | 40 +++++ .../java/android/media/tv/TvInputService.java | 32 ++++ .../interactive/ITvInteractiveAppClient.aidl | 2 + .../interactive/ITvInteractiveAppManager.aidl | 2 + .../interactive/ITvInteractiveAppSession.aidl | 2 + .../ITvInteractiveAppSessionCallback.aidl | 2 + .../ITvInteractiveAppSessionWrapper.java | 11 ++ .../interactive/TvInteractiveAppManager.java | 39 +++++ .../interactive/TvInteractiveAppService.java | 46 +++++ .../server/tv/TvInputManagerService.java | 48 +++++- .../TvInteractiveAppManagerService.java | 41 +++++ 20 files changed, 509 insertions(+), 6 deletions(-) create mode 100644 media/java/android/media/tv/AdBuffer.aidl create mode 100644 media/java/android/media/tv/AdBuffer.java diff --git a/media/java/android/media/tv/AdBuffer.aidl b/media/java/android/media/tv/AdBuffer.aidl new file mode 100644 index 0000000000000..b1e2d3ebcb650 --- /dev/null +++ b/media/java/android/media/tv/AdBuffer.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2022 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; + +parcelable AdBuffer; diff --git a/media/java/android/media/tv/AdBuffer.java b/media/java/android/media/tv/AdBuffer.java new file mode 100644 index 0000000000000..ed44508d22621 --- /dev/null +++ b/media/java/android/media/tv/AdBuffer.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2022 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; + +import android.annotation.NonNull; +import android.media.MediaCodec.BufferFlag; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.SharedMemory; + +/** + * Buffer for advertisement data. + * @hide + */ +public class AdBuffer implements Parcelable { + private final int mId; + @NonNull + private final String mMimeType; + @NonNull + private final SharedMemory mBuffer; + private final int mOffset; + private final int mLength; + private final long mPresentationTimeUs; + private final int mFlags; + + public AdBuffer( + int id, + @NonNull String mimeType, + @NonNull SharedMemory buffer, + int offset, + int length, + long presentationTimeUs, + @BufferFlag int flags) { + this.mId = id; + this.mMimeType = mimeType; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mimeType); + this.mBuffer = buffer; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, buffer); + this.mOffset = offset; + this.mLength = length; + this.mPresentationTimeUs = presentationTimeUs; + this.mFlags = flags; + } + + /** + * Gets corresponding AD request ID. + */ + public int getId() { + return mId; + } + + /** + * Gets the mime type of the data. + */ + @NonNull + public String getMimeType() { + return mMimeType; + } + + /** + * Gets the shared memory which stores the data. + */ + @NonNull + public SharedMemory getSharedMemory() { + return mBuffer; + } + + /** + * Gets the offset of the buffer. + */ + public int getOffset() { + return mOffset; + } + + /** + * Gets the data length. + */ + public int getLength() { + return mLength; + } + + /** + * Gets the presentation time in microseconds. + */ + public long getPresentationTimeUs() { + return mPresentationTimeUs; + } + + /** + * Gets the flags. + */ + @BufferFlag + public int getFlags() { + return mFlags; + } + + @Override + public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + dest.writeInt(mId); + dest.writeString(mMimeType); + dest.writeTypedObject(mBuffer, flags); + dest.writeInt(mOffset); + dest.writeInt(mLength); + dest.writeLong(mPresentationTimeUs); + dest.writeInt(mFlags); + } + + @Override + public int describeContents() { + return 0; + } + + private AdBuffer(@NonNull Parcel in) { + int id = in.readInt(); + String mimeType = in.readString(); + SharedMemory buffer = (SharedMemory) in.readTypedObject(SharedMemory.CREATOR); + int offset = in.readInt(); + int length = in.readInt(); + long presentationTimeUs = in.readLong(); + int flags = in.readInt(); + + this.mId = id; + this.mMimeType = mimeType; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mMimeType); + this.mBuffer = buffer; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mBuffer); + this.mOffset = offset; + this.mLength = length; + this.mPresentationTimeUs = presentationTimeUs; + this.mFlags = flags; + } + + public static final @NonNull Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public AdBuffer[] newArray(int size) { + return new AdBuffer[size]; + } + + @Override + public AdBuffer createFromParcel(Parcel in) { + return new AdBuffer(in); + } + }; +} diff --git a/media/java/android/media/tv/AdRequest.java b/media/java/android/media/tv/AdRequest.java index f2fb93d803a81..60dfc5e56dac1 100644 --- a/media/java/android/media/tv/AdRequest.java +++ b/media/java/android/media/tv/AdRequest.java @@ -19,6 +19,7 @@ package android.media.tv; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.net.Uri; import android.os.Bundle; import android.os.Parcel; import android.os.ParcelFileDescriptor; @@ -69,10 +70,25 @@ public final class AdRequest implements Parcelable { private final long mEchoInterval; private final String mMediaFileType; private final Bundle mMetadata; + private final Uri mUri; public AdRequest(int id, @RequestType int requestType, @Nullable ParcelFileDescriptor fileDescriptor, long startTime, long stopTime, long echoInterval, @Nullable String mediaFileType, @NonNull Bundle metadata) { + this(id, requestType, fileDescriptor, null, startTime, stopTime, echoInterval, + mediaFileType, metadata); + } + + /** @hide */ + public AdRequest(int id, @RequestType int requestType, @Nullable Uri uri, long startTime, + long stopTime, long echoInterval, @NonNull Bundle metadata) { + this(id, requestType, null, uri, startTime, stopTime, echoInterval, null, metadata); + } + + private AdRequest(int id, @RequestType int requestType, + @Nullable ParcelFileDescriptor fileDescriptor, @Nullable Uri uri, long startTime, + long stopTime, long echoInterval, @Nullable String mediaFileType, + @NonNull Bundle metadata) { mId = id; mRequestType = requestType; mFileDescriptor = fileDescriptor; @@ -81,15 +97,23 @@ public final class AdRequest implements Parcelable { mEchoInterval = echoInterval; mMediaFileType = mediaFileType; mMetadata = metadata; + mUri = uri; } private AdRequest(Parcel source) { mId = source.readInt(); mRequestType = source.readInt(); - if (source.readInt() != 0) { + int readInt = source.readInt(); + if (readInt == 1) { mFileDescriptor = ParcelFileDescriptor.CREATOR.createFromParcel(source); + mUri = null; + } else if (readInt == 2) { + String stringUri = source.readString(); + mUri = stringUri == null ? null : Uri.parse(stringUri); + mFileDescriptor = null; } else { mFileDescriptor = null; + mUri = null; } mStartTime = source.readLong(); mStopTime = source.readLong(); @@ -117,13 +141,25 @@ public final class AdRequest implements Parcelable { * Gets the file descriptor of the AD media. * * @return The file descriptor of the AD media. Can be {@code null} for - * {@link #REQUEST_TYPE_STOP} + * {@link #REQUEST_TYPE_STOP} or a URI is used. */ @Nullable public ParcelFileDescriptor getFileDescriptor() { return mFileDescriptor; } + /** + * Gets the URI of the AD media. + * + * @return The URI of the AD media. Can be {@code null} for {@link #REQUEST_TYPE_STOP} or a file + * descriptor is used. + * @hide + */ + @Nullable + public Uri getUri() { + return mUri; + } + /** * Gets the start time of the AD media in milliseconds. *

0 means start immediately @@ -189,6 +225,10 @@ public final class AdRequest implements Parcelable { if (mFileDescriptor != null) { dest.writeInt(1); mFileDescriptor.writeToParcel(dest, flags); + } else if (mUri != null) { + dest.writeInt(2); + String stringUri = mUri.toString(); + dest.writeString(stringUri); } else { dest.writeInt(0); } diff --git a/media/java/android/media/tv/AdResponse.java b/media/java/android/media/tv/AdResponse.java index 08c66aba07d50..a15e8c180d50a 100644 --- a/media/java/android/media/tv/AdResponse.java +++ b/media/java/android/media/tv/AdResponse.java @@ -34,7 +34,8 @@ public final class AdResponse implements Parcelable { RESPONSE_TYPE_PLAYING, RESPONSE_TYPE_FINISHED, RESPONSE_TYPE_STOPPED, - RESPONSE_TYPE_ERROR + RESPONSE_TYPE_ERROR, + RESPONSE_TYPE_BUFFERING }) public @interface ResponseType {} @@ -42,6 +43,8 @@ public final class AdResponse implements Parcelable { public static final int RESPONSE_TYPE_FINISHED = 2; public static final int RESPONSE_TYPE_STOPPED = 3; public static final int RESPONSE_TYPE_ERROR = 4; + /** @hide */ + public static final int RESPONSE_TYPE_BUFFERING = 5; public static final @NonNull Parcelable.Creator CREATOR = new Parcelable.Creator() { diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl index 49148ceb84e24..ed2fd20659edd 100644 --- a/media/java/android/media/tv/ITvInputClient.aidl +++ b/media/java/android/media/tv/ITvInputClient.aidl @@ -17,6 +17,7 @@ package android.media.tv; import android.content.ComponentName; +import android.media.tv.AdBuffer; import android.media.tv.AdResponse; import android.media.tv.AitInfo; import android.media.tv.BroadcastInfoResponse; @@ -59,4 +60,5 @@ oneway interface ITvInputClient { // For ad response void onAdResponse(in AdResponse response, int seq); + void onAdBufferConsumed(in AdBuffer buffer, int seq); } diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl index 2a33ee6a6b4d2..f7c1e3c8abde8 100644 --- a/media/java/android/media/tv/ITvInputManager.aidl +++ b/media/java/android/media/tv/ITvInputManager.aidl @@ -20,6 +20,7 @@ import android.content.ComponentName; import android.content.Intent; import android.graphics.Rect; import android.media.PlaybackParams; +import android.media.tv.AdBuffer; import android.media.tv.AdRequest; import android.media.tv.BroadcastInfoRequest; import android.media.tv.DvbDeviceInfo; @@ -110,6 +111,7 @@ interface ITvInputManager { // For ad request void requestAd(in IBinder sessionToken, in AdRequest request, int userId); + void notifyAdBuffer(in IBinder sessionToken, in AdBuffer buffer, int userId); // For TV input hardware binding List getHardwareList(); diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl index 98200345b85de..326b98d997397 100644 --- a/media/java/android/media/tv/ITvInputSession.aidl +++ b/media/java/android/media/tv/ITvInputSession.aidl @@ -18,6 +18,7 @@ package android.media.tv; import android.graphics.Rect; import android.media.PlaybackParams; +import android.media.tv.AdBuffer; import android.media.tv.AdRequest; import android.media.tv.BroadcastInfoRequest; import android.media.tv.TvTrackInfo; @@ -71,4 +72,5 @@ oneway interface ITvInputSession { // For ad request void requestAd(in AdRequest request); + void notifyAdBuffer(in AdBuffer buffer); } diff --git a/media/java/android/media/tv/ITvInputSessionCallback.aidl b/media/java/android/media/tv/ITvInputSessionCallback.aidl index 9dfdb78061a70..b2a8d1c35d014 100644 --- a/media/java/android/media/tv/ITvInputSessionCallback.aidl +++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl @@ -16,6 +16,7 @@ package android.media.tv; +import android.media.tv.AdBuffer; import android.media.tv.AdResponse; import android.media.tv.AitInfo; import android.media.tv.BroadcastInfoResponse; @@ -56,4 +57,5 @@ oneway interface ITvInputSessionCallback { // For ad response void onAdResponse(in AdResponse response); + void onAdBufferConsumed(in AdBuffer buffer); } diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java index 8911f6c7d4be4..634f1024ab199 100644 --- a/media/java/android/media/tv/ITvInputSessionWrapper.java +++ b/media/java/android/media/tv/ITvInputSessionWrapper.java @@ -74,6 +74,7 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand private static final int DO_REMOVE_BROADCAST_INFO = 25; private static final int DO_SET_IAPP_NOTIFICATION_ENABLED = 26; private static final int DO_REQUEST_AD = 27; + private static final int DO_NOTIFY_AD_BUFFER = 28; private final boolean mIsRecordingSession; private final HandlerCaller mCaller; @@ -224,6 +225,7 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand case DO_START_RECORDING: { SomeArgs args = (SomeArgs) msg.obj; mTvInputRecordingSessionImpl.startRecording((Uri) args.arg1, (Bundle) args.arg2); + args.recycle(); break; } case DO_STOP_RECORDING: { @@ -254,6 +256,10 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand mTvInputSessionImpl.requestAd((AdRequest) msg.obj); break; } + case DO_NOTIFY_AD_BUFFER: { + mTvInputSessionImpl.notifyAdBuffer((AdBuffer) msg.obj); + break; + } default: { Log.w(TAG, "Unhandled message code: " + msg.what); break; @@ -424,6 +430,11 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_REQUEST_AD, request)); } + @Override + public void notifyAdBuffer(AdBuffer buffer) { + mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_NOTIFY_AD_BUFFER, buffer)); + } + private final class TvInputEventReceiver extends InputEventReceiver { public TvInputEventReceiver(InputChannel inputChannel, Looper looper) { super(inputChannel, looper); diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 04d28e7ddd915..690fcb1db574f 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -965,6 +965,19 @@ public final class TvInputManager { }); } } + + void postAdBufferConsumed(AdBuffer buffer) { + if (mSession.mIAppNotificationEnabled) { + mHandler.post(new Runnable() { + @Override + public void run() { + if (mSession.getInteractiveAppSession() != null) { + mSession.getInteractiveAppSession().notifyAdBufferConsumed(buffer); + } + } + }); + } + } } /** @@ -1412,6 +1425,18 @@ public final class TvInputManager { record.postAdResponse(response); } } + + @Override + public void onAdBufferConsumed(AdBuffer buffer, int seq) { + synchronized (mSessionCallbackRecordMap) { + SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); + if (record == null) { + Log.e(TAG, "Callback not found for seq " + seq); + return; + } + record.postAdBufferConsumed(buffer); + } + } }; ITvInputManagerCallback managerCallback = new ITvInputManagerCallback.Stub() { @Override @@ -3204,6 +3229,21 @@ public final class TvInputManager { } } + /** + * Notifies when the advertisement buffer is filled and ready to be read. + */ + public void notifyAdBuffer(AdBuffer buffer) { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.notifyAdBuffer(mToken, buffer, mUserId); + } catch (RemoteException e) { + 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; diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 70acf25afc51c..c46cdbc78b90e 100755 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -913,6 +913,27 @@ public abstract class TvInputService extends Service { }); } + /** + * Notifies the advertisement buffer is consumed. + * @hide + */ + public void notifyAdBufferConsumed(AdBuffer buffer) { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread + @Override + public void run() { + try { + if (DEBUG) Log.d(TAG, "notifyAdBufferConsumed"); + if (mSessionCallback != null) { + mSessionCallback.onAdBufferConsumed(buffer); + } + } catch (RemoteException e) { + Log.w(TAG, "error in notifyAdBufferConsumed", e); + } + } + }); + } + private void notifyTimeShiftStartPositionChanged(final long timeMs) { executeOrPostRunnableOnMainThread(new Runnable() { @MainThread @@ -1128,6 +1149,13 @@ public abstract class TvInputService extends Service { public void onRequestAd(@NonNull AdRequest request) { } + /** + * Called when advertisement buffer is ready. + * @hide + */ + public void onAdBuffer(AdBuffer buffer) { + } + /** * Tunes to a given channel. * @@ -1753,6 +1781,10 @@ public abstract class TvInputService extends Service { onRequestAd(request); } + void notifyAdBuffer(AdBuffer buffer) { + onAdBuffer(buffer); + } + /** * Takes care of dispatching incoming input events and tells whether the event was handled. */ diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl index 9b8ec5e53ae92..98357fc5a8838 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl @@ -17,6 +17,7 @@ package android.media.tv.interactive; import android.graphics.Rect; +import android.media.tv.AdBuffer; import android.media.tv.AdRequest; import android.media.tv.BroadcastInfoRequest; import android.net.Uri; @@ -37,6 +38,7 @@ oneway interface ITvInteractiveAppClient { void onSessionStateChanged(int state, int err, int seq); void onBiInteractiveAppCreated(in Uri biIAppUri, in String biIAppId, int seq); void onTeletextAppStateChanged(int state, int seq); + void onAdBuffer(in AdBuffer buffer, int seq); void onCommandRequest(in String cmdType, in Bundle parameters, int seq); void onSetVideoBounds(in Rect rect, int seq); void onRequestCurrentChannelUri(int seq); diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl index 38fc71711f7d1..8bfceee01a7f9 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl @@ -17,6 +17,7 @@ package android.media.tv.interactive; import android.graphics.Rect; +import android.media.tv.AdBuffer; import android.media.tv.AdResponse; import android.media.tv.BroadcastInfoResponse; import android.media.tv.TvTrackInfo; @@ -71,6 +72,7 @@ interface ITvInteractiveAppManager { void notifyBroadcastInfoResponse(in IBinder sessionToken, in BroadcastInfoResponse response, int UserId); void notifyAdResponse(in IBinder sessionToken, in AdResponse response, int UserId); + void notifyAdBufferConsumed(in IBinder sessionToken, in AdBuffer buffer, int userId); void createMediaView(in IBinder sessionToken, in IBinder windowToken, in Rect frame, int userId); diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl index 9e3353672af26..1953117b44b20 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl @@ -19,6 +19,7 @@ package android.media.tv.interactive; import android.graphics.Rect; import android.media.tv.BroadcastInfoResponse; import android.net.Uri; +import android.media.tv.AdBuffer; import android.media.tv.AdResponse; import android.media.tv.BroadcastInfoResponse; import android.media.tv.TvTrackInfo; @@ -59,6 +60,7 @@ oneway interface ITvInteractiveAppSession { void dispatchSurfaceChanged(int format, int width, int height); void notifyBroadcastInfoResponse(in BroadcastInfoResponse response); void notifyAdResponse(in AdResponse response); + void notifyAdBufferConsumed(in AdBuffer buffer); void createMediaView(in IBinder windowToken, in Rect frame); void relayoutMediaView(in Rect frame); diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl index 4ce58711ceac5..cd4f410ad73c0 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl @@ -17,6 +17,7 @@ package android.media.tv.interactive; import android.graphics.Rect; +import android.media.tv.AdBuffer; import android.media.tv.AdRequest; import android.media.tv.BroadcastInfoRequest; import android.media.tv.interactive.ITvInteractiveAppSession; @@ -36,6 +37,7 @@ oneway interface ITvInteractiveAppSessionCallback { void onSessionStateChanged(int state, int err); void onBiInteractiveAppCreated(in Uri biIAppUri, in String biIAppId); void onTeletextAppStateChanged(int state); + void onAdBuffer(in AdBuffer buffer); void onCommandRequest(in String cmdType, in Bundle parameters); void onSetVideoBounds(in Rect rect); void onRequestCurrentChannelUri(); diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java index a2fdfe01ccec7..b6463265d38ef 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.graphics.Rect; +import android.media.tv.AdBuffer; import android.media.tv.AdResponse; import android.media.tv.BroadcastInfoResponse; import android.media.tv.TvContentRating; @@ -83,6 +84,7 @@ public class ITvInteractiveAppSessionWrapper private static final int DO_REMOVE_MEDIA_VIEW = 29; private static final int DO_NOTIFY_RECORDING_STARTED = 30; private static final int DO_NOTIFY_RECORDING_STOPPED = 31; + private static final int DO_NOTIFY_AD_BUFFER_CONSUMED = 32; private final HandlerCaller mCaller; private Session mSessionImpl; @@ -253,6 +255,10 @@ public class ITvInteractiveAppSessionWrapper mSessionImpl.removeMediaView(true); break; } + case DO_NOTIFY_AD_BUFFER_CONSUMED: { + mSessionImpl.notifyAdBufferConsumed((AdBuffer) msg.obj); + break; + } default: { Log.w(TAG, "Unhandled message code: " + msg.what); break; @@ -424,6 +430,11 @@ public class ITvInteractiveAppSessionWrapper mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_NOTIFY_AD_RESPONSE, response)); } + @Override + public void notifyAdBufferConsumed(AdBuffer buffer) { + mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_NOTIFY_AD_BUFFER_CONSUMED, buffer)); + } + @Override public void createMediaView(IBinder windowToken, Rect frame) { mCaller.executeOrSendMessage( diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java index 287df40d0d31c..c57efc8694275 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; import android.annotation.SystemService; import android.content.Context; import android.graphics.Rect; +import android.media.tv.AdBuffer; import android.media.tv.AdRequest; import android.media.tv.AdResponse; import android.media.tv.BroadcastInfoRequest; @@ -558,6 +559,18 @@ public final class TvInteractiveAppManager { record.postTeletextAppStateChanged(state); } } + + @Override + public void onAdBuffer(AdBuffer buffer, int seq) { + synchronized (mSessionCallbackRecordMap) { + SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); + if (record == null) { + Log.e(TAG, "Callback not found for seq " + seq); + return; + } + record.postAdBuffer(buffer); + } + } }; ITvInteractiveAppManagerCallback managerCallback = new ITvInteractiveAppManagerCallback.Stub() { @@ -1277,6 +1290,21 @@ public final class TvInteractiveAppManager { } } + /** + * Notifies the advertisement buffer is consumed. + */ + public void notifyAdBufferConsumed(AdBuffer buffer) { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.notifyAdBufferConsumed(mToken, buffer, mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Releases this session. */ @@ -1808,6 +1836,17 @@ public final class TvInteractiveAppManager { } }); } + + void postAdBuffer(AdBuffer buffer) { + mHandler.post(new Runnable() { + @Override + public void run() { + if (mSession.getInputSession() != null) { + mSession.getInputSession().notifyAdBuffer(buffer); + } + } + }); + } } /** diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java index 90eed9ec83cae..e8205ba96aa39 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java @@ -30,6 +30,7 @@ import android.content.Context; import android.content.Intent; import android.graphics.PixelFormat; import android.graphics.Rect; +import android.media.tv.AdBuffer; import android.media.tv.AdRequest; import android.media.tv.AdResponse; import android.media.tv.BroadcastInfoRequest; @@ -614,6 +615,13 @@ public abstract class TvInteractiveAppService extends Service { public void onAdResponse(@NonNull AdResponse response) { } + /** + * Called when an advertisement buffer is consumed. + * @hide + */ + public void onAdBufferConsumed(AdBuffer buffer) { + } + @Override public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) { return false; @@ -1188,6 +1196,17 @@ public abstract class TvInteractiveAppService extends Service { onAdResponse(response); } + /** + * Calls {@link #onAdBufferConsumed}. + */ + void notifyAdBufferConsumed(AdBuffer buffer) { + if (DEBUG) { + Log.d(TAG, + "notifyAdBufferConsumed (buffer=" + buffer + ")"); + } + onAdBufferConsumed(buffer); + } + /** * Calls {@link #onRecordingStarted(String)}. */ @@ -1289,6 +1308,33 @@ public abstract class TvInteractiveAppService extends Service { }); } + + /** + * Notifies when the advertisement buffer is filled and ready to be read. + * @hide + */ + @CallSuper + public void notifyAdBuffer(AdBuffer buffer) { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread + @Override + public void run() { + try { + if (DEBUG) { + Log.d(TAG, + "notifyAdBuffer(buffer=" + buffer + ")"); + } + if (mSessionCallback != null) { + mSessionCallback.onAdBuffer(buffer); + } + } catch (RemoteException e) { + Log.w(TAG, "error in notifyAdBuffer", e); + } + } + }); + } + + /** * Takes care of dispatching incoming input events and tells whether the event was handled. */ diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index b95d3726af074..863ee756af8d2 100755 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -44,6 +44,7 @@ import android.graphics.Rect; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.media.PlaybackParams; +import android.media.tv.AdBuffer; import android.media.tv.AdRequest; import android.media.tv.AdResponse; import android.media.tv.AitInfo; @@ -2520,7 +2521,30 @@ public final class TvInputManagerService extends SystemService { } } finally { Binder.restoreCallingIdentity(identity); - }; + } + } + + @Override + public void notifyAdBuffer( + IBinder sessionToken, AdBuffer buffer, int userId) { + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, + userId, "notifyAdBuffer"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, + resolvedUserId); + getSessionLocked(sessionState).notifyAdBuffer(buffer); + } catch (RemoteException | SessionNotFoundException e) { + Slog.e(TAG, "error in notifyAdBuffer", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } } @Override @@ -3624,7 +3648,7 @@ public final class TvInputManagerService extends SystemService { } @Override - public void onBroadcastInfoResponse (BroadcastInfoResponse response) { + public void onBroadcastInfoResponse(BroadcastInfoResponse response) { synchronized (mLock) { if (DEBUG) { Slog.d(TAG, "onBroadcastInfoResponse()"); @@ -3641,7 +3665,7 @@ public final class TvInputManagerService extends SystemService { } @Override - public void onAdResponse (AdResponse response) { + public void onAdResponse(AdResponse response) { synchronized (mLock) { if (DEBUG) { Slog.d(TAG, "onAdResponse()"); @@ -3656,6 +3680,24 @@ public final class TvInputManagerService extends SystemService { } } } + + @Override + public void onAdBufferConsumed(AdBuffer buffer) { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "onAdBufferConsumed()"); + } + if (mSessionState.session == null || mSessionState.client == null) { + return; + } + try { + mSessionState.client.onAdBufferConsumed( + buffer, mSessionState.seq); + } catch (RemoteException e) { + Slog.e(TAG, "error in onAdBufferConsumed", e); + } + } + } } @VisibleForTesting 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 2c8fd967ae8a7..f173f7ab8b3d1 100644 --- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java +++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java @@ -29,6 +29,7 @@ import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; import android.graphics.Rect; +import android.media.tv.AdBuffer; import android.media.tv.AdRequest; import android.media.tv.AdResponse; import android.media.tv.BroadcastInfoRequest; @@ -1512,6 +1513,29 @@ public class TvInteractiveAppManagerService extends SystemService { } } + @Override + public void notifyAdBufferConsumed( + IBinder sessionToken, AdBuffer buffer, int userId) { + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId, + "notifyAdBufferConsumed"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, + resolvedUserId); + getSessionLocked(sessionState).notifyAdBufferConsumed(buffer); + } catch (RemoteException | SessionNotFoundException e) { + Slogf.e(TAG, "error in notifyAdBufferConsumed", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + @Override public void registerCallback(final ITvInteractiveAppManagerCallback callback, int userId) { int callingPid = Binder.getCallingPid(); @@ -2378,6 +2402,23 @@ public class TvInteractiveAppManagerService extends SystemService { } } + @Override + public void onAdBuffer(AdBuffer buffer) { + synchronized (mLock) { + if (DEBUG) { + Slogf.d(TAG, "onAdBuffer(buffer=" + buffer + ")"); + } + if (mSessionState.mSession == null || mSessionState.mClient == null) { + return; + } + try { + mSessionState.mClient.onAdBuffer(buffer, mSessionState.mSeq); + } catch (RemoteException e) { + Slogf.e(TAG, "error in onAdBuffer", e); + } + } + } + @GuardedBy("mLock") private boolean addSessionTokenToClientStateLocked(ITvInteractiveAppSession session) { try {