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 com.google.android.collect.Sets;
20
21import android.content.ContentUris;
22import android.content.ContentValues;
23import android.content.Intent;
24import android.content.pm.ProviderInfo;
25import android.database.Cursor;
26import android.media.tv.TvContract;
27import android.media.tv.TvContract.Channels;
28import android.media.tv.TvContract.Programs;
29import android.media.tv.TvContract.WatchedPrograms;
30import android.net.Uri;
31import android.os.Bundle;
32import android.os.SystemClock;
33import android.provider.Settings;
34import android.test.ServiceTestCase;
35import android.test.mock.MockContentProvider;
36import android.test.mock.MockContentResolver;
37
38import java.util.ArrayList;
39import java.util.Arrays;
40import java.util.Collection;
41import java.util.HashSet;
42import java.util.Objects;
43import java.util.Set;
44
45public class EpgDataCleanupServiceTests extends ServiceTestCase<EpgDataCleanupService> {
46    private static final String FAKE_INPUT_ID = "EpgDataCleanupServiceTests";
47
48    private MockContentResolver mResolver;
49    private TvProvider mProvider;
50
51    public EpgDataCleanupServiceTests() {
52        super(EpgDataCleanupService.class);
53    }
54
55    @Override
56    protected void setUp() throws Exception {
57        super.setUp();
58
59        mResolver = new MockContentResolver();
60        // DateUtils tries to access Settings provider to get date format string.
61        mResolver.addProvider(Settings.AUTHORITY, new MockContentProvider() {
62            @Override
63            public Bundle call(String method, String request, Bundle args) {
64                return new Bundle();
65            }
66        });
67
68        mProvider = new TvProviderForTesting();
69        mResolver.addProvider(TvContract.AUTHORITY, mProvider);
70
71        setContext(new MockTvProviderContext(mResolver, getSystemContext()));
72
73        final ProviderInfo info = new ProviderInfo();
74        info.authority = TvContract.AUTHORITY;
75        mProvider.attachInfoForTesting(getContext(), info);
76
77        startService(new Intent(getContext(), EpgDataCleanupService.class));
78    }
79
80    @Override
81    protected void tearDown() throws Exception {
82        mProvider.shutdown();
83        super.tearDown();
84    }
85
86    private static class Program {
87        long id;
88        final long startTime;
89        final long endTime;
90
91        Program(long startTime, long endTime) {
92            this(-1, startTime, endTime);
93        }
94
95        Program(long id, long startTime, long endTime) {
96            this.id = id;
97            this.startTime = startTime;
98            this.endTime = endTime;
99        }
100
101        @Override
102        public boolean equals(Object obj) {
103            if (!(obj instanceof Program)) {
104                return false;
105            }
106            Program that = (Program) obj;
107            return Objects.equals(id, that.id)
108                    && Objects.equals(startTime, that.startTime)
109                    && Objects.equals(endTime, that.endTime);
110        }
111
112        @Override
113        public int hashCode() {
114            return Objects.hash(id, startTime, endTime);
115        }
116
117        @Override
118        public String toString() {
119            return "Program(id=" + id + ",start=" + startTime + ",end=" + endTime + ")";
120        }
121    }
122
123    private long insertChannel() {
124        ContentValues values = new ContentValues();
125        values.put(Channels.COLUMN_INPUT_ID, FAKE_INPUT_ID);
126        Uri uri = mResolver.insert(Channels.CONTENT_URI, values);
127        assertNotNull(uri);
128        return ContentUris.parseId(uri);
129    }
130
131    private void insertPrograms(Program... programs) {
132        insertPrograms(Arrays.asList(programs));
133    }
134
135    private void insertPrograms(Collection<Program> programs) {
136        long channelId = insertChannel();
137
138        ContentValues values = new ContentValues();
139        values.put(Programs.COLUMN_CHANNEL_ID, channelId);
140        for (Program program : programs) {
141            values.put(Programs.COLUMN_START_TIME_UTC_MILLIS, program.startTime);
142            values.put(Programs.COLUMN_END_TIME_UTC_MILLIS, program.endTime);
143            Uri uri = mResolver.insert(Programs.CONTENT_URI, values);
144            assertNotNull(uri);
145            program.id = ContentUris.parseId(uri);
146        }
147    }
148
149    private Set<Program> queryPrograms() {
150        String[] projection = new String[] {
151            Programs._ID,
152            Programs.COLUMN_START_TIME_UTC_MILLIS,
153            Programs.COLUMN_END_TIME_UTC_MILLIS,
154        };
155
156        Cursor cursor = mResolver.query(Programs.CONTENT_URI, projection, null, null, null);
157        assertNotNull(cursor);
158        try {
159            Set<Program> programs = Sets.newHashSet();
160            while (cursor.moveToNext()) {
161                programs.add(new Program(cursor.getLong(0), cursor.getLong(1), cursor.getLong(2)));
162            }
163            return programs;
164        } finally {
165            cursor.close();
166        }
167    }
168
169    private void insertWatchedPrograms(Program... programs) {
170        insertWatchedPrograms(Arrays.asList(programs));
171    }
172
173    private void insertWatchedPrograms(Collection<Program> programs) {
174        long channelId = insertChannel();
175
176        ContentValues values = new ContentValues();
177        values.put(WatchedPrograms.COLUMN_PACKAGE_NAME, getContext().getPackageName());
178        values.put(WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
179        for (Program program : programs) {
180            values.put(WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS, program.startTime);
181            values.put(WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, program.endTime);
182            Uri uri = mResolver.insert(WatchedPrograms.CONTENT_URI, values);
183            assertNotNull(uri);
184            program.id = ContentUris.parseId(uri);
185        }
186    }
187
188    private Set<Program> queryWatchedPrograms() {
189        String[] projection = new String[] {
190            WatchedPrograms._ID,
191            WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
192            WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS,
193        };
194
195        Cursor cursor = mResolver.query(WatchedPrograms.CONTENT_URI, projection, null, null, null);
196        assertNotNull(cursor);
197        try {
198            Set<Program> programs = Sets.newHashSet();
199            while (cursor.moveToNext()) {
200                programs.add(new Program(cursor.getLong(0), cursor.getLong(1), cursor.getLong(2)));
201            }
202            return programs;
203        } finally {
204            cursor.close();
205        }
206    }
207
208    @Override
209    public void testServiceTestCaseSetUpProperly() throws Exception {
210        assertNotNull(getService());
211    }
212
213    public void testClearOldPrograms() {
214        Program program = new Program(1, 2);
215        insertPrograms(program);
216
217        getService().clearOldPrograms(2);
218        assertEquals("Program should NOT be deleted if it ended at given time.",
219                Sets.newHashSet(program), queryPrograms());
220
221        getService().clearOldPrograms(3);
222        assertTrue("Program should be deleted if it ended before given time.",
223                queryPrograms().isEmpty());
224
225        ArrayList<Program> programs = new ArrayList<Program>();
226        for (int i = 0; i < 10; i++) {
227            programs.add(new Program(999 + i, 1000 + i));
228        }
229        insertPrograms(programs);
230
231        getService().clearOldPrograms(1005);
232        assertEquals("Program should be deleted if and only if it ended before given time.",
233                new HashSet<Program>(programs.subList(5, 10)), queryPrograms());
234    }
235
236    public void testClearOldWatchedPrograms() {
237        Program program = new Program(1, 2);
238        insertWatchedPrograms(program);
239
240        getService().clearOldWatchHistory(1);
241        assertEquals("Watch history should NOT be deleted if watch started at given time.",
242                Sets.newHashSet(program), queryWatchedPrograms());
243
244        getService().clearOldWatchHistory(2);
245        assertTrue("Watch history shuold be deleted if watch started before given time.",
246                queryWatchedPrograms().isEmpty());
247
248        ArrayList<Program> programs = new ArrayList<Program>();
249        for (int i = 0; i < 10; i++) {
250            programs.add(new Program(1000 + i, 1001 + i));
251        }
252        insertWatchedPrograms(programs);
253
254        getService().clearOldWatchHistory(1005);
255        assertEquals("Watch history should be deleted if and only if it started before given time.",
256                new HashSet<Program>(programs.subList(5, 10)), queryWatchedPrograms());
257    }
258
259    public void testClearOverflowWatchHistory() {
260        ArrayList<Program> programs = new ArrayList<Program>();
261        for (int i = 0; i < 10; i++) {
262            programs.add(new Program(1000 + i, 1001 + i));
263        }
264        insertWatchedPrograms(programs);
265
266        getService().clearOverflowWatchHistory(5);
267        assertEquals("Watch history should be deleted in watch start time order.",
268                new HashSet<Program>(programs.subList(5, 10)), queryWatchedPrograms());
269
270        getService().clearOverflowWatchHistory(0);
271        assertTrue("All history should be deleted.", queryWatchedPrograms().isEmpty());
272    }
273}
274