diff options
17 files changed, 1550 insertions, 17 deletions
diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl index 897827750d4e..b6446214db31 100644 --- a/media/java/android/media/tv/ITvInputClient.aidl +++ b/media/java/android/media/tv/ITvInputClient.aidl @@ -69,4 +69,6 @@ oneway interface ITvInputClient { // For ad response void onAdResponse(in AdResponse response, int seq); void onAdBufferConsumed(in AdBuffer buffer, int seq); + + void onTvInputSessionData(in String type, in Bundle data, int seq); } diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl index 2f6575e65077..84c197d9e20f 100644 --- a/media/java/android/media/tv/ITvInputManager.aidl +++ b/media/java/android/media/tv/ITvInputManager.aidl @@ -152,4 +152,6 @@ interface ITvInputManager { // For freezing video playback void setVideoFrozen(in IBinder sessionToken, boolean isFrozen, int userId); + + void notifyTvAdSessionData(in IBinder sessionToken, in String type, in Bundle data, int userId); } diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl index a93f18db3dca..7b20c3976fd6 100644 --- a/media/java/android/media/tv/ITvInputSession.aidl +++ b/media/java/android/media/tv/ITvInputSession.aidl @@ -86,4 +86,6 @@ oneway interface ITvInputSession { // For freezing video void setVideoFrozen(boolean isFrozen); + + void notifyTvAdSessionData(in String type, in Bundle data); } diff --git a/media/java/android/media/tv/ITvInputSessionCallback.aidl b/media/java/android/media/tv/ITvInputSessionCallback.aidl index 8e2702a50662..76e079fb908c 100644 --- a/media/java/android/media/tv/ITvInputSessionCallback.aidl +++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl @@ -68,4 +68,6 @@ oneway interface ITvInputSessionCallback { // For messages sent from the TV input void onTvMessage(int type, in Bundle data); + + void onTvInputSessionData(in String type, in Bundle data); } diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java index 921104dc70c8..999e2cfbcede 100644 --- a/media/java/android/media/tv/ITvInputSessionWrapper.java +++ b/media/java/android/media/tv/ITvInputSessionWrapper.java @@ -82,6 +82,7 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand private static final int DO_STOP_PLAYBACK = 33; private static final int DO_START_PLAYBACK = 34; private static final int DO_SET_VIDEO_FROZEN = 35; + private static final int DO_NOTIFY_AD_SESSION_DATA = 36; private final boolean mIsRecordingSession; private final HandlerCaller mCaller; @@ -287,6 +288,7 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand case DO_NOTIFY_TV_MESSAGE: { SomeArgs args = (SomeArgs) msg.obj; mTvInputSessionImpl.onTvMessageReceived((Integer) args.arg1, (Bundle) args.arg2); + args.recycle(); break; } case DO_STOP_PLAYBACK: { @@ -301,6 +303,12 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand mTvInputSessionImpl.setVideoFrozen((Boolean) msg.obj); break; } + case DO_NOTIFY_AD_SESSION_DATA: { + SomeArgs args = (SomeArgs) msg.obj; + mTvInputSessionImpl.notifyTvAdSessionData((String) args.arg1, (Bundle) args.arg2); + args.recycle(); + break; + } default: { Log.w(TAG, "Unhandled message code: " + msg.what); break; @@ -488,6 +496,12 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand } @Override + public void notifyTvAdSessionData(String type, Bundle data) { + mCaller.executeOrSendMessage( + mCaller.obtainMessageOO(DO_NOTIFY_AD_SESSION_DATA, type, data)); + } + + @Override public void setVideoFrozen(boolean isFrozen) { mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_VIDEO_FROZEN, isFrozen)); } diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index be1b67547103..8720bfef1e49 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -22,6 +22,7 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.StringDef; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; @@ -535,6 +536,220 @@ public final class TvInputManager { */ public static final int SIGNAL_STRENGTH_STRONG = 3; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @StringDef(prefix = "SESSION_DATA_TYPE_", value = { + SESSION_DATA_TYPE_TUNED, + SESSION_DATA_TYPE_TRACK_SELECTED, + SESSION_DATA_TYPE_TRACKS_CHANGED, + SESSION_DATA_TYPE_VIDEO_AVAILABLE, + SESSION_DATA_TYPE_VIDEO_UNAVAILABLE, + SESSION_DATA_TYPE_BROADCAST_INFO_RESPONSE, + SESSION_DATA_TYPE_AD_RESPONSE, + SESSION_DATA_TYPE_AD_BUFFER_CONSUMED, + SESSION_DATA_TYPE_TV_MESSAGE}) + public @interface SessionDataType {} + + /** + * Informs the application that the session has been tuned to the given channel. + * + * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle) + * @see SESSION_DATA_KEY_CHANNEL_URI + * @hide + */ + public static final String SESSION_DATA_TYPE_TUNED = "tuned"; + + /** + * Sends the type and ID of a selected track. This is used to inform the application that a + * specific track is selected. + * + * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle) + * @see SESSION_DATA_KEY_TRACK_TYPE + * @see SESSION_DATA_KEY_TRACK_ID + * @hide + */ + public static final String SESSION_DATA_TYPE_TRACK_SELECTED = "track_selected"; + + /** + * Sends the list of all audio/video/subtitle tracks. + * + * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle) + * @see SESSION_DATA_KEY_TRACKS + * @hide + */ + public static final String SESSION_DATA_TYPE_TRACKS_CHANGED = "tracks_changed"; + + /** + * Informs the application that the video is now available for watching. + * + * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle) + * @hide + */ + public static final String SESSION_DATA_TYPE_VIDEO_AVAILABLE = "video_available"; + + /** + * Informs the application that the video became unavailable for some reason. + * + * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle) + * @see SESSION_DATA_KEY_VIDEO_UNAVAILABLE_REASON + * @hide + */ + public static final String SESSION_DATA_TYPE_VIDEO_UNAVAILABLE = "video_unavailable"; + + /** + * Notifies response for broadcast info. + * + * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle) + * @see SESSION_DATA_KEY_BROADCAST_INFO_RESPONSE + * @hide + */ + public static final String SESSION_DATA_TYPE_BROADCAST_INFO_RESPONSE = + "broadcast_info_response"; + + /** + * Notifies response for advertisement. + * + * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle) + * @see SESSION_DATA_KEY_AD_RESPONSE + * @hide + */ + public static final String SESSION_DATA_TYPE_AD_RESPONSE = "ad_response"; + + /** + * Notifies the advertisement buffer is consumed. + * + * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle) + * @see SESSION_DATA_KEY_AD_BUFFER + * @hide + */ + public static final String SESSION_DATA_TYPE_AD_BUFFER_CONSUMED = "ad_buffer_consumed"; + + /** + * Sends the TV message. + * + * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle) + * @see TvInputService.Session#notifyTvMessage(int, Bundle) + * @see SESSION_DATA_KEY_TV_MESSAGE_TYPE + * @hide + */ + public static final String SESSION_DATA_TYPE_TV_MESSAGE = "tv_message"; + + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @StringDef(prefix = "SESSION_DATA_KEY_", value = { + SESSION_DATA_KEY_CHANNEL_URI, + SESSION_DATA_KEY_TRACK_TYPE, + SESSION_DATA_KEY_TRACK_ID, + SESSION_DATA_KEY_TRACKS, + SESSION_DATA_KEY_VIDEO_UNAVAILABLE_REASON, + SESSION_DATA_KEY_BROADCAST_INFO_RESPONSE, + SESSION_DATA_KEY_AD_RESPONSE, + SESSION_DATA_KEY_AD_BUFFER, + SESSION_DATA_KEY_TV_MESSAGE_TYPE}) + public @interface SessionDataKey {} + + /** + * The URI of a channel. + * + * <p> Type: android.net.Uri + * + * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle) + * @hide + */ + public static final String SESSION_DATA_KEY_CHANNEL_URI = "channel_uri"; + + /** + * The type of the track. + * + * <p>One of {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO}, + * {@link TvTrackInfo#TYPE_SUBTITLE}. + * + * <p> Type: Integer + * + * @see TvTrackInfo#getType() + * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle) + * @hide + */ + public static final String SESSION_DATA_KEY_TRACK_TYPE = "track_type"; + + /** + * The ID of the track. + * + * <p> Type: String + * + * @see TvTrackInfo#getId() + * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle) + * @hide + */ + public static final String SESSION_DATA_KEY_TRACK_ID = "track_id"; + + /** + * A list which includes track information. + * + * <p> Type: {@code java.util.List<android.media.tv.TvTrackInfo> } + * + * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle) + * @hide + */ + public static final String SESSION_DATA_KEY_TRACKS = "tracks"; + + /** + * The reason why the video became unavailable. + * <p>The value can be {@link VIDEO_UNAVAILABLE_REASON_BUFFERING}, + * {@link VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY}, etc. + * + * <p> Type: Integer + * + * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle) + * @hide + */ + public static final String SESSION_DATA_KEY_VIDEO_UNAVAILABLE_REASON = + "video_unavailable_reason"; + + /** + * An object of {@link BroadcastInfoResponse}. + * + * <p> Type: android.media.tv.BroadcastInfoResponse + * + * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle) + * @hide + */ + public static final String SESSION_DATA_KEY_BROADCAST_INFO_RESPONSE = "broadcast_info_response"; + + /** + * An object of {@link AdResponse}. + * + * <p> Type: android.media.tv.AdResponse + * + * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle) + * @hide + */ + public static final String SESSION_DATA_KEY_AD_RESPONSE = "ad_response"; + + /** + * An object of {@link AdBuffer}. + * + * <p> Type: android.media.tv.AdBuffer + * + * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle) + * @hide + */ + public static final String SESSION_DATA_KEY_AD_BUFFER = "ad_buffer"; + + /** + * The type of TV message. + * <p>It can be one of {@link TV_MESSAGE_TYPE_WATERMARK}, + * {@link TV_MESSAGE_TYPE_CLOSED_CAPTION}, {@link TV_MESSAGE_TYPE_OTHER} + * + * <p> Type: Integer + * + * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle) + * @hide + */ + public static final String SESSION_DATA_KEY_TV_MESSAGE_TYPE = "tv_message_type"; + + /** * An unknown state of the client pid gets from the TvInputManager. Client gets this value when * query through {@link getClientPid(String sessionId)} fails. @@ -1271,6 +1486,17 @@ public final class TvInputManager { }); } } + + void postTvInputSessionData(String type, Bundle data) { + mHandler.post(new Runnable() { + @Override + public void run() { + if (mSession.getAdSession() != null) { + mSession.getAdSession().notifyTvInputSessionData(type, data); + } + } + }); + } } /** @@ -1820,6 +2046,18 @@ public final class TvInputManager { record.postAdBufferConsumed(buffer); } } + + @Override + public void onTvInputSessionData(String type, Bundle data, int seq) { + synchronized (mSessionCallbackRecordMap) { + SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); + if (record == null) { + Log.e(TAG, "Callback not found for seq " + seq); + return; + } + record.postTvInputSessionData(type, data); + } + } }; ITvInputManagerCallback managerCallback = new ITvInputManagerCallback.Stub() { @Override @@ -3844,6 +4082,21 @@ public final class TvInputManager { } } + /** + * Notifies data from session of linked TvAdService. + */ + public void notifyTvAdSessionData(String type, Bundle data) { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.notifyTvAdSessionData(mToken, type, data, 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 6301a27bac4d..a022b1c7cf64 100644 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -34,6 +34,7 @@ import android.graphics.Rect; import android.hardware.hdmi.HdmiDeviceInfo; import android.media.AudioPresentation; import android.media.PlaybackParams; +import android.media.tv.ad.TvAdManager; import android.media.tv.interactive.TvInteractiveAppService; import android.net.Uri; import android.os.AsyncTask; @@ -1259,6 +1260,37 @@ public abstract class TvInputService extends Service { } /** + * Notifies data related to this session to corresponding linked + * {@link android.media.tv.ad.TvAdService} object via TvAdView. + * + * <p>Methods like {@link #notifyBroadcastInfoResponse(BroadcastInfoResponse)} sends the + * related data to linked {@link android.media.tv.interactive.TvInteractiveAppService}, but + * don't work for {@link android.media.tv.ad.TvAdService}. The method is used specifically + * for {@link android.media.tv.ad.TvAdService} use cases. + * + * @param type data type + * @param data the related data values + * @hide + */ + public void notifyTvInputSessionData( + @NonNull @TvInputManager.SessionDataType String type, @NonNull Bundle data) { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread + @Override + public void run() { + try { + if (DEBUG) Log.d(TAG, "notifyTvInputSessionData"); + if (mSessionCallback != null) { + mSessionCallback.onTvInputSessionData(type, data); + } + } catch (RemoteException e) { + Log.w(TAG, "error in notifyTvInputSessionData", e); + } + } + }); + } + + /** * 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. * @@ -1401,6 +1433,20 @@ public abstract class TvInputService extends Service { public void onAdBufferReady(@NonNull AdBuffer buffer) { } + + /** + * Called when data from the linked {@link android.media.tv.ad.TvAdService} is received. + * + * @param type the type of the data + * @param data a bundle contains the data received + * @see android.media.tv.ad.TvAdService.Session#notifyTvAdSessionData(String, Bundle) + * @see android.media.tv.ad.TvAdView#setTvView(TvView) + * @hide + */ + public void onTvAdSessionData( + @NonNull @TvAdManager.SessionDataType String type, @NonNull Bundle data) { + } + /** * Tunes to a given channel. * @@ -2166,6 +2212,10 @@ public abstract class TvInputService extends Service { onAdBufferReady(buffer); } + void notifyTvAdSessionData(String type, Bundle data) { + onTvAdSessionData(type, data); + } + void onTvMessageReceived(int type, Bundle data) { onTvMessage(type, data); } diff --git a/media/java/android/media/tv/ad/ITvAdClient.aidl b/media/java/android/media/tv/ad/ITvAdClient.aidl index 34d96b374a35..49046d029198 100644 --- a/media/java/android/media/tv/ad/ITvAdClient.aidl +++ b/media/java/android/media/tv/ad/ITvAdClient.aidl @@ -16,6 +16,7 @@ package android.media.tv.ad; +import android.os.Bundle; import android.view.InputChannel; /** @@ -27,4 +28,11 @@ 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); + void onRequestCurrentVideoBounds(int seq); + void onRequestCurrentChannelUri(int seq); + void onRequestTrackInfoList(int seq); + void onRequestCurrentTvInputId(int seq); + void onRequestSigning( + in String id, in String algorithm, in String alias, in byte[] data, int seq); + void onTvAdSessionData(in String type, in Bundle data, 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 9620065ecf33..5c5f095fddcd 100644 --- a/media/java/android/media/tv/ad/ITvAdManager.aidl +++ b/media/java/android/media/tv/ad/ITvAdManager.aidl @@ -31,6 +31,7 @@ import android.view.Surface; */ interface ITvAdManager { List<TvAdServiceInfo> getTvAdServiceList(int userId); + void sendAppLinkCommand(String serviceId, in Bundle command, int userId); void createSession( in ITvAdClient client, in String serviceId, in String type, int seq, int userId); void releaseSession(in IBinder sessionToken, int userId); @@ -58,4 +59,7 @@ interface ITvAdManager { int userId); void relayoutMediaView(in IBinder sessionToken, in Rect frame, int userId); void removeMediaView(in IBinder sessionToken, int userId); + + void notifyTvInputSessionData( + in IBinder sessionToken, in String type, in Bundle data, int userId); } diff --git a/media/java/android/media/tv/ad/ITvAdSession.aidl b/media/java/android/media/tv/ad/ITvAdSession.aidl index 69afb175bb83..eba0bc83f3f2 100644 --- a/media/java/android/media/tv/ad/ITvAdSession.aidl +++ b/media/java/android/media/tv/ad/ITvAdSession.aidl @@ -46,4 +46,6 @@ oneway interface ITvAdSession { void createMediaView(in IBinder windowToken, in Rect frame); void relayoutMediaView(in Rect frame); void removeMediaView(); + + void notifyTvInputSessionData(in String type, in Bundle data); } diff --git a/media/java/android/media/tv/ad/ITvAdSessionCallback.aidl b/media/java/android/media/tv/ad/ITvAdSessionCallback.aidl index f21ef198ab01..e4e5cbb6c251 100644 --- a/media/java/android/media/tv/ad/ITvAdSessionCallback.aidl +++ b/media/java/android/media/tv/ad/ITvAdSessionCallback.aidl @@ -17,6 +17,7 @@ package android.media.tv.ad; import android.media.tv.ad.ITvAdSession; +import android.os.Bundle; /** * Helper interface for ITvAdSession to allow TvAdService to notify the system service when there is @@ -26,4 +27,10 @@ import android.media.tv.ad.ITvAdSession; oneway interface ITvAdSessionCallback { void onSessionCreated(in ITvAdSession session); void onLayoutSurface(int left, int top, int right, int bottom); + void onRequestCurrentVideoBounds(); + void onRequestCurrentChannelUri(); + void onRequestTrackInfoList(); + void onRequestCurrentTvInputId(); + void onRequestSigning(in String id, in String algorithm, in String alias, in byte[] data); + void onTvAdSessionData(in String type, in Bundle data); }
\ 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 index 251351de609e..e10a20e042e6 100644 --- a/media/java/android/media/tv/ad/ITvAdSessionWrapper.java +++ b/media/java/android/media/tv/ad/ITvAdSessionWrapper.java @@ -65,6 +65,7 @@ public class ITvAdSessionWrapper private static final int DO_SEND_SIGNING_RESULT = 14; private static final int DO_NOTIFY_ERROR = 15; private static final int DO_NOTIFY_TV_MESSAGE = 16; + private static final int DO_NOTIFY_INPUT_SESSION_DATA = 17; private final HandlerCaller mCaller; private TvAdService.Session mSessionImpl; @@ -180,6 +181,12 @@ public class ITvAdSessionWrapper args.recycle(); break; } + case DO_NOTIFY_INPUT_SESSION_DATA: { + SomeArgs args = (SomeArgs) msg.obj; + mSessionImpl.notifyTvInputSessionData((String) args.arg1, (Bundle) args.arg2); + args.recycle(); + break; + } default: { Log.w(TAG, "Unhandled message code: " + msg.what); break; @@ -280,6 +287,12 @@ public class ITvAdSessionWrapper mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_MEDIA_VIEW)); } + @Override + public void notifyTvInputSessionData(String type, Bundle data) { + mCaller.executeOrSendMessage( + mCaller.obtainMessageOO(DO_NOTIFY_INPUT_SESSION_DATA, type, data)); + } + private final class TvAdEventReceiver extends InputEventReceiver { TvAdEventReceiver(InputChannel inputChannel, Looper looper) { super(inputChannel, looper); diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java index 4dce72f62785..02c0d75dc2b6 100644 --- a/media/java/android/media/tv/ad/TvAdManager.java +++ b/media/java/android/media/tv/ad/TvAdManager.java @@ -16,12 +16,15 @@ package android.media.tv.ad; +import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.StringDef; import android.annotation.SystemService; import android.content.Context; import android.graphics.Rect; +import android.media.tv.AdBuffer; import android.media.tv.TvInputManager; import android.media.tv.TvTrackInfo; import android.media.tv.flags.Flags; @@ -43,7 +46,10 @@ import android.view.View; import com.android.internal.util.Preconditions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.concurrent.Executor; @@ -57,6 +63,198 @@ public class TvAdManager { // TODO: implement more methods and unhide APIs. private static final String TAG = "TvAdManager"; + /** + * Key for package name in app link. + * <p>Type: String + * + * @see #sendAppLinkCommand(String, Bundle) + * @hide + */ + public static final String APP_LINK_KEY_PACKAGE_NAME = "package_name"; + + /** + * Key for class name in app link. + * <p>Type: String + * + * @see #sendAppLinkCommand(String, Bundle) + * @hide + */ + public static final String APP_LINK_KEY_CLASS_NAME = "class_name"; + + /** + * Key for command type in app link command. + * <p>Type: String + * + * @see #sendAppLinkCommand(String, Bundle) + * @hide + */ + public static final String APP_LINK_KEY_COMMAND_TYPE = "command_type"; + + /** + * Key for service ID in app link command. + * <p>Type: String + * + * @see #sendAppLinkCommand(String, Bundle) + * @hide + */ + public static final String APP_LINK_KEY_SERVICE_ID = "service_id"; + + /** + * Key for back URI in app link command. + * <p>Type: String + * + * @see #sendAppLinkCommand(String, Bundle) + * @hide + */ + public static final String APP_LINK_KEY_BACK_URI = "back_uri"; + + /** + * Broadcast intent action to send app command to TV app. + * + * @see #sendAppLinkCommand(String, Bundle) + * @hide + */ + public static final String ACTION_APP_LINK_COMMAND = + "android.media.tv.ad.action.APP_LINK_COMMAND"; + + /** + * Intent key for TV input ID. It's used to send app command to TV app. + * <p>Type: String + * + * @see #sendAppLinkCommand(String, Bundle) + * @see #ACTION_APP_LINK_COMMAND + * @hide + */ + public static final String INTENT_KEY_TV_INPUT_ID = "tv_input_id"; + + /** + * Intent key for TV AD service ID. It's used to send app command to TV app. + * <p>Type: String + * + * @see #sendAppLinkCommand(String, Bundle) + * @see #ACTION_APP_LINK_COMMAND + * @see TvAdServiceInfo#getId() + * @hide + */ + public static final String INTENT_KEY_AD_SERVICE_ID = "ad_service_id"; + + /** + * Intent key for TV channel URI. It's used to send app command to TV app. + * <p>Type: android.net.Uri + * + * @see #sendAppLinkCommand(String, Bundle) + * @see #ACTION_APP_LINK_COMMAND + * @hide + */ + public static final String INTENT_KEY_CHANNEL_URI = "channel_uri"; + + /** + * Intent key for command type. It's used to send app command to TV app. The value of this key + * could vary according to TV apps. + * <p>Type: String + * + * @see #sendAppLinkCommand(String, Bundle) + * @see #ACTION_APP_LINK_COMMAND + * @hide + */ + public static final String INTENT_KEY_COMMAND_TYPE = "command_type"; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @StringDef(prefix = "SESSION_DATA_TYPE_", value = { + SESSION_DATA_TYPE_AD_REQUEST, + SESSION_DATA_TYPE_AD_BUFFER_READY, + SESSION_DATA_TYPE_BROADCAST_INFO_REQUEST, + SESSION_DATA_TYPE_REMOVE_BROADCAST_INFO_REQUEST}) + public @interface SessionDataType {} + + /** + * Sends an advertisement request to be processed by the related TV input. + * + * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle) + * @see SESSION_DATA_KEY_AD_REQUEST + * @hide + */ + public static final String SESSION_DATA_TYPE_AD_REQUEST = "ad_request"; + + /** + * Notifies the advertisement buffer is ready. + * + * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle) + * @see SESSION_DATA_KEY_AD_BUFFER + * @hide + */ + public static final String SESSION_DATA_TYPE_AD_BUFFER_READY = "ad_buffer_ready"; + + /** + * Sends request for broadcast info. + * + * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle) + * @see SESSION_DATA_KEY_BROADCAST_INFO_RESQUEST + * @hide + */ + public static final String SESSION_DATA_TYPE_BROADCAST_INFO_REQUEST = "broadcast_info_request"; + + /** + * Removes request for broadcast info. + * + * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle) + * @see SESSION_DATA_KEY_BROADCAST_INFO_REQUEST_ID + * @hide + */ + public static final String SESSION_DATA_TYPE_REMOVE_BROADCAST_INFO_REQUEST = + "remove_broadcast_info_request"; + + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @StringDef(prefix = "SESSION_DATA_KEY_", value = { + SESSION_DATA_KEY_AD_REQUEST, + SESSION_DATA_KEY_AD_BUFFER, + SESSION_DATA_KEY_BROADCAST_INFO_REQUEST, + SESSION_DATA_KEY_REQUEST_ID}) + public @interface SessionDataKey {} + + /** + * An object of {@link android.media.tv.AdRequest}. + * + * <p> Type: android.media.tv.AdRequest + * + * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle) + * @hide + */ + public static final String SESSION_DATA_KEY_AD_REQUEST = "ad_request"; + + /** + * An object of {@link AdBuffer}. + * + * <p> Type: android.media.tv.AdBuffer + * + * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle) + * @hide + */ + public static final String SESSION_DATA_KEY_AD_BUFFER = "ad_buffer"; + + /** + * An object of {@link android.media.tv.BroadcastInfoRequest}. + * + * <p> Type: android.media.tv.BroadcastInfoRequest + * + * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle) + * @hide + */ + public static final String SESSION_DATA_KEY_BROADCAST_INFO_REQUEST = "broadcast_info_request"; + + /** + * The ID of {@link android.media.tv.BroadcastInfoRequest}. + * + * <p> Type: Integer + * + * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle) + * @hide + */ + public static final String SESSION_DATA_KEY_REQUEST_ID = "request_id"; + private final ITvAdManager mService; private final int mUserId; @@ -125,6 +323,79 @@ public class TvAdManager { } } + @Override + public void onRequestCurrentVideoBounds(int seq) { + synchronized (mSessionCallbackRecordMap) { + SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); + if (record == null) { + Log.e(TAG, "Callback not found for seq " + seq); + return; + } + record.postRequestCurrentVideoBounds(); + } + } + + @Override + public void onRequestCurrentChannelUri(int seq) { + synchronized (mSessionCallbackRecordMap) { + SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); + if (record == null) { + Log.e(TAG, "Callback not found for seq " + seq); + return; + } + record.postRequestCurrentChannelUri(); + } + } + + @Override + public void onRequestTrackInfoList(int seq) { + synchronized (mSessionCallbackRecordMap) { + SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); + if (record == null) { + Log.e(TAG, "Callback not found for seq " + seq); + return; + } + record.postRequestTrackInfoList(); + } + } + + @Override + public void onRequestCurrentTvInputId(int seq) { + synchronized (mSessionCallbackRecordMap) { + SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); + if (record == null) { + Log.e(TAG, "Callback not found for seq " + seq); + return; + } + record.postRequestCurrentTvInputId(); + } + } + + @Override + public void onRequestSigning( + String id, String algorithm, String alias, byte[] data, int seq) { + synchronized (mSessionCallbackRecordMap) { + SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); + if (record == null) { + Log.e(TAG, "Callback not found for seq " + seq); + return; + } + record.postRequestSigning(id, algorithm, alias, data); + } + } + + @Override + public void onTvAdSessionData(String type, Bundle data, int seq) { + synchronized (mSessionCallbackRecordMap) { + SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); + if (record == null) { + Log.e(TAG, "Callback not found for seq " + seq); + return; + } + record.postTvAdSessionData(type, data); + } + } + }; ITvAdManagerCallback managerCallback = @@ -220,6 +491,59 @@ public class TvAdManager { } /** + * Sends app link command. + * + * @param serviceId The ID of TV AD service which the command to be sent to. The ID can be found + * in {@link TvAdServiceInfo#getId()}. + * @param command The command to be sent. + * @hide + */ + public void sendAppLinkCommand(@NonNull String serviceId, @NonNull Bundle command) { + try { + mService.sendAppLinkCommand(serviceId, command, mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Registers a {@link TvAdServiceCallback}. + * + * @param callback A callback used to monitor status of the TV AD services. + * @param executor A {@link Executor} that the status change will be delivered to. + * @hide + */ + public void registerCallback( + @CallbackExecutor @NonNull Executor executor, + @NonNull TvAdServiceCallback callback) { + Preconditions.checkNotNull(callback); + Preconditions.checkNotNull(executor); + synchronized (mLock) { + mCallbackRecords.add(new TvAdServiceCallbackRecord(callback, executor)); + } + } + + /** + * Unregisters the existing {@link TvAdServiceCallback}. + * + * @param callback The existing callback to remove. + * @hide + */ + public void unregisterCallback(@NonNull final TvAdServiceCallback callback) { + Preconditions.checkNotNull(callback); + synchronized (mLock) { + for (Iterator<TvAdServiceCallbackRecord> it = mCallbackRecords.iterator(); + it.hasNext(); ) { + TvAdServiceCallbackRecord record = it.next(); + if (record.getCallback() == callback) { + it.remove(); + break; + } + } + } + } + + /** * The Session provides the per-session functionality of AD service. * @hide */ @@ -533,6 +857,88 @@ public class TvAdManager { } } + /** + * Notifies data from session of linked TvInputService. + */ + public void notifyTvInputSessionData(String type, Bundle data) { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.notifyTvInputSessionData(mToken, type, data, mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Dispatches an input event to this session. + * + * @param event An {@link InputEvent} to dispatch. Cannot be {@code null}. + * @param token A token used to identify the input event later in the callback. + * @param callback A callback used to receive the dispatch result. Cannot be {@code null}. + * @param handler A {@link Handler} that the dispatch result will be delivered to. Cannot be + * {@code null}. + * @return Returns {@link #DISPATCH_HANDLED} if the event was handled. Returns + * {@link #DISPATCH_NOT_HANDLED} if the event was not handled. Returns + * {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the callback will + * be invoked later. + * @hide + */ + public int dispatchInputEvent(@NonNull InputEvent event, Object token, + @NonNull FinishedInputEventCallback callback, @NonNull Handler handler) { + Preconditions.checkNotNull(event); + Preconditions.checkNotNull(callback); + Preconditions.checkNotNull(handler); + synchronized (mHandler) { + if (mInputChannel == null) { + return DISPATCH_NOT_HANDLED; + } + PendingEvent p = obtainPendingEventLocked(event, token, callback, handler); + if (Looper.myLooper() == Looper.getMainLooper()) { + // Already running on the main thread so we can send the event immediately. + return sendInputEventOnMainLooperLocked(p); + } + + // Post the event to the main thread. + Message msg = mHandler.obtainMessage(InputEventHandler.MSG_SEND_INPUT_EVENT, p); + msg.setAsynchronous(true); + mHandler.sendMessage(msg); + return DISPATCH_IN_PROGRESS; + } + } + + private PendingEvent obtainPendingEventLocked(InputEvent event, Object token, + FinishedInputEventCallback callback, Handler handler) { + PendingEvent p = mPendingEventPool.acquire(); + if (p == null) { + p = new PendingEvent(); + } + p.mEvent = event; + p.mEventToken = token; + p.mCallback = callback; + p.mEventHandler = handler; + return 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 InputEventHandler extends Handler { public static final int MSG_SEND_INPUT_EVENT = 1; public static final int MSG_TIMEOUT_INPUT_EVENT = 2; @@ -639,23 +1045,6 @@ public class TvAdManager { 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); @@ -730,6 +1119,58 @@ public class TvAdManager { public void onLayoutSurface(Session session, int left, int top, int right, int bottom) { } + /** + * This is called when {@link TvAdService.Session#requestCurrentVideoBounds} is + * called. + * + * @param session A {@link TvAdService.Session} associated with this callback. + */ + public void onRequestCurrentVideoBounds(Session session) { + } + + /** + * This is called when {@link TvAdService.Session#requestCurrentChannelUri} is + * called. + * + * @param session A {@link TvAdService.Session} associated with this callback. + */ + public void onRequestCurrentChannelUri(Session session) { + } + + /** + * This is called when {@link TvAdService.Session#requestTrackInfoList} is + * called. + * + * @param session A {@link TvAdService.Session} associated with this callback. + */ + public void onRequestTrackInfoList(Session session) { + } + + /** + * This is called when {@link TvAdService.Session#requestCurrentTvInputId} is + * called. + * + * @param session A {@link TvAdService.Session} associated with this callback. + */ + public void onRequestCurrentTvInputId(Session session) { + } + + /** + * This is called when + * {@link TvAdService.Session#requestSigning(String, String, String, byte[])} is + * called. + * + * @param session A {@link TvAdService.Session} associated with this callback. + * @param signingId the ID to identify the request. + * @param algorithm the standard name of the signature algorithm requested, such as + * MD5withRSA, SHA256withDSA, etc. + * @param alias the alias of the corresponding {@link java.security.KeyStore}. + * @param data the original bytes to be signed. + */ + public void onRequestSigning( + Session session, String signingId, String algorithm, String alias, byte[] data) { + } + } /** @@ -810,6 +1251,62 @@ public class TvAdManager { } }); } + + void postRequestCurrentVideoBounds() { + mHandler.post(new Runnable() { + @Override + public void run() { + mSessionCallback.onRequestCurrentVideoBounds(mSession); + } + }); + } + + void postRequestCurrentChannelUri() { + mHandler.post(new Runnable() { + @Override + public void run() { + mSessionCallback.onRequestCurrentChannelUri(mSession); + } + }); + } + + void postRequestTrackInfoList() { + mHandler.post(new Runnable() { + @Override + public void run() { + mSessionCallback.onRequestTrackInfoList(mSession); + } + }); + } + + void postRequestCurrentTvInputId() { + mHandler.post(new Runnable() { + @Override + public void run() { + mSessionCallback.onRequestCurrentTvInputId(mSession); + } + }); + } + + void postRequestSigning(String id, String algorithm, String alias, byte[] data) { + mHandler.post(new Runnable() { + @Override + public void run() { + mSessionCallback.onRequestSigning(mSession, id, algorithm, alias, data); + } + }); + } + + void postTvAdSessionData(String type, Bundle data) { + mHandler.post(new Runnable() { + @Override + public void run() { + if (mSession.getInputSession() != null) { + mSession.getInputSession().notifyTvAdSessionData(type, data); + } + } + }); + } } private static final class TvAdServiceCallbackRecord { diff --git a/media/java/android/media/tv/ad/TvAdService.java b/media/java/android/media/tv/ad/TvAdService.java index 5d818375ad53..4d8f5c8b640b 100644 --- a/media/java/android/media/tv/ad/TvAdService.java +++ b/media/java/android/media/tv/ad/TvAdService.java @@ -31,6 +31,7 @@ import android.graphics.PixelFormat; import android.graphics.Rect; import android.media.tv.TvInputManager; import android.media.tv.TvTrackInfo; +import android.media.tv.TvView; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; @@ -132,6 +133,9 @@ public abstract class TvAdService extends Service { /** * Called when app link command is received. + * + * @see TvAdManager#sendAppLinkCommand(String, Bundle) + * @hide */ public void onAppLinkCommand(@NonNull Bundle command) { } @@ -279,6 +283,143 @@ public abstract class TvAdService extends Service { onResetAdService(); } + /** + * Requests the bounds of the current video. + * @hide + */ + @CallSuper + public void requestCurrentVideoBounds() { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread + @Override + public void run() { + try { + if (DEBUG) { + Log.d(TAG, "requestCurrentVideoBounds"); + } + if (mSessionCallback != null) { + mSessionCallback.onRequestCurrentVideoBounds(); + } + } catch (RemoteException e) { + Log.w(TAG, "error in requestCurrentVideoBounds", e); + } + } + }); + } + + /** + * Requests the URI of the current channel. + * @hide + */ + @CallSuper + public void requestCurrentChannelUri() { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread + @Override + public void run() { + try { + if (DEBUG) { + Log.d(TAG, "requestCurrentChannelUri"); + } + if (mSessionCallback != null) { + mSessionCallback.onRequestCurrentChannelUri(); + } + } catch (RemoteException e) { + Log.w(TAG, "error in requestCurrentChannelUri", e); + } + } + }); + } + + /** + * Requests the list of {@link TvTrackInfo}. + * @hide + */ + @CallSuper + public void requestTrackInfoList() { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread + @Override + public void run() { + try { + if (DEBUG) { + Log.d(TAG, "requestTrackInfoList"); + } + if (mSessionCallback != null) { + mSessionCallback.onRequestTrackInfoList(); + } + } catch (RemoteException e) { + Log.w(TAG, "error in requestTrackInfoList", e); + } + } + }); + } + + /** + * Requests current TV input ID. + * + * @see android.media.tv.TvInputInfo + * @hide + */ + @CallSuper + public void requestCurrentTvInputId() { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread + @Override + public void run() { + try { + if (DEBUG) { + Log.d(TAG, "requestCurrentTvInputId"); + } + if (mSessionCallback != null) { + mSessionCallback.onRequestCurrentTvInputId(); + } + } catch (RemoteException e) { + Log.w(TAG, "error in requestCurrentTvInputId", e); + } + } + }); + } + + /** + * Requests signing of the given data. + * + * <p>This is used when the corresponding server of the AD service app requires signing + * during handshaking, and the service doesn't have the built-in private key. The private + * key is provided by the content providers and pre-built in the related app, such as TV + * app. + * + * @param signingId the ID to identify the request. When a result is received, this ID can + * be used to correlate the result with the request. + * @param algorithm the standard name of the signature algorithm requested, such as + * MD5withRSA, SHA256withDSA, etc. The name is from standards like + * FIPS PUB 186-4 and PKCS #1. + * @param alias the alias of the corresponding {@link java.security.KeyStore}. + * @param data the original bytes to be signed. + * + * @see #onSigningResult(String, byte[]) + */ + @CallSuper + public void requestSigning(@NonNull String signingId, @NonNull String algorithm, + @NonNull String alias, @NonNull byte[] data) { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread + @Override + public void run() { + try { + if (DEBUG) { + Log.d(TAG, "requestSigning"); + } + if (mSessionCallback != null) { + mSessionCallback.onRequestSigning(signingId, algorithm, alias, data); + } + } catch (RemoteException e) { + Log.w(TAG, "error in requestSigning", e); + } + } + }); + } + @Override public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) { return false; @@ -470,6 +611,19 @@ public abstract class TvAdService extends Service { } /** + * Called when data from the linked {@link android.media.tv.TvInputService} is received. + * + * @param type the type of the data + * @param data a bundle contains the data received + * @see android.media.tv.TvInputService.Session#notifyTvAdSessionData(String, Bundle) + * @see android.media.tv.ad.TvAdView#setTvView(TvView) + * @hide + */ + public void onTvInputSessionData( + @NonNull @TvInputManager.SessionDataType String type, @NonNull Bundle data) { + } + + /** * Called when the size of the media view is changed by the application. * * <p>This is always called at least once when the session is created regardless of whether @@ -497,6 +651,33 @@ public abstract class TvAdService extends Service { } /** + * Notifies data related to this session to corresponding linked + * {@link android.media.tv.TvInputService} object via TvView. + * + * @param type data type + * @param data the related data values + * @see TvAdView#setTvView(TvView) + * @hide + */ + public void notifyTvAdSessionData( + @NonNull @TvAdManager.SessionDataType String type, @NonNull Bundle data) { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread + @Override + public void run() { + try { + if (DEBUG) Log.d(TAG, "notifyTvAdSessionData"); + if (mSessionCallback != null) { + mSessionCallback.onTvAdSessionData(type, data); + } + } catch (RemoteException e) { + Log.w(TAG, "error in notifyTvAdSessionData", e); + } + } + }); + } + + /** * Takes care of dispatching incoming input events and tells whether the event was handled. */ int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) { @@ -594,6 +775,10 @@ public abstract class TvAdService extends Service { onTvMessage(type, data); } + void notifyTvInputSessionData(String type, Bundle data) { + onTvInputSessionData(type, data); + } + private void executeOrPostRunnableOnMainThread(Runnable action) { synchronized (mLock) { if (mSessionCallback == null) { diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java index ec23b7c4532d..604dbd5a3399 100644 --- a/media/java/android/media/tv/ad/TvAdView.java +++ b/media/java/android/media/tv/ad/TvAdView.java @@ -28,17 +28,21 @@ import android.graphics.RectF; import android.media.tv.TvInputManager; import android.media.tv.TvTrackInfo; import android.media.tv.TvView; +import android.media.tv.ad.TvAdManager.Session.FinishedInputEventCallback; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.util.AttributeSet; import android.util.Log; import android.util.Xml; +import android.view.InputEvent; +import android.view.KeyEvent; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.ViewGroup; +import android.view.ViewRootImpl; import java.util.List; import java.util.concurrent.Executor; @@ -88,6 +92,7 @@ public class TvAdView extends ViewGroup { private boolean mMediaViewCreated; private Rect mMediaViewFrame; + private OnUnhandledInputEventListener mOnUnhandledInputEventListener; @@ -327,6 +332,109 @@ public class TvAdView extends ViewGroup { mSession.dispatchSurfaceChanged(format, width, height); } + private final FinishedInputEventCallback mFinishedInputEventCallback = + new FinishedInputEventCallback() { + @Override + public void onFinishedInputEvent(Object token, boolean handled) { + if (DEBUG) { + Log.d(TAG, "onFinishedInputEvent(token=" + token + ", handled=" + + handled + ")"); + } + if (handled) { + return; + } + // TODO: Re-order unhandled events. + InputEvent event = (InputEvent) token; + if (dispatchUnhandledInputEvent(event)) { + return; + } + ViewRootImpl viewRootImpl = getViewRootImpl(); + if (viewRootImpl != null) { + viewRootImpl.dispatchUnhandledInputEvent(event); + } + } + }; + + /** + * Dispatches an unhandled input event to the next receiver. + * + * It gives the host application a chance to dispatch the unhandled input events. + * + * @param event The input event. + * @return {@code true} if the event was handled by the view, {@code false} otherwise. + * @hide + */ + public boolean dispatchUnhandledInputEvent(@NonNull InputEvent event) { + if (mOnUnhandledInputEventListener != null) { + if (mOnUnhandledInputEventListener.onUnhandledInputEvent(event)) { + return true; + } + } + return onUnhandledInputEvent(event); + } + + /** + * Called when an unhandled input event also has not been handled by the user provided + * callback. This is the last chance to handle the unhandled input event in the + * TvAdView. + * + * @param event The input event. + * @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}. + */ + public boolean onUnhandledInputEvent(@NonNull InputEvent event) { + return false; + } + + /** + * Sets a listener to be invoked when an input event is not handled + * by the TV AD service. + * + * @param listener The callback to be invoked when the unhandled input event is received. + * @hide + */ + public void setOnUnhandledInputEventListener( + @NonNull @CallbackExecutor Executor executor, + @NonNull OnUnhandledInputEventListener listener) { + mOnUnhandledInputEventListener = listener; + // TODO: handle CallbackExecutor + } + + /** + * Gets the {@link OnUnhandledInputEventListener}. + * <p>Returns {@code null} if the listener is not set or is cleared. + * + * @see #setOnUnhandledInputEventListener(Executor, OnUnhandledInputEventListener) + * @see #clearOnUnhandledInputEventListener() + * @hide + */ + @Nullable + public OnUnhandledInputEventListener getOnUnhandledInputEventListener() { + return mOnUnhandledInputEventListener; + } + + /** + * Clears the {@link OnUnhandledInputEventListener}. + * @hide + */ + public void clearOnUnhandledInputEventListener() { + mOnUnhandledInputEventListener = null; + } + + @Override + public boolean dispatchKeyEvent(@NonNull KeyEvent event) { + if (super.dispatchKeyEvent(event)) { + return true; + } + if (mSession == null) { + return false; + } + InputEvent copiedEvent = event.copy(); + int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback, + mHandler); + return ret != TvAdManager.Session.DISPATCH_NOT_HANDLED; + } + /** * Prepares the AD service of corresponding {@link TvAdService}. * @@ -504,6 +612,24 @@ public class TvAdView extends ViewGroup { } /** + * Interface definition for a callback to be invoked when the unhandled input event is received. + * @hide + */ + public interface OnUnhandledInputEventListener { + /** + * Called when an input event was not handled by the TV AD service. + * + * <p>This is called asynchronously from where the event is dispatched. It gives the host + * application a chance to handle the unhandled input events. + * + * @param event The input event. + * @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}. + */ + boolean onUnhandledInputEvent(@NonNull InputEvent event); + } + + /** * Sets the callback to be invoked when an event is dispatched to this TvAdView. * * @param callback the callback to receive events. MUST NOT be {@code null}. @@ -606,6 +732,117 @@ public class TvAdView extends ViewGroup { mUseRequestedSurfaceLayout = true; requestLayout(); } + + @Override + public void onRequestCurrentVideoBounds(TvAdManager.Session session) { + if (DEBUG) { + Log.d(TAG, "onRequestCurrentVideoBounds"); + } + if (this != mSessionCallback) { + Log.w(TAG, "onRequestCurrentVideoBounds - session not created"); + return; + } + synchronized (mCallbackLock) { + if (mCallbackExecutor != null) { + mCallbackExecutor.execute(() -> { + synchronized (mCallbackLock) { + if (mCallback != null) { + mCallback.onRequestCurrentVideoBounds(mServiceId); + } + } + }); + } + } + } + + @Override + public void onRequestCurrentChannelUri(TvAdManager.Session session) { + if (DEBUG) { + Log.d(TAG, "onRequestCurrentChannelUri"); + } + if (this != mSessionCallback) { + Log.w(TAG, "onRequestCurrentChannelUri - session not created"); + return; + } + synchronized (mCallbackLock) { + if (mCallbackExecutor != null) { + mCallbackExecutor.execute(() -> { + synchronized (mCallbackLock) { + if (mCallback != null) { + mCallback.onRequestCurrentChannelUri(mServiceId); + } + } + }); + } + } + } + + @Override + public void onRequestTrackInfoList(TvAdManager.Session session) { + if (DEBUG) { + Log.d(TAG, "onRequestTrackInfoList"); + } + if (this != mSessionCallback) { + Log.w(TAG, "onRequestTrackInfoList - session not created"); + return; + } + synchronized (mCallbackLock) { + if (mCallbackExecutor != null) { + mCallbackExecutor.execute(() -> { + synchronized (mCallbackLock) { + if (mCallback != null) { + mCallback.onRequestTrackInfoList(mServiceId); + } + } + }); + } + } + } + + @Override + public void onRequestCurrentTvInputId(TvAdManager.Session session) { + if (DEBUG) { + Log.d(TAG, "onRequestCurrentTvInputId"); + } + if (this != mSessionCallback) { + Log.w(TAG, "onRequestCurrentTvInputId - session not created"); + return; + } + synchronized (mCallbackLock) { + if (mCallbackExecutor != null) { + mCallbackExecutor.execute(() -> { + synchronized (mCallbackLock) { + if (mCallback != null) { + mCallback.onRequestCurrentTvInputId(mServiceId); + } + } + }); + } + } + } + + @Override + public void onRequestSigning(TvAdManager.Session session, String id, String algorithm, + String alias, byte[] data) { + if (DEBUG) { + Log.d(TAG, "onRequestSigning"); + } + if (this != mSessionCallback) { + Log.w(TAG, "onRequestSigning - session not created"); + return; + } + synchronized (mCallbackLock) { + if (mCallbackExecutor != null) { + mCallbackExecutor.execute(() -> { + synchronized (mCallbackLock) { + if (mCallback != null) { + mCallback.onRequestSigning(mServiceId, id, algorithm, alias, data); + } + } + }); + } + } + } } /** @@ -613,5 +850,55 @@ public class TvAdView extends ViewGroup { * @hide */ public abstract static class TvAdCallback { + + /** + * This is called when {@link TvAdService.Session#requestCurrentVideoBounds()} + * is called. + * + * @param serviceId The ID of the TV AD service bound to this view. + */ + public void onRequestCurrentVideoBounds(@NonNull String serviceId) { + } + + /** + * This is called when {@link TvAdService.Session#requestCurrentChannelUri()} is + * called. + * + * @param serviceId The ID of the AD service bound to this view. + */ + public void onRequestCurrentChannelUri(@NonNull String serviceId) { + } + + /** + * This is called when {@link TvAdService.Session#requestTrackInfoList()} is called. + * + * @param serviceId The ID of the AD service bound to this view. + */ + public void onRequestTrackInfoList(@NonNull String serviceId) { + } + + /** + * This is called when {@link TvAdService.Session#requestCurrentTvInputId()} is called. + * + * @param serviceId The ID of the AD service bound to this view. + */ + public void onRequestCurrentTvInputId(@NonNull String serviceId) { + } + + /** + * This is called when + * {@link TvAdService.Session#requestSigning(String, String, String, byte[])} is called. + * + * @param serviceId The ID of the AD service bound to this view. + * @param signingId the ID to identify the request. + * @param algorithm the standard name of the signature algorithm requested, such as + * MD5withRSA, SHA256withDSA, etc. + * @param alias the alias of the corresponding {@link java.security.KeyStore}. + * @param data the original bytes to be signed. + * + */ + public void onRequestSigning(@NonNull String serviceId, @NonNull String signingId, + @NonNull String algorithm, @NonNull String alias, @NonNull byte[] data) { + } } } diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index e434df7836c4..089a88691af3 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -2791,6 +2791,29 @@ public final class TvInputManagerService extends SystemService { } @Override + public void notifyTvAdSessionData( + IBinder sessionToken, String type, Bundle data, int userId) { + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, + userId, "notifyTvAdSessionData"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, + resolvedUserId); + getSessionLocked(sessionState).notifyTvAdSessionData(type, data); + } catch (RemoteException | SessionNotFoundException e) { + Slog.e(TAG, "error in notifyTvAdSessionData", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public int getClientPid(String sessionId) { ensureTunerResourceAccessPermission(); final long identity = Binder.clearCallingIdentity(); @@ -4322,6 +4345,23 @@ public final class TvInputManagerService extends SystemService { } } } + + @Override + public void onTvInputSessionData(String type, Bundle data) { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "onTvInputSessionData()"); + } + if (mSessionState.session == null || mSessionState.client == null) { + return; + } + try { + mSessionState.client.onTvInputSessionData(type, data, mSessionState.seq); + } catch (RemoteException e) { + Slog.e(TAG, "error in onTvInputSessionData", 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 eacd3f8d4d86..ffce50e5bd5e 100644 --- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java +++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java @@ -937,6 +937,41 @@ public class TvInteractiveAppManagerService extends SystemService { } @Override + public void sendAppLinkCommand(String serviceId, Bundle command, int userId) { + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), + Binder.getCallingUid(), userId, "sendAppLinkCommand"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + UserState userState = getOrCreateUserStateLocked(resolvedUserId); + TvAdServiceState adServiceState = userState.mAdServiceMap.get(serviceId); + if (adServiceState == null) { + Slogf.e(TAG, "failed to sendAppLinkCommand - unknown service id " + + serviceId); + return; + } + ComponentName componentName = adServiceState.mInfo.getComponent(); + AdServiceState serviceState = userState.mAdServiceStateMap.get(componentName); + if (serviceState == null) { + serviceState = new AdServiceState(componentName, serviceId, resolvedUserId); + serviceState.addPendingAppLinkCommand(command); + userState.mAdServiceStateMap.put(componentName, serviceState); + updateAdServiceConnectionLocked(componentName, resolvedUserId); + } else if (serviceState.mService != null) { + serviceState.mService.sendAppLinkCommand(command); + } else { + serviceState.addPendingAppLinkCommand(command); + updateAdServiceConnectionLocked(componentName, resolvedUserId); + } + } + } catch (RemoteException e) { + Slogf.e(TAG, "error in sendAppLinkCommand", e); + } 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(); @@ -1320,6 +1355,32 @@ public class TvInteractiveAppManagerService extends SystemService { } @Override + public void notifyTvInputSessionData( + IBinder sessionToken, String type, Bundle data, int userId) { + if (DEBUG) { + Slogf.d(TAG, "notifyTvInputSessionData(type=%d)", type); + } + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId, + "notifyTvInputSessionData"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + AdSessionState sessionState = + getAdSessionStateLocked(sessionToken, callingUid, resolvedUserId); + getAdSessionLocked(sessionState).notifyTvInputSessionData(type, data); + } catch (RemoteException | SessionNotFoundException e) { + Slogf.e(TAG, "error in notifyTvInputSessionData", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public void registerCallback(final ITvAdManagerCallback callback, int userId) { int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); @@ -4358,6 +4419,110 @@ public class TvInteractiveAppManagerService extends SystemService { } } + @Override + public void onRequestCurrentVideoBounds() { + synchronized (mLock) { + if (DEBUG) { + Slogf.d(TAG, "onRequestCurrentVideoBounds"); + } + if (mSessionState.mSession == null || mSessionState.mClient == null) { + return; + } + try { + mSessionState.mClient.onRequestCurrentVideoBounds(mSessionState.mSeq); + } catch (RemoteException e) { + Slogf.e(TAG, "error in onRequestCurrentVideoBounds", e); + } + } + } + + @Override + public void onRequestCurrentChannelUri() { + synchronized (mLock) { + if (DEBUG) { + Slogf.d(TAG, "onRequestCurrentChannelUri"); + } + if (mSessionState.mSession == null || mSessionState.mClient == null) { + return; + } + try { + mSessionState.mClient.onRequestCurrentChannelUri(mSessionState.mSeq); + } catch (RemoteException e) { + Slogf.e(TAG, "error in onRequestCurrentChannelUri", e); + } + } + } + + @Override + public void onRequestTrackInfoList() { + synchronized (mLock) { + if (DEBUG) { + Slogf.d(TAG, "onRequestTrackInfoList"); + } + if (mSessionState.mSession == null || mSessionState.mClient == null) { + return; + } + try { + mSessionState.mClient.onRequestTrackInfoList(mSessionState.mSeq); + } catch (RemoteException e) { + Slogf.e(TAG, "error in onRequestTrackInfoList", e); + } + } + } + + @Override + public void onRequestCurrentTvInputId() { + synchronized (mLock) { + if (DEBUG) { + Slogf.d(TAG, "onRequestCurrentTvInputId"); + } + if (mSessionState.mSession == null || mSessionState.mClient == null) { + return; + } + try { + mSessionState.mClient.onRequestCurrentTvInputId(mSessionState.mSeq); + } catch (RemoteException e) { + Slogf.e(TAG, "error in onRequestCurrentTvInputId", e); + } + } + } + + + @Override + public void onRequestSigning(String id, String algorithm, String alias, byte[] data) { + synchronized (mLock) { + if (DEBUG) { + Slogf.d(TAG, "onRequestSigning"); + } + if (mSessionState.mSession == null || mSessionState.mClient == null) { + return; + } + try { + mSessionState.mClient.onRequestSigning( + id, algorithm, alias, data, mSessionState.mSeq); + } catch (RemoteException e) { + Slogf.e(TAG, "error in onRequestSigning", e); + } + } + } + + @Override + public void onTvAdSessionData(String type, Bundle data) { + synchronized (mLock) { + if (DEBUG) { + Slogf.d(TAG, "onTvAdSessionData"); + } + if (mSessionState.mSession == null || mSessionState.mClient == null) { + return; + } + try { + mSessionState.mClient.onTvAdSessionData(type, data, mSessionState.mSeq); + } catch (RemoteException e) { + Slogf.e(TAG, "error in onTvAdSessionData", e); + } + } + } + @GuardedBy("mLock") private boolean addAdSessionTokenToClientStateLocked(ITvAdSession session) { try { |