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.content.ContentResolver;
20import android.content.ContentUris;
21import android.content.Context;
22import android.database.Cursor;
23import android.media.MediaFormat;
24import android.media.PlaybackParams;
25import android.media.tv.TvContentRating;
26import android.media.tv.TvContract;
27import android.media.tv.TvInputManager;
28import android.media.tv.TvTrackInfo;
29import android.net.Uri;
30import android.os.Handler;
31import android.os.HandlerThread;
32import android.os.Message;
33import android.os.SystemClock;
34import android.support.annotation.AnyThread;
35import android.support.annotation.MainThread;
36import android.support.annotation.WorkerThread;
37import android.text.Html;
38import android.util.Log;
39import android.util.Pair;
40import android.util.SparseArray;
41import android.view.Surface;
42import android.view.accessibility.CaptioningManager;
43
44import com.google.android.exoplayer.audio.AudioCapabilities;
45import com.google.android.exoplayer.ExoPlayer;
46import com.android.tv.common.SoftPreconditions;
47import com.android.tv.common.TvContentRatingCache;
48import com.android.tv.tuner.TunerPreferences;
49import com.android.tv.tuner.data.Cea708Data;
50import com.android.tv.tuner.data.PsipData.EitItem;
51import com.android.tv.tuner.data.PsipData.TvTracksInterface;
52import com.android.tv.tuner.data.TunerChannel;
53import com.android.tv.tuner.data.nano.Channel;
54import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
55import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
56import com.android.tv.tuner.exoplayer.MpegTsRendererBuilder;
57import com.android.tv.tuner.exoplayer.buffer.BufferManager;
58import com.android.tv.tuner.exoplayer.buffer.DvrStorageManager;
59import com.android.tv.tuner.exoplayer.MpegTsPlayer;
60import com.android.tv.tuner.source.TsDataSource;
61import com.android.tv.tuner.source.TsDataSourceManager;
62import com.android.tv.tuner.util.StatusTextUtils;
63
64import java.io.File;
65import java.io.FileNotFoundException;
66import java.io.IOException;
67import java.util.ArrayList;
68import java.util.Iterator;
69import java.util.List;
70import java.util.Objects;
71import java.util.concurrent.CountDownLatch;
72
73/**
74 * {@link TunerSessionWorker} implements a handler thread which processes TV input jobs
75 * such as handling {@link ExoPlayer}, managing a tuner device, trickplay, and so on.
76 */
77@WorkerThread
78public class TunerSessionWorker implements PlaybackBufferListener,
79        MpegTsPlayer.VideoEventListener, MpegTsPlayer.Listener, EventDetector.EventListener,
80        ChannelDataManager.ProgramInfoListener, Handler.Callback {
81    private static final String TAG = "TunerSessionWorker";
82    private static final boolean DEBUG = false;
83    private static final boolean ENABLE_PROFILER = true;
84    private static final String PLAY_FROM_CHANNEL = "channel";
85
86    // Public messages
87    public static final int MSG_SELECT_TRACK = 1;
88    public static final int MSG_UPDATE_CAPTION_TRACK = 2;
89    public static final int MSG_SET_STREAM_VOLUME = 3;
90    public static final int MSG_TIMESHIFT_PAUSE = 4;
91    public static final int MSG_TIMESHIFT_RESUME = 5;
92    public static final int MSG_TIMESHIFT_SEEK_TO = 6;
93    public static final int MSG_TIMESHIFT_SET_PLAYBACKPARAMS = 7;
94    public static final int MSG_AUDIO_CAPABILITIES_CHANGED = 8;
95    public static final int MSG_UNBLOCKED_RATING = 9;
96
97    // Private messages
98    private static final int MSG_TUNE = 1000;
99    private static final int MSG_RELEASE = 1001;
100    private static final int MSG_RETRY_PLAYBACK = 1002;
101    private static final int MSG_START_PLAYBACK = 1003;
102    private static final int MSG_UPDATE_PROGRAM = 1008;
103    private static final int MSG_SCHEDULE_OF_PROGRAMS = 1009;
104    private static final int MSG_UPDATE_CHANNEL_INFO = 1010;
105    private static final int MSG_TRICKPLAY_BY_SEEK = 1011;
106    private static final int MSG_SMOOTH_TRICKPLAY_MONITOR = 1012;
107    private static final int MSG_PARENTAL_CONTROLS = 1015;
108    private static final int MSG_RESCHEDULE_PROGRAMS = 1016;
109    private static final int MSG_BUFFER_START_TIME_CHANGED = 1017;
110    private static final int MSG_CHECK_SIGNAL = 1018;
111    private static final int MSG_DISCOVER_CAPTION_SERVICE_NUMBER = 1019;
112    private static final int MSG_RESET_PLAYBACK = 1020;
113    private static final int MSG_BUFFER_STATE_CHANGED = 1021;
114    private static final int MSG_PROGRAM_DATA_RESULT = 1022;
115    private static final int MSG_STOP_TUNE = 1023;
116    private static final int MSG_SET_SURFACE = 1024;
117    private static final int MSG_NOTIFY_AUDIO_TRACK_UPDATED = 1025;
118
119    private static final int TS_PACKET_SIZE = 188;
120    private static final int CHECK_NO_SIGNAL_INITIAL_DELAY_MS = 4000;
121    private static final int CHECK_NO_SIGNAL_PERIOD_MS = 500;
122    private static final int RECOVER_STOPPED_PLAYBACK_PERIOD_MS = 2500;
123    private static final int PARENTAL_CONTROLS_INTERVAL_MS = 5000;
124    private static final int RESCHEDULE_PROGRAMS_INITIAL_DELAY_MS = 4000;
125    private static final int RESCHEDULE_PROGRAMS_INTERVAL_MS = 10000;
126    private static final int RESCHEDULE_PROGRAMS_TOLERANCE_MS = 2000;
127    // The following 3s is defined empirically. This should be larger than 2s considering video
128    // key frame interval in the TS stream.
129    private static final int PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS = 3000;
130    private static final int PLAYBACK_RETRY_DELAY_MS = 5000;
131    private static final int MAX_IMMEDIATE_RETRY_COUNT = 5;
132    private static final long INVALID_TIME = -1;
133
134    // Some examples of the track ids of the audio tracks, "a0", "a1", "a2".
135    // The number after prefix is being used for indicating a index of the given audio track.
136    private static final String AUDIO_TRACK_PREFIX = "a";
137
138    // Some examples of the tracks id of the caption tracks, "s1", "s2", "s3".
139    // The number after prefix is being used for indicating a index of a caption service number
140    // of the given caption track.
141    private static final String SUBTITLE_TRACK_PREFIX = "s";
142    private static final int TRACK_PREFIX_SIZE = 1;
143    private static final String VIDEO_TRACK_ID = "v";
144    private static final long BUFFER_UNDERFLOW_BUFFER_MS = 5000;
145
146    // Actual interval would be divided by the speed.
147    private static final int EXPECTED_KEY_FRAME_INTERVAL_MS = 500;
148    private static final int MIN_TRICKPLAY_SEEK_INTERVAL_MS = 20;
149    private static final int TRICKPLAY_MONITOR_INTERVAL_MS = 250;
150
151    private final Context mContext;
152    private final ChannelDataManager mChannelDataManager;
153    private final TsDataSourceManager mSourceManager;
154    private volatile Surface mSurface;
155    private volatile float mVolume = 1.0f;
156    private volatile boolean mCaptionEnabled;
157    private volatile MpegTsPlayer mPlayer;
158    private volatile TunerChannel mChannel;
159    private volatile Long mRecordingDuration;
160    private volatile long mRecordStartTimeMs;
161    private volatile long mBufferStartTimeMs;
162    private String mRecordingId;
163    private final Handler mHandler;
164    private int mRetryCount;
165    private final ArrayList<TvTrackInfo> mTvTracks;
166    private final SparseArray<AtscAudioTrack> mAudioTrackMap;
167    private final SparseArray<AtscCaptionTrack> mCaptionTrackMap;
168    private AtscCaptionTrack mCaptionTrack;
169    private PlaybackParams mPlaybackParams = new PlaybackParams();
170    private boolean mPlayerStarted = false;
171    private boolean mReportedDrawnToSurface = false;
172    private boolean mReportedWeakSignal = false;
173    private EitItem mProgram;
174    private List<EitItem> mPrograms;
175    private final TvInputManager mTvInputManager;
176    private boolean mChannelBlocked;
177    private TvContentRating mUnblockedContentRating;
178    private long mLastPositionMs;
179    private AudioCapabilities mAudioCapabilities;
180    private final CountDownLatch mReleaseLatch = new CountDownLatch(1);
181    private long mLastLimitInBytes;
182    private long mLastPositionInBytes;
183    private final BufferManager mBufferManager;
184    private final TvContentRatingCache mTvContentRatingCache = TvContentRatingCache.getInstance();
185    private final TunerSession mSession;
186    private int mPlayerState = ExoPlayer.STATE_IDLE;
187    private long mPreparingStartTimeMs;
188    private long mBufferingStartTimeMs;
189    private long mReadyStartTimeMs;
190
191    public TunerSessionWorker(Context context, ChannelDataManager channelDataManager,
192                BufferManager bufferManager, TunerSession tunerSession) {
193        if (DEBUG) Log.d(TAG, "TunerSessionWorker created");
194        mContext = context;
195
196        // HandlerThread should be set up before it is registered as a listener in the all other
197        // components.
198        HandlerThread handlerThread = new HandlerThread(TAG);
199        handlerThread.start();
200        mHandler = new Handler(handlerThread.getLooper(), this);
201        mSession = tunerSession;
202        mChannelDataManager = channelDataManager;
203        mChannelDataManager.setListener(this);
204        mChannelDataManager.checkDataVersion(mContext);
205        mSourceManager = TsDataSourceManager.createSourceManager(false);
206        mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE);
207        mTvTracks = new ArrayList<>();
208        mAudioTrackMap = new SparseArray<>();
209        mCaptionTrackMap = new SparseArray<>();
210        CaptioningManager captioningManager =
211                (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
212        mCaptionEnabled = captioningManager.isEnabled();
213        mPlaybackParams.setSpeed(1.0f);
214        mBufferManager = bufferManager;
215        mPreparingStartTimeMs = INVALID_TIME;
216        mBufferingStartTimeMs = INVALID_TIME;
217        mReadyStartTimeMs = INVALID_TIME;
218    }
219
220    // Public methods
221    @MainThread
222    public void tune(Uri channelUri) {
223        mHandler.removeCallbacksAndMessages(null);
224        mSourceManager.setHasPendingTune();
225        sendMessage(MSG_TUNE, channelUri);
226    }
227
228    @MainThread
229    public void stopTune() {
230        mHandler.removeCallbacksAndMessages(null);
231        sendMessage(MSG_STOP_TUNE);
232    }
233
234    /**
235     * Sets {@link Surface}.
236     */
237    @MainThread
238    public void setSurface(Surface surface) {
239        if (surface != null && !surface.isValid()) {
240            Log.w(TAG, "Ignoring invalid surface.");
241            return;
242        }
243        // mSurface is kept even when tune is called right after. But, messages can be deleted by
244        // tune or updateChannelBlockStatus. So mSurface should be stored here, not through message.
245        mSurface = surface;
246        mHandler.sendEmptyMessage(MSG_SET_SURFACE);
247    }
248
249    /**
250     * Sets volume.
251     */
252    @MainThread
253    public void setStreamVolume(float volume) {
254        // mVolume is kept even when tune is called right after. But, messages can be deleted by
255        // tune or updateChannelBlockStatus. So mVolume is stored here and mPlayer.setVolume will be
256        // called in MSG_SET_STREAM_VOLUME.
257        mVolume = volume;
258        mHandler.sendEmptyMessage(MSG_SET_STREAM_VOLUME);
259    }
260
261    /**
262     * Sets if caption is enabled or disabled.
263     */
264    @MainThread
265    public void setCaptionEnabled(boolean captionEnabled) {
266        // mCaptionEnabled is kept even when tune is called right after. But, messages can be
267        // deleted by tune or updateChannelBlockStatus. So mCaptionEnabled is stored here and
268        // start/stopCaptionTrack will be called in MSG_UPDATE_CAPTION_STATUS.
269        mCaptionEnabled = captionEnabled;
270        mHandler.sendEmptyMessage(MSG_UPDATE_CAPTION_TRACK);
271    }
272
273    public TunerChannel getCurrentChannel() {
274        return mChannel;
275    }
276
277    @MainThread
278    public long getStartPosition() {
279        return mBufferStartTimeMs;
280    }
281
282
283    private String getRecordingPath() {
284        return Uri.parse(mRecordingId).getPath();
285    }
286
287    private Long getDurationForRecording(String recordingId) {
288        try {
289            DvrStorageManager storageManager =
290                    new DvrStorageManager(new File(getRecordingPath()), false);
291            Pair<String, MediaFormat> trackInfo = null;
292            try {
293                trackInfo = storageManager.readTrackInfoFile(false);
294            } catch (FileNotFoundException e) {
295            }
296            if (trackInfo == null) {
297                trackInfo = storageManager.readTrackInfoFile(true);
298            }
299            Long durationUs = trackInfo.second.getLong(MediaFormat.KEY_DURATION);
300            // we need duration by milli for trickplay notification.
301            return durationUs != null ? durationUs / 1000 : null;
302        } catch (IOException e) {
303            Log.e(TAG, "meta file for recording was not found: " + recordingId);
304            return null;
305        }
306    }
307
308    @MainThread
309    public long getCurrentPosition() {
310        // TODO: More precise time may be necessary.
311        MpegTsPlayer mpegTsPlayer = mPlayer;
312        long currentTime = mpegTsPlayer != null
313                ? mRecordStartTimeMs + mpegTsPlayer.getCurrentPosition() : mRecordStartTimeMs;
314        if (mChannel == null && mPlayerState == ExoPlayer.STATE_ENDED) {
315            currentTime = mRecordingDuration + mRecordStartTimeMs;
316        }
317        if (DEBUG) {
318            long systemCurrentTime = System.currentTimeMillis();
319            Log.d(TAG, "currentTime = " + currentTime
320                    + " ; System.currentTimeMillis() = " + systemCurrentTime
321                    + " ; diff = " + (currentTime - systemCurrentTime));
322        }
323        return currentTime;
324    }
325
326    @AnyThread
327    public void sendMessage(int messageType) {
328        mHandler.sendEmptyMessage(messageType);
329    }
330
331    @AnyThread
332    public void sendMessage(int messageType, Object object) {
333        mHandler.obtainMessage(messageType, object).sendToTarget();
334    }
335
336    @AnyThread
337    public void sendMessage(int messageType, int arg1, int arg2, Object object) {
338        mHandler.obtainMessage(messageType, arg1, arg2, object).sendToTarget();
339    }
340
341    @MainThread
342    public void release() {
343        if (DEBUG) Log.d(TAG, "release()");
344        mChannelDataManager.setListener(null);
345        mHandler.removeCallbacksAndMessages(null);
346        mHandler.sendEmptyMessage(MSG_RELEASE);
347        try {
348            mReleaseLatch.await();
349        } catch (InterruptedException e) {
350            Log.e(TAG, "Couldn't wait for finish of MSG_RELEASE", e);
351        } finally {
352            mHandler.getLooper().quitSafely();
353        }
354    }
355
356    // MpegTsPlayer.Listener
357    // Called in the same thread as mHandler.
358    @Override
359    public void onStateChanged(boolean playWhenReady, int playbackState) {
360        if (DEBUG) Log.d(TAG, "ExoPlayer state change: " + playbackState + " " + playWhenReady);
361        if (playbackState == mPlayerState) {
362            return;
363        }
364        mReadyStartTimeMs = INVALID_TIME;
365        mPreparingStartTimeMs = INVALID_TIME;
366        mBufferingStartTimeMs = INVALID_TIME;
367        if (playbackState == ExoPlayer.STATE_READY) {
368            if (DEBUG) Log.d(TAG, "ExoPlayer ready");
369            if (!mPlayerStarted) {
370                sendMessage(MSG_START_PLAYBACK, mPlayer);
371            }
372            mReadyStartTimeMs = SystemClock.elapsedRealtime();
373        } else if (playbackState == ExoPlayer.STATE_PREPARING) {
374            mPreparingStartTimeMs = SystemClock.elapsedRealtime();
375        } else if (playbackState == ExoPlayer.STATE_BUFFERING) {
376            mBufferingStartTimeMs = SystemClock.elapsedRealtime();
377        } else if (playbackState == ExoPlayer.STATE_ENDED) {
378            // Final status
379            // notification of STATE_ENDED from MpegTsPlayer will be ignored afterwards.
380            Log.i(TAG, "Player ended: end of stream");
381            if (mChannel != null) {
382                sendMessage(MSG_RETRY_PLAYBACK, mPlayer);
383            }
384        }
385        mPlayerState = playbackState;
386    }
387
388    @Override
389    public void onError(Exception e) {
390        if (TunerPreferences.getStoreTsStream(mContext)) {
391            // Crash intentionally to capture the error causing TS file.
392            Log.e(TAG, "Crash intentionally to capture the error causing TS file. "
393                    + e.getMessage());
394            SoftPreconditions.checkState(false);
395        }
396        // There maybe some errors that finally raise ExoPlaybackException and will be handled here.
397        // If we are playing live stream, retrying playback maybe helpful. But for recorded stream,
398        // retrying playback is not helpful.
399        if (mChannel != null) {
400            mHandler.obtainMessage(MSG_RETRY_PLAYBACK, mPlayer).sendToTarget();
401        }
402    }
403
404    @Override
405    public void onVideoSizeChanged(int width, int height, float pixelWidthHeight) {
406        if (mChannel != null && mChannel.hasVideo()) {
407            updateVideoTrack(width, height);
408        }
409        if (mRecordingId != null) {
410            updateVideoTrack(width, height);
411        }
412    }
413
414    @Override
415    public void onDrawnToSurface(MpegTsPlayer player, Surface surface) {
416        if (mSurface != null && mPlayerStarted) {
417            if (DEBUG) Log.d(TAG, "MSG_DRAWN_TO_SURFACE");
418            mBufferStartTimeMs = mRecordStartTimeMs =
419                    (mRecordingId != null) ? 0 : System.currentTimeMillis();
420            notifyVideoAvailable();
421            mReportedDrawnToSurface = true;
422
423            // If surface is drawn successfully, it means that the playback was brought back
424            // to normal and therefore, the playback recovery status will be reset through
425            // setting a zero value to the retry count.
426            // TODO: Consider audio only channels for detecting playback status changes to
427            //       be normal.
428            mRetryCount = 0;
429            if (mCaptionEnabled && mCaptionTrack != null) {
430                startCaptionTrack();
431            } else {
432                stopCaptionTrack();
433            }
434            mHandler.sendEmptyMessage(MSG_NOTIFY_AUDIO_TRACK_UPDATED);
435        }
436    }
437
438    @Override
439    public void onSmoothTrickplayForceStopped() {
440        if (mPlayer == null || !mHandler.hasMessages(MSG_SMOOTH_TRICKPLAY_MONITOR)) {
441            return;
442        }
443        mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR);
444        doTrickplayBySeek((int) mPlayer.getCurrentPosition());
445    }
446
447    @Override
448    public void onAudioUnplayable() {
449        if (mPlayer == null) {
450            return;
451        }
452        Log.i(TAG, "AC3 audio cannot be played due to device limitation");
453        mSession.sendUiMessage(
454                TunerSession.MSG_UI_SHOW_AUDIO_UNPLAYABLE);
455    }
456
457    // MpegTsPlayer.VideoEventListener
458    @Override
459    public void onEmitCaptionEvent(Cea708Data.CaptionEvent event) {
460        mSession.sendUiMessage(TunerSession.MSG_UI_PROCESS_CAPTION_TRACK, event);
461    }
462
463    @Override
464    public void onDiscoverCaptionServiceNumber(int serviceNumber) {
465        sendMessage(MSG_DISCOVER_CAPTION_SERVICE_NUMBER, serviceNumber);
466    }
467
468    // ChannelDataManager.ProgramInfoListener
469    @Override
470    public void onProgramsArrived(TunerChannel channel, List<EitItem> programs) {
471        sendMessage(MSG_SCHEDULE_OF_PROGRAMS, new Pair<>(channel, programs));
472    }
473
474    @Override
475    public void onChannelArrived(TunerChannel channel) {
476        sendMessage(MSG_UPDATE_CHANNEL_INFO, channel);
477    }
478
479    @Override
480    public void onRescanNeeded() {
481        mSession.sendUiMessage(TunerSession.MSG_UI_TOAST_RESCAN_NEEDED);
482    }
483
484    @Override
485    public void onRequestProgramsResponse(TunerChannel channel, List<EitItem> programs) {
486        sendMessage(MSG_PROGRAM_DATA_RESULT, new Pair<>(channel, programs));
487    }
488
489    // PlaybackBufferListener
490    @Override
491    public void onBufferStartTimeChanged(long startTimeMs) {
492        sendMessage(MSG_BUFFER_START_TIME_CHANGED, startTimeMs);
493    }
494
495    @Override
496    public void onBufferStateChanged(boolean available) {
497        sendMessage(MSG_BUFFER_STATE_CHANGED, available);
498    }
499
500    @Override
501    public void onDiskTooSlow() {
502        sendMessage(MSG_RETRY_PLAYBACK, mPlayer);
503    }
504
505    // EventDetector.EventListener
506    @Override
507    public void onChannelDetected(TunerChannel channel, boolean channelArrivedAtFirstTime) {
508        mChannelDataManager.notifyChannelDetected(channel, channelArrivedAtFirstTime);
509    }
510
511    @Override
512    public void onEventDetected(TunerChannel channel, List<EitItem> items) {
513        mChannelDataManager.notifyEventDetected(channel, items);
514    }
515
516    @Override
517    public void onChannelScanDone() {
518        // do nothing.
519    }
520
521    private long parseChannel(Uri uri) {
522        try {
523            List<String> paths = uri.getPathSegments();
524            if (paths.size() > 1 && paths.get(0).equals(PLAY_FROM_CHANNEL)) {
525                return ContentUris.parseId(uri);
526            }
527        } catch (UnsupportedOperationException | NumberFormatException e) {
528        }
529        return -1;
530    }
531
532    private static class RecordedProgram {
533        private final long mChannelId;
534        private final String mDataUri;
535
536        private static final String[] PROJECTION = {
537            TvContract.Programs.COLUMN_CHANNEL_ID,
538            TvContract.RecordedPrograms.COLUMN_RECORDING_DATA_URI,
539        };
540
541        public RecordedProgram(Cursor cursor) {
542            int index = 0;
543            mChannelId = cursor.getLong(index++);
544            mDataUri = cursor.getString(index++);
545        }
546
547        public RecordedProgram(long channelId, String dataUri) {
548            mChannelId = channelId;
549            mDataUri = dataUri;
550        }
551
552        public static RecordedProgram onQuery(Cursor c) {
553            RecordedProgram recording = null;
554            if (c != null && c.moveToNext()) {
555                recording = new RecordedProgram(c);
556            }
557            return recording;
558        }
559
560        public String getDataUri() {
561            return mDataUri;
562        }
563    }
564
565    private RecordedProgram getRecordedProgram(Uri recordedUri) {
566        ContentResolver resolver = mContext.getContentResolver();
567        try(Cursor c = resolver.query(recordedUri, RecordedProgram.PROJECTION, null, null, null)) {
568            if (c != null) {
569                RecordedProgram result = RecordedProgram.onQuery(c);
570                if (DEBUG) {
571                    Log.d(TAG, "Finished query for " + this);
572                }
573                return result;
574            } else {
575                if (c == null) {
576                    Log.e(TAG, "Unknown query error for " + this);
577                } else {
578                    if (DEBUG) Log.d(TAG, "Canceled query for " + this);
579                }
580                return null;
581            }
582        }
583    }
584
585    private String parseRecording(Uri uri) {
586        RecordedProgram recording = getRecordedProgram(uri);
587        if (recording != null) {
588            return recording.getDataUri();
589        }
590        return null;
591    }
592
593    @Override
594    public boolean handleMessage(Message msg) {
595        switch (msg.what) {
596            case MSG_TUNE: {
597                if (DEBUG) Log.d(TAG, "MSG_TUNE");
598
599                // When sequential tuning messages arrived, it skips middle tuning messages in order
600                // to change to the last requested channel quickly.
601                if (mHandler.hasMessages(MSG_TUNE)) {
602                    return true;
603                }
604                notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING);
605                Uri channelUri = (Uri) msg.obj;
606                String recording = null;
607                long channelId = parseChannel(channelUri);
608                TunerChannel channel = (channelId == -1) ? null
609                        : mChannelDataManager.getChannel(channelId);
610                if (channelId == -1) {
611                    recording = parseRecording(channelUri);
612                }
613                if (channel == null && recording == null) {
614                    Log.w(TAG, "onTune() is failed. Can't find channel for " + channelUri);
615                    stopTune();
616                    notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN);
617                    return true;
618                }
619                mHandler.removeCallbacksAndMessages(null);
620                if (channel != null) {
621                    mChannelDataManager.requestProgramsData(channel);
622                }
623                prepareTune(channel, recording);
624                // TODO: Need to refactor. notifyContentAllowed() should not be called if parental
625                // control is turned on.
626                mSession.notifyContentAllowed();
627                resetPlayback();
628                resetTvTracks();
629                mHandler.sendEmptyMessageDelayed(MSG_RESCHEDULE_PROGRAMS,
630                        RESCHEDULE_PROGRAMS_INITIAL_DELAY_MS);
631                return true;
632            }
633            case MSG_STOP_TUNE: {
634                if (DEBUG) Log.d(TAG, "MSG_STOP_TUNE");
635                mChannel = null;
636                stopPlayback();
637                stopCaptionTrack();
638                resetTvTracks();
639                notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN);
640                return true;
641            }
642            case MSG_RELEASE: {
643                if (DEBUG) Log.d(TAG, "MSG_RELEASE");
644                mHandler.removeCallbacksAndMessages(null);
645                stopPlayback();
646                stopCaptionTrack();
647                mSourceManager.release();
648                mReleaseLatch.countDown();
649                return true;
650            }
651            case MSG_RETRY_PLAYBACK: {
652                if (mPlayer == msg.obj) {
653                    Log.i(TAG, "Retrying the playback for channel: " + mChannel);
654                    mHandler.removeMessages(MSG_RETRY_PLAYBACK);
655                    // When there is a request of retrying playback, don't reuse TunerHal.
656                    mSourceManager.setKeepTuneStatus(false);
657                    mRetryCount++;
658                    if (DEBUG) {
659                        Log.d(TAG, "MSG_RETRY_PLAYBACK " + mRetryCount);
660                    }
661                    if (mRetryCount <= MAX_IMMEDIATE_RETRY_COUNT) {
662                        resetPlayback();
663                    } else {
664                        // When it reaches this point, it may be due to an error that occurred in
665                        // the tuner device. Calling stopPlayback() resets the tuner device
666                        // to recover from the error.
667                        stopPlayback();
668                        stopCaptionTrack();
669
670                        notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL);
671
672                        // After MAX_IMMEDIATE_RETRY_COUNT, give some delay of an empirically chosen
673                        // value before recovering the playback.
674                        mHandler.sendEmptyMessageDelayed(MSG_RESET_PLAYBACK,
675                                RECOVER_STOPPED_PLAYBACK_PERIOD_MS);
676                    }
677                }
678                return true;
679            }
680            case MSG_RESET_PLAYBACK: {
681                if (DEBUG) Log.d(TAG, "MSG_RESET_PLAYBACK");
682                resetPlayback();
683                return true;
684            }
685            case MSG_START_PLAYBACK: {
686                if (DEBUG) Log.d(TAG, "MSG_START_PLAYBACK");
687                if (mChannel != null || mRecordingId != null) {
688                    startPlayback(msg.obj);
689                }
690                return true;
691            }
692            case MSG_UPDATE_PROGRAM: {
693                if (mChannel != null) {
694                    EitItem program = (EitItem) msg.obj;
695                    updateTvTracks(program, false);
696                    mHandler.sendEmptyMessage(MSG_PARENTAL_CONTROLS);
697                }
698                return true;
699            }
700            case MSG_SCHEDULE_OF_PROGRAMS: {
701                mHandler.removeMessages(MSG_UPDATE_PROGRAM);
702                Pair<TunerChannel, List<EitItem>> pair =
703                        (Pair<TunerChannel, List<EitItem>>) msg.obj;
704                TunerChannel channel = pair.first;
705                if (mChannel == null) {
706                    return true;
707                }
708                if (mChannel != null && mChannel.compareTo(channel) != 0) {
709                    return true;
710                }
711                mPrograms = pair.second;
712                EitItem currentProgram = getCurrentProgram();
713                if (currentProgram == null) {
714                    mProgram = null;
715                }
716                long currentTimeMs = getCurrentPosition();
717                if (mPrograms != null) {
718                    for (EitItem item : mPrograms) {
719                        if (currentProgram != null && currentProgram.compareTo(item) == 0) {
720                            if (DEBUG) {
721                                Log.d(TAG, "Update current TvTracks " + item);
722                            }
723                            if (mProgram != null && mProgram.compareTo(item) == 0) {
724                                continue;
725                            }
726                            mProgram = item;
727                            updateTvTracks(item, false);
728                        } else if (item.getStartTimeUtcMillis() > currentTimeMs) {
729                            if (DEBUG) {
730                                Log.d(TAG, "Update next TvTracks " + item + " "
731                                        + (item.getStartTimeUtcMillis() - currentTimeMs));
732                            }
733                            mHandler.sendMessageDelayed(
734                                    mHandler.obtainMessage(MSG_UPDATE_PROGRAM, item),
735                                    item.getStartTimeUtcMillis() - currentTimeMs);
736                        }
737                    }
738                }
739                mHandler.sendEmptyMessage(MSG_PARENTAL_CONTROLS);
740                return true;
741            }
742            case MSG_UPDATE_CHANNEL_INFO: {
743                TunerChannel channel = (TunerChannel) msg.obj;
744                if (mChannel != null && mChannel.compareTo(channel) == 0) {
745                    updateChannelInfo(channel);
746                }
747                return true;
748            }
749            case MSG_PROGRAM_DATA_RESULT: {
750                TunerChannel channel = (TunerChannel) ((Pair) msg.obj).first;
751
752                // If there already exists, skip it since real-time data is a top priority,
753                if (mChannel != null && mChannel.compareTo(channel) == 0
754                        && mPrograms == null && mProgram == null) {
755                    sendMessage(MSG_SCHEDULE_OF_PROGRAMS, msg.obj);
756                }
757                return true;
758            }
759            case MSG_TRICKPLAY_BY_SEEK: {
760                if (mPlayer == null) {
761                    return true;
762                }
763                doTrickplayBySeek(msg.arg1);
764                return true;
765            }
766            case MSG_SMOOTH_TRICKPLAY_MONITOR: {
767                if (mPlayer == null) {
768                    return true;
769                }
770                long systemCurrentTime = System.currentTimeMillis();
771                long position = getCurrentPosition();
772                if (mRecordingId == null) {
773                    // Checks if the position exceeds the upper bound when forwarding,
774                    // or exceed the lower bound when rewinding.
775                    // If the direction is not checked, there can be some issues.
776                    // (See b/29939781 for more details.)
777                    if ((position > systemCurrentTime && mPlaybackParams.getSpeed() > 0L)
778                            || (position < mBufferStartTimeMs && mPlaybackParams.getSpeed() < 0L)) {
779                        doTimeShiftResume();
780                        return true;
781                    }
782                } else {
783                    if (position > mRecordingDuration || position < 0) {
784                        doTimeShiftPause();
785                        return true;
786                    }
787                }
788                mHandler.sendEmptyMessageDelayed(MSG_SMOOTH_TRICKPLAY_MONITOR,
789                        TRICKPLAY_MONITOR_INTERVAL_MS);
790                return true;
791            }
792            case MSG_RESCHEDULE_PROGRAMS: {
793                doReschedulePrograms();
794                return true;
795            }
796            case MSG_PARENTAL_CONTROLS: {
797                doParentalControls();
798                mHandler.removeMessages(MSG_PARENTAL_CONTROLS);
799                mHandler.sendEmptyMessageDelayed(MSG_PARENTAL_CONTROLS,
800                        PARENTAL_CONTROLS_INTERVAL_MS);
801                return true;
802            }
803            case MSG_UNBLOCKED_RATING: {
804                mUnblockedContentRating = (TvContentRating) msg.obj;
805                doParentalControls();
806                mHandler.removeMessages(MSG_PARENTAL_CONTROLS);
807                mHandler.sendEmptyMessageDelayed(MSG_PARENTAL_CONTROLS,
808                        PARENTAL_CONTROLS_INTERVAL_MS);
809                return true;
810            }
811            case MSG_DISCOVER_CAPTION_SERVICE_NUMBER: {
812                int serviceNumber = (int) msg.obj;
813                doDiscoverCaptionServiceNumber(serviceNumber);
814                return true;
815            }
816            case MSG_SELECT_TRACK: {
817                if (mChannel != null) {
818                    doSelectTrack(msg.arg1, (String) msg.obj);
819                } else if (mRecordingId != null) {
820                    // TODO : mChannel == null && mRecordingId != null
821                    Log.d(TAG, "track selected for recording");
822                }
823                return true;
824            }
825            case MSG_UPDATE_CAPTION_TRACK: {
826                if (mCaptionEnabled) {
827                    startCaptionTrack();
828                } else {
829                    stopCaptionTrack();
830                }
831                return true;
832            }
833            case MSG_TIMESHIFT_PAUSE: {
834                if (DEBUG) Log.d(TAG, "MSG_TIMESHIFT_PAUSE");
835                if (mPlayer == null) {
836                    return true;
837                }
838                doTimeShiftPause();
839                return true;
840            }
841            case MSG_TIMESHIFT_RESUME: {
842                if (DEBUG) Log.d(TAG, "MSG_TIMESHIFT_RESUME");
843                if (mPlayer == null) {
844                    return true;
845                }
846                doTimeShiftResume();
847                return true;
848            }
849            case MSG_TIMESHIFT_SEEK_TO: {
850                long position = (long) msg.obj;
851                if (DEBUG) Log.d(TAG, "MSG_TIMESHIFT_SEEK_TO (position=" + position + ")");
852                if (mPlayer == null) {
853                    return true;
854                }
855                doTimeShiftSeekTo(position);
856                return true;
857            }
858            case MSG_TIMESHIFT_SET_PLAYBACKPARAMS: {
859                if (mPlayer == null) {
860                    return true;
861                }
862                doTimeShiftSetPlaybackParams((PlaybackParams) msg.obj);
863                return true;
864            }
865            case MSG_AUDIO_CAPABILITIES_CHANGED: {
866                AudioCapabilities capabilities = (AudioCapabilities) msg.obj;
867                if (DEBUG) {
868                    Log.d(TAG, "MSG_AUDIO_CAPABILITIES_CHANGED " + capabilities);
869                }
870                if (capabilities == null) {
871                    return true;
872                }
873                if (!capabilities.equals(mAudioCapabilities)) {
874                    // HDMI supported encodings are changed. restart player.
875                    mAudioCapabilities = capabilities;
876                    resetPlayback();
877                }
878                return true;
879            }
880            case MSG_SET_STREAM_VOLUME: {
881                if (mPlayer != null && mPlayer.isPlaying()) {
882                    mPlayer.setVolume(mVolume);
883                }
884                return true;
885            }
886            case MSG_BUFFER_START_TIME_CHANGED: {
887                if (mPlayer == null) {
888                    return true;
889                }
890                mBufferStartTimeMs = (long) msg.obj;
891                if (!hasEnoughBackwardBuffer()
892                        && (!mPlayer.isPlaying() || mPlaybackParams.getSpeed() < 1.0f)) {
893                    mPlayer.setPlayWhenReady(true);
894                    mPlayer.setAudioTrack(true);
895                    mPlaybackParams.setSpeed(1.0f);
896                }
897                return true;
898            }
899            case MSG_BUFFER_STATE_CHANGED: {
900                boolean available = (boolean) msg.obj;
901                mSession.notifyTimeShiftStatusChanged(available
902                        ? TvInputManager.TIME_SHIFT_STATUS_AVAILABLE
903                        : TvInputManager.TIME_SHIFT_STATUS_UNAVAILABLE);
904                return true;
905            }
906            case MSG_CHECK_SIGNAL: {
907                if (mChannel == null || mPlayer == null) {
908                    return true;
909                }
910                TsDataSource source = mPlayer.getDataSource();
911                long limitInBytes = source != null ? source.getBufferedPosition() : 0L;
912                long positionInBytes = source != null ? source.getLastReadPosition() : 0L;
913                if (TunerDebug.ENABLED) {
914                    TunerDebug.calculateDiff();
915                    mSession.sendUiMessage(TunerSession.MSG_UI_SET_STATUS_TEXT,
916                            Html.fromHtml(
917                                    StatusTextUtils.getStatusWarningInHTML(
918                                            (limitInBytes - mLastLimitInBytes)
919                                                    / TS_PACKET_SIZE,
920                                            TunerDebug.getVideoFrameDrop(),
921                                            TunerDebug.getBytesInQueue(),
922                                            TunerDebug.getAudioPositionUs(),
923                                            TunerDebug.getAudioPositionUsRate(),
924                                            TunerDebug.getAudioPtsUs(),
925                                            TunerDebug.getAudioPtsUsRate(),
926                                            TunerDebug.getVideoPtsUs(),
927                                            TunerDebug.getVideoPtsUsRate()
928                                    )));
929                }
930                if (DEBUG) {
931                    Log.d(TAG, String.format("MSG_CHECK_SIGNAL position: %d, limit: %d",
932                            positionInBytes, limitInBytes));
933                }
934                mSession.sendUiMessage(TunerSession.MSG_UI_HIDE_MESSAGE);
935                long currentTime = SystemClock.elapsedRealtime();
936                boolean noBufferRead = positionInBytes == mLastPositionInBytes
937                        && limitInBytes == mLastLimitInBytes;
938                boolean isBufferingTooLong = mBufferingStartTimeMs != INVALID_TIME
939                        && currentTime - mBufferingStartTimeMs
940                                > PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS;
941                boolean isPreparingTooLong = mPreparingStartTimeMs != INVALID_TIME
942                        && currentTime - mPreparingStartTimeMs
943                        > PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS;
944                boolean isWeakSignal = source != null
945                        && mChannel.getType() == Channel.TYPE_TUNER
946                        && (noBufferRead || isBufferingTooLong || isPreparingTooLong);
947                if (isWeakSignal && !mReportedWeakSignal) {
948                    if (!mHandler.hasMessages(MSG_RETRY_PLAYBACK)) {
949                        mHandler.sendMessageDelayed(mHandler.obtainMessage(
950                                MSG_RETRY_PLAYBACK, mPlayer), PLAYBACK_RETRY_DELAY_MS);
951                    }
952                    if (mPlayer != null) {
953                        mPlayer.setAudioTrack(false);
954                    }
955                    notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL);
956                } else if (!isWeakSignal && mReportedWeakSignal) {
957                    boolean isPlaybackStable = mReadyStartTimeMs != INVALID_TIME
958                            && currentTime - mReadyStartTimeMs
959                                    > PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS;
960                    if (!isPlaybackStable) {
961                        // Wait until playback becomes stable.
962                    } else if (mReportedDrawnToSurface) {
963                        mHandler.removeMessages(MSG_RETRY_PLAYBACK);
964                        notifyVideoAvailable();
965                        mPlayer.setAudioTrack(true);
966                    }
967                }
968                mLastLimitInBytes = limitInBytes;
969                mLastPositionInBytes = positionInBytes;
970                mHandler.sendEmptyMessageDelayed(MSG_CHECK_SIGNAL, CHECK_NO_SIGNAL_PERIOD_MS);
971                return true;
972            }
973            case MSG_SET_SURFACE: {
974                if (mPlayer != null) {
975                    mPlayer.setSurface(mSurface);
976                } else {
977                    // TODO: Since surface is dynamically set, we can remove the dependency of
978                    // playback start on mSurface nullity.
979                    resetPlayback();
980                }
981                return true;
982            }
983            case MSG_NOTIFY_AUDIO_TRACK_UPDATED: {
984                notifyAudioTracksUpdated();
985                return true;
986            }
987            default: {
988                Log.w(TAG, "Unhandled message code: " + msg.what);
989                return false;
990            }
991        }
992    }
993
994    // Private methods
995    private void doSelectTrack(int type, String trackId) {
996        int numTrackId = trackId != null
997                ? Integer.parseInt(trackId.substring(TRACK_PREFIX_SIZE)) : -1;
998        if (type == TvTrackInfo.TYPE_AUDIO) {
999            if (trackId == null) {
1000                return;
1001            }
1002            AtscAudioTrack audioTrack = mAudioTrackMap.get(numTrackId);
1003            if (audioTrack == null) {
1004                return;
1005            }
1006            int oldAudioPid = mChannel.getAudioPid();
1007            mChannel.selectAudioTrack(audioTrack.index);
1008            int newAudioPid = mChannel.getAudioPid();
1009            if (oldAudioPid != newAudioPid) {
1010                mPlayer.setSelectedTrack(MpegTsPlayer.TRACK_TYPE_AUDIO, audioTrack.index);
1011            }
1012            mSession.notifyTrackSelected(type, trackId);
1013        } else if (type == TvTrackInfo.TYPE_SUBTITLE) {
1014            if (trackId == null) {
1015                mSession.notifyTrackSelected(type, null);
1016                mCaptionTrack = null;
1017                stopCaptionTrack();
1018                return;
1019            }
1020            for (TvTrackInfo track : mTvTracks) {
1021                if (track.getId().equals(trackId)) {
1022                    // The service number of the caption service is used for track id of a
1023                    // subtitle track. Passes the following track id on to TsParser.
1024                    mSession.notifyTrackSelected(type, trackId);
1025                    mCaptionTrack = mCaptionTrackMap.get(numTrackId);
1026                    startCaptionTrack();
1027                    return;
1028                }
1029            }
1030        }
1031    }
1032
1033    private MpegTsPlayer createPlayer(AudioCapabilities capabilities, BufferManager bufferManager) {
1034        if (capabilities == null) {
1035            Log.w(TAG, "No Audio Capabilities");
1036        }
1037
1038        MpegTsPlayer player = new MpegTsPlayer(
1039                new MpegTsRendererBuilder(mContext, bufferManager, this),
1040                mHandler, mSourceManager, capabilities, this);
1041        Log.i(TAG, "Passthrough AC3 renderer");
1042        if (DEBUG) Log.d(TAG, "ExoPlayer created");
1043        return player;
1044    }
1045
1046    private void startCaptionTrack() {
1047        if (mCaptionEnabled && mCaptionTrack != null) {
1048            mSession.sendUiMessage(
1049                    TunerSession.MSG_UI_START_CAPTION_TRACK, mCaptionTrack);
1050            if (mPlayer != null) {
1051                mPlayer.setCaptionServiceNumber(mCaptionTrack.serviceNumber);
1052            }
1053        }
1054    }
1055
1056    private void stopCaptionTrack() {
1057        if (mPlayer != null) {
1058            mPlayer.setCaptionServiceNumber(Cea708Data.EMPTY_SERVICE_NUMBER);
1059        }
1060        mSession.sendUiMessage(TunerSession.MSG_UI_STOP_CAPTION_TRACK);
1061    }
1062
1063    private void resetTvTracks() {
1064        mTvTracks.clear();
1065        mAudioTrackMap.clear();
1066        mCaptionTrackMap.clear();
1067        mSession.sendUiMessage(TunerSession.MSG_UI_RESET_CAPTION_TRACK);
1068        mSession.notifyTracksChanged(mTvTracks);
1069    }
1070
1071    private void updateTvTracks(TvTracksInterface tvTracksInterface, boolean fromPmt) {
1072        if (DEBUG) {
1073            Log.d(TAG, "UpdateTvTracks " + tvTracksInterface);
1074        }
1075        List<AtscAudioTrack> audioTracks = tvTracksInterface.getAudioTracks();
1076        List<AtscCaptionTrack> captionTracks = tvTracksInterface.getCaptionTracks();
1077        // According to ATSC A/69 chapter 6.9, both PMT and EIT should have descriptors for audio
1078        // tracks, but in real world, we see some bogus audio track info in EIT, so, we trust audio
1079        // track info in PMT more and use info in EIT only when we have nothing.
1080        if (audioTracks != null && !audioTracks.isEmpty()
1081                && (mChannel.getAudioTracks() == null || fromPmt)) {
1082            updateAudioTracks(audioTracks);
1083        }
1084        if (captionTracks == null || captionTracks.isEmpty()) {
1085            if (tvTracksInterface.hasCaptionTrack()) {
1086                updateCaptionTracks(captionTracks);
1087            }
1088        } else {
1089            updateCaptionTracks(captionTracks);
1090        }
1091    }
1092
1093    private void removeTvTracks(int trackType) {
1094        Iterator<TvTrackInfo> iterator = mTvTracks.iterator();
1095        while (iterator.hasNext()) {
1096            TvTrackInfo tvTrackInfo = iterator.next();
1097            if (tvTrackInfo.getType() == trackType) {
1098                iterator.remove();
1099            }
1100        }
1101    }
1102
1103    private void updateVideoTrack(int width, int height) {
1104        removeTvTracks(TvTrackInfo.TYPE_VIDEO);
1105        mTvTracks.add(new TvTrackInfo.Builder(TvTrackInfo.TYPE_VIDEO, VIDEO_TRACK_ID)
1106                .setVideoWidth(width).setVideoHeight(height).build());
1107        mSession.notifyTracksChanged(mTvTracks);
1108        mSession.notifyTrackSelected(TvTrackInfo.TYPE_VIDEO, VIDEO_TRACK_ID);
1109    }
1110
1111    private void updateAudioTracks(List<AtscAudioTrack> audioTracks) {
1112        if (DEBUG) {
1113            Log.d(TAG, "Update AudioTracks " + audioTracks);
1114        }
1115        mAudioTrackMap.clear();
1116        if (audioTracks != null) {
1117            int index = 0;
1118            for (AtscAudioTrack audioTrack : audioTracks) {
1119                audioTrack.index = index;
1120                mAudioTrackMap.put(index, audioTrack);
1121                ++index;
1122            }
1123        }
1124        mHandler.sendEmptyMessage(MSG_NOTIFY_AUDIO_TRACK_UPDATED);
1125    }
1126
1127    private void notifyAudioTracksUpdated() {
1128        if (mPlayer == null) {
1129            // Audio tracks will be updated later once player initialization is done.
1130            return;
1131        }
1132        int audioTrackCount = mPlayer.getTrackCount(MpegTsPlayer.TRACK_TYPE_AUDIO);
1133        removeTvTracks(TvTrackInfo.TYPE_AUDIO);
1134        for (int i = 0; i < audioTrackCount; i++) {
1135            AtscAudioTrack audioTrack = mAudioTrackMap.get(i);
1136            if (audioTrack == null) {
1137                continue;
1138            }
1139            String language = audioTrack.language;
1140            if (language == null && mChannel.getAudioTracks() != null
1141                    && mChannel.getAudioTracks().size() == mAudioTrackMap.size()) {
1142                // If a language is not present, use a language field in PMT section parsed.
1143                language = mChannel.getAudioTracks().get(i).language;
1144            }
1145            // Save the index to the audio track.
1146            // Later, when an audio track is selected, both the audio pid and its audio stream
1147            // type reside in the selected index position of the tuner channel's audio data.
1148            audioTrack.index = i;
1149            TvTrackInfo.Builder builder = new TvTrackInfo.Builder(
1150                    TvTrackInfo.TYPE_AUDIO, AUDIO_TRACK_PREFIX + i);
1151            builder.setLanguage(language);
1152            builder.setAudioChannelCount(audioTrack.channelCount);
1153            builder.setAudioSampleRate(audioTrack.sampleRate);
1154            TvTrackInfo track = builder.build();
1155            mTvTracks.add(track);
1156        }
1157        mSession.notifyTracksChanged(mTvTracks);
1158    }
1159
1160    private void updateCaptionTracks(List<AtscCaptionTrack> captionTracks) {
1161        if (DEBUG) {
1162            Log.d(TAG, "Update CaptionTrack " + captionTracks);
1163        }
1164        removeTvTracks(TvTrackInfo.TYPE_SUBTITLE);
1165        mCaptionTrackMap.clear();
1166        if (captionTracks != null) {
1167            for (AtscCaptionTrack captionTrack : captionTracks) {
1168                if (mCaptionTrackMap.indexOfKey(captionTrack.serviceNumber) >= 0) {
1169                    continue;
1170                }
1171                String language = captionTrack.language;
1172
1173                // The service number of the caption service is used for track id of a subtitle.
1174                // Later, when a subtitle is chosen, track id will be passed on to TsParser.
1175                TvTrackInfo.Builder builder =
1176                        new TvTrackInfo.Builder(TvTrackInfo.TYPE_SUBTITLE,
1177                                SUBTITLE_TRACK_PREFIX + captionTrack.serviceNumber);
1178                builder.setLanguage(language);
1179                mTvTracks.add(builder.build());
1180                mCaptionTrackMap.put(captionTrack.serviceNumber, captionTrack);
1181            }
1182        }
1183        mSession.notifyTracksChanged(mTvTracks);
1184    }
1185
1186    private void updateChannelInfo(TunerChannel channel) {
1187        if (DEBUG) {
1188            Log.d(TAG, String.format("Channel Info (old) videoPid: %d audioPid: %d " +
1189                    "audioSize: %d", mChannel.getVideoPid(), mChannel.getAudioPid(),
1190                    mChannel.getAudioPids().size()));
1191        }
1192
1193        // The list of the audio tracks resided in a channel is often changed depending on a
1194        // program being on the air. So, we should update the streaming PIDs and types of the
1195        // tuned channel according to the newly received channel data.
1196        int oldVideoPid = mChannel.getVideoPid();
1197        int oldAudioPid = mChannel.getAudioPid();
1198        List<Integer> audioPids = channel.getAudioPids();
1199        List<Integer> audioStreamTypes = channel.getAudioStreamTypes();
1200        int size = audioPids.size();
1201        mChannel.setVideoPid(channel.getVideoPid());
1202        mChannel.setAudioPids(audioPids);
1203        mChannel.setAudioStreamTypes(audioStreamTypes);
1204        updateTvTracks(channel, true);
1205        int index = audioPids.isEmpty() ? -1 : 0;
1206        for (int i = 0; i < size; ++i) {
1207            if (audioPids.get(i) == oldAudioPid) {
1208                index = i;
1209                break;
1210            }
1211        }
1212        mChannel.selectAudioTrack(index);
1213        mSession.notifyTrackSelected(TvTrackInfo.TYPE_AUDIO,
1214                index == -1 ? null : AUDIO_TRACK_PREFIX + index);
1215
1216        // Reset playback if there is a change in the listening streaming PIDs.
1217        if (oldVideoPid != mChannel.getVideoPid()
1218                || oldAudioPid != mChannel.getAudioPid()) {
1219            // TODO: Implement a switching between tracks more smoothly.
1220            resetPlayback();
1221        }
1222        if (DEBUG) {
1223            Log.d(TAG, String.format("Channel Info (new) videoPid: %d audioPid: %d " +
1224                    " audioSize: %d", mChannel.getVideoPid(), mChannel.getAudioPid(),
1225                    mChannel.getAudioPids().size()));
1226        }
1227    }
1228
1229    private void stopPlayback() {
1230        mChannelDataManager.removeAllCallbacksAndMessages();
1231        if (mPlayer != null) {
1232            mPlayer.setPlayWhenReady(false);
1233            mPlayer.release();
1234            mPlayer = null;
1235            mPlayerState = ExoPlayer.STATE_IDLE;
1236            mPlaybackParams.setSpeed(1.0f);
1237            mPlayerStarted = false;
1238            mReportedDrawnToSurface = false;
1239            mPreparingStartTimeMs = INVALID_TIME;
1240            mBufferingStartTimeMs = INVALID_TIME;
1241            mReadyStartTimeMs = INVALID_TIME;
1242            mSession.sendUiMessage(TunerSession.MSG_UI_HIDE_AUDIO_UNPLAYABLE);
1243            mSession.notifyTimeShiftStatusChanged(TvInputManager.TIME_SHIFT_STATUS_UNAVAILABLE);
1244        }
1245    }
1246
1247    private void startPlayback(Object playerObj) {
1248        // TODO: provide hasAudio()/hasVideo() for play recordings.
1249        if (mPlayer == null || mPlayer != playerObj) {
1250            return;
1251        }
1252        if (mChannel != null && !mChannel.hasAudio()) {
1253            if (DEBUG) Log.d(TAG, "Channel " + mChannel + " does not have audio.");
1254            // Playbacks with video-only stream have not been tested yet.
1255            // No video-only channel has been found.
1256            notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN);
1257            return;
1258        }
1259        if (mChannel != null && ((mChannel.hasAudio() && !mPlayer.hasAudio())
1260                || (mChannel.hasVideo() && !mPlayer.hasVideo()))) {
1261            // Tracks haven't been detected in the extractor. Try again.
1262            sendMessage(MSG_RETRY_PLAYBACK, mPlayer);
1263            return;
1264        }
1265        // Since mSurface is volatile, we define a local variable surface to keep the same value
1266        // inside this method.
1267        Surface surface = mSurface;
1268        if (surface != null && !mPlayerStarted) {
1269            mPlayer.setSurface(surface);
1270            mPlayer.setPlayWhenReady(true);
1271            mPlayer.setVolume(mVolume);
1272            if (mChannel != null && !mChannel.hasVideo() && mChannel.hasAudio()) {
1273                notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY);
1274            } else if (!mReportedWeakSignal) {
1275                // Doesn't show buffering during weak signal.
1276                notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING);
1277            }
1278            mSession.sendUiMessage(TunerSession.MSG_UI_HIDE_MESSAGE);
1279            mPlayerStarted = true;
1280        }
1281    }
1282
1283    private void preparePlayback() {
1284        SoftPreconditions.checkState(mPlayer == null);
1285        if (mChannel == null && mRecordingId == null) {
1286            return;
1287        }
1288        mSourceManager.setKeepTuneStatus(true);
1289        BufferManager bufferManager = mChannel != null ? mBufferManager : new BufferManager(
1290                new DvrStorageManager(new File(getRecordingPath()), false));
1291        MpegTsPlayer player = createPlayer(mAudioCapabilities, bufferManager);
1292        player.setCaptionServiceNumber(Cea708Data.EMPTY_SERVICE_NUMBER);
1293        player.setVideoEventListener(this);
1294        player.setCaptionServiceNumber(mCaptionTrack != null ?
1295                mCaptionTrack.serviceNumber : Cea708Data.EMPTY_SERVICE_NUMBER);
1296        if (!player.prepare(mContext, mChannel, this)) {
1297            mSourceManager.setKeepTuneStatus(false);
1298            player.release();
1299            if (!mHandler.hasMessages(MSG_TUNE)) {
1300                // When prepare failed, there may be some errors related to hardware. In that
1301                // case, retry playback immediately may not help.
1302                notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL);
1303                mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RETRY_PLAYBACK, mPlayer),
1304                        PLAYBACK_RETRY_DELAY_MS);
1305            }
1306        } else {
1307            mPlayer = player;
1308            mPlayerStarted = false;
1309            mHandler.removeMessages(MSG_CHECK_SIGNAL);
1310            mHandler.sendEmptyMessageDelayed(MSG_CHECK_SIGNAL, CHECK_NO_SIGNAL_INITIAL_DELAY_MS);
1311        }
1312    }
1313
1314    private void resetPlayback() {
1315        long timestamp, oldTimestamp;
1316        timestamp = SystemClock.elapsedRealtime();
1317        stopPlayback();
1318        stopCaptionTrack();
1319        if (ENABLE_PROFILER) {
1320            oldTimestamp = timestamp;
1321            timestamp = SystemClock.elapsedRealtime();
1322            Log.i(TAG, "[Profiler] stopPlayback() takes " + (timestamp - oldTimestamp) + " ms");
1323        }
1324        if (mChannelBlocked || mSurface == null) {
1325            return;
1326        }
1327        preparePlayback();
1328    }
1329
1330    private void prepareTune(TunerChannel channel, String recording) {
1331        mChannelBlocked = false;
1332        mUnblockedContentRating = null;
1333        mRetryCount = 0;
1334        mChannel = channel;
1335        mRecordingId = recording;
1336        mRecordingDuration = recording != null ? getDurationForRecording(recording) : null;
1337        mProgram = null;
1338        mPrograms = null;
1339        mBufferStartTimeMs = mRecordStartTimeMs =
1340                (mRecordingId != null) ? 0 : System.currentTimeMillis();
1341        mLastPositionMs = 0;
1342        mCaptionTrack = null;
1343        mHandler.sendEmptyMessage(MSG_PARENTAL_CONTROLS);
1344    }
1345
1346    private void doReschedulePrograms() {
1347        long currentPositionMs = getCurrentPosition();
1348        long forwardDifference = Math.abs(currentPositionMs - mLastPositionMs
1349                - RESCHEDULE_PROGRAMS_INTERVAL_MS);
1350        mLastPositionMs = currentPositionMs;
1351
1352        // A gap is measured as the time difference between previous and next current position
1353        // periodically. If the gap has a significant difference with an interval of a period,
1354        // this means that there is a change of playback status and the programs of the current
1355        // channel should be rescheduled to new playback timeline.
1356        if (forwardDifference > RESCHEDULE_PROGRAMS_TOLERANCE_MS) {
1357            if (DEBUG) {
1358                Log.d(TAG, "reschedule programs size:"
1359                        + (mPrograms != null ? mPrograms.size() : 0) + " current program: "
1360                        + getCurrentProgram());
1361            }
1362            mHandler.obtainMessage(MSG_SCHEDULE_OF_PROGRAMS, new Pair<>(mChannel, mPrograms))
1363                    .sendToTarget();
1364        }
1365        mHandler.removeMessages(MSG_RESCHEDULE_PROGRAMS);
1366        mHandler.sendEmptyMessageDelayed(MSG_RESCHEDULE_PROGRAMS,
1367                RESCHEDULE_PROGRAMS_INTERVAL_MS);
1368    }
1369
1370    private int getTrickPlaySeekIntervalMs() {
1371        return Math.max(EXPECTED_KEY_FRAME_INTERVAL_MS / (int) Math.abs(mPlaybackParams.getSpeed()),
1372                MIN_TRICKPLAY_SEEK_INTERVAL_MS);
1373    }
1374
1375    private void doTrickplayBySeek(int seekPositionMs) {
1376        mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK);
1377        if (mPlaybackParams.getSpeed() == 1.0f || !mPlayer.isPrepared()) {
1378            return;
1379        }
1380        if (seekPositionMs < mBufferStartTimeMs - mRecordStartTimeMs) {
1381            if (mPlaybackParams.getSpeed() > 1.0f) {
1382                // If fast forwarding, the seekPositionMs can be out of the buffered range
1383                // because of chuck evictions.
1384                seekPositionMs = (int) (mBufferStartTimeMs - mRecordStartTimeMs);
1385            } else {
1386                mPlayer.seekTo(mBufferStartTimeMs - mRecordStartTimeMs);
1387                mPlaybackParams.setSpeed(1.0f);
1388                mPlayer.setAudioTrack(true);
1389                return;
1390            }
1391        } else if (seekPositionMs > System.currentTimeMillis() - mRecordStartTimeMs) {
1392            mPlayer.seekTo(System.currentTimeMillis() - mRecordStartTimeMs);
1393            mPlaybackParams.setSpeed(1.0f);
1394            mPlayer.setAudioTrack(true);
1395            return;
1396        }
1397
1398        long delayForNextSeek = getTrickPlaySeekIntervalMs();
1399        if (!mPlayer.isBuffering()) {
1400            mPlayer.seekTo(seekPositionMs);
1401        } else {
1402            delayForNextSeek = MIN_TRICKPLAY_SEEK_INTERVAL_MS;
1403        }
1404        seekPositionMs += mPlaybackParams.getSpeed() * delayForNextSeek;
1405        mHandler.sendMessageDelayed(mHandler.obtainMessage(
1406                MSG_TRICKPLAY_BY_SEEK, seekPositionMs, 0), delayForNextSeek);
1407    }
1408
1409    private void doTimeShiftPause() {
1410        mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR);
1411        mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK);
1412        if (!hasEnoughBackwardBuffer()) {
1413            return;
1414        }
1415        mPlaybackParams.setSpeed(1.0f);
1416        mPlayer.setPlayWhenReady(false);
1417        mPlayer.setAudioTrack(true);
1418    }
1419
1420    private void doTimeShiftResume() {
1421        mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR);
1422        mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK);
1423        mPlaybackParams.setSpeed(1.0f);
1424        mPlayer.setPlayWhenReady(true);
1425        mPlayer.setAudioTrack(true);
1426    }
1427
1428    private void doTimeShiftSeekTo(long timeMs) {
1429        mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR);
1430        mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK);
1431        mPlayer.seekTo((int) (timeMs - mRecordStartTimeMs));
1432    }
1433
1434    private void doTimeShiftSetPlaybackParams(PlaybackParams params) {
1435        if (!hasEnoughBackwardBuffer() && params.getSpeed() < 1.0f) {
1436            return;
1437        }
1438        mPlaybackParams = params;
1439        float speed = mPlaybackParams.getSpeed();
1440        if (speed == 1.0f) {
1441            mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR);
1442            mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK);
1443            doTimeShiftResume();
1444        } else if (mPlayer.supportSmoothTrickPlay(speed)) {
1445            mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK);
1446            mPlayer.setAudioTrack(false);
1447            mPlayer.startSmoothTrickplay(mPlaybackParams);
1448            mHandler.sendEmptyMessageDelayed(MSG_SMOOTH_TRICKPLAY_MONITOR,
1449                    TRICKPLAY_MONITOR_INTERVAL_MS);
1450        } else {
1451            mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR);
1452            if (!mHandler.hasMessages(MSG_TRICKPLAY_BY_SEEK)) {
1453                mPlayer.setAudioTrack(false);
1454                mPlayer.setPlayWhenReady(false);
1455                // Initiate trickplay
1456                mHandler.sendMessage(mHandler.obtainMessage(MSG_TRICKPLAY_BY_SEEK,
1457                        (int) (mPlayer.getCurrentPosition()
1458                                + speed * getTrickPlaySeekIntervalMs()), 0));
1459            }
1460        }
1461    }
1462
1463    private EitItem getCurrentProgram() {
1464        if (mPrograms == null || mPrograms.isEmpty()) {
1465            return null;
1466        }
1467        if (mChannel.getType() == Channel.TYPE_FILE) {
1468            // For the playback from the local file, we use the first one from the given program.
1469            EitItem first = mPrograms.get(0);
1470            if (first != null && (mProgram == null
1471                    || first.getStartTimeUtcMillis() < mProgram.getStartTimeUtcMillis())) {
1472                return first;
1473            }
1474            return null;
1475        }
1476        long currentTimeMs = getCurrentPosition();
1477        for (EitItem item : mPrograms) {
1478            if (item.getStartTimeUtcMillis() <= currentTimeMs
1479                    && item.getEndTimeUtcMillis() >= currentTimeMs) {
1480                return item;
1481            }
1482        }
1483        return null;
1484    }
1485
1486    private void doParentalControls() {
1487        boolean isParentalControlsEnabled = mTvInputManager.isParentalControlsEnabled();
1488        if (isParentalControlsEnabled) {
1489            TvContentRating blockContentRating = getContentRatingOfCurrentProgramBlocked();
1490            if (DEBUG) {
1491                if (blockContentRating != null) {
1492                    Log.d(TAG, "Check parental controls: blocked by content rating - "
1493                            + blockContentRating);
1494                } else {
1495                    Log.d(TAG, "Check parental controls: available");
1496                }
1497            }
1498            updateChannelBlockStatus(blockContentRating != null, blockContentRating);
1499        } else {
1500            if (DEBUG) {
1501                Log.d(TAG, "Check parental controls: available");
1502            }
1503            updateChannelBlockStatus(false, null);
1504        }
1505    }
1506
1507    private void doDiscoverCaptionServiceNumber(int serviceNumber) {
1508        int index = mCaptionTrackMap.indexOfKey(serviceNumber);
1509        if (index < 0) {
1510            AtscCaptionTrack captionTrack = new AtscCaptionTrack();
1511            captionTrack.serviceNumber = serviceNumber;
1512            captionTrack.wideAspectRatio = false;
1513            captionTrack.easyReader = false;
1514            mCaptionTrackMap.put(serviceNumber, captionTrack);
1515            mTvTracks.add(new TvTrackInfo.Builder(TvTrackInfo.TYPE_SUBTITLE,
1516                    SUBTITLE_TRACK_PREFIX + serviceNumber).build());
1517            mSession.notifyTracksChanged(mTvTracks);
1518        }
1519    }
1520
1521    private TvContentRating getContentRatingOfCurrentProgramBlocked() {
1522        EitItem currentProgram = getCurrentProgram();
1523        if (currentProgram == null) {
1524            return null;
1525        }
1526        TvContentRating[] ratings = mTvContentRatingCache
1527                .getRatings(currentProgram.getContentRating());
1528        if (ratings == null) {
1529            return null;
1530        }
1531        for (TvContentRating rating : ratings) {
1532            if (!Objects.equals(mUnblockedContentRating, rating) && mTvInputManager
1533                    .isRatingBlocked(rating)) {
1534                return rating;
1535            }
1536        }
1537        return null;
1538    }
1539
1540    private void updateChannelBlockStatus(boolean channelBlocked,
1541            TvContentRating contentRating) {
1542        if (mChannelBlocked == channelBlocked) {
1543            return;
1544        }
1545        mChannelBlocked = channelBlocked;
1546        if (mChannelBlocked) {
1547            mHandler.removeCallbacksAndMessages(null);
1548            stopPlayback();
1549            resetTvTracks();
1550            if (contentRating != null) {
1551                mSession.notifyContentBlocked(contentRating);
1552            }
1553            mHandler.sendEmptyMessageDelayed(MSG_PARENTAL_CONTROLS, PARENTAL_CONTROLS_INTERVAL_MS);
1554        } else {
1555            mHandler.removeCallbacksAndMessages(null);
1556            resetPlayback();
1557            mSession.notifyContentAllowed();
1558            mHandler.sendEmptyMessageDelayed(MSG_RESCHEDULE_PROGRAMS,
1559                    RESCHEDULE_PROGRAMS_INITIAL_DELAY_MS);
1560            mHandler.removeMessages(MSG_CHECK_SIGNAL);
1561            mHandler.sendEmptyMessageDelayed(MSG_CHECK_SIGNAL, CHECK_NO_SIGNAL_INITIAL_DELAY_MS);
1562        }
1563    }
1564
1565    private boolean hasEnoughBackwardBuffer() {
1566        return mPlayer.getCurrentPosition() + BUFFER_UNDERFLOW_BUFFER_MS
1567                >= mBufferStartTimeMs - mRecordStartTimeMs;
1568    }
1569
1570    private void notifyVideoUnavailable(final int reason) {
1571        mReportedWeakSignal = (reason == TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL);
1572        if (mSession != null) {
1573            mSession.notifyVideoUnavailable(reason);
1574        }
1575    }
1576
1577    private void notifyVideoAvailable() {
1578        mReportedWeakSignal = false;
1579        if (mSession != null) {
1580            mSession.notifyVideoAvailable();
1581        }
1582    }
1583}
1584