1 /*
2  * Copyright (C) 2017 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.tv.data.epg;
18 
19 /** Tests for {@link EpgFetcher}. */
20 import static com.google.common.truth.Truth.assertThat;
21 
22 import android.content.ContentResolver;
23 import android.content.ContentValues;
24 import android.database.sqlite.SQLiteDatabase;
25 import android.media.tv.TvContract;
26 
27 import androidx.tvprovider.media.tv.Channel;
28 
29 import com.android.tv.common.CommonPreferences;
30 import com.android.tv.common.buildtype.HasBuildType.BuildType;
31 import com.android.tv.common.flags.impl.DefaultBackendKnobsFlags;
32 import com.android.tv.common.flags.impl.SettableFlagsModule;
33 import com.android.tv.common.util.PostalCodeUtils;
34 import com.android.tv.data.ChannelDataManager;
35 import com.android.tv.features.TvFeatures;
36 import com.android.tv.perf.PerformanceMonitor;
37 import com.android.tv.perf.stub.StubPerformanceMonitor;
38 import com.android.tv.testing.DbTestingUtils;
39 import com.android.tv.testing.EpgTestData;
40 import com.android.tv.testing.FakeEpgReader;
41 import com.android.tv.testing.FakeTvInputManagerHelper;
42 import com.android.tv.testing.TestSingletonApp;
43 import com.android.tv.testing.constants.ConfigConstants;
44 import com.android.tv.testing.fakes.FakeClock;
45 import com.android.tv.testing.fakes.FakeTvProvider;
46 import com.android.tv.testing.robo.ContentProviders;
47 
48 import com.google.android.tv.livechannels.epg.provider.EpgContentProvider;
49 import com.google.android.tv.partner.support.EpgContract;
50 import com.google.common.collect.ImmutableList;
51 import com.android.tv.common.flags.proto.TypedFeatures.StringListParam;
52 
53 import dagger.Component;
54 import dagger.Module;
55 import dagger.android.AndroidInjectionModule;
56 import dagger.android.AndroidInjector;
57 import dagger.android.DispatchingAndroidInjector;
58 import dagger.android.HasAndroidInjector;
59 
60 import org.junit.After;
61 import org.junit.Before;
62 import org.junit.Test;
63 import org.junit.runner.RunWith;
64 import org.robolectric.RobolectricTestRunner;
65 import org.robolectric.RuntimeEnvironment;
66 import org.robolectric.Shadows;
67 import org.robolectric.android.util.concurrent.RoboExecutorService;
68 import org.robolectric.annotation.Config;
69 
70 import java.util.List;
71 import java.util.concurrent.ExecutionException;
72 import java.util.concurrent.TimeUnit;
73 
74 import javax.inject.Inject;
75 
76 /** Tests for {@link EpgFetcherImpl}. */
77 @RunWith(RobolectricTestRunner.class)
78 @Config(sdk = ConfigConstants.SDK, application = EpgFetcherImplTest.TestApp.class)
79 public class EpgFetcherImplTest {
80 
81     /** TestApp for {@link EpgFetcherImplTest} */
82     public static class TestApp extends TestSingletonApp implements HasAndroidInjector {
83         @Inject DispatchingAndroidInjector<Object> dispatchingAndroidInjector;
84 
85         @Override
onCreate()86         public void onCreate() {
87             super.onCreate();
88             DaggerEpgFetcherImplTest_TestAppComponent.builder()
89                     .settableFlagsModule(flagsModule)
90                     .build()
91                     .inject(this);
92         }
93 
94         @Override
androidInjector()95         public AndroidInjector<Object> androidInjector() {
96             return dispatchingAndroidInjector;
97         }
98     }
99 
100     /** Component for {@link EpgFetcherImplTest} */
101     @Component(
102             modules = {
103                 AndroidInjectionModule.class,
104                 TestModule.class,
105                 EpgContentProvider.Module.class
106             })
107     interface TestAppComponent extends AndroidInjector<TestApp> {}
108 
109     /** Module for {@link EpgFetcherImplTest} */
110     @Module(includes = {SettableFlagsModule.class})
111     public static class TestModule {}
112 
113     private static final String[] PROGRAM_COLUMNS = {
114         TvContract.Programs.COLUMN_CHANNEL_ID,
115         TvContract.Programs.COLUMN_TITLE,
116         TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
117         TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS
118     };
119 
120     private static final String[] CHANNEL_COLUMNS = {
121         TvContract.Channels.COLUMN_DISPLAY_NAME,
122         TvContract.Channels.COLUMN_DISPLAY_NUMBER,
123         TvContract.Channels.COLUMN_NETWORK_AFFILIATION
124     };
125 
126     private FakeClock mFakeClock;
127     private EpgFetcherImpl mEpgFetcher;
128     private ChannelDataManager mChannelDataManager;
129     private FakeEpgReader mEpgReader;
130     private PerformanceMonitor mPerformanceMonitor = new StubPerformanceMonitor();
131     private ContentResolver mContentResolver;
132     private FakeTvProvider mTvProvider;
133     private EpgContentProvider mEpgProvider;
134     private EpgContentProvider.EpgDatabaseHelper mDatabaseHelper;
135     private TestApp mTestApp;
136 
137     @Before
setup()138     public void setup() {
139 
140         TvFeatures.CLOUD_EPG_FOR_3RD_PARTY.enableForTest();
141         mTestApp = (TestApp) RuntimeEnvironment.application;
142         Shadows.shadowOf(RuntimeEnvironment.application)
143                 .grantPermissions("com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA");
144         mDatabaseHelper = new EpgContentProvider.EpgDatabaseHelper(RuntimeEnvironment.application);
145         CommonPreferences.initialize(RuntimeEnvironment.application);
146         PostalCodeUtils.setLastPostalCode(RuntimeEnvironment.application, "90210");
147         EpgFetchHelper.setLastLineupId(RuntimeEnvironment.application, "test90210");
148         mTvProvider = ContentProviders.register(FakeTvProvider.class, TvContract.AUTHORITY);
149         mEpgProvider = ContentProviders.register(EpgContentProvider.class, EpgContract.AUTHORITY);
150         mEpgProvider.setCallingPackage_("com.google.android.tv");
151         mFakeClock = FakeClock.createWithCurrentTime();
152         FakeTvInputManagerHelper fakeTvInputManagerHelper =
153                 new FakeTvInputManagerHelper(RuntimeEnvironment.application);
154         mContentResolver = RuntimeEnvironment.application.getContentResolver();
155         mChannelDataManager =
156                 new ChannelDataManager(
157                         RuntimeEnvironment.application,
158                         fakeTvInputManagerHelper,
159                         new RoboExecutorService(),
160                         mContentResolver);
161         fakeTvInputManagerHelper.start();
162         mChannelDataManager.start();
163         mEpgReader = new FakeEpgReader(mFakeClock);
164         mEpgFetcher =
165                 new EpgFetcherImpl(
166                         RuntimeEnvironment.application,
167                         new EpgInputWhiteList(
168                                 mTestApp.flagsModule.cloudEpgFlags,
169                                 mTestApp.flagsModule.legacyFlags),
170                         mChannelDataManager,
171                         mEpgReader,
172                         mPerformanceMonitor,
173                         mFakeClock,
174                         new DefaultBackendKnobsFlags(),
175                         BuildType.NO_JNI_TEST);
176         EpgTestData.DATA_90210.loadData(mFakeClock, mEpgReader); // This also sets fake clock
177         EpgFetchHelper.setLastEpgUpdatedTimestamp(
178                 RuntimeEnvironment.application,
179                 mFakeClock.currentTimeMillis() - TimeUnit.DAYS.toMillis(1));
180     }
181 
182     @After
after()183     public void after() {
184         mChannelDataManager.stop();
185         TvFeatures.CLOUD_EPG_FOR_3RD_PARTY.resetForTests();
186     }
187 
188     @Test
fetchImmediately_nochannels()189     public void fetchImmediately_nochannels() throws ExecutionException, InterruptedException {
190         EpgFetcherImpl.FetchAsyncTask fetcherTask = mEpgFetcher.createFetchTask(null, null);
191         fetcherTask.execute();
192 
193         assertThat(fetcherTask.get()).isEqualTo(EpgFetcherImpl.REASON_NO_BUILT_IN_CHANNELS);
194         List<List<String>> rows =
195                 DbTestingUtils.toList(
196                         mContentResolver.query(
197                                 TvContract.Programs.CONTENT_URI,
198                                 PROGRAM_COLUMNS,
199                                 null,
200                                 null,
201                                 null));
202         assertThat(rows).isEmpty();
203     }
204 
205     @Test
fetchImmediately_testChannel()206     public void fetchImmediately_testChannel() throws ExecutionException, InterruptedException {
207         // The channels must be in the app package.
208         // For this test the package is com.android.tv.data.epg
209         insertTestChannels(
210                 "com.android.tv.data.epg/.tuner.TunerTvInputService", EpgTestData.CHANNEL_10);
211         EpgFetcherImpl.FetchAsyncTask fetcherTask = mEpgFetcher.createFetchTask(null, null);
212         fetcherTask.execute();
213 
214         assertThat(fetcherTask.get()).isNull();
215         List<List<String>> rows =
216                 DbTestingUtils.toList(
217                         mContentResolver.query(
218                                 TvContract.Programs.CONTENT_URI,
219                                 PROGRAM_COLUMNS,
220                                 null,
221                                 null,
222                                 null));
223         assertThat(rows)
224                 .containsExactly(
225                         ImmutableList.of("1", "Program 1", "1496358000000", "1496359800000"));
226     }
227 
228     @Test
fetchImmediately_epgChannel()229     public void fetchImmediately_epgChannel() throws ExecutionException, InterruptedException {
230         mTestApp.flagsModule.cloudEpgFlags.setThirdPartyEpgInput(
231                 StringListParam.newBuilder().addElement("com.example/.Input").build());
232         insertTestChannels("com.example/.Input", EpgTestData.CHANNEL_10, EpgTestData.CHANNEL_11);
233         createTestEpgInput();
234         EpgFetcherImpl.FetchAsyncTask fetcherTask = mEpgFetcher.createFetchTask(null, null);
235         fetcherTask.execute();
236 
237         assertThat(fetcherTask.get()).isNull();
238         List<List<String>> rows =
239                 DbTestingUtils.toList(
240                         mContentResolver.query(
241                                 TvContract.Programs.CONTENT_URI,
242                                 PROGRAM_COLUMNS,
243                                 null,
244                                 null,
245                                 null));
246         assertThat(rows)
247                 .containsExactly(
248                         ImmutableList.of("1", "Program 1", "1496358000000", "1496359800000"),
249                         ImmutableList.of("2", "Program 2", "1496359800000", "1496361600000"));
250     }
251 
252     @Test
testUpdateNetworkAffiliation()253     public void testUpdateNetworkAffiliation() throws ExecutionException, InterruptedException {
254         if (!TvFeatures.STORE_NETWORK_AFFILIATION.isEnabled(RuntimeEnvironment.application)) {
255             return;
256         }
257         // set network affiliation to null so that it can be updated later
258         Channel channel =
259                 new Channel.Builder(EpgTestData.CHANNEL_10).setNetworkAffiliation(null).build();
260         // The channels must be in the app package.
261         // For this test the package is com.android.tv.data.epg
262         insertTestChannels("com.android.tv.data.epg/.tuner.TunerTvInputService", channel);
263 
264         List<List<String>> rows =
265                 DbTestingUtils.toList(
266                         mContentResolver.query(
267                                 TvContract.Channels.CONTENT_URI,
268                                 CHANNEL_COLUMNS,
269                                 null,
270                                 null,
271                                 null));
272         assertThat(rows).containsExactly(ImmutableList.of("Channel TEN", "10", "null"));
273         EpgFetcherImpl.FetchAsyncTask fetcherTask = mEpgFetcher.createFetchTask(null, null);
274         fetcherTask.execute();
275 
276         assertThat(fetcherTask.get()).isNull();
277         rows =
278                 DbTestingUtils.toList(
279                         mContentResolver.query(
280                                 TvContract.Channels.CONTENT_URI,
281                                 CHANNEL_COLUMNS,
282                                 null,
283                                 null,
284                                 null));
285         // network affiliation should be updated
286         assertThat(rows)
287                 .containsExactly(
288                         ImmutableList.of("Channel TEN", "10", "Channel 10 Network Affiliation"));
289     }
290 
insertTestChannels(String inputId, Channel... channels)291     protected void insertTestChannels(String inputId, Channel... channels) {
292 
293         for (Channel channel : channels) {
294             ContentValues values =
295                     new Channel.Builder(channel).setInputId(inputId).build().toContentValues();
296             String packageName = inputId.substring(0, inputId.indexOf('/'));
297             mTvProvider.setCallingPackage(packageName);
298             mContentResolver.insert(TvContract.Channels.CONTENT_URI, values);
299             mTvProvider.setCallingPackage("com.android.tv");
300         }
301     }
302 
createTestEpgInput()303     private void createTestEpgInput() {
304         // Use the database helper so we can set the package name.
305         SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
306         ContentValues values = new ContentValues();
307         values.put(EpgContract.EpgInputs.COLUMN_ID, "1");
308         values.put(EpgContract.EpgInputs.COLUMN_PACKAGE_NAME, "com.example");
309         values.put(EpgContract.EpgInputs.COLUMN_INPUT_ID, "com.example/.Input");
310         values.put(EpgContract.EpgInputs.COLUMN_LINEUP_ID, "lineup1");
311         long rowId = db.insert("epg_input", null, values);
312         assertThat(rowId).isEqualTo(1);
313     }
314 }
315