1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.media.tv.ad;
18 
19 import android.annotation.CallSuper;
20 import android.annotation.FlaggedApi;
21 import android.annotation.MainThread;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.Px;
25 import android.annotation.SdkConstant;
26 import android.annotation.SuppressLint;
27 import android.app.ActivityManager;
28 import android.app.Service;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.graphics.PixelFormat;
32 import android.graphics.Rect;
33 import android.media.tv.TvInputManager;
34 import android.media.tv.TvTrackInfo;
35 import android.media.tv.TvView;
36 import android.media.tv.flags.Flags;
37 import android.net.Uri;
38 import android.os.AsyncTask;
39 import android.os.Bundle;
40 import android.os.Handler;
41 import android.os.IBinder;
42 import android.os.Message;
43 import android.os.Process;
44 import android.os.RemoteCallbackList;
45 import android.os.RemoteException;
46 import android.util.Log;
47 import android.view.Gravity;
48 import android.view.InputChannel;
49 import android.view.InputDevice;
50 import android.view.InputEvent;
51 import android.view.InputEventReceiver;
52 import android.view.KeyEvent;
53 import android.view.MotionEvent;
54 import android.view.Surface;
55 import android.view.View;
56 import android.view.WindowManager;
57 import android.widget.FrameLayout;
58 
59 import com.android.internal.os.SomeArgs;
60 
61 import java.util.ArrayList;
62 import java.util.List;
63 
64 /**
65  * The TvAdService class represents a TV client-side advertisement service.
66  */
67 @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
68 public abstract class TvAdService extends Service {
69     private static final boolean DEBUG = false;
70     private static final String TAG = "TvAdService";
71 
72     private static final int DETACH_MEDIA_VIEW_TIMEOUT_MS = 5000;
73 
74     /**
75      * Name under which a TvAdService component publishes information about itself. This meta-data
76      * must reference an XML resource containing an
77      * <code>&lt;{@link android.R.styleable#TvAdService tv-ad-service}&gt;</code> tag.
78      */
79     public static final String SERVICE_META_DATA = "android.media.tv.ad.service";
80 
81     /**
82      * This is the interface name that a service implementing a TV AD service should
83      * say that it supports -- that is, this is the action it uses for its intent filter. To be
84      * supported, the service must also require the
85      * android.Manifest.permission#BIND_TV_AD_SERVICE permission so that other
86      * applications cannot abuse it.
87      */
88     @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
89     public static final String SERVICE_INTERFACE = "android.media.tv.ad.TvAdService";
90 
91     private final Handler mServiceHandler = new ServiceHandler();
92     private final RemoteCallbackList<ITvAdServiceCallback> mCallbacks = new RemoteCallbackList<>();
93 
94     @Override
95     @Nullable
onBind(@ullable Intent intent)96     public final IBinder onBind(@Nullable Intent intent) {
97         ITvAdService.Stub tvAdServiceBinder = new ITvAdService.Stub() {
98             @Override
99             public void registerCallback(ITvAdServiceCallback cb) {
100                 if (cb != null) {
101                     mCallbacks.register(cb);
102                 }
103             }
104 
105             @Override
106             public void unregisterCallback(ITvAdServiceCallback cb) {
107                 if (cb != null) {
108                     mCallbacks.unregister(cb);
109                 }
110             }
111 
112             @Override
113             public void createSession(InputChannel channel, ITvAdSessionCallback cb,
114                     String serviceId, String type) {
115                 if (cb == null) {
116                     return;
117                 }
118                 SomeArgs args = SomeArgs.obtain();
119                 args.arg1 = channel;
120                 args.arg2 = cb;
121                 args.arg3 = serviceId;
122                 args.arg4 = type;
123                 mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args)
124                         .sendToTarget();
125             }
126 
127             @Override
128             public void sendAppLinkCommand(Bundle command) {
129                 onAppLinkCommand(command);
130             }
131         };
132         return tvAdServiceBinder;
133     }
134 
135     /**
136      * Called when app link command is received.
137      *
138      * @see TvAdManager#sendAppLinkCommand(String, Bundle)
139      */
onAppLinkCommand(@onNull Bundle command)140     public void onAppLinkCommand(@NonNull Bundle command) {
141     }
142 
143 
144     /**
145      * Returns a concrete implementation of {@link Session}.
146      *
147      * <p>May return {@code null} if this TV AD service fails to create a session for some
148      * reason.
149      *
150      * @param serviceId The ID of the TV AD associated with the session.
151      * @param type The type of the TV AD associated with the session.
152      */
153     @Nullable
onCreateSession(@onNull String serviceId, @NonNull String type)154     public abstract Session onCreateSession(@NonNull String serviceId, @NonNull String type);
155 
156     /**
157      * Base class for derived classes to implement to provide a TV AD session.
158      */
159     public abstract static class Session implements KeyEvent.Callback {
160         private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState();
161 
162         private final Object mLock = new Object();
163         // @GuardedBy("mLock")
164         private ITvAdSessionCallback mSessionCallback;
165         // @GuardedBy("mLock")
166         private final List<Runnable> mPendingActions = new ArrayList<>();
167         private final Context mContext;
168         final Handler mHandler;
169         private final WindowManager mWindowManager;
170         private WindowManager.LayoutParams mWindowParams;
171         private Surface mSurface;
172         private FrameLayout mMediaViewContainer;
173         private View mMediaView;
174         private MediaViewCleanUpTask mMediaViewCleanUpTask;
175         private boolean mMediaViewEnabled;
176         private IBinder mWindowToken;
177         private Rect mMediaFrame;
178 
179 
180         /**
181          * Creates a new Session.
182          *
183          * @param context The context of the application
184          */
Session(@onNull Context context)185         public Session(@NonNull Context context) {
186             mContext = context;
187             mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
188             mHandler = new Handler(context.getMainLooper());
189         }
190 
191         /**
192          * Enables or disables the media view.
193          *
194          * <p>By default, the media view is disabled. Must be called explicitly after the
195          * session is created to enable the media view.
196          *
197          * <p>The TV AD service can disable its media view when needed.
198          *
199          * @param enable {@code true} if you want to enable the media view. {@code false}
200          *            otherwise.
201          */
202         @CallSuper
setMediaViewEnabled(final boolean enable)203         public void setMediaViewEnabled(final boolean enable) {
204             mHandler.post(new Runnable() {
205                 @Override
206                 public void run() {
207                     if (enable == mMediaViewEnabled) {
208                         return;
209                     }
210                     mMediaViewEnabled = enable;
211                     if (enable) {
212                         if (mWindowToken != null) {
213                             createMediaView(mWindowToken, mMediaFrame);
214                         }
215                     } else {
216                         removeMediaView(false);
217                     }
218                 }
219             });
220         }
221 
222         /**
223          * Returns {@code true} if media view is enabled, {@code false} otherwise.
224          *
225          * @see #setMediaViewEnabled(boolean)
226          */
isMediaViewEnabled()227         public boolean isMediaViewEnabled() {
228             return mMediaViewEnabled;
229         }
230 
231         /**
232          * Releases TvAdService session.
233          */
onRelease()234         public abstract void onRelease();
235 
release()236         void release() {
237             onRelease();
238             if (mSurface != null) {
239                 mSurface.release();
240                 mSurface = null;
241             }
242             synchronized (mLock) {
243                 mSessionCallback = null;
244                 mPendingActions.clear();
245             }
246             // Removes the media view lastly so that any hanging on the main thread can be handled
247             // in {@link #scheduleMediaViewCleanup}.
248             removeMediaView(true);
249         }
250 
251         /**
252          * Starts TvAdService session.
253          */
onStartAdService()254         public void onStartAdService() {
255         }
256 
257         /**
258          * Stops TvAdService session.
259          */
onStopAdService()260         public void onStopAdService() {
261         }
262 
263         /**
264          * Resets TvAdService session.
265          */
onResetAdService()266         public void onResetAdService() {
267         }
268 
startAdService()269         void startAdService() {
270             onStartAdService();
271         }
272 
stopAdService()273         void stopAdService() {
274             onStopAdService();
275         }
276 
resetAdService()277         void resetAdService() {
278             onResetAdService();
279         }
280 
281         /**
282          * Requests the bounds of the current video.
283          */
284         @CallSuper
requestCurrentVideoBounds()285         public void requestCurrentVideoBounds() {
286             executeOrPostRunnableOnMainThread(new Runnable() {
287                 @MainThread
288                 @Override
289                 public void run() {
290                     try {
291                         if (DEBUG) {
292                             Log.d(TAG, "requestCurrentVideoBounds");
293                         }
294                         if (mSessionCallback != null) {
295                             mSessionCallback.onRequestCurrentVideoBounds();
296                         }
297                     } catch (RemoteException e) {
298                         Log.w(TAG, "error in requestCurrentVideoBounds", e);
299                     }
300                 }
301             });
302         }
303 
304         /**
305          * Requests the URI of the current channel.
306          */
307         @CallSuper
requestCurrentChannelUri()308         public void requestCurrentChannelUri() {
309             executeOrPostRunnableOnMainThread(new Runnable() {
310                 @MainThread
311                 @Override
312                 public void run() {
313                     try {
314                         if (DEBUG) {
315                             Log.d(TAG, "requestCurrentChannelUri");
316                         }
317                         if (mSessionCallback != null) {
318                             mSessionCallback.onRequestCurrentChannelUri();
319                         }
320                     } catch (RemoteException e) {
321                         Log.w(TAG, "error in requestCurrentChannelUri", e);
322                     }
323                 }
324             });
325         }
326 
327         /**
328          * Requests the list of {@link TvTrackInfo}.
329          */
330         @CallSuper
requestTrackInfoList()331         public void requestTrackInfoList() {
332             executeOrPostRunnableOnMainThread(new Runnable() {
333                 @MainThread
334                 @Override
335                 public void run() {
336                     try {
337                         if (DEBUG) {
338                             Log.d(TAG, "requestTrackInfoList");
339                         }
340                         if (mSessionCallback != null) {
341                             mSessionCallback.onRequestTrackInfoList();
342                         }
343                     } catch (RemoteException e) {
344                         Log.w(TAG, "error in requestTrackInfoList", e);
345                     }
346                 }
347             });
348         }
349 
350         /**
351          * Requests current TV input ID.
352          *
353          * @see android.media.tv.TvInputInfo
354          */
355         @CallSuper
requestCurrentTvInputId()356         public void requestCurrentTvInputId() {
357             executeOrPostRunnableOnMainThread(new Runnable() {
358                 @MainThread
359                 @Override
360                 public void run() {
361                     try {
362                         if (DEBUG) {
363                             Log.d(TAG, "requestCurrentTvInputId");
364                         }
365                         if (mSessionCallback != null) {
366                             mSessionCallback.onRequestCurrentTvInputId();
367                         }
368                     } catch (RemoteException e) {
369                         Log.w(TAG, "error in requestCurrentTvInputId", e);
370                     }
371                 }
372             });
373         }
374 
375         /**
376          * Requests signing of the given data.
377          *
378          * <p>This is used when the corresponding server of the AD service app requires signing
379          * during handshaking, and the service doesn't have the built-in private key. The private
380          * key is provided by the content providers and pre-built in the related app, such as TV
381          * app.
382          *
383          * @param signingId the ID to identify the request. When a result is received, this ID can
384          *                  be used to correlate the result with the request.
385          * @param algorithm the standard name of the signature algorithm requested, such as
386          *                  MD5withRSA, SHA256withDSA, etc. The name is from standards like
387          *                  FIPS PUB 186-4 and PKCS #1.
388          * @param alias the alias of the corresponding {@link java.security.KeyStore}.
389          * @param data the original bytes to be signed.
390          *
391          * @see #onSigningResult(String, byte[])
392          */
393         @CallSuper
requestSigning(@onNull String signingId, @NonNull String algorithm, @NonNull String alias, @NonNull byte[] data)394         public void requestSigning(@NonNull String signingId, @NonNull String algorithm,
395                 @NonNull String alias, @NonNull byte[] data) {
396             executeOrPostRunnableOnMainThread(new Runnable() {
397                 @MainThread
398                 @Override
399                 public void run() {
400                     try {
401                         if (DEBUG) {
402                             Log.d(TAG, "requestSigning");
403                         }
404                         if (mSessionCallback != null) {
405                             mSessionCallback.onRequestSigning(signingId, algorithm, alias, data);
406                         }
407                     } catch (RemoteException e) {
408                         Log.w(TAG, "error in requestSigning", e);
409                     }
410                 }
411             });
412         }
413 
414         @Override
onKeyDown(int keyCode, @Nullable KeyEvent event)415         public boolean onKeyDown(int keyCode, @Nullable KeyEvent event) {
416             return false;
417         }
418 
419         @Override
onKeyLongPress(int keyCode, @Nullable KeyEvent event)420         public boolean onKeyLongPress(int keyCode, @Nullable KeyEvent event) {
421             return false;
422         }
423 
424         @Override
onKeyMultiple(int keyCode, int count, @Nullable KeyEvent event)425         public boolean onKeyMultiple(int keyCode, int count, @Nullable KeyEvent event) {
426             return false;
427         }
428 
429         @Override
onKeyUp(int keyCode, @Nullable KeyEvent event)430         public boolean onKeyUp(int keyCode, @Nullable KeyEvent event) {
431             return false;
432         }
433 
434         /**
435          * Implement this method to handle touch screen motion events on the current session.
436          *
437          * @param event The motion event being received.
438          * @return If you handled the event, return {@code true}. If you want to allow the event to
439          *         be handled by the next receiver, return {@code false}.
440          * @see View#onTouchEvent
441          */
onTouchEvent(@onNull MotionEvent event)442         public boolean onTouchEvent(@NonNull MotionEvent event) {
443             return false;
444         }
445 
446         /**
447          * Implement this method to handle trackball events on the current session.
448          *
449          * @param event The motion event being received.
450          * @return If you handled the event, return {@code true}. If you want to allow the event to
451          *         be handled by the next receiver, return {@code false}.
452          * @see View#onTrackballEvent
453          */
onTrackballEvent(@onNull MotionEvent event)454         public boolean onTrackballEvent(@NonNull MotionEvent event) {
455             return false;
456         }
457 
458         /**
459          * Implement this method to handle generic motion events on the current session.
460          *
461          * @param event The motion event being received.
462          * @return If you handled the event, return {@code true}. If you want to allow the event to
463          *         be handled by the next receiver, return {@code false}.
464          * @see View#onGenericMotionEvent
465          */
onGenericMotionEvent(@onNull MotionEvent event)466         public boolean onGenericMotionEvent(@NonNull MotionEvent event) {
467             return false;
468         }
469 
470         /**
471          * Assigns a size and position to the surface passed in {@link #onSetSurface}. The position
472          * is relative to the overlay view that sits on top of this surface.
473          *
474          * @param left Left position in pixels, relative to the overlay view.
475          * @param top Top position in pixels, relative to the overlay view.
476          * @param right Right position in pixels, relative to the overlay view.
477          * @param bottom Bottom position in pixels, relative to the overlay view.
478          *
479          */
480         @CallSuper
layoutSurface(final int left, final int top, final int right, final int bottom)481         public void layoutSurface(final int left, final int top, final int right,
482                 final int bottom) {
483             if (left > right || top > bottom) {
484                 throw new IllegalArgumentException("Invalid parameter");
485             }
486             executeOrPostRunnableOnMainThread(new Runnable() {
487                 @MainThread
488                 @Override
489                 public void run() {
490                     try {
491                         if (DEBUG) {
492                             Log.d(TAG, "layoutSurface (l=" + left + ", t=" + top
493                                     + ", r=" + right + ", b=" + bottom + ",)");
494                         }
495                         if (mSessionCallback != null) {
496                             mSessionCallback.onLayoutSurface(left, top, right, bottom);
497                         }
498                     } catch (RemoteException e) {
499                         Log.w(TAG, "error in layoutSurface", e);
500                     }
501                 }
502             });
503         }
504 
505         /**
506          * Called when the application sets the surface.
507          *
508          * <p>The TV AD service should render AD UI onto the given surface. When called with
509          * {@code null}, the AD service should immediately free any references to the currently set
510          * surface and stop using it.
511          *
512          * @param surface The surface to be used for AD UI rendering. Can be {@code null}.
513          * @return {@code true} if the surface was set successfully, {@code false} otherwise.
514          */
onSetSurface(@ullable Surface surface)515         public abstract boolean onSetSurface(@Nullable Surface surface);
516 
517         /**
518          * Called after any structural changes (format or size) have been made to the surface passed
519          * in {@link #onSetSurface}. This method is always called at least once, after
520          * {@link #onSetSurface} is called with non-null surface.
521          *
522          * @param format The new {@link PixelFormat} of the surface.
523          * @param width The new width of the surface.
524          * @param height The new height of the surface.
525          */
onSurfaceChanged(@ixelFormat.Format int format, int width, int height)526         public void onSurfaceChanged(@PixelFormat.Format int format, int width, int height) {
527         }
528 
529         /**
530          * Receives current video bounds.
531          *
532          * @param bounds the rectangle area for rendering the current video.
533          */
onCurrentVideoBounds(@onNull Rect bounds)534         public void onCurrentVideoBounds(@NonNull Rect bounds) {
535         }
536 
537         /**
538          * Receives current channel URI.
539          */
onCurrentChannelUri(@ullable Uri channelUri)540         public void onCurrentChannelUri(@Nullable Uri channelUri) {
541         }
542 
543         /**
544          * Receives track list.
545          */
onTrackInfoList(@onNull List<TvTrackInfo> tracks)546         public void onTrackInfoList(@NonNull List<TvTrackInfo> tracks) {
547         }
548 
549         /**
550          * Receives current TV input ID.
551          */
onCurrentTvInputId(@ullable String inputId)552         public void onCurrentTvInputId(@Nullable String inputId) {
553         }
554 
555         /**
556          * Receives signing result.
557          *
558          * @param signingId the ID to identify the request. It's the same as the corresponding ID in
559          *        {@link Session#requestSigning(String, String, String, byte[])}
560          * @param result the signed result.
561          *
562          * @see #requestSigning(String, String, String, byte[])
563          */
onSigningResult(@onNull String signingId, @NonNull byte[] result)564         public void onSigningResult(@NonNull String signingId, @NonNull byte[] result) {
565         }
566 
567         /**
568          * Called when the application sends information of an error.
569          *
570          * @param errMsg the message of the error.
571          * @param params additional parameters of the error. For example, the signingId of {@link
572          *     TvAdView.TvAdCallback#onRequestSigning(String, String, String, String, byte[])}
573          *     can be included to identify the related signing request, and the method name
574          *     "onRequestSigning" can also be added to the params.
575          *
576          * @see TvAdView#ERROR_KEY_METHOD_NAME
577          */
onError(@onNull String errMsg, @NonNull Bundle params)578         public void onError(@NonNull String errMsg, @NonNull Bundle params) {
579         }
580 
581         /**
582          * Called when a TV message is received
583          *
584          * @param type The type of message received, such as
585          * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
586          * @param data The raw data of the message. The bundle keys are:
587          *             {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
588          *             {@link TvInputManager#TV_MESSAGE_KEY_GROUP_ID},
589          *             {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
590          *             {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
591          *             See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
592          *             how to parse this data.
593          */
onTvMessage(@vInputManager.TvMessageType int type, @NonNull Bundle data)594         public void onTvMessage(@TvInputManager.TvMessageType int type,
595                 @NonNull Bundle data) {
596         }
597 
598         /**
599          * Called when data from the linked {@link android.media.tv.TvInputService} is received.
600          *
601          * @param type the type of the data
602          * @param data a bundle contains the data received
603          * @see android.media.tv.TvInputService.Session#sendTvInputSessionData(String, Bundle)
604          * @see android.media.tv.ad.TvAdView#setTvView(TvView)
605          */
onTvInputSessionData( @onNull @vInputManager.SessionDataType String type, @NonNull Bundle data)606         public void onTvInputSessionData(
607                 @NonNull @TvInputManager.SessionDataType String type, @NonNull Bundle data) {
608         }
609 
610         /**
611          * Called when the size of the media view is changed by the application.
612          *
613          * <p>This is always called at least once when the session is created regardless of whether
614          * the media view is enabled or not. The media view container size is the same as the
615          * containing {@link TvAdView}. Note that the size of the underlying surface can
616          * be different if the surface was changed by calling {@link #layoutSurface}.
617          *
618          * @param width The width of the media view, in pixels.
619          * @param height The height of the media view, in pixels.
620          */
onMediaViewSizeChanged(@x int width, @Px int height)621         public void onMediaViewSizeChanged(@Px int width, @Px int height) {
622         }
623 
624         /**
625          * Called when the application requests to create a media view. Each session
626          * implementation can override this method and return its own view.
627          *
628          * @return a view attached to the media window. {@code null} if no media view is created.
629          */
630         @Nullable
onCreateMediaView()631         public View onCreateMediaView() {
632             return null;
633         }
634 
635         /**
636          * Sends data related to this session to corresponding linked
637          * {@link android.media.tv.TvInputService} object via TvView.
638          *
639          * @param type data type
640          * @param data the related data values
641          * @see TvAdView#setTvView(TvView)
642          */
sendTvAdSessionData( @onNull @vAdManager.SessionDataType String type, @NonNull Bundle data)643         public void sendTvAdSessionData(
644                 @NonNull @TvAdManager.SessionDataType String type, @NonNull Bundle data) {
645             executeOrPostRunnableOnMainThread(new Runnable() {
646                 @MainThread
647                 @Override
648                 public void run() {
649                     try {
650                         if (DEBUG) Log.d(TAG, "sendTvAdSessionData");
651                         if (mSessionCallback != null) {
652                             mSessionCallback.onTvAdSessionData(type, data);
653                         }
654                     } catch (RemoteException e) {
655                         Log.w(TAG, "error in sendTvAdSessionData", e);
656                     }
657                 }
658             });
659         }
660 
661         /**
662          * Notifies when the session state is changed.
663          *
664          * @param state the current session state.
665          * @param err the error code for error state. {@link TvAdManager#ERROR_NONE} is
666          *            used when the state is not {@link TvAdManager#SESSION_STATE_ERROR}.
667          */
668         @CallSuper
notifySessionStateChanged( @vAdManager.SessionState int state, @TvAdManager.ErrorCode int err)669         public void notifySessionStateChanged(
670                 @TvAdManager.SessionState int state,
671                 @TvAdManager.ErrorCode int err) {
672             executeOrPostRunnableOnMainThread(new Runnable() {
673                 @MainThread
674                 @Override
675                 public void run() {
676                     if (DEBUG) {
677                         Log.d(TAG, "notifySessionStateChanged (state="
678                                 + state + "; err=" + err + ")");
679                     }
680                     // TODO: handle session callback
681                 }
682             });
683         }
684 
685         /**
686          * Takes care of dispatching incoming input events and tells whether the event was handled.
687          */
dispatchInputEvent(InputEvent event, InputEventReceiver receiver)688         int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
689             if (DEBUG) Log.d(TAG, "dispatchInputEvent(" + event + ")");
690             if (event instanceof KeyEvent) {
691                 KeyEvent keyEvent = (KeyEvent) event;
692                 if (keyEvent.dispatch(this, mDispatcherState, this)) {
693                     return TvAdManager.Session.DISPATCH_HANDLED;
694                 }
695 
696                 // TODO: special handlings of navigation keys and media keys
697             } else if (event instanceof MotionEvent) {
698                 MotionEvent motionEvent = (MotionEvent) event;
699                 final int source = motionEvent.getSource();
700                 if (motionEvent.isTouchEvent()) {
701                     if (onTouchEvent(motionEvent)) {
702                         return TvAdManager.Session.DISPATCH_HANDLED;
703                     }
704                 } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
705                     if (onTrackballEvent(motionEvent)) {
706                         return TvAdManager.Session.DISPATCH_HANDLED;
707                     }
708                 } else {
709                     if (onGenericMotionEvent(motionEvent)) {
710                         return TvAdManager.Session.DISPATCH_HANDLED;
711                     }
712                 }
713             }
714             // TODO: handle overlay view
715             return TvAdManager.Session.DISPATCH_NOT_HANDLED;
716         }
717 
718 
initialize(ITvAdSessionCallback callback)719         private void initialize(ITvAdSessionCallback callback) {
720             synchronized (mLock) {
721                 mSessionCallback = callback;
722                 for (Runnable runnable : mPendingActions) {
723                     runnable.run();
724                 }
725                 mPendingActions.clear();
726             }
727         }
728 
729         /**
730          * Calls {@link #onSetSurface}.
731          */
setSurface(Surface surface)732         void setSurface(Surface surface) {
733             onSetSurface(surface);
734             if (mSurface != null) {
735                 mSurface.release();
736             }
737             mSurface = surface;
738             // TODO: Handle failure.
739         }
740 
741         /**
742          * Calls {@link #onSurfaceChanged}.
743          */
dispatchSurfaceChanged(int format, int width, int height)744         void dispatchSurfaceChanged(int format, int width, int height) {
745             if (DEBUG) {
746                 Log.d(TAG, "dispatchSurfaceChanged(format=" + format + ", width=" + width
747                         + ", height=" + height + ")");
748             }
749             onSurfaceChanged(format, width, height);
750         }
751 
sendCurrentVideoBounds(@onNull Rect bounds)752         void sendCurrentVideoBounds(@NonNull Rect bounds) {
753             onCurrentVideoBounds(bounds);
754         }
755 
sendCurrentChannelUri(@ullable Uri channelUri)756         void sendCurrentChannelUri(@Nullable Uri channelUri) {
757             onCurrentChannelUri(channelUri);
758         }
759 
sendTrackInfoList(@onNull List<TvTrackInfo> tracks)760         void sendTrackInfoList(@NonNull List<TvTrackInfo> tracks) {
761             onTrackInfoList(tracks);
762         }
763 
sendCurrentTvInputId(@ullable String inputId)764         void sendCurrentTvInputId(@Nullable String inputId) {
765             onCurrentTvInputId(inputId);
766         }
767 
sendSigningResult(String signingId, byte[] result)768         void sendSigningResult(String signingId, byte[] result) {
769             onSigningResult(signingId, result);
770         }
771 
notifyError(String errMsg, Bundle params)772         void notifyError(String errMsg, Bundle params) {
773             onError(errMsg, params);
774         }
775 
notifyTvMessage(int type, Bundle data)776         void notifyTvMessage(int type, Bundle data) {
777             if (DEBUG) {
778                 Log.d(TAG, "notifyTvMessage (type=" + type + ", data= " + data + ")");
779             }
780             onTvMessage(type, data);
781         }
782 
notifyTvInputSessionData(String type, Bundle data)783         void notifyTvInputSessionData(String type, Bundle data) {
784             onTvInputSessionData(type, data);
785         }
786 
executeOrPostRunnableOnMainThread(Runnable action)787         private void executeOrPostRunnableOnMainThread(Runnable action) {
788             synchronized (mLock) {
789                 if (mSessionCallback == null) {
790                     // The session is not initialized yet.
791                     mPendingActions.add(action);
792                 } else {
793                     if (mHandler.getLooper().isCurrentThread()) {
794                         action.run();
795                     } else {
796                         // Posts the runnable if this is not called from the main thread
797                         mHandler.post(action);
798                     }
799                 }
800             }
801         }
802 
803         /**
804          * Creates a media view. This calls {@link #onCreateMediaView} to get a view to attach
805          * to the media window.
806          *
807          * @param windowToken A window token of the application.
808          * @param frame A position of the media view.
809          */
createMediaView(IBinder windowToken, Rect frame)810         void createMediaView(IBinder windowToken, Rect frame) {
811             if (mMediaViewContainer != null) {
812                 removeMediaView(false);
813             }
814             if (DEBUG) Log.d(TAG, "create media view(" + frame + ")");
815             mWindowToken = windowToken;
816             mMediaFrame = frame;
817             onMediaViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
818             if (!mMediaViewEnabled) {
819                 return;
820             }
821             mMediaView = onCreateMediaView();
822             if (mMediaView == null) {
823                 return;
824             }
825             if (mMediaViewCleanUpTask != null) {
826                 mMediaViewCleanUpTask.cancel(true);
827                 mMediaViewCleanUpTask = null;
828             }
829             // Creates a container view to check hanging on the media view detaching.
830             // Adding/removing the media view to/from the container make the view attach/detach
831             // logic run on the main thread.
832             mMediaViewContainer = new FrameLayout(mContext.getApplicationContext());
833             mMediaViewContainer.addView(mMediaView);
834 
835             int type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
836             // We make the overlay view non-focusable and non-touchable so that
837             // the application that owns the window token can decide whether to consume or
838             // dispatch the input events.
839             int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
840                     | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
841                     | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
842             if (ActivityManager.isHighEndGfx()) {
843                 flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
844             }
845             mWindowParams = new WindowManager.LayoutParams(
846                     frame.right - frame.left, frame.bottom - frame.top,
847                     frame.left, frame.top, type, flags, PixelFormat.TRANSPARENT);
848             mWindowParams.privateFlags |=
849                     WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
850             mWindowParams.gravity = Gravity.START | Gravity.TOP;
851             mWindowParams.token = windowToken;
852             mWindowManager.addView(mMediaViewContainer, mWindowParams);
853         }
854 
855         /**
856          * Relayouts the current media view.
857          *
858          * @param frame A new position of the media view.
859          */
relayoutMediaView(Rect frame)860         void relayoutMediaView(Rect frame) {
861             if (DEBUG) Log.d(TAG, "relayoutMediaView(" + frame + ")");
862             if (mMediaFrame == null || mMediaFrame.width() != frame.width()
863                     || mMediaFrame.height() != frame.height()) {
864                 // Note: relayoutMediaView is called whenever TvAdView's layout is
865                 // changed regardless of setMediaViewEnabled.
866                 onMediaViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
867             }
868             mMediaFrame = frame;
869             if (!mMediaViewEnabled || mMediaViewContainer == null) {
870                 return;
871             }
872             mWindowParams.x = frame.left;
873             mWindowParams.y = frame.top;
874             mWindowParams.width = frame.right - frame.left;
875             mWindowParams.height = frame.bottom - frame.top;
876             mWindowManager.updateViewLayout(mMediaViewContainer, mWindowParams);
877         }
878 
879         /**
880          * Removes the current media view.
881          */
removeMediaView(boolean clearWindowToken)882         void removeMediaView(boolean clearWindowToken) {
883             if (DEBUG) Log.d(TAG, "removeMediaView(" + mMediaViewContainer + ")");
884             if (clearWindowToken) {
885                 mWindowToken = null;
886                 mMediaFrame = null;
887             }
888             if (mMediaViewContainer != null) {
889                 // Removes the media view from the view hierarchy in advance so that it can be
890                 // cleaned up in the {@link MediaViewCleanUpTask} if the remove process is
891                 // hanging.
892                 mMediaViewContainer.removeView(mMediaView);
893                 mMediaView = null;
894                 mWindowManager.removeView(mMediaViewContainer);
895                 mMediaViewContainer = null;
896                 mWindowParams = null;
897             }
898         }
899 
900         /**
901          * Schedules a task which checks whether the media view is detached and kills the process
902          * if it is not. Note that this method is expected to be called in a non-main thread.
903          */
scheduleMediaViewCleanup()904         void scheduleMediaViewCleanup() {
905             View mediaViewParent = mMediaViewContainer;
906             if (mediaViewParent != null) {
907                 mMediaViewCleanUpTask = new MediaViewCleanUpTask();
908                 mMediaViewCleanUpTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
909                         mediaViewParent);
910             }
911         }
912     }
913 
914     private static final class MediaViewCleanUpTask extends AsyncTask<View, Void, Void> {
915         @Override
doInBackground(View... views)916         protected Void doInBackground(View... views) {
917             View mediaViewParent = views[0];
918             try {
919                 Thread.sleep(DETACH_MEDIA_VIEW_TIMEOUT_MS);
920             } catch (InterruptedException e) {
921                 return null;
922             }
923             if (isCancelled()) {
924                 return null;
925             }
926             if (mediaViewParent.isAttachedToWindow()) {
927                 Log.e(TAG, "Time out on releasing media view. Killing "
928                         + mediaViewParent.getContext().getPackageName());
929                 android.os.Process.killProcess(Process.myPid());
930             }
931             return null;
932         }
933     }
934 
935 
936     @SuppressLint("HandlerLeak")
937     private final class ServiceHandler extends Handler {
938         private static final int DO_CREATE_SESSION = 1;
939         private static final int DO_NOTIFY_SESSION_CREATED = 2;
940 
941         @Override
handleMessage(Message msg)942         public void handleMessage(Message msg) {
943             switch (msg.what) {
944                 case DO_CREATE_SESSION: {
945                     SomeArgs args = (SomeArgs) msg.obj;
946                     InputChannel channel = (InputChannel) args.arg1;
947                     ITvAdSessionCallback cb = (ITvAdSessionCallback) args.arg2;
948                     String serviceId = (String) args.arg3;
949                     String type = (String) args.arg4;
950                     args.recycle();
951                     TvAdService.Session sessionImpl = onCreateSession(serviceId, type);
952                     if (sessionImpl == null) {
953                         try {
954                             // Failed to create a session.
955                             cb.onSessionCreated(null);
956                         } catch (RemoteException e) {
957                             Log.e(TAG, "error in onSessionCreated", e);
958                         }
959                         return;
960                     }
961                     ITvAdSession stub =
962                             new ITvAdSessionWrapper(TvAdService.this, sessionImpl, channel);
963 
964                     SomeArgs someArgs = SomeArgs.obtain();
965                     someArgs.arg1 = sessionImpl;
966                     someArgs.arg2 = stub;
967                     someArgs.arg3 = cb;
968                     mServiceHandler.obtainMessage(
969                             DO_NOTIFY_SESSION_CREATED, someArgs).sendToTarget();
970                     return;
971                 }
972                 case DO_NOTIFY_SESSION_CREATED: {
973                     SomeArgs args = (SomeArgs) msg.obj;
974                     Session sessionImpl = (Session) args.arg1;
975                     ITvAdSession stub = (ITvAdSession) args.arg2;
976                     ITvAdSessionCallback cb = (ITvAdSessionCallback) args.arg3;
977                     try {
978                         cb.onSessionCreated(stub);
979                     } catch (RemoteException e) {
980                         Log.e(TAG, "error in onSessionCreated", e);
981                     }
982                     if (sessionImpl != null) {
983                         sessionImpl.initialize(cb);
984                     }
985                     args.recycle();
986                     return;
987                 }
988                 default: {
989                     Log.w(TAG, "Unhandled message code: " + msg.what);
990                     return;
991                 }
992             }
993         }
994 
995     }
996 }
997