1 /* 2 * Copyright (C) 2015 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; 18 19 import static android.os.Looper.getMainLooper; 20 21 import static com.google.common.truth.Truth.assertThat; 22 import static com.google.common.truth.Truth.assertWithMessage; 23 24 import static org.robolectric.Shadows.shadowOf; 25 26 import android.content.ContentResolver; 27 import android.media.tv.TvContract; 28 29 import com.android.tv.common.flags.impl.DefaultBackendKnobsFlags; 30 import com.android.tv.data.api.Program; 31 import com.android.tv.perf.stub.StubPerformanceMonitor; 32 import com.android.tv.testing.FakeTvInputManagerHelper; 33 import com.android.tv.testing.TestSingletonApp; 34 import com.android.tv.testing.constants.ConfigConstants; 35 import com.android.tv.testing.constants.Constants; 36 import com.android.tv.testing.data.ProgramInfo; 37 import com.android.tv.testing.data.ProgramUtils; 38 import com.android.tv.testing.fakes.FakeClock; 39 import com.android.tv.testing.fakes.FakeTvProvider; 40 import com.android.tv.testing.robo.ContentProviders; 41 import com.android.tv.testing.testdata.TestData; 42 43 import org.junit.After; 44 import org.junit.Before; 45 import org.junit.Test; 46 import org.junit.runner.RunWith; 47 import org.robolectric.RobolectricTestRunner; 48 import org.robolectric.RuntimeEnvironment; 49 import org.robolectric.android.util.concurrent.RoboExecutorService; 50 import org.robolectric.annotation.Config; 51 52 import java.util.List; 53 import java.util.concurrent.CountDownLatch; 54 import java.util.concurrent.TimeUnit; 55 56 /** Test for {@link ProgramDataManager} */ 57 @RunWith(RobolectricTestRunner.class) 58 @Config(sdk = ConfigConstants.SDK, application = TestSingletonApp.class) 59 public class ProgramDataManagerTest { 60 61 // Wait time for expected success. 62 private static final long WAIT_TIME_OUT_MS = 1000L; 63 // Wait time for expected failure. 64 private static final long FAILURE_TIME_OUT_MS = 300L; 65 66 private ProgramDataManager mProgramDataManager; 67 private FakeClock mClock; 68 private TestProgramDataManagerCallback mCallback; 69 70 @Before setUp()71 public void setUp() { 72 mClock = FakeClock.createWithCurrentTime(); 73 mCallback = new TestProgramDataManagerCallback(); 74 ContentProviders.register(FakeTvProvider.class, TvContract.AUTHORITY); 75 TestData.DEFAULT_10_CHANNELS.init( 76 RuntimeEnvironment.application, mClock, TimeUnit.DAYS.toMillis(1)); 77 FakeTvInputManagerHelper tvInputManagerHelper = 78 new FakeTvInputManagerHelper(RuntimeEnvironment.application); 79 RoboExecutorService executor = new RoboExecutorService(); 80 ContentResolver contentResolver = RuntimeEnvironment.application.getContentResolver(); 81 ChannelDataManager channelDataManager = 82 new ChannelDataManager( 83 RuntimeEnvironment.application, 84 tvInputManagerHelper, 85 executor, 86 contentResolver); 87 mProgramDataManager = 88 new ProgramDataManager( 89 RuntimeEnvironment.application, 90 executor, 91 RuntimeEnvironment.application.getContentResolver(), 92 mClock, 93 getMainLooper(), 94 new DefaultBackendKnobsFlags(), 95 new StubPerformanceMonitor(), 96 channelDataManager, 97 tvInputManagerHelper); 98 99 mProgramDataManager.setPrefetchEnabled(true); 100 mProgramDataManager.addCallback(mCallback); 101 } 102 103 @After tearDown()104 public void tearDown() { 105 mProgramDataManager.stop(); 106 } 107 startAndWaitForComplete()108 private void startAndWaitForComplete() throws InterruptedException { 109 mProgramDataManager.start(); 110 shadowOf(getMainLooper()).idle(); 111 assertThat(mCallback.channelUpdatedLatch.await(WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS)) 112 .isTrue(); 113 } 114 115 /** Test for {@link ProgramInfo#getIndex} and {@link ProgramInfo#getStartTimeMs}. */ 116 @Test testProgramUtils()117 public void testProgramUtils() { 118 ProgramInfo stub = ProgramInfo.create(); 119 for (long channelId = 1; channelId < Constants.UNIT_TEST_CHANNEL_COUNT; channelId++) { 120 int index = stub.getIndex(mClock.currentTimeMillis(), channelId); 121 long startTimeMs = stub.getStartTimeMs(index, channelId); 122 ProgramInfo programAt = stub.build(RuntimeEnvironment.application, index); 123 assertThat(startTimeMs).isAtMost(mClock.currentTimeMillis()); 124 assertThat(mClock.currentTimeMillis()).isLessThan(startTimeMs + programAt.durationMs); 125 } 126 } 127 128 /** 129 * Test for following methods. 130 * 131 * <p>{@link ProgramDataManager#getCurrentProgram(long)}, {@link 132 * ProgramDataManager#getPrograms(long, long)}, {@link 133 * ProgramDataManager#setPrefetchTimeRange(long)}. 134 */ 135 @Test testGetPrograms()136 public void testGetPrograms() throws InterruptedException { 137 // Initial setup to test {@link ProgramDataManager#setPrefetchTimeRange(long)}. 138 long preventSnapDelayMs = ProgramDataManager.PROGRAM_GUIDE_SNAP_TIME_MS * 2; 139 long prefetchTimeRangeStartMs = System.currentTimeMillis() + preventSnapDelayMs; 140 mClock.setCurrentTimeMillis(prefetchTimeRangeStartMs + preventSnapDelayMs); 141 mProgramDataManager.setPrefetchTimeRange(prefetchTimeRangeStartMs); 142 143 startAndWaitForComplete(); 144 145 for (long channelId = 1; channelId <= Constants.UNIT_TEST_CHANNEL_COUNT; channelId++) { 146 Program currentProgram = mProgramDataManager.getCurrentProgram(channelId); 147 // Test {@link ProgramDataManager#getCurrentProgram(long)}. 148 assertThat(currentProgram).isNotNull(); 149 assertWithMessage("currentProgramStartTime") 150 .that(currentProgram.getStartTimeUtcMillis()) 151 .isLessThan(mClock.currentTimeMillis()); 152 assertWithMessage("currentProgramEndTime") 153 .that(currentProgram.getEndTimeUtcMillis()) 154 .isGreaterThan(mClock.currentTimeMillis()); 155 156 // Test {@link ProgramDataManager#getPrograms(long)}. 157 // Case #1: Normal case 158 List<Program> programs = 159 mProgramDataManager.getPrograms(channelId, mClock.currentTimeMillis()); 160 ProgramInfo stub = ProgramInfo.create(); 161 int index = stub.getIndex(mClock.currentTimeMillis(), channelId); 162 for (Program program : programs) { 163 ProgramInfo programInfoAt = stub.build(RuntimeEnvironment.application, index); 164 long startTimeMs = stub.getStartTimeMs(index, channelId); 165 assertProgramEquals(startTimeMs, programInfoAt, program); 166 index++; 167 } 168 // Case #2: Corner cases where there's a program that starts at the start of the range. 169 long startTimeMs = programs.get(0).getStartTimeUtcMillis(); 170 programs = mProgramDataManager.getPrograms(channelId, startTimeMs); 171 assertThat(programs.get(0).getStartTimeUtcMillis()).isEqualTo(startTimeMs); 172 173 // Test {@link ProgramDataManager#setPrefetchTimeRange(long)}. 174 programs = 175 mProgramDataManager.getPrograms( 176 channelId, prefetchTimeRangeStartMs - TimeUnit.HOURS.toMillis(1)); 177 for (Program program : programs) { 178 assertThat(program.getEndTimeUtcMillis()).isAtLeast(prefetchTimeRangeStartMs); 179 } 180 } 181 } 182 183 /** 184 * Test for following methods. 185 * 186 * <p>{@link ProgramDataManager#addOnCurrentProgramUpdatedListener}, {@link 187 * ProgramDataManager#removeOnCurrentProgramUpdatedListener}. 188 */ 189 @Test testCurrentProgramListener()190 public void testCurrentProgramListener() throws InterruptedException { 191 final long testChannelId = 1; 192 ProgramInfo stub = ProgramInfo.create(); 193 int index = stub.getIndex(mClock.currentTimeMillis(), testChannelId); 194 // Set current time to few seconds before the current program ends, 195 // so we can see if callback is called as expected. 196 long nextProgramStartTimeMs = stub.getStartTimeMs(index + 1, testChannelId); 197 ProgramInfo nextProgramInfo = stub.build(RuntimeEnvironment.application, index + 1); 198 mClock.setCurrentTimeMillis(nextProgramStartTimeMs - (WAIT_TIME_OUT_MS / 2)); 199 200 startAndWaitForComplete(); 201 // Note that changing current time doesn't affect the current program 202 // because current program is updated after waiting for the program's duration. 203 // See {@link ProgramDataManager#updateCurrentProgram}. 204 TestProgramDataManagerOnCurrentProgramUpdatedListener listener = 205 new TestProgramDataManagerOnCurrentProgramUpdatedListener(); 206 mClock.setCurrentTimeMillis(mClock.currentTimeMillis() + WAIT_TIME_OUT_MS); 207 mProgramDataManager.addOnCurrentProgramUpdatedListener(testChannelId, listener); 208 shadowOf(getMainLooper()).runToEndOfTasks(); 209 assertThat( 210 listener.currentProgramUpdatedLatch.await( 211 WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS)) 212 .isTrue(); 213 assertThat(listener.updatedChannelId).isEqualTo(testChannelId); 214 Program currentProgram = mProgramDataManager.getCurrentProgram(testChannelId); 215 assertProgramEquals(nextProgramStartTimeMs, nextProgramInfo, currentProgram); 216 assertThat(currentProgram).isEqualTo(listener.updatedProgram); 217 } 218 219 /** Test if program data is refreshed after the program insertion. */ 220 @Test testContentProviderUpdate()221 public void testContentProviderUpdate() throws InterruptedException { 222 final long testChannelId = 1; 223 startAndWaitForComplete(); 224 // Force program data manager to update program data whenever it's changes. 225 mProgramDataManager.setProgramPrefetchUpdateWait(0); 226 mCallback.reset(); 227 List<Program> programList = 228 mProgramDataManager.getPrograms(testChannelId, mClock.currentTimeMillis()); 229 assertThat(programList).isNotNull(); 230 long lastProgramEndTime = programList.get(programList.size() - 1).getEndTimeUtcMillis(); 231 // Make change in content provider 232 ProgramUtils.populatePrograms( 233 RuntimeEnvironment.application, 234 TvContract.buildChannelUri(testChannelId), 235 ProgramInfo.create(), 236 mClock, 237 TimeUnit.DAYS.toMillis(2)); 238 shadowOf(getMainLooper()).runToEndOfTasks(); 239 assertThat(mCallback.programUpdatedLatch.await(WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS)) 240 .isTrue(); 241 programList = mProgramDataManager.getPrograms(testChannelId, mClock.currentTimeMillis()); 242 assertThat(lastProgramEndTime) 243 .isLessThan(programList.get(programList.size() - 1).getEndTimeUtcMillis()); 244 } 245 246 /** Test for {@link ProgramDataManager#setPauseProgramUpdate(boolean)}. */ 247 @Test testSetPauseProgramUpdate()248 public void testSetPauseProgramUpdate() throws InterruptedException { 249 final long testChannelId = 1; 250 startAndWaitForComplete(); 251 // Force program data manager to update program data whenever it's changes. 252 mProgramDataManager.setProgramPrefetchUpdateWait(0); 253 mCallback.reset(); 254 mProgramDataManager.setPauseProgramUpdate(true); 255 ProgramUtils.populatePrograms( 256 RuntimeEnvironment.application, 257 TvContract.buildChannelUri(testChannelId), 258 ProgramInfo.create(), 259 mClock, 260 TimeUnit.DAYS.toMillis(2)); 261 shadowOf(getMainLooper()).runToEndOfTasks(); 262 assertThat(mCallback.programUpdatedLatch.await(FAILURE_TIME_OUT_MS, TimeUnit.MILLISECONDS)) 263 .isFalse(); 264 } 265 assertProgramEquals( long expectedStartTime, ProgramInfo expectedInfo, Program actualProgram)266 public static void assertProgramEquals( 267 long expectedStartTime, ProgramInfo expectedInfo, Program actualProgram) { 268 assertWithMessage("title").that(actualProgram.getTitle()).isEqualTo(expectedInfo.title); 269 assertWithMessage("episode") 270 .that(actualProgram.getEpisodeTitle()) 271 .isEqualTo(expectedInfo.episode); 272 assertWithMessage("description") 273 .that(actualProgram.getDescription()) 274 .isEqualTo(expectedInfo.description); 275 assertWithMessage("startTime") 276 .that(actualProgram.getStartTimeUtcMillis()) 277 .isEqualTo(expectedStartTime); 278 assertWithMessage("endTime") 279 .that(actualProgram.getEndTimeUtcMillis()) 280 .isEqualTo(expectedStartTime + expectedInfo.durationMs); 281 } 282 283 private static class TestProgramDataManagerCallback implements ProgramDataManager.Callback { 284 public CountDownLatch programUpdatedLatch = new CountDownLatch(1); 285 public CountDownLatch channelUpdatedLatch = new CountDownLatch(1); 286 287 @Override onProgramUpdated()288 public void onProgramUpdated() { 289 programUpdatedLatch.countDown(); 290 } 291 292 @Override onChannelUpdated()293 public void onChannelUpdated() { 294 channelUpdatedLatch.countDown(); 295 } 296 reset()297 public void reset() { 298 programUpdatedLatch = new CountDownLatch(1); 299 channelUpdatedLatch = new CountDownLatch(1); 300 } 301 } 302 303 private static class TestProgramDataManagerOnCurrentProgramUpdatedListener 304 implements OnCurrentProgramUpdatedListener { 305 public final CountDownLatch currentProgramUpdatedLatch = new CountDownLatch(1); 306 public long updatedChannelId = -1; 307 public Program updatedProgram = null; 308 309 @Override onCurrentProgramUpdated(long channelId, Program program)310 public void onCurrentProgramUpdated(long channelId, Program program) { 311 updatedChannelId = channelId; 312 updatedProgram = program; 313 currentProgramUpdatedLatch.countDown(); 314 } 315 } 316 } 317