TimeShiftManager.java revision 816a4be1a0f34f6a48877c8afd3dbbca19eac435
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.annotation.SuppressLint;
20816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.content.ContentResolver;
21816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.content.Context;
22816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.os.Handler;
23816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.os.Message;
24816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.support.annotation.IntDef;
25816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.support.annotation.NonNull;
26816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.support.annotation.Nullable;
27816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.support.annotation.VisibleForTesting;
28816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.util.Log;
29816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.util.Range;
30816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
31816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.data.Channel;
32816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.data.OnCurrentProgramUpdatedListener;
33816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.data.Program;
34816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.data.ProgramDataManager;
35816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.ui.TunableTvView;
36816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.ui.TunableTvView.TimeShiftListener;
37816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.util.AsyncDbTask;
38816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.util.Utils;
39816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
40816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.lang.annotation.Retention;
41816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.lang.annotation.RetentionPolicy;
42816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.ArrayList;
43816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Collections;
44816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Iterator;
45816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.LinkedList;
46816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.List;
47816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Objects;
48816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Queue;
49816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.concurrent.TimeUnit;
50816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
51816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko/**
52816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * A class which manages the time shift feature in Live Channels. It consists of two parts.
53816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * {@link PlayController} controls the playback such as play/pause, rewind and fast-forward using
54816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * {@link TunableTvView} which communicates with TvInputService through
55816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * {@link android.media.tv.TvInputService.Session}.
56816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * {@link ProgramManager} loads programs of the current channel in the background.
57816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */
58816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkopublic class TimeShiftManager {
59816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final String TAG = "TimeShiftManager";
60816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final boolean DEBUG = false;
61816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
62816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @Retention(RetentionPolicy.SOURCE)
63816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @IntDef({PLAY_STATUS_PAUSED, PLAY_STATUS_PLAYING})
64816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public @interface PlayStatus {}
65816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static final int PLAY_STATUS_PAUSED  = 0;
66816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static final int PLAY_STATUS_PLAYING = 1;
67816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
68816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @Retention(RetentionPolicy.SOURCE)
69816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @IntDef({PLAY_SPEED_1X, PLAY_SPEED_2X, PLAY_SPEED_3X, PLAY_SPEED_4X, PLAY_SPEED_5X})
70816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public @interface PlaySpeed{}
71816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static final int PLAY_SPEED_1X = 1;
72816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static final int PLAY_SPEED_2X = 2;
73816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static final int PLAY_SPEED_3X = 3;
74816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static final int PLAY_SPEED_4X = 4;
75816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static final int PLAY_SPEED_5X = 5;
76816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
77816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final int SHORT_PROGRAM_THRESHOLD_MILLIS = 46 * 60 * 1000;  // 46 mins.
78816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final int[] SHORT_PROGRAM_SPEED_FACTORS = new int[] {2, 4, 12, 48};
79816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final int[] LONG_PROGRAM_SPEED_FACTORS = new int[] {2, 8, 32, 128};
80816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
81816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @Retention(RetentionPolicy.SOURCE)
82816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @IntDef({PLAY_DIRECTION_FORWARD, PLAY_DIRECTION_BACKWARD})
83816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public @interface PlayDirection{}
84816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static final int PLAY_DIRECTION_FORWARD  = 0;
85816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static final int PLAY_DIRECTION_BACKWARD = 1;
86816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
87816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @Retention(RetentionPolicy.SOURCE)
88816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @IntDef({TIME_SHIFT_ACTION_ID_PLAY, TIME_SHIFT_ACTION_ID_PAUSE, TIME_SHIFT_ACTION_ID_REWIND,
89816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        TIME_SHIFT_ACTION_ID_FAST_FORWARD, TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS,
90816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT})
91816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public @interface TimeShiftActionId{}
92816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static final int TIME_SHIFT_ACTION_ID_PLAY = 1;
93816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static final int TIME_SHIFT_ACTION_ID_PAUSE = 1 << 1;
94816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static final int TIME_SHIFT_ACTION_ID_REWIND = 1 << 2;
95816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static final int TIME_SHIFT_ACTION_ID_FAST_FORWARD = 1 << 3;
96816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static final int TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS = 1 << 4;
97816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public static final int TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT = 1 << 5;
98816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
99816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final int MSG_GET_CURRENT_POSITION = 1000;
100816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final int MSG_PREFETCH_PROGRAM = 1001;
101816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final long REQUEST_CURRENT_POSITION_INTERVAL = TimeUnit.SECONDS.toMillis(1);
102816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final long MAX_DUMMY_PROGRAM_DURATION = TimeUnit.MINUTES.toMillis(30);
103816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @VisibleForTesting
104816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    static final long INVALID_TIME = -1;
105816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final long PREFETCH_TIME_OFFSET_FROM_PROGRAM_END = TimeUnit.MINUTES.toMillis(1);
106816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final long PREFETCH_DURATION_FOR_NEXT = TimeUnit.HOURS.toMillis(2);
107816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
108816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @VisibleForTesting
109816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    static final long REQUEST_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(3);
110816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
111816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
112816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * If the user presses the {@link android.view.KeyEvent#KEYCODE_MEDIA_PREVIOUS} button within
113816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * this threshold from the program start time, the play position moves to the start of the
114816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * previous program.
115816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Otherwise, the play position moves to the start of the current program.
116816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * This value is specified in the UX document.
117816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
118816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final long PROGRAM_START_TIME_THRESHOLD = TimeUnit.SECONDS.toMillis(3);
119816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
120816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * If the current position enters within this range from the recording start time, rewind action
121816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * and jump to previous action is disabled.
122816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Similarly, if the current position enters within this range from the current system time,
123816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * fast forward action and jump to next action is disabled.
124816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * It must be three times longer than {@link #REQUEST_CURRENT_POSITION_INTERVAL} at least.
125816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
126816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final long DISABLE_ACTION_THRESHOLD = 3 * REQUEST_CURRENT_POSITION_INTERVAL;
127816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
128816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * If the current position goes out of this range from the recording start time, rewind action
129816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * and jump to previous action is enabled.
130816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Similarly, if the current position goes out of this range from the current system time,
131816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * fast forward action and jump to next action is enabled.
132816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Enable threshold and disable threshold must be different because the current position
133816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * does not have the continuous value. It changes every one second.
134816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
135816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final long ENABLE_ACTION_THRESHOLD =
136816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            DISABLE_ACTION_THRESHOLD + 3 * REQUEST_CURRENT_POSITION_INTERVAL;
137816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
138816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * The current position sent from TIS can not be exactly the same as the current system time
139816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * due to the elapsed time to pass the message from TIS to Live Channels.
140816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * So the boundary threshold is necessary.
141816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * The same goes for the recording start time.
142816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * It must be three times longer than {@link #REQUEST_CURRENT_POSITION_INTERVAL} at least.
143816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
144816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final long RECORDING_BOUNDARY_THRESHOLD = 3 * REQUEST_CURRENT_POSITION_INTERVAL;
145816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
146816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private final PlayController mPlayController;
147816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private final ProgramManager mProgramManager;
148816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @VisibleForTesting
149816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    final CurrentPositionMediator mCurrentPositionMediator = new CurrentPositionMediator();
150816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
151816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private Listener mListener;
152816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private final OnCurrentProgramUpdatedListener mOnCurrentProgramUpdatedListener;
153816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private int mEnabledActionIds = TIME_SHIFT_ACTION_ID_PLAY | TIME_SHIFT_ACTION_ID_PAUSE
154816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            | TIME_SHIFT_ACTION_ID_REWIND | TIME_SHIFT_ACTION_ID_FAST_FORWARD
155816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            | TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS | TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT;
156816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private int mLastActionId = 0;
157816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
158816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    // TODO: Remove these variables once API level 23 is available.
159816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private final Context mContext;
160816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
161816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private Program mCurrentProgram;
162816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    // This variable is used to block notification while changing the availability status.
163816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private boolean mNotificationEnabled;
164816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
165816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @SuppressLint("HandlerLeak")
166816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private final Handler mHandler = new Handler() {
167816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        @Override
168816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        public void handleMessage(Message msg) {
169816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            switch (msg.what) {
170816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                case MSG_GET_CURRENT_POSITION:
171816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mPlayController.handleGetCurrentPosition();
172816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    break;
173816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                case MSG_PREFETCH_PROGRAM:
174816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mProgramManager.prefetchPrograms();
175816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    break;
176816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
177816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
178816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    };
179816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
180816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public TimeShiftManager(Context context, TunableTvView tvView,
181816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            ProgramDataManager programDataManager,
182816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            OnCurrentProgramUpdatedListener onCurrentProgramUpdatedListener) {
183816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mContext = context;
184816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mPlayController = new PlayController(tvView);
185816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mProgramManager = new ProgramManager(programDataManager);
186816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mOnCurrentProgramUpdatedListener = onCurrentProgramUpdatedListener;
187816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        tvView.setOnScreenBlockedListener(new TunableTvView.OnScreenBlockingChangedListener() {
188816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            @Override
189816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            public void onScreenBlockingChanged(boolean blocked) {
190816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                onAvailabilityChanged();
191816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
192816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        });
193816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
194816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
195816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
196816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Sets a listener which will receive events from this class.
197816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
198816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void setListener(Listener listener) {
199816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mListener = listener;
200816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
201816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
202816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
203816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Checks if the trick play is available for the current channel.
204816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
205816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public boolean isAvailable() {
206816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return mPlayController.isAvailable();
207816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
208816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
209816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
210816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Returns the current time position in milliseconds.
211816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
212816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public long getCurrentPositionMs() {
213816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return mCurrentPositionMediator.mCurrentPositionMs;
214816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
215816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
216816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    void setCurrentPositionMs(long currentTimeMs) {
217816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mCurrentPositionMediator.onCurrentPositionChanged(currentTimeMs);
218816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
219816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
220816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
221816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Returns the start time of the recording in milliseconds.
222816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
223816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public long getRecordStartTimeMs() {
224816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        long oldestProgramStartTime = mProgramManager.getOldestProgramStartTime();
225816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return oldestProgramStartTime == INVALID_TIME ? INVALID_TIME
226816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                : mPlayController.mRecordStartTimeMs;
227816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
228816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
229816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
230816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Plays the media.
231816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     *
232816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * @throws IllegalStateException if the trick play is not available.
233816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
234816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void play() {
235816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (!isActionEnabled(TIME_SHIFT_ACTION_ID_PLAY)) {
236816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return;
237816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
238816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mLastActionId = TIME_SHIFT_ACTION_ID_PLAY;
239816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mPlayController.play();
240816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        updateActions();
241816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
242816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
243816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
244816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Pauses the playback.
245816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     *
246816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * @throws IllegalStateException if the trick play is not available.
247816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
248816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void pause() {
249816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (!isActionEnabled(TIME_SHIFT_ACTION_ID_PAUSE)) {
250816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return;
251816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
252816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mLastActionId = TIME_SHIFT_ACTION_ID_PAUSE;
253816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mPlayController.pause();
254816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        updateActions();
255816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
256816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
257816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
258816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Toggles the playing and paused state.
259816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     *
260816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * @throws IllegalStateException if the trick play is not available.
261816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
262816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void togglePlayPause() {
263816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mPlayController.togglePlayPause();
264816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
265816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
266816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
267816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Plays the media in backward direction. The playback speed is increased by 1x each time
268816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * this is called. The range of the speed is from 2x to 5x.
269816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * If the playing position is considered the same as the record start time, it does nothing
270816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     *
271816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * @throws IllegalStateException if the trick play is not available.
272816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
273816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void rewind() {
274816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (!isActionEnabled(TIME_SHIFT_ACTION_ID_REWIND)) {
275816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return;
276816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
277816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mLastActionId = TIME_SHIFT_ACTION_ID_REWIND;
278816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mPlayController.rewind();
279816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        updateActions();
280816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
281816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
282816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
283816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Plays the media in forward direction. The playback speed is increased by 1x each time
284816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * this is called. The range of the speed is from 2x to 5x.
285816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * If the playing position is the same as the current time, it does nothing.
286816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     *
287816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * @throws IllegalStateException if the trick play is not available.
288816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
289816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void fastForward() {
290816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (!isActionEnabled(TIME_SHIFT_ACTION_ID_FAST_FORWARD)) {
291816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return;
292816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
293816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mLastActionId = TIME_SHIFT_ACTION_ID_FAST_FORWARD;
294816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mPlayController.fastForward();
295816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        updateActions();
296816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
297816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
298816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
299816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Jumps to the start of the current program.
300816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * If the currently playing position is within 3 seconds
301816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * (={@link #PROGRAM_START_TIME_THRESHOLD})from the start time of the program, it goes to
302816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * the start of the previous program if exists.
303816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * If the playing position is the same as the record start time, it does nothing.
304816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     *
305816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * @throws IllegalStateException if the trick play is not available.
306816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
307816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void jumpToPrevious() {
308816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (!isActionEnabled(TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS)) {
309816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return;
310816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
311816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        Program program = mProgramManager.getProgramAt(
312816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mCurrentPositionMediator.mCurrentPositionMs - PROGRAM_START_TIME_THRESHOLD);
313816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (program == null) {
314816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return;
315816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
316816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        long seekPosition =
317816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                Math.max(program.getStartTimeUtcMillis(), mPlayController.mRecordStartTimeMs);
318816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mLastActionId = TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS;
319816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mPlayController.seekTo(seekPosition);
320816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mCurrentPositionMediator.onSeekRequested(seekPosition);
321816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        updateActions();
322816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
323816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
324816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
325816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Jumps to the start of the next program if exists.
326816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * If there's no next program, it jumps to the current system time and shows the live TV.
327816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * If the playing position is considered the same as the current time, it does nothing.
328816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     *
329816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * @throws IllegalStateException if the trick play is not available.
330816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
331816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void jumpToNext() {
332816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (!isActionEnabled(TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT)) {
333816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return;
334816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
335816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        Program currentProgram = mProgramManager.getProgramAt(
336816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mCurrentPositionMediator.mCurrentPositionMs);
337816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (currentProgram == null) {
338816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return;
339816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
340816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        Program nextProgram = mProgramManager.getProgramAt(currentProgram.getEndTimeUtcMillis());
341816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        long currentTimeMs = System.currentTimeMillis();
342816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mLastActionId = TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT;
343816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (nextProgram == null || nextProgram.getStartTimeUtcMillis() > currentTimeMs) {
344816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mPlayController.seekTo(currentTimeMs);
345816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mPlayController.isForwarding()) {
346816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                // The current position will be the current system time from now.
347816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mPlayController.mIsPlayOffsetChanged = false;
348816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mCurrentPositionMediator.initialize(currentTimeMs);
349816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            } else {
350816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                // The current position would not be the current system time.
351816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                // So need to wait for the correct time from TIS.
352816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mCurrentPositionMediator.onSeekRequested(currentTimeMs);
353816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
354816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        } else {
355816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mPlayController.seekTo(nextProgram.getStartTimeUtcMillis());
356816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mCurrentPositionMediator.onSeekRequested(nextProgram.getStartTimeUtcMillis());
357816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
358816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        updateActions();
359816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
360816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
361816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
362816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Returns the playback status. The value is PLAY_STATUS_PAUSED or PLAY_STATUS_PLAYING.
363816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
364816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @PlayStatus public int getPlayStatus() {
365816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return mPlayController.mPlayStatus;
366816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
367816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
368816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
369816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Returns the displayed playback speed. The value is one of PLAY_SPEED_1X, PLAY_SPEED_2X,
370816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * PLAY_SPEED_3X, PLAY_SPEED_4X and PLAY_SPEED_5X.
371816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
372816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @PlaySpeed public int getDisplayedPlaySpeed() {
373816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return mPlayController.mDisplayedPlaySpeed;
374816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
375816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
376816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
377816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Returns the playback speed. The value is PLAY_DIRECTION_FORWARD or PLAY_DIRECTION_BACKWARD.
378816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
379816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @PlayDirection public int getPlayDirection() {
380816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return mPlayController.mPlayDirection;
381816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
382816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
383816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
384816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Returns the ID of the last action..
385816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
386816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @TimeShiftActionId public int getLastActionId() {
387816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return mLastActionId;
388816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
389816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
390816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
391816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Enables or disables the time-shift actions.
392816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
393816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @VisibleForTesting
394816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    void enableAction(@TimeShiftActionId int actionId, boolean enable) {
395816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        int oldEnabledActionIds = mEnabledActionIds;
396816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (enable) {
397816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mEnabledActionIds |= actionId;
398816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        } else {
399816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mEnabledActionIds &= ~actionId;
400816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
401816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (mNotificationEnabled && mListener != null
402816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                && oldEnabledActionIds != mEnabledActionIds) {
403816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mListener.onActionEnabledChanged(actionId, enable);
404816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
405816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
406816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
407816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public boolean isActionEnabled(@TimeShiftActionId int actionId) {
408816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return (mEnabledActionIds & actionId) == actionId;
409816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
410816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
411816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private void updateActions() {
412816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (isAvailable()) {
413816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            enableAction(TIME_SHIFT_ACTION_ID_PLAY, true);
414816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            enableAction(TIME_SHIFT_ACTION_ID_PAUSE, true);
415816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            // Rewind action and jump to previous action.
416816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            long threshold = isActionEnabled(TIME_SHIFT_ACTION_ID_REWIND)
417816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    ? DISABLE_ACTION_THRESHOLD : ENABLE_ACTION_THRESHOLD;
418816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            boolean enabled = mCurrentPositionMediator.mCurrentPositionMs
419816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    - mPlayController.mRecordStartTimeMs > threshold;
420816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            enableAction(TIME_SHIFT_ACTION_ID_REWIND, enabled);
421816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            enableAction(TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS, enabled);
422816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            // Fast forward action and jump to next action
423816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            threshold = isActionEnabled(TIME_SHIFT_ACTION_ID_FAST_FORWARD)
424816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    ? DISABLE_ACTION_THRESHOLD : ENABLE_ACTION_THRESHOLD;
425816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            enabled = System.currentTimeMillis() - mCurrentPositionMediator.mCurrentPositionMs
426816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    > threshold;
427816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            enableAction(TIME_SHIFT_ACTION_ID_FAST_FORWARD, enabled);
428816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            enableAction(TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT, enabled);
429816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        } else {
430816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            enableAction(TIME_SHIFT_ACTION_ID_PLAY, false);
431816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            enableAction(TIME_SHIFT_ACTION_ID_PAUSE, false);
432816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            enableAction(TIME_SHIFT_ACTION_ID_REWIND, false);
433816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            enableAction(TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS, false);
434816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            enableAction(TIME_SHIFT_ACTION_ID_FAST_FORWARD, false);
435816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            enableAction(TIME_SHIFT_ACTION_ID_PLAY, false);
436816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
437816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
438816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
439816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private void updateCurrentProgram() {
440816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        Program currentProgram = getProgramAt(mCurrentPositionMediator.mCurrentPositionMs);
441816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (!Program.isValid(currentProgram)) {
442816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            currentProgram = null;
443816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
444816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (!Objects.equals(mCurrentProgram, currentProgram)) {
445816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (DEBUG) Log.d(TAG, "Current program has been updated. " + currentProgram);
446816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mCurrentProgram = currentProgram;
447816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mNotificationEnabled && mOnCurrentProgramUpdatedListener != null) {
448816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                Channel channel = mPlayController.getCurrentChannel();
449816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (channel != null) {
450816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mOnCurrentProgramUpdatedListener.onCurrentProgramUpdated(channel.getId(),
451816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            mCurrentProgram);
452816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mPlayController.onCurrentProgramChanged();
453816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
454816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
455816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
456816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
457816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
458816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
459816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Returns {@code true} if the trick play is available and it's playing to the forward direction
460816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * with normal speed, otherwise {@code false}.
461816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
462816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public boolean isNormalPlaying() {
463816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return mPlayController.isAvailable()
464816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                && mPlayController.mPlayStatus == PLAY_STATUS_PLAYING
465816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                && mPlayController.mPlayDirection == PLAY_DIRECTION_FORWARD
466816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                && mPlayController.mDisplayedPlaySpeed == PLAY_SPEED_1X;
467816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
468816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
469816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
470816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Checks if the trick play is available and it's playback status is paused.
471816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
472816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public boolean isPaused() {
473816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return mPlayController.isAvailable() && mPlayController.mPlayStatus == PLAY_STATUS_PAUSED;
474816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
475816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
476816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
477816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Returns the program which airs at the given time.
478816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
479816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @NonNull
480816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public Program getProgramAt(long timeMs) {
481816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        Program program = mProgramManager.getProgramAt(timeMs);
482816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (program == null) {
483816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            // Guard just in case when the program prefetch handler doesn't work on time.
484816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mProgramManager.addDummyProgramsAt(timeMs);
485816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            program = mProgramManager.getProgramAt(timeMs);
486816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
487816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return program;
488816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
489816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
490816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    void onAvailabilityChanged() {
491816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mProgramManager.onAvailabilityChanged(mPlayController.isAvailable(),
492816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mPlayController.getCurrentChannel(), mPlayController.mRecordStartTimeMs);
493816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        updateActions();
494816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        // Availability change notification should be always sent
495816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        // even if mNotificationEnabled is false.
496816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (mListener != null) {
497816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mListener.onAvailabilityChanged();
498816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
499816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
500816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
501816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    void onRecordStartTimeChanged() {
502816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (mPlayController.isAvailable()) {
503816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mProgramManager.onRecordStartTimeChanged(mPlayController.mRecordStartTimeMs);
504816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
505816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        updateActions();
506816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (mNotificationEnabled && mListener != null) {
507816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mListener.onRecordStartTimeChanged();
508816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
509816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
510816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
511816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    void onCurrentPositionChanged() {
512816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        updateActions();
513816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        updateCurrentProgram();
514816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (mNotificationEnabled && mListener != null) {
515816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mListener.onCurrentPositionChanged();
516816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
517816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
518816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
519816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    void onPlayStatusChanged(@PlayStatus int status) {
520816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (mNotificationEnabled && mListener != null) {
521816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mListener.onPlayStatusChanged(status);
522816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
523816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
524816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
525816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    void onProgramInfoChanged() {
526816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        updateCurrentProgram();
527816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (mNotificationEnabled && mListener != null) {
528816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mListener.onProgramInfoChanged();
529816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
530816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
531816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
532816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
533816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Returns the current program which airs right now.<p>
534816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     *
535816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * If the program is a dummy program, which means there's no program information,
536816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * returns {@code null}.
537816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
538816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @Nullable
539816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public Program getCurrentProgram() {
540816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (isAvailable()) {
541816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return mCurrentProgram;
542816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
543816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return null;
544816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
545816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
546816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private int getPlaybackSpeed() {
547816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        int[] playbackSpeedList;
548816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (getCurrentProgram() == null || getCurrentProgram().getEndTimeUtcMillis()
549816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                - getCurrentProgram().getStartTimeUtcMillis() > SHORT_PROGRAM_THRESHOLD_MILLIS) {
550816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            playbackSpeedList = LONG_PROGRAM_SPEED_FACTORS;
551816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        } else {
552816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            playbackSpeedList = SHORT_PROGRAM_SPEED_FACTORS;
553816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
554816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        switch (mPlayController.mDisplayedPlaySpeed) {
555816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            case PLAY_SPEED_1X:
556816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return 1;
557816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            case PLAY_SPEED_2X:
558816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return playbackSpeedList[0];
559816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            case PLAY_SPEED_3X:
560816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return playbackSpeedList[1];
561816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            case PLAY_SPEED_4X:
562816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return playbackSpeedList[2];
563816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            case PLAY_SPEED_5X:
564816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return playbackSpeedList[3];
565816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            default:
566816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                Log.w(TAG, "Unknown displayed play speed is chosen : "
567816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        + mPlayController.mDisplayedPlaySpeed);
568816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return 1;
569816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
570816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
571816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
572816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
573816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * A class which controls the trick play.
574816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
575816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private class PlayController {
576816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private final TunableTvView mTvView;
577816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
578816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private long mPossibleStartTimeMs;
579816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private long mRecordStartTimeMs;
580816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
581816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        @PlayStatus private int mPlayStatus = PLAY_STATUS_PAUSED;
582816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        @PlaySpeed private int mDisplayedPlaySpeed = PLAY_SPEED_1X;
583816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        @PlayDirection private int mPlayDirection = PLAY_DIRECTION_FORWARD;
584816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private int mPlaybackSpeed;
585816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
586816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        /**
587816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * Indicates that the trick play is not playing the current time position.
588816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * It is set true when {@link PlayController#pause}, {@link PlayController#rewind},
589816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * {@link PlayController#fastForward} and {@link PlayController#seekTo}
590816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * is called.
591816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * If it is true, the current time is equal to System.currentTimeMillis().
592816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         */
593816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private boolean mIsPlayOffsetChanged;
594816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
595816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        PlayController(TunableTvView tvView) {
596816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mTvView = tvView;
597816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mTvView.setTimeShiftListener(new TimeShiftListener() {
598816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                @Override
599816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                public void onAvailabilityChanged() {
600816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // Do not send the notifications while the availability is changing,
601816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // because the variables are in the intermediate state.
602816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // For example, the current program can be null.
603816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mNotificationEnabled = false;
604816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mDisplayedPlaySpeed = PLAY_SPEED_1X;
605816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mPlaybackSpeed = 1;
606816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mPlayDirection = PLAY_DIRECTION_FORWARD;
607816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mIsPlayOffsetChanged = false;
608816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mPossibleStartTimeMs = System.currentTimeMillis();
609816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mRecordStartTimeMs = mPossibleStartTimeMs;
610816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mCurrentPositionMediator.initialize(mPossibleStartTimeMs);
611816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mHandler.removeMessages(MSG_GET_CURRENT_POSITION);
612816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
613816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (isAvailable()) {
614816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        // When the media availability message has come.
615816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        mPlayController.setPlayStatus(PLAY_STATUS_PLAYING);
616816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        mHandler.sendEmptyMessageDelayed(MSG_GET_CURRENT_POSITION,
617816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                                REQUEST_CURRENT_POSITION_INTERVAL);
618816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    } else {
619816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        // When the tune command is sent.
620816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        mPlayController.setPlayStatus(PLAY_STATUS_PAUSED);
621816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
622816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    TimeShiftManager.this.onAvailabilityChanged();
623816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mNotificationEnabled = true;
624816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
625816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
626816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                @Override
627816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                public void onRecordStartTimeChanged(long recordStartTimeMs) {
628816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (recordStartTimeMs < mPossibleStartTimeMs) {
629816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        // Do not warn in this case because it can happen in normal cases.
630816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        if (DEBUG) {
631816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            Log.d(TAG, "Record start time is less then the time when it became "
632816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                                    + "available. {availableStartTime="
633816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                                    + Utils.toTimeString(mPossibleStartTimeMs)
634816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                                    + ", recordStartTimeMs=" + Utils.toTimeString(recordStartTimeMs)
635816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                                    + "}");
636816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        }
637816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        recordStartTimeMs = mPossibleStartTimeMs;
638816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
639816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (mRecordStartTimeMs == recordStartTimeMs) {
640816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        return;
641816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
642816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mRecordStartTimeMs = recordStartTimeMs;
643816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    TimeShiftManager.this.onRecordStartTimeChanged();
644816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
645816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // According to the UX guidelines, the stream should be resumed if the
646816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // recording buffer fills up while paused, which means that the current time
647816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // position is the same as or before the recording start time.
648816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // But, for this application and the TIS, it's an erroneous and confusing
649816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // situation if the current time position is before the recording start time.
650816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // So, we recommend the TIS to keep the current time position greater than or
651816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // equal to the recording start time.
652816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // And here, we assume that the buffer is full if the current time position
653816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // is nearly equal to the recording start time.
654816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (mPlayStatus == PLAY_STATUS_PAUSED &&
655816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            getCurrentPositionMs() - mRecordStartTimeMs
656816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            < RECORDING_BOUNDARY_THRESHOLD) {
657816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        TimeShiftManager.this.play();
658816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
659816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
660816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            });
661816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
662816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
663816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        boolean isAvailable() {
664816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return mTvView.isTimeShiftAvailable() && !mTvView.isScreenBlocked();
665816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
666816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
667816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void handleGetCurrentPosition() {
668816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mIsPlayOffsetChanged) {
669816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                long currentTimeMs = System.currentTimeMillis();
670816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                long currentPositionMs = Math.max(
671816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        Math.min(mTvView.timeshiftGetCurrentPositionMs(), currentTimeMs),
672816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        mRecordStartTimeMs);
673816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                boolean isCurrentTime =
674816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        currentTimeMs - currentPositionMs < RECORDING_BOUNDARY_THRESHOLD;
675816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                long newCurrentPositionMs;
676816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (isCurrentTime && isForwarding()) {
677816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // It's playing forward and the current playing position reached
678816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // the current system time. i.e. The live stream is played.
679816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // Therefore no need to call TvView.timeshiftGetCurrentPositionMs
680816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // any more.
681816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    newCurrentPositionMs = currentTimeMs;
682816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mIsPlayOffsetChanged = false;
683816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (mDisplayedPlaySpeed > PLAY_SPEED_1X) {
684816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        TimeShiftManager.this.play();
685816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
686816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                } else {
687816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    newCurrentPositionMs = currentPositionMs;
688816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    boolean isRecordStartTime = currentPositionMs - mRecordStartTimeMs
689816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            < RECORDING_BOUNDARY_THRESHOLD;
690816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (isRecordStartTime && isRewinding()) {
691816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        TimeShiftManager.this.play();
692816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
693816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
694816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                setCurrentPositionMs(newCurrentPositionMs);
695816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            } else {
696816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                setCurrentPositionMs(System.currentTimeMillis());
697816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                TimeShiftManager.this.onCurrentPositionChanged();
698816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
699816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            // Need to send message here just in case there is no or invalid response
700816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            // for the current time position request from TIS.
701816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mHandler.sendEmptyMessageDelayed(MSG_GET_CURRENT_POSITION,
702816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    REQUEST_CURRENT_POSITION_INTERVAL);
703816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
704816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
705816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void play() {
706816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mDisplayedPlaySpeed = PLAY_SPEED_1X;
707816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mPlaybackSpeed = 1;
708816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mPlayDirection = PLAY_DIRECTION_FORWARD;
709816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mTvView.timeshiftPlay();
710816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            setPlayStatus(PLAY_STATUS_PLAYING);
711816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
712816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
713816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void pause() {
714816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mDisplayedPlaySpeed = PLAY_SPEED_1X;
715816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mPlaybackSpeed = 1;
716816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mTvView.timeshiftPause();
717816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            setPlayStatus(PLAY_STATUS_PAUSED);
718816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mIsPlayOffsetChanged = true;
719816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
720816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
721816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void togglePlayPause() {
722816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mPlayStatus == PLAY_STATUS_PAUSED) {
723816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                play();
724816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            } else {
725816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                pause();
726816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
727816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
728816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
729816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void rewind() {
730816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mPlayDirection == PLAY_DIRECTION_BACKWARD) {
731816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                increaseDisplayedPlaySpeed();
732816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            } else {
733816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mDisplayedPlaySpeed = PLAY_SPEED_2X;
734816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
735816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mPlayDirection = PLAY_DIRECTION_BACKWARD;
736816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mPlaybackSpeed = getPlaybackSpeed();
737816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mTvView.timeshiftRewind(mPlaybackSpeed);
738816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            setPlayStatus(PLAY_STATUS_PLAYING);
739816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mIsPlayOffsetChanged = true;
740816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
741816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
742816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void fastForward() {
743816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mPlayDirection == PLAY_DIRECTION_FORWARD) {
744816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                increaseDisplayedPlaySpeed();
745816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            } else {
746816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mDisplayedPlaySpeed = PLAY_SPEED_2X;
747816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
748816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mPlayDirection = PLAY_DIRECTION_FORWARD;
749816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mPlaybackSpeed = getPlaybackSpeed();
750816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mTvView.timeshiftFastForward(mPlaybackSpeed);
751816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            setPlayStatus(PLAY_STATUS_PLAYING);
752816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mIsPlayOffsetChanged = true;
753816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
754816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
755816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        /**
756816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * Moves to the specified time.
757816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         */
758816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void seekTo(long timeMs) {
759816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mTvView.timeshiftSeekTo(Math.min(System.currentTimeMillis(),
760816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    Math.max(mRecordStartTimeMs, timeMs)));
761816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mIsPlayOffsetChanged = true;
762816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
763816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
764816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void onCurrentProgramChanged() {
765816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            // Update playback speed
766816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mDisplayedPlaySpeed == PLAY_SPEED_1X) {
767816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return;
768816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
769816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            int playbackSpeed = getPlaybackSpeed();
770816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (playbackSpeed != mPlaybackSpeed) {
771816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mPlaybackSpeed = playbackSpeed;
772816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (mPlayDirection == PLAY_DIRECTION_FORWARD) {
773816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mTvView.timeshiftFastForward(mPlaybackSpeed);
774816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                } else {
775816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mTvView.timeshiftRewind(mPlaybackSpeed);
776816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
777816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
778816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
779816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
780816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private void increaseDisplayedPlaySpeed() {
781816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            switch (mDisplayedPlaySpeed) {
782816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                case PLAY_SPEED_1X:
783816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mDisplayedPlaySpeed = PLAY_SPEED_2X;
784816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    break;
785816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                case PLAY_SPEED_2X:
786816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mDisplayedPlaySpeed = PLAY_SPEED_3X;
787816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    break;
788816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                case PLAY_SPEED_3X:
789816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mDisplayedPlaySpeed = PLAY_SPEED_4X;
790816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    break;
791816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                case PLAY_SPEED_4X:
792816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mDisplayedPlaySpeed = PLAY_SPEED_5X;
793816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    break;
794816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
795816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
796816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
797816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private void setPlayStatus(@PlayStatus int status) {
798816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mPlayStatus = status;
799816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            TimeShiftManager.this.onPlayStatusChanged(status);
800816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
801816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
802816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        boolean isForwarding() {
803816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return mPlayStatus == PLAY_STATUS_PLAYING && mPlayDirection == PLAY_DIRECTION_FORWARD;
804816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
805816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
806816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private boolean isRewinding() {
807816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return mPlayStatus == PLAY_STATUS_PLAYING && mPlayDirection == PLAY_DIRECTION_BACKWARD;
808816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
809816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
810816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        Channel getCurrentChannel() {
811816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return mTvView.getCurrentChannel();
812816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
813816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
814816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
815816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private class ProgramManager {
816816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private final ProgramDataManager mProgramDataManager;
817816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private Channel mChannel;
818816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private final List<Program> mPrograms = new ArrayList<>();
819816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private final Queue<Range<Long>> mProgramLoadQueue = new LinkedList<>();
820816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private LoadProgramsForCurrentChannelTask mProgramLoadTask = null;
821816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
822816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        ProgramManager(ProgramDataManager programDataManager) {
823816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mProgramDataManager = programDataManager;
824816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
825816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
826816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void onAvailabilityChanged(boolean available, Channel channel, long currentPositionMs) {
827816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mProgramLoadQueue.clear();
828816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mProgramLoadTask != null) {
829816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mProgramLoadTask.cancel(true);
830816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
831816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mHandler.removeMessages(MSG_PREFETCH_PROGRAM);
832816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mPrograms.clear();
833816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mChannel = channel;
834816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (channel == null || channel.isPassthrough()) {
835816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return;
836816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
837816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (available) {
838816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                Program program = mProgramDataManager.getCurrentProgram(channel.getId());
839816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                long prefetchStartTimeMs;
840816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (program != null) {
841816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mPrograms.add(program);
842816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    prefetchStartTimeMs = program.getEndTimeUtcMillis();
843816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                } else {
844816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    prefetchStartTimeMs = Utils.floorTime(currentPositionMs,
845816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            MAX_DUMMY_PROGRAM_DURATION);
846816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
847816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                // Create dummy program
848816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mPrograms.addAll(createDummyPrograms(prefetchStartTimeMs,
849816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        currentPositionMs + PREFETCH_DURATION_FOR_NEXT));
850816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                schedulePrefetchPrograms();
851816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                TimeShiftManager.this.onProgramInfoChanged();
852816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
853816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
854816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
855816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void onRecordStartTimeChanged(long startTimeMs) {
856816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mChannel == null || mChannel.isPassthrough()) {
857816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return;
858816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
859816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            long currentMs = System.currentTimeMillis();
860816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
861816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            long fetchStartTimeMs = Utils.floorTime(startTimeMs, MAX_DUMMY_PROGRAM_DURATION);
862816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            boolean needToLoad = addDummyPrograms(fetchStartTimeMs,
863816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    currentMs + PREFETCH_DURATION_FOR_NEXT);
864816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (needToLoad) {
865816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                Range<Long> period = Range.create(fetchStartTimeMs, currentMs);
866816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mProgramLoadQueue.add(period);
867816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                startTaskIfNeeded();
868816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
869816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
870816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
871816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private void startTaskIfNeeded() {
872816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mProgramLoadQueue.isEmpty()) {
873816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return;
874816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
875816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mProgramLoadTask == null || mProgramLoadTask.isCancelled()) {
876816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                startNext();
877816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            } else {
878816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                switch (mProgramLoadTask.getStatus()) {
879816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    case PENDING:
880816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        if (mProgramLoadTask.overlaps(mProgramLoadQueue)) {
881816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            if (mProgramLoadTask.cancel(true)) {
882816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                                mProgramLoadQueue.add(mProgramLoadTask.getPeriod());
883816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                                mProgramLoadTask = null;
884816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                                startNext();
885816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            }
886816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        }
887816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        break;
888816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    case RUNNING:
889816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        // Remove pending task fully satisfied by the current
890816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        Range<Long> current = mProgramLoadTask.getPeriod();
891816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        Iterator<Range<Long>> i = mProgramLoadQueue.iterator();
892816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        while (i.hasNext()) {
893816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            Range<Long> r = i.next();
894816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            if (current.contains(r)) {
895816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                                i.remove();
896816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            }
897816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        }
898816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        break;
899816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    case FINISHED:
900816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        // The task should have already cleared it self, clear and restart anyways.
901816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        Log.w(TAG, mProgramLoadTask + " is finished, but was not cleared");
902816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        startNext();
903816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        break;
904816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
905816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
906816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
907816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
908816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private void startNext() {
909816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mProgramLoadTask = null;
910816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mProgramLoadQueue.isEmpty()) {
911816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return;
912816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
913816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
914816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            Range<Long> next = mProgramLoadQueue.poll();
915816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            // Extend next to include any overlapping Ranges.
916816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            Iterator<Range<Long>> i = mProgramLoadQueue.iterator();
917816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            while(i.hasNext()) {
918816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                Range<Long> r = i.next();
919816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if(next.contains(r.getLower()) || next.contains(r.getUpper())){
920816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    i.remove();
921816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    next = next.extend(r);
922816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
923816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
924816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mChannel != null) {
925816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mProgramLoadTask = new LoadProgramsForCurrentChannelTask(
926816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        mContext.getContentResolver(), next);
927816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mProgramLoadTask.executeOnDbThread();
928816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
929816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
930816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
931816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void addDummyProgramsAt(long timeMs) {
932816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            addDummyPrograms(timeMs, timeMs + PREFETCH_DURATION_FOR_NEXT);
933816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
934816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
935816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private boolean addDummyPrograms(Range<Long> period) {
936816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return addDummyPrograms(period.getLower(), period.getUpper());
937816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
938816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
939816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private boolean addDummyPrograms(long startTimeMs, long endTimeMs) {
940816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            boolean added = false;
941816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mPrograms.isEmpty()) {
942816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                // Insert dummy program.
943816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mPrograms.addAll(createDummyPrograms(startTimeMs, endTimeMs));
944816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return true;
945816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
946816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            // Insert dummy program to the head of the list if needed.
947816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            Program firstProgram = mPrograms.get(0);
948816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (startTimeMs < firstProgram.getStartTimeUtcMillis()) {
949816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (!firstProgram.isValid()) {
950816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // Already the firstProgram is dummy.
951816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mPrograms.remove(0);
952816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mPrograms.addAll(0,
953816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            createDummyPrograms(startTimeMs, firstProgram.getEndTimeUtcMillis()));
954816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                } else {
955816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mPrograms.addAll(0,
956816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            createDummyPrograms(startTimeMs, firstProgram.getStartTimeUtcMillis()));
957816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
958816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                added = true;
959816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
960816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            // Insert dummy program to the tail of the list if needed.
961816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            Program lastProgram = mPrograms.get(mPrograms.size() - 1);
962816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (endTimeMs > lastProgram.getEndTimeUtcMillis()) {
963816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (!lastProgram.isValid()) {
964816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // Already the lastProgram is dummy.
965816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mPrograms.remove(mPrograms.size() - 1);
966816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mPrograms.addAll(
967816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            createDummyPrograms(lastProgram.getStartTimeUtcMillis(), endTimeMs));
968816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                } else {
969816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mPrograms.addAll(
970816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            createDummyPrograms(lastProgram.getEndTimeUtcMillis(), endTimeMs));
971816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
972816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                added = true;
973816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
974816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            // Insert dummy programs if the holes exist in the list.
975816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            for (int i = 1; i < mPrograms.size(); ++i) {
976816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                long endOfPrevious = mPrograms.get(i - 1).getEndTimeUtcMillis();
977816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                long startOfCurrent = mPrograms.get(i).getStartTimeUtcMillis();
978816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (startOfCurrent > endOfPrevious) {
979816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    List<Program> dummyPrograms =
980816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            createDummyPrograms(endOfPrevious, startOfCurrent);
981816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mPrograms.addAll(i, dummyPrograms);
982816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    i += dummyPrograms.size();
983816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    added = true;
984816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
985816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
986816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return added;
987816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
988816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
989816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private void removeDummyPrograms() {
990816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            for (int i = 0; i < mPrograms.size(); ++i) {
991816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                Program program = mPrograms.get(i);
992816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (!program.isValid()) {
993816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mPrograms.remove(i--);
994816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
995816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
996816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
997816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
998816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private void removeOverlappedPrograms(List<Program> loadedPrograms) {
999816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mPrograms.size() == 0) {
1000816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return;
1001816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1002816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            Program program = mPrograms.get(0);
1003816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            for (int i = 0, j = 0; i < mPrograms.size() && j < loadedPrograms.size(); ++j) {
1004816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                Program loadedProgram = loadedPrograms.get(j);
1005816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                // Skip previous programs.
1006816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                while (program.getEndTimeUtcMillis() < loadedProgram.getStartTimeUtcMillis()) {
1007816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    // Reached end of mPrograms.
1008816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (++i == mPrograms.size()) {
1009816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        return;
1010816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
1011816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    program = mPrograms.get(i);
1012816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
1013816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                // Remove overlapped programs.
1014816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                while (program.getStartTimeUtcMillis() < loadedProgram.getEndTimeUtcMillis()
1015816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        && program.getEndTimeUtcMillis() > loadedProgram.getStartTimeUtcMillis()) {
1016816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mPrograms.remove(i);
1017816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (i >= mPrograms.size()) {
1018816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        break;
1019816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
1020816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    program = mPrograms.get(i);
1021816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
1022816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1023816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
1024816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1025816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        // Returns a list of dummy programs.
1026816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        // The maximum duration of a dummy program is {@link MAX_DUMMY_PROGRAM_DURATION}.
1027816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        // So if the duration ({@code endTimeMs}-{@code startTimeMs}) is greater than the duration,
1028816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        // we need to create multiple dummy programs.
1029816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        // The reason of the limitation of the duration is because we want the trick play viewer
1030816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        // to show the time-line duration of {@link MAX_DUMMY_PROGRAM_DURATION} at most
1031816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        // for a dummy program.
1032816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private List<Program> createDummyPrograms(long startTimeMs, long endTimeMs) {
1033816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (startTimeMs >= endTimeMs) {
1034816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return Collections.emptyList();
1035816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1036816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            List<Program> programs = new ArrayList<>();
1037816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            long start = startTimeMs;
1038816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            long end = Utils.ceilTime(startTimeMs, MAX_DUMMY_PROGRAM_DURATION);
1039816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            while (end < endTimeMs) {
1040816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                programs.add(new Program.Builder()
1041816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        .setStartTimeUtcMillis(start)
1042816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        .setEndTimeUtcMillis(end)
1043816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        .build());
1044816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                start = end;
1045816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                end += MAX_DUMMY_PROGRAM_DURATION;
1046816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1047816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            programs.add(new Program.Builder()
1048816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    .setStartTimeUtcMillis(start)
1049816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    .setEndTimeUtcMillis(endTimeMs)
1050816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    .build());
1051816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return programs;
1052816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
1053816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1054816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        Program getProgramAt(long timeMs) {
1055816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return getProgramAt(timeMs, 0, mPrograms.size() - 1);
1056816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
1057816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1058816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private Program getProgramAt(long timeMs, int start, int end) {
1059816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (start > end) {
1060816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return null;
1061816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1062816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            int mid = (start + end) / 2;
1063816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            Program program = mPrograms.get(mid);
1064816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (program.getStartTimeUtcMillis() > timeMs) {
1065816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return getProgramAt(timeMs, start, mid - 1);
1066816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            } else if (program.getEndTimeUtcMillis() <= timeMs) {
1067816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return getProgramAt(timeMs, mid+1, end);
1068816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            } else {
1069816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return program;
1070816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1071816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
1072816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1073816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private long getOldestProgramStartTime() {
1074816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mPrograms.isEmpty()) {
1075816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return INVALID_TIME;
1076816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1077816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return mPrograms.get(0).getStartTimeUtcMillis();
1078816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
1079816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1080816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private Program getLastValidProgram() {
1081816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            for (int i = mPrograms.size() - 1; i >= 0; --i) {
1082816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                Program program = mPrograms.get(i);
1083816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (program.isValid()) {
1084816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    return program;
1085816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
1086816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1087816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return null;
1088816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
1089816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1090816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private void schedulePrefetchPrograms() {
1091816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (DEBUG) Log.d(TAG, "Scheduling prefetching programs.");
1092816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mHandler.hasMessages(MSG_PREFETCH_PROGRAM)) {
1093816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return;
1094816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1095816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            Program lastValidProgram = getLastValidProgram();
1096816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (DEBUG) Log.d(TAG, "Last valid program = " + lastValidProgram);
1097816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (lastValidProgram != null) {
1098816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                long delay = lastValidProgram.getEndTimeUtcMillis()
1099816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        - PREFETCH_TIME_OFFSET_FROM_PROGRAM_END - System.currentTimeMillis();
1100816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mHandler.sendEmptyMessageDelayed(MSG_PREFETCH_PROGRAM, delay);
1101816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (DEBUG) Log.d(TAG, "Scheduling with " + delay + "(ms) delays.");
1102816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            } else {
1103816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mHandler.sendEmptyMessage(MSG_PREFETCH_PROGRAM);
1104816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (DEBUG) Log.d(TAG, "Scheduling promptly.");
1105816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1106816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
1107816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1108816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private void prefetchPrograms() {
1109816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            long startTimeMs;
1110816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            Program lastValidProgram = getLastValidProgram();
1111816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (lastValidProgram == null) {
1112816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                startTimeMs = System.currentTimeMillis();
1113816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            } else {
1114816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                startTimeMs = lastValidProgram.getEndTimeUtcMillis();
1115816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1116816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            long endTimeMs = System.currentTimeMillis() + PREFETCH_DURATION_FOR_NEXT;
1117816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (DEBUG) {
1118816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                Log.d(TAG, "Prefetch task starts: {startTime=" + Utils.toTimeString(startTimeMs)
1119816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        + ", endTime=" + Utils.toTimeString(endTimeMs) + "}");
1120816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1121816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mProgramLoadQueue.add(Range.create(startTimeMs, endTimeMs));
1122816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            startTaskIfNeeded();
1123816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
1124816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1125816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private class LoadProgramsForCurrentChannelTask
1126816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                extends AsyncDbTask.LoadProgramsForChannelTask {
1127816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1128816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            public LoadProgramsForCurrentChannelTask(ContentResolver contentResolver,
1129816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    Range<Long> period) {
1130816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                super(contentResolver, mChannel.getId(), period);
1131816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1132816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1133816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            @Override
1134816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            protected void onPostExecute(List<Program> programs) {
1135816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (DEBUG) {
1136816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    Log.d(TAG, "Programs are loaded {channelId=" + mChannelId +
1137816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            ", from=" + Utils.toTimeString(mPeriod.getLower()) +
1138816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            ", to=" + Utils.toTimeString(mPeriod.getUpper()) +
1139816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            "}");
1140816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
1141816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                //remove pending tasks that are fully satisfied by this query.
1142816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                Iterator<Range<Long>> it = mProgramLoadQueue.iterator();
1143816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                while (it.hasNext()) {
1144816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    Range<Long> r = it.next();
1145816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (mPeriod.contains(r)) {
1146816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        it.remove();
1147816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
1148816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
1149816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (programs == null || programs.isEmpty() || mPrograms.isEmpty()) {
1150816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mPrograms.addAll(programs);
1151816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (addDummyPrograms(mPeriod)) {
1152816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        TimeShiftManager.this.onProgramInfoChanged();
1153816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
1154816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    schedulePrefetchPrograms();
1155816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    startNextLoadingIfNeeded();
1156816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    return;
1157816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
1158816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                removeDummyPrograms();
1159816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                removeOverlappedPrograms(programs);
1160816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                Program loadedProgram = programs.get(0);
1161816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                for (int i = 0; i < mPrograms.size() && !programs.isEmpty(); ++i) {
1162816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    Program program = mPrograms.get(i);
1163816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    while (program.getStartTimeUtcMillis() > loadedProgram
1164816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            .getStartTimeUtcMillis()) {
1165816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        mPrograms.add(i++, loadedProgram);
1166816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        programs.remove(0);
1167816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        if (programs.isEmpty()) {
1168816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            break;
1169816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        }
1170816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        loadedProgram = programs.get(0);
1171816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
1172816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
1173816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mPrograms.addAll(programs);
1174816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                addDummyPrograms(mPeriod);
1175816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                TimeShiftManager.this.onProgramInfoChanged();
1176816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                schedulePrefetchPrograms();
1177816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                startNextLoadingIfNeeded();
1178816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1179816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1180816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            @Override
1181816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            protected void onCancelled(List<Program> programs) {
1182816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (DEBUG) {
1183816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    Log.d(TAG, "Program loading has been canceled {channelId=" + (mChannel == null
1184816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            ? "null" : mChannelId) + ", from=" + Utils
1185816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            .toTimeString(mPeriod.getLower()) + ", to=" + Utils
1186816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            .toTimeString(mPeriod.getUpper()) + "}");
1187816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
1188816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                startNextLoadingIfNeeded();
1189816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1190816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1191816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            private void startNextLoadingIfNeeded() {
1192816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mProgramLoadTask = null;
1193816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                // Need to post to handler, because the task is still running.
1194816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mHandler.post(new Runnable() {
1195816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    @Override
1196816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    public void run() {
1197816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        startTaskIfNeeded();
1198816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
1199816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                });
1200816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1201816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1202816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            public boolean overlaps(Queue<Range<Long>> programLoadQueue) {
1203816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                for (Range<Long> r : programLoadQueue) {
1204816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (mPeriod.contains(r.getLower()) || mPeriod.contains(r.getUpper())) {
1205816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        return true;
1206816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
1207816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
1208816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return false;
1209816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1210816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
1211816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
1212816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1213816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @VisibleForTesting
1214816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    final class CurrentPositionMediator {
1215816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        long mCurrentPositionMs;
1216816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        long mSeekRequestTimeMs;
1217816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1218816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void initialize(long timeMs) {
1219816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mSeekRequestTimeMs = INVALID_TIME;
1220816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mCurrentPositionMs = timeMs;
1221816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            TimeShiftManager.this.onCurrentPositionChanged();
1222816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
1223816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1224816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void onSeekRequested(long seekTimeMs) {
1225816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mSeekRequestTimeMs = System.currentTimeMillis();
1226816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mCurrentPositionMs = seekTimeMs;
1227816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            TimeShiftManager.this.onCurrentPositionChanged();
1228816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
1229816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1230816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void onCurrentPositionChanged(long currentPositionMs) {
1231816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mSeekRequestTimeMs == INVALID_TIME) {
1232816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mCurrentPositionMs = currentPositionMs;
1233816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                TimeShiftManager.this.onCurrentPositionChanged();
1234816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return;
1235816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1236816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            long currentTimeMs = System.currentTimeMillis();
1237816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            boolean isValid = Math.abs(currentPositionMs - mCurrentPositionMs) < REQUEST_TIMEOUT_MS;
1238816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            boolean isTimeout = currentTimeMs > mSeekRequestTimeMs + REQUEST_TIMEOUT_MS;
1239816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (isValid || isTimeout) {
1240816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                initialize(currentPositionMs);
1241816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            } else {
1242816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (getPlayStatus() == PLAY_STATUS_PLAYING) {
1243816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (getPlayDirection() == PLAY_DIRECTION_FORWARD) {
1244816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        mCurrentPositionMs += (currentTimeMs - mSeekRequestTimeMs)
1245816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                                * getPlaybackSpeed();
1246816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    } else {
1247816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        mCurrentPositionMs -= (currentTimeMs - mSeekRequestTimeMs)
1248816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                                * getPlaybackSpeed();
1249816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
1250816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
1251816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                TimeShiftManager.this.onCurrentPositionChanged();
1252816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
1253816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
1254816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
1255816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1256816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
1257816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * The listener used to receive the events by the time-shift manager
1258816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
1259816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public interface Listener {
1260816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        /**
1261816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * Called when the availability of the time-shift for the current channel has been changed.
1262816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * If the time shift is available, {@link TimeShiftManager#getRecordStartTimeMs} should
1263816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * return the valid time.
1264816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         */
1265816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void onAvailabilityChanged();
1266816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1267816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        /**
1268816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * Called when the play status is changed between {@link #PLAY_STATUS_PLAYING} and
1269816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * {@link #PLAY_STATUS_PAUSED}
1270816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         *
1271816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * @param status The new play state.
1272816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         */
1273816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void onPlayStatusChanged(int status);
1274816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1275816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        /**
1276816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * Called when the recordStartTime has been changed.
1277816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         */
1278816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void onRecordStartTimeChanged();
1279816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1280816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        /**
1281816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * Called when the current position is changed.
1282816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         */
1283816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void onCurrentPositionChanged();
1284816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1285816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        /**
1286816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * Called when the program information is updated.
1287816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         */
1288816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void onProgramInfoChanged();
1289816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1290816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        /**
1291816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * Called when an action becomes enabled or disabled.
1292816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         */
1293816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void onActionEnabledChanged(@TimeShiftActionId int actionId, boolean enabled);
1294816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
1295816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko}
1296