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.data;
18816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
19816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.content.ContentResolver;
20816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.content.Context;
21816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.database.ContentObserver;
22816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.database.Cursor;
23816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.media.tv.TvContract;
24816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.media.tv.TvContract.Programs;
25816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.net.Uri;
26816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.os.Handler;
27816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.os.Looper;
28816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.os.Message;
296ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport android.support.annotation.AnyThread;
30ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkoimport android.support.annotation.MainThread;
31816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.support.annotation.VisibleForTesting;
322e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalkoimport android.util.ArraySet;
33816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.util.Log;
34816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.util.LongSparseArray;
35816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.util.LruCache;
36816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
37ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkoimport com.android.tv.common.MemoryManageable;
382e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalkoimport com.android.tv.common.SoftPreconditions;
39816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.util.AsyncDbTask;
40816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.util.Clock;
417d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalkoimport com.android.tv.util.MultiLongSparseArray;
42816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.util.Utils;
43816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
44816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.ArrayList;
45816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Collections;
46816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.HashMap;
47816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.HashSet;
48816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.List;
49816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.ListIterator;
50816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Map;
51816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Objects;
52816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Set;
536ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport java.util.concurrent.ConcurrentHashMap;
54816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.concurrent.TimeUnit;
55816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
56ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko@MainThread
57816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkopublic class ProgramDataManager implements MemoryManageable {
58816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final String TAG = "ProgramDataManager";
59816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final boolean DEBUG = false;
60816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
61816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    // To prevent from too many program update operations at the same time, we give random interval
62816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    // between PERIODIC_PROGRAM_UPDATE_MIN_MS and PERIODIC_PROGRAM_UPDATE_MAX_MS.
63816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final long PERIODIC_PROGRAM_UPDATE_MIN_MS = TimeUnit.MINUTES.toMillis(5);
64816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final long PERIODIC_PROGRAM_UPDATE_MAX_MS = TimeUnit.MINUTES.toMillis(10);
65816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final long PROGRAM_PREFETCH_UPDATE_WAIT_MS = TimeUnit.SECONDS.toMillis(5);
66816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    // TODO: need to optimize consecutive DB updates.
67816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final long CURRENT_PROGRAM_UPDATE_WAIT_MS = TimeUnit.SECONDS.toMillis(5);
68816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @VisibleForTesting
69816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    static final long PROGRAM_GUIDE_SNAP_TIME_MS = TimeUnit.MINUTES.toMillis(30);
70816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @VisibleForTesting
71816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    static final long PROGRAM_GUIDE_MAX_TIME_RANGE = TimeUnit.DAYS.toMillis(2);
72816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
73816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    // TODO: Use TvContract constants, once they become public.
74816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final String PARAM_START_TIME = "start_time";
75816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final String PARAM_END_TIME = "end_time";
76816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    // COLUMN_CHANNEL_ID, COLUMN_END_TIME_UTC_MILLIS are added to detect duplicated programs.
77816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    // Duplicated programs are always consecutive by the sorting order.
78816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final String SORT_BY_TIME = Programs.COLUMN_START_TIME_UTC_MILLIS + ", "
79816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            + Programs.COLUMN_CHANNEL_ID + ", " + Programs.COLUMN_END_TIME_UTC_MILLIS;
80816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
81816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final int MSG_UPDATE_CURRENT_PROGRAMS = 1000;
82816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final int MSG_UPDATE_ONE_CURRENT_PROGRAM = 1001;
83816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final int MSG_UPDATE_PREFETCH_PROGRAM = 1002;
84816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
85816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private final Clock mClock;
86816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private final ContentResolver mContentResolver;
87816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private boolean mStarted;
886ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    // Updated only on the main thread.
896ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private volatile boolean mCurrentProgramsLoadFinished;
90816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private ProgramsUpdateTask mProgramsUpdateTask;
91816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private final LongSparseArray<UpdateCurrentProgramForChannelTask> mProgramUpdateTaskMap =
92816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            new LongSparseArray<>();
936ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private final Map<Long, Program> mChannelIdCurrentProgramMap = new ConcurrentHashMap<>();
947d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko    private final MultiLongSparseArray<OnCurrentProgramUpdatedListener>
957d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            mChannelId2ProgramUpdatedListeners = new MultiLongSparseArray<>();
96816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private final Handler mHandler;
972e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    private final Set<Listener> mListeners = new ArraySet<>();
98816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
99816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private final ContentObserver mProgramObserver;
100816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1017d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko    private boolean mPrefetchEnabled;
1027d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko    private long mProgramPrefetchUpdateWaitMs;
1037d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko    private long mLastPrefetchTaskRunMs;
1047d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko    private ProgramsPrefetchTask mProgramsPrefetchTask;
105816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private Map<Long, ArrayList<Program>> mChannelIdProgramCache = new HashMap<>();
106816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
107816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    // Any program that ends prior to this time will be removed from the cache
108816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    // when a channel's current program is updated.
109816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    // Note that there's no limit for end time.
110816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private long mPrefetchTimeRangeStartMs;
111816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
112816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private boolean mPauseProgramUpdate = false;
113816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private final LruCache<Long, Program> mZeroLengthProgramCache = new LruCache<>(10);
1142e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko
1156ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    @MainThread
116816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public ProgramDataManager(Context context) {
1176ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        this(context.getContentResolver(), Clock.SYSTEM, Looper.myLooper());
118816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
119816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
120816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @VisibleForTesting
1216ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    ProgramDataManager(ContentResolver contentResolver, Clock time, Looper looper) {
122816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mClock = time;
123816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mContentResolver = contentResolver;
124816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mHandler = new MyHandler(looper);
125816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mProgramObserver = new ContentObserver(mHandler) {
126816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            @Override
127816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            public void onChange(boolean selfChange) {
128816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (!mHandler.hasMessages(MSG_UPDATE_CURRENT_PROGRAMS)) {
129816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mHandler.sendEmptyMessage(MSG_UPDATE_CURRENT_PROGRAMS);
130816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
131816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (isProgramUpdatePaused()) {
132816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    return;
133816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
1347d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                if (mPrefetchEnabled) {
1357d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                    // The delay time of an existing MSG_UPDATE_PREFETCH_PROGRAM could be quite long
1362e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko                    // up to PROGRAM_GUIDE_SNAP_TIME_MS. So we need to remove the existing message
1372e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko                    // and send MSG_UPDATE_PREFETCH_PROGRAM again.
1387d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                    mHandler.removeMessages(MSG_UPDATE_PREFETCH_PROGRAM);
1397d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                    mHandler.sendEmptyMessage(MSG_UPDATE_PREFETCH_PROGRAM);
1407d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                }
141816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
142816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        };
143816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mProgramPrefetchUpdateWaitMs = PROGRAM_PREFETCH_UPDATE_WAIT_MS;
144816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
145816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
146816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @VisibleForTesting
147816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    ContentObserver getContentObserver() {
148816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return mProgramObserver;
149816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
150816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
151816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
152816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Set the program prefetch update wait which gives the delay to query all programs from DB
153816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * to prevent from too frequent DB queries.
154816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Default value is {@link #PROGRAM_PREFETCH_UPDATE_WAIT_MS}
155816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
156816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @VisibleForTesting
157816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    void setProgramPrefetchUpdateWait(long programPrefetchUpdateWaitMs) {
158816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mProgramPrefetchUpdateWaitMs = programPrefetchUpdateWaitMs;
159816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
160816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
161816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
162816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Starts the manager.
163816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
164816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void start() {
165816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (mStarted) {
166816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return;
167816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
168816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mStarted = true;
169816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        // Should be called directly instead of posting MSG_UPDATE_CURRENT_PROGRAMS message
170816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        // to the handler. If not, another DB task can be executed before loading current programs.
171816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        handleUpdateCurrentPrograms();
1727d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko        if (mPrefetchEnabled) {
1737d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            mHandler.sendEmptyMessage(MSG_UPDATE_PREFETCH_PROGRAM);
1747d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko        }
175816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mContentResolver.registerContentObserver(Programs.CONTENT_URI,
176816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                true, mProgramObserver);
177816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
178816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
179816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
180816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Stops the manager. It clears manager states and runs pending DB operations. Added listeners
181816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * aren't automatically removed by this method.
182816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
1837d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko    @VisibleForTesting
184816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void stop() {
185816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (!mStarted) {
186816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return;
187816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
188816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mStarted = false;
189816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mContentResolver.unregisterContentObserver(mProgramObserver);
190816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mHandler.removeCallbacksAndMessages(null);
191816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
192816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        clearTask(mProgramUpdateTaskMap);
193816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        cancelPrefetchTask();
194816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (mProgramsUpdateTask != null) {
195816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mProgramsUpdateTask.cancel(true);
196816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mProgramsUpdateTask = null;
197816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
198816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
199816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
2006ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    @AnyThread
2016ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    public boolean isCurrentProgramsLoadFinished() {
2026ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        return mCurrentProgramsLoadFinished;
2036ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    }
2046ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
2056ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    /** Returns the current program at the specified channel. */
2066ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    @AnyThread
207816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public Program getCurrentProgram(long channelId) {
208816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return mChannelIdCurrentProgramMap.get(channelId);
209816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
210816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
2116ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    /** Returns all the current programs. */
2126ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    @AnyThread
2136ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    public List<Program> getCurrentPrograms() {
2146ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        return new ArrayList<>(mChannelIdCurrentProgramMap.values());
2156ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    }
2166ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
217816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
2182e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko     * Reloads program data.
2192e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko     */
2202e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    public void reload() {
2212e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        if (!mHandler.hasMessages(MSG_UPDATE_CURRENT_PROGRAMS)) {
2222e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko            mHandler.sendEmptyMessage(MSG_UPDATE_CURRENT_PROGRAMS);
2232e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        }
2242e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        if (mPrefetchEnabled && !mHandler.hasMessages(MSG_UPDATE_PREFETCH_PROGRAM)) {
2252e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko            mHandler.sendEmptyMessage(MSG_UPDATE_PREFETCH_PROGRAM);
2262e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        }
2272e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    }
2282e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko
2292e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    /**
230816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * A listener interface to receive notification on program data retrieval from DB.
231816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
232816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public interface Listener {
233816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        /**
234816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * Called when a Program data is now available through getProgram()
235816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * after the DB operation is done which wasn't before.
236816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * This would be called only if fetched data is around the selected program.
237816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         **/
238816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void onProgramUpdated();
239816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
240816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
241816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
242816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Adds the {@link Listener}.
243816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
244816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void addListener(Listener listener) {
245816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mListeners.add(listener);
246816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
247816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
248816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
249816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Removes the {@link Listener}.
250816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
251816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void removeListener(Listener listener) {
252816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mListeners.remove(listener);
253816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
254816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
255816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
2567d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko     * Enables or Disables program prefetch.
2577d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko     */
2587d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko    public void setPrefetchEnabled(boolean enable) {
2597d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko        if (mPrefetchEnabled == enable) {
2607d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            return;
2617d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko        }
2627d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko        if (enable) {
2637d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            mPrefetchEnabled = true;
2647d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            mLastPrefetchTaskRunMs = 0;
2657d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            if (mStarted) {
2667d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                mHandler.sendEmptyMessage(MSG_UPDATE_PREFETCH_PROGRAM);
2677d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            }
2687d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko        } else {
2697d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            mPrefetchEnabled = false;
2707d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            cancelPrefetchTask();
2717d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            mChannelIdProgramCache.clear();
2727d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            mHandler.removeMessages(MSG_UPDATE_PREFETCH_PROGRAM);
2737d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko        }
2747d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko    }
2757d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko
2767d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko    /**
277816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Returns the programs for the given channel which ends after the given start time.
278816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     *
2797d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko     * <p> Prefetch should be enabled to call it.
2807d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko     *
281816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * @return {@link List} with Programs. It may includes dummy program if the entry needs DB
282816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     *         operations to get.
283816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
284816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public List<Program> getPrograms(long channelId, long startTime) {
2857d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko        SoftPreconditions.checkState(mPrefetchEnabled, TAG, "Prefetch is disabled.");
286816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        ArrayList<Program> cachedPrograms = mChannelIdProgramCache.get(channelId);
287816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (cachedPrograms == null) {
288816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return Collections.emptyList();
289816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
290816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        int startIndex = getProgramIndexAt(cachedPrograms, startTime);
291816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return Collections.unmodifiableList(
292816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                cachedPrograms.subList(startIndex, cachedPrograms.size()));
293816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
294816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
295816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    // Returns the index of program that is played at the specified time.
296816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    // If there isn't, return the first program among programs that starts after the given time
297816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    // if returnNextProgram is {@code true}.
298816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private int getProgramIndexAt(List<Program> programs, long time) {
299816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        Program key = mZeroLengthProgramCache.get(time);
300816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (key == null) {
301816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            key = createDummyProgram(time, time);
302816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mZeroLengthProgramCache.put(time, key);
303816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
304816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        int index = Collections.binarySearch(programs, key);
305816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (index < 0) {
306816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            index = -(index + 1); // change it to index to be added.
307816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (index > 0 && isProgramPlayedAt(programs.get(index - 1), time)) {
308816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                // A program is played at that time.
309816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return index - 1;
310816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
311816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return index;
312816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
313816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return index;
314816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
315816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
316816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private boolean isProgramPlayedAt(Program program, long time) {
317816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return program.getStartTimeUtcMillis() <= time && time <= program.getEndTimeUtcMillis();
318816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
319816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
320816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
321816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Adds the listener to be notified if current program is updated for a channel.
322816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     *
323816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * @param channelId A channel ID to get notified. If it's {@link Channel#INVALID_ID}, the
324816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     *            listener would be called whenever a current program is updated.
325816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
326816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void addOnCurrentProgramUpdatedListener(
327816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            long channelId, OnCurrentProgramUpdatedListener listener) {
3287d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko        mChannelId2ProgramUpdatedListeners
3297d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                .put(channelId, listener);
330816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
331816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
332816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
333816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Removes the listener previously added by
334816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * {@link #addOnCurrentProgramUpdatedListener(long, OnCurrentProgramUpdatedListener)}.
335816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
336816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void removeOnCurrentProgramUpdatedListener(
337816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            long channelId, OnCurrentProgramUpdatedListener listener) {
3387d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko        mChannelId2ProgramUpdatedListeners
3397d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                .remove(channelId, listener);
340816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
341816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
342816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private void notifyCurrentProgramUpdate(long channelId, Program program) {
3437d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko        for (OnCurrentProgramUpdatedListener listener : mChannelId2ProgramUpdatedListeners
3447d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                .get(channelId)) {
3457d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            listener.onCurrentProgramUpdated(channelId, program);
3466ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
3477d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko        for (OnCurrentProgramUpdatedListener listener : mChannelId2ProgramUpdatedListeners
3487d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                .get(Channel.INVALID_ID)) {
3497d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            listener.onCurrentProgramUpdated(channelId, program);
3506ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
351816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
352816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
353816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private void updateCurrentProgram(long channelId, Program program) {
3546ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        Program previousProgram = program == null ? mChannelIdCurrentProgramMap.remove(channelId)
3556ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                : mChannelIdCurrentProgramMap.put(channelId, program);
356816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (!Objects.equals(program, previousProgram)) {
3577d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            if (mPrefetchEnabled) {
3587d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                removePreviousProgramsAndUpdateCurrentProgramInCache(channelId, program);
3597d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            }
360816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            notifyCurrentProgramUpdate(channelId, program);
361816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
362816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
363816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        long delayedTime;
364816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (program == null) {
365816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            delayedTime = PERIODIC_PROGRAM_UPDATE_MIN_MS
366816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    + (long) (Math.random() * (PERIODIC_PROGRAM_UPDATE_MAX_MS
367816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            - PERIODIC_PROGRAM_UPDATE_MIN_MS));
368816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        } else {
369816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            delayedTime = program.getEndTimeUtcMillis() - mClock.currentTimeMillis();
370816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
371816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mHandler.sendMessageDelayed(mHandler.obtainMessage(
372816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                MSG_UPDATE_ONE_CURRENT_PROGRAM, channelId), delayedTime);
373816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
374816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
375816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private void removePreviousProgramsAndUpdateCurrentProgramInCache(
376816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            long channelId, Program currentProgram) {
3777d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko        SoftPreconditions.checkState(mPrefetchEnabled, TAG, "Prefetch is disabled.");
378816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (!Program.isValid(currentProgram)) {
379816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return;
380816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
381816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        ArrayList<Program> cachedPrograms = mChannelIdProgramCache.remove(channelId);
382816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (cachedPrograms == null) {
383816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return;
384816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
385816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        ListIterator<Program> i = cachedPrograms.listIterator();
386816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        while (i.hasNext()) {
387816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            Program cachedProgram = i.next();
388816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (cachedProgram.getEndTimeUtcMillis() <= mPrefetchTimeRangeStartMs) {
389816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                // Remove previous programs which will not be shown in program guide.
390816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                i.remove();
391816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                continue;
392816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
393816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
394816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (cachedProgram.getEndTimeUtcMillis() <= currentProgram
395816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    .getStartTimeUtcMillis()) {
396816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                // Keep the programs that ends earlier than current program
397816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                // but later than mPrefetchTimeRangeStartMs.
398816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                continue;
399816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
400816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
401816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            // Update dummy program around current program if any.
402816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (cachedProgram.getStartTimeUtcMillis() < currentProgram
403816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    .getStartTimeUtcMillis()) {
404816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                // The dummy program starts earlier than the current program. Adjust its end time.
405816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                i.set(createDummyProgram(cachedProgram.getStartTimeUtcMillis(),
406816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        currentProgram.getStartTimeUtcMillis()));
407816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                i.add(currentProgram);
408816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            } else {
409816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                i.set(currentProgram);
410816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
411816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (currentProgram.getEndTimeUtcMillis() < cachedProgram.getEndTimeUtcMillis()) {
412816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                // The dummy program ends later than the current program. Adjust its start time.
413816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                i.add(createDummyProgram(currentProgram.getEndTimeUtcMillis(),
414816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        cachedProgram.getEndTimeUtcMillis()));
415816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
416816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            break;
417816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
4187d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko        if (cachedPrograms.isEmpty()) {
4197d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            // If all the cached programs finish before mPrefetchTimeRangeStartMs, the
4207d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            // currentProgram would not have a chance to be inserted to the cache.
4217d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            cachedPrograms.add(currentProgram);
4227d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko        }
423816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mChannelIdProgramCache.put(channelId, cachedPrograms);
424816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
425816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
426816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private void handleUpdateCurrentPrograms() {
427816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (mProgramsUpdateTask != null) {
428816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mHandler.sendEmptyMessageDelayed(MSG_UPDATE_CURRENT_PROGRAMS,
429816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    CURRENT_PROGRAM_UPDATE_WAIT_MS);
430816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return;
431816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
432816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        clearTask(mProgramUpdateTaskMap);
433816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mHandler.removeMessages(MSG_UPDATE_ONE_CURRENT_PROGRAM);
434816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mProgramsUpdateTask = new ProgramsUpdateTask(mContentResolver, mClock.currentTimeMillis());
435816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mProgramsUpdateTask.executeOnDbThread();
436816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
437816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
438816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private class ProgramsPrefetchTask
439816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            extends AsyncDbTask<Void, Void, Map<Long, ArrayList<Program>>> {
440816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private final long mStartTimeMs;
441816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private final long mEndTimeMs;
442816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
443816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private boolean mSuccess;
444816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
445816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        public ProgramsPrefetchTask() {
446816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            long time = mClock.currentTimeMillis();
447816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mStartTimeMs = Utils
448816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    .floorTime(time - PROGRAM_GUIDE_SNAP_TIME_MS, PROGRAM_GUIDE_SNAP_TIME_MS);
449816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mEndTimeMs = mStartTimeMs + PROGRAM_GUIDE_MAX_TIME_RANGE;
450816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mSuccess = false;
451816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
452816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
453816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        @Override
454816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        protected Map<Long, ArrayList<Program>> doInBackground(Void... params) {
455816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            Map<Long, ArrayList<Program>> programMap = new HashMap<>();
456816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (DEBUG) {
457816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                Log.d(TAG, "Starts programs prefetch. " + Utils.toTimeString(mStartTimeMs) + "-"
458816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        + Utils.toTimeString(mEndTimeMs));
459816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
460816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            Uri uri = Programs.CONTENT_URI.buildUpon()
461816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    .appendQueryParameter(PARAM_START_TIME, String.valueOf(mStartTimeMs))
462816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    .appendQueryParameter(PARAM_END_TIME, String.valueOf(mEndTimeMs)).build();
463816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            final int RETRY_COUNT = 3;
464816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            Program lastReadProgram = null;
465816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            for (int retryCount = RETRY_COUNT; retryCount > 0; retryCount--) {
466816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (isProgramUpdatePaused()) {
467816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    return null;
468816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
469816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                programMap.clear();
470816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                try (Cursor c = mContentResolver.query(uri, Program.PROJECTION, null, null,
471816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        SORT_BY_TIME)) {
472816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (c == null) {
473816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        continue;
474816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
475816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    while (c.moveToNext()) {
47607b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko                        int duplicateCount = 0;
477816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        if (isCancelled()) {
478816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            if (DEBUG) {
479816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                                Log.d(TAG, "ProgramsPrefetchTask canceled.");
480816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            }
481816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            return null;
482816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        }
483816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        Program program = Program.fromCursor(c);
484ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                        if (Program.isDuplicate(program, lastReadProgram)) {
48507b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko                            duplicateCount++;
486816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            continue;
487816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        } else {
488816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            lastReadProgram = program;
489816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        }
490816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        ArrayList<Program> programs = programMap.get(program.getChannelId());
491816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        if (programs == null) {
492816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            programs = new ArrayList<>();
493816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            programMap.put(program.getChannelId(), programs);
494816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        }
495816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        programs.add(program);
49607b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko                        if (duplicateCount > 0) {
49707b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko                            Log.w(TAG, "Found " + duplicateCount + " duplicate programs");
49807b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko                        }
499816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
500816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mSuccess = true;
501816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    break;
502816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                } catch (IllegalStateException e) {
503816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (DEBUG) {
504816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        Log.d(TAG, "Database is changed while querying. Will retry.");
505816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
5067d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                } catch (SecurityException e) {
5077d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                    Log.d(TAG, "Security exception during program data query", e);
508816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
509816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
510816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (DEBUG) {
511816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                Log.d(TAG, "Ends programs prefetch for " + programMap.size() + " channels");
512816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
513816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return programMap;
514816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
515816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
516816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        @Override
517816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        protected void onPostExecute(Map<Long, ArrayList<Program>> programs) {
518816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mProgramsPrefetchTask = null;
519816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (isProgramUpdatePaused()) {
520816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                // ProgramsPrefetchTask will run again once setPauseProgramUpdate(false) is called.
521816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return;
522816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
523816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            long nextMessageDelayedTime;
524816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (mSuccess) {
525816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mChannelIdProgramCache = programs;
526816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                notifyProgramUpdated();
527816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                long currentTime = mClock.currentTimeMillis();
528816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mLastPrefetchTaskRunMs = currentTime;
529816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                nextMessageDelayedTime =
530816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        Utils.floorTime(mLastPrefetchTaskRunMs + PROGRAM_GUIDE_SNAP_TIME_MS,
531816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                                PROGRAM_GUIDE_SNAP_TIME_MS) - currentTime;
532816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            } else {
533816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                nextMessageDelayedTime = PERIODIC_PROGRAM_UPDATE_MIN_MS;
534816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
535816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (!mHandler.hasMessages(MSG_UPDATE_PREFETCH_PROGRAM)) {
536816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mHandler.sendEmptyMessageDelayed(MSG_UPDATE_PREFETCH_PROGRAM,
537816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        nextMessageDelayedTime);
538816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
539816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
540816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
541816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
542816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private void notifyProgramUpdated() {
543816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        for (Listener listener : mListeners) {
544816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            listener.onProgramUpdated();
545816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
546816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
547816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
548816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private class ProgramsUpdateTask extends AsyncDbTask.AsyncQueryTask<List<Program>> {
549816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        public ProgramsUpdateTask(ContentResolver contentResolver, long time) {
550816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            super(contentResolver, Programs.CONTENT_URI.buildUpon()
551816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            .appendQueryParameter(PARAM_START_TIME, String.valueOf(time))
552816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            .appendQueryParameter(PARAM_END_TIME, String.valueOf(time)).build(),
553816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    Program.PROJECTION, null, null, SORT_BY_TIME);
554816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
555816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
556816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        @Override
557816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        public List<Program> onQuery(Cursor c) {
558816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            final List<Program> programs = new ArrayList<>();
559816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (c != null) {
56007b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko                int duplicateCount = 0;
561816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                Program lastReadProgram = null;
562816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                while (c.moveToNext()) {
563816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (isCancelled()) {
564816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        return programs;
565816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
566816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    Program program = Program.fromCursor(c);
567ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                    if (Program.isDuplicate(program, lastReadProgram)) {
56807b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko                        duplicateCount++;
569816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        continue;
570816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    } else {
571816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        lastReadProgram = program;
572816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
573816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    programs.add(program);
574816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
57507b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko                if (duplicateCount > 0) {
57607b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko                    Log.w(TAG, "Found " + duplicateCount + " duplicate programs");
57707b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko                }
578816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
579816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return programs;
580816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
581816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
582816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        @Override
583816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        protected void onPostExecute(List<Program> programs) {
58407b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko            if (DEBUG) Log.d(TAG, "ProgramsUpdateTask done");
585816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mProgramsUpdateTask = null;
5866ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            if (programs != null) {
5876ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                Set<Long> removedChannelIds = new HashSet<>(mChannelIdCurrentProgramMap.keySet());
5886ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                for (Program program : programs) {
5896ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    long channelId = program.getChannelId();
5906ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    updateCurrentProgram(channelId, program);
5916ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    removedChannelIds.remove(channelId);
5926ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                }
5936ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                for (Long channelId : removedChannelIds) {
5946ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    if (mPrefetchEnabled) {
5956ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        mChannelIdProgramCache.remove(channelId);
5966ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    }
5976ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    mChannelIdCurrentProgramMap.remove(channelId);
5986ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    notifyCurrentProgramUpdate(channelId, null);
5997d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                }
600816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
6016ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            mCurrentProgramsLoadFinished = true;
602816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
603816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
604816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
605816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private class UpdateCurrentProgramForChannelTask extends AsyncDbTask.AsyncQueryTask<Program> {
606816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private final long mChannelId;
607816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        private UpdateCurrentProgramForChannelTask(ContentResolver contentResolver, long channelId,
608816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                long time) {
609816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            super(contentResolver, TvContract.buildProgramsUriForChannel(channelId, time, time),
610816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    Program.PROJECTION, null, null, SORT_BY_TIME);
611816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mChannelId = channelId;
612816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
613816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
614816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        @Override
615816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        public Program onQuery(Cursor c) {
616816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            Program program = null;
617816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (c != null && c.moveToNext()) {
618816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                program = Program.fromCursor(c);
619816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
620816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return program;
621816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
622816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
623816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        @Override
624816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        protected void onPostExecute(Program program) {
625816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mProgramUpdateTaskMap.remove(mChannelId);
626816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            updateCurrentProgram(mChannelId, program);
627816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
628816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
629816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
630816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private class MyHandler extends Handler {
631816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        public MyHandler(Looper looper) {
632816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            super(looper);
633816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
634816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
635816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        @Override
636816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        public void handleMessage(Message msg) {
637816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            switch (msg.what) {
638816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                case MSG_UPDATE_CURRENT_PROGRAMS:
639816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    handleUpdateCurrentPrograms();
640816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    break;
641816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                case MSG_UPDATE_ONE_CURRENT_PROGRAM: {
642816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    long channelId = (Long) msg.obj;
643816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    UpdateCurrentProgramForChannelTask oldTask = mProgramUpdateTaskMap
644816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            .get(channelId);
645816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (oldTask != null) {
646816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        oldTask.cancel(true);
647816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
648816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    UpdateCurrentProgramForChannelTask
649816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            task = new UpdateCurrentProgramForChannelTask(
650816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            mContentResolver, channelId, mClock.currentTimeMillis());
651816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mProgramUpdateTaskMap.put(channelId, task);
652816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    task.executeOnDbThread();
653816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    break;
654816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
655816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                case MSG_UPDATE_PREFETCH_PROGRAM: {
656816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (isProgramUpdatePaused()) {
657816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        return;
658816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
659816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (mProgramsPrefetchTask != null) {
660816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        mHandler.sendEmptyMessageDelayed(msg.what, mProgramPrefetchUpdateWaitMs);
661816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        return;
662816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
663816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    long delayMillis = mLastPrefetchTaskRunMs + mProgramPrefetchUpdateWaitMs
664816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            - mClock.currentTimeMillis();
665816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (delayMillis > 0) {
666816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        mHandler.sendEmptyMessageDelayed(MSG_UPDATE_PREFETCH_PROGRAM, delayMillis);
667816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    } else {
668816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        mProgramsPrefetchTask = new ProgramsPrefetchTask();
669816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        mProgramsPrefetchTask.executeOnDbThread();
670816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
671816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    break;
672816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
673816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
674816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
675816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
676816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
677816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
678816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Pause program update.
679816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Updating program data will result in UI refresh,
680816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * but UI is fragile to handle it so we'd better disable it for a while.
6817d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko     *
6827d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko     * <p> Prefetch should be enabled to call it.
683816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
684816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void setPauseProgramUpdate(boolean pauseProgramUpdate) {
6857d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko        SoftPreconditions.checkState(mPrefetchEnabled, TAG, "Prefetch is disabled.");
686816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (mPauseProgramUpdate && !pauseProgramUpdate) {
687816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (!mHandler.hasMessages(MSG_UPDATE_PREFETCH_PROGRAM)) {
688816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                // MSG_UPDATE_PRFETCH_PROGRAM can be empty
689816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                // if prefetch task is launched while program update is paused.
690816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                // Update immediately in that case.
691816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mHandler.sendEmptyMessage(MSG_UPDATE_PREFETCH_PROGRAM);
692816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
693816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
694816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mPauseProgramUpdate = pauseProgramUpdate;
695816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
696816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
697816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private boolean isProgramUpdatePaused() {
698816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        // Although pause is requested, we need to keep updating if cache is empty.
699816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return mPauseProgramUpdate && !mChannelIdProgramCache.isEmpty();
700816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
701816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
702816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
703816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Sets program data prefetch time range.
704816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Any program data that ends before the start time will be removed from the cache later.
705816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Note that there's no limit for end time.
7067d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko     *
7077d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko     * <p> Prefetch should be enabled to call it.
708816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
709816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void setPrefetchTimeRange(long startTimeMs) {
7107d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko        SoftPreconditions.checkState(mPrefetchEnabled, TAG, "Prefetch is disabled.");
711816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (mPrefetchTimeRangeStartMs > startTimeMs) {
7127d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            // Fetch the programs immediately to re-create the cache.
7137d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            if (!mHandler.hasMessages(MSG_UPDATE_PREFETCH_PROGRAM)) {
7147d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                mHandler.sendEmptyMessage(MSG_UPDATE_PREFETCH_PROGRAM);
7157d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            }
716816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
717816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mPrefetchTimeRangeStartMs = startTimeMs;
718816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
719816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
720816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private void clearTask(LongSparseArray<UpdateCurrentProgramForChannelTask> tasks) {
721816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        for (int i = 0; i < tasks.size(); i++) {
722816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            tasks.valueAt(i).cancel(true);
723816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
724816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        tasks.clear();
725816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
726816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
727816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private void cancelPrefetchTask() {
728816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (mProgramsPrefetchTask != null) {
729816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mProgramsPrefetchTask.cancel(true);
730816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mProgramsPrefetchTask = null;
731816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
732816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
733816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
734816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    // Create dummy program which indicates data isn't loaded yet so DB query is required.
735816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private Program createDummyProgram(long startTimeMs, long endTimeMs) {
736816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return new Program.Builder()
737816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                .setChannelId(Channel.INVALID_ID)
738816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                .setStartTimeUtcMillis(startTimeMs)
739816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                .setEndTimeUtcMillis(endTimeMs).build();
740816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
741816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
742816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @Override
743816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void performTrimMemory(int level) {
7447d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko        mChannelId2ProgramUpdatedListeners.clearEmptyCache();
745816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
746816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko}
747