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