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}