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