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