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