1/*
2 * Copyright (C) 2015 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 com.android.tv.tuner.tvinput;
18
19import android.annotation.TargetApi;
20import android.content.Context;
21import android.media.PlaybackParams;
22import android.media.tv.TvContentRating;
23import android.media.tv.TvInputManager;
24import android.media.tv.TvInputService;
25import android.net.Uri;
26import android.os.Build;
27import android.os.Handler;
28import android.os.Message;
29import android.os.SystemClock;
30import android.text.Html;
31import android.util.Log;
32import android.view.LayoutInflater;
33import android.view.Surface;
34import android.view.View;
35import android.view.ViewGroup;
36import android.widget.TextView;
37import android.widget.Toast;
38
39import com.google.android.exoplayer.audio.AudioCapabilities;
40import com.android.tv.tuner.R;
41import com.android.tv.tuner.TunerPreferences;
42import com.android.tv.tuner.TunerPreferences.TunerPreferencesChangedListener;
43import com.android.tv.tuner.cc.CaptionLayout;
44import com.android.tv.tuner.cc.CaptionTrackRenderer;
45import com.android.tv.tuner.data.Cea708Data.CaptionEvent;
46import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
47import com.android.tv.tuner.util.GlobalSettingsUtils;
48import com.android.tv.tuner.util.StatusTextUtils;
49import com.android.tv.tuner.util.SystemPropertiesProxy;
50
51/**
52 * Provides a tuner TV input session. It handles Overlay UI works. Main tuner input functions
53 * are implemented in {@link TunerSessionWorker}.
54 */
55public class TunerSession extends TvInputService.Session implements
56        Handler.Callback, TunerPreferencesChangedListener {
57    private static final String TAG = "TunerSession";
58    private static final boolean DEBUG = false;
59    private static final String USBTUNER_SHOW_DEBUG = "persist.tv.tuner.show_debug";
60
61    public static final int MSG_UI_SHOW_MESSAGE = 1;
62    public static final int MSG_UI_HIDE_MESSAGE = 2;
63    public static final int MSG_UI_SHOW_AUDIO_UNPLAYABLE = 3;
64    public static final int MSG_UI_HIDE_AUDIO_UNPLAYABLE = 4;
65    public static final int MSG_UI_PROCESS_CAPTION_TRACK = 5;
66    public static final int MSG_UI_START_CAPTION_TRACK = 6;
67    public static final int MSG_UI_STOP_CAPTION_TRACK = 7;
68    public static final int MSG_UI_RESET_CAPTION_TRACK = 8;
69    public static final int MSG_UI_CLEAR_CAPTION_RENDERER = 9;
70    public static final int MSG_UI_SET_STATUS_TEXT = 10;
71    public static final int MSG_UI_TOAST_RESCAN_NEEDED = 11;
72
73    private final Context mContext;
74    private final Handler mUiHandler;
75    private final View mOverlayView;
76    private final TextView mMessageView;
77    private final TextView mStatusView;
78    private final TextView mAudioStatusView;
79    private final ViewGroup mMessageLayout;
80    private final CaptionTrackRenderer mCaptionTrackRenderer;
81    private final TunerSessionWorker mSessionWorker;
82    private boolean mReleased = false;
83    private boolean mPlayPaused;
84    private long mTuneStartTimestamp;
85
86    public TunerSession(Context context, ChannelDataManager channelDataManager) {
87        super(context);
88        mContext = context;
89        mUiHandler = new Handler(this);
90        LayoutInflater inflater = (LayoutInflater)
91                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
92        mOverlayView = inflater.inflate(R.layout.ut_overlay_view, null);
93        mMessageLayout = (ViewGroup) mOverlayView.findViewById(R.id.message_layout);
94        mMessageLayout.setVisibility(View.INVISIBLE);
95        mMessageView = (TextView) mOverlayView.findViewById(R.id.message);
96        mStatusView = (TextView) mOverlayView.findViewById(R.id.tuner_status);
97        boolean showDebug = SystemPropertiesProxy.getBoolean(USBTUNER_SHOW_DEBUG, false);
98        mStatusView.setVisibility(showDebug ? View.VISIBLE : View.INVISIBLE);
99        mAudioStatusView = (TextView) mOverlayView.findViewById(R.id.audio_status);
100        mAudioStatusView.setVisibility(View.INVISIBLE);
101        CaptionLayout captionLayout = (CaptionLayout) mOverlayView.findViewById(R.id.caption);
102        mCaptionTrackRenderer = new CaptionTrackRenderer(captionLayout);
103        mSessionWorker = new TunerSessionWorker(context, channelDataManager, this);
104        TunerPreferences.setTunerPreferencesChangedListener(this);
105    }
106
107    public boolean isReleased() {
108        return mReleased;
109    }
110
111    @Override
112    public View onCreateOverlayView() {
113        return mOverlayView;
114    }
115
116    @Override
117    public boolean onSelectTrack(int type, String trackId) {
118        mSessionWorker.sendMessage(TunerSessionWorker.MSG_SELECT_TRACK, type, 0, trackId);
119        return false;
120    }
121
122    @Override
123    public void onSetCaptionEnabled(boolean enabled) {
124        mSessionWorker.setCaptionEnabled(enabled);
125    }
126
127    @Override
128    public void onSetStreamVolume(float volume) {
129        mSessionWorker.setStreamVolume(volume);
130    }
131
132    @Override
133    public boolean onSetSurface(Surface surface) {
134        mSessionWorker.setSurface(surface);
135        return true;
136    }
137
138    @Override
139    public void onTimeShiftPause() {
140        mSessionWorker.sendMessage(TunerSessionWorker.MSG_TIMESHIFT_PAUSE);
141        mPlayPaused = true;
142    }
143
144    @Override
145    public void onTimeShiftResume() {
146        mSessionWorker.sendMessage(TunerSessionWorker.MSG_TIMESHIFT_RESUME);
147        mPlayPaused = false;
148    }
149
150    @Override
151    public void onTimeShiftSeekTo(long timeMs) {
152        if (DEBUG) Log.d(TAG, "Timeshift seekTo requested position: " + timeMs / 1000);
153        mSessionWorker.sendMessage(TunerSessionWorker.MSG_TIMESHIFT_SEEK_TO,
154                mPlayPaused ? 1 : 0, 0, timeMs);
155    }
156
157    @Override
158    public void onTimeShiftSetPlaybackParams(PlaybackParams params) {
159        mSessionWorker.sendMessage(
160                TunerSessionWorker.MSG_TIMESHIFT_SET_PLAYBACKPARAMS, params);
161    }
162
163    @Override
164    public long onTimeShiftGetStartPosition() {
165        return mSessionWorker.getStartPosition();
166    }
167
168    @Override
169    public long onTimeShiftGetCurrentPosition() {
170        return mSessionWorker.getCurrentPosition();
171    }
172
173    @Override
174    public boolean onTune(Uri channelUri) {
175        if (DEBUG) {
176            Log.d(TAG, "onTune to " + channelUri != null ? channelUri.toString() : "");
177        }
178        if (channelUri == null) {
179            Log.w(TAG, "onTune() is failed due to null channelUri.");
180            mSessionWorker.stopTune();
181            return false;
182        }
183        mTuneStartTimestamp = SystemClock.elapsedRealtime();
184        mSessionWorker.tune(channelUri);
185        mPlayPaused = false;
186        return true;
187    }
188
189    @TargetApi(Build.VERSION_CODES.N)
190    @Override
191    public void onTimeShiftPlay(Uri recordUri) {
192        if (recordUri == null) {
193            Log.w(TAG, "onTimeShiftPlay() is failed due to null channelUri.");
194            mSessionWorker.stopTune();
195            return;
196        }
197        mTuneStartTimestamp = SystemClock.elapsedRealtime();
198        mSessionWorker.tune(recordUri);
199        mPlayPaused = false;
200    }
201
202    @Override
203    public void onUnblockContent(TvContentRating unblockedRating) {
204        mSessionWorker.sendMessage(TunerSessionWorker.MSG_UNBLOCKED_RATING,
205                unblockedRating);
206    }
207
208    @Override
209    public void onRelease() {
210        if (DEBUG) {
211            Log.d(TAG, "onRelease");
212        }
213        mReleased = true;
214        mSessionWorker.release();
215        mUiHandler.removeCallbacksAndMessages(null);
216        TunerPreferences.setTunerPreferencesChangedListener(null);
217    }
218
219    /**
220     * Sets {@link AudioCapabilities}.
221     */
222    public void setAudioCapabilities(AudioCapabilities audioCapabilities) {
223        mSessionWorker.sendMessage(TunerSessionWorker.MSG_AUDIO_CAPABILITIES_CHANGED,
224                audioCapabilities);
225    }
226
227    @Override
228    public void notifyVideoAvailable() {
229        super.notifyVideoAvailable();
230        if (mTuneStartTimestamp != 0) {
231            Log.i(TAG, "[Profiler] Video available in "
232                    + (SystemClock.elapsedRealtime() - mTuneStartTimestamp) + " ms");
233            mTuneStartTimestamp = 0;
234        }
235    }
236
237    @Override
238    public void notifyVideoUnavailable(int reason) {
239        super.notifyVideoUnavailable(reason);
240        if (reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING
241                && reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL) {
242            notifyTimeShiftStatusChanged(TvInputManager.TIME_SHIFT_STATUS_UNAVAILABLE);
243        }
244    }
245
246    public void sendUiMessage(int message) {
247        mUiHandler.sendEmptyMessage(message);
248    }
249
250    public void sendUiMessage(int message, Object object) {
251        mUiHandler.obtainMessage(message, object).sendToTarget();
252    }
253
254    public void sendUiMessage(int message, int arg1, int arg2, Object object) {
255        mUiHandler.obtainMessage(message, arg1, arg2, object).sendToTarget();
256    }
257
258    @Override
259    public boolean handleMessage(Message msg) {
260        switch (msg.what) {
261            case MSG_UI_SHOW_MESSAGE: {
262                mMessageView.setText((String) msg.obj);
263                mMessageLayout.setVisibility(View.VISIBLE);
264                return true;
265            }
266            case MSG_UI_HIDE_MESSAGE: {
267                mMessageLayout.setVisibility(View.INVISIBLE);
268                return true;
269            }
270            case MSG_UI_SHOW_AUDIO_UNPLAYABLE: {
271                // Showing message of enabling surround sound only when global surround sound
272                // setting is "never".
273                final int value = GlobalSettingsUtils.getEncodedSurroundOutputSettings(mContext);
274                if (value == GlobalSettingsUtils.ENCODED_SURROUND_OUTPUT_NEVER) {
275                    mAudioStatusView.setText(Html.fromHtml(StatusTextUtils.getAudioWarningInHTML(
276                            mContext.getString(R.string.ut_surround_sound_disabled))));
277                } else {
278                    mAudioStatusView.setText(Html.fromHtml(StatusTextUtils.getAudioWarningInHTML(
279                            mContext.getString(R.string.audio_passthrough_not_supported))));
280                }
281                mAudioStatusView.setVisibility(View.VISIBLE);
282                return true;
283            }
284            case MSG_UI_HIDE_AUDIO_UNPLAYABLE: {
285                mAudioStatusView.setVisibility(View.INVISIBLE);
286                return true;
287            }
288            case MSG_UI_PROCESS_CAPTION_TRACK: {
289                mCaptionTrackRenderer.processCaptionEvent((CaptionEvent) msg.obj);
290                return true;
291            }
292            case MSG_UI_START_CAPTION_TRACK: {
293                mCaptionTrackRenderer.start((AtscCaptionTrack) msg.obj);
294                return true;
295            }
296            case MSG_UI_STOP_CAPTION_TRACK: {
297                mCaptionTrackRenderer.stop();
298                return true;
299            }
300            case MSG_UI_RESET_CAPTION_TRACK: {
301                mCaptionTrackRenderer.reset();
302                return true;
303            }
304            case MSG_UI_CLEAR_CAPTION_RENDERER: {
305                mCaptionTrackRenderer.clear();
306                return true;
307            }
308            case MSG_UI_SET_STATUS_TEXT: {
309                mStatusView.setText((CharSequence) msg.obj);
310                return true;
311            }
312            case MSG_UI_TOAST_RESCAN_NEEDED: {
313                Toast.makeText(mContext, R.string.ut_rescan_needed, Toast.LENGTH_LONG).show();
314                return true;
315            }
316        }
317        return false;
318    }
319
320    @Override
321    public void onTunerPreferencesChanged() {
322        mSessionWorker.sendMessage(TunerSessionWorker.MSG_TUNER_PREFERENCES_CHANGED);
323    }
324}
325