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 
17 package com.android.providers.tv;
18 
19 import android.app.IntentService;
20 import android.content.Intent;
21 import android.database.Cursor;
22 import android.media.tv.TvContract.Programs;
23 import android.media.tv.TvContract.WatchedPrograms;
24 import android.text.format.DateUtils;
25 import android.util.Log;
26 
27 import com.android.internal.annotations.VisibleForTesting;
28 
29 import java.util.concurrent.TimeUnit;
30 
31 /**
32  * A service that cleans up EPG data.
33  */
34 public class EpgDataCleanupService extends IntentService {
35     private static final boolean DEBUG = true;
36     private static final String TAG = "EpgDataCleanupService";
37 
38     static final String ACTION_CLEAN_UP_EPG_DATA =
39             "com.android.providers.tv.intent.CLEAN_UP_EPG_DATA";
40 
EpgDataCleanupService()41     public EpgDataCleanupService() {
42         super("EpgDataCleanupService");
43     }
44 
45     @Override
onHandleIntent(Intent intent)46     protected void onHandleIntent(Intent intent) {
47         if (DEBUG) {
48             Log.d(TAG, "Received intent: " + intent);
49         }
50         final String action = intent.getAction();
51         if (!ACTION_CLEAN_UP_EPG_DATA.equals(action)) {
52             return;
53         }
54 
55         long nowMillis = System.currentTimeMillis();
56 
57         int maxProgramAgeInDays = getResources().getInteger(R.integer.max_program_age_in_days);
58         if (maxProgramAgeInDays > 0) {
59             clearOldPrograms(nowMillis - TimeUnit.DAYS.toMillis(maxProgramAgeInDays));
60         }
61 
62         int maxWatchedProgramAgeInDays =
63                 getResources().getInteger(R.integer.max_watched_program_age_in_days);
64         if (maxWatchedProgramAgeInDays > 0) {
65             clearOldWatchHistory(nowMillis - TimeUnit.DAYS.toMillis(maxWatchedProgramAgeInDays));
66         }
67 
68         int maxWatchedProgramEntryCount =
69                 getResources().getInteger(R.integer.max_watched_program_entry_count);
70         if (maxWatchedProgramEntryCount > 0) {
71             clearOverflowWatchHistory(maxWatchedProgramEntryCount);
72         }
73     }
74 
75     /**
76      * Clear program info that ended before {@code maxEndTimeMillis}.
77      */
78     @VisibleForTesting
clearOldPrograms(long maxEndTimeMillis)79     void clearOldPrograms(long maxEndTimeMillis) {
80         int deleteCount = getContentResolver().delete(
81                 Programs.CONTENT_URI,
82                 Programs.COLUMN_END_TIME_UTC_MILLIS + "<?",
83                 new String[] { String.valueOf(maxEndTimeMillis) });
84         if (DEBUG && deleteCount > 0) {
85             Log.d(TAG, "Deleted " + deleteCount + " programs"
86                   + " (reason: ended before "
87                   + DateUtils.getRelativeTimeSpanString(this, maxEndTimeMillis) + ")");
88         }
89     }
90 
91     /**
92      * Clear watch history whose watch started before {@code maxStartTimeMillis}.
93      * In theory, history entry for currently watching program can be deleted
94      * (e.g., have been watching since before {@code maxStartTimeMillis}).
95      */
96     @VisibleForTesting
clearOldWatchHistory(long maxStartTimeMillis)97     void clearOldWatchHistory(long maxStartTimeMillis) {
98         int deleteCount = getContentResolver().delete(
99                 WatchedPrograms.CONTENT_URI,
100                 WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS + "<?",
101                 new String[] { String.valueOf(maxStartTimeMillis) });
102         if (DEBUG && deleteCount > 0) {
103             Log.d(TAG, "Deleted " + deleteCount + " watched programs"
104                   + " (reason: started before "
105                   + DateUtils.getRelativeTimeSpanString(this, maxStartTimeMillis) + ")");
106         }
107     }
108 
109     /**
110      * Clear watch history except last {@code maxEntryCount} entries.
111      * "Last" here is based on watch start time, and so, in theory, history entry for program
112      * that user was watching until recent reboot can be deleted earlier than other entries
113      * which ended before.
114      */
115     @VisibleForTesting
clearOverflowWatchHistory(int maxEntryCount)116     void clearOverflowWatchHistory(int maxEntryCount) {
117         Cursor cursor = getContentResolver().query(
118                 WatchedPrograms.CONTENT_URI,
119                 new String[] { WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS }, null, null,
120                 WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS);
121         if (cursor == null) {
122             Log.e(TAG, "Failed to query watched program");
123             return;
124         }
125         int totalCount;
126         long maxStartTimeMillis;
127         try {
128             totalCount = cursor.getCount();
129             int overflowCount = totalCount - maxEntryCount;
130             if (overflowCount <= 0) {
131                 return;
132             }
133             if (!cursor.moveToPosition(overflowCount - 1)) {
134                 Log.e(TAG, "Failed to query watched program");
135                 return;
136             }
137             maxStartTimeMillis = cursor.getLong(0);
138         } finally {
139             cursor.close();
140         }
141 
142         int deleteCount = getContentResolver().delete(
143                 WatchedPrograms.CONTENT_URI,
144                 WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS + "<?",
145                 new String[] { String.valueOf(maxStartTimeMillis + 1) });
146         if (DEBUG && deleteCount > 0) {
147             Log.d(TAG, "Deleted " + deleteCount + " of " + totalCount + " watched programs"
148                   + " (reason: entry count > " + maxEntryCount + ")");
149         }
150     }
151 }
152