/* * Copyright 2021 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.interactive; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.RectF; import android.media.PlaybackParams; import android.media.tv.TvInputManager; import android.media.tv.TvRecordingInfo; import android.media.tv.TvTrackInfo; import android.media.tv.TvView; import android.media.tv.flags.Flags; import android.media.tv.interactive.TvInteractiveAppManager.Session; import android.media.tv.interactive.TvInteractiveAppManager.Session.FinishedInputEventCallback; import android.media.tv.interactive.TvInteractiveAppManager.SessionCallback; import android.net.Uri; import android.net.http.SslCertificate; 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.security.KeyStore; import java.util.Arrays; import java.util.List; import java.util.concurrent.Executor; /** * Displays contents of interactive TV applications. */ public class TvInteractiveAppView extends ViewGroup { private static final String TAG = "TvInteractiveAppView"; private static final boolean DEBUG = false; private static final int SET_TVVIEW_SUCCESS = 1; private static final int SET_TVVIEW_FAIL = 2; private static final int UNSET_TVVIEW_SUCCESS = 3; private static final int UNSET_TVVIEW_FAIL = 4; /** * Used to share client {@link java.security.cert.Certificate} with * {@link TvInteractiveAppService}. * @see #createBiInteractiveApp(Uri, Bundle) * @see java.security.cert.Certificate */ public static final String BI_INTERACTIVE_APP_KEY_CERTIFICATE = "certificate"; /** * Used to share the {@link KeyStore} alias with {@link TvInteractiveAppService}. * @see #createBiInteractiveApp(Uri, Bundle) * @see KeyStore#aliases() */ public static final String BI_INTERACTIVE_APP_KEY_ALIAS = "alias"; /** * Used to share the {@link java.security.PrivateKey} with {@link TvInteractiveAppService}. *

The private key is optional. It is used to encrypt data when necessary. * * @see #createBiInteractiveApp(Uri, Bundle) * @see java.security.PrivateKey */ public static final String BI_INTERACTIVE_APP_KEY_PRIVATE_KEY = "private_key"; /** * Additional HTTP headers to be used by {@link TvInteractiveAppService} to load the * broadcast-independent interactive application. * @see #createBiInteractiveApp(Uri, Bundle) */ public static final String BI_INTERACTIVE_APP_KEY_HTTP_ADDITIONAL_HEADERS = "http_additional_headers"; /** * HTTP user agent to be used by {@link TvInteractiveAppService} for broadcast-independent * interactive application. * @see #createBiInteractiveApp(Uri, Bundle) */ public static final String BI_INTERACTIVE_APP_KEY_HTTP_USER_AGENT = "http_user_agent"; /** * The name of the method where the error happened, if applicable. For example, if there is an * error during signing, the request name is "onRequestSigning". * @see #notifyError(String, Bundle) */ public static final String ERROR_KEY_METHOD_NAME = "method_name"; private final TvInteractiveAppManager mTvInteractiveAppManager; private final Handler mHandler = new Handler(); private final Object mCallbackLock = new Object(); private Session mSession; private MySessionCallback mSessionCallback; private TvInteractiveAppCallback mCallback; private Executor mCallbackExecutor; private SurfaceView mSurfaceView; private Surface mSurface; private boolean mSurfaceChanged; private int mSurfaceFormat; private int mSurfaceWidth; private int mSurfaceHeight; private boolean mUseRequestedSurfaceLayout; private int mSurfaceViewLeft; private int mSurfaceViewRight; private int mSurfaceViewTop; private int mSurfaceViewBottom; private boolean mMediaViewCreated; private Rect mMediaViewFrame; private final AttributeSet mAttrs; private final int mDefStyleAttr; private final XmlResourceParser mParser; private OnUnhandledInputEventListener mOnUnhandledInputEventListener; private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() { @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { if (DEBUG) { Log.d(TAG, "surfaceChanged(holder=" + holder + ", format=" + format + ", width=" + width + ", height=" + height + ")"); } mSurfaceFormat = format; mSurfaceWidth = width; mSurfaceHeight = height; mSurfaceChanged = true; dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight); } @Override public void surfaceCreated(SurfaceHolder holder) { mSurface = holder.getSurface(); setSessionSurface(mSurface); } @Override public void surfaceDestroyed(SurfaceHolder holder) { mSurface = null; mSurfaceChanged = false; setSessionSurface(null); } }; public TvInteractiveAppView(@NonNull Context context) { this(context, null, 0); } public TvInteractiveAppView(@NonNull Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public TvInteractiveAppView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); int sourceResId = Resources.getAttributeSetSourceResId(attrs); if (sourceResId != Resources.ID_NULL) { Log.d(TAG, "Build local AttributeSet"); mParser = context.getResources().getXml(sourceResId); mAttrs = Xml.asAttributeSet(mParser); } else { Log.d(TAG, "Use passed in AttributeSet"); mParser = null; mAttrs = attrs; } mDefStyleAttr = defStyleAttr; resetSurfaceView(); mTvInteractiveAppManager = (TvInteractiveAppManager) getContext().getSystemService( Context.TV_INTERACTIVE_APP_SERVICE); } /** * Sets the callback to be invoked when an event is dispatched to this TvInteractiveAppView. * * @param callback the callback to receive events. MUST NOT be {@code null}. * * @see #clearCallback() */ public void setCallback( @NonNull @CallbackExecutor Executor executor, @NonNull TvInteractiveAppCallback callback) { com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, callback); synchronized (mCallbackLock) { mCallbackExecutor = executor; mCallback = callback; } } /** * Clears the callback. * * @see #setCallback(Executor, TvInteractiveAppCallback) */ public void clearCallback() { synchronized (mCallbackLock) { mCallback = null; mCallbackExecutor = null; } } @Override public void onAttachedToWindow() { super.onAttachedToWindow(); createSessionMediaView(); } @Override public void onDetachedFromWindow() { removeSessionMediaView(); super.onDetachedFromWindow(); } @Override public void onLayout(boolean changed, int left, int top, int right, int bottom) { if (DEBUG) { Log.d(TAG, "onLayout (left=" + left + ", top=" + top + ", right=" + right + ", bottom=" + bottom + ",)"); } if (mUseRequestedSurfaceLayout) { mSurfaceView.layout(mSurfaceViewLeft, mSurfaceViewTop, mSurfaceViewRight, mSurfaceViewBottom); } else { mSurfaceView.layout(0, 0, right - left, bottom - top); } } @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { mSurfaceView.measure(widthMeasureSpec, heightMeasureSpec); int width = mSurfaceView.getMeasuredWidth(); int height = mSurfaceView.getMeasuredHeight(); int childState = mSurfaceView.getMeasuredState(); setMeasuredDimension(resolveSizeAndState(width, widthMeasureSpec, childState), resolveSizeAndState(height, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT)); } @Override public void onVisibilityChanged(@NonNull View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); mSurfaceView.setVisibility(visibility); if (visibility == View.VISIBLE) { createSessionMediaView(); } else { removeSessionMediaView(); } } private void resetSurfaceView() { if (mSurfaceView != null) { mSurfaceView.getHolder().removeCallback(mSurfaceHolderCallback); removeView(mSurfaceView); } mSurface = null; mSurfaceView = new SurfaceView(getContext(), mAttrs, mDefStyleAttr) { @Override protected void updateSurface() { super.updateSurface(); relayoutSessionMediaView(); }}; // The surface view's content should be treated as secure all the time. mSurfaceView.setSecure(true); mSurfaceView.getHolder().addCallback(mSurfaceHolderCallback); mSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT); mSurfaceView.setZOrderOnTop(false); mSurfaceView.setZOrderMediaOverlay(true); addView(mSurfaceView); } /** * Resets this TvInteractiveAppView to release its resources. * *

It can be reused by call {@link #prepareInteractiveApp(String, int)}. */ public void reset() { if (DEBUG) Log.d(TAG, "reset()"); resetInternal(); } private void createSessionMediaView() { // TODO: handle z-order if (mSession == null || !isAttachedToWindow() || mMediaViewCreated) { return; } mMediaViewFrame = getViewFrameOnScreen(); mSession.createMediaView(this, mMediaViewFrame); mMediaViewCreated = true; } private void removeSessionMediaView() { if (mSession == null || !mMediaViewCreated) { return; } mSession.removeMediaView(); mMediaViewCreated = false; mMediaViewFrame = null; } private void relayoutSessionMediaView() { if (mSession == null || !isAttachedToWindow() || !mMediaViewCreated) { return; } Rect viewFrame = getViewFrameOnScreen(); if (viewFrame.equals(mMediaViewFrame)) { return; } mSession.relayoutMediaView(viewFrame); mMediaViewFrame = viewFrame; } private Rect getViewFrameOnScreen() { Rect frame = new Rect(); getGlobalVisibleRect(frame); RectF frameF = new RectF(frame); getMatrix().mapRect(frameF); frameF.round(frame); return frame; } private void setSessionSurface(Surface surface) { if (mSession == null) { return; } mSession.setSurface(surface); } private void dispatchSurfaceChanged(int format, int width, int height) { if (mSession == null) { return; } 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. */ 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 * TvInteractiveAppView. * * @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 Interactive App. * * @param listener The callback to be invoked when the unhandled input event is received. */ public void setOnUnhandledInputEventListener( @NonNull @CallbackExecutor Executor executor, @NonNull OnUnhandledInputEventListener listener) { mOnUnhandledInputEventListener = listener; // TODO: handle CallbackExecutor } /** * Gets the {@link OnUnhandledInputEventListener}. *

Returns {@code null} if the listener is not set or is cleared. * * @see #setOnUnhandledInputEventListener(Executor, OnUnhandledInputEventListener) * @see #clearOnUnhandledInputEventListener() */ @Nullable public OnUnhandledInputEventListener getOnUnhandledInputEventListener() { return mOnUnhandledInputEventListener; } /** * Clears the {@link OnUnhandledInputEventListener}. */ 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 != Session.DISPATCH_NOT_HANDLED; } /** * Prepares the interactive application runtime environment of corresponding * {@link TvInteractiveAppService}. * * @param iAppServiceId the interactive app service ID, which can be found in * {@link TvInteractiveAppServiceInfo#getId()}. * * @see android.media.tv.interactive.TvInteractiveAppManager#getTvInteractiveAppServiceList() */ public void prepareInteractiveApp( @NonNull String iAppServiceId, @TvInteractiveAppServiceInfo.InteractiveAppType int type) { // TODO: document and handle the cases that this method is called multiple times. if (DEBUG) { Log.d(TAG, "prepareInteractiveApp"); } mSessionCallback = new MySessionCallback(iAppServiceId, type); if (mTvInteractiveAppManager != null) { mTvInteractiveAppManager.createSession(iAppServiceId, type, mSessionCallback, mHandler); } } /** * Starts the interactive application. */ public void startInteractiveApp() { if (DEBUG) { Log.d(TAG, "startInteractiveApp"); } if (mSession != null) { mSession.startInteractiveApp(); } } /** * Stops the interactive application. */ public void stopInteractiveApp() { if (DEBUG) { Log.d(TAG, "stopInteractiveApp"); } if (mSession != null) { mSession.stopInteractiveApp(); } } /** * Resets the interactive application. * *

This releases the resources of the corresponding {@link TvInteractiveAppService.Session}. */ public void resetInteractiveApp() { if (DEBUG) { Log.d(TAG, "resetInteractiveApp"); } if (mSession != null) { mSession.resetInteractiveApp(); } } /** * Sends current video bounds to related TV interactive app. * * @param bounds the rectangle area for rendering the current video. */ public void sendCurrentVideoBounds(@NonNull Rect bounds) { if (DEBUG) { Log.d(TAG, "sendCurrentVideoBounds"); } if (mSession != null) { mSession.sendCurrentVideoBounds(bounds); } } /** * Sends current channel URI to related TV interactive app. * * @param channelUri The current channel URI; {@code null} if there is no currently tuned * channel. */ public void sendCurrentChannelUri(@Nullable Uri channelUri) { if (DEBUG) { Log.d(TAG, "sendCurrentChannelUri"); } if (mSession != null) { mSession.sendCurrentChannelUri(channelUri); } } /** * Sends current channel logical channel number (LCN) to related TV interactive app. */ public void sendCurrentChannelLcn(int lcn) { if (DEBUG) { Log.d(TAG, "sendCurrentChannelLcn"); } if (mSession != null) { mSession.sendCurrentChannelLcn(lcn); } } /** * Sends stream volume to related TV interactive app. * * @param volume a volume value between {@code 0.0f} and {@code 1.0f}, inclusive. */ public void sendStreamVolume(float volume) { if (DEBUG) { Log.d(TAG, "sendStreamVolume"); } if (mSession != null) { mSession.sendStreamVolume(volume); } } /** * Sends track info list to related TV interactive app. */ public void sendTrackInfoList(@Nullable List tracks) { if (DEBUG) { Log.d(TAG, "sendTrackInfoList"); } if (mSession != null) { mSession.sendTrackInfoList(tracks); } } /** * Sends the currently selected track info to the TV Interactive App in response to a * {@link TvInteractiveAppCallback#onRequestSelectedTrackInfo(String)} request. * * @param tracks list of {@link TvTrackInfo} of the currently selected track(s) */ @FlaggedApi(Flags.FLAG_TIAF_V_APIS) public void sendSelectedTrackInfo(@Nullable List tracks) { if (DEBUG) { Log.d(TAG, "sendSelectedTrackInfo"); } if (mSession != null) { mSession.sendSelectedTrackInfo(tracks); } } /** * Sends current TV input ID to related TV interactive app. * * @param inputId The current TV input ID whose channel is tuned. {@code null} if no channel is * tuned. * @see android.media.tv.TvInputInfo */ public void sendCurrentTvInputId(@Nullable String inputId) { if (DEBUG) { Log.d(TAG, "sendCurrentTvInputId"); } if (mSession != null) { mSession.sendCurrentTvInputId(inputId); } } /** * Sends the current time shift mode to the TV interactive app bound to this view * * @param mode The current time shift mode. The value is one of the following: * {@link TvInputManager#TIME_SHIFT_MODE_OFF}, {@link TvInputManager#TIME_SHIFT_MODE_LOCAL}, * {@link TvInputManager#TIME_SHIFT_MODE_NETWORK}, * {@link TvInputManager#TIME_SHIFT_MODE_AUTO}. */ public void sendTimeShiftMode(@android.media.tv.TvInputManager.TimeShiftMode int mode) { if (DEBUG) { Log.d(TAG, "sendTimeShiftMode"); } if (mSession != null) { mSession.sendTimeShiftMode(mode); } } /** * Sends the available supported playback speeds to the TV interactive app bound to this view. * * @param speeds An ordered array of playback speeds, expressed as values relative to the * normal playback speed (1.0), at which the current content can be played as * a time-shifted broadcast. This is an empty array if the supported playback * speeds are unknown or the video/broadcast is not in time shift mode. If * currently in time shift mode, this array will normally include at least * the values 1.0 (normal speed) and 0.0 (paused). * @see PlaybackParams#getSpeed() */ public void sendAvailableSpeeds(@NonNull float[] speeds) { if (DEBUG) { Log.d(TAG, "sendAvailableSpeeds"); } if (mSession != null) { Arrays.sort(speeds); mSession.sendAvailableSpeeds(speeds); } } /** * Sends the requested {@link android.media.tv.TvRecordingInfo}. * * @see TvInteractiveAppService.Session#requestTvRecordingInfo(String) * @param recordingInfo The recording info requested. {@code null} if no recording found. */ public void sendTvRecordingInfo(@Nullable TvRecordingInfo recordingInfo) { if (DEBUG) { Log.d(TAG, "sendTvRecordingInfo"); } if (mSession != null) { mSession.sendTvRecordingInfo(recordingInfo); } } /** * Sends the requested {@link android.media.tv.TvRecordingInfo}. * * @see TvInteractiveAppService.Session#requestTvRecordingInfoList(int) * @param recordingInfoList The list of recording info requested. Returns an empty list if no * matching recording info found. */ public void sendTvRecordingInfoList(@NonNull List recordingInfoList) { if (DEBUG) { Log.d(TAG, "sendTvRecordingInfoList"); } if (mSession != null) { mSession.sendTvRecordingInfoList(recordingInfoList); } } /** * Alerts the related TV interactive app service that a recording has been started. * * @param recordingId The ID of the recording started. This ID is created and maintained by the * TV app and is used to identify the recording in the future. * * @param requestId The ID of the request when * {@link TvInteractiveAppService.Session#requestStartRecording(String, Uri)} * is called. {@code null} if the recording is not triggered by a request. * This ID should be created by the {@link TvInteractiveAppService} and * can be any string. * @see TvInteractiveAppView#notifyRecordingStopped(String) */ public void notifyRecordingStarted(@NonNull String recordingId, @Nullable String requestId) { if (DEBUG) { Log.d(TAG, "notifyRecordingStarted"); } if (mSession != null) { mSession.notifyRecordingStarted(recordingId, requestId); } } /** * Alerts the TV interactive app that a recording has been stopped. * * @param recordingId The ID of the recording stopped. This ID is created and maintained * by the TV app when a recording is started. * @see TvInteractiveAppView#notifyRecordingStarted(String, String) */ public void notifyRecordingStopped(@NonNull String recordingId) { if (DEBUG) { Log.d(TAG, "notifyRecordingStopped"); } if (mSession != null) { mSession.notifyRecordingStopped(recordingId); } } /** * Alerts the TV Interactive app that the video freeze state has been updated. If {@code true}, * the video is frozen on the last frame while audio playback continues. * * @param isFrozen Whether the video is frozen. */ @FlaggedApi(Flags.FLAG_TIAF_V_APIS) public void notifyVideoFreezeUpdated(boolean isFrozen) { if (DEBUG) { Log.d(TAG, "notifyVideoFreezeUpdated"); } if (mSession != null) { mSession.notifyVideoFreezeUpdated(isFrozen); } } /** * Sends signing result to related TV interactive app. * *

This is used when the corresponding server of the broadcast-independent interactive * app requires signing during handshaking, and the interactive app 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. It's the same as the corresponding ID in * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, byte[])} * @param result the signed result. */ public void sendSigningResult(@NonNull String signingId, @NonNull byte[] result) { if (DEBUG) { Log.d(TAG, "sendSigningResult"); } if (mSession != null) { mSession.sendSigningResult(signingId, result); } } /** * Sends the requested SSL certificate to the TV Interactive App * @param host the host name of the SSL authentication server. * @param port the port of the SSL authentication server. E.g., 443 * @param cert the SSL certificate requested */ @FlaggedApi(Flags.FLAG_TIAF_V_APIS) public void sendCertificate(@NonNull String host, int port, @NonNull SslCertificate cert) { if (DEBUG) { Log.d(TAG, "sendCertificate"); } if (mSession != null) { mSession.sendCertificate(host, port, cert); } } /** * Notifies the corresponding {@link TvInteractiveAppService} when there is an error. * * @param errMsg the message of the error. * @param params additional parameters of the error. For example, the signingId of {@link * TvInteractiveAppCallback#onRequestSigning(String, String, String, String, byte[])} can be * included to identify the related signing request, and the method name "onRequestSigning" * can also be added to the params. * * @see #ERROR_KEY_METHOD_NAME */ public void notifyError(@NonNull String errMsg, @NonNull Bundle params) { if (DEBUG) { Log.d(TAG, "notifyError msg=" + errMsg + "; params=" + params); } if (mSession != null) { mSession.notifyError(errMsg, params); } } /** * Notifies the corresponding {@link TvInteractiveAppService} when a time shift * {@link android.media.PlaybackParams} is set or changed. * * @see TvView#timeShiftSetPlaybackParams(PlaybackParams) * @param params The new {@link PlaybackParams} that was set or changed. */ public void notifyTimeShiftPlaybackParams(@NonNull PlaybackParams params) { if (DEBUG) { Log.d(TAG, "notifyTimeShiftPlaybackParams params=" + params); } if (mSession != null) { mSession.notifyTimeShiftPlaybackParams(params); } } /** * Notifies the corresponding {@link TvInteractiveAppService} when time shift * status is changed. * * @see TvView.TvInputCallback#onTimeShiftStatusChanged(String, int) * @see android.media.tv.TvInputService.Session#notifyTimeShiftStatusChanged(int) * @param inputId The ID of the input for which the time shift status has changed. * @param status The status of which the input has changed to. Should be one of the * following. *

*/ public void notifyTimeShiftStatusChanged( @NonNull String inputId, @TvInputManager.TimeShiftStatus int status) { if (DEBUG) { Log.d(TAG, "notifyTimeShiftStatusChanged inputId=" + inputId + "; status=" + status); } if (mSession != null) { mSession.notifyTimeShiftStatusChanged(inputId, status); } } /** * Notifies the corresponding {@link TvInteractiveAppService} when time shift * start position is changed. * * @see TvView.TimeShiftPositionCallback#onTimeShiftStartPositionChanged(String, long) * @param inputId The ID of the input for which the time shift start position has changed. * @param timeMs The start position for time shifting, in milliseconds since the epoch. */ public void notifyTimeShiftStartPositionChanged(@NonNull String inputId, long timeMs) { if (DEBUG) { Log.d(TAG, "notifyTimeShiftStartPositionChanged inputId=" + inputId + "; timeMs=" + timeMs); } if (mSession != null) { mSession.notifyTimeShiftStartPositionChanged(inputId, timeMs); } } /** * Notifies the corresponding {@link TvInteractiveAppService} when time shift * current position is changed. * * @see TvView.TimeShiftPositionCallback#onTimeShiftCurrentPositionChanged(String, long) * @param inputId The ID of the input for which the time shift current position has changed. * @param timeMs The current position for time shifting, in milliseconds since the epoch. */ public void notifyTimeShiftCurrentPositionChanged(@NonNull String inputId, long timeMs) { if (DEBUG) { Log.d(TAG, "notifyTimeShiftCurrentPositionChanged inputId=" + inputId + "; timeMs=" + timeMs); } if (mSession != null) { mSession.notifyTimeShiftCurrentPositionChanged(inputId, timeMs); } } /** * This is called to notify the corresponding interactive app service when an error occurred * while establishing a connection to the recording session for the corresponding TV input. * * @param recordingId The ID of the related recording which is sent via * {@link #notifyRecordingStarted(String, String)} * @param inputId The ID of the TV input bound to the current TvRecordingClient. * @see android.media.tv.TvRecordingClient.RecordingCallback#onConnectionFailed(String) * @hide */ public void notifyRecordingConnectionFailed( @NonNull String recordingId, @NonNull String inputId) { if (DEBUG) { Log.d(TAG, "notifyRecordingConnectionFailed recordingId=" + recordingId + "; inputId=" + inputId); } if (mSession != null) { mSession.notifyRecordingConnectionFailed(recordingId, inputId); } } /** * This is called to notify the corresponding interactive app service when the connection to * the current recording session is lost. * * @param recordingId The ID of the related recording which is sent via * {@link #notifyRecordingStarted(String, String)} * @param inputId The ID of the TV input bound to the current TvRecordingClient. * @see android.media.tv.TvRecordingClient.RecordingCallback#onDisconnected(String) * @hide */ public void notifyRecordingDisconnected( @NonNull String recordingId, @NonNull String inputId) { if (DEBUG) { Log.d(TAG, "notifyRecordingDisconnected recordingId=" + recordingId + "; inputId=" + inputId); } if (mSession != null) { mSession.notifyRecordingDisconnected(recordingId, inputId); } } /** * This is called to notify the corresponding interactive app service when the recording session * has been tuned to the given channel and is ready to start recording. * * @param recordingId The ID of the related recording which is sent via * {@link #notifyRecordingStarted(String, String)} * @param channelUri The URI of the tuned channel. * @see android.media.tv.TvRecordingClient.RecordingCallback#onTuned(Uri) * @hide */ public void notifyRecordingTuned( @NonNull String recordingId, @NonNull Uri channelUri) { if (DEBUG) { Log.d(TAG, "notifyRecordingTuned recordingId=" + recordingId + "; channelUri=" + channelUri); } if (mSession != null) { mSession.notifyRecordingTuned(recordingId, channelUri); } } /** * This is called to notify the corresponding interactive app service when an issue has * occurred. It may be called at any time after the current recording session is created until * it is released. * * @param recordingId The ID of the related recording which is sent via * {@link #notifyRecordingStarted(String, String)} * @param err The error code. Should be one of the following. * * @see android.media.tv.TvRecordingClient.RecordingCallback#onError(int) * @hide */ public void notifyRecordingError( @NonNull String recordingId, @TvInputManager.RecordingError int err) { if (DEBUG) { Log.d(TAG, "notifyRecordingError recordingId=" + recordingId + "; err=" + err); } if (mSession != null) { mSession.notifyRecordingError(recordingId, err); } } /** * This is called to notify the corresponding interactive app service when the recording has * been scheduled. * * @param recordingId The ID assigned to this recording by the app. It can be used to send * recording related requests such as * {@link TvInteractiveAppService.Session#requestStopRecording(String)}. * @param requestId The ID of the request when * {@link TvInteractiveAppService.Session#requestScheduleRecording} is called. * {@code null} if the recording is not triggered by a request. * This ID should be created by the {@link TvInteractiveAppService} and * can be any string. */ public void notifyRecordingScheduled( @NonNull String recordingId, @Nullable String requestId) { if (DEBUG) { Log.d(TAG, "notifyRecordingScheduled recordingId=" + recordingId + "; requestId=" + requestId); } if (mSession != null) { mSession.notifyRecordingScheduled(recordingId, requestId); } } /** * This is called to notify the corresponding interactive app service when a new TV message * is received. * * @param type The type of message received, such as * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK} * @param data The raw data of the message. The bundle keys are: * {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID}, * {@link TvInputManager#TV_MESSAGE_KEY_GROUP_ID}, * {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE}, * {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}. * See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on * how to parse this data. */ public void notifyTvMessage(@NonNull @TvInputManager.TvMessageType int type, @NonNull Bundle data) { if (DEBUG) { Log.d(TAG, "notifyTvMessage type=" + type + "; data=" + data); } if (mSession != null) { mSession.notifyTvMessage(type, data); } } private void resetInternal() { mSessionCallback = null; if (mSession != null) { setSessionSurface(null); removeSessionMediaView(); mUseRequestedSurfaceLayout = false; mSession.release(); mSession = null; resetSurfaceView(); } } /** * Creates broadcast-independent(BI) interactive application. * *

{@link TvInteractiveAppCallback#onBiInteractiveAppCreated(String, Uri, String)} will be * called for the result. * * @param biIAppUri URI associated this BI interactive app. * @param params optional parameters for broadcast-independent interactive application, such as * {@link #BI_INTERACTIVE_APP_KEY_CERTIFICATE}. * * @see TvInteractiveAppCallback#onBiInteractiveAppCreated(String, Uri, String) * @see #BI_INTERACTIVE_APP_KEY_CERTIFICATE * @see #BI_INTERACTIVE_APP_KEY_HTTP_ADDITIONAL_HEADERS * @see #BI_INTERACTIVE_APP_KEY_HTTP_USER_AGENT */ public void createBiInteractiveApp(@NonNull Uri biIAppUri, @Nullable Bundle params) { if (DEBUG) { Log.d(TAG, "createBiInteractiveApp Uri=" + biIAppUri + ", params=" + params); } if (mSession != null) { mSession.createBiInteractiveApp(biIAppUri, params); } } /** * Destroys broadcast-independent(BI) interactive application. * * @param biIAppId the BI interactive app ID from {@link #createBiInteractiveApp(Uri, Bundle)} * * @see #createBiInteractiveApp(Uri, Bundle) */ public void destroyBiInteractiveApp(@NonNull String biIAppId) { if (DEBUG) { Log.d(TAG, "destroyBiInteractiveApp biIAppId=" + biIAppId); } if (mSession != null) { mSession.destroyBiInteractiveApp(biIAppId); } } /** @hide */ public Session getInteractiveAppSession() { return mSession; } /** * Sets the TvInteractiveAppView to receive events from TIS. This method links the session of * TvInteractiveAppManager to TvInputManager session, so the TIAS can get the TIS events. * * @param tvView the TvView to be linked to this TvInteractiveAppView via linking of Sessions. * @return The result of the operation. */ public int setTvView(@Nullable TvView tvView) { if (tvView == null) { return unsetTvView(); } TvInputManager.Session inputSession = tvView.getInputSession(); if (inputSession == null || mSession == null) { return SET_TVVIEW_FAIL; } mSession.setInputSession(inputSession); inputSession.setInteractiveAppSession(mSession); return SET_TVVIEW_SUCCESS; } private int unsetTvView() { if (mSession == null || mSession.getInputSession() == null) { return UNSET_TVVIEW_FAIL; } mSession.getInputSession().setInteractiveAppSession(null); mSession.setInputSession(null); return UNSET_TVVIEW_SUCCESS; } /** * To toggle Digital Teletext Application if there is one in AIT app list. * *

A Teletext Application is a broadcast-related application to display text and basic * graphics. * * @param enable {@code true} to enable Teletext app; {@code false} to disable it. */ public void setTeletextAppEnabled(boolean enable) { if (DEBUG) { Log.d(TAG, "setTeletextAppEnabled enable=" + enable); } if (mSession != null) { mSession.setTeletextAppEnabled(enable); } } /** * Callback used to receive various status updates on the {@link TvInteractiveAppView}. */ public abstract static class TvInteractiveAppCallback { // TODO: unhide the following public APIs /** * This is called when a playback command is requested to be processed by the related TV * input. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. * @param cmdType type of the command * @param parameters parameters of the command */ public void onPlaybackCommandRequest( @NonNull String iAppServiceId, @NonNull @TvInteractiveAppService.PlaybackCommandType String cmdType, @NonNull Bundle parameters) { } /** * This is called when a time shift command is requested to be processed by the related TV * input. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. * @param cmdType type of the command * @param parameters parameters of the command */ public void onTimeShiftCommandRequest( @NonNull String iAppServiceId, @NonNull @TvInteractiveAppService.TimeShiftCommandType String cmdType, @NonNull Bundle parameters) { } /** * This is called when the state of corresponding interactive app is changed. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. * @param state the current state. * @param err the error code for error state. {@link TvInteractiveAppManager#ERROR_NONE} * is used when the state is not * {@link TvInteractiveAppManager#INTERACTIVE_APP_STATE_ERROR}. */ public void onStateChanged( @NonNull String iAppServiceId, @TvInteractiveAppManager.InteractiveAppState int state, @TvInteractiveAppManager.ErrorCode int err) { } /** * This is called when broadcast-independent (BI) interactive app is created. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. * @param biIAppUri URI associated this BI interactive app. This is the same URI in * {@link #createBiInteractiveApp(Uri, Bundle)} * @param biIAppId BI interactive app ID, which can be used to destroy the BI interactive * app. {@code null} if it's not created successfully. * * @see #createBiInteractiveApp(Uri, Bundle) * @see #destroyBiInteractiveApp(String) */ public void onBiInteractiveAppCreated(@NonNull String iAppServiceId, @NonNull Uri biIAppUri, @Nullable String biIAppId) { } /** * This is called when the digital teletext app state is changed. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. * @param state digital teletext app current state. */ public void onTeletextAppStateChanged( @NonNull String iAppServiceId, @TvInteractiveAppManager.TeletextAppState int state) { } /** * This is called when {@link TvInteractiveAppService.Session#setVideoBounds(Rect)} is * called. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. */ public void onSetVideoBounds(@NonNull String iAppServiceId, @NonNull Rect rect) { } /** * This is called when {@link TvInteractiveAppService.Session#requestCurrentVideoBounds()} * is called. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. */ public void onRequestCurrentVideoBounds(@NonNull String iAppServiceId) { } /** * This is called when {@link TvInteractiveAppService.Session#requestCurrentChannelUri()} is * called. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. */ public void onRequestCurrentChannelUri(@NonNull String iAppServiceId) { } /** * This is called when {@link TvInteractiveAppService.Session#requestCurrentChannelLcn()} is * called. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. */ public void onRequestCurrentChannelLcn(@NonNull String iAppServiceId) { } /** * This is called when {@link TvInteractiveAppService.Session#requestStreamVolume()} is * called. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. */ public void onRequestStreamVolume(@NonNull String iAppServiceId) { } /** * This is called when {@link TvInteractiveAppService.Session#requestTrackInfoList()} is * called. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. */ public void onRequestTrackInfoList(@NonNull String iAppServiceId) { } /** * This is called when {@link TvInteractiveAppService.Session#requestSelectedTrackInfo()} is * called. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. */ @FlaggedApi(Flags.FLAG_TIAF_V_APIS) public void onRequestSelectedTrackInfo(@NonNull String iAppServiceId) { } /** * This is called when {@link TvInteractiveAppService.Session#requestCurrentTvInputId()} is * called. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. */ public void onRequestCurrentTvInputId(@NonNull String iAppServiceId) { } /** * This is called when {@link TvInteractiveAppService.Session#requestTimeShiftMode()} is * called. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. */ public void onRequestTimeShiftMode(@NonNull String iAppServiceId) { } /** * This is called when {@link TvInteractiveAppService.Session#requestAvailableSpeeds()} is * called. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. */ public void onRequestAvailableSpeeds(@NonNull String iAppServiceId) { } /** * This is called when * {@link TvInteractiveAppService.Session#requestStartRecording(String, Uri)} is called. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. * @param requestId The ID of this request which is used to match the corresponding * response. The request ID in * {@link #notifyRecordingStarted(String, String)} for this request is the * same as the ID sent here. This should be defined by the * TIAS and can be any string. Should this API be called with the * same requestId twice, both requests should be handled regardless * by the TV application. * @param programUri The URI of the program to record * */ public void onRequestStartRecording(@NonNull String iAppServiceId, @NonNull String requestId, @Nullable Uri programUri) { } /** * This is called when {@link TvInteractiveAppService.Session#requestStopRecording(String)} * is called. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. * @param recordingId The ID of the recording to stop. This is provided by the TV app in * {@link #notifyRecordingStarted(String, String)} * @see #notifyRecordingStarted(String, String) * @see #notifyRecordingStopped(String) */ public void onRequestStopRecording( @NonNull String iAppServiceId, @NonNull String recordingId) { } /** * This is called when * {@link TvInteractiveAppService.Session#requestScheduleRecording(String, String, Uri, Uri, Bundle)} * is called. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. * @param requestId The ID of this request which is used to match the corresponding * response. The request ID in * {@link #notifyRecordingScheduled(String, String)} for this request is * the same as the ID sent here. This should be defined by the * TIAS and can be any string. Should this API be called with the * same requestId twice, both requests should be handled regardless * by the TV application. * @param inputId The ID of the TV input for the given channel. * @param channelUri The URI of a channel to be recorded. * @param programUri The URI of the TV program to be recorded. * @param params Domain-specific data for this tune request. Keys must be a scoped * name, i.e. prefixed with a package name you own, so that different developers * will not create conflicting keys. * @see android.media.tv.TvRecordingClient#tune(String, Uri, Bundle) * @see android.media.tv.TvRecordingClient#startRecording(Uri) */ public void onRequestScheduleRecording(@NonNull String iAppServiceId, @NonNull String requestId, @NonNull String inputId, @NonNull Uri channelUri, @NonNull Uri programUri, @NonNull Bundle params) { } /** * This is called when * {@link TvInteractiveAppService.Session#requestScheduleRecording(String, String, Uri, long, long, int, Bundle)} * is called. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. * @param requestId The ID of this request which is used to match the corresponding * response. The request ID in * {@link #notifyRecordingScheduled(String, String)} for this request is * the same as the ID sent here. This should be defined by the * TIAS and can be any string. Should this API be called with the * same requestId twice, both requests should be handled regardless * by the TV application. * @param inputId The ID of the TV input for the given channel. * @param channelUri The URI of a channel to be recorded. * @param startTime The start time of the recording in milliseconds since epoch. * @param duration The duration of the recording in milliseconds. * @param repeatDays The repeated days. 0 if not repeated. * @param params Domain-specific data for this tune request. Keys must be a scoped * name, i.e. prefixed with a package name you own, so that different developers * will not create conflicting keys. * @see android.media.tv.TvRecordingClient#tune(String, Uri, Bundle) * @see android.media.tv.TvRecordingClient#startRecording(Uri) */ public void onRequestScheduleRecording(@NonNull String iAppServiceId, @NonNull String requestId, @NonNull String inputId, @NonNull Uri channelUri, long startTime, long duration, int repeatDays, @NonNull Bundle params) { } /** * This is called when * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, byte[])} is * called. * * @param iAppServiceId The ID of the TV interactive app 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 iAppServiceId, @NonNull String signingId, @NonNull String algorithm, @NonNull String alias, @NonNull byte[] data) { } /** * This is called when * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, int, byte[])} * is called. * * @param iAppServiceId The ID of the TV interactive app 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 host The hostname of the SSL authentication server. * @param port The port of the SSL authentication server. * @param data the original bytes to be signed. */ @FlaggedApi(Flags.FLAG_TIAF_V_APIS) public void onRequestSigning(@NonNull String iAppServiceId, @NonNull String signingId, @NonNull String algorithm, @NonNull String host, int port, @NonNull byte[] data) { } /** * This is called when * {@link TvInteractiveAppService.Session#requestCertificate(String, int)} is called. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. * @param host The hostname of the SSL authentication server. * @param port The port of the SSL authentication server. */ @FlaggedApi(Flags.FLAG_TIAF_V_APIS) public void onRequestCertificate(@NonNull String iAppServiceId, @NonNull String host, int port) { } /** * This is called when {@link TvInteractiveAppService.Session#setTvRecordingInfo(String, * TvRecordingInfo)} is called. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. * @param recordingId The ID of the recording to set the info for. This is provided by the * TV app in {@link TvInteractiveAppView#notifyRecordingStarted(String, String)} * @param recordingInfo The {@link TvRecordingInfo} to set to the recording. */ public void onSetTvRecordingInfo( @NonNull String iAppServiceId, @NonNull String recordingId, @NonNull TvRecordingInfo recordingInfo) { } /** * This is called when * {@link TvInteractiveAppService.Session#requestTvRecordingInfo(String)} is * called. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. * @param recordingId The ID of the recording to get the info for. This is provided by the * TV app in * {@link TvInteractiveAppView#notifyRecordingStarted(String, String)} */ public void onRequestTvRecordingInfo( @NonNull String iAppServiceId, @NonNull String recordingId) { } /** * This is called when * {@link TvInteractiveAppService.Session#requestTvRecordingInfoList(int)} is * called. * * @param iAppServiceId The ID of the TV interactive app service bound to this view. * @param type The type of recording requested to retrieve. */ public void onRequestTvRecordingInfoList( @NonNull String iAppServiceId, @TvRecordingInfo.TvRecordingListType int type) { } } /** * Interface definition for a callback to be invoked when the unhandled input event is received. */ public interface OnUnhandledInputEventListener { /** * Called when an input event was not handled by the TV Interactive App. * *

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); } private class MySessionCallback extends SessionCallback { final String mIAppServiceId; int mType; MySessionCallback(String iAppServiceId, int type) { mIAppServiceId = iAppServiceId; mType = type; } @Override public void onSessionCreated(Session session) { if (DEBUG) { Log.d(TAG, "onSessionCreated()"); } if (this != mSessionCallback) { Log.w(TAG, "onSessionCreated - session already created"); // This callback is obsolete. if (session != null) { session.release(); } return; } mSession = session; if (session != null) { // mSurface may not be ready yet as soon as starting an application. // In the case, we don't send Session.setSurface(null) unnecessarily. // setSessionSurface will be called in surfaceCreated. if (mSurface != null) { setSessionSurface(mSurface); if (mSurfaceChanged) { dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight); } } createSessionMediaView(); } else { // Failed to create // Todo: forward error to Tv App mSessionCallback = null; } } @Override public void onSessionReleased(Session session) { if (DEBUG) { Log.d(TAG, "onSessionReleased()"); } if (this != mSessionCallback) { Log.w(TAG, "onSessionReleased - session not created"); return; } mMediaViewCreated = false; mMediaViewFrame = null; mSessionCallback = null; mSession = null; } @Override public void onLayoutSurface(Session session, int left, int top, int right, int bottom) { if (DEBUG) { Log.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top + ", right=" + right + ", bottom=" + bottom + ",)"); } if (this != mSessionCallback) { Log.w(TAG, "onLayoutSurface - session not created"); return; } mSurfaceViewLeft = left; mSurfaceViewTop = top; mSurfaceViewRight = right; mSurfaceViewBottom = bottom; mUseRequestedSurfaceLayout = true; requestLayout(); } @Override public void onCommandRequest( Session session, @TvInteractiveAppService.PlaybackCommandType String cmdType, Bundle parameters) { if (DEBUG) { Log.d(TAG, "onCommandRequest (cmdType=" + cmdType + ", parameters=" + parameters.toString() + ")"); } if (this != mSessionCallback) { Log.w(TAG, "onCommandRequest - session not created"); return; } synchronized (mCallbackLock) { if (mCallbackExecutor != null) { mCallbackExecutor.execute(() -> { synchronized (mCallbackLock) { if (mCallback != null) { mCallback.onPlaybackCommandRequest( mIAppServiceId, cmdType, parameters); } } }); } } } @Override public void onTimeShiftCommandRequest( Session session, @TvInteractiveAppService.TimeShiftCommandType String cmdType, Bundle parameters) { if (DEBUG) { Log.d(TAG, "onTimeShiftCommandRequest (cmdType=" + cmdType + ", parameters=" + parameters.toString() + ")"); } if (this != mSessionCallback) { Log.w(TAG, "onTimeShiftCommandRequest - session not created"); return; } synchronized (mCallbackLock) { if (mCallbackExecutor != null) { mCallbackExecutor.execute(() -> { synchronized (mCallbackLock) { if (mCallback != null) { mCallback.onTimeShiftCommandRequest( mIAppServiceId, cmdType, parameters); } } }); } } } @Override public void onSessionStateChanged( Session session, @TvInteractiveAppManager.InteractiveAppState int state, @TvInteractiveAppManager.ErrorCode int err) { if (DEBUG) { Log.d(TAG, "onSessionStateChanged (state=" + state + "; err=" + err + ")"); } if (this != mSessionCallback) { Log.w(TAG, "onSessionStateChanged - session not created"); return; } synchronized (mCallbackLock) { if (mCallbackExecutor != null) { mCallbackExecutor.execute(() -> { synchronized (mCallbackLock) { if (mCallback != null) { mCallback.onStateChanged(mIAppServiceId, state, err); } } }); } } } @Override public void onBiInteractiveAppCreated(Session session, Uri biIAppUri, String biIAppId) { if (DEBUG) { Log.d(TAG, "onBiInteractiveAppCreated (biIAppUri=" + biIAppUri + ", biIAppId=" + biIAppId + ")"); } if (this != mSessionCallback) { Log.w(TAG, "onBiInteractiveAppCreated - session not created"); return; } synchronized (mCallbackLock) { if (mCallbackExecutor != null) { mCallbackExecutor.execute(() -> { synchronized (mCallbackLock) { if (mCallback != null) { mCallback.onBiInteractiveAppCreated( mIAppServiceId, biIAppUri, biIAppId); } } }); } } } @Override public void onTeletextAppStateChanged(Session session, int state) { if (DEBUG) { Log.d(TAG, "onTeletextAppStateChanged (state=" + state + ")"); } if (this != mSessionCallback) { Log.w(TAG, "onTeletextAppStateChanged - session not created"); return; } if (mCallback != null) { mCallback.onTeletextAppStateChanged(mIAppServiceId, state); } } @Override public void onSetVideoBounds(Session session, Rect rect) { if (DEBUG) { Log.d(TAG, "onSetVideoBounds (rect=" + rect + ")"); } if (this != mSessionCallback) { Log.w(TAG, "onSetVideoBounds - session not created"); return; } synchronized (mCallbackLock) { if (mCallbackExecutor != null) { mCallbackExecutor.execute(() -> { synchronized (mCallbackLock) { if (mCallback != null) { mCallback.onSetVideoBounds(mIAppServiceId, rect); } } }); } } } @Override public void onRequestCurrentVideoBounds(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(mIAppServiceId); } } }); } } } @Override public void onRequestCurrentChannelUri(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(mIAppServiceId); } } }); } } } @Override public void onRequestCurrentChannelLcn(Session session) { if (DEBUG) { Log.d(TAG, "onRequestCurrentChannelLcn"); } if (this != mSessionCallback) { Log.w(TAG, "onRequestCurrentChannelLcn - session not created"); return; } synchronized (mCallbackLock) { if (mCallbackExecutor != null) { mCallbackExecutor.execute(() -> { synchronized (mCallbackLock) { if (mCallback != null) { mCallback.onRequestCurrentChannelLcn(mIAppServiceId); } } }); } } } @Override public void onRequestStreamVolume(Session session) { if (DEBUG) { Log.d(TAG, "onRequestStreamVolume"); } if (this != mSessionCallback) { Log.w(TAG, "onRequestStreamVolume - session not created"); return; } synchronized (mCallbackLock) { if (mCallbackExecutor != null) { mCallbackExecutor.execute(() -> { synchronized (mCallbackLock) { if (mCallback != null) { mCallback.onRequestStreamVolume(mIAppServiceId); } } }); } } } @Override public void onRequestTrackInfoList(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(mIAppServiceId); } } }); } } } @Override public void onRequestSelectedTrackInfo(Session session) { if (DEBUG) { Log.d(TAG, "onRequestSelectedTrackInfo"); } if (this != mSessionCallback) { Log.w(TAG, "onRequestSelectedTrackInfo - session not created"); return; } synchronized (mCallbackLock) { if (mCallbackExecutor != null) { mCallbackExecutor.execute(() -> { synchronized (mCallbackLock) { if (mCallback != null) { mCallback.onRequestSelectedTrackInfo(mIAppServiceId); } } }); } } } @Override public void onRequestCurrentTvInputId(Session session) { if (DEBUG) { Log.d(TAG, "onRequestCurrentTvInputId"); } if (this != mSessionCallback) { Log.w(TAG, "onRequestCurrentTvInputId - session not created"); return; } if (mCallback != null) { mCallback.onRequestCurrentTvInputId(mIAppServiceId); } } @Override public void onRequestTimeShiftMode(Session session) { if (DEBUG) { Log.d(TAG, "onRequestTimeShiftMode"); } if (this != mSessionCallback) { Log.w(TAG, "onRequestTimeShiftMode - session not created"); return; } if (mCallback != null) { mCallback.onRequestTimeShiftMode(mIAppServiceId); } } @Override public void onRequestAvailableSpeeds(Session session) { if (DEBUG) { Log.d(TAG, "onRequestAvailableSpeeds"); } if (this != mSessionCallback) { Log.w(TAG, "onRequestAvailableSpeeds - session not created"); return; } if (mCallback != null) { mCallback.onRequestAvailableSpeeds(mIAppServiceId); } } @Override public void onRequestStartRecording(Session session, String requestId, Uri programUri) { if (DEBUG) { Log.d(TAG, "onRequestStartRecording"); } if (this != mSessionCallback) { Log.w(TAG, "onRequestStartRecording - session not created"); return; } if (mCallback != null) { mCallback.onRequestStartRecording(mIAppServiceId, requestId, programUri); } } @Override public void onRequestStopRecording(Session session, String recordingId) { if (DEBUG) { Log.d(TAG, "onRequestStopRecording"); } if (this != mSessionCallback) { Log.w(TAG, "onRequestStopRecording - session not created"); return; } if (mCallback != null) { mCallback.onRequestStopRecording(mIAppServiceId, recordingId); } } @Override public void onSetTvRecordingInfo( Session session, String recordingId, TvRecordingInfo recordingInfo) { if (DEBUG) { Log.d(TAG, "onSetRecordingInfo"); } if (this != mSessionCallback) { Log.w(TAG, "onSetRecordingInfo - session not created"); return; } if (mCallback != null) { mCallback.onSetTvRecordingInfo(mIAppServiceId, recordingId, recordingInfo); } } @Override public void onRequestScheduleRecording(Session session, @NonNull String requestId, @NonNull String inputId, @NonNull Uri channelUri, Uri programUri, @NonNull Bundle params) { if (DEBUG) { Log.d(TAG, "onRequestScheduleRecording"); } if (this != mSessionCallback) { Log.w(TAG, "onRequestScheduleRecording - session not created"); return; } if (mCallback != null) { mCallback.onRequestScheduleRecording(mIAppServiceId, requestId, inputId, channelUri, programUri, params); } } public void onRequestScheduleRecording(Session session, @NonNull String requestId, @NonNull String inputId, @NonNull Uri channelUri, long startTime, long duration, int repeatDays, @NonNull Bundle params) { if (DEBUG) { Log.d(TAG, "onRequestScheduleRecording"); } if (this != mSessionCallback) { Log.w(TAG, "onRequestScheduleRecording - session not created"); return; } if (mCallback != null) { mCallback.onRequestScheduleRecording(mIAppServiceId, requestId, inputId, channelUri, startTime, duration, repeatDays, params); } } @Override public void onRequestTvRecordingInfo(Session session, String recordingId) { if (DEBUG) { Log.d(TAG, "onRequestRecordingInfo"); } if (this != mSessionCallback) { Log.w(TAG, "onRequestRecordingInfo - session not created"); return; } if (mCallback != null) { mCallback.onRequestTvRecordingInfo(mIAppServiceId, recordingId); } } @Override public void onRequestTvRecordingInfoList(Session session, int type) { if (DEBUG) { Log.d(TAG, "onRequestRecordingInfoList"); } if (this != mSessionCallback) { Log.w(TAG, "onRequestRecordingInfoList - session not created"); return; } if (mCallback != null) { mCallback.onRequestTvRecordingInfoList(mIAppServiceId, type); } } @Override public void onRequestSigning( 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; } if (mCallback != null) { mCallback.onRequestSigning(mIAppServiceId, id, algorithm, alias, data); } } @Override public void onRequestSigning( Session session, String id, String algorithm, String host, int port, byte[] data) { if (DEBUG) { Log.d(TAG, "onRequestSigning"); } if (this != mSessionCallback) { Log.w(TAG, "onRequestSigning - session not created"); return; } if (mCallback != null && Flags.tiafVApis()) { mCallback.onRequestSigning(mIAppServiceId, id, algorithm, host, port, data); } } @Override public void onRequestCertificate(Session session, String host, int port) { if (DEBUG) { Log.d(TAG, "onRequestCertificate"); } if (this != mSessionCallback) { Log.w(TAG, "onRequestCertificate - session not created"); return; } if (mCallback != null && Flags.tiafVApis()) { mCallback.onRequestCertificate(mIAppServiceId, host, port); } } } }