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.recommendation;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.junit.Assert.assertEquals;
22 
23 import androidx.test.filters.SmallTest;
24 import androidx.test.runner.AndroidJUnit4;
25 
26 import com.android.tv.data.ProgramImpl;
27 import com.android.tv.data.api.Program;
28 import com.android.tv.recommendation.RoutineWatchEvaluator.ProgramTime;
29 
30 import org.junit.Test;
31 import org.junit.runner.RunWith;
32 
33 import java.util.Calendar;
34 import java.util.List;
35 import java.util.concurrent.TimeUnit;
36 
37 /** Tests for {@link RoutineWatchEvaluator}. */
38 @SmallTest
39 @RunWith(AndroidJUnit4.class)
40 public class RoutineWatchEvaluatorTest extends EvaluatorTestCase<RoutineWatchEvaluator> {
41     private static class ScoredItem implements Comparable<ScoredItem> {
42         private final String mBase;
43         private final String mText;
44         private final double mScore;
45 
ScoredItem(String base, String text)46         private ScoredItem(String base, String text) {
47             this.mBase = base;
48             this.mText = text;
49             this.mScore = RoutineWatchEvaluator.calculateTitleMatchScore(base, text);
50         }
51 
52         @Override
compareTo(ScoredItem scoredItem)53         public int compareTo(ScoredItem scoredItem) {
54             return Double.compare(mScore, scoredItem.mScore);
55         }
56 
57         @Override
toString()58         public String toString() {
59             return mBase + " scored with " + mText + " is " + mScore;
60         }
61     }
62 
score(String t1, String t2)63     private static ScoredItem score(String t1, String t2) {
64         return new ScoredItem(t1, t2);
65     }
66 
67     @Override
createEvaluator()68     public RoutineWatchEvaluator createEvaluator() {
69         return new RoutineWatchEvaluator();
70     }
71 
72     @Test
testSplitTextToWords()73     public void testSplitTextToWords() {
74         assertThat(RoutineWatchEvaluator.splitTextToWords("")).containsExactly().inOrder();
75         assertThat(RoutineWatchEvaluator.splitTextToWords("Google"))
76                 .containsExactly("Google")
77                 .inOrder();
78         assertThat(RoutineWatchEvaluator.splitTextToWords("The Big Bang Theory"))
79                 .containsExactly("The", "Big", "Bang", "Theory")
80                 .inOrder();
81         assertThat(RoutineWatchEvaluator.splitTextToWords("Hello, world!"))
82                 .containsExactly("Hello", "world")
83                 .inOrder();
84         assertThat(RoutineWatchEvaluator.splitTextToWords("Adam's Rib"))
85                 .containsExactly("Adam's", "Rib")
86                 .inOrder();
87         assertThat(RoutineWatchEvaluator.splitTextToWords("G.I. Joe"))
88                 .containsExactly("G.I", "Joe")
89                 .inOrder();
90         assertThat(RoutineWatchEvaluator.splitTextToWords("A.I.")).containsExactly("A.I").inOrder();
91     }
92 
93     @Test
testCalculateMaximumMatchedWordSequenceLength()94     public void testCalculateMaximumMatchedWordSequenceLength() {
95         assertMaximumMatchedWordSequenceLength(0, "", "Google");
96         assertMaximumMatchedWordSequenceLength(2, "The Big Bang Theory", "Big Bang");
97         assertMaximumMatchedWordSequenceLength(2, "The Big Bang Theory", "Theory Of Big Bang");
98         assertMaximumMatchedWordSequenceLength(4, "The Big Bang Theory", "The Big Bang Theory");
99         assertMaximumMatchedWordSequenceLength(1, "Modern Family", "Family Guy");
100         assertMaximumMatchedWordSequenceLength(1, "The Simpsons", "The Walking Dead");
101         assertMaximumMatchedWordSequenceLength(3, "Game Of Thrones 1", "Game Of Thrones 6");
102         assertMaximumMatchedWordSequenceLength(0, "Dexter", "Friends");
103     }
104 
105     @Test
testCalculateTitleMatchScore_empty()106     public void testCalculateTitleMatchScore_empty() {
107         assertEqualScores(0.0, RoutineWatchEvaluator.calculateTitleMatchScore("", ""));
108         assertEqualScores(0.0, RoutineWatchEvaluator.calculateTitleMatchScore("foo", ""));
109         assertEqualScores(0.0, RoutineWatchEvaluator.calculateTitleMatchScore("", "foo"));
110     }
111 
112     @Test
testCalculateTitleMatchScore_spaces()113     public void testCalculateTitleMatchScore_spaces() {
114         assertEqualScores(0.0, RoutineWatchEvaluator.calculateTitleMatchScore(" ", " "));
115         assertEqualScores(0.0, RoutineWatchEvaluator.calculateTitleMatchScore("foo", " "));
116         assertEqualScores(0.0, RoutineWatchEvaluator.calculateTitleMatchScore(" ", "foo"));
117     }
118 
119     @Test
testCalculateTitleMatchScore_null()120     public void testCalculateTitleMatchScore_null() {
121         assertEqualScores(0.0, RoutineWatchEvaluator.calculateTitleMatchScore(null, null));
122         assertEqualScores(0.0, RoutineWatchEvaluator.calculateTitleMatchScore("foo", null));
123         assertEqualScores(0.0, RoutineWatchEvaluator.calculateTitleMatchScore(null, "foo"));
124     }
125 
126     @Test
testCalculateTitleMatchScore_longerMatchIsBetter()127     public void testCalculateTitleMatchScore_longerMatchIsBetter() {
128         String base = "foo bar baz";
129     assertThat(
130             new ScoredItem[] {
131               score(base, ""),
132               score(base, "bar"),
133               score(base, "foo bar"),
134               score(base, "foo bar baz")
135             })
136         .asList()
137         .isInOrder();
138     }
139 
140     @Test
testProgramTime_createFromProgram()141     public void testProgramTime_createFromProgram() {
142         Calendar time = Calendar.getInstance();
143         int todayDayOfWeek = time.get(Calendar.DAY_OF_WEEK);
144         // Value of DayOfWeek is between 1 and 7 (inclusive).
145         int tomorrowDayOfWeek = (todayDayOfWeek % 7) + 1;
146 
147         // Today 00:00 - 01:00.
148         ProgramTime programTimeToday0000to0100 =
149                 ProgramTime.createFromProgram(
150                         createDummyProgram(todayAtHourMin(0, 0), TimeUnit.HOURS.toMillis(1)));
151         assertProgramTime(
152                 todayDayOfWeek,
153                 hourMinuteToSec(0, 0),
154                 hourMinuteToSec(1, 0),
155                 programTimeToday0000to0100);
156 
157         // Today 23:30 - 24:30.
158         ProgramTime programTimeToday2330to2430 =
159                 ProgramTime.createFromProgram(
160                         createDummyProgram(todayAtHourMin(23, 30), TimeUnit.HOURS.toMillis(1)));
161         assertProgramTime(
162                 todayDayOfWeek,
163                 hourMinuteToSec(23, 30),
164                 hourMinuteToSec(24, 30),
165                 programTimeToday2330to2430);
166 
167         // Tomorrow 00:00 - 01:00.
168         ProgramTime programTimeTomorrow0000to0100 =
169                 ProgramTime.createFromProgram(
170                         createDummyProgram(tomorrowAtHourMin(0, 0), TimeUnit.HOURS.toMillis(1)));
171         assertProgramTime(
172                 tomorrowDayOfWeek,
173                 hourMinuteToSec(0, 0),
174                 hourMinuteToSec(1, 0),
175                 programTimeTomorrow0000to0100);
176 
177         // Tomorrow 23:30 - 24:30.
178         ProgramTime programTimeTomorrow2330to2430 =
179                 ProgramTime.createFromProgram(
180                         createDummyProgram(tomorrowAtHourMin(23, 30), TimeUnit.HOURS.toMillis(1)));
181         assertProgramTime(
182                 tomorrowDayOfWeek,
183                 hourMinuteToSec(23, 30),
184                 hourMinuteToSec(24, 30),
185                 programTimeTomorrow2330to2430);
186 
187         // Today 18:00 - Tomorrow 12:00.
188         ProgramTime programTimeToday1800to3600 =
189                 ProgramTime.createFromProgram(
190                         createDummyProgram(todayAtHourMin(18, 0), TimeUnit.HOURS.toMillis(18)));
191         // Maximum duration of ProgramTime is 12 hours.
192         // So, this program looks like it ends at Tomorrow 06:00 (30:00).
193         assertProgramTime(
194                 todayDayOfWeek,
195                 hourMinuteToSec(18, 0),
196                 hourMinuteToSec(30, 0),
197                 programTimeToday1800to3600);
198     }
199 
200     @Test
testCalculateOverlappedIntervalScore()201     public void testCalculateOverlappedIntervalScore() {
202         // Today 21:00 - 24:00.
203         ProgramTime programTimeToday2100to2400 =
204                 ProgramTime.createFromProgram(
205                         createDummyProgram(todayAtHourMin(21, 0), TimeUnit.HOURS.toMillis(3)));
206         // Today 22:00 - 01:00.
207         ProgramTime programTimeToday2200to0100 =
208                 ProgramTime.createFromProgram(
209                         createDummyProgram(todayAtHourMin(22, 0), TimeUnit.HOURS.toMillis(3)));
210         // Tomorrow 00:00 - 03:00.
211         ProgramTime programTimeTomorrow0000to0300 =
212                 ProgramTime.createFromProgram(
213                         createDummyProgram(tomorrowAtHourMin(0, 0), TimeUnit.HOURS.toMillis(3)));
214         // Tomorrow 20:00 - Tomorrow 23:00.
215         ProgramTime programTimeTomorrow2000to2300 =
216                 ProgramTime.createFromProgram(
217                         createDummyProgram(tomorrowAtHourMin(20, 0), TimeUnit.HOURS.toMillis(3)));
218 
219         // Check intersection time and commutative law in all cases.
220         int oneHourInSec = hourMinuteToSec(1, 0);
221         assertOverlappedIntervalScore(
222                 2 * oneHourInSec, true, programTimeToday2100to2400, programTimeToday2200to0100);
223         assertOverlappedIntervalScore(
224                 0, false, programTimeToday2100to2400, programTimeTomorrow0000to0300);
225         assertOverlappedIntervalScore(
226                 2 * oneHourInSec, false, programTimeToday2100to2400, programTimeTomorrow2000to2300);
227         assertOverlappedIntervalScore(
228                 oneHourInSec, true, programTimeToday2200to0100, programTimeTomorrow0000to0300);
229         assertOverlappedIntervalScore(
230                 oneHourInSec, false, programTimeToday2200to0100, programTimeTomorrow2000to2300);
231         assertOverlappedIntervalScore(
232                 0, false, programTimeTomorrow0000to0300, programTimeTomorrow2000to2300);
233     }
234 
235     @Test
testGetTimeOfDayInSec()236     public void testGetTimeOfDayInSec() {
237         // Time was set as 00:00:00. So, getTimeOfDay must returns 0 (= 0 * 60 * 60 + 0 * 60 + 0).
238         assertEquals(
239                 "TimeOfDayInSec",
240                 hourMinuteToSec(0, 0),
241                 RoutineWatchEvaluator.getTimeOfDayInSec(todayAtHourMin(0, 0)));
242 
243         // Time was set as 23:59:59. So, getTimeOfDay must returns 23 * 60 + 60 + 59 * 60 + 59.
244         assertEquals(
245                 "TimeOfDayInSec",
246                 hourMinuteSecondToSec(23, 59, 59),
247                 RoutineWatchEvaluator.getTimeOfDayInSec(todayAtHourMinSec(23, 59, 59)));
248     }
249 
assertMaximumMatchedWordSequenceLength( int expectedLength, String text1, String text2)250     private void assertMaximumMatchedWordSequenceLength(
251             int expectedLength, String text1, String text2) {
252         List<String> wordList1 = RoutineWatchEvaluator.splitTextToWords(text1);
253         List<String> wordList2 = RoutineWatchEvaluator.splitTextToWords(text2);
254         assertEquals(
255                 "MaximumMatchedWordSequenceLength",
256                 expectedLength,
257                 RoutineWatchEvaluator.calculateMaximumMatchedWordSequenceLength(
258                         wordList1, wordList2));
259         assertEquals(
260                 "MaximumMatchedWordSequenceLength",
261                 expectedLength,
262                 RoutineWatchEvaluator.calculateMaximumMatchedWordSequenceLength(
263                         wordList2, wordList1));
264     }
265 
assertProgramTime( int expectedWeekDay, int expectedStartTimeOfDayInSec, int expectedEndTimeOfDayInSec, ProgramTime actualProgramTime)266     private void assertProgramTime(
267             int expectedWeekDay,
268             int expectedStartTimeOfDayInSec,
269             int expectedEndTimeOfDayInSec,
270             ProgramTime actualProgramTime) {
271         assertEquals("Weekday", expectedWeekDay, actualProgramTime.weekDay);
272         assertEquals(
273                 "StartTimeOfDayInSec",
274                 expectedStartTimeOfDayInSec,
275                 actualProgramTime.startTimeOfDayInSec);
276         assertEquals(
277                 "EndTimeOfDayInSec",
278                 expectedEndTimeOfDayInSec,
279                 actualProgramTime.endTimeOfDayInSec);
280     }
281 
assertOverlappedIntervalScore( int expectedSeconds, boolean overlappedOnSameDay, ProgramTime t1, ProgramTime t2)282     private void assertOverlappedIntervalScore(
283             int expectedSeconds, boolean overlappedOnSameDay, ProgramTime t1, ProgramTime t2) {
284         double score = expectedSeconds;
285         if (!overlappedOnSameDay) {
286             score *= RoutineWatchEvaluator.MULTIPLIER_FOR_UNMATCHED_DAY_OF_WEEK;
287         }
288         // Two tests for testing commutative law.
289         assertEqualScores(
290                 "OverlappedIntervalScore",
291                 score,
292                 RoutineWatchEvaluator.calculateOverlappedIntervalScore(t1, t2));
293         assertEqualScores(
294                 "OverlappedIntervalScore",
295                 score,
296                 RoutineWatchEvaluator.calculateOverlappedIntervalScore(t2, t1));
297     }
298 
hourMinuteToSec(int hour, int minute)299     private int hourMinuteToSec(int hour, int minute) {
300         return hourMinuteSecondToSec(hour, minute, 0);
301     }
302 
hourMinuteSecondToSec(int hour, int minute, int second)303     private int hourMinuteSecondToSec(int hour, int minute, int second) {
304         return hour * 60 * 60 + minute * 60 + second;
305     }
306 
todayAtHourMin(int hour, int minute)307     private Calendar todayAtHourMin(int hour, int minute) {
308         return todayAtHourMinSec(hour, minute, 0);
309     }
310 
todayAtHourMinSec(int hour, int minute, int second)311     private Calendar todayAtHourMinSec(int hour, int minute, int second) {
312         Calendar time = Calendar.getInstance();
313         time.set(Calendar.HOUR_OF_DAY, hour);
314         time.set(Calendar.MINUTE, minute);
315         time.set(Calendar.SECOND, second);
316         return time;
317     }
318 
tomorrowAtHourMin(int hour, int minute)319     private Calendar tomorrowAtHourMin(int hour, int minute) {
320         Calendar time = todayAtHourMin(hour, minute);
321         time.add(Calendar.DATE, 1);
322         return time;
323     }
324 
createDummyProgram(Calendar startTime, long programDurationMs)325     private Program createDummyProgram(Calendar startTime, long programDurationMs) {
326         long startTimeMs = startTime.getTimeInMillis();
327 
328         return new ProgramImpl.Builder()
329                 .setStartTimeUtcMillis(startTimeMs)
330                 .setEndTimeUtcMillis(startTimeMs + programDurationMs)
331                 .build();
332     }
333 }
334