1/* 2 * Copyright (C) 2016 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.tv.dvr.recorder; 18 19import static android.support.test.InstrumentationRegistry.getContext; 20import static org.junit.Assert.assertTrue; 21import static org.mockito.Matchers.any; 22import static org.mockito.Matchers.anyLong; 23import static org.mockito.Matchers.eq; 24import static org.mockito.Mockito.mock; 25import static org.mockito.Mockito.never; 26import static org.mockito.Mockito.timeout; 27import static org.mockito.Mockito.verify; 28import static org.mockito.Mockito.when; 29 30import android.app.AlarmManager; 31import android.media.tv.TvInputInfo; 32import android.os.Build; 33import android.os.Looper; 34import android.os.SystemClock; 35import android.support.test.filters.SdkSuppress; 36import android.support.test.filters.SmallTest; 37 38import com.android.tv.InputSessionManager; 39import com.android.tv.data.Channel; 40import com.android.tv.data.ChannelDataManager; 41import com.android.tv.dvr.DvrManager; 42import com.android.tv.dvr.WritableDvrDataManager; 43import com.android.tv.dvr.data.ScheduledRecording; 44import com.android.tv.dvr.recorder.InputTaskScheduler.RecordingTaskFactory; 45import com.android.tv.testing.FakeClock; 46import com.android.tv.testing.dvr.RecordingTestUtils; 47import com.android.tv.util.Clock; 48import com.android.tv.util.TestUtils; 49 50import org.junit.Before; 51import org.junit.Test; 52import org.mockito.Mock; 53import org.mockito.MockitoAnnotations; 54 55import java.util.ArrayList; 56import java.util.List; 57import java.util.concurrent.TimeUnit; 58 59/** 60 * Tests for {@link InputTaskScheduler}. 61 */ 62@SmallTest 63@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) 64public class InputTaskSchedulerTest { 65 private static final String INPUT_ID = "input_id"; 66 private static final int CHANNEL_ID = 1; 67 private static final long LISTENER_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(1); 68 private static final int TUNER_COUNT_ONE = 1; 69 private static final int TUNER_COUNT_TWO = 2; 70 private static final long LOW_PRIORITY = 1; 71 private static final long HIGH_PRIORITY = 2; 72 73 private FakeClock mFakeClock; 74 private InputTaskScheduler mScheduler; 75 @Mock private DvrManager mDvrManager; 76 @Mock private WritableDvrDataManager mDataManager; 77 @Mock private InputSessionManager mSessionManager; 78 @Mock private AlarmManager mMockAlarmManager; 79 @Mock private ChannelDataManager mChannelDataManager; 80 private List<RecordingTask> mRecordingTasks; 81 82 @Before 83 public void setUp() throws Exception { 84 if (Looper.myLooper() == null) { 85 Looper.prepare(); 86 } 87 mRecordingTasks = new ArrayList(); 88 MockitoAnnotations.initMocks(this); 89 mFakeClock = FakeClock.createWithCurrentTime(); 90 TvInputInfo input = createTvInputInfo(TUNER_COUNT_ONE); 91 mScheduler = new InputTaskScheduler(getContext(), input, Looper.myLooper(), 92 mChannelDataManager, mDvrManager, mDataManager, mSessionManager, mFakeClock, 93 new RecordingTaskFactory() { 94 @Override 95 public RecordingTask createRecordingTask(ScheduledRecording scheduledRecording, 96 Channel channel, DvrManager dvrManager, 97 InputSessionManager sessionManager, WritableDvrDataManager dataManager, 98 Clock clock) { 99 RecordingTask task = mock(RecordingTask.class); 100 when(task.getPriority()).thenReturn(scheduledRecording.getPriority()); 101 when(task.getEndTimeMs()).thenReturn(scheduledRecording.getEndTimeMs()); 102 mRecordingTasks.add(task); 103 return task; 104 } 105 }); 106 } 107 108 @Test 109 public void testAddSchedule_past() { 110 ScheduledRecording r = RecordingTestUtils.createTestRecordingWithPeriod(INPUT_ID, 111 CHANNEL_ID, 0L, 1L); 112 when(mDataManager.getScheduledRecording(anyLong())).thenReturn(r); 113 mScheduler.handleAddSchedule(r); 114 mScheduler.handleBuildSchedule(); 115 verify(mDataManager, timeout((int) LISTENER_TIMEOUT_MS).times(1)) 116 .changeState(any(ScheduledRecording.class), 117 eq(ScheduledRecording.STATE_RECORDING_FAILED)); 118 } 119 120 @Test 121 public void testAddSchedule_start() { 122 mScheduler.handleAddSchedule(RecordingTestUtils.createTestRecordingWithPeriod(INPUT_ID, 123 CHANNEL_ID, mFakeClock.currentTimeMillis(), 124 mFakeClock.currentTimeMillis() + TimeUnit.HOURS.toMillis(1))); 125 mScheduler.handleBuildSchedule(); 126 verify(mRecordingTasks.get(0), timeout((int) LISTENER_TIMEOUT_MS).times(1)).start(); 127 } 128 129 @Test 130 public void testAddSchedule_consecutiveNoStop() { 131 long startTimeMs = mFakeClock.currentTimeMillis(); 132 long endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1); 133 long id = 0; 134 mScheduler.handleAddSchedule( 135 RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod(++id, CHANNEL_ID, 136 LOW_PRIORITY, startTimeMs, endTimeMs)); 137 mScheduler.handleBuildSchedule(); 138 startTimeMs = endTimeMs; 139 endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1); 140 mScheduler.handleAddSchedule( 141 RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod(++id, CHANNEL_ID, 142 HIGH_PRIORITY, startTimeMs, endTimeMs)); 143 mScheduler.handleBuildSchedule(); 144 verify(mRecordingTasks.get(0), timeout((int) LISTENER_TIMEOUT_MS).times(1)).start(); 145 // The first schedule should not be stopped because the second one should wait for the end 146 // of the first schedule. 147 SystemClock.sleep(LISTENER_TIMEOUT_MS); 148 verify(mRecordingTasks.get(0), never()).stop(); 149 } 150 151 @Test 152 public void testAddSchedule_consecutiveNoFail() { 153 long startTimeMs = mFakeClock.currentTimeMillis(); 154 long endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1); 155 long id = 0; 156 when(mDataManager.getScheduledRecording(anyLong())).thenReturn(ScheduledRecording 157 .builder(INPUT_ID, CHANNEL_ID, 0L, 0L).build()); 158 mScheduler.handleAddSchedule( 159 RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod(++id, CHANNEL_ID, 160 HIGH_PRIORITY, startTimeMs, endTimeMs)); 161 mScheduler.handleBuildSchedule(); 162 startTimeMs = endTimeMs; 163 endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1); 164 mScheduler.handleAddSchedule( 165 RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod(++id, CHANNEL_ID, 166 LOW_PRIORITY, startTimeMs, endTimeMs)); 167 mScheduler.handleBuildSchedule(); 168 verify(mRecordingTasks.get(0), timeout((int) LISTENER_TIMEOUT_MS).times(1)).start(); 169 SystemClock.sleep(LISTENER_TIMEOUT_MS); 170 verify(mRecordingTasks.get(0), never()).stop(); 171 // The second schedule should not fail because it can starts after the first one finishes. 172 SystemClock.sleep(LISTENER_TIMEOUT_MS); 173 verify(mDataManager, never()) 174 .changeState(any(ScheduledRecording.class), 175 eq(ScheduledRecording.STATE_RECORDING_FAILED)); 176 } 177 178 @Test 179 public void testAddSchedule_consecutiveUseLessSession() throws Exception { 180 TvInputInfo input = createTvInputInfo(TUNER_COUNT_TWO); 181 mScheduler.updateTvInputInfo(input); 182 long startTimeMs = mFakeClock.currentTimeMillis(); 183 long endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1); 184 long id = 0; 185 mScheduler.handleAddSchedule( 186 RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod(++id, CHANNEL_ID, 187 LOW_PRIORITY, startTimeMs, endTimeMs)); 188 mScheduler.handleBuildSchedule(); 189 startTimeMs = endTimeMs; 190 endTimeMs = startTimeMs + TimeUnit.SECONDS.toMillis(1); 191 mScheduler.handleAddSchedule( 192 RecordingTestUtils.createTestRecordingWithIdAndPriorityAndPeriod(++id, CHANNEL_ID, 193 HIGH_PRIORITY, startTimeMs, endTimeMs)); 194 mScheduler.handleBuildSchedule(); 195 verify(mRecordingTasks.get(0), timeout((int) LISTENER_TIMEOUT_MS).times(1)).start(); 196 SystemClock.sleep(LISTENER_TIMEOUT_MS); 197 verify(mRecordingTasks.get(0), never()).stop(); 198 // The second schedule should wait until the first one finishes rather than creating a new 199 // session even though there are available tuners. 200 assertTrue(mRecordingTasks.size() == 1); 201 } 202 203 @Test 204 public void testUpdateSchedule_noCancel() { 205 ScheduledRecording r = RecordingTestUtils.createTestRecordingWithPeriod(INPUT_ID, 206 CHANNEL_ID, mFakeClock.currentTimeMillis(), 207 mFakeClock.currentTimeMillis() + TimeUnit.HOURS.toMillis(1)); 208 mScheduler.handleAddSchedule(r); 209 mScheduler.handleBuildSchedule(); 210 mScheduler.handleUpdateSchedule(r); 211 SystemClock.sleep(LISTENER_TIMEOUT_MS); 212 verify(mRecordingTasks.get(0), never()).cancel(); 213 } 214 215 @Test 216 public void testUpdateSchedule_cancel() { 217 ScheduledRecording r = RecordingTestUtils.createTestRecordingWithPeriod(INPUT_ID, 218 CHANNEL_ID, mFakeClock.currentTimeMillis(), 219 mFakeClock.currentTimeMillis() + TimeUnit.HOURS.toMillis(2)); 220 mScheduler.handleAddSchedule(r); 221 mScheduler.handleBuildSchedule(); 222 mScheduler.handleUpdateSchedule(ScheduledRecording.buildFrom(r) 223 .setStartTimeMs(mFakeClock.currentTimeMillis() + TimeUnit.HOURS.toMillis(1)) 224 .build()); 225 verify(mRecordingTasks.get(0), timeout((int) LISTENER_TIMEOUT_MS).times(1)).cancel(); 226 } 227 228 private TvInputInfo createTvInputInfo(int tunerCount) throws Exception { 229 return TestUtils.createTvInputInfo(null, null, null, 0, false, true, tunerCount); 230 } 231} 232