1816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko/*
2816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Copyright (C) 2015 The Android Open Source Project
3816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko *
4816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Licensed under the Apache License, Version 2.0 (the "License");
5816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * you may not use this file except in compliance with the License.
6816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * You may obtain a copy of the License at
7816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko *
8816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko *      http://www.apache.org/licenses/LICENSE-2.0
9816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko *
10816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Unless required by applicable law or agreed to in writing, software
11816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * distributed under the License is distributed on an "AS IS" BASIS,
12816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * See the License for the specific language governing permissions and
14816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * limitations under the License.
15816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */
16816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
17816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkopackage com.android.tv;
18816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
19816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.content.ContentResolver;
20816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.content.Context;
21816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.os.Handler;
22816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.os.Message;
23816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.support.annotation.IntDef;
24816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.support.annotation.NonNull;
25816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.support.annotation.Nullable;
26816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.support.annotation.VisibleForTesting;
27816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.util.Log;
28816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.util.Range;
29816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
3007b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalkoimport com.android.tv.analytics.Tracker;
3148dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport com.android.tv.common.SoftPreconditions;
3207b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalkoimport com.android.tv.common.WeakHandler;
3348dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport com.android.tv.common.recording.RecordedProgram;
34816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.data.Channel;
35816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.data.OnCurrentProgramUpdatedListener;
36816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.data.Program;
37816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.data.ProgramDataManager;
38816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.ui.TunableTvView;
39816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.ui.TunableTvView.TimeShiftListener;
40816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.util.AsyncDbTask;
41816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.util.Utils;
42816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
43816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.lang.annotation.Retention;
44816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.lang.annotation.RetentionPolicy;
45816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.ArrayList;
46816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Collections;
47816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Iterator;
48816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.LinkedList;
49816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.List;
50816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Objects;
51816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Queue;
52816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.concurrent.TimeUnit;
53816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
54816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko/**
55516b6a59b79ecf950b32f1286f4d4d288484088fNick Chalko * A class which manages the time shift feature in Live TV. It consists of two parts.
56816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * {@link PlayController} controls the playback such as play/pause, rewind and fast-forward using
57816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * {@link TunableTvView} which communicates with TvInputService through
58816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * {@link android.media.tv.TvInputService.Session}.
59816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * {@link ProgramManager} loads programs of the current channel in the background.
60816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */
61816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkopublic class TimeShiftManager {
62816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final String TAG = "TimeShiftManager";
63816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final boolean DEBUG = false;
64816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
65816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @Retention(RetentionPolicy.SOURCE)
66816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @IntDef({PLAY_STATUS_PAUSED, PLAY_STATUS_PLAYING})
67816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public @interface PlayStatus {}
68816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static final int PLAY_STATUS_PAUSED  = 0;
69816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static final int PLAY_STATUS_PLAYING = 1;
70816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
71816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @Retention(RetentionPolicy.SOURCE)
72816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @IntDef({PLAY_SPEED_1X, PLAY_SPEED_2X, PLAY_SPEED_3X, PLAY_SPEED_4X, PLAY_SPEED_5X})
73816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public @interface PlaySpeed{}
74816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static final int PLAY_SPEED_1X = 1;
75816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static final int PLAY_SPEED_2X = 2;
76816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static final int PLAY_SPEED_3X = 3;
77816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static final int PLAY_SPEED_4X = 4;
78816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static final int PLAY_SPEED_5X = 5;
79816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
80816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final int SHORT_PROGRAM_THRESHOLD_MILLIS = 46 * 60 * 1000;  // 46 mins.
81816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final int[] SHORT_PROGRAM_SPEED_FACTORS = new int[] {2, 4, 12, 48};
82816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final int[] LONG_PROGRAM_SPEED_FACTORS = new int[] {2, 8, 32, 128};
83816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
84816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @Retention(RetentionPolicy.SOURCE)
85816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @IntDef({PLAY_DIRECTION_FORWARD, PLAY_DIRECTION_BACKWARD})
86816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public @interface PlayDirection{}
87816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static final int PLAY_DIRECTION_FORWARD  = 0;
88816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static final int PLAY_DIRECTION_BACKWARD = 1;
89816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
90816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @Retention(RetentionPolicy.SOURCE)
91ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    @IntDef(flag = true, value = {TIME_SHIFT_ACTION_ID_PLAY, TIME_SHIFT_ACTION_ID_PAUSE,
92ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            TIME_SHIFT_ACTION_ID_REWIND, TIME_SHIFT_ACTION_ID_FAST_FORWARD,
93ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS, TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT})
94816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public @interface TimeShiftActionId{}
95816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static final int TIME_SHIFT_ACTION_ID_PLAY = 1;
96816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static final int TIME_SHIFT_ACTION_ID_PAUSE = 1 << 1;
97816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static final int TIME_SHIFT_ACTION_ID_REWIND = 1 << 2;
98816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static final int TIME_SHIFT_ACTION_ID_FAST_FORWARD = 1 << 3;
99816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static final int TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS = 1 << 4;
100816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static final int TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT = 1 << 5;
101816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
102816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final int MSG_GET_CURRENT_POSITION = 1000;
103816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final int MSG_PREFETCH_PROGRAM = 1001;
104816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final long REQUEST_CURRENT_POSITION_INTERVAL = TimeUnit.SECONDS.toMillis(1);
105816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final long MAX_DUMMY_PROGRAM_DURATION = TimeUnit.MINUTES.toMillis(30);
106816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @VisibleForTesting
107816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    static final long INVALID_TIME = -1;
108ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    static final long CURRENT_TIME = -2;
109816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final long PREFETCH_TIME_OFFSET_FROM_PROGRAM_END = TimeUnit.MINUTES.toMillis(1);
110816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final long PREFETCH_DURATION_FOR_NEXT = TimeUnit.HOURS.toMillis(2);
111816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
112816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @VisibleForTesting
113816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    static final long REQUEST_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(3);
114816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
115816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
116816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * If the user presses the {@link android.view.KeyEvent#KEYCODE_MEDIA_PREVIOUS} button within
117816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * this threshold from the program start time, the play position moves to the start of the
118816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * previous program.
119816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Otherwise, the play position moves to the start of the current program.
120816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * This value is specified in the UX document.
121816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
122816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final long PROGRAM_START_TIME_THRESHOLD = TimeUnit.SECONDS.toMillis(3);
123816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
124816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * If the current position enters within this range from the recording start time, rewind action
125816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * and jump to previous action is disabled.
126816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Similarly, if the current position enters within this range from the current system time,
127816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * fast forward action and jump to next action is disabled.
128816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * It must be three times longer than {@link #REQUEST_CURRENT_POSITION_INTERVAL} at least.
129816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
130816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final long DISABLE_ACTION_THRESHOLD = 3 * REQUEST_CURRENT_POSITION_INTERVAL;
131816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
132816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * If the current position goes out of this range from the recording start time, rewind action
133816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * and jump to previous action is enabled.
134816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Similarly, if the current position goes out of this range from the current system time,
135816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * fast forward action and jump to next action is enabled.
136816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Enable threshold and disable threshold must be different because the current position
137816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * does not have the continuous value. It changes every one second.
138816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
139816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final long ENABLE_ACTION_THRESHOLD =
140816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            DISABLE_ACTION_THRESHOLD + 3 * REQUEST_CURRENT_POSITION_INTERVAL;
141816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
142816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * The current position sent from TIS can not be exactly the same as the current system time
143516b6a59b79ecf950b32f1286f4d4d288484088fNick Chalko     * due to the elapsed time to pass the message from TIS to Live TV.
144816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * So the boundary threshold is necessary.
145816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * The same goes for the recording start time.
146816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * It must be three times longer than {@link #REQUEST_CURRENT_POSITION_INTERVAL} at least.
147816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
148816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final long RECORDING_BOUNDARY_THRESHOLD = 3 * REQUEST_CURRENT_POSITION_INTERVAL;
149816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
150816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private final PlayController mPlayController;
151816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private final ProgramManager mProgramManager;
15207b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko    private final Tracker mTracker;
153816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @VisibleForTesting
154816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    final CurrentPositionMediator mCurrentPositionMediator = new CurrentPositionMediator();
155816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
156816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private Listener mListener;
157816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private final OnCurrentProgramUpdatedListener mOnCurrentProgramUpdatedListener;
158816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private int mEnabledActionIds = TIME_SHIFT_ACTION_ID_PLAY | TIME_SHIFT_ACTION_ID_PAUSE
159816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            | TIME_SHIFT_ACTION_ID_REWIND | TIME_SHIFT_ACTION_ID_FAST_FORWARD
160816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            | TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS | TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT;
16107b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko    @TimeShiftActionId
162816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private int mLastActionId = 0;
163816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
164816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    // TODO: Remove these variables once API level 23 is available.
165816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private final Context mContext;
166816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
167816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private Program mCurrentProgram;
168816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    // This variable is used to block notification while changing the availability status.
169816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private boolean mNotificationEnabled;
170816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
17107b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko    private final Handler mHandler = new TimeShiftHandler(this);
172816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
173816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public TimeShiftManager(Context context, TunableTvView tvView,
17407b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko            ProgramDataManager programDataManager, Tracker tracker,
175816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            OnCurrentProgramUpdatedListener onCurrentProgramUpdatedListener) {
176816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mContext = context;
177816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mPlayController = new PlayController(tvView);
178816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mProgramManager = new ProgramManager(programDataManager);
17907b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko        mTracker = tracker;
180816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mOnCurrentProgramUpdatedListener = onCurrentProgramUpdatedListener;
181816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        tvView.setOnScreenBlockedListener(new TunableTvView.OnScreenBlockingChangedListener() {
182816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            @Override
183816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            public void onScreenBlockingChanged(boolean blocked) {
18448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                mPlayController.onAvailabilityChanged();
185816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
186816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        });
187816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
188816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
189816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
190816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Sets a listener which will receive events from this class.
191816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
192816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void setListener(Listener listener) {
193816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mListener = listener;
194816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
195816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
196816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
197816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Checks if the trick play is available for the current channel.
198816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
199816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public boolean isAvailable() {
20048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        return mPlayController.mAvailable;
201816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
202816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
203816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
204816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Returns the current time position in milliseconds.
205816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
206816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public long getCurrentPositionMs() {
207816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return mCurrentPositionMediator.mCurrentPositionMs;
208816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
209816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
210816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    void setCurrentPositionMs(long currentTimeMs) {
211816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mCurrentPositionMediator.onCurrentPositionChanged(currentTimeMs);
212816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
213816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
214816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
215816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Returns the start time of the recording in milliseconds.
216816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
217816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public long getRecordStartTimeMs() {
218816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        long oldestProgramStartTime = mProgramManager.getOldestProgramStartTime();
219816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return oldestProgramStartTime == INVALID_TIME ? INVALID_TIME
220816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                : mPlayController.mRecordStartTimeMs;
221816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
222816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
223816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
224ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko     * Returns the end time of the recording in milliseconds.
225ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko     */
226ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    public long getRecordEndTimeMs() {
227ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        if (mPlayController.mRecordEndTimeMs == CURRENT_TIME) {
228ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            return System.currentTimeMillis();
229ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        } else {
230ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            return mPlayController.mRecordEndTimeMs;
231ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        }
232ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    }
233ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko
234ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    /**
235816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Plays the media.
236816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     *
237816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * @throws IllegalStateException if the trick play is not available.
238816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
239816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void play() {
240816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (!isActionEnabled(TIME_SHIFT_ACTION_ID_PLAY)) {
241816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return;
242816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
24307b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko        mTracker.sendTimeShiftAction(TIME_SHIFT_ACTION_ID_PLAY);
244816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mLastActionId = TIME_SHIFT_ACTION_ID_PLAY;
245816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mPlayController.play();
246816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        updateActions();
247816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
248816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
249816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
250816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Pauses the playback.
251816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     *
252816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * @throws IllegalStateException if the trick play is not available.
253816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
254816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void pause() {
255816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (!isActionEnabled(TIME_SHIFT_ACTION_ID_PAUSE)) {
256816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return;
257816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
258816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mLastActionId = TIME_SHIFT_ACTION_ID_PAUSE;
25907b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko        mTracker.sendTimeShiftAction(mLastActionId);
260816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mPlayController.pause();
261816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        updateActions();
262816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
263816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
264816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
265816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Toggles the playing and paused state.
266816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     *
267816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * @throws IllegalStateException if the trick play is not available.
268816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
269816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void togglePlayPause() {
270816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mPlayController.togglePlayPause();
271816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
272816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
273816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
274816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Plays the media in backward direction. The playback speed is increased by 1x each time
275816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * this is called. The range of the speed is from 2x to 5x.
276816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * If the playing position is considered the same as the record start time, it does nothing
277816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     *
278816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * @throws IllegalStateException if the trick play is not available.
279816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
280816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void rewind() {
281816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (!isActionEnabled(TIME_SHIFT_ACTION_ID_REWIND)) {
282816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return;
283816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
284816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mLastActionId = TIME_SHIFT_ACTION_ID_REWIND;
28507b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko        mTracker.sendTimeShiftAction(mLastActionId);
286816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mPlayController.rewind();
287816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        updateActions();
288816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
289816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
290816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
291816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Plays the media in forward direction. The playback speed is increased by 1x each time
292816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * this is called. The range of the speed is from 2x to 5x.
293816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * If the playing position is the same as the current time, it does nothing.
294816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     *
295816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * @throws IllegalStateException if the trick play is not available.
296816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
297816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void fastForward() {
298816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (!isActionEnabled(TIME_SHIFT_ACTION_ID_FAST_FORWARD)) {
299816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return;
300816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
301816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mLastActionId = TIME_SHIFT_ACTION_ID_FAST_FORWARD;
30207b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko        mTracker.sendTimeShiftAction(mLastActionId);
303816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mPlayController.fastForward();
304816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        updateActions();
305816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
306816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
307816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
308816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Jumps to the start of the current program.
309816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * If the currently playing position is within 3 seconds
310816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * (={@link #PROGRAM_START_TIME_THRESHOLD})from the start time of the program, it goes to
311816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * the start of the previous program if exists.
312816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * If the playing position is the same as the record start time, it does nothing.
313816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     *
314816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * @throws IllegalStateException if the trick play is not available.
315816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
316816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void jumpToPrevious() {
317816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (!isActionEnabled(TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS)) {
318816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return;
319816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
320816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        Program program = mProgramManager.getProgramAt(
321816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mCurrentPositionMediator.mCurrentPositionMs - PROGRAM_START_TIME_THRESHOLD);
322816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (program == null) {
323816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return;
324816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
325816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        long seekPosition =
326816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                Math.max(program.getStartTimeUtcMillis(), mPlayController.mRecordStartTimeMs);
327816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mLastActionId = TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS;
32807b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko        mTracker.sendTimeShiftAction(mLastActionId);
329816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mPlayController.seekTo(seekPosition);
330816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mCurrentPositionMediator.onSeekRequested(seekPosition);
331816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        updateActions();
332816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
333816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
334816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
335816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Jumps to the start of the next program if exists.
336816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * If there's no next program, it jumps to the current system time and shows the live TV.
337816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * If the playing position is considered the same as the current time, it does nothing.
338816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     *
339816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * @throws IllegalStateException if the trick play is not available.
340816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
341816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void jumpToNext() {
342816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (!isActionEnabled(TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT)) {
343816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return;
344816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
345816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        Program currentProgram = mProgramManager.getProgramAt(
346816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mCurrentPositionMediator.mCurrentPositionMs);
347816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (currentProgram == null) {
348816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return;
349816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
350816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        Program nextProgram = mProgramManager.getProgramAt(currentProgram.getEndTimeUtcMillis());
351816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        long currentTimeMs = System.currentTimeMillis();
352816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mLastActionId = TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT;
35307b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko        mTracker.sendTimeShiftAction(mLastActionId);
354816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (nextProgram == null || nextProgram.getStartTimeUtcMillis() > currentTimeMs) {
355816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mPlayController.seekTo(currentTimeMs);
356816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mPlayController.isForwarding()) {
357816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                // The current position will be the current system time from now.
358816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mPlayController.mIsPlayOffsetChanged = false;
359816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mCurrentPositionMediator.initialize(currentTimeMs);
360816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            } else {
361816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                // The current position would not be the current system time.
362816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                // So need to wait for the correct time from TIS.
363816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mCurrentPositionMediator.onSeekRequested(currentTimeMs);
364816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
365816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        } else {
366816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mPlayController.seekTo(nextProgram.getStartTimeUtcMillis());
367816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mCurrentPositionMediator.onSeekRequested(nextProgram.getStartTimeUtcMillis());
368816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
369816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        updateActions();
370816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
371816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
372816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
373816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Returns the playback status. The value is PLAY_STATUS_PAUSED or PLAY_STATUS_PLAYING.
374816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
375816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @PlayStatus public int getPlayStatus() {
376816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return mPlayController.mPlayStatus;
377816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
378816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
379816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
380816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Returns the displayed playback speed. The value is one of PLAY_SPEED_1X, PLAY_SPEED_2X,
381816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * PLAY_SPEED_3X, PLAY_SPEED_4X and PLAY_SPEED_5X.
382816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
383816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @PlaySpeed public int getDisplayedPlaySpeed() {
384816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return mPlayController.mDisplayedPlaySpeed;
385816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
386816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
387816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
388816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Returns the playback speed. The value is PLAY_DIRECTION_FORWARD or PLAY_DIRECTION_BACKWARD.
389816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
390816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @PlayDirection public int getPlayDirection() {
391816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return mPlayController.mPlayDirection;
392816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
393816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
394816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
395816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Returns the ID of the last action..
396816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
397816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @TimeShiftActionId public int getLastActionId() {
398816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return mLastActionId;
399816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
400816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
401816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
402816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Enables or disables the time-shift actions.
403816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
404816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @VisibleForTesting
405816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    void enableAction(@TimeShiftActionId int actionId, boolean enable) {
406816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        int oldEnabledActionIds = mEnabledActionIds;
407816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (enable) {
408816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mEnabledActionIds |= actionId;
409816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        } else {
410816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mEnabledActionIds &= ~actionId;
411816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
412816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (mNotificationEnabled && mListener != null
413816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                && oldEnabledActionIds != mEnabledActionIds) {
414816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mListener.onActionEnabledChanged(actionId, enable);
415816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
416816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
417816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
418816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public boolean isActionEnabled(@TimeShiftActionId int actionId) {
419816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return (mEnabledActionIds & actionId) == actionId;
420816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
421816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
422816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private void updateActions() {
423816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (isAvailable()) {
424816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            enableAction(TIME_SHIFT_ACTION_ID_PLAY, true);
425816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            enableAction(TIME_SHIFT_ACTION_ID_PAUSE, true);
426816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            // Rewind action and jump to previous action.
427816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            long threshold = isActionEnabled(TIME_SHIFT_ACTION_ID_REWIND)
428816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    ? DISABLE_ACTION_THRESHOLD : ENABLE_ACTION_THRESHOLD;
429816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            boolean enabled = mCurrentPositionMediator.mCurrentPositionMs
430816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    - mPlayController.mRecordStartTimeMs > threshold;
431816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            enableAction(TIME_SHIFT_ACTION_ID_REWIND, enabled);
432816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            enableAction(TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS, enabled);
433816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            // Fast forward action and jump to next action
434816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            threshold = isActionEnabled(TIME_SHIFT_ACTION_ID_FAST_FORWARD)
435816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    ? DISABLE_ACTION_THRESHOLD : ENABLE_ACTION_THRESHOLD;
436ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            enabled = getRecordEndTimeMs() - mCurrentPositionMediator.mCurrentPositionMs
437816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    > threshold;
438816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            enableAction(TIME_SHIFT_ACTION_ID_FAST_FORWARD, enabled);
439816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            enableAction(TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT, enabled);
440816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        } else {
441816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            enableAction(TIME_SHIFT_ACTION_ID_PLAY, false);
442816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            enableAction(TIME_SHIFT_ACTION_ID_PAUSE, false);
443816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            enableAction(TIME_SHIFT_ACTION_ID_REWIND, false);
444816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            enableAction(TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS, false);
445816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            enableAction(TIME_SHIFT_ACTION_ID_FAST_FORWARD, false);
446816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            enableAction(TIME_SHIFT_ACTION_ID_PLAY, false);
447816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
448816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
449816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
450816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private void updateCurrentProgram() {
451816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        Program currentProgram = getProgramAt(mCurrentPositionMediator.mCurrentPositionMs);
452816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (!Program.isValid(currentProgram)) {
453816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            currentProgram = null;
454816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
455816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (!Objects.equals(mCurrentProgram, currentProgram)) {
456816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (DEBUG) Log.d(TAG, "Current program has been updated. " + currentProgram);
457816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mCurrentProgram = currentProgram;
458816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mNotificationEnabled && mOnCurrentProgramUpdatedListener != null) {
459816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                Channel channel = mPlayController.getCurrentChannel();
460816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (channel != null) {
461816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mOnCurrentProgramUpdatedListener.onCurrentProgramUpdated(channel.getId(),
462816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            mCurrentProgram);
463816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mPlayController.onCurrentProgramChanged();
464816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
465816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
466816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
467816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
468816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
469816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
47048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho     * Checks whether the TV is playing the recorded content.
47148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho     */
47248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    public boolean isRecordingPlayback() {
47348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        return mPlayController.mRecordingPlayback;
47448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
47548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
47648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    /**
477816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Returns {@code true} if the trick play is available and it's playing to the forward direction
478816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * with normal speed, otherwise {@code false}.
479816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
480816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public boolean isNormalPlaying() {
48148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        return mPlayController.mAvailable
482816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                && mPlayController.mPlayStatus == PLAY_STATUS_PLAYING
483816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                && mPlayController.mPlayDirection == PLAY_DIRECTION_FORWARD
484816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                && mPlayController.mDisplayedPlaySpeed == PLAY_SPEED_1X;
485816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
486816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
487816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
488816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Checks if the trick play is available and it's playback status is paused.
489816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
490816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public boolean isPaused() {
49148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        return mPlayController.mAvailable && mPlayController.mPlayStatus == PLAY_STATUS_PAUSED;
492816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
493816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
494816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
495816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Returns the program which airs at the given time.
496816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
497816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @NonNull
498816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public Program getProgramAt(long timeMs) {
499816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        Program program = mProgramManager.getProgramAt(timeMs);
500816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (program == null) {
501816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            // Guard just in case when the program prefetch handler doesn't work on time.
502816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mProgramManager.addDummyProgramsAt(timeMs);
503816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            program = mProgramManager.getProgramAt(timeMs);
504816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
505816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return program;
506816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
507816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
508816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    void onAvailabilityChanged() {
50948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mProgramManager.onAvailabilityChanged(mPlayController.mAvailable,
51048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                mPlayController.mRecordingPlayback ? null : mPlayController.getCurrentChannel(),
51148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                mPlayController.mRecordStartTimeMs);
512816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        updateActions();
513816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        // Availability change notification should be always sent
514816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        // even if mNotificationEnabled is false.
515816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (mListener != null) {
516816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mListener.onAvailabilityChanged();
517816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
518816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
519816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
520ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    void onRecordTimeRangeChanged() {
52148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (mPlayController.mAvailable) {
522ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            mProgramManager.onRecordTimeRangeChanged(mPlayController.mRecordStartTimeMs,
523ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                    mPlayController.mRecordEndTimeMs);
524816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
525816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        updateActions();
526816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (mNotificationEnabled && mListener != null) {
527ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            mListener.onRecordTimeRangeChanged();
528816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
529816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
530816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
531816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    void onCurrentPositionChanged() {
532816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        updateActions();
533816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        updateCurrentProgram();
534816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (mNotificationEnabled && mListener != null) {
535816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mListener.onCurrentPositionChanged();
536816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
537816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
538816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
539816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    void onPlayStatusChanged(@PlayStatus int status) {
540816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (mNotificationEnabled && mListener != null) {
541816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mListener.onPlayStatusChanged(status);
542816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
543816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
544816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
545816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    void onProgramInfoChanged() {
546816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        updateCurrentProgram();
547816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (mNotificationEnabled && mListener != null) {
548816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mListener.onProgramInfoChanged();
549816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
550816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
551816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
552816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
553816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Returns the current program which airs right now.<p>
554816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     *
555816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * If the program is a dummy program, which means there's no program information,
556816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * returns {@code null}.
557816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
558816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @Nullable
559816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public Program getCurrentProgram() {
560816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (isAvailable()) {
561816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return mCurrentProgram;
562816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
563816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return null;
564816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
565816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
566816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private int getPlaybackSpeed() {
567816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        int[] playbackSpeedList;
568816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (getCurrentProgram() == null || getCurrentProgram().getEndTimeUtcMillis()
569816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                - getCurrentProgram().getStartTimeUtcMillis() > SHORT_PROGRAM_THRESHOLD_MILLIS) {
570816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            playbackSpeedList = LONG_PROGRAM_SPEED_FACTORS;
571816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        } else {
572816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            playbackSpeedList = SHORT_PROGRAM_SPEED_FACTORS;
573816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
574816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        switch (mPlayController.mDisplayedPlaySpeed) {
575816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            case PLAY_SPEED_1X:
576816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return 1;
577816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            case PLAY_SPEED_2X:
578816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return playbackSpeedList[0];
579816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            case PLAY_SPEED_3X:
580816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return playbackSpeedList[1];
581816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            case PLAY_SPEED_4X:
582816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return playbackSpeedList[2];
583816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            case PLAY_SPEED_5X:
584816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return playbackSpeedList[3];
585816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            default:
586816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                Log.w(TAG, "Unknown displayed play speed is chosen : "
587816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        + mPlayController.mDisplayedPlaySpeed);
588816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return 1;
589816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
590816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
591816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
592816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
593816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * A class which controls the trick play.
594816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
595816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private class PlayController {
596816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private final TunableTvView mTvView;
597816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
598816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private long mRecordStartTimeMs;
599ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        private long mRecordEndTimeMs;
600816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
601816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        @PlayStatus private int mPlayStatus = PLAY_STATUS_PAUSED;
602816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        @PlaySpeed private int mDisplayedPlaySpeed = PLAY_SPEED_1X;
603816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        @PlayDirection private int mPlayDirection = PLAY_DIRECTION_FORWARD;
604816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private int mPlaybackSpeed;
60548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        private boolean mAvailable;
60648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        private boolean mRecordingPlayback;
607816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
608816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        /**
609816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * Indicates that the trick play is not playing the current time position.
610816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * It is set true when {@link PlayController#pause}, {@link PlayController#rewind},
611816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * {@link PlayController#fastForward} and {@link PlayController#seekTo}
612816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * is called.
613816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * If it is true, the current time is equal to System.currentTimeMillis().
614816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         */
615816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private boolean mIsPlayOffsetChanged;
616816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
617816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        PlayController(TunableTvView tvView) {
618816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mTvView = tvView;
619816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mTvView.setTimeShiftListener(new TimeShiftListener() {
620816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                @Override
621816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                public void onAvailabilityChanged() {
62248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                    PlayController.this.onAvailabilityChanged();
623816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
624816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
625816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                @Override
626816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                public void onRecordStartTimeChanged(long recordStartTimeMs) {
627816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (mRecordStartTimeMs == recordStartTimeMs) {
628816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        return;
629816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
630816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mRecordStartTimeMs = recordStartTimeMs;
631ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                    TimeShiftManager.this.onRecordTimeRangeChanged();
632816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
633816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // According to the UX guidelines, the stream should be resumed if the
634816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // recording buffer fills up while paused, which means that the current time
635816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // position is the same as or before the recording start time.
636816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // But, for this application and the TIS, it's an erroneous and confusing
637816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // situation if the current time position is before the recording start time.
638816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // So, we recommend the TIS to keep the current time position greater than or
639816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // equal to the recording start time.
640816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // And here, we assume that the buffer is full if the current time position
641816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // is nearly equal to the recording start time.
642816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (mPlayStatus == PLAY_STATUS_PAUSED &&
643816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            getCurrentPositionMs() - mRecordStartTimeMs
644816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            < RECORDING_BOUNDARY_THRESHOLD) {
645816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        TimeShiftManager.this.play();
646816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
647816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
648816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            });
649816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
650816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
65148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        void onAvailabilityChanged() {
65248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            boolean newAvailable = mTvView.isTimeShiftAvailable() && !mTvView.isScreenBlocked();
65348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            if (mAvailable == newAvailable) {
65448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                return;
65548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            }
65648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mAvailable = newAvailable;
65748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            // Do not send the notifications while the availability is changing,
65848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            // because the variables are in the intermediate state.
65948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            // For example, the current program can be null.
66048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mNotificationEnabled = false;
66148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mDisplayedPlaySpeed = PLAY_SPEED_1X;
66248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mPlaybackSpeed = 1;
66348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mPlayDirection = PLAY_DIRECTION_FORWARD;
66448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mRecordingPlayback = mTvView.isRecordingPlayback();
66548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            if (mRecordingPlayback) {
66648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                RecordedProgram recordedProgram = mTvView.getPlayingRecordedProgram();
66748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                SoftPreconditions.checkNotNull(recordedProgram);
66848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                mIsPlayOffsetChanged = true;
66948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                mRecordStartTimeMs = 0;
67048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                mRecordEndTimeMs = recordedProgram.getDurationMillis();
67148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            } else {
67248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                mIsPlayOffsetChanged = false;
67348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                mRecordStartTimeMs = System.currentTimeMillis();
67448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                mRecordEndTimeMs = CURRENT_TIME;
67548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            }
67648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mCurrentPositionMediator.initialize(mRecordStartTimeMs);
67748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mHandler.removeMessages(MSG_GET_CURRENT_POSITION);
67848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
67948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            if (mAvailable) {
68048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                // When the media availability message has come.
68148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                mPlayController.setPlayStatus(PLAY_STATUS_PLAYING);
68248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                mHandler.sendEmptyMessageDelayed(MSG_GET_CURRENT_POSITION,
68348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                        REQUEST_CURRENT_POSITION_INTERVAL);
68448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            } else {
68548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                // When the tune command is sent.
68648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                mPlayController.setPlayStatus(PLAY_STATUS_PAUSED);
68748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            }
68848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            TimeShiftManager.this.onAvailabilityChanged();
68948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mNotificationEnabled = true;
690816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
691816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
692816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void handleGetCurrentPosition() {
693816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mIsPlayOffsetChanged) {
694ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                long currentTimeMs = mRecordEndTimeMs == CURRENT_TIME ? System.currentTimeMillis()
695ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                        : mRecordEndTimeMs;
696816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                long currentPositionMs = Math.max(
697816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        Math.min(mTvView.timeshiftGetCurrentPositionMs(), currentTimeMs),
698816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        mRecordStartTimeMs);
699816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                boolean isCurrentTime =
700816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        currentTimeMs - currentPositionMs < RECORDING_BOUNDARY_THRESHOLD;
701816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                long newCurrentPositionMs;
702816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (isCurrentTime && isForwarding()) {
703816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // It's playing forward and the current playing position reached
704816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // the current system time. i.e. The live stream is played.
705816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // Therefore no need to call TvView.timeshiftGetCurrentPositionMs
706816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // any more.
707816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    newCurrentPositionMs = currentTimeMs;
708816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mIsPlayOffsetChanged = false;
709816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (mDisplayedPlaySpeed > PLAY_SPEED_1X) {
710816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        TimeShiftManager.this.play();
711816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
712816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                } else {
713816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    newCurrentPositionMs = currentPositionMs;
714816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    boolean isRecordStartTime = currentPositionMs - mRecordStartTimeMs
715816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            < RECORDING_BOUNDARY_THRESHOLD;
716816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (isRecordStartTime && isRewinding()) {
717816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        TimeShiftManager.this.play();
718816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
719816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
720816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                setCurrentPositionMs(newCurrentPositionMs);
721816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            } else {
722816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                setCurrentPositionMs(System.currentTimeMillis());
723816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                TimeShiftManager.this.onCurrentPositionChanged();
724816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
725816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            // Need to send message here just in case there is no or invalid response
726816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            // for the current time position request from TIS.
727816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mHandler.sendEmptyMessageDelayed(MSG_GET_CURRENT_POSITION,
728816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    REQUEST_CURRENT_POSITION_INTERVAL);
729816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
730816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
731816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void play() {
732816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mDisplayedPlaySpeed = PLAY_SPEED_1X;
733816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mPlaybackSpeed = 1;
734816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mPlayDirection = PLAY_DIRECTION_FORWARD;
735816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mTvView.timeshiftPlay();
736816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            setPlayStatus(PLAY_STATUS_PLAYING);
737816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
738816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
739816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void pause() {
740816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mDisplayedPlaySpeed = PLAY_SPEED_1X;
741816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mPlaybackSpeed = 1;
742816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mTvView.timeshiftPause();
743816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            setPlayStatus(PLAY_STATUS_PAUSED);
744816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mIsPlayOffsetChanged = true;
745816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
746816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
747816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void togglePlayPause() {
748816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mPlayStatus == PLAY_STATUS_PAUSED) {
749816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                play();
75007b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko                mTracker.sendTimeShiftAction(TIME_SHIFT_ACTION_ID_PLAY);
751816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            } else {
752816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                pause();
75307b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko                mTracker.sendTimeShiftAction(TIME_SHIFT_ACTION_ID_PAUSE);
754816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
755816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
756816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
757816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void rewind() {
758816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mPlayDirection == PLAY_DIRECTION_BACKWARD) {
759816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                increaseDisplayedPlaySpeed();
760816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            } else {
761816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mDisplayedPlaySpeed = PLAY_SPEED_2X;
762816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
763816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mPlayDirection = PLAY_DIRECTION_BACKWARD;
764816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mPlaybackSpeed = getPlaybackSpeed();
765816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mTvView.timeshiftRewind(mPlaybackSpeed);
766816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            setPlayStatus(PLAY_STATUS_PLAYING);
767816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mIsPlayOffsetChanged = true;
768816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
769816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
770816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void fastForward() {
771816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mPlayDirection == PLAY_DIRECTION_FORWARD) {
772816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                increaseDisplayedPlaySpeed();
773816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            } else {
774816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mDisplayedPlaySpeed = PLAY_SPEED_2X;
775816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
776816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mPlayDirection = PLAY_DIRECTION_FORWARD;
777816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mPlaybackSpeed = getPlaybackSpeed();
778816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mTvView.timeshiftFastForward(mPlaybackSpeed);
779816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            setPlayStatus(PLAY_STATUS_PLAYING);
780816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mIsPlayOffsetChanged = true;
781816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
782816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
783816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        /**
784816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * Moves to the specified time.
785816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         */
786816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void seekTo(long timeMs) {
787ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            mTvView.timeshiftSeekTo(Math.min(mRecordEndTimeMs == CURRENT_TIME
788ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                    ? System.currentTimeMillis() : mRecordEndTimeMs,
789ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                            Math.max(mRecordStartTimeMs, timeMs)));
790816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mIsPlayOffsetChanged = true;
791816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
792816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
793816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void onCurrentProgramChanged() {
794816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            // Update playback speed
795816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mDisplayedPlaySpeed == PLAY_SPEED_1X) {
796816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return;
797816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
798816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            int playbackSpeed = getPlaybackSpeed();
799816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (playbackSpeed != mPlaybackSpeed) {
800816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mPlaybackSpeed = playbackSpeed;
801816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (mPlayDirection == PLAY_DIRECTION_FORWARD) {
802816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mTvView.timeshiftFastForward(mPlaybackSpeed);
803816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                } else {
804816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mTvView.timeshiftRewind(mPlaybackSpeed);
805816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
806816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
807816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
808816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
809816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private void increaseDisplayedPlaySpeed() {
810816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            switch (mDisplayedPlaySpeed) {
811816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                case PLAY_SPEED_1X:
812816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mDisplayedPlaySpeed = PLAY_SPEED_2X;
813816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    break;
814816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                case PLAY_SPEED_2X:
815816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mDisplayedPlaySpeed = PLAY_SPEED_3X;
816816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    break;
817816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                case PLAY_SPEED_3X:
818816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mDisplayedPlaySpeed = PLAY_SPEED_4X;
819816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    break;
820816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                case PLAY_SPEED_4X:
821816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mDisplayedPlaySpeed = PLAY_SPEED_5X;
822816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    break;
823816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
824816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
825816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
826816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private void setPlayStatus(@PlayStatus int status) {
827816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mPlayStatus = status;
828816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            TimeShiftManager.this.onPlayStatusChanged(status);
829816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
830816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
831816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        boolean isForwarding() {
832816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return mPlayStatus == PLAY_STATUS_PLAYING && mPlayDirection == PLAY_DIRECTION_FORWARD;
833816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
834816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
835816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private boolean isRewinding() {
836816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return mPlayStatus == PLAY_STATUS_PLAYING && mPlayDirection == PLAY_DIRECTION_BACKWARD;
837816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
838816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
839816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        Channel getCurrentChannel() {
840816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return mTvView.getCurrentChannel();
841816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
842816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
843816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
844816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private class ProgramManager {
845816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private final ProgramDataManager mProgramDataManager;
846816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private Channel mChannel;
847816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private final List<Program> mPrograms = new ArrayList<>();
848816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private final Queue<Range<Long>> mProgramLoadQueue = new LinkedList<>();
849816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private LoadProgramsForCurrentChannelTask mProgramLoadTask = null;
85048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        private int mEmptyFetchCount = 0;
851816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
852816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        ProgramManager(ProgramDataManager programDataManager) {
853816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mProgramDataManager = programDataManager;
854816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
855816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
856816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void onAvailabilityChanged(boolean available, Channel channel, long currentPositionMs) {
85748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            if (DEBUG) {
85848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                Log.d(TAG, "onAvailabilityChanged(" + available + "+," + channel + ", "
85948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                        + currentPositionMs + ")");
86048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            }
86148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
862816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mProgramLoadQueue.clear();
863816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mProgramLoadTask != null) {
864816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mProgramLoadTask.cancel(true);
865816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
866816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mHandler.removeMessages(MSG_PREFETCH_PROGRAM);
867816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mPrograms.clear();
86848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mEmptyFetchCount = 0;
869816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mChannel = channel;
870816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (channel == null || channel.isPassthrough()) {
871816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return;
872816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
873816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (available) {
874816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                Program program = mProgramDataManager.getCurrentProgram(channel.getId());
875816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                long prefetchStartTimeMs;
876816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (program != null) {
877816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mPrograms.add(program);
878816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    prefetchStartTimeMs = program.getEndTimeUtcMillis();
879816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                } else {
880816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    prefetchStartTimeMs = Utils.floorTime(currentPositionMs,
881816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            MAX_DUMMY_PROGRAM_DURATION);
882816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
883816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                // Create dummy program
884816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mPrograms.addAll(createDummyPrograms(prefetchStartTimeMs,
885816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        currentPositionMs + PREFETCH_DURATION_FOR_NEXT));
886816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                schedulePrefetchPrograms();
887816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                TimeShiftManager.this.onProgramInfoChanged();
888816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
889816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
890816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
891ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        void onRecordTimeRangeChanged(long startTimeMs, long endTimeMs) {
892816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mChannel == null || mChannel.isPassthrough()) {
893816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return;
894816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
895ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            if (endTimeMs == CURRENT_TIME) {
896ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                endTimeMs = System.currentTimeMillis();
897ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            }
898816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
899816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            long fetchStartTimeMs = Utils.floorTime(startTimeMs, MAX_DUMMY_PROGRAM_DURATION);
900816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            boolean needToLoad = addDummyPrograms(fetchStartTimeMs,
901ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                    endTimeMs + PREFETCH_DURATION_FOR_NEXT);
902816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (needToLoad) {
903ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                Range<Long> period = Range.create(fetchStartTimeMs, endTimeMs);
904816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mProgramLoadQueue.add(period);
905816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                startTaskIfNeeded();
906816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
907816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
908816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
909816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private void startTaskIfNeeded() {
910816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mProgramLoadQueue.isEmpty()) {
911816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return;
912816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
913816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mProgramLoadTask == null || mProgramLoadTask.isCancelled()) {
914816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                startNext();
915816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            } else {
916816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                switch (mProgramLoadTask.getStatus()) {
917816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    case PENDING:
918816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        if (mProgramLoadTask.overlaps(mProgramLoadQueue)) {
919816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            if (mProgramLoadTask.cancel(true)) {
920816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                                mProgramLoadQueue.add(mProgramLoadTask.getPeriod());
921816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                                mProgramLoadTask = null;
922816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                                startNext();
923816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            }
924816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        }
925816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        break;
926816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    case RUNNING:
927816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        // Remove pending task fully satisfied by the current
928816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        Range<Long> current = mProgramLoadTask.getPeriod();
929816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        Iterator<Range<Long>> i = mProgramLoadQueue.iterator();
930816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        while (i.hasNext()) {
931816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            Range<Long> r = i.next();
932816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            if (current.contains(r)) {
933816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                                i.remove();
934816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            }
935816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        }
936816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        break;
937816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    case FINISHED:
938816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        // The task should have already cleared it self, clear and restart anyways.
939816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        Log.w(TAG, mProgramLoadTask + " is finished, but was not cleared");
940816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        startNext();
941816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        break;
942816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
943816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
944816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
945816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
946816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private void startNext() {
947816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mProgramLoadTask = null;
948816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mProgramLoadQueue.isEmpty()) {
949816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return;
950816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
951816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
952816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            Range<Long> next = mProgramLoadQueue.poll();
953816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            // Extend next to include any overlapping Ranges.
954816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            Iterator<Range<Long>> i = mProgramLoadQueue.iterator();
955816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            while(i.hasNext()) {
956816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                Range<Long> r = i.next();
957816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if(next.contains(r.getLower()) || next.contains(r.getUpper())){
958816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    i.remove();
959816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    next = next.extend(r);
960816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
961816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
962816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mChannel != null) {
963816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mProgramLoadTask = new LoadProgramsForCurrentChannelTask(
964816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        mContext.getContentResolver(), next);
965816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mProgramLoadTask.executeOnDbThread();
966816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
967816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
968816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
969816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void addDummyProgramsAt(long timeMs) {
970816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            addDummyPrograms(timeMs, timeMs + PREFETCH_DURATION_FOR_NEXT);
971816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
972816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
973816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private boolean addDummyPrograms(Range<Long> period) {
974816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return addDummyPrograms(period.getLower(), period.getUpper());
975816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
976816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
977816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private boolean addDummyPrograms(long startTimeMs, long endTimeMs) {
978816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            boolean added = false;
979816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mPrograms.isEmpty()) {
980816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                // Insert dummy program.
981816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mPrograms.addAll(createDummyPrograms(startTimeMs, endTimeMs));
982816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return true;
983816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
984816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            // Insert dummy program to the head of the list if needed.
985816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            Program firstProgram = mPrograms.get(0);
986816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (startTimeMs < firstProgram.getStartTimeUtcMillis()) {
987816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (!firstProgram.isValid()) {
988816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // Already the firstProgram is dummy.
989816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mPrograms.remove(0);
990816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mPrograms.addAll(0,
991816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            createDummyPrograms(startTimeMs, firstProgram.getEndTimeUtcMillis()));
992816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                } else {
993816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mPrograms.addAll(0,
994816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            createDummyPrograms(startTimeMs, firstProgram.getStartTimeUtcMillis()));
995816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
996816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                added = true;
997816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
998816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            // Insert dummy program to the tail of the list if needed.
999816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            Program lastProgram = mPrograms.get(mPrograms.size() - 1);
1000816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (endTimeMs > lastProgram.getEndTimeUtcMillis()) {
1001816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (!lastProgram.isValid()) {
1002816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // Already the lastProgram is dummy.
1003816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mPrograms.remove(mPrograms.size() - 1);
1004816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mPrograms.addAll(
1005816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            createDummyPrograms(lastProgram.getStartTimeUtcMillis(), endTimeMs));
1006816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                } else {
1007816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mPrograms.addAll(
1008816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            createDummyPrograms(lastProgram.getEndTimeUtcMillis(), endTimeMs));
1009816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
1010816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                added = true;
1011816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1012816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            // Insert dummy programs if the holes exist in the list.
1013816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            for (int i = 1; i < mPrograms.size(); ++i) {
1014816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                long endOfPrevious = mPrograms.get(i - 1).getEndTimeUtcMillis();
1015816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                long startOfCurrent = mPrograms.get(i).getStartTimeUtcMillis();
1016816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (startOfCurrent > endOfPrevious) {
1017816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    List<Program> dummyPrograms =
1018816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            createDummyPrograms(endOfPrevious, startOfCurrent);
1019816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mPrograms.addAll(i, dummyPrograms);
1020816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    i += dummyPrograms.size();
1021816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    added = true;
1022816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
1023816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1024816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return added;
1025816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
1026816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1027816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private void removeDummyPrograms() {
1028816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            for (int i = 0; i < mPrograms.size(); ++i) {
1029816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                Program program = mPrograms.get(i);
1030816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (!program.isValid()) {
1031816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mPrograms.remove(i--);
1032816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
1033816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1034816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
1035816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1036816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private void removeOverlappedPrograms(List<Program> loadedPrograms) {
1037816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mPrograms.size() == 0) {
1038816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return;
1039816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1040816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            Program program = mPrograms.get(0);
1041816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            for (int i = 0, j = 0; i < mPrograms.size() && j < loadedPrograms.size(); ++j) {
1042816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                Program loadedProgram = loadedPrograms.get(j);
1043816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                // Skip previous programs.
1044816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                while (program.getEndTimeUtcMillis() < loadedProgram.getStartTimeUtcMillis()) {
1045816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // Reached end of mPrograms.
1046816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (++i == mPrograms.size()) {
1047816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        return;
1048816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
1049816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    program = mPrograms.get(i);
1050816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
1051816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                // Remove overlapped programs.
1052816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                while (program.getStartTimeUtcMillis() < loadedProgram.getEndTimeUtcMillis()
1053816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        && program.getEndTimeUtcMillis() > loadedProgram.getStartTimeUtcMillis()) {
1054816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mPrograms.remove(i);
1055816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (i >= mPrograms.size()) {
1056816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        break;
1057816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
1058816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    program = mPrograms.get(i);
1059816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
1060816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1061816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
1062816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1063816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        // Returns a list of dummy programs.
1064816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        // The maximum duration of a dummy program is {@link MAX_DUMMY_PROGRAM_DURATION}.
1065816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        // So if the duration ({@code endTimeMs}-{@code startTimeMs}) is greater than the duration,
1066816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        // we need to create multiple dummy programs.
1067816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        // The reason of the limitation of the duration is because we want the trick play viewer
1068816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        // to show the time-line duration of {@link MAX_DUMMY_PROGRAM_DURATION} at most
1069816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        // for a dummy program.
1070816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private List<Program> createDummyPrograms(long startTimeMs, long endTimeMs) {
1071816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (startTimeMs >= endTimeMs) {
1072816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return Collections.emptyList();
1073816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1074816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            List<Program> programs = new ArrayList<>();
1075816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            long start = startTimeMs;
1076816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            long end = Utils.ceilTime(startTimeMs, MAX_DUMMY_PROGRAM_DURATION);
1077816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            while (end < endTimeMs) {
1078816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                programs.add(new Program.Builder()
1079816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        .setStartTimeUtcMillis(start)
1080816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        .setEndTimeUtcMillis(end)
1081816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        .build());
1082816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                start = end;
1083816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                end += MAX_DUMMY_PROGRAM_DURATION;
1084816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1085816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            programs.add(new Program.Builder()
1086816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    .setStartTimeUtcMillis(start)
1087816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    .setEndTimeUtcMillis(endTimeMs)
1088816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    .build());
1089816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return programs;
1090816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
1091816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1092816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        Program getProgramAt(long timeMs) {
1093816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return getProgramAt(timeMs, 0, mPrograms.size() - 1);
1094816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
1095816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1096816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private Program getProgramAt(long timeMs, int start, int end) {
1097816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (start > end) {
1098816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return null;
1099816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1100816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            int mid = (start + end) / 2;
1101816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            Program program = mPrograms.get(mid);
1102816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (program.getStartTimeUtcMillis() > timeMs) {
1103816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return getProgramAt(timeMs, start, mid - 1);
1104816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            } else if (program.getEndTimeUtcMillis() <= timeMs) {
1105816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return getProgramAt(timeMs, mid+1, end);
1106816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            } else {
1107816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return program;
1108816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1109816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
1110816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1111816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private long getOldestProgramStartTime() {
1112816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mPrograms.isEmpty()) {
1113816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return INVALID_TIME;
1114816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1115816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return mPrograms.get(0).getStartTimeUtcMillis();
1116816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
1117816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1118816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private Program getLastValidProgram() {
1119816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            for (int i = mPrograms.size() - 1; i >= 0; --i) {
1120816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                Program program = mPrograms.get(i);
1121816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (program.isValid()) {
1122816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    return program;
1123816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
1124816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1125816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return null;
1126816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
1127816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1128816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private void schedulePrefetchPrograms() {
1129816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (DEBUG) Log.d(TAG, "Scheduling prefetching programs.");
1130816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mHandler.hasMessages(MSG_PREFETCH_PROGRAM)) {
1131816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return;
1132816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1133816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            Program lastValidProgram = getLastValidProgram();
1134816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (DEBUG) Log.d(TAG, "Last valid program = " + lastValidProgram);
113548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            final long delay;
1136816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (lastValidProgram != null) {
113748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                delay = lastValidProgram.getEndTimeUtcMillis()
1138816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        - PREFETCH_TIME_OFFSET_FROM_PROGRAM_END - System.currentTimeMillis();
1139816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            } else {
114048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                // Since there might not be any program data delay the retry 5 seconds,
114148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                // then 30 seconds then 5 minutes
114248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                switch (mEmptyFetchCount) {
114348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                    case 0:
114448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                        delay = 0;
114548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                        break;
114648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                    case 1:
114748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                        delay = TimeUnit.SECONDS.toMillis(5);
114848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                        break;
114948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                    case 2:
115048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                        delay = TimeUnit.SECONDS.toMillis(30);
115148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                        break;
115248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                    default:
115348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                        delay = TimeUnit.MINUTES.toMillis(5);
115448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                        break;
115548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                }
115648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                if (DEBUG) {
115748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                    Log.d(TAG,
115848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                            "No last valid  program. Already tried " + mEmptyFetchCount + " times");
115948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                }
1160816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
116148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mHandler.sendEmptyMessageDelayed(MSG_PREFETCH_PROGRAM, delay);
116248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            if (DEBUG) Log.d(TAG, "Scheduling with " + delay + "(ms) delays.");
1163816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
1164816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
116548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        // Prefecth programs within PREFETCH_DURATION_FOR_NEXT from now.
1166816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private void prefetchPrograms() {
1167816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            long startTimeMs;
1168816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            Program lastValidProgram = getLastValidProgram();
1169816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (lastValidProgram == null) {
1170816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                startTimeMs = System.currentTimeMillis();
1171816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            } else {
1172816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                startTimeMs = lastValidProgram.getEndTimeUtcMillis();
1173816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1174816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            long endTimeMs = System.currentTimeMillis() + PREFETCH_DURATION_FOR_NEXT;
117548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            if (startTimeMs <= endTimeMs) {
117648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                if (DEBUG) {
117748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                    Log.d(TAG, "Prefetch task starts: {startTime=" + Utils.toTimeString(startTimeMs)
117848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                            + ", endTime=" + Utils.toTimeString(endTimeMs) + "}");
117948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                }
118048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                mProgramLoadQueue.add(Range.create(startTimeMs, endTimeMs));
1181816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1182816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            startTaskIfNeeded();
1183816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
1184816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1185816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private class LoadProgramsForCurrentChannelTask
1186816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                extends AsyncDbTask.LoadProgramsForChannelTask {
1187816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1188816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            public LoadProgramsForCurrentChannelTask(ContentResolver contentResolver,
1189816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    Range<Long> period) {
1190816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                super(contentResolver, mChannel.getId(), period);
1191816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1192816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1193816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            @Override
1194816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            protected void onPostExecute(List<Program> programs) {
1195816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (DEBUG) {
1196816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    Log.d(TAG, "Programs are loaded {channelId=" + mChannelId +
1197816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            ", from=" + Utils.toTimeString(mPeriod.getLower()) +
1198816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            ", to=" + Utils.toTimeString(mPeriod.getUpper()) +
1199816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            "}");
1200816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
1201816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                //remove pending tasks that are fully satisfied by this query.
1202816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                Iterator<Range<Long>> it = mProgramLoadQueue.iterator();
1203816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                while (it.hasNext()) {
1204816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    Range<Long> r = it.next();
1205816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (mPeriod.contains(r)) {
1206816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        it.remove();
1207816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
1208816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
120948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                if (programs == null || programs.isEmpty()) {
121048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                    mEmptyFetchCount++;
1211816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (addDummyPrograms(mPeriod)) {
1212816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        TimeShiftManager.this.onProgramInfoChanged();
1213816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
1214816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    schedulePrefetchPrograms();
1215816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    startNextLoadingIfNeeded();
1216816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    return;
1217816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
121848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                mEmptyFetchCount = 0;
121948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                if(!mPrograms.isEmpty()) {
122048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                    removeDummyPrograms();
122148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                    removeOverlappedPrograms(programs);
122248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                    Program loadedProgram = programs.get(0);
122348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                    for (int i = 0; i < mPrograms.size() && !programs.isEmpty(); ++i) {
122448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                        Program program = mPrograms.get(i);
122548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                        while (program.getStartTimeUtcMillis() > loadedProgram
122648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                                .getStartTimeUtcMillis()) {
122748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                            mPrograms.add(i++, loadedProgram);
122848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                            programs.remove(0);
122948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                            if (programs.isEmpty()) {
123048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                                break;
123148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                            }
123248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                            loadedProgram = programs.get(0);
1233816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        }
1234816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
1235816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
1236816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mPrograms.addAll(programs);
1237816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                addDummyPrograms(mPeriod);
1238816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                TimeShiftManager.this.onProgramInfoChanged();
1239816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                schedulePrefetchPrograms();
1240816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                startNextLoadingIfNeeded();
1241816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1242816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1243816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            @Override
1244816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            protected void onCancelled(List<Program> programs) {
1245816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (DEBUG) {
1246816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    Log.d(TAG, "Program loading has been canceled {channelId=" + (mChannel == null
1247816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            ? "null" : mChannelId) + ", from=" + Utils
1248816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            .toTimeString(mPeriod.getLower()) + ", to=" + Utils
1249816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            .toTimeString(mPeriod.getUpper()) + "}");
1250816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
1251816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                startNextLoadingIfNeeded();
1252816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1253816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1254816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            private void startNextLoadingIfNeeded() {
1255816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mProgramLoadTask = null;
1256816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                // Need to post to handler, because the task is still running.
1257816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mHandler.post(new Runnable() {
1258816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    @Override
1259816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    public void run() {
1260816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        startTaskIfNeeded();
1261816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
1262816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                });
1263816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1264816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1265816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            public boolean overlaps(Queue<Range<Long>> programLoadQueue) {
1266816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                for (Range<Long> r : programLoadQueue) {
1267816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (mPeriod.contains(r.getLower()) || mPeriod.contains(r.getUpper())) {
1268816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        return true;
1269816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
1270816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
1271816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return false;
1272816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1273816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
1274816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
1275816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1276816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @VisibleForTesting
1277816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    final class CurrentPositionMediator {
1278816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        long mCurrentPositionMs;
1279816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        long mSeekRequestTimeMs;
1280816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1281816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void initialize(long timeMs) {
1282816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mSeekRequestTimeMs = INVALID_TIME;
1283816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mCurrentPositionMs = timeMs;
1284816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            TimeShiftManager.this.onCurrentPositionChanged();
1285816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
1286816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1287816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void onSeekRequested(long seekTimeMs) {
1288816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mSeekRequestTimeMs = System.currentTimeMillis();
1289816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mCurrentPositionMs = seekTimeMs;
1290816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            TimeShiftManager.this.onCurrentPositionChanged();
1291816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
1292816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1293816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void onCurrentPositionChanged(long currentPositionMs) {
1294816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mSeekRequestTimeMs == INVALID_TIME) {
1295816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mCurrentPositionMs = currentPositionMs;
1296816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                TimeShiftManager.this.onCurrentPositionChanged();
1297816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return;
1298816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1299816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            long currentTimeMs = System.currentTimeMillis();
1300816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            boolean isValid = Math.abs(currentPositionMs - mCurrentPositionMs) < REQUEST_TIMEOUT_MS;
1301816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            boolean isTimeout = currentTimeMs > mSeekRequestTimeMs + REQUEST_TIMEOUT_MS;
1302816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (isValid || isTimeout) {
1303816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                initialize(currentPositionMs);
1304816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            } else {
1305816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (getPlayStatus() == PLAY_STATUS_PLAYING) {
1306816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (getPlayDirection() == PLAY_DIRECTION_FORWARD) {
1307816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        mCurrentPositionMs += (currentTimeMs - mSeekRequestTimeMs)
1308816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                                * getPlaybackSpeed();
1309816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    } else {
1310816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        mCurrentPositionMs -= (currentTimeMs - mSeekRequestTimeMs)
1311816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                                * getPlaybackSpeed();
1312816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
1313816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
1314816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                TimeShiftManager.this.onCurrentPositionChanged();
1315816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1316816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
1317816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
1318816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1319816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
1320816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * The listener used to receive the events by the time-shift manager
1321816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
1322816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public interface Listener {
1323816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        /**
1324816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * Called when the availability of the time-shift for the current channel has been changed.
1325816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * If the time shift is available, {@link TimeShiftManager#getRecordStartTimeMs} should
1326816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * return the valid time.
1327816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         */
1328816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void onAvailabilityChanged();
1329816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1330816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        /**
1331816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * Called when the play status is changed between {@link #PLAY_STATUS_PLAYING} and
1332816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * {@link #PLAY_STATUS_PAUSED}
1333816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         *
1334816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * @param status The new play state.
1335816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         */
1336816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void onPlayStatusChanged(int status);
1337816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1338816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        /**
1339816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * Called when the recordStartTime has been changed.
1340816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         */
1341ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        void onRecordTimeRangeChanged();
1342816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1343816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        /**
1344816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * Called when the current position is changed.
1345816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         */
1346816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void onCurrentPositionChanged();
1347816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1348816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        /**
1349816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * Called when the program information is updated.
1350816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         */
1351816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void onProgramInfoChanged();
1352816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1353816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        /**
1354816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * Called when an action becomes enabled or disabled.
1355816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         */
1356816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void onActionEnabledChanged(@TimeShiftActionId int actionId, boolean enabled);
1357816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
135807b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko
135907b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko    private static class TimeShiftHandler extends WeakHandler<TimeShiftManager> {
136007b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko        public TimeShiftHandler(TimeShiftManager ref) {
136107b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko            super(ref);
136207b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko        }
136307b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko
136407b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko        @Override
136507b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko        public void handleMessage(Message msg, @NonNull TimeShiftManager timeShiftManager) {
136607b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko            switch (msg.what) {
136707b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko                case MSG_GET_CURRENT_POSITION:
136807b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko                    timeShiftManager.mPlayController.handleGetCurrentPosition();
136907b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko                    break;
137007b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko                case MSG_PREFETCH_PROGRAM:
137107b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko                    timeShiftManager.mProgramManager.prefetchPrograms();
137207b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko                    break;
137307b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko            }
137407b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko        }
137507b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko    }
1376816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko}
1377