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