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