TunableTvView.java revision 4e5a6a47adb40039a27536236656204f9ad12df3
1package com.android.tv.ui;
2
3import android.content.Context;
4import android.content.pm.PackageManager;
5import android.media.tv.TvInputInfo;
6import android.media.tv.TvInputManager;
7import android.media.tv.TvTrackInfo;
8import android.media.tv.TvView;
9import android.net.Uri;
10import android.util.AttributeSet;
11import android.util.Log;
12import android.view.KeyEvent;
13import android.view.MotionEvent;
14import android.view.SurfaceHolder;
15import android.view.SurfaceView;
16
17import com.android.tv.R;
18import com.android.tv.data.Channel;
19import com.android.tv.data.StreamInfo;
20import com.android.tv.util.TvInputManagerHelper;
21import com.android.tv.util.Utils;
22
23import java.util.List;
24
25public class TunableTvView extends TvView implements StreamInfo {
26    private static final boolean DEBUG = true;
27    private static final String TAG = "TunableTvView";
28
29    private static final int DELAY_FOR_SURFACE_RELEASE = 300;
30    public static final String PERMISSION_RECEIVE_INPUT_EVENT =
31            "android.permission.RECEIVE_INPUT_EVENT";
32
33    private long mChannelId = Channel.INVALID_ID;
34    private TvInputManagerHelper mInputManagerHelper;
35    private boolean mStarted;
36    private TvInputInfo mInputInfo;
37    private OnTuneListener mOnTuneListener;
38    private int mVideoWidth;
39    private int mVideoHeight;
40    private int mVideoFormat = StreamInfo.VIDEO_DEFINITION_LEVEL_UNKNOWN;
41    private int mAudioChannelCount = StreamInfo.AUDIO_CHANNEL_COUNT_UNKNOWN;
42    private boolean mHasClosedCaption = false;
43    private boolean mIsVideoAvailable;
44    private int mVideoUnavailableReason;
45    private SurfaceView mSurface;
46    private boolean mCanReceiveInputEvent;
47
48    private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
49        @Override
50        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { }
51
52        @Override
53        public void surfaceCreated(SurfaceHolder holder) { }
54
55        @Override
56        public void surfaceDestroyed(SurfaceHolder holder) {
57            // TODO: It is a hack to wait to release a surface at TIS. If there is a way to
58            // know when the surface is released at TIS, we don't need this hack.
59            try {
60                if (DEBUG) Log.d(TAG, "Sleep to wait destroying a surface");
61                Thread.sleep(DELAY_FOR_SURFACE_RELEASE);
62                if (DEBUG) Log.d(TAG, "Wake up from sleeping");
63            } catch (InterruptedException e) {
64                e.printStackTrace();
65            }
66        }
67    };
68
69    private final TvInputListener mListener =
70            new TvInputListener() {
71                @Override
72                public void onError(String inputId, int errorCode) {
73                    if (errorCode == TvView.ERROR_BUSY) {
74                        Log.w(TAG, "Failed to bind an input");
75                        long channelId = mChannelId;
76                        mChannelId = Channel.INVALID_ID;
77                        mInputInfo = null;
78                        mCanReceiveInputEvent = false;
79                        if (mOnTuneListener != null) {
80                            mOnTuneListener.onTuned(false, channelId);
81                            mOnTuneListener = null;
82                        }
83                    } else if (errorCode == TvView.ERROR_TV_INPUT_DISCONNECTED) {
84                        Log.w(TAG, "Session is released by crash");
85                        long channelId = mChannelId;
86                        mChannelId = Channel.INVALID_ID;
87                        mInputInfo = null;
88                        mCanReceiveInputEvent = false;
89                        if (mOnTuneListener != null) {
90                            mOnTuneListener.onUnexpectedStop(channelId);
91                            mOnTuneListener = null;
92                        }
93                    }
94                }
95
96                @Override
97                public void onChannelRetuned(String inputId, Uri channelUri) {
98                    if (DEBUG) {
99                        Log.d(TAG, "onChannelRetuned(inputId=" + inputId + ", channelUri="
100                                + channelUri + ")");
101                    }
102                    // TODO: update {@code mChannelId}.
103                    if (mOnTuneListener != null) {
104                        mOnTuneListener.onChannelChanged(channelUri);
105                    }
106                }
107
108                @Override
109                public void onTrackInfoChanged(String inputId, List<TvTrackInfo> tracks) {
110                    for (TvTrackInfo track : tracks) {
111                        int type = track.getInt(TvTrackInfo.KEY_TYPE);
112                        boolean selected = track.getBoolean(TvTrackInfo.KEY_IS_SELECTED);
113                        if (type == TvTrackInfo.VALUE_TYPE_VIDEO && selected) {
114                            mVideoWidth = track.getInt(TvTrackInfo.KEY_WIDTH);
115                            mVideoHeight = track.getInt(TvTrackInfo.KEY_HEIGHT);
116                            mVideoFormat = Utils.getVideoDefinitionLevelFromSize(
117                                    mVideoWidth, mVideoHeight);
118                        } else if (type == TvTrackInfo.VALUE_TYPE_AUDIO && selected) {
119                            mAudioChannelCount = track.getInt(TvTrackInfo.KEY_CHANNEL_COUNT);
120                        } else if (type == TvTrackInfo.VALUE_TYPE_SUBTITLE) {
121                            mHasClosedCaption = true;
122                        }
123                    }
124                    if (mOnTuneListener != null) {
125                        mOnTuneListener.onStreamInfoChanged(TunableTvView.this);
126                    }
127                }
128
129                @Override
130                public void onVideoAvailable(String inputId) {
131                    mIsVideoAvailable = true;
132                    if (mOnTuneListener != null) {
133                        mOnTuneListener.onStreamInfoChanged(TunableTvView.this);
134                    }
135                }
136
137                @Override
138                public void onVideoUnavailable(String inputId, int reason) {
139                    mIsVideoAvailable = false;
140                    mVideoUnavailableReason = reason;
141                    if (mOnTuneListener != null) {
142                        mOnTuneListener.onStreamInfoChanged(TunableTvView.this);
143                    }
144                }
145            };
146
147    public TunableTvView(Context context) {
148        this(context, null, 0);
149    }
150
151    public TunableTvView(Context context, AttributeSet attrs) {
152        this(context, attrs, 0);
153    }
154
155    public TunableTvView(Context context, AttributeSet attrs, int defStyleAttr) {
156        super(context, attrs, defStyleAttr);
157        for (int i = 0; i < getChildCount(); ++i) {
158            if (getChildAt(i) instanceof SurfaceView) {
159                mSurface = (SurfaceView) getChildAt(i);
160                mSurface.getHolder().addCallback(mSurfaceHolderCallback);
161                return;
162            }
163        }
164        throw new RuntimeException("TvView does not have SurfaceView.");
165    }
166
167    public void start(TvInputManagerHelper tvInputManagerHelper) {
168        mInputManagerHelper = tvInputManagerHelper;
169        if (mStarted) {
170            return;
171        }
172        mStarted = true;
173    }
174
175    public void stop() {
176        if (!mStarted) {
177            return;
178        }
179        mStarted = false;
180        reset();
181        mChannelId = Channel.INVALID_ID;
182        mInputInfo = null;
183        mCanReceiveInputEvent = false;
184        mOnTuneListener = null;
185    }
186
187    public boolean isPlaying() {
188        return mStarted;
189    }
190
191    public boolean tuneTo(long channelId, OnTuneListener listener) {
192        if (!mStarted) {
193            throw new IllegalStateException("TvView isn't started");
194        }
195        if (DEBUG) Log.d(TAG, "tuneTo " + channelId);
196        mVideoWidth = 0;
197        mVideoHeight = 0;
198        mVideoFormat = StreamInfo.VIDEO_DEFINITION_LEVEL_UNKNOWN;
199        mAudioChannelCount = StreamInfo.AUDIO_CHANNEL_COUNT_UNKNOWN;
200        mHasClosedCaption = false;
201        String inputId = Utils.getInputIdForChannel(getContext(), channelId);
202        TvInputInfo inputInfo = mInputManagerHelper.getTvInputInfo(inputId);
203        if (inputInfo == null
204                || mInputManagerHelper.getInputState(inputInfo) ==
205                        TvInputManager.INPUT_STATE_DISCONNECTED) {
206            return false;
207        }
208        mOnTuneListener = listener;
209        mChannelId = channelId;
210        if (!inputInfo.equals(mInputInfo)) {
211            reset();
212            // TODO: It is a hack to wait to release a surface at TIS. If there is a way to
213            // know when the surface is released at TIS, we don't need this hack.
214            try {
215                Thread.sleep(DELAY_FOR_SURFACE_RELEASE);
216            } catch (InterruptedException e) {
217                e.printStackTrace();
218            }
219            mInputInfo = inputInfo;
220            mCanReceiveInputEvent = mContext.getPackageManager().checkPermission(
221                    PERMISSION_RECEIVE_INPUT_EVENT, mInputInfo.getComponent().getPackageName())
222                            == PackageManager.PERMISSION_GRANTED;
223        }
224        setTvInputListener(mListener);
225        tune(mInputInfo.getId(), Utils.getChannelUri(mChannelId));
226        if (mOnTuneListener != null) {
227            // TODO: Add a callback for tune complete and call onTuned when it was successful.
228            mOnTuneListener.onTuned(true, mChannelId);
229        }
230        return true;
231    }
232
233    @Override
234    public TvInputInfo getCurrentTvInputInfo() {
235        return mInputInfo;
236    }
237
238    public long getCurrentChannelId() {
239        return mChannelId;
240    }
241
242    public void setPip(boolean isPip) {
243        mSurface.setZOrderMediaOverlay(isPip);
244    }
245
246    @Override
247    public void setStreamVolume(float volume) {
248        if (!mStarted) {
249            throw new IllegalStateException("TvView isn't started");
250        }
251        if (DEBUG)
252            Log.d(TAG, "setStreamVolume " + volume);
253        super.setStreamVolume(volume);
254    }
255
256    @Override
257    public boolean dispatchKeyEvent(KeyEvent event) {
258        return mCanReceiveInputEvent && super.dispatchKeyEvent(event);
259    }
260
261    @Override
262    public boolean dispatchTouchEvent(MotionEvent event) {
263        return mCanReceiveInputEvent && super.dispatchTouchEvent(event);
264    }
265
266    @Override
267    public boolean dispatchTrackballEvent(MotionEvent event) {
268        return mCanReceiveInputEvent && super.dispatchTrackballEvent(event);
269    }
270
271    @Override
272    public boolean dispatchGenericMotionEvent(MotionEvent event) {
273        return mCanReceiveInputEvent && super.dispatchGenericMotionEvent(event);
274    }
275
276    public interface OnTuneListener {
277        void onTuned(boolean success, long channelId);
278        void onUnexpectedStop(long channelId);
279        void onStreamInfoChanged(StreamInfo info);
280        void onChannelChanged(Uri channel);
281    }
282
283    @Override
284    public int getVideoWidth() {
285        return mVideoWidth;
286    }
287
288    @Override
289    public int getVideoHeight() {
290        return mVideoHeight;
291    }
292
293    @Override
294    public int getVideoDefinitionLevel() {
295        return mVideoFormat;
296    }
297
298    @Override
299    public int getAudioChannelCount() {
300        return mAudioChannelCount;
301    }
302
303    @Override
304    public boolean hasClosedCaption() {
305        return mHasClosedCaption;
306    }
307
308    @Override
309    public boolean isVideoAvailable() {
310        return mIsVideoAvailable;
311    }
312
313    @Override
314    public int getVideoUnavailableReason() {
315        return mVideoUnavailableReason;
316    }
317}
318