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
17package com.android.tv.recommendation;
18
19import android.test.MoreAsserts;
20import android.test.suitebuilder.annotation.SmallTest;
21
22import com.android.tv.data.Program;
23import com.android.tv.recommendation.RoutineWatchEvaluator.ProgramTime;
24
25import java.util.Arrays;
26import java.util.Calendar;
27import java.util.List;
28import java.util.TreeSet;
29import java.util.concurrent.TimeUnit;
30
31@SmallTest
32public class RoutineWatchEvaluatorTest extends EvaluatorTestCase<RoutineWatchEvaluator> {
33    private static class ScoredItem implements Comparable<ScoredItem> {
34        private final String mBase;
35        private final String mText;
36        private final double mScore;
37
38        private ScoredItem(String base, String text) {
39            this.mBase = base;
40            this.mText = text;
41            this.mScore = RoutineWatchEvaluator.calculateTitleMatchScore(base, text);
42        }
43
44        @Override
45        public int compareTo(ScoredItem scoredItem) {
46            return Double.compare(mScore, scoredItem.mScore);
47        }
48
49        @Override
50        public String toString() {
51            return mBase + " scored with " + mText + " is " + mScore;
52        }
53    }
54
55    private static ScoredItem score(String t1, String t2) {
56        return new ScoredItem(t1, t2);
57    }
58
59    @Override
60    public RoutineWatchEvaluator createEvaluator() {
61        return new RoutineWatchEvaluator();
62    }
63
64    public void testSplitTextToWords() {
65        assertSplitTextToWords("");
66        assertSplitTextToWords("Google", "Google");
67        assertSplitTextToWords("The Big Bang Theory", "The", "Big", "Bang", "Theory");
68        assertSplitTextToWords("Hello, world!", "Hello", "world");
69        assertSplitTextToWords("Adam's Rib", "Adam's", "Rib");
70        assertSplitTextToWords("G.I. Joe", "G.I", "Joe");
71        assertSplitTextToWords("A.I.", "A.I");
72    }
73
74    public void testCalculateMaximumMatchedWordSequenceLength() {
75        assertMaximumMatchedWordSequenceLength(0, "", "Google");
76        assertMaximumMatchedWordSequenceLength(2, "The Big Bang Theory", "Big Bang");
77        assertMaximumMatchedWordSequenceLength(2, "The Big Bang Theory", "Theory Of Big Bang");
78        assertMaximumMatchedWordSequenceLength(4, "The Big Bang Theory", "The Big Bang Theory");
79        assertMaximumMatchedWordSequenceLength(1, "Modern Family", "Family Guy");
80        assertMaximumMatchedWordSequenceLength(1, "The Simpsons", "The Walking Dead");
81        assertMaximumMatchedWordSequenceLength(3, "Game Of Thrones 1", "Game Of Thrones 6");
82        assertMaximumMatchedWordSequenceLength(0, "Dexter", "Friends");
83    }
84
85    public void testCalculateTitleMatchScore_empty() {
86        assertEquals(0.0, RoutineWatchEvaluator.calculateTitleMatchScore("", ""));
87        assertEquals(0.0, RoutineWatchEvaluator.calculateTitleMatchScore("foo", ""));
88        assertEquals(0.0, RoutineWatchEvaluator.calculateTitleMatchScore("", "foo"));
89    }
90
91    public void testCalculateTitleMatchScore_spaces() {
92        assertEquals(0.0, RoutineWatchEvaluator.calculateTitleMatchScore(" ", " "));
93        assertEquals(0.0, RoutineWatchEvaluator.calculateTitleMatchScore("foo", " "));
94        assertEquals(0.0, RoutineWatchEvaluator.calculateTitleMatchScore(" ", "foo"));
95    }
96
97
98    public void testCalculateTitleMatchScore_null() {
99        assertEquals(0.0, RoutineWatchEvaluator.calculateTitleMatchScore(null, null));
100        assertEquals(0.0, RoutineWatchEvaluator.calculateTitleMatchScore("foo", null));
101        assertEquals(0.0, RoutineWatchEvaluator.calculateTitleMatchScore(null, "foo"));
102    }
103
104    public void testCalculateTitleMatchScore_longerMatchIsBetter() {
105        String base = "foo bar baz";
106        assertInOrder(
107                score(base, ""),
108                score(base, "bar"),
109                score(base, "foo bar"),
110                score(base, "foo bar baz"));
111    }
112
113    public void testProgramTime_createFromProgram() {
114        Calendar time = Calendar.getInstance();
115        int todayDayOfWeek = time.get(Calendar.DAY_OF_WEEK);
116        // Value of DayOfWeek is between 1 and 7 (inclusive).
117        int tomorrowDayOfWeek = (todayDayOfWeek % 7) + 1;
118
119        // Today 00:00 - 01:00.
120        ProgramTime programTimeToday0000_0100 = ProgramTime.createFromProgram(
121                createDummyProgram(todayAtHourMin(0, 0), TimeUnit.HOURS.toMillis(1)));
122        assertProgramTime(todayDayOfWeek, hourMinuteToSec(0, 0), hourMinuteToSec(1, 0),
123                programTimeToday0000_0100);
124
125        // Today 23:30 - 24:30.
126        ProgramTime programTimeToday2330_2430 = ProgramTime.createFromProgram(
127                createDummyProgram(todayAtHourMin(23, 30), TimeUnit.HOURS.toMillis(1)));
128        assertProgramTime(todayDayOfWeek, hourMinuteToSec(23, 30), hourMinuteToSec(24, 30),
129                programTimeToday2330_2430);
130
131        // Tomorrow 00:00 - 01:00.
132        ProgramTime programTimeTomorrow0000_0100 = ProgramTime.createFromProgram(
133                createDummyProgram(tomorrowAtHourMin(0, 0), TimeUnit.HOURS.toMillis(1)));
134        assertProgramTime(tomorrowDayOfWeek, hourMinuteToSec(0, 0), hourMinuteToSec(1, 0),
135                programTimeTomorrow0000_0100);
136
137        // Tomorrow 23:30 - 24:30.
138        ProgramTime programTimeTomorrow2330_2430 = ProgramTime.createFromProgram(
139                createDummyProgram(tomorrowAtHourMin(23, 30), TimeUnit.HOURS.toMillis(1)));
140        assertProgramTime(tomorrowDayOfWeek, hourMinuteToSec(23, 30), hourMinuteToSec(24, 30),
141                programTimeTomorrow2330_2430);
142
143        // Today 18:00 - Tomorrow 12:00.
144        ProgramTime programTimeToday1800_3600 = ProgramTime.createFromProgram(
145                createDummyProgram(todayAtHourMin(18, 0), TimeUnit.HOURS.toMillis(18)));
146        // Maximum duration of ProgramTime is 12 hours.
147        // So, this program looks like it ends at Tomorrow 06:00 (30:00).
148        assertProgramTime(todayDayOfWeek, hourMinuteToSec(18, 0), hourMinuteToSec(30, 0),
149                programTimeToday1800_3600);
150    }
151
152    public void testCalculateOverlappedIntervalScore() {
153        // Today 21:00 - 24:00.
154        ProgramTime programTimeToday2100_2400 = ProgramTime.createFromProgram(
155                createDummyProgram(todayAtHourMin(21, 0), TimeUnit.HOURS.toMillis(3)));
156        // Today 22:00 - 01:00.
157        ProgramTime programTimeToday2200_0100 = ProgramTime.createFromProgram(
158                createDummyProgram(todayAtHourMin(22, 0), TimeUnit.HOURS.toMillis(3)));
159        // Tomorrow 00:00 - 03:00.
160        ProgramTime programTimeTomorrow0000_0300 = ProgramTime.createFromProgram(
161                createDummyProgram(tomorrowAtHourMin(0, 0), TimeUnit.HOURS.toMillis(3)));
162        // Tomorrow 20:00 - Tomorrow 23:00.
163        ProgramTime programTimeTomorrow2000_2300 = ProgramTime.createFromProgram(
164                createDummyProgram(tomorrowAtHourMin(20, 0), TimeUnit.HOURS.toMillis(3)));
165
166        // Check intersection time and commutative law in all cases.
167        int oneHourInSec = hourMinuteToSec(1, 0);
168        assertOverlappedIntervalScore(2 * oneHourInSec, true, programTimeToday2100_2400,
169                programTimeToday2200_0100);
170        assertOverlappedIntervalScore(0, false, programTimeToday2100_2400,
171                programTimeTomorrow0000_0300);
172        assertOverlappedIntervalScore(2 * oneHourInSec, false, programTimeToday2100_2400,
173                programTimeTomorrow2000_2300);
174        assertOverlappedIntervalScore(oneHourInSec, true, programTimeToday2200_0100,
175                programTimeTomorrow0000_0300);
176        assertOverlappedIntervalScore(oneHourInSec, false, programTimeToday2200_0100,
177                programTimeTomorrow2000_2300);
178        assertOverlappedIntervalScore(0, false, programTimeTomorrow0000_0300,
179                programTimeTomorrow2000_2300);
180    }
181
182    public void testGetTimeOfDayInSec() {
183        // Time was set as 00:00:00. So, getTimeOfDay must returns 0 (= 0 * 60 * 60 + 0 * 60 + 0).
184        assertEquals("TimeOfDayInSec", hourMinuteToSec(0, 0),
185                RoutineWatchEvaluator.getTimeOfDayInSec(todayAtHourMin(0, 0)));
186
187        // Time was set as 23:59:59. So, getTimeOfDay must returns 23 * 60 + 60 + 59 * 60 + 59.
188        assertEquals("TimeOfDayInSec", hourMinuteSecondToSec(23, 59, 59),
189                RoutineWatchEvaluator.getTimeOfDayInSec(todayAtHourMinSec(23, 59, 59)));
190    }
191
192    private void assertSplitTextToWords(String text, String... words) {
193        List<String> wordList = RoutineWatchEvaluator.splitTextToWords(text);
194        MoreAsserts.assertContentsInOrder(wordList, words);
195    }
196
197    private void assertMaximumMatchedWordSequenceLength(int expectedLength, String text1,
198            String text2) {
199        List<String> wordList1 = RoutineWatchEvaluator.splitTextToWords(text1);
200        List<String> wordList2 = RoutineWatchEvaluator.splitTextToWords(text2);
201        assertEquals("MaximumMatchedWordSequenceLength", expectedLength,
202                mEvaluator.calculateMaximumMatchedWordSequenceLength(wordList1, wordList2));
203        assertEquals("MaximumMatchedWordSequenceLength", expectedLength,
204                mEvaluator.calculateMaximumMatchedWordSequenceLength(wordList2, wordList1));
205    }
206
207    private void assertProgramTime(int expectedWeekDay, int expectedStartTimeOfDayInSec,
208            int expectedEndTimeOfDayInSec, ProgramTime actualProgramTime) {
209        assertEquals("Weekday", expectedWeekDay, actualProgramTime.weekDay);
210        assertEquals("StartTimeOfDayInSec", expectedStartTimeOfDayInSec,
211                actualProgramTime.startTimeOfDayInSec);
212        assertEquals("EndTimeOfDayInSec", expectedEndTimeOfDayInSec,
213                actualProgramTime.endTimeOfDayInSec);
214    }
215
216    private void assertOverlappedIntervalScore(int expectedSeconds, boolean overlappedOnSameDay,
217            ProgramTime t1, ProgramTime t2) {
218        double score = expectedSeconds;
219        if (!overlappedOnSameDay) {
220            score *= RoutineWatchEvaluator.MULTIPLIER_FOR_UNMATCHED_DAY_OF_WEEK;
221        }
222        // Two tests for testing commutative law.
223        assertEquals("OverlappedIntervalScore", score,
224                mEvaluator.calculateOverlappedIntervalScore(t1, t2));
225        assertEquals("OverlappedIntervalScore", score,
226                mEvaluator.calculateOverlappedIntervalScore(t2, t1));
227    }
228
229    private int hourMinuteToSec(int hour, int minute) {
230        return hourMinuteSecondToSec(hour, minute, 0);
231    }
232
233    private int hourMinuteSecondToSec(int hour, int minute, int second) {
234        return hour * 60 * 60 + minute * 60 + second;
235    }
236
237    private Calendar todayAtHourMin(int hour, int minute) {
238        return todayAtHourMinSec(hour, minute, 0);
239    }
240
241    private Calendar todayAtHourMinSec(int hour, int minute, int second) {
242        Calendar time = Calendar.getInstance();
243        time.set(Calendar.HOUR_OF_DAY, hour);
244        time.set(Calendar.MINUTE, minute);
245        time.set(Calendar.SECOND, second);
246        return time;
247    }
248
249    private Calendar tomorrowAtHourMin(int hour, int minute) {
250        Calendar time = todayAtHourMin(hour, minute);
251        time.add(Calendar.DATE, 1);
252        return time;
253    }
254
255    private Program createDummyProgram(Calendar startTime, long programDurationMs) {
256        long startTimeMs = startTime.getTimeInMillis();
257
258        return new Program.Builder().setStartTimeUtcMillis(startTimeMs)
259                .setEndTimeUtcMillis(startTimeMs + programDurationMs).build();
260    }
261
262    private static <T> void assertInOrder(T... items) {
263        TreeSet<T> copy = new TreeSet<>(Arrays.asList(items));
264        MoreAsserts.assertContentsInOrder(copy, items);
265    }
266}
267