| /* |
| * 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.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.interactive.TvInteractiveAppManager.Session; |
| import android.media.tv.interactive.TvInteractiveAppManager.Session.FinishedInputEventCallback; |
| import android.media.tv.interactive.TvInteractiveAppManager.SessionCallback; |
| 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.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}. |
| * <p>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. |
| * |
| * <p>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}. |
| * <p>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. |
| * |
| * <p>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<TvTrackInfo> tracks) { |
| if (DEBUG) { |
| Log.d(TAG, "sendTrackInfoList"); |
| } |
| if (mSession != null) { |
| mSession.sendTrackInfoList(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<TvRecordingInfo> 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); |
| } |
| } |
| |
| /** |
| * Sends signing result to related TV interactive app. |
| * |
| * <p>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); |
| } |
| } |
| |
| /** |
| * 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. |
| * <ul> |
| * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNKNOWN} |
| * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNSUPPORTED} |
| * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNAVAILABLE} |
| * <li>{@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE} |
| * </ul> |
| */ |
| 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. |
| * <ul> |
| * <li>{@link TvInputManager#RECORDING_ERROR_UNKNOWN} |
| * <li>{@link TvInputManager#RECORDING_ERROR_INSUFFICIENT_SPACE} |
| * <li>{@link TvInputManager#RECORDING_ERROR_RESOURCE_BUSY} |
| * </ul> |
| * @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. |
| * |
| * <p>{@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. |
| * |
| * <p>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#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 <em>must</em> 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 <em>must</em> 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#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. |
| * |
| * <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); |
| } |
| |
| 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 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); |
| } |
| } |
| } |
| } |