16ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko/*
26ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko * Copyright (C) 2017 The Android Open Source Project
36ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko *
46ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko * Licensed under the Apache License, Version 2.0 (the "License");
56ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko * you may not use this file except in compliance with the License.
66ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko * You may obtain a copy of the License at
76ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko *
86ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko *      http://www.apache.org/licenses/LICENSE-2.0
96ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko *
106ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko * Unless required by applicable law or agreed to in writing, software
116ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko * distributed under the License is distributed on an "AS IS" BASIS,
126ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
136ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko * See the License for the specific language governing permissions and
146ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko * limitations under the License.
156ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko */
166ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
176ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkopackage com.android.tv.data.epg;
186ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
196ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport android.content.ContentProviderOperation;
206ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport android.content.Context;
216ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport android.content.OperationApplicationException;
226ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport android.database.Cursor;
236ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport android.media.tv.TvContract;
246ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport android.media.tv.TvContract.Programs;
256ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport android.os.RemoteException;
266ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport android.preference.PreferenceManager;
276ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport android.support.annotation.WorkerThread;
286ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport android.text.TextUtils;
296ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport android.util.Log;
306ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
316ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport com.android.tv.data.Program;
326ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
336ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport java.util.ArrayList;
346ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport java.util.Collections;
356ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport java.util.List;
366ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport java.util.concurrent.TimeUnit;
376ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
386ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko/** The helper class for {@link com.android.tv.data.epg.EpgFetcher} */
396ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoclass EpgFetchHelper {
406ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final String TAG = "EpgFetchHelper";
416ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final boolean DEBUG = false;
426ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
436ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final long PROGRAM_QUERY_DURATION_MS = TimeUnit.DAYS.toMillis(30);
446ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final int BATCH_OPERATION_COUNT = 100;
456ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
466ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    // Value: Long
476ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final String KEY_LAST_UPDATED_EPG_TIMESTAMP =
486ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            "com.android.tv.data.epg.EpgFetcher.LastUpdatedEpgTimestamp";
496ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    // Value: String
506ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final String KEY_LAST_LINEUP_ID =
516ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            "com.android.tv.data.epg.EpgFetcher.LastLineupId";
526ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
536ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static long sLastEpgUpdatedTimestamp = -1;
546ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static String sLastLineupId;
556ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
566ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private EpgFetchHelper() { }
576ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
586ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    /**
596ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * Updates newly fetched EPG data for the given channel to local providers. The method will
606ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * compare the broadcasting time and try to match each newly fetched program with old programs
616ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * of that channel in the database one by one. It will update the matched old program, or insert
626ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * the new program if there is no matching program can be found in the database and at the same
636ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * time remove those old programs which conflicts with the inserted one.
646ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
656ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * @param channelId the target channel ID.
666ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * @param fetchedPrograms the newly fetched program data.
676ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * @return {@code true} if new program data are successfully updated. Otherwise {@code false}.
686ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     */
696ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    static boolean updateEpgData(Context context, long channelId, List<Program> fetchedPrograms) {
706ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        final int fetchedProgramsCount = fetchedPrograms.size();
716ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        if (fetchedProgramsCount == 0) {
726ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            return false;
736ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
746ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        boolean updated = false;
756ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        long startTimeMs = System.currentTimeMillis();
766ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        long endTimeMs = startTimeMs + PROGRAM_QUERY_DURATION_MS;
776ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        List<Program> oldPrograms = queryPrograms(context, channelId, startTimeMs, endTimeMs);
786ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        int oldProgramsIndex = 0;
796ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        int newProgramsIndex = 0;
806ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
816ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        // Compare the new programs with old programs one by one and update/delete the old one
826ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        // or insert new program if there is no matching program in the database.
836ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        ArrayList<ContentProviderOperation> ops = new ArrayList<>();
846ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        while (newProgramsIndex < fetchedProgramsCount) {
856ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            Program oldProgram = oldProgramsIndex < oldPrograms.size()
866ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    ? oldPrograms.get(oldProgramsIndex) : null;
876ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            Program newProgram = fetchedPrograms.get(newProgramsIndex);
886ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            boolean addNewProgram = false;
896ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            if (oldProgram != null) {
906ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                if (oldProgram.equals(newProgram)) {
916ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    // Exact match. No need to update. Move on to the next programs.
926ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    oldProgramsIndex++;
936ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    newProgramsIndex++;
946ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                } else if (hasSameTitleAndOverlap(oldProgram, newProgram)) {
956ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    // Partial match. Update the old program with the new one.
966ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    // NOTE: Use 'update' in this case instead of 'insert' and 'delete'. There
976ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    // could be application specific settings which belong to the old program.
986ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    ops.add(ContentProviderOperation.newUpdate(
996ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                            TvContract.buildProgramUri(oldProgram.getId()))
1006ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                            .withValues(Program.toContentValues(newProgram))
1016ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                            .build());
1026ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    oldProgramsIndex++;
1036ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    newProgramsIndex++;
1046ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                } else if (oldProgram.getEndTimeUtcMillis() < newProgram.getEndTimeUtcMillis()) {
1056ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    // No match. Remove the old program first to see if the next program in
1066ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    // {@code oldPrograms} partially matches the new program.
1076ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    ops.add(ContentProviderOperation.newDelete(
1086ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                            TvContract.buildProgramUri(oldProgram.getId()))
1096ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                            .build());
1106ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    oldProgramsIndex++;
1116ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                } else {
1126ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    // No match. The new program does not match any of the old programs. Insert
1136ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    // it as a new program.
1146ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    addNewProgram = true;
1156ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    newProgramsIndex++;
1166ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                }
1176ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            } else {
1186ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                // No old programs. Just insert new programs.
1196ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                addNewProgram = true;
1206ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                newProgramsIndex++;
1216ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            }
1226ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            if (addNewProgram) {
1236ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                ops.add(ContentProviderOperation
1246ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        .newInsert(Programs.CONTENT_URI)
1256ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        .withValues(Program.toContentValues(newProgram))
1266ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        .build());
1276ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            }
1286ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            // Throttle the batch operation not to cause TransactionTooLargeException.
1296ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            if (ops.size() > BATCH_OPERATION_COUNT || newProgramsIndex >= fetchedProgramsCount) {
1306ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                try {
1316ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    if (DEBUG) {
1326ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        int size = ops.size();
1336ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        Log.d(TAG, "Running " + size + " operations for channel " + channelId);
1346ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        for (int i = 0; i < size; ++i) {
1356ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                            Log.d(TAG, "Operation(" + i + "): " + ops.get(i));
1366ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        }
1376ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    }
1386ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    context.getContentResolver().applyBatch(TvContract.AUTHORITY, ops);
1396ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    updated = true;
1406ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                } catch (RemoteException | OperationApplicationException e) {
1416ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    Log.e(TAG, "Failed to insert programs.", e);
1426ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    return updated;
1436ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                }
1446ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                ops.clear();
1456ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            }
1466ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
1476ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        if (DEBUG) {
1486ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            Log.d(TAG, "Updated " + fetchedProgramsCount + " programs for channel " + channelId);
1496ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
1506ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        return updated;
1516ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    }
1526ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
1536ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static List<Program> queryPrograms(Context context, long channelId,
1546ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            long startTimeMs, long endTimeMs) {
1556ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        try (Cursor c = context.getContentResolver().query(
1566ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                TvContract.buildProgramsUriForChannel(channelId, startTimeMs, endTimeMs),
1576ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                Program.PROJECTION, null, null, Programs.COLUMN_START_TIME_UTC_MILLIS)) {
1586ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            if (c == null) {
1596ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                return Collections.emptyList();
1606ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            }
1616ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            ArrayList<Program> programs = new ArrayList<>();
1626ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            while (c.moveToNext()) {
1636ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                programs.add(Program.fromCursor(c));
1646ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            }
1656ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            return programs;
1666ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
1676ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    }
1686ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
1696ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    /**
1706ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * Returns {@code true} if the {@code oldProgram} needs to be updated with the
1716ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * {@code newProgram}.
1726ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     */
1736ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static boolean hasSameTitleAndOverlap(Program oldProgram, Program newProgram) {
1746ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        // NOTE: Here, we update the old program if it has the same title and overlaps with the
1756ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        // new program. The test logic is just an example and you can modify this. E.g. check
1766ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        // whether the both programs have the same program ID if your EPG supports any ID for
1776ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        // the programs.
1786ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        return TextUtils.equals(oldProgram.getTitle(), newProgram.getTitle())
1796ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                && oldProgram.getStartTimeUtcMillis() <= newProgram.getEndTimeUtcMillis()
1806ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                && newProgram.getStartTimeUtcMillis() <= oldProgram.getEndTimeUtcMillis();
1816ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    }
1826ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
1836ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    /**
1846ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * Sets the last known lineup ID into shared preferences for future usage. If channels are not
1856ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * re-scanned, EPG fetcher can directly use this value instead of checking the correct lineup ID
1866ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * every time when it needs to fetch EPG data.
1876ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     */
1886ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    @WorkerThread
1896ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    synchronized static void setLastLineupId(Context context, String lineupId) {
1906ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        if (DEBUG) {
1916ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            if (lineupId == null) {
1926ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                Log.d(TAG, "Clear stored lineup id: " + sLastLineupId);
1936ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            }
1946ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
1956ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        sLastLineupId = lineupId;
1966ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        PreferenceManager.getDefaultSharedPreferences(context).edit()
1976ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                .putString(KEY_LAST_LINEUP_ID, lineupId).apply();
1986ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    }
1996ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
2006ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    /**
2016ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * Gets the last known lineup ID from shared preferences.
2026ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     */
2036ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    synchronized static String getLastLineupId(Context context) {
2046ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        if (sLastLineupId == null) {
2056ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            sLastLineupId = PreferenceManager.getDefaultSharedPreferences(context)
2066ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    .getString(KEY_LAST_LINEUP_ID, null);
2076ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
2086ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        if (DEBUG) Log.d(TAG, "Last lineup is " + sLastLineupId);
2096ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        return sLastLineupId;
2106ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    }
2116ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
2126ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    /**
2136ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * Sets the last updated timestamp of EPG data into shared preferences. If the EPG data is not
2146ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * out-dated, it's not necessary for EPG fetcher to fetch EPG again.
2156ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     */
2166ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    @WorkerThread
2176ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    synchronized static void setLastEpgUpdatedTimestamp(Context context, long timestamp) {
2186ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        sLastEpgUpdatedTimestamp = timestamp;
2196ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        PreferenceManager.getDefaultSharedPreferences(context).edit().putLong(
2206ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                KEY_LAST_UPDATED_EPG_TIMESTAMP, timestamp).apply();
2216ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    }
2226ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
2236ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    /**
2246ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * Gets the last updated timestamp of EPG data.
2256ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     */
2266ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    synchronized static long getLastEpgUpdatedTimestamp(Context context) {
2276ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        if (sLastEpgUpdatedTimestamp < 0) {
2286ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            sLastEpgUpdatedTimestamp = PreferenceManager.getDefaultSharedPreferences(context)
2296ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    .getLong(KEY_LAST_UPDATED_EPG_TIMESTAMP, 0);
2306ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
2316ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        return sLastEpgUpdatedTimestamp;
2326ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    }
2336ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko}