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><{@link android.R.styleable#TvAdService tv-ad-service}></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