Home | History | Annotate | Download | only in tv
      1 /*
      2  * Copyright (C) 2014 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;
     18 
     19 import android.annotation.FloatRange;
     20 import android.annotation.NonNull;
     21 import android.annotation.Nullable;
     22 import android.annotation.RequiresPermission;
     23 import android.annotation.SystemApi;
     24 import android.content.Context;
     25 import android.content.pm.PackageManager;
     26 import android.graphics.Canvas;
     27 import android.graphics.PorterDuff;
     28 import android.graphics.Rect;
     29 import android.graphics.RectF;
     30 import android.graphics.Region;
     31 import android.media.PlaybackParams;
     32 import android.media.tv.TvInputManager.Session;
     33 import android.media.tv.TvInputManager.Session.FinishedInputEventCallback;
     34 import android.media.tv.TvInputManager.SessionCallback;
     35 import android.net.Uri;
     36 import android.os.Bundle;
     37 import android.os.Handler;
     38 import android.text.TextUtils;
     39 import android.util.AttributeSet;
     40 import android.util.Log;
     41 import android.util.Pair;
     42 import android.view.InputEvent;
     43 import android.view.KeyEvent;
     44 import android.view.MotionEvent;
     45 import android.view.Surface;
     46 import android.view.SurfaceHolder;
     47 import android.view.SurfaceView;
     48 import android.view.View;
     49 import android.view.ViewGroup;
     50 import android.view.ViewRootImpl;
     51 
     52 import java.lang.ref.WeakReference;
     53 import java.util.ArrayDeque;
     54 import java.util.List;
     55 import java.util.Queue;
     56 
     57 /**
     58  * Displays TV contents. The TvView class provides a high level interface for applications to show
     59  * TV programs from various TV sources that implement {@link TvInputService}. (Note that the list of
     60  * TV inputs available on the system can be obtained by calling
     61  * {@link TvInputManager#getTvInputList() TvInputManager.getTvInputList()}.)
     62  *
     63  * <p>Once the application supplies the URI for a specific TV channel to {@link #tune}
     64  * method, it takes care of underlying service binding (and unbinding if the current TvView is
     65  * already bound to a service) and automatically allocates/deallocates resources needed. In addition
     66  * to a few essential methods to control how the contents are presented, it also provides a way to
     67  * dispatch input events to the connected TvInputService in order to enable custom key actions for
     68  * the TV input.
     69  */
     70 public class TvView extends ViewGroup {
     71     private static final String TAG = "TvView";
     72     private static final boolean DEBUG = false;
     73 
     74     private static final int ZORDER_MEDIA = 0;
     75     private static final int ZORDER_MEDIA_OVERLAY = 1;
     76     private static final int ZORDER_ON_TOP = 2;
     77 
     78     private static final WeakReference<TvView> NULL_TV_VIEW = new WeakReference<>(null);
     79 
     80     private static final Object sMainTvViewLock = new Object();
     81     private static WeakReference<TvView> sMainTvView = NULL_TV_VIEW;
     82 
     83     private final Handler mHandler = new Handler();
     84     private Session mSession;
     85     private SurfaceView mSurfaceView;
     86     private Surface mSurface;
     87     private boolean mOverlayViewCreated;
     88     private Rect mOverlayViewFrame;
     89     private final TvInputManager mTvInputManager;
     90     private MySessionCallback mSessionCallback;
     91     private TvInputCallback mCallback;
     92     private OnUnhandledInputEventListener mOnUnhandledInputEventListener;
     93     private Float mStreamVolume;
     94     private Boolean mCaptionEnabled;
     95     private final Queue<Pair<String, Bundle>> mPendingAppPrivateCommands = new ArrayDeque<>();
     96 
     97     private boolean mSurfaceChanged;
     98     private int mSurfaceFormat;
     99     private int mSurfaceWidth;
    100     private int mSurfaceHeight;
    101     private final AttributeSet mAttrs;
    102     private final int mDefStyleAttr;
    103     private int mWindowZOrder;
    104     private boolean mUseRequestedSurfaceLayout;
    105     private int mSurfaceViewLeft;
    106     private int mSurfaceViewRight;
    107     private int mSurfaceViewTop;
    108     private int mSurfaceViewBottom;
    109     private TimeShiftPositionCallback mTimeShiftPositionCallback;
    110 
    111     private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
    112         @Override
    113         public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    114             if (DEBUG) {
    115                 Log.d(TAG, "surfaceChanged(holder=" + holder + ", format=" + format + ", width="
    116                     + width + ", height=" + height + ")");
    117             }
    118             mSurfaceFormat = format;
    119             mSurfaceWidth = width;
    120             mSurfaceHeight = height;
    121             mSurfaceChanged = true;
    122             dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
    123         }
    124 
    125         @Override
    126         public void surfaceCreated(SurfaceHolder holder) {
    127             mSurface = holder.getSurface();
    128             setSessionSurface(mSurface);
    129         }
    130 
    131         @Override
    132         public void surfaceDestroyed(SurfaceHolder holder) {
    133             mSurface = null;
    134             mSurfaceChanged = false;
    135             setSessionSurface(null);
    136         }
    137     };
    138 
    139     private final FinishedInputEventCallback mFinishedInputEventCallback =
    140             new FinishedInputEventCallback() {
    141         @Override
    142         public void onFinishedInputEvent(Object token, boolean handled) {
    143             if (DEBUG) {
    144                 Log.d(TAG, "onFinishedInputEvent(token=" + token + ", handled=" + handled + ")");
    145             }
    146             if (handled) {
    147                 return;
    148             }
    149             // TODO: Re-order unhandled events.
    150             InputEvent event = (InputEvent) token;
    151             if (dispatchUnhandledInputEvent(event)) {
    152                 return;
    153             }
    154             ViewRootImpl viewRootImpl = getViewRootImpl();
    155             if (viewRootImpl != null) {
    156                 viewRootImpl.dispatchUnhandledInputEvent(event);
    157             }
    158         }
    159     };
    160 
    161     public TvView(Context context) {
    162         this(context, null, 0);
    163     }
    164 
    165     public TvView(Context context, AttributeSet attrs) {
    166         this(context, attrs, 0);
    167     }
    168 
    169     public TvView(Context context, AttributeSet attrs, int defStyleAttr) {
    170         super(context, attrs, defStyleAttr);
    171         mAttrs = attrs;
    172         mDefStyleAttr = defStyleAttr;
    173         resetSurfaceView();
    174         mTvInputManager = (TvInputManager) getContext().getSystemService(Context.TV_INPUT_SERVICE);
    175     }
    176 
    177     /**
    178      * Sets the callback to be invoked when an event is dispatched to this TvView.
    179      *
    180      * @param callback The callback to receive events. A value of {@code null} removes the existing
    181      *            callback.
    182      */
    183     public void setCallback(@Nullable TvInputCallback callback) {
    184         mCallback = callback;
    185     }
    186 
    187     /**
    188      * Sets this as the main {@link TvView}.
    189      *
    190      * <p>The main {@link TvView} is a {@link TvView} whose corresponding TV input determines the
    191      * HDMI-CEC active source device. For an HDMI port input, one of source devices that is
    192      * connected to that HDMI port becomes the active source. For an HDMI-CEC logical device input,
    193      * the corresponding HDMI-CEC logical device becomes the active source. For any non-HDMI input
    194      * (including the tuner, composite, S-Video, etc.), the internal device (= TV itself) becomes
    195      * the active source.
    196      *
    197      * <p>First tuned {@link TvView} becomes main automatically, and keeps to be main until either
    198      * {@link #reset} is called for the main {@link TvView} or {@code setMain()} is called for other
    199      * {@link TvView}.
    200      * @hide
    201      */
    202     @SystemApi
    203     @RequiresPermission(android.Manifest.permission.CHANGE_HDMI_CEC_ACTIVE_SOURCE)
    204     public void setMain() {
    205         synchronized (sMainTvViewLock) {
    206             sMainTvView = new WeakReference<>(this);
    207             if (hasWindowFocus() && mSession != null) {
    208                 mSession.setMain();
    209             }
    210         }
    211     }
    212 
    213     /**
    214      * Controls whether the TvView's surface is placed on top of another regular surface view in the
    215      * window (but still behind the window itself).
    216      * This is typically used to place overlays on top of an underlying TvView.
    217      *
    218      * <p>Note that this must be set before the TvView's containing window is attached to the
    219      * window manager.
    220      *
    221      * <p>Calling this overrides any previous call to {@link #setZOrderOnTop}.
    222      *
    223      * @param isMediaOverlay {@code true} to be on top of another regular surface, {@code false}
    224      *            otherwise.
    225      */
    226     public void setZOrderMediaOverlay(boolean isMediaOverlay) {
    227         if (isMediaOverlay) {
    228             mWindowZOrder = ZORDER_MEDIA_OVERLAY;
    229             removeSessionOverlayView();
    230         } else {
    231             mWindowZOrder = ZORDER_MEDIA;
    232             createSessionOverlayView();
    233         }
    234         if (mSurfaceView != null) {
    235             // ZOrderOnTop(false) removes WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
    236             // from WindowLayoutParam as well as changes window type.
    237             mSurfaceView.setZOrderOnTop(false);
    238             mSurfaceView.setZOrderMediaOverlay(isMediaOverlay);
    239         }
    240     }
    241 
    242     /**
    243      * Controls whether the TvView's surface is placed on top of its window. Normally it is placed
    244      * behind the window, to allow it to (for the most part) appear to composite with the views in
    245      * the hierarchy.  By setting this, you cause it to be placed above the window. This means that
    246      * none of the contents of the window this TvView is in will be visible on top of its surface.
    247      *
    248      * <p>Note that this must be set before the TvView's containing window is attached to the window
    249      * manager.
    250      *
    251      * <p>Calling this overrides any previous call to {@link #setZOrderMediaOverlay}.
    252      *
    253      * @param onTop {@code true} to be on top of its window, {@code false} otherwise.
    254      */
    255     public void setZOrderOnTop(boolean onTop) {
    256         if (onTop) {
    257             mWindowZOrder = ZORDER_ON_TOP;
    258             removeSessionOverlayView();
    259         } else {
    260             mWindowZOrder = ZORDER_MEDIA;
    261             createSessionOverlayView();
    262         }
    263         if (mSurfaceView != null) {
    264             mSurfaceView.setZOrderMediaOverlay(false);
    265             mSurfaceView.setZOrderOnTop(onTop);
    266         }
    267      }
    268 
    269     /**
    270      * Sets the relative stream volume of this TvView.
    271      *
    272      * <p>This method is primarily used to handle audio focus changes or mute a specific TvView when
    273      * multiple views are displayed. If the method has not yet been called, the TvView assumes the
    274      * default value of {@code 1.0f}.
    275      *
    276      * @param volume A volume value between {@code 0.0f} to {@code 1.0f}.
    277      */
    278     public void setStreamVolume(@FloatRange(from = 0.0, to = 1.0) float volume) {
    279         if (DEBUG) Log.d(TAG, "setStreamVolume(" + volume + ")");
    280         mStreamVolume = volume;
    281         if (mSession == null) {
    282             // Volume will be set once the connection has been made.
    283             return;
    284         }
    285         mSession.setStreamVolume(volume);
    286     }
    287 
    288     /**
    289      * Tunes to a given channel.
    290      *
    291      * @param inputId The ID of the TV input for the given channel.
    292      * @param channelUri The URI of a channel.
    293      */
    294     public void tune(@NonNull String inputId, Uri channelUri) {
    295         tune(inputId, channelUri, null);
    296     }
    297 
    298     /**
    299      * Tunes to a given channel. This can be used to provide domain-specific features that are only
    300      * known between certain clients and their TV inputs.
    301      *
    302      * @param inputId The ID of TV input for the given channel.
    303      * @param channelUri The URI of a channel.
    304      * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped
    305      *            name, i.e. prefixed with a package name you own, so that different developers will
    306      *            not create conflicting keys.
    307      */
    308     public void tune(String inputId, Uri channelUri, Bundle params) {
    309         if (DEBUG) Log.d(TAG, "tune(" + channelUri + ")");
    310         if (TextUtils.isEmpty(inputId)) {
    311             throw new IllegalArgumentException("inputId cannot be null or an empty string");
    312         }
    313         synchronized (sMainTvViewLock) {
    314             if (sMainTvView.get() == null) {
    315                 sMainTvView = new WeakReference<>(this);
    316             }
    317         }
    318         if (mSessionCallback != null && TextUtils.equals(mSessionCallback.mInputId, inputId)) {
    319             if (mSession != null) {
    320                 mSession.tune(channelUri, params);
    321             } else {
    322                 // createSession() was called but the actual session for the given inputId has not
    323                 // yet been created. Just replace the existing tuning params in the callback with
    324                 // the new ones and tune later in onSessionCreated(). It is not necessary to create
    325                 // a new callback because this tuning request was made on the same inputId.
    326                 mSessionCallback.mChannelUri = channelUri;
    327                 mSessionCallback.mTuneParams = params;
    328             }
    329         } else {
    330             resetInternal();
    331             // In case createSession() is called multiple times across different inputId's before
    332             // any session is created (e.g. when quickly tuning to a channel from input A and then
    333             // to another channel from input B), only the callback for the last createSession()
    334             // should be invoked. (The previous callbacks are simply ignored.) To do that, we create
    335             // a new callback each time and keep mSessionCallback pointing to the last one. If
    336             // MySessionCallback.this is different from mSessionCallback, we know that this callback
    337             // is obsolete and should ignore it.
    338             mSessionCallback = new MySessionCallback(inputId, channelUri, params);
    339             if (mTvInputManager != null) {
    340                 mTvInputManager.createSession(inputId, mSessionCallback, mHandler);
    341             }
    342         }
    343     }
    344 
    345     /**
    346      * Resets this TvView.
    347      *
    348      * <p>This method is primarily used to un-tune the current TvView.
    349      */
    350     public void reset() {
    351         if (DEBUG) Log.d(TAG, "reset()");
    352         synchronized (sMainTvViewLock) {
    353             if (this == sMainTvView.get()) {
    354                 sMainTvView = NULL_TV_VIEW;
    355             }
    356         }
    357         resetInternal();
    358     }
    359 
    360     private void resetInternal() {
    361         mSessionCallback = null;
    362         mPendingAppPrivateCommands.clear();
    363         if (mSession != null) {
    364             setSessionSurface(null);
    365             removeSessionOverlayView();
    366             mUseRequestedSurfaceLayout = false;
    367             mSession.release();
    368             mSession = null;
    369             resetSurfaceView();
    370         }
    371     }
    372 
    373     /**
    374      * Requests to unblock TV content according to the given rating.
    375      *
    376      * <p>This notifies TV input that blocked content is now OK to play.
    377      *
    378      * @param unblockedRating A TvContentRating to unblock.
    379      * @see TvInputService.Session#notifyContentBlocked(TvContentRating)
    380      * @removed
    381      */
    382     public void requestUnblockContent(TvContentRating unblockedRating) {
    383         unblockContent(unblockedRating);
    384     }
    385 
    386     /**
    387      * Requests to unblock TV content according to the given rating.
    388      *
    389      * <p>This notifies TV input that blocked content is now OK to play.
    390      *
    391      * @param unblockedRating A TvContentRating to unblock.
    392      * @see TvInputService.Session#notifyContentBlocked(TvContentRating)
    393      * @hide
    394      */
    395     @SystemApi
    396     @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS)
    397     public void unblockContent(TvContentRating unblockedRating) {
    398         if (mSession != null) {
    399             mSession.unblockContent(unblockedRating);
    400         }
    401     }
    402 
    403     /**
    404      * Enables or disables the caption in this TvView.
    405      *
    406      * <p>Note that this method does not take any effect unless the current TvView is tuned.
    407      *
    408      * @param enabled {@code true} to enable, {@code false} to disable.
    409      */
    410     public void setCaptionEnabled(boolean enabled) {
    411         if (DEBUG) Log.d(TAG, "setCaptionEnabled(" + enabled + ")");
    412         mCaptionEnabled = enabled;
    413         if (mSession != null) {
    414             mSession.setCaptionEnabled(enabled);
    415         }
    416     }
    417 
    418     /**
    419      * Selects a track.
    420      *
    421      * @param type The type of the track to select. The type can be {@link TvTrackInfo#TYPE_AUDIO},
    422      *            {@link TvTrackInfo#TYPE_VIDEO} or {@link TvTrackInfo#TYPE_SUBTITLE}.
    423      * @param trackId The ID of the track to select. {@code null} means to unselect the current
    424      *            track for a given type.
    425      * @see #getTracks
    426      * @see #getSelectedTrack
    427      */
    428     public void selectTrack(int type, String trackId) {
    429         if (mSession != null) {
    430             mSession.selectTrack(type, trackId);
    431         }
    432     }
    433 
    434     /**
    435      * Returns the list of tracks. Returns {@code null} if the information is not available.
    436      *
    437      * @param type The type of the tracks. The type can be {@link TvTrackInfo#TYPE_AUDIO},
    438      *            {@link TvTrackInfo#TYPE_VIDEO} or {@link TvTrackInfo#TYPE_SUBTITLE}.
    439      * @see #selectTrack
    440      * @see #getSelectedTrack
    441      */
    442     public List<TvTrackInfo> getTracks(int type) {
    443         if (mSession == null) {
    444             return null;
    445         }
    446         return mSession.getTracks(type);
    447     }
    448 
    449     /**
    450      * Returns the ID of the selected track for a given type. Returns {@code null} if the
    451      * information is not available or the track is not selected.
    452      *
    453      * @param type The type of the selected tracks. The type can be {@link TvTrackInfo#TYPE_AUDIO},
    454      *            {@link TvTrackInfo#TYPE_VIDEO} or {@link TvTrackInfo#TYPE_SUBTITLE}.
    455      * @see #selectTrack
    456      * @see #getTracks
    457      */
    458     public String getSelectedTrack(int type) {
    459         if (mSession == null) {
    460             return null;
    461         }
    462         return mSession.getSelectedTrack(type);
    463     }
    464 
    465     /**
    466      * Plays a given recorded TV program.
    467      *
    468      * @param inputId The ID of the TV input that created the given recorded program.
    469      * @param recordedProgramUri The URI of a recorded program.
    470      */
    471     public void timeShiftPlay(String inputId, Uri recordedProgramUri) {
    472         if (DEBUG) Log.d(TAG, "timeShiftPlay(" + recordedProgramUri + ")");
    473         if (TextUtils.isEmpty(inputId)) {
    474             throw new IllegalArgumentException("inputId cannot be null or an empty string");
    475         }
    476         synchronized (sMainTvViewLock) {
    477             if (sMainTvView.get() == null) {
    478                 sMainTvView = new WeakReference<>(this);
    479             }
    480         }
    481         if (mSessionCallback != null && TextUtils.equals(mSessionCallback.mInputId, inputId)) {
    482             if (mSession != null) {
    483                 mSession.timeShiftPlay(recordedProgramUri);
    484             } else {
    485                 mSessionCallback.mRecordedProgramUri = recordedProgramUri;
    486             }
    487         } else {
    488             resetInternal();
    489             mSessionCallback = new MySessionCallback(inputId, recordedProgramUri);
    490             if (mTvInputManager != null) {
    491                 mTvInputManager.createSession(inputId, mSessionCallback, mHandler);
    492             }
    493         }
    494     }
    495 
    496     /**
    497      * Pauses playback. No-op if it is already paused. Call {@link #timeShiftResume} to resume.
    498      */
    499     public void timeShiftPause() {
    500         if (mSession != null) {
    501             mSession.timeShiftPause();
    502         }
    503     }
    504 
    505     /**
    506      * Resumes playback. No-op if it is already resumed. Call {@link #timeShiftPause} to pause.
    507      */
    508     public void timeShiftResume() {
    509         if (mSession != null) {
    510             mSession.timeShiftResume();
    511         }
    512     }
    513 
    514     /**
    515      * Seeks to a specified time position. {@code timeMs} must be equal to or greater than the start
    516      * position returned by {@link TimeShiftPositionCallback#onTimeShiftStartPositionChanged} and
    517      * equal to or less than the current time.
    518      *
    519      * @param timeMs The time position to seek to, in milliseconds since the epoch.
    520      */
    521     public void timeShiftSeekTo(long timeMs) {
    522         if (mSession != null) {
    523             mSession.timeShiftSeekTo(timeMs);
    524         }
    525     }
    526 
    527     /**
    528      * Sets playback rate using {@link android.media.PlaybackParams}.
    529      *
    530      * @param params The playback params.
    531      */
    532     public void timeShiftSetPlaybackParams(@NonNull PlaybackParams params) {
    533         if (mSession != null) {
    534             mSession.timeShiftSetPlaybackParams(params);
    535         }
    536     }
    537 
    538     /**
    539      * Sets the callback to be invoked when the time shift position is changed.
    540      *
    541      * @param callback The callback to receive time shift position changes. A value of {@code null}
    542      *            removes the existing callback.
    543      */
    544     public void setTimeShiftPositionCallback(@Nullable TimeShiftPositionCallback callback) {
    545         mTimeShiftPositionCallback = callback;
    546         ensurePositionTracking();
    547     }
    548 
    549     private void ensurePositionTracking() {
    550         if (mSession == null) {
    551             return;
    552         }
    553         mSession.timeShiftEnablePositionTracking(mTimeShiftPositionCallback != null);
    554     }
    555 
    556     /**
    557      * Sends a private command to the underlying TV input. This can be used to provide
    558      * domain-specific features that are only known between certain clients and their TV inputs.
    559      *
    560      * @param action The name of the private command to send. This <em>must</em> be a scoped name,
    561      *            i.e. prefixed with a package name you own, so that different developers will not
    562      *            create conflicting commands.
    563      * @param data An optional bundle to send with the command.
    564      */
    565     public void sendAppPrivateCommand(@NonNull String action, Bundle data) {
    566         if (TextUtils.isEmpty(action)) {
    567             throw new IllegalArgumentException("action cannot be null or an empty string");
    568         }
    569         if (mSession != null) {
    570             mSession.sendAppPrivateCommand(action, data);
    571         } else {
    572             Log.w(TAG, "sendAppPrivateCommand - session not yet created (action \"" + action
    573                     + "\" pending)");
    574             mPendingAppPrivateCommands.add(Pair.create(action, data));
    575         }
    576     }
    577 
    578     /**
    579      * Dispatches an unhandled input event to the next receiver.
    580      *
    581      * <p>Except system keys, TvView always consumes input events in the normal flow. This is called
    582      * asynchronously from where the event is dispatched. It gives the host application a chance to
    583      * dispatch the unhandled input events.
    584      *
    585      * @param event The input event.
    586      * @return {@code true} if the event was handled by the view, {@code false} otherwise.
    587      */
    588     public boolean dispatchUnhandledInputEvent(InputEvent event) {
    589         if (mOnUnhandledInputEventListener != null) {
    590             if (mOnUnhandledInputEventListener.onUnhandledInputEvent(event)) {
    591                 return true;
    592             }
    593         }
    594         return onUnhandledInputEvent(event);
    595     }
    596 
    597     /**
    598      * Called when an unhandled input event also has not been handled by the user provided
    599      * callback. This is the last chance to handle the unhandled input event in the TvView.
    600      *
    601      * @param event The input event.
    602      * @return If you handled the event, return {@code true}. If you want to allow the event to be
    603      *         handled by the next receiver, return {@code false}.
    604      */
    605     public boolean onUnhandledInputEvent(InputEvent event) {
    606         return false;
    607     }
    608 
    609     /**
    610      * Registers a callback to be invoked when an input event is not handled by the bound TV input.
    611      *
    612      * @param listener The callback to be invoked when the unhandled input event is received.
    613      */
    614     public void setOnUnhandledInputEventListener(OnUnhandledInputEventListener listener) {
    615         mOnUnhandledInputEventListener = listener;
    616     }
    617 
    618     @Override
    619     public boolean dispatchKeyEvent(KeyEvent event) {
    620         if (super.dispatchKeyEvent(event)) {
    621             return true;
    622         }
    623         if (DEBUG) Log.d(TAG, "dispatchKeyEvent(" + event + ")");
    624         if (mSession == null) {
    625             return false;
    626         }
    627         InputEvent copiedEvent = event.copy();
    628         int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback,
    629                 mHandler);
    630         return ret != Session.DISPATCH_NOT_HANDLED;
    631     }
    632 
    633     @Override
    634     public boolean dispatchTouchEvent(MotionEvent event) {
    635         if (super.dispatchTouchEvent(event)) {
    636             return true;
    637         }
    638         if (DEBUG) Log.d(TAG, "dispatchTouchEvent(" + event + ")");
    639         if (mSession == null) {
    640             return false;
    641         }
    642         InputEvent copiedEvent = event.copy();
    643         int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback,
    644                 mHandler);
    645         return ret != Session.DISPATCH_NOT_HANDLED;
    646     }
    647 
    648     @Override
    649     public boolean dispatchTrackballEvent(MotionEvent event) {
    650         if (super.dispatchTrackballEvent(event)) {
    651             return true;
    652         }
    653         if (DEBUG) Log.d(TAG, "dispatchTrackballEvent(" + event + ")");
    654         if (mSession == null) {
    655             return false;
    656         }
    657         InputEvent copiedEvent = event.copy();
    658         int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback,
    659                 mHandler);
    660         return ret != Session.DISPATCH_NOT_HANDLED;
    661     }
    662 
    663     @Override
    664     public boolean dispatchGenericMotionEvent(MotionEvent event) {
    665         if (super.dispatchGenericMotionEvent(event)) {
    666             return true;
    667         }
    668         if (DEBUG) Log.d(TAG, "dispatchGenericMotionEvent(" + event + ")");
    669         if (mSession == null) {
    670             return false;
    671         }
    672         InputEvent copiedEvent = event.copy();
    673         int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback,
    674                 mHandler);
    675         return ret != Session.DISPATCH_NOT_HANDLED;
    676     }
    677 
    678     @Override
    679     public void dispatchWindowFocusChanged(boolean hasFocus) {
    680         super.dispatchWindowFocusChanged(hasFocus);
    681         // Other app may have shown its own main TvView.
    682         // Set main again to regain main session.
    683         synchronized (sMainTvViewLock) {
    684             if (hasFocus && this == sMainTvView.get() && mSession != null
    685                     && checkChangeHdmiCecActiveSourcePermission()) {
    686                 mSession.setMain();
    687             }
    688         }
    689     }
    690 
    691     @Override
    692     protected void onAttachedToWindow() {
    693         super.onAttachedToWindow();
    694         createSessionOverlayView();
    695     }
    696 
    697     @Override
    698     protected void onDetachedFromWindow() {
    699         removeSessionOverlayView();
    700         super.onDetachedFromWindow();
    701     }
    702 
    703     @Override
    704     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    705         if (DEBUG) {
    706             Log.d(TAG, "onLayout (left=" + left + ", top=" + top + ", right=" + right
    707                     + ", bottom=" + bottom + ",)");
    708         }
    709         if (mUseRequestedSurfaceLayout) {
    710             mSurfaceView.layout(mSurfaceViewLeft, mSurfaceViewTop, mSurfaceViewRight,
    711                     mSurfaceViewBottom);
    712         } else {
    713             mSurfaceView.layout(0, 0, right - left, bottom - top);
    714         }
    715     }
    716 
    717     @Override
    718     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    719         mSurfaceView.measure(widthMeasureSpec, heightMeasureSpec);
    720         int width = mSurfaceView.getMeasuredWidth();
    721         int height = mSurfaceView.getMeasuredHeight();
    722         int childState = mSurfaceView.getMeasuredState();
    723         setMeasuredDimension(resolveSizeAndState(width, widthMeasureSpec, childState),
    724                 resolveSizeAndState(height, heightMeasureSpec,
    725                         childState << MEASURED_HEIGHT_STATE_SHIFT));
    726     }
    727 
    728     @Override
    729     public boolean gatherTransparentRegion(Region region) {
    730         if (mWindowZOrder != ZORDER_ON_TOP) {
    731             if (region != null) {
    732                 int width = getWidth();
    733                 int height = getHeight();
    734                 if (width > 0 && height > 0) {
    735                     int location[] = new int[2];
    736                     getLocationInWindow(location);
    737                     int left = location[0];
    738                     int top = location[1];
    739                     region.op(left, top, left + width, top + height, Region.Op.UNION);
    740                 }
    741             }
    742         }
    743         return super.gatherTransparentRegion(region);
    744     }
    745 
    746     @Override
    747     public void draw(Canvas canvas) {
    748         if (mWindowZOrder != ZORDER_ON_TOP) {
    749             // Punch a hole so that the underlying overlay view and surface can be shown.
    750             canvas.drawColor(0, PorterDuff.Mode.CLEAR);
    751         }
    752         super.draw(canvas);
    753     }
    754 
    755     @Override
    756     protected void dispatchDraw(Canvas canvas) {
    757         if (mWindowZOrder != ZORDER_ON_TOP) {
    758             // Punch a hole so that the underlying overlay view and surface can be shown.
    759             canvas.drawColor(0, PorterDuff.Mode.CLEAR);
    760         }
    761         super.dispatchDraw(canvas);
    762     }
    763 
    764     @Override
    765     protected void onVisibilityChanged(View changedView, int visibility) {
    766         super.onVisibilityChanged(changedView, visibility);
    767         mSurfaceView.setVisibility(visibility);
    768         if (visibility == View.VISIBLE) {
    769             createSessionOverlayView();
    770         } else {
    771             removeSessionOverlayView();
    772         }
    773     }
    774 
    775     private void resetSurfaceView() {
    776         if (mSurfaceView != null) {
    777             mSurfaceView.getHolder().removeCallback(mSurfaceHolderCallback);
    778             removeView(mSurfaceView);
    779         }
    780         mSurface = null;
    781         mSurfaceView = new SurfaceView(getContext(), mAttrs, mDefStyleAttr) {
    782             @Override
    783             protected void updateSurface() {
    784                 super.updateSurface();
    785                 relayoutSessionOverlayView();
    786             }};
    787         // The surface view's content should be treated as secure all the time.
    788         mSurfaceView.setSecure(true);
    789         mSurfaceView.getHolder().addCallback(mSurfaceHolderCallback);
    790         if (mWindowZOrder == ZORDER_MEDIA_OVERLAY) {
    791             mSurfaceView.setZOrderMediaOverlay(true);
    792         } else if (mWindowZOrder == ZORDER_ON_TOP) {
    793             mSurfaceView.setZOrderOnTop(true);
    794         }
    795         addView(mSurfaceView);
    796     }
    797 
    798     private void setSessionSurface(Surface surface) {
    799         if (mSession == null) {
    800             return;
    801         }
    802         mSession.setSurface(surface);
    803     }
    804 
    805     private void dispatchSurfaceChanged(int format, int width, int height) {
    806         if (mSession == null) {
    807             return;
    808         }
    809         mSession.dispatchSurfaceChanged(format, width, height);
    810     }
    811 
    812     private void createSessionOverlayView() {
    813         if (mSession == null || !isAttachedToWindow()
    814                 || mOverlayViewCreated || mWindowZOrder != ZORDER_MEDIA) {
    815             return;
    816         }
    817         mOverlayViewFrame = getViewFrameOnScreen();
    818         mSession.createOverlayView(this, mOverlayViewFrame);
    819         mOverlayViewCreated = true;
    820     }
    821 
    822     private void removeSessionOverlayView() {
    823         if (mSession == null || !mOverlayViewCreated) {
    824             return;
    825         }
    826         mSession.removeOverlayView();
    827         mOverlayViewCreated = false;
    828         mOverlayViewFrame = null;
    829     }
    830 
    831     private void relayoutSessionOverlayView() {
    832         if (mSession == null || !isAttachedToWindow() || !mOverlayViewCreated
    833                 || mWindowZOrder != ZORDER_MEDIA) {
    834             return;
    835         }
    836         Rect viewFrame = getViewFrameOnScreen();
    837         if (viewFrame.equals(mOverlayViewFrame)) {
    838             return;
    839         }
    840         mSession.relayoutOverlayView(viewFrame);
    841         mOverlayViewFrame = viewFrame;
    842     }
    843 
    844     private Rect getViewFrameOnScreen() {
    845         Rect frame = new Rect();
    846         getGlobalVisibleRect(frame);
    847         RectF frameF = new RectF(frame);
    848         getMatrix().mapRect(frameF);
    849         frameF.round(frame);
    850         return frame;
    851     }
    852 
    853     private boolean checkChangeHdmiCecActiveSourcePermission() {
    854         return getContext().checkSelfPermission(
    855                 android.Manifest.permission.CHANGE_HDMI_CEC_ACTIVE_SOURCE)
    856                         == PackageManager.PERMISSION_GRANTED;
    857     }
    858 
    859     /**
    860      * Callback used to receive time shift position changes.
    861      */
    862     public abstract static class TimeShiftPositionCallback {
    863 
    864         /**
    865          * This is called when the start position for time shifting has changed.
    866          *
    867          * <p>The start position for time shifting indicates the earliest possible time the user can
    868          * seek to. Initially this is equivalent to the time when the underlying TV input starts
    869          * recording. Later it may be adjusted because there is insufficient space or the duration
    870          * of recording is limited. The application must not allow the user to seek to a position
    871          * earlier than the start position.
    872          *
    873          * <p>For playback of a recorded program initiated by {@link #timeShiftPlay(String, Uri)},
    874          * the start position is the time when playback starts. It does not change.
    875          *
    876          * @param inputId The ID of the TV input bound to this view.
    877          * @param timeMs The start position for time shifting, in milliseconds since the epoch.
    878          */
    879         public void onTimeShiftStartPositionChanged(String inputId, long timeMs) {
    880         }
    881 
    882         /**
    883          * This is called when the current position for time shifting has changed.
    884          *
    885          * <p>The current position for time shifting is the same as the current position of
    886          * playback. During playback, the current position changes continuously. When paused, it
    887          * does not change.
    888          *
    889          * <p>Note that {@code timeMs} is wall-clock time.
    890          *
    891          * @param inputId The ID of the TV input bound to this view.
    892          * @param timeMs The current position for time shifting, in milliseconds since the epoch.
    893          */
    894         public void onTimeShiftCurrentPositionChanged(String inputId, long timeMs) {
    895         }
    896     }
    897 
    898     /**
    899      * Callback used to receive various status updates on the {@link TvView}.
    900      */
    901     public abstract static class TvInputCallback {
    902 
    903         /**
    904          * This is invoked when an error occurred while establishing a connection to the underlying
    905          * TV input.
    906          *
    907          * @param inputId The ID of the TV input bound to this view.
    908          */
    909         public void onConnectionFailed(String inputId) {
    910         }
    911 
    912         /**
    913          * This is invoked when the existing connection to the underlying TV input is lost.
    914          *
    915          * @param inputId The ID of the TV input bound to this view.
    916          */
    917         public void onDisconnected(String inputId) {
    918         }
    919 
    920         /**
    921          * This is invoked when the channel of this TvView is changed by the underlying TV input
    922          * without any {@link TvView#tune} request.
    923          *
    924          * @param inputId The ID of the TV input bound to this view.
    925          * @param channelUri The URI of a channel.
    926          */
    927         public void onChannelRetuned(String inputId, Uri channelUri) {
    928         }
    929 
    930         /**
    931          * This is called when the track information has been changed.
    932          *
    933          * @param inputId The ID of the TV input bound to this view.
    934          * @param tracks A list which includes track information.
    935          */
    936         public void onTracksChanged(String inputId, List<TvTrackInfo> tracks) {
    937         }
    938 
    939         /**
    940          * This is called when there is a change on the selected tracks.
    941          *
    942          * @param inputId The ID of the TV input bound to this view.
    943          * @param type The type of the track selected. The type can be
    944          *            {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or
    945          *            {@link TvTrackInfo#TYPE_SUBTITLE}.
    946          * @param trackId The ID of the track selected.
    947          */
    948         public void onTrackSelected(String inputId, int type, String trackId) {
    949         }
    950 
    951         /**
    952          * This is invoked when the video size has been changed. It is also called when the first
    953          * time video size information becomes available after this view is tuned to a specific
    954          * channel.
    955          *
    956          * @param inputId The ID of the TV input bound to this view.
    957          * @param width The width of the video.
    958          * @param height The height of the video.
    959          */
    960         public void onVideoSizeChanged(String inputId, int width, int height) {
    961         }
    962 
    963         /**
    964          * This is called when the video is available, so the TV input starts the playback.
    965          *
    966          * @param inputId The ID of the TV input bound to this view.
    967          */
    968         public void onVideoAvailable(String inputId) {
    969         }
    970 
    971         /**
    972          * This is called when the video is not available, so the TV input stops the playback.
    973          *
    974          * @param inputId The ID of the TV input bound to this view.
    975          * @param reason The reason why the TV input stopped the playback:
    976          * <ul>
    977          * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_UNKNOWN}
    978          * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING}
    979          * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL}
    980          * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_BUFFERING}
    981          * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY}
    982          * </ul>
    983          */
    984         public void onVideoUnavailable(
    985                 String inputId, @TvInputManager.VideoUnavailableReason int reason) {
    986         }
    987 
    988         /**
    989          * This is called when the current program content turns out to be allowed to watch since
    990          * its content rating is not blocked by parental controls.
    991          *
    992          * @param inputId The ID of the TV input bound to this view.
    993          */
    994         public void onContentAllowed(String inputId) {
    995         }
    996 
    997         /**
    998          * This is called when the current program content turns out to be not allowed to watch
    999          * since its content rating is blocked by parental controls.
   1000          *
   1001          * @param inputId The ID of the TV input bound to this view.
   1002          * @param rating The content rating of the blocked program.
   1003          */
   1004         public void onContentBlocked(String inputId, TvContentRating rating) {
   1005         }
   1006 
   1007         /**
   1008          * This is invoked when a custom event from the bound TV input is sent to this view.
   1009          *
   1010          * @param inputId The ID of the TV input bound to this view.
   1011          * @param eventType The type of the event.
   1012          * @param eventArgs Optional arguments of the event.
   1013          * @hide
   1014          */
   1015         @SystemApi
   1016         public void onEvent(String inputId, String eventType, Bundle eventArgs) {
   1017         }
   1018 
   1019         /**
   1020          * This is called when the time shift status is changed.
   1021          *
   1022          * @param inputId The ID of the TV input bound to this view.
   1023          * @param status The current time shift status. Should be one of the followings.
   1024          * <ul>
   1025          * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNSUPPORTED}
   1026          * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNAVAILABLE}
   1027          * <li>{@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE}
   1028          * </ul>
   1029          */
   1030         public void onTimeShiftStatusChanged(
   1031                 String inputId, @TvInputManager.TimeShiftStatus int status) {
   1032         }
   1033     }
   1034 
   1035     /**
   1036      * Interface definition for a callback to be invoked when the unhandled input event is received.
   1037      */
   1038     public interface OnUnhandledInputEventListener {
   1039         /**
   1040          * Called when an input event was not handled by the bound TV input.
   1041          *
   1042          * <p>This is called asynchronously from where the event is dispatched. It gives the host
   1043          * application a chance to handle the unhandled input events.
   1044          *
   1045          * @param event The input event.
   1046          * @return If you handled the event, return {@code true}. If you want to allow the event to
   1047          *         be handled by the next receiver, return {@code false}.
   1048          */
   1049         boolean onUnhandledInputEvent(InputEvent event);
   1050     }
   1051 
   1052     private class MySessionCallback extends SessionCallback {
   1053         final String mInputId;
   1054         Uri mChannelUri;
   1055         Bundle mTuneParams;
   1056         Uri mRecordedProgramUri;
   1057 
   1058         MySessionCallback(String inputId, Uri channelUri, Bundle tuneParams) {
   1059             mInputId = inputId;
   1060             mChannelUri = channelUri;
   1061             mTuneParams = tuneParams;
   1062         }
   1063 
   1064         MySessionCallback(String inputId, Uri recordedProgramUri) {
   1065             mInputId = inputId;
   1066             mRecordedProgramUri = recordedProgramUri;
   1067         }
   1068 
   1069         @Override
   1070         public void onSessionCreated(Session session) {
   1071             if (DEBUG) {
   1072                 Log.d(TAG, "onSessionCreated()");
   1073             }
   1074             if (this != mSessionCallback) {
   1075                 Log.w(TAG, "onSessionCreated - session already created");
   1076                 // This callback is obsolete.
   1077                 if (session != null) {
   1078                     session.release();
   1079                 }
   1080                 return;
   1081             }
   1082             mSession = session;
   1083             if (session != null) {
   1084                 // Sends the pending app private commands first.
   1085                 for (Pair<String, Bundle> command : mPendingAppPrivateCommands) {
   1086                     mSession.sendAppPrivateCommand(command.first, command.second);
   1087                 }
   1088                 mPendingAppPrivateCommands.clear();
   1089 
   1090                 synchronized (sMainTvViewLock) {
   1091                     if (hasWindowFocus() && TvView.this == sMainTvView.get()
   1092                             && checkChangeHdmiCecActiveSourcePermission()) {
   1093                         mSession.setMain();
   1094                     }
   1095                 }
   1096                 // mSurface may not be ready yet as soon as starting an application.
   1097                 // In the case, we don't send Session.setSurface(null) unnecessarily.
   1098                 // setSessionSurface will be called in surfaceCreated.
   1099                 if (mSurface != null) {
   1100                     setSessionSurface(mSurface);
   1101                     if (mSurfaceChanged) {
   1102                         dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
   1103                     }
   1104                 }
   1105                 createSessionOverlayView();
   1106                 if (mStreamVolume != null) {
   1107                     mSession.setStreamVolume(mStreamVolume);
   1108                 }
   1109                 if (mCaptionEnabled != null) {
   1110                     mSession.setCaptionEnabled(mCaptionEnabled);
   1111                 }
   1112                 if (mChannelUri != null) {
   1113                     mSession.tune(mChannelUri, mTuneParams);
   1114                 } else {
   1115                     mSession.timeShiftPlay(mRecordedProgramUri);
   1116                 }
   1117                 ensurePositionTracking();
   1118             } else {
   1119                 mSessionCallback = null;
   1120                 if (mCallback != null) {
   1121                     mCallback.onConnectionFailed(mInputId);
   1122                 }
   1123             }
   1124         }
   1125 
   1126         @Override
   1127         public void onSessionReleased(Session session) {
   1128             if (DEBUG) {
   1129                 Log.d(TAG, "onSessionReleased()");
   1130             }
   1131             if (this != mSessionCallback) {
   1132                 Log.w(TAG, "onSessionReleased - session not created");
   1133                 return;
   1134             }
   1135             mOverlayViewCreated = false;
   1136             mOverlayViewFrame = null;
   1137             mSessionCallback = null;
   1138             mSession = null;
   1139             if (mCallback != null) {
   1140                 mCallback.onDisconnected(mInputId);
   1141             }
   1142         }
   1143 
   1144         @Override
   1145         public void onChannelRetuned(Session session, Uri channelUri) {
   1146             if (DEBUG) {
   1147                 Log.d(TAG, "onChannelChangedByTvInput(" + channelUri + ")");
   1148             }
   1149             if (this != mSessionCallback) {
   1150                 Log.w(TAG, "onChannelRetuned - session not created");
   1151                 return;
   1152             }
   1153             if (mCallback != null) {
   1154                 mCallback.onChannelRetuned(mInputId, channelUri);
   1155             }
   1156         }
   1157 
   1158         @Override
   1159         public void onTracksChanged(Session session, List<TvTrackInfo> tracks) {
   1160             if (DEBUG) {
   1161                 Log.d(TAG, "onTracksChanged(" + tracks + ")");
   1162             }
   1163             if (this != mSessionCallback) {
   1164                 Log.w(TAG, "onTracksChanged - session not created");
   1165                 return;
   1166             }
   1167             if (mCallback != null) {
   1168                 mCallback.onTracksChanged(mInputId, tracks);
   1169             }
   1170         }
   1171 
   1172         @Override
   1173         public void onTrackSelected(Session session, int type, String trackId) {
   1174             if (DEBUG) {
   1175                 Log.d(TAG, "onTrackSelected(type=" + type + ", trackId=" + trackId + ")");
   1176             }
   1177             if (this != mSessionCallback) {
   1178                 Log.w(TAG, "onTrackSelected - session not created");
   1179                 return;
   1180             }
   1181             if (mCallback != null) {
   1182                 mCallback.onTrackSelected(mInputId, type, trackId);
   1183             }
   1184         }
   1185 
   1186         @Override
   1187         public void onVideoSizeChanged(Session session, int width, int height) {
   1188             if (DEBUG) {
   1189                 Log.d(TAG, "onVideoSizeChanged()");
   1190             }
   1191             if (this != mSessionCallback) {
   1192                 Log.w(TAG, "onVideoSizeChanged - session not created");
   1193                 return;
   1194             }
   1195             if (mCallback != null) {
   1196                 mCallback.onVideoSizeChanged(mInputId, width, height);
   1197             }
   1198         }
   1199 
   1200         @Override
   1201         public void onVideoAvailable(Session session) {
   1202             if (DEBUG) {
   1203                 Log.d(TAG, "onVideoAvailable()");
   1204             }
   1205             if (this != mSessionCallback) {
   1206                 Log.w(TAG, "onVideoAvailable - session not created");
   1207                 return;
   1208             }
   1209             if (mCallback != null) {
   1210                 mCallback.onVideoAvailable(mInputId);
   1211             }
   1212         }
   1213 
   1214         @Override
   1215         public void onVideoUnavailable(Session session, int reason) {
   1216             if (DEBUG) {
   1217                 Log.d(TAG, "onVideoUnavailable(reason=" + reason + ")");
   1218             }
   1219             if (this != mSessionCallback) {
   1220                 Log.w(TAG, "onVideoUnavailable - session not created");
   1221                 return;
   1222             }
   1223             if (mCallback != null) {
   1224                 mCallback.onVideoUnavailable(mInputId, reason);
   1225             }
   1226         }
   1227 
   1228         @Override
   1229         public void onContentAllowed(Session session) {
   1230             if (DEBUG) {
   1231                 Log.d(TAG, "onContentAllowed()");
   1232             }
   1233             if (this != mSessionCallback) {
   1234                 Log.w(TAG, "onContentAllowed - session not created");
   1235                 return;
   1236             }
   1237             if (mCallback != null) {
   1238                 mCallback.onContentAllowed(mInputId);
   1239             }
   1240         }
   1241 
   1242         @Override
   1243         public void onContentBlocked(Session session, TvContentRating rating) {
   1244             if (DEBUG) {
   1245                 Log.d(TAG, "onContentBlocked(rating=" + rating + ")");
   1246             }
   1247             if (this != mSessionCallback) {
   1248                 Log.w(TAG, "onContentBlocked - session not created");
   1249                 return;
   1250             }
   1251             if (mCallback != null) {
   1252                 mCallback.onContentBlocked(mInputId, rating);
   1253             }
   1254         }
   1255 
   1256         @Override
   1257         public void onLayoutSurface(Session session, int left, int top, int right, int bottom) {
   1258             if (DEBUG) {
   1259                 Log.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top + ", right="
   1260                         + right + ", bottom=" + bottom + ",)");
   1261             }
   1262             if (this != mSessionCallback) {
   1263                 Log.w(TAG, "onLayoutSurface - session not created");
   1264                 return;
   1265             }
   1266             mSurfaceViewLeft = left;
   1267             mSurfaceViewTop = top;
   1268             mSurfaceViewRight = right;
   1269             mSurfaceViewBottom = bottom;
   1270             mUseRequestedSurfaceLayout = true;
   1271             requestLayout();
   1272         }
   1273 
   1274         @Override
   1275         public void onSessionEvent(Session session, String eventType, Bundle eventArgs) {
   1276             if (DEBUG) {
   1277                 Log.d(TAG, "onSessionEvent(" + eventType + ")");
   1278             }
   1279             if (this != mSessionCallback) {
   1280                 Log.w(TAG, "onSessionEvent - session not created");
   1281                 return;
   1282             }
   1283             if (mCallback != null) {
   1284                 mCallback.onEvent(mInputId, eventType, eventArgs);
   1285             }
   1286         }
   1287 
   1288         @Override
   1289         public void onTimeShiftStatusChanged(Session session, int status) {
   1290             if (DEBUG) {
   1291                 Log.d(TAG, "onTimeShiftStatusChanged()");
   1292             }
   1293             if (this != mSessionCallback) {
   1294                 Log.w(TAG, "onTimeShiftStatusChanged - session not created");
   1295                 return;
   1296             }
   1297             if (mCallback != null) {
   1298                 mCallback.onTimeShiftStatusChanged(mInputId, status);
   1299             }
   1300         }
   1301 
   1302         @Override
   1303         public void onTimeShiftStartPositionChanged(Session session, long timeMs) {
   1304             if (DEBUG) {
   1305                 Log.d(TAG, "onTimeShiftStartPositionChanged()");
   1306             }
   1307             if (this != mSessionCallback) {
   1308                 Log.w(TAG, "onTimeShiftStartPositionChanged - session not created");
   1309                 return;
   1310             }
   1311             if (mTimeShiftPositionCallback != null) {
   1312                 mTimeShiftPositionCallback.onTimeShiftStartPositionChanged(mInputId, timeMs);
   1313             }
   1314         }
   1315 
   1316         @Override
   1317         public void onTimeShiftCurrentPositionChanged(Session session, long timeMs) {
   1318             if (DEBUG) {
   1319                 Log.d(TAG, "onTimeShiftCurrentPositionChanged()");
   1320             }
   1321             if (this != mSessionCallback) {
   1322                 Log.w(TAG, "onTimeShiftCurrentPositionChanged - session not created");
   1323                 return;
   1324             }
   1325             if (mTimeShiftPositionCallback != null) {
   1326                 mTimeShiftPositionCallback.onTimeShiftCurrentPositionChanged(mInputId, timeMs);
   1327             }
   1328         }
   1329     }
   1330 }
   1331