<{@link android.R.styleable#TvAdService tv-ad-service}>
tag.
*/
public static final String SERVICE_META_DATA = "android.media.tv.ad.service";
/**
* This is the interface name that a service implementing a TV AD service should
* say that it supports -- that is, this is the action it uses for its intent filter. To be
* supported, the service must also require the
* android.Manifest.permission#BIND_TV_AD_SERVICE permission so that other
* applications cannot abuse it.
*/
@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
public static final String SERVICE_INTERFACE = "android.media.tv.ad.TvAdService";
private final Handler mServiceHandler = new ServiceHandler();
private final RemoteCallbackListMay return {@code null} if this TV AD service fails to create a session for some
* reason.
*
* @param serviceId The ID of the TV AD associated with the session.
* @param type The type of the TV AD associated with the session.
*/
@Nullable
public abstract Session onCreateSession(@NonNull String serviceId, @NonNull String type);
/**
* Base class for derived classes to implement to provide a TV AD session.
*/
public abstract static class Session implements KeyEvent.Callback {
private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState();
private final Object mLock = new Object();
// @GuardedBy("mLock")
private ITvAdSessionCallback mSessionCallback;
// @GuardedBy("mLock")
private final List By default, the media view is disabled. Must be called explicitly after the
* session is created to enable the media view.
*
* The TV AD service can disable its media view when needed.
*
* @param enable {@code true} if you want to enable the media view. {@code false}
* otherwise.
*/
@CallSuper
public void setMediaViewEnabled(final boolean enable) {
mHandler.post(new Runnable() {
@Override
public void run() {
if (enable == mMediaViewEnabled) {
return;
}
mMediaViewEnabled = enable;
if (enable) {
if (mWindowToken != null) {
createMediaView(mWindowToken, mMediaFrame);
}
} else {
removeMediaView(false);
}
}
});
}
/**
* Returns {@code true} if media view is enabled, {@code false} otherwise.
*
* @see #setMediaViewEnabled(boolean)
*/
public boolean isMediaViewEnabled() {
return mMediaViewEnabled;
}
/**
* Releases TvAdService session.
*/
public abstract void onRelease();
void release() {
onRelease();
if (mSurface != null) {
mSurface.release();
mSurface = null;
}
synchronized (mLock) {
mSessionCallback = null;
mPendingActions.clear();
}
// Removes the media view lastly so that any hanging on the main thread can be handled
// in {@link #scheduleMediaViewCleanup}.
removeMediaView(true);
}
/**
* Starts TvAdService session.
*/
public void onStartAdService() {
}
/**
* Stops TvAdService session.
*/
public void onStopAdService() {
}
/**
* Resets TvAdService session.
*/
public void onResetAdService() {
}
void startAdService() {
onStartAdService();
}
void stopAdService() {
onStopAdService();
}
void resetAdService() {
onResetAdService();
}
/**
* Requests the bounds of the current video.
*/
@CallSuper
public void requestCurrentVideoBounds() {
executeOrPostRunnableOnMainThread(new Runnable() {
@MainThread
@Override
public void run() {
try {
if (DEBUG) {
Log.d(TAG, "requestCurrentVideoBounds");
}
if (mSessionCallback != null) {
mSessionCallback.onRequestCurrentVideoBounds();
}
} catch (RemoteException e) {
Log.w(TAG, "error in requestCurrentVideoBounds", e);
}
}
});
}
/**
* Requests the URI of the current channel.
*/
@CallSuper
public void requestCurrentChannelUri() {
executeOrPostRunnableOnMainThread(new Runnable() {
@MainThread
@Override
public void run() {
try {
if (DEBUG) {
Log.d(TAG, "requestCurrentChannelUri");
}
if (mSessionCallback != null) {
mSessionCallback.onRequestCurrentChannelUri();
}
} catch (RemoteException e) {
Log.w(TAG, "error in requestCurrentChannelUri", e);
}
}
});
}
/**
* Requests the list of {@link TvTrackInfo}.
*/
@CallSuper
public void requestTrackInfoList() {
executeOrPostRunnableOnMainThread(new Runnable() {
@MainThread
@Override
public void run() {
try {
if (DEBUG) {
Log.d(TAG, "requestTrackInfoList");
}
if (mSessionCallback != null) {
mSessionCallback.onRequestTrackInfoList();
}
} catch (RemoteException e) {
Log.w(TAG, "error in requestTrackInfoList", e);
}
}
});
}
/**
* Requests current TV input ID.
*
* @see android.media.tv.TvInputInfo
*/
@CallSuper
public void requestCurrentTvInputId() {
executeOrPostRunnableOnMainThread(new Runnable() {
@MainThread
@Override
public void run() {
try {
if (DEBUG) {
Log.d(TAG, "requestCurrentTvInputId");
}
if (mSessionCallback != null) {
mSessionCallback.onRequestCurrentTvInputId();
}
} catch (RemoteException e) {
Log.w(TAG, "error in requestCurrentTvInputId", e);
}
}
});
}
/**
* Requests signing of the given data.
*
* This is used when the corresponding server of the AD service app requires signing
* during handshaking, and the service doesn't have the built-in private key. The private
* key is provided by the content providers and pre-built in the related app, such as TV
* app.
*
* @param signingId the ID to identify the request. When a result is received, this ID can
* be used to correlate the result with the request.
* @param algorithm the standard name of the signature algorithm requested, such as
* MD5withRSA, SHA256withDSA, etc. The name is from standards like
* FIPS PUB 186-4 and PKCS #1.
* @param alias the alias of the corresponding {@link java.security.KeyStore}.
* @param data the original bytes to be signed.
*
* @see #onSigningResult(String, byte[])
*/
@CallSuper
public void requestSigning(@NonNull String signingId, @NonNull String algorithm,
@NonNull String alias, @NonNull byte[] data) {
executeOrPostRunnableOnMainThread(new Runnable() {
@MainThread
@Override
public void run() {
try {
if (DEBUG) {
Log.d(TAG, "requestSigning");
}
if (mSessionCallback != null) {
mSessionCallback.onRequestSigning(signingId, algorithm, alias, data);
}
} catch (RemoteException e) {
Log.w(TAG, "error in requestSigning", e);
}
}
});
}
@Override
public boolean onKeyDown(int keyCode, @Nullable KeyEvent event) {
return false;
}
@Override
public boolean onKeyLongPress(int keyCode, @Nullable KeyEvent event) {
return false;
}
@Override
public boolean onKeyMultiple(int keyCode, int count, @Nullable KeyEvent event) {
return false;
}
@Override
public boolean onKeyUp(int keyCode, @Nullable KeyEvent event) {
return false;
}
/**
* Implement this method to handle touch screen motion events on the current session.
*
* @param event The motion event being received.
* @return If you handled the event, return {@code true}. If you want to allow the event to
* be handled by the next receiver, return {@code false}.
* @see View#onTouchEvent
*/
public boolean onTouchEvent(@NonNull MotionEvent event) {
return false;
}
/**
* Implement this method to handle trackball events on the current session.
*
* @param event The motion event being received.
* @return If you handled the event, return {@code true}. If you want to allow the event to
* be handled by the next receiver, return {@code false}.
* @see View#onTrackballEvent
*/
public boolean onTrackballEvent(@NonNull MotionEvent event) {
return false;
}
/**
* Implement this method to handle generic motion events on the current session.
*
* @param event The motion event being received.
* @return If you handled the event, return {@code true}. If you want to allow the event to
* be handled by the next receiver, return {@code false}.
* @see View#onGenericMotionEvent
*/
public boolean onGenericMotionEvent(@NonNull MotionEvent event) {
return false;
}
/**
* Assigns a size and position to the surface passed in {@link #onSetSurface}. The position
* is relative to the overlay view that sits on top of this surface.
*
* @param left Left position in pixels, relative to the overlay view.
* @param top Top position in pixels, relative to the overlay view.
* @param right Right position in pixels, relative to the overlay view.
* @param bottom Bottom position in pixels, relative to the overlay view.
*
*/
@CallSuper
public void layoutSurface(final int left, final int top, final int right,
final int bottom) {
if (left > right || top > bottom) {
throw new IllegalArgumentException("Invalid parameter");
}
executeOrPostRunnableOnMainThread(new Runnable() {
@MainThread
@Override
public void run() {
try {
if (DEBUG) {
Log.d(TAG, "layoutSurface (l=" + left + ", t=" + top
+ ", r=" + right + ", b=" + bottom + ",)");
}
if (mSessionCallback != null) {
mSessionCallback.onLayoutSurface(left, top, right, bottom);
}
} catch (RemoteException e) {
Log.w(TAG, "error in layoutSurface", e);
}
}
});
}
/**
* Called when the application sets the surface.
*
* The TV AD service should render AD UI onto the given surface. When called with
* {@code null}, the AD service should immediately free any references to the currently set
* surface and stop using it.
*
* @param surface The surface to be used for AD UI rendering. Can be {@code null}.
* @return {@code true} if the surface was set successfully, {@code false} otherwise.
*/
public abstract boolean onSetSurface(@Nullable Surface surface);
/**
* Called after any structural changes (format or size) have been made to the surface passed
* in {@link #onSetSurface}. This method is always called at least once, after
* {@link #onSetSurface} is called with non-null surface.
*
* @param format The new {@link PixelFormat} of the surface.
* @param width The new width of the surface.
* @param height The new height of the surface.
*/
public void onSurfaceChanged(@PixelFormat.Format int format, int width, int height) {
}
/**
* Receives current video bounds.
*
* @param bounds the rectangle area for rendering the current video.
*/
public void onCurrentVideoBounds(@NonNull Rect bounds) {
}
/**
* Receives current channel URI.
*/
public void onCurrentChannelUri(@Nullable Uri channelUri) {
}
/**
* Receives track list.
*/
public void onTrackInfoList(@NonNull List This is always called at least once when the session is created regardless of whether
* the media view is enabled or not. The media view container size is the same as the
* containing {@link TvAdView}. Note that the size of the underlying surface can
* be different if the surface was changed by calling {@link #layoutSurface}.
*
* @param width The width of the media view, in pixels.
* @param height The height of the media view, in pixels.
*/
public void onMediaViewSizeChanged(@Px int width, @Px int height) {
}
/**
* Called when the application requests to create a media view. Each session
* implementation can override this method and return its own view.
*
* @return a view attached to the media window. {@code null} if no media view is created.
*/
@Nullable
public View onCreateMediaView() {
return null;
}
/**
* Sends data related to this session to corresponding linked
* {@link android.media.tv.TvInputService} object via TvView.
*
* @param type data type
* @param data the related data values
* @see TvAdView#setTvView(TvView)
*/
public void sendTvAdSessionData(
@NonNull @TvAdManager.SessionDataType String type, @NonNull Bundle data) {
executeOrPostRunnableOnMainThread(new Runnable() {
@MainThread
@Override
public void run() {
try {
if (DEBUG) Log.d(TAG, "sendTvAdSessionData");
if (mSessionCallback != null) {
mSessionCallback.onTvAdSessionData(type, data);
}
} catch (RemoteException e) {
Log.w(TAG, "error in sendTvAdSessionData", e);
}
}
});
}
/**
* Notifies when the session state is changed.
*
* @param state the current session state.
* @param err the error code for error state. {@link TvAdManager#ERROR_NONE} is
* used when the state is not {@link TvAdManager#SESSION_STATE_ERROR}.
*/
@CallSuper
public void notifySessionStateChanged(
@TvAdManager.SessionState int state,
@TvAdManager.ErrorCode int err) {
executeOrPostRunnableOnMainThread(new Runnable() {
@MainThread
@Override
public void run() {
if (DEBUG) {
Log.d(TAG, "notifySessionStateChanged (state="
+ state + "; err=" + err + ")");
}
// TODO: handle session callback
}
});
}
/**
* Takes care of dispatching incoming input events and tells whether the event was handled.
*/
int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
if (DEBUG) Log.d(TAG, "dispatchInputEvent(" + event + ")");
if (event instanceof KeyEvent) {
KeyEvent keyEvent = (KeyEvent) event;
if (keyEvent.dispatch(this, mDispatcherState, this)) {
return TvAdManager.Session.DISPATCH_HANDLED;
}
// TODO: special handlings of navigation keys and media keys
} else if (event instanceof MotionEvent) {
MotionEvent motionEvent = (MotionEvent) event;
final int source = motionEvent.getSource();
if (motionEvent.isTouchEvent()) {
if (onTouchEvent(motionEvent)) {
return TvAdManager.Session.DISPATCH_HANDLED;
}
} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
if (onTrackballEvent(motionEvent)) {
return TvAdManager.Session.DISPATCH_HANDLED;
}
} else {
if (onGenericMotionEvent(motionEvent)) {
return TvAdManager.Session.DISPATCH_HANDLED;
}
}
}
// TODO: handle overlay view
return TvAdManager.Session.DISPATCH_NOT_HANDLED;
}
private void initialize(ITvAdSessionCallback callback) {
synchronized (mLock) {
mSessionCallback = callback;
for (Runnable runnable : mPendingActions) {
runnable.run();
}
mPendingActions.clear();
}
}
/**
* Calls {@link #onSetSurface}.
*/
void setSurface(Surface surface) {
onSetSurface(surface);
if (mSurface != null) {
mSurface.release();
}
mSurface = surface;
// TODO: Handle failure.
}
/**
* Calls {@link #onSurfaceChanged}.
*/
void dispatchSurfaceChanged(int format, int width, int height) {
if (DEBUG) {
Log.d(TAG, "dispatchSurfaceChanged(format=" + format + ", width=" + width
+ ", height=" + height + ")");
}
onSurfaceChanged(format, width, height);
}
void sendCurrentVideoBounds(@NonNull Rect bounds) {
onCurrentVideoBounds(bounds);
}
void sendCurrentChannelUri(@Nullable Uri channelUri) {
onCurrentChannelUri(channelUri);
}
void sendTrackInfoList(@NonNull List