1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.providers.tv; 18 19import android.app.IntentService; 20import android.content.Intent; 21import android.database.Cursor; 22import android.media.tv.TvContract.Programs; 23import android.media.tv.TvContract.WatchedPrograms; 24import android.os.SystemClock; 25import android.text.format.DateUtils; 26import android.util.Log; 27 28import com.android.internal.annotations.VisibleForTesting; 29 30import java.util.concurrent.TimeUnit; 31 32/** 33 * A service that cleans up EPG data. 34 */ 35public class EpgDataCleanupService extends IntentService { 36 private static final boolean DEBUG = true; 37 private static final String TAG = "EpgDataCleanupService"; 38 39 static final String ACTION_CLEAN_UP_EPG_DATA = 40 "com.android.providers.tv.intent.CLEAN_UP_EPG_DATA"; 41 42 public EpgDataCleanupService() { 43 super("EpgDataCleanupService"); 44 } 45 46 @Override 47 protected void onHandleIntent(Intent intent) { 48 if (DEBUG) { 49 Log.d(TAG, "Received intent: " + intent); 50 } 51 final String action = intent.getAction(); 52 if (!ACTION_CLEAN_UP_EPG_DATA.equals(action)) { 53 return; 54 } 55 56 long nowMillis = System.currentTimeMillis(); 57 58 int maxProgramAgeInDays = getResources().getInteger(R.integer.max_program_age_in_days); 59 if (maxProgramAgeInDays > 0) { 60 clearOldPrograms(nowMillis - TimeUnit.DAYS.toMillis(maxProgramAgeInDays)); 61 } 62 63 int maxWatchedProgramAgeInDays = 64 getResources().getInteger(R.integer.max_watched_program_age_in_days); 65 if (maxWatchedProgramAgeInDays > 0) { 66 clearOldWatchHistory(nowMillis - TimeUnit.DAYS.toMillis(maxWatchedProgramAgeInDays)); 67 } 68 69 int maxWatchedProgramEntryCount = 70 getResources().getInteger(R.integer.max_watched_program_entry_count); 71 if (maxWatchedProgramEntryCount > 0) { 72 clearOverflowWatchHistory(maxWatchedProgramEntryCount); 73 } 74 } 75 76 /** 77 * Clear program info that ended before {@code maxEndTimeMillis}. 78 */ 79 @VisibleForTesting 80 void clearOldPrograms(long maxEndTimeMillis) { 81 int deleteCount = getContentResolver().delete( 82 Programs.CONTENT_URI, 83 Programs.COLUMN_END_TIME_UTC_MILLIS + "<?", 84 new String[] { String.valueOf(maxEndTimeMillis) }); 85 if (DEBUG && deleteCount > 0) { 86 Log.d(TAG, "Deleted " + deleteCount + " programs" 87 + " (reason: ended before " 88 + DateUtils.getRelativeTimeSpanString(this, maxEndTimeMillis) + ")"); 89 } 90 } 91 92 /** 93 * Clear watch history whose watch started before {@code maxStartTimeMillis}. 94 * In theory, history entry for currently watching program can be deleted 95 * (e.g., have been watching since before {@code maxStartTimeMillis}). 96 */ 97 @VisibleForTesting 98 void clearOldWatchHistory(long maxStartTimeMillis) { 99 int deleteCount = getContentResolver().delete( 100 WatchedPrograms.CONTENT_URI, 101 WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS + "<?", 102 new String[] { String.valueOf(maxStartTimeMillis) }); 103 if (DEBUG && deleteCount > 0) { 104 Log.d(TAG, "Deleted " + deleteCount + " watched programs" 105 + " (reason: started before " 106 + DateUtils.getRelativeTimeSpanString(this, maxStartTimeMillis) + ")"); 107 } 108 } 109 110 /** 111 * Clear watch history except last {@code maxEntryCount} entries. 112 * "Last" here is based on watch start time, and so, in theory, history entry for program 113 * that user was watching until recent reboot can be deleted earlier than other entries 114 * which ended before. 115 */ 116 @VisibleForTesting 117 void clearOverflowWatchHistory(int maxEntryCount) { 118 Cursor cursor = getContentResolver().query( 119 WatchedPrograms.CONTENT_URI, 120 new String[] { WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS }, null, null, 121 WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS); 122 if (cursor == null) { 123 Log.e(TAG, "Failed to query watched program"); 124 return; 125 } 126 int totalCount; 127 long maxStartTimeMillis; 128 try { 129 totalCount = cursor.getCount(); 130 int overflowCount = totalCount - maxEntryCount; 131 if (overflowCount <= 0) { 132 return; 133 } 134 if (!cursor.moveToPosition(overflowCount - 1)) { 135 Log.e(TAG, "Failed to query watched program"); 136 return; 137 } 138 maxStartTimeMillis = cursor.getLong(0); 139 } finally { 140 cursor.close(); 141 } 142 143 int deleteCount = getContentResolver().delete( 144 WatchedPrograms.CONTENT_URI, 145 WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS + "<?", 146 new String[] { String.valueOf(maxStartTimeMillis + 1) }); 147 if (DEBUG && deleteCount > 0) { 148 Log.d(TAG, "Deleted " + deleteCount + " of " + totalCount + " watched programs" 149 + " (reason: entry count > " + maxEntryCount + ")"); 150 } 151 } 152} 153