TvView.java revision d0f00588834806d3f52c95c2d5fb13d9a92bddfc
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
17package android.media.tv;
18
19import android.annotation.SystemApi;
20import android.content.Context;
21import android.graphics.Rect;
22import android.media.tv.TvInputManager.Session;
23import android.media.tv.TvInputManager.Session.FinishedInputEventCallback;
24import android.media.tv.TvInputManager.SessionCallback;
25import android.net.Uri;
26import android.os.Bundle;
27import android.os.Handler;
28import android.text.TextUtils;
29import android.util.AttributeSet;
30import android.util.Log;
31import android.view.InputEvent;
32import android.view.KeyEvent;
33import android.view.MotionEvent;
34import android.view.Surface;
35import android.view.SurfaceHolder;
36import android.view.SurfaceView;
37import android.view.View;
38import android.view.ViewGroup;
39import android.view.ViewRootImpl;
40
41import java.lang.ref.WeakReference;
42import java.util.List;
43
44/**
45 * Displays TV contents. The TvView class provides a high level interface for applications to show
46 * TV programs from various TV sources that implement {@link TvInputService}. (Note that the list of
47 * TV inputs available on the system can be obtained by calling
48 * {@link TvInputManager#getTvInputList() TvInputManager.getTvInputList()}.)
49 * <p>
50 * Once the application supplies the URI for a specific TV channel to {@link #tune(String, Uri)}
51 * method, it takes care of underlying service binding (and unbinding if the current TvView is
52 * already bound to a service) and automatically allocates/deallocates resources needed. In addition
53 * to a few essential methods to control how the contents are presented, it also provides a way to
54 * dispatch input events to the connected TvInputService in order to enable custom key actions for
55 * the TV input.
56 * </p>
57 */
58public class TvView extends ViewGroup {
59    private static final String TAG = "TvView";
60    // STOPSHIP: Turn debugging off.
61    private static final boolean DEBUG = true;
62
63    private static final int VIDEO_SIZE_VALUE_UNKNOWN = 0;
64
65    private static final int ZORDER_MEDIA = 0;
66    private static final int ZORDER_MEDIA_OVERLAY = 1;
67    private static final int ZORDER_ON_TOP = 2;
68
69    private static final int CAPTION_DEFAULT = 0;
70    private static final int CAPTION_ENABLED = 1;
71    private static final int CAPTION_DISABLED = 2;
72
73    private static final WeakReference<TvView> NULL_TV_VIEW = new WeakReference(null);
74
75    private static final Object sMainTvViewLock = new Object();
76    private static WeakReference<TvView> sMainTvView = NULL_TV_VIEW;
77
78    private final Handler mHandler = new Handler();
79    private Session mSession;
80    private SurfaceView mSurfaceView;
81    private Surface mSurface;
82    private boolean mOverlayViewCreated;
83    private Rect mOverlayViewFrame;
84    private final TvInputManager mTvInputManager;
85    private MySessionCallback mSessionCallback;
86    private TvInputListener mListener;
87    private OnUnhandledInputEventListener mOnUnhandledInputEventListener;
88    private boolean mHasStreamVolume;
89    private float mStreamVolume;
90    private int mVideoWidth = VIDEO_SIZE_VALUE_UNKNOWN;
91    private int mVideoHeight = VIDEO_SIZE_VALUE_UNKNOWN;
92    private boolean mSurfaceChanged;
93    private int mSurfaceFormat;
94    private int mSurfaceWidth;
95    private int mSurfaceHeight;
96    private final AttributeSet mAttrs;
97    private final int mDefStyleAttr;
98    private int mWindowZOrder;
99    private boolean mUseRequestedSurfaceLayout;
100    private int mSurfaceViewLeft;
101    private int mSurfaceViewRight;
102    private int mSurfaceViewTop;
103    private int mSurfaceViewBottom;
104    private int mCaptionEnabled;
105
106    private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
107        @Override
108        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
109            if (DEBUG) {
110                Log.d(TAG, "surfaceChanged(holder=" + holder + ", format=" + format + ", width="
111                    + width + ", height=" + height + ")");
112            }
113            mSurfaceFormat = format;
114            mSurfaceWidth = width;
115            mSurfaceHeight = height;
116            mSurfaceChanged = true;
117            dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
118        }
119
120        @Override
121        public void surfaceCreated(SurfaceHolder holder) {
122            mSurface = holder.getSurface();
123            setSessionSurface(mSurface);
124        }
125
126        @Override
127        public void surfaceDestroyed(SurfaceHolder holder) {
128            mSurface = null;
129            mSurfaceChanged = false;
130            setSessionSurface(null);
131        }
132    };
133
134    private final FinishedInputEventCallback mFinishedInputEventCallback =
135            new FinishedInputEventCallback() {
136        @Override
137        public void onFinishedInputEvent(Object token, boolean handled) {
138            if (DEBUG) {
139                Log.d(TAG, "onFinishedInputEvent(token=" + token + ", handled=" + handled + ")");
140            }
141            if (handled) {
142                return;
143            }
144            // TODO: Re-order unhandled events.
145            InputEvent event = (InputEvent) token;
146            if (dispatchUnhandledInputEvent(event)) {
147                return;
148            }
149            ViewRootImpl viewRootImpl = getViewRootImpl();
150            if (viewRootImpl != null) {
151                viewRootImpl.dispatchUnhandledInputEvent(event);
152            }
153        }
154    };
155
156    public TvView(Context context) {
157        this(context, null, 0);
158    }
159
160    public TvView(Context context, AttributeSet attrs) {
161        this(context, attrs, 0);
162    }
163
164    public TvView(Context context, AttributeSet attrs, int defStyleAttr) {
165        super(context, attrs, defStyleAttr);
166        mAttrs = attrs;
167        mDefStyleAttr = defStyleAttr;
168        resetSurfaceView();
169        mTvInputManager = (TvInputManager) getContext().getSystemService(Context.TV_INPUT_SERVICE);
170    }
171
172    /**
173     * Sets a listener for events in this TvView.
174     *
175     * @param listener The listener to be called with events. A value of {@code null} removes any
176     *         existing listener.
177     */
178    public void setTvInputListener(TvInputListener listener) {
179        mListener = listener;
180    }
181
182    /**
183     * Sets this as the main {@link TvView}.
184     * <p>
185     * The main {@link TvView} is a {@link TvView} whose corresponding TV input determines the
186     * HDMI-CEC active source device. For an HDMI port input, one of source devices that is
187     * connected to that HDMI port becomes the active source. For an HDMI-CEC logical device input,
188     * the corresponding HDMI-CEC logical device becomes the active source. For any non-HDMI input
189     * (including the tuner, composite, S-Video, etc.), the internal device (= TV itself) becomes
190     * the active source.
191     * </p><p>
192     * First tuned {@link TvView} becomes main automatically, and keeps to be main until either
193     * {@link #reset} is called for the main {@link TvView} or {@link #setMain} is called for other
194     * {@link TvView}.
195     * </p>
196     * @hide
197     */
198    @SystemApi
199    public void setMain() {
200        synchronized (sMainTvViewLock) {
201            sMainTvView = new WeakReference(this);
202            if (hasWindowFocus() && mSession != null) {
203                mSession.setMain();
204            }
205        }
206    }
207
208    /**
209     * Sets the Z order of a window owning the surface of this TvView above the normal TvView
210     * but below an application.
211     *
212     * @see SurfaceView#setZOrderMediaOverlay
213     * @hide
214     */
215    @SystemApi
216    public void setZOrderMediaOverlay(boolean isMediaOverlay) {
217        if (isMediaOverlay) {
218            mWindowZOrder = ZORDER_MEDIA_OVERLAY;
219            removeSessionOverlayView();
220        } else {
221            mWindowZOrder = ZORDER_MEDIA;
222            createSessionOverlayView();
223        }
224        if (mSurfaceView != null) {
225            // ZOrderOnTop(false) removes WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
226            // from WindowLayoutParam as well as changes window type.
227            mSurfaceView.setZOrderOnTop(false);
228            mSurfaceView.setZOrderMediaOverlay(isMediaOverlay);
229        }
230    }
231
232    /**
233     * Sets the Z order of a window owning the surface of this TvView on top of an application.
234     *
235     * @see SurfaceView#setZOrderOnTop
236     * @hide
237     */
238    @SystemApi
239    public void setZOrderOnTop(boolean onTop) {
240        if (onTop) {
241            mWindowZOrder = ZORDER_ON_TOP;
242            removeSessionOverlayView();
243        } else {
244            mWindowZOrder = ZORDER_MEDIA;
245            createSessionOverlayView();
246        }
247        if (mSurfaceView != null) {
248            mSurfaceView.setZOrderMediaOverlay(false);
249            mSurfaceView.setZOrderOnTop(onTop);
250        }
251     }
252
253    /**
254     * Sets the relative stream volume of this session to handle a change of audio focus.
255     *
256     * @param volume A volume value between 0.0f to 1.0f.
257     */
258    public void setStreamVolume(float volume) {
259        if (DEBUG) Log.d(TAG, "setStreamVolume(" + volume + ")");
260        mHasStreamVolume = true;
261        mStreamVolume = volume;
262        if (mSession == null) {
263            // Volume will be set once the connection has been made.
264            return;
265        }
266        mSession.setStreamVolume(volume);
267    }
268
269    /**
270     * Tunes to a given channel.
271     *
272     * @param inputId The ID of TV input which will play the given channel.
273     * @param channelUri The URI of a channel.
274     */
275    public void tune(String inputId, Uri channelUri) {
276        tune(inputId, channelUri, null);
277    }
278
279    /**
280     * Tunes to a given channel.
281     *
282     * @param inputId The ID of TV input which will play the given channel.
283     * @param channelUri The URI of a channel.
284     * @param params Extra parameters which might be handled with the tune event.
285     * @hide
286     */
287    @SystemApi
288    public void tune(String inputId, Uri channelUri, Bundle params) {
289        if (DEBUG) Log.d(TAG, "tune(" + channelUri + ")");
290        if (TextUtils.isEmpty(inputId)) {
291            throw new IllegalArgumentException("inputId cannot be null or an empty string");
292        }
293        synchronized (sMainTvViewLock) {
294            if (sMainTvView.get() == null) {
295                sMainTvView = new WeakReference(this);
296            }
297        }
298        if (mSessionCallback != null && mSessionCallback.mInputId.equals(inputId)) {
299            if (mSession != null) {
300                mSession.tune(channelUri, params);
301            } else {
302                // Session is not created yet. Replace the channel which will be set once the
303                // session is made.
304                mSessionCallback.mChannelUri = channelUri;
305                mSessionCallback.mTuneParams = params;
306            }
307        } else {
308            resetInternal();
309            // When createSession() is called multiple times before the callback is called,
310            // only the callback of the last createSession() call will be actually called back.
311            // The previous callbacks will be ignored. For the logic, mSessionCallback
312            // is newly assigned for every createSession request and compared with
313            // MySessionCreateCallback.this.
314            mSessionCallback = new MySessionCallback(inputId, channelUri, params);
315            mTvInputManager.createSession(inputId, mSessionCallback, mHandler);
316        }
317    }
318
319    /**
320     * Resets this TvView.
321     * <p>
322     * This method is primarily used to un-tune the current TvView.
323     */
324    public void reset() {
325        if (DEBUG) Log.d(TAG, "reset()");
326        synchronized (sMainTvViewLock) {
327            if (this == sMainTvView.get()) {
328                sMainTvView = NULL_TV_VIEW;
329            }
330        }
331        resetInternal();
332    }
333
334    private void resetInternal() {
335        if (mSession != null) {
336            release();
337            resetSurfaceView();
338        }
339    }
340
341    /**
342     * Requests to unblock TV content according to the given rating.
343     * <p>
344     * This notifies TV input that blocked content is now OK to play.
345     * </p>
346     *
347     * @param unblockedRating A TvContentRating to unblock.
348     * @see TvInputService.Session#notifyContentBlocked(TvContentRating)
349     * @hide
350     */
351    @SystemApi
352    public void requestUnblockContent(TvContentRating unblockedRating) {
353        if (mSession != null) {
354            mSession.requestUnblockContent(unblockedRating);
355        }
356    }
357
358    /**
359     * Enables or disables the caption in this TvView.
360     * <p>
361     * Note that this method does not take any effect unless the current TvView is tuned.
362     *
363     * @param enabled {@code true} to enable, {@code false} to disable.
364     */
365    public void setCaptionEnabled(boolean enabled) {
366        mCaptionEnabled = enabled ? CAPTION_ENABLED : CAPTION_DISABLED;
367        if (mSession != null) {
368            mSession.setCaptionEnabled(enabled);
369        }
370    }
371
372    /**
373     * Selects a track.
374     *
375     * @param type The type of the track to select. The type can be {@link TvTrackInfo#TYPE_AUDIO},
376     *            {@link TvTrackInfo#TYPE_VIDEO} or {@link TvTrackInfo#TYPE_SUBTITLE}.
377     * @param trackId The ID of the track to select. {@code null} means to unselect the current
378     *            track for a given type.
379     * @see #getTracks
380     * @see #getSelectedTrack
381     */
382    public void selectTrack(int type, String trackId) {
383        if (mSession != null) {
384            mSession.selectTrack(type, trackId);
385        }
386    }
387
388    /**
389     * Returns the list of tracks. Returns {@code null} if the information is not available.
390     *
391     * @param type The type of the tracks. The type can be {@link TvTrackInfo#TYPE_AUDIO},
392     *            {@link TvTrackInfo#TYPE_VIDEO} or {@link TvTrackInfo#TYPE_SUBTITLE}.
393     * @see #selectTrack
394     * @see #getSelectedTrack
395     */
396    public List<TvTrackInfo> getTracks(int type) {
397        if (mSession == null) {
398            return null;
399        }
400        return mSession.getTracks(type);
401    }
402
403    /**
404     * Returns the ID of the selected track for a given type. Returns {@code null} if the
405     * information is not available or the track is not selected.
406     *
407     * @param type The type of the selected tracks. The type can be {@link TvTrackInfo#TYPE_AUDIO},
408     *            {@link TvTrackInfo#TYPE_VIDEO} or {@link TvTrackInfo#TYPE_SUBTITLE}.
409     * @see #selectTrack
410     * @see #getTracks
411     */
412    public String getSelectedTrack(int type) {
413        if (mSession == null) {
414            return null;
415        }
416        return mSession.getSelectedTrack(type);
417    }
418
419    /**
420     * Calls {@link TvInputService.Session#appPrivateCommand(String, Bundle)
421     * TvInputService.Session.appPrivateCommand()} on the current TvView.
422     *
423     * @param action Name of the command to be performed. This <em>must</em> be a scoped name, i.e.
424     *            prefixed with a package name you own, so that different developers will not create
425     *            conflicting commands.
426     * @param data Any data to include with the command.
427     * @hide
428     */
429    @SystemApi
430    public void sendAppPrivateCommand(String action, Bundle data) {
431        if (TextUtils.isEmpty(action)) {
432            throw new IllegalArgumentException("action cannot be null or an empty string");
433        }
434        if (mSession != null) {
435            mSession.sendAppPrivateCommand(action, data);
436        }
437    }
438
439    /**
440     * Dispatches an unhandled input event to the next receiver.
441     * <p>
442     * Except system keys, TvView always consumes input events in the normal flow. This is called
443     * asynchronously from where the event is dispatched. It gives the host application a chance to
444     * dispatch the unhandled input events.
445     *
446     * @param event The input event.
447     * @return {@code true} if the event was handled by the view, {@code false} otherwise.
448     */
449    public boolean dispatchUnhandledInputEvent(InputEvent event) {
450        if (mOnUnhandledInputEventListener != null) {
451            if (mOnUnhandledInputEventListener.onUnhandledInputEvent(event)) {
452                return true;
453            }
454        }
455        return onUnhandledInputEvent(event);
456    }
457
458    /**
459     * Called when an unhandled input event also has not been handled by the user provided
460     * callback. This is the last chance to handle the unhandled input event in the TvView.
461     *
462     * @param event The input event.
463     * @return If you handled the event, return {@code true}. If you want to allow the event to be
464     *         handled by the next receiver, return {@code false}.
465     */
466    public boolean onUnhandledInputEvent(InputEvent event) {
467        return false;
468    }
469
470    /**
471     * Registers a callback to be invoked when an input event is not handled by the bound TV input.
472     *
473     * @param listener The callback to be invoked when the unhandled input event is received.
474     */
475    public void setOnUnhandledInputEventListener(OnUnhandledInputEventListener listener) {
476        mOnUnhandledInputEventListener = listener;
477    }
478
479    @Override
480    public boolean dispatchKeyEvent(KeyEvent event) {
481        if (super.dispatchKeyEvent(event)) {
482            return true;
483        }
484        if (DEBUG) Log.d(TAG, "dispatchKeyEvent(" + event + ")");
485        if (mSession == null) {
486            return false;
487        }
488        InputEvent copiedEvent = event.copy();
489        int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback,
490                mHandler);
491        return ret != Session.DISPATCH_NOT_HANDLED;
492    }
493
494    @Override
495    public boolean dispatchTouchEvent(MotionEvent event) {
496        if (super.dispatchTouchEvent(event)) {
497            return true;
498        }
499        if (DEBUG) Log.d(TAG, "dispatchTouchEvent(" + event + ")");
500        if (mSession == null) {
501            return false;
502        }
503        InputEvent copiedEvent = event.copy();
504        int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback,
505                mHandler);
506        return ret != Session.DISPATCH_NOT_HANDLED;
507    }
508
509    @Override
510    public boolean dispatchTrackballEvent(MotionEvent event) {
511        if (super.dispatchTrackballEvent(event)) {
512            return true;
513        }
514        if (DEBUG) Log.d(TAG, "dispatchTrackballEvent(" + event + ")");
515        if (mSession == null) {
516            return false;
517        }
518        InputEvent copiedEvent = event.copy();
519        int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback,
520                mHandler);
521        return ret != Session.DISPATCH_NOT_HANDLED;
522    }
523
524    @Override
525    public boolean dispatchGenericMotionEvent(MotionEvent event) {
526        if (super.dispatchGenericMotionEvent(event)) {
527            return true;
528        }
529        if (DEBUG) Log.d(TAG, "dispatchGenericMotionEvent(" + event + ")");
530        if (mSession == null) {
531            return false;
532        }
533        InputEvent copiedEvent = event.copy();
534        int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback,
535                mHandler);
536        return ret != Session.DISPATCH_NOT_HANDLED;
537    }
538
539    @Override
540    public void dispatchWindowFocusChanged(boolean hasFocus) {
541        super.dispatchWindowFocusChanged(hasFocus);
542        // Other app may have shown its own main TvView.
543        // Set main again to regain main session.
544        synchronized (sMainTvViewLock) {
545            if (hasFocus && this == sMainTvView.get() && mSession != null) {
546                mSession.setMain();
547            }
548        }
549    }
550
551    @Override
552    protected void onAttachedToWindow() {
553        super.onAttachedToWindow();
554        createSessionOverlayView();
555    }
556
557    @Override
558    protected void onDetachedFromWindow() {
559        removeSessionOverlayView();
560        super.onDetachedFromWindow();
561    }
562
563    @Override
564    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
565        if (DEBUG) {
566            Log.d(TAG, "onLayout (left=" + left + ", top=" + top + ", right=" + right
567                    + ", bottom=" + bottom + ",)");
568        }
569        if (mUseRequestedSurfaceLayout) {
570            mSurfaceView.layout(mSurfaceViewLeft, mSurfaceViewTop, mSurfaceViewRight,
571                    mSurfaceViewBottom);
572        } else {
573            mSurfaceView.layout(0, 0, right - left, bottom - top);
574        }
575    }
576
577    @Override
578    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
579        mSurfaceView.measure(widthMeasureSpec, heightMeasureSpec);
580        int width = mSurfaceView.getMeasuredWidth();
581        int height = mSurfaceView.getMeasuredHeight();
582        int childState = mSurfaceView.getMeasuredState();
583        setMeasuredDimension(resolveSizeAndState(width, widthMeasureSpec, childState),
584                resolveSizeAndState(height, heightMeasureSpec,
585                        childState << MEASURED_HEIGHT_STATE_SHIFT));
586    }
587
588    @Override
589    protected void onVisibilityChanged(View changedView, int visibility) {
590        super.onVisibilityChanged(changedView, visibility);
591        mSurfaceView.setVisibility(visibility);
592        if (visibility == View.VISIBLE) {
593            createSessionOverlayView();
594        } else {
595            removeSessionOverlayView();
596        }
597    }
598
599    private void resetSurfaceView() {
600        if (mSurfaceView != null) {
601            mSurfaceView.getHolder().removeCallback(mSurfaceHolderCallback);
602            removeView(mSurfaceView);
603        }
604        mSurface = null;
605        mSurfaceView = new SurfaceView(getContext(), mAttrs, mDefStyleAttr) {
606            @Override
607            protected void updateWindow(boolean force, boolean redrawNeeded) {
608                super.updateWindow(force, redrawNeeded);
609                relayoutSessionOverlayView();
610            }};
611        mSurfaceView.getHolder().addCallback(mSurfaceHolderCallback);
612        if (mWindowZOrder == ZORDER_MEDIA_OVERLAY) {
613            mSurfaceView.setZOrderMediaOverlay(true);
614        } else if (mWindowZOrder == ZORDER_ON_TOP) {
615            mSurfaceView.setZOrderOnTop(true);
616        }
617        addView(mSurfaceView);
618    }
619
620    private void release() {
621        setSessionSurface(null);
622        removeSessionOverlayView();
623        mUseRequestedSurfaceLayout = false;
624        mSession.release();
625        mSession = null;
626        mSessionCallback = null;
627    }
628
629    private void setSessionSurface(Surface surface) {
630        if (mSession == null) {
631            return;
632        }
633        mSession.setSurface(surface);
634    }
635
636    private void dispatchSurfaceChanged(int format, int width, int height) {
637        if (mSession == null) {
638            return;
639        }
640        mSession.dispatchSurfaceChanged(format, width, height);
641    }
642
643    private void createSessionOverlayView() {
644        if (mSession == null || !isAttachedToWindow()
645                || mOverlayViewCreated || mWindowZOrder != ZORDER_MEDIA) {
646            return;
647        }
648        mOverlayViewFrame = getViewFrameOnScreen();
649        mSession.createOverlayView(this, mOverlayViewFrame);
650        mOverlayViewCreated = true;
651    }
652
653    private void removeSessionOverlayView() {
654        if (mSession == null || !mOverlayViewCreated) {
655            return;
656        }
657        mSession.removeOverlayView();
658        mOverlayViewCreated = false;
659        mOverlayViewFrame = null;
660    }
661
662    private void relayoutSessionOverlayView() {
663        if (mSession == null || !isAttachedToWindow() || !mOverlayViewCreated
664                || mWindowZOrder != ZORDER_MEDIA) {
665            return;
666        }
667        Rect viewFrame = getViewFrameOnScreen();
668        if (viewFrame.equals(mOverlayViewFrame)) {
669            return;
670        }
671        mSession.relayoutOverlayView(viewFrame);
672        mOverlayViewFrame = viewFrame;
673    }
674
675    private Rect getViewFrameOnScreen() {
676        int[] location = new int[2];
677        getLocationOnScreen(location);
678        return new Rect(location[0], location[1],
679                location[0] + getWidth(), location[1] + getHeight());
680    }
681
682    /**
683     * Interface used to receive various status updates on the {@link TvView}.
684     */
685    public abstract static class TvInputListener {
686
687        /**
688         * This is invoked when an error occurred while establishing a connection to the underlying
689         * TV input.
690         *
691         * @param inputId The ID of the TV input bound to this view.
692         */
693        public void onConnectionFailed(String inputId) {
694        }
695
696        /**
697         * This is invoked when the existing connection to the underlying TV input is lost.
698         *
699         * @param inputId The ID of the TV input bound to this view.
700         */
701        public void onDisconnected(String inputId) {
702        }
703
704        /**
705         * This is invoked when the view is tuned to a specific channel and starts decoding video
706         * stream from there. It is also called later when the video size is changed.
707         *
708         * @param inputId The ID of the TV input bound to this view.
709         * @param width The width of the video.
710         * @param height The height of the video.
711         */
712        public void onVideoSizeChanged(String inputId, int width, int height) {
713        }
714
715        /**
716         * This is invoked when the channel of this TvView is changed by the underlying TV input
717         * with out any {@link TvView#tune(String, Uri)} request.
718         *
719         * @param inputId The ID of the TV input bound to this view.
720         * @param channelUri The URI of a channel.
721         */
722        public void onChannelRetuned(String inputId, Uri channelUri) {
723        }
724
725        /**
726         * This is called when the track information has been changed.
727         *
728         * @param inputId The ID of the TV input bound to this view.
729         * @param tracks A list which includes track information.
730         */
731        public void onTracksChanged(String inputId, List<TvTrackInfo> tracks) {
732        }
733
734        /**
735         * This is called when there is a change on the selected tracks.
736         *
737         * @param inputId The ID of the TV input bound to this view.
738         * @param type The type of the track selected. The type can be
739         *            {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or
740         *            {@link TvTrackInfo#TYPE_SUBTITLE}.
741         * @param trackId The ID of the track selected.
742         */
743        public void onTrackSelected(String inputId, int type, String trackId) {
744        }
745
746        /**
747         * This is called when the video is available, so the TV input starts the playback.
748         *
749         * @param inputId The ID of the TV input bound to this view.
750         */
751        public void onVideoAvailable(String inputId) {
752        }
753
754        /**
755         * This is called when the video is not available, so the TV input stops the playback.
756         *
757         * @param inputId The ID of the TV input bound to this view.
758         * @param reason The reason why the TV input stopped the playback:
759         * <ul>
760         * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_UNKNOWN}
761         * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING}
762         * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL}
763         * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_BUFFERING}
764         * </ul>
765         */
766        public void onVideoUnavailable(String inputId, int reason) {
767        }
768
769        /**
770         * This is called when the current program content turns out to be allowed to watch since
771         * its content rating is not blocked by parental controls.
772         *
773         * @param inputId The ID of the TV input bound to this view.
774         */
775        public void onContentAllowed(String inputId) {
776        }
777
778        /**
779         * This is called when the current program content turns out to be not allowed to watch
780         * since its content rating is blocked by parental controls.
781         *
782         * @param inputId The ID of the TV input bound to this view.
783         * @param rating The content rating of the blocked program.
784         */
785        public void onContentBlocked(String inputId, TvContentRating rating) {
786        }
787
788        /**
789         * This is invoked when a custom event from the bound TV input is sent to this view.
790         *
791         * @param eventType The type of the event.
792         * @param eventArgs Optional arguments of the event.
793         * @hide
794         */
795        @SystemApi
796        public void onEvent(String inputId, String eventType, Bundle eventArgs) {
797        }
798    }
799
800    /**
801     * Interface definition for a callback to be invoked when the unhandled input event is received.
802     */
803    public interface OnUnhandledInputEventListener {
804        /**
805         * Called when an input event was not handled by the bound TV input.
806         * <p>
807         * This is called asynchronously from where the event is dispatched. It gives the host
808         * application a chance to handle the unhandled input events.
809         *
810         * @param event The input event.
811         * @return If you handled the event, return {@code true}. If you want to allow the event to
812         *         be handled by the next receiver, return {@code false}.
813         */
814        boolean onUnhandledInputEvent(InputEvent event);
815    }
816
817    private class MySessionCallback extends SessionCallback {
818        final String mInputId;
819        Uri mChannelUri;
820        Bundle mTuneParams;
821
822        MySessionCallback(String inputId, Uri channelUri, Bundle tuneParams) {
823            mInputId = inputId;
824            mChannelUri = channelUri;
825            mTuneParams = tuneParams;
826        }
827
828        @Override
829        public void onSessionCreated(Session session) {
830            if (this != mSessionCallback) {
831                // This callback is obsolete.
832                if (session != null) {
833                    session.release();
834                }
835                return;
836            }
837            if (DEBUG) {
838                Log.d(TAG, "onSessionCreated()");
839            }
840            mSession = session;
841            if (session != null) {
842                synchronized (sMainTvViewLock) {
843                    if (hasWindowFocus() && TvView.this == sMainTvView.get()) {
844                        mSession.setMain();
845                    }
846                }
847                // mSurface may not be ready yet as soon as starting an application.
848                // In the case, we don't send Session.setSurface(null) unnecessarily.
849                // setSessionSurface will be called in surfaceCreated.
850                if (mSurface != null) {
851                    setSessionSurface(mSurface);
852                    if (mSurfaceChanged) {
853                        dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
854                    }
855                }
856                createSessionOverlayView();
857                if (mCaptionEnabled != CAPTION_DEFAULT) {
858                    mSession.setCaptionEnabled(mCaptionEnabled == CAPTION_ENABLED);
859                }
860                mSession.tune(mChannelUri, mTuneParams);
861                if (mHasStreamVolume) {
862                    mSession.setStreamVolume(mStreamVolume);
863                }
864            } else {
865                if (mListener != null) {
866                    mListener.onConnectionFailed(mInputId);
867                }
868            }
869        }
870
871        @Override
872        public void onSessionReleased(Session session) {
873            if (this != mSessionCallback) {
874                return;
875            }
876            mOverlayViewCreated = false;
877            mOverlayViewFrame = null;
878            mSessionCallback = null;
879            mSession = null;
880            if (mListener != null) {
881                mListener.onDisconnected(mInputId);
882            }
883        }
884
885        @Override
886        public void onChannelRetuned(Session session, Uri channelUri) {
887            if (this != mSessionCallback) {
888                return;
889            }
890            if (DEBUG) {
891                Log.d(TAG, "onChannelChangedByTvInput(" + channelUri + ")");
892            }
893            if (mListener != null) {
894                mListener.onChannelRetuned(mInputId, channelUri);
895            }
896        }
897
898        @Override
899        public void onTracksChanged(Session session, List<TvTrackInfo> tracks) {
900            if (this != mSessionCallback) {
901                return;
902            }
903            if (DEBUG) {
904                Log.d(TAG, "onTracksChanged()");
905            }
906            if (mListener != null) {
907                mListener.onTracksChanged(mInputId, tracks);
908            }
909        }
910
911        @Override
912        public void onTrackSelected(Session session, int type, String trackId) {
913            if (this != mSessionCallback) {
914                return;
915            }
916            if (DEBUG) {
917                Log.d(TAG, "onTrackSelected()");
918            }
919            // TODO: Update the video size when the type is TYPE_VIDEO.
920            if (mListener != null) {
921                mListener.onTrackSelected(mInputId, type, trackId);
922            }
923        }
924
925        @Override
926        public void onVideoAvailable(Session session) {
927            if (this != mSessionCallback) {
928                return;
929            }
930            if (DEBUG) {
931                Log.d(TAG, "onVideoAvailable()");
932            }
933            if (mListener != null) {
934                mListener.onVideoAvailable(mInputId);
935            }
936        }
937
938        @Override
939        public void onVideoUnavailable(Session session, int reason) {
940            if (this != mSessionCallback) {
941                return;
942            }
943            if (DEBUG) {
944                Log.d(TAG, "onVideoUnavailable(" + reason + ")");
945            }
946            if (mListener != null) {
947                mListener.onVideoUnavailable(mInputId, reason);
948            }
949        }
950
951        @Override
952        public void onContentAllowed(Session session) {
953            if (this != mSessionCallback) {
954                return;
955            }
956            if (DEBUG) {
957                Log.d(TAG, "onContentAllowed()");
958            }
959            if (mListener != null) {
960                mListener.onContentAllowed(mInputId);
961            }
962        }
963
964        @Override
965        public void onContentBlocked(Session session, TvContentRating rating) {
966            if (DEBUG) {
967                Log.d(TAG, "onContentBlocked()");
968            }
969            if (mListener != null) {
970                mListener.onContentBlocked(mInputId, rating);
971            }
972        }
973
974        @Override
975        public void onLayoutSurface(Session session, int left, int top, int right, int bottom) {
976            if (DEBUG) {
977                Log.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top + ", right="
978                        + right + ", bottom=" + bottom + ",)");
979            }
980            mSurfaceViewLeft = left;
981            mSurfaceViewTop = top;
982            mSurfaceViewRight = right;
983            mSurfaceViewBottom = bottom;
984            mUseRequestedSurfaceLayout = true;
985            requestLayout();
986        }
987
988        @Override
989        public void onSessionEvent(Session session, String eventType, Bundle eventArgs) {
990            if (this != mSessionCallback) {
991                return;
992            }
993            if (DEBUG) {
994                Log.d(TAG, "onSessionEvent(" + eventType + ")");
995            }
996            if (mListener != null) {
997                mListener.onEvent(mInputId, eventType, eventArgs);
998            }
999        }
1000    }
1001}
1002