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