TunableTvView.java revision d82ca62a0fefaed41493540df7d622cfcfb2cb6c
1d422754c0cc8475d6e2c8c079ed2ee2e96213edbChulwoo Leepackage com.android.tv.ui;
2900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho
3900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Choimport android.content.Context;
4829ca34b57d208a84430b2ccecd8975598d086daYoungsang Choimport android.content.pm.PackageManager;
59035590d94d136a020e499ce720b9d2cf1f6f45cJae Seoimport android.media.tv.TvInputInfo;
64e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kimimport android.media.tv.TvInputManager;
75459428da9aca1b91e3a976a80e1db783caac674Dongwon Kangimport android.media.tv.TvTrackInfo;
89035590d94d136a020e499ce720b9d2cf1f6f45cJae Seoimport android.media.tv.TvView;
9d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkerstenimport android.media.tv.TvView.OnUnhandledInputEventListener;
10d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkerstenimport android.media.tv.TvView.TvInputListener;
11cdde1b3be4391c4b0d3b57cce3c63eee8c81819aDongwon Kangimport android.net.Uri;
12900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Choimport android.util.AttributeSet;
13900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Choimport android.util.Log;
14829ca34b57d208a84430b2ccecd8975598d086daYoungsang Choimport android.view.KeyEvent;
15829ca34b57d208a84430b2ccecd8975598d086daYoungsang Choimport android.view.MotionEvent;
16900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Choimport android.view.SurfaceHolder;
17b47a0ad3227b940175fa4ecfe5c4113da72c8237Dongwon Kangimport android.view.SurfaceView;
18d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkerstenimport android.view.ViewGroup;
19d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkerstenimport android.widget.FrameLayout;
20900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho
214e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kimimport com.android.tv.R;
22d422754c0cc8475d6e2c8c079ed2ee2e96213edbChulwoo Leeimport com.android.tv.data.Channel;
2321abb13274f379e69c3c86d51e493122cccca345Dongwon Kangimport com.android.tv.data.StreamInfo;
24d422754c0cc8475d6e2c8c079ed2ee2e96213edbChulwoo Leeimport com.android.tv.util.TvInputManagerHelper;
25d422754c0cc8475d6e2c8c079ed2ee2e96213edbChulwoo Leeimport com.android.tv.util.Utils;
26900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho
275459428da9aca1b91e3a976a80e1db783caac674Dongwon Kangimport java.util.List;
285459428da9aca1b91e3a976a80e1db783caac674Dongwon Kang
29d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkerstenpublic class TunableTvView extends FrameLayout implements StreamInfo {
30d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten    // STOPSHIP: Turn debugging off
31900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho    private static final boolean DEBUG = true;
32900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho    private static final String TAG = "TunableTvView";
33900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho
34900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho    private static final int DELAY_FOR_SURFACE_RELEASE = 300;
35829ca34b57d208a84430b2ccecd8975598d086daYoungsang Cho    public static final String PERMISSION_RECEIVE_INPUT_EVENT =
36829ca34b57d208a84430b2ccecd8975598d086daYoungsang Cho            "android.permission.RECEIVE_INPUT_EVENT";
37900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho
38d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten    private final TvView mTvView;
39d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten    private final SurfaceView mSurfaceView;
40900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho    private long mChannelId = Channel.INVALID_ID;
41900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho    private TvInputManagerHelper mInputManagerHelper;
42900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho    private boolean mStarted;
43900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho    private TvInputInfo mInputInfo;
44900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho    private OnTuneListener mOnTuneListener;
45e06ded45d6e3b83e1a325e97ccd168c6d1c0d2deJaewan Kim    private int mVideoWidth;
46e06ded45d6e3b83e1a325e97ccd168c6d1c0d2deJaewan Kim    private int mVideoHeight;
4721abb13274f379e69c3c86d51e493122cccca345Dongwon Kang    private int mVideoFormat = StreamInfo.VIDEO_DEFINITION_LEVEL_UNKNOWN;
48e51a084c2518f17cee43c86e2f9df907d1f2cafbDongwon Kang    private int mAudioChannelCount = StreamInfo.AUDIO_CHANNEL_COUNT_UNKNOWN;
49e51a084c2518f17cee43c86e2f9df907d1f2cafbDongwon Kang    private boolean mHasClosedCaption = false;
504e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kim    private boolean mIsVideoAvailable;
514e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kim    private int mVideoUnavailableReason;
52829ca34b57d208a84430b2ccecd8975598d086daYoungsang Cho    private boolean mCanReceiveInputEvent;
53d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten    private boolean mIsMuted;
54d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten    private float mVolume;
55900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho
56900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho    private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
57900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        @Override
58900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { }
59900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho
60900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        @Override
61900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        public void surfaceCreated(SurfaceHolder holder) { }
62900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho
63900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        @Override
64900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        public void surfaceDestroyed(SurfaceHolder holder) {
65900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho            // TODO: It is a hack to wait to release a surface at TIS. If there is a way to
66900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho            // know when the surface is released at TIS, we don't need this hack.
67900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho            try {
68900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho                if (DEBUG) Log.d(TAG, "Sleep to wait destroying a surface");
69900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho                Thread.sleep(DELAY_FOR_SURFACE_RELEASE);
70900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho                if (DEBUG) Log.d(TAG, "Wake up from sleeping");
71900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho            } catch (InterruptedException e) {
72900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho                e.printStackTrace();
73900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho            }
74900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        }
75900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho    };
76900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho
779bb548ba08d7e222f276287fead545c6a11704d8Jae Seo    private final TvInputListener mListener =
789bb548ba08d7e222f276287fead545c6a11704d8Jae Seo            new TvInputListener() {
79900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho                @Override
809bb548ba08d7e222f276287fead545c6a11704d8Jae Seo                public void onError(String inputId, int errorCode) {
819bb548ba08d7e222f276287fead545c6a11704d8Jae Seo                    if (errorCode == TvView.ERROR_BUSY) {
829bb548ba08d7e222f276287fead545c6a11704d8Jae Seo                        Log.w(TAG, "Failed to bind an input");
83900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho                        long channelId = mChannelId;
84900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho                        mChannelId = Channel.INVALID_ID;
85900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho                        mInputInfo = null;
86829ca34b57d208a84430b2ccecd8975598d086daYoungsang Cho                        mCanReceiveInputEvent = false;
87900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho                        if (mOnTuneListener != null) {
88900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho                            mOnTuneListener.onTuned(false, channelId);
89900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho                            mOnTuneListener = null;
90900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho                        }
919bb548ba08d7e222f276287fead545c6a11704d8Jae Seo                    } else if (errorCode == TvView.ERROR_TV_INPUT_DISCONNECTED) {
929bb548ba08d7e222f276287fead545c6a11704d8Jae Seo                        Log.w(TAG, "Session is released by crash");
939bb548ba08d7e222f276287fead545c6a11704d8Jae Seo                        long channelId = mChannelId;
949bb548ba08d7e222f276287fead545c6a11704d8Jae Seo                        mChannelId = Channel.INVALID_ID;
959bb548ba08d7e222f276287fead545c6a11704d8Jae Seo                        mInputInfo = null;
96829ca34b57d208a84430b2ccecd8975598d086daYoungsang Cho                        mCanReceiveInputEvent = false;
979bb548ba08d7e222f276287fead545c6a11704d8Jae Seo                        if (mOnTuneListener != null) {
989bb548ba08d7e222f276287fead545c6a11704d8Jae Seo                            mOnTuneListener.onUnexpectedStop(channelId);
999bb548ba08d7e222f276287fead545c6a11704d8Jae Seo                            mOnTuneListener = null;
1009bb548ba08d7e222f276287fead545c6a11704d8Jae Seo                        }
101900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho                    }
102900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho                }
103900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho
104900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho                @Override
1055459428da9aca1b91e3a976a80e1db783caac674Dongwon Kang                public void onChannelRetuned(String inputId, Uri channelUri) {
1068f1ff2894363afbb6909a3a1edc1f3cbe8657c11Jae Seo                    if (DEBUG) {
1075459428da9aca1b91e3a976a80e1db783caac674Dongwon Kang                        Log.d(TAG, "onChannelRetuned(inputId=" + inputId + ", channelUri="
1085459428da9aca1b91e3a976a80e1db783caac674Dongwon Kang                                + channelUri + ")");
1098f1ff2894363afbb6909a3a1edc1f3cbe8657c11Jae Seo                    }
1105459428da9aca1b91e3a976a80e1db783caac674Dongwon Kang                    // TODO: update {@code mChannelId}.
11121abb13274f379e69c3c86d51e493122cccca345Dongwon Kang                    if (mOnTuneListener != null) {
1125459428da9aca1b91e3a976a80e1db783caac674Dongwon Kang                        mOnTuneListener.onChannelChanged(channelUri);
11321abb13274f379e69c3c86d51e493122cccca345Dongwon Kang                    }
11421abb13274f379e69c3c86d51e493122cccca345Dongwon Kang                }
115fc4ae3d84120d40af0d1ce5da7b0b1e40003e9aaDongwon Kang
116fc4ae3d84120d40af0d1ce5da7b0b1e40003e9aaDongwon Kang                @Override
1175459428da9aca1b91e3a976a80e1db783caac674Dongwon Kang                public void onTrackInfoChanged(String inputId, List<TvTrackInfo> tracks) {
1185459428da9aca1b91e3a976a80e1db783caac674Dongwon Kang                    for (TvTrackInfo track : tracks) {
1195459428da9aca1b91e3a976a80e1db783caac674Dongwon Kang                        int type = track.getInt(TvTrackInfo.KEY_TYPE);
1205459428da9aca1b91e3a976a80e1db783caac674Dongwon Kang                        boolean selected = track.getBoolean(TvTrackInfo.KEY_IS_SELECTED);
1215459428da9aca1b91e3a976a80e1db783caac674Dongwon Kang                        if (type == TvTrackInfo.VALUE_TYPE_VIDEO && selected) {
1225459428da9aca1b91e3a976a80e1db783caac674Dongwon Kang                            mVideoWidth = track.getInt(TvTrackInfo.KEY_WIDTH);
1235459428da9aca1b91e3a976a80e1db783caac674Dongwon Kang                            mVideoHeight = track.getInt(TvTrackInfo.KEY_HEIGHT);
1245459428da9aca1b91e3a976a80e1db783caac674Dongwon Kang                            mVideoFormat = Utils.getVideoDefinitionLevelFromSize(
1255459428da9aca1b91e3a976a80e1db783caac674Dongwon Kang                                    mVideoWidth, mVideoHeight);
1265459428da9aca1b91e3a976a80e1db783caac674Dongwon Kang                        } else if (type == TvTrackInfo.VALUE_TYPE_AUDIO && selected) {
1275459428da9aca1b91e3a976a80e1db783caac674Dongwon Kang                            mAudioChannelCount = track.getInt(TvTrackInfo.KEY_CHANNEL_COUNT);
1285459428da9aca1b91e3a976a80e1db783caac674Dongwon Kang                        } else if (type == TvTrackInfo.VALUE_TYPE_SUBTITLE) {
1295459428da9aca1b91e3a976a80e1db783caac674Dongwon Kang                            mHasClosedCaption = true;
1305459428da9aca1b91e3a976a80e1db783caac674Dongwon Kang                        }
131fc4ae3d84120d40af0d1ce5da7b0b1e40003e9aaDongwon Kang                    }
132fc4ae3d84120d40af0d1ce5da7b0b1e40003e9aaDongwon Kang                    if (mOnTuneListener != null) {
133fc4ae3d84120d40af0d1ce5da7b0b1e40003e9aaDongwon Kang                        mOnTuneListener.onStreamInfoChanged(TunableTvView.this);
134fc4ae3d84120d40af0d1ce5da7b0b1e40003e9aaDongwon Kang                    }
135fc4ae3d84120d40af0d1ce5da7b0b1e40003e9aaDongwon Kang                }
1364e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kim
1374e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kim                @Override
1384e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kim                public void onVideoAvailable(String inputId) {
1394e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kim                    mIsVideoAvailable = true;
140d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten                    unblock();
1414e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kim                    if (mOnTuneListener != null) {
1424e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kim                        mOnTuneListener.onStreamInfoChanged(TunableTvView.this);
1434e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kim                    }
1444e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kim                }
1454e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kim
1464e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kim                @Override
1474e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kim                public void onVideoUnavailable(String inputId, int reason) {
1484e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kim                    mIsVideoAvailable = false;
1494e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kim                    mVideoUnavailableReason = reason;
150d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten                    block(reason);
1514e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kim                    if (mOnTuneListener != null) {
1524e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kim                        mOnTuneListener.onStreamInfoChanged(TunableTvView.this);
1534e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kim                    }
1544e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kim                }
155900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho            };
156900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho
157900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho    public TunableTvView(Context context) {
158d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        this(context, null);
159900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho    }
160900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho
161900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho    public TunableTvView(Context context, AttributeSet attrs) {
162900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        this(context, attrs, 0);
163900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho    }
164900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho
165900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho    public TunableTvView(Context context, AttributeSet attrs, int defStyleAttr) {
166d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        this(context, attrs, defStyleAttr, 0);
167d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten    }
168d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten
169d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten    public TunableTvView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
170d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        super(context, attrs, defStyleAttr, defStyleRes);
171d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        inflate(getContext(), R.layout.tunable_tv_view, this);
172d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten
173d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        mTvView = (TvView) findViewById(R.id.tv_view);
174d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        mSurfaceView = findSurfaceView(mTvView);
175900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho    }
176900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho
177900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho    public void start(TvInputManagerHelper tvInputManagerHelper) {
178900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        mInputManagerHelper = tvInputManagerHelper;
179900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        if (mStarted) {
180900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho            return;
181900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        }
182900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        mStarted = true;
183900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho    }
184900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho
185900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho    public void stop() {
186900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        if (!mStarted) {
187900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho            return;
188900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        }
189900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        mStarted = false;
190d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        mTvView.reset();
191900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        mChannelId = Channel.INVALID_ID;
192900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        mInputInfo = null;
193829ca34b57d208a84430b2ccecd8975598d086daYoungsang Cho        mCanReceiveInputEvent = false;
194900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        mOnTuneListener = null;
195900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho    }
196900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho
197900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho    public boolean isPlaying() {
198900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        return mStarted;
199900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho    }
200900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho
201900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho    public boolean tuneTo(long channelId, OnTuneListener listener) {
202900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        if (!mStarted) {
203900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho            throw new IllegalStateException("TvView isn't started");
204900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        }
205900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        if (DEBUG) Log.d(TAG, "tuneTo " + channelId);
206e06ded45d6e3b83e1a325e97ccd168c6d1c0d2deJaewan Kim        mVideoWidth = 0;
207e06ded45d6e3b83e1a325e97ccd168c6d1c0d2deJaewan Kim        mVideoHeight = 0;
20821abb13274f379e69c3c86d51e493122cccca345Dongwon Kang        mVideoFormat = StreamInfo.VIDEO_DEFINITION_LEVEL_UNKNOWN;
209e51a084c2518f17cee43c86e2f9df907d1f2cafbDongwon Kang        mAudioChannelCount = StreamInfo.AUDIO_CHANNEL_COUNT_UNKNOWN;
210e51a084c2518f17cee43c86e2f9df907d1f2cafbDongwon Kang        mHasClosedCaption = false;
211900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        String inputId = Utils.getInputIdForChannel(getContext(), channelId);
212900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        TvInputInfo inputInfo = mInputManagerHelper.getTvInputInfo(inputId);
213085333963f5b4cbfa9d6e42512690ffbca873b96Wonsik Kim        if (inputInfo == null
2144e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kim                || mInputManagerHelper.getInputState(inputInfo) ==
2154e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kim                        TvInputManager.INPUT_STATE_DISCONNECTED) {
216900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho            return false;
217900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        }
218900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        mOnTuneListener = listener;
219900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        mChannelId = channelId;
220900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        if (!inputInfo.equals(mInputInfo)) {
221d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten            mTvView.reset();
222900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho            // TODO: It is a hack to wait to release a surface at TIS. If there is a way to
223900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho            // know when the surface is released at TIS, we don't need this hack.
224900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho            try {
225900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho                Thread.sleep(DELAY_FOR_SURFACE_RELEASE);
226900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho            } catch (InterruptedException e) {
227900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho                e.printStackTrace();
228900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho            }
229900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho            mInputInfo = inputInfo;
230d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten            mCanReceiveInputEvent = getContext().getPackageManager().checkPermission(
231829ca34b57d208a84430b2ccecd8975598d086daYoungsang Cho                    PERMISSION_RECEIVE_INPUT_EVENT, mInputInfo.getComponent().getPackageName())
232829ca34b57d208a84430b2ccecd8975598d086daYoungsang Cho                            == PackageManager.PERMISSION_GRANTED;
2339bb548ba08d7e222f276287fead545c6a11704d8Jae Seo        }
234d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        mTvView.setTvInputListener(mListener);
235d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        mTvView.tune(mInputInfo.getId(), Utils.getChannelUri(mChannelId));
2369bb548ba08d7e222f276287fead545c6a11704d8Jae Seo        if (mOnTuneListener != null) {
2379bb548ba08d7e222f276287fead545c6a11704d8Jae Seo            // TODO: Add a callback for tune complete and call onTuned when it was successful.
2389bb548ba08d7e222f276287fead545c6a11704d8Jae Seo            mOnTuneListener.onTuned(true, mChannelId);
239900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        }
240900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        return true;
241900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho    }
242900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho
24387c1cbb508c96538d1e69ba1fdf3d9f4d52710d6Jaewan Kim    @Override
244900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho    public TvInputInfo getCurrentTvInputInfo() {
245900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        return mInputInfo;
246900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho    }
247900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho
248900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho    public long getCurrentChannelId() {
249900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        return mChannelId;
250900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho    }
251900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho
252b47a0ad3227b940175fa4ecfe5c4113da72c8237Dongwon Kang    public void setPip(boolean isPip) {
253d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        mSurfaceView.setZOrderMediaOverlay(isPip);
254b47a0ad3227b940175fa4ecfe5c4113da72c8237Dongwon Kang    }
255b47a0ad3227b940175fa4ecfe5c4113da72c8237Dongwon Kang
256561696c364ee330f4316544f1f85498393883526Jae Seo    public void setStreamVolume(float volume) {
257900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        if (!mStarted) {
258900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho            throw new IllegalStateException("TvView isn't started");
259900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        }
260d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        if (DEBUG) Log.d(TAG, "setStreamVolume " + volume);
261d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        mVolume = volume;
262d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        if (!mIsMuted) {
263d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten            mTvView.setStreamVolume(volume);
264d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        }
265900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho    }
266900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho
267829ca34b57d208a84430b2ccecd8975598d086daYoungsang Cho    @Override
268829ca34b57d208a84430b2ccecd8975598d086daYoungsang Cho    public boolean dispatchKeyEvent(KeyEvent event) {
269d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        return mCanReceiveInputEvent && mTvView.dispatchKeyEvent(event);
270829ca34b57d208a84430b2ccecd8975598d086daYoungsang Cho    }
271829ca34b57d208a84430b2ccecd8975598d086daYoungsang Cho
272829ca34b57d208a84430b2ccecd8975598d086daYoungsang Cho    @Override
273829ca34b57d208a84430b2ccecd8975598d086daYoungsang Cho    public boolean dispatchTouchEvent(MotionEvent event) {
274d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        return mCanReceiveInputEvent && mTvView.dispatchTouchEvent(event);
275829ca34b57d208a84430b2ccecd8975598d086daYoungsang Cho    }
276829ca34b57d208a84430b2ccecd8975598d086daYoungsang Cho
277829ca34b57d208a84430b2ccecd8975598d086daYoungsang Cho    @Override
278829ca34b57d208a84430b2ccecd8975598d086daYoungsang Cho    public boolean dispatchTrackballEvent(MotionEvent event) {
279d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        return mCanReceiveInputEvent && mTvView.dispatchTrackballEvent(event);
280829ca34b57d208a84430b2ccecd8975598d086daYoungsang Cho    }
281829ca34b57d208a84430b2ccecd8975598d086daYoungsang Cho
282829ca34b57d208a84430b2ccecd8975598d086daYoungsang Cho    @Override
283829ca34b57d208a84430b2ccecd8975598d086daYoungsang Cho    public boolean dispatchGenericMotionEvent(MotionEvent event) {
284d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        return mCanReceiveInputEvent && mTvView.dispatchGenericMotionEvent(event);
285829ca34b57d208a84430b2ccecd8975598d086daYoungsang Cho    }
286829ca34b57d208a84430b2ccecd8975598d086daYoungsang Cho
287900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho    public interface OnTuneListener {
288900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        void onTuned(boolean success, long channelId);
289900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho        void onUnexpectedStop(long channelId);
29021abb13274f379e69c3c86d51e493122cccca345Dongwon Kang        void onStreamInfoChanged(StreamInfo info);
291cdde1b3be4391c4b0d3b57cce3c63eee8c81819aDongwon Kang        void onChannelChanged(Uri channel);
29221abb13274f379e69c3c86d51e493122cccca345Dongwon Kang    }
29321abb13274f379e69c3c86d51e493122cccca345Dongwon Kang
29421abb13274f379e69c3c86d51e493122cccca345Dongwon Kang    @Override
295e06ded45d6e3b83e1a325e97ccd168c6d1c0d2deJaewan Kim    public int getVideoWidth() {
296e06ded45d6e3b83e1a325e97ccd168c6d1c0d2deJaewan Kim        return mVideoWidth;
297e06ded45d6e3b83e1a325e97ccd168c6d1c0d2deJaewan Kim    }
298e06ded45d6e3b83e1a325e97ccd168c6d1c0d2deJaewan Kim
299e06ded45d6e3b83e1a325e97ccd168c6d1c0d2deJaewan Kim    @Override
300e06ded45d6e3b83e1a325e97ccd168c6d1c0d2deJaewan Kim    public int getVideoHeight() {
301e06ded45d6e3b83e1a325e97ccd168c6d1c0d2deJaewan Kim        return mVideoHeight;
302e06ded45d6e3b83e1a325e97ccd168c6d1c0d2deJaewan Kim    }
303e06ded45d6e3b83e1a325e97ccd168c6d1c0d2deJaewan Kim
304e06ded45d6e3b83e1a325e97ccd168c6d1c0d2deJaewan Kim    @Override
30521abb13274f379e69c3c86d51e493122cccca345Dongwon Kang    public int getVideoDefinitionLevel() {
30621abb13274f379e69c3c86d51e493122cccca345Dongwon Kang        return mVideoFormat;
307900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho    }
308e51a084c2518f17cee43c86e2f9df907d1f2cafbDongwon Kang
309e51a084c2518f17cee43c86e2f9df907d1f2cafbDongwon Kang    @Override
310e51a084c2518f17cee43c86e2f9df907d1f2cafbDongwon Kang    public int getAudioChannelCount() {
311e51a084c2518f17cee43c86e2f9df907d1f2cafbDongwon Kang        return mAudioChannelCount;
312e51a084c2518f17cee43c86e2f9df907d1f2cafbDongwon Kang    }
313e51a084c2518f17cee43c86e2f9df907d1f2cafbDongwon Kang
314e51a084c2518f17cee43c86e2f9df907d1f2cafbDongwon Kang    @Override
315e51a084c2518f17cee43c86e2f9df907d1f2cafbDongwon Kang    public boolean hasClosedCaption() {
316e51a084c2518f17cee43c86e2f9df907d1f2cafbDongwon Kang        return mHasClosedCaption;
317e51a084c2518f17cee43c86e2f9df907d1f2cafbDongwon Kang    }
3184e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kim
3194e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kim    @Override
3204e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kim    public boolean isVideoAvailable() {
3214e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kim        return mIsVideoAvailable;
3224e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kim    }
3234e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kim
3244e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kim    @Override
3254e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kim    public int getVideoUnavailableReason() {
3264e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kim        return mVideoUnavailableReason;
3274e5a6a47adb40039a27536236656204f9ad12df3Jaewan Kim    }
328d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten
329d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten    public void setOnUnhandledInputEventListener(OnUnhandledInputEventListener listener) {
330d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        mTvView.setOnUnhandledInputEventListener(listener);
331d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten    }
332d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten
333d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten    public List<TvTrackInfo> getTracks() {
334d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        return mTvView.getTracks();
335d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten    }
336d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten
337d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten    public void selectTrack(TvTrackInfo track) {
338d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        mTvView.selectTrack(track);
339d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten    }
340d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten
341d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten    private void block(int reason) {
342d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        hideBlock();
343d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        switch (reason) {
344d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten            case TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN:
345d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten            default:
346d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten                findViewById(R.id.block_reason_unknown).setVisibility(VISIBLE);
347d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten                break;
348d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten            case TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNE:
349d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten                findViewById(R.id.block_reason_tune).setVisibility(VISIBLE);
350d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten                break;
351d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten            case TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL:
352d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten                findViewById(R.id.block_reason_weak_signal).setVisibility(VISIBLE);
353d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten                break;
354d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten            case TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING:
355d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten                findViewById(R.id.block_reason_buffering).setVisibility(VISIBLE);
356d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten                break;
357d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        }
358d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        mute();
359d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten    }
360d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten
361d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten    private void unblock() {
362d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        hideBlock();
363d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        unmute();
364d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten    }
365d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten
366d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten    private void mute() {
367d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        mIsMuted = true;
368d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        mTvView.setStreamVolume(0);
369d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten    }
370d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten
371d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten    private void unmute() {
372d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        mIsMuted = false;
373d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        mTvView.setStreamVolume(mVolume);
374d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten    }
375d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten
376d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten    private SurfaceView findSurfaceView(ViewGroup view) {
377d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        for (int i = 0; i < view.getChildCount(); ++i) {
378d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten            if (view.getChildAt(i) instanceof SurfaceView) {
379d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten                SurfaceView surfaceView = (SurfaceView) mTvView.getChildAt(i);
380d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten                surfaceView.getHolder().addCallback(mSurfaceHolderCallback);
381d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten                return surfaceView;
382d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten            }
383d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        }
384d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        throw new RuntimeException("TvView does not have SurfaceView.");
385d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten    }
386d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten
387d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten    private void hideBlock() {
388d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        findViewById(R.id.block_reason_unknown).setVisibility(GONE);
389d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        findViewById(R.id.block_reason_tune).setVisibility(GONE);
390d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        findViewById(R.id.block_reason_weak_signal).setVisibility(GONE);
391d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten        findViewById(R.id.block_reason_buffering).setVisibility(GONE);
392d82ca62a0fefaed41493540df7d622cfcfb2cb6cChristofer Åkersten    }
393900cc7509f1edf86db9ba1e4b71b7d1096769a0aYoungsang Cho}
394