1816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko/* 2816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Copyright (C) 2015 The Android Open Source Project 3816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 4816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Licensed under the Apache License, Version 2.0 (the "License"); 5816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * you may not use this file except in compliance with the License. 6816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * You may obtain a copy of the License at 7816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 8816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * http://www.apache.org/licenses/LICENSE-2.0 9816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 10816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Unless required by applicable law or agreed to in writing, software 11816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * distributed under the License is distributed on an "AS IS" BASIS, 12816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * See the License for the specific language governing permissions and 14816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * limitations under the License. 15816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 16816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 17816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkopackage com.android.tv.recommendation; 18816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 192e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalkoimport android.support.annotation.Nullable; 20816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.support.annotation.VisibleForTesting; 212e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalkoimport android.text.TextUtils; 22816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 23816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.data.Program; 24816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 25816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.text.BreakIterator; 26816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.ArrayList; 27816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Calendar; 28816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.List; 29816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.concurrent.TimeUnit; 30816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 31816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkopublic class RoutineWatchEvaluator extends Recommender.Evaluator { 32816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // TODO: test and refine constant values in WatchedProgramRecommender in order to 33816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // improve the performance of this recommender. 34816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final double REQUIRED_MIN_SCORE = 0.15; 35816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @VisibleForTesting 36816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko static final double MULTIPLIER_FOR_UNMATCHED_DAY_OF_WEEK = 0.7; 37816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final double TITLE_MATCH_WEIGHT = 0.5; 38816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final double TIME_MATCH_WEIGHT = 1 - TITLE_MATCH_WEIGHT; 39816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final long DIFF_MS_TOLERANCE_FOR_OLD_PROGRAM = TimeUnit.DAYS.toMillis(14); 40816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final long MAX_DIFF_MS_FOR_OLD_PROGRAM = TimeUnit.DAYS.toMillis(56); 41816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 42816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 43816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public double evaluateChannel(long channelId) { 44816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko ChannelRecord cr = getRecommender().getChannelRecord(channelId); 45816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (cr == null) { 46816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return NOT_RECOMMENDED; 47816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 48816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 49816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Program currentProgram = cr.getCurrentProgram(); 50816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (currentProgram == null) { 51816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return NOT_RECOMMENDED; 52816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 53816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 54816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko WatchedProgram[] watchHistory = cr.getWatchHistory(); 55816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (watchHistory.length < 1) { 56816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return NOT_RECOMMENDED; 57816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 58816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 59816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Program watchedProgram = watchHistory[watchHistory.length - 1].getProgram(); 60816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko long startTimeDiffMsWithCurrentProgram = currentProgram.getStartTimeUtcMillis() 61816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko - watchedProgram.getStartTimeUtcMillis(); 62816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (startTimeDiffMsWithCurrentProgram >= MAX_DIFF_MS_FOR_OLD_PROGRAM) { 63816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return NOT_RECOMMENDED; 64816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 65816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 66816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko double maxScore = NOT_RECOMMENDED; 67816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko long watchedDurationMs = watchHistory[watchHistory.length - 1].getWatchedDurationMs(); 68816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko for (int i = watchHistory.length - 2; i >= 0; --i) { 69816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (watchedProgram.getStartTimeUtcMillis() 70816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko == watchHistory[i].getProgram().getStartTimeUtcMillis()) { 71816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko watchedDurationMs += watchHistory[i].getWatchedDurationMs(); 72816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } else { 73816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko double score = calculateRoutineWatchScore( 74816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko currentProgram, watchedProgram, watchedDurationMs); 75816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (score >= REQUIRED_MIN_SCORE && score > maxScore) { 76816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko maxScore = score; 77816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 78816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko watchedProgram = watchHistory[i].getProgram(); 79816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko watchedDurationMs = watchHistory[i].getWatchedDurationMs(); 80816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko startTimeDiffMsWithCurrentProgram = currentProgram.getStartTimeUtcMillis() 81816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko - watchedProgram.getStartTimeUtcMillis(); 82816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (startTimeDiffMsWithCurrentProgram >= MAX_DIFF_MS_FOR_OLD_PROGRAM) { 83816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return maxScore; 84816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 85816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 86816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 87816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko double score = calculateRoutineWatchScore( 88816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko currentProgram, watchedProgram, watchedDurationMs); 89816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (score >= REQUIRED_MIN_SCORE && score > maxScore) { 90816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko maxScore = score; 91816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 92816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return maxScore; 93816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 94816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 952e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko private static double calculateRoutineWatchScore(Program currentProgram, Program watchedProgram, 962e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko long watchedDurationMs) { 97816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko double timeMatchScore = calculateTimeMatchScore(currentProgram, watchedProgram); 98816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko double titleMatchScore = calculateTitleMatchScore( 99816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko currentProgram.getTitle(), watchedProgram.getTitle()); 100816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko double watchDurationScore = calculateWatchDurationScore(watchedProgram, watchedDurationMs); 101816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko long diffMs = currentProgram.getStartTimeUtcMillis() 102816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko - watchedProgram.getStartTimeUtcMillis(); 103816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko double multiplierForOldProgram = (diffMs < MAX_DIFF_MS_FOR_OLD_PROGRAM) 104816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko ? 1.0 - (double) Math.max(diffMs - DIFF_MS_TOLERANCE_FOR_OLD_PROGRAM, 0) 105816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko / (MAX_DIFF_MS_FOR_OLD_PROGRAM - DIFF_MS_TOLERANCE_FOR_OLD_PROGRAM) 106816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko : 0.0; 107816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return (titleMatchScore * TITLE_MATCH_WEIGHT + timeMatchScore * TIME_MATCH_WEIGHT) 108816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * watchDurationScore * multiplierForOldProgram; 109816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 110816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1112e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko @VisibleForTesting 1122e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko static double calculateTitleMatchScore(@Nullable String title1, @Nullable String title2) { 1132e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko if (TextUtils.isEmpty(title1) || TextUtils.isEmpty(title2)) { 1142e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko return 0; 1152e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko } 116816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko List<String> wordList1 = splitTextToWords(title1); 117816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko List<String> wordList2 = splitTextToWords(title2); 1182e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko if (wordList1.isEmpty() || wordList2.isEmpty()) { 1192e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko return 0; 1202e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko } 121816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko int maxMatchedWordSeqLen = calculateMaximumMatchedWordSequenceLength( 122816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko wordList1, wordList2); 123816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 124816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // F-measure score 125816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko double precision = (double) maxMatchedWordSeqLen / wordList1.size(); 126816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko double recall = (double) maxMatchedWordSeqLen / wordList2.size(); 127816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return 2.0 * precision * recall / (precision + recall); 128816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 129816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 130816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @VisibleForTesting 1312e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko static int calculateMaximumMatchedWordSequenceLength(List<String> toSearchWords, 1322e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko List<String> toMatchWords) { 133816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko int[] matchedWordSeqLen = new int[toMatchWords.size()]; 134816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko int maxMatchedWordSeqLen = 0; 135816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko for (String word : toSearchWords) { 136816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko for (int j = toMatchWords.size() - 1; j >= 0; --j) { 137816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (word.equals(toMatchWords.get(j))) { 138816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko matchedWordSeqLen[j] = j > 0 ? matchedWordSeqLen[j - 1] + 1 : 1; 139816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } else { 140816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko maxMatchedWordSeqLen = Math.max(maxMatchedWordSeqLen, matchedWordSeqLen[j]); 141816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko matchedWordSeqLen[j] = 0; 142816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 143816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 144816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 145816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko for (int len : matchedWordSeqLen) { 146816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko maxMatchedWordSeqLen = Math.max(maxMatchedWordSeqLen, len); 147816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 148816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 149816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return maxMatchedWordSeqLen; 150816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 151816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1522e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko private static double calculateTimeMatchScore(Program p1, Program p2) { 153816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko ProgramTime t1 = ProgramTime.createFromProgram(p1); 154816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko ProgramTime t2 = ProgramTime.createFromProgram(p2); 155816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 156816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko double dupTimeScore = calculateOverlappedIntervalScore(t1, t2); 157816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 158816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // F-measure score 159816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko double precision = dupTimeScore / (t1.endTimeOfDayInSec - t1.startTimeOfDayInSec); 160816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko double recall = dupTimeScore / (t2.endTimeOfDayInSec - t2.startTimeOfDayInSec); 161816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return 2.0 * precision * recall / (precision + recall); 162816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 163816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 164816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @VisibleForTesting 1652e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko static double calculateOverlappedIntervalScore(ProgramTime t1, ProgramTime t2) { 166816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (t1.dayChanged && !t2.dayChanged) { 167816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Swap two values. 168816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return calculateOverlappedIntervalScore(t2, t1); 169816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 170816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 171816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko boolean sameDay = false; 172816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Handle cases like (00:00 - 02:00) - (01:00 - 03:00) or (22:00 - 25:00) - (23:00 - 26:00). 173816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko double score = Math.max(0, Math.min(t1.endTimeOfDayInSec, t2.endTimeOfDayInSec) 174816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko - Math.max(t1.startTimeOfDayInSec, t2.startTimeOfDayInSec)); 175816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (score > 0) { 176816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko sameDay = (t1.weekDay == t2.weekDay); 177816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } else if (t1.dayChanged != t2.dayChanged) { 178816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // To handle cases like t1 : (00:00 - 01:00) and t2 : (23:00 - 25:00). 179816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko score = Math.max(0, Math.min(t1.endTimeOfDayInSec, t2.endTimeOfDayInSec - 24 * 60 * 60) 180816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko - t1.startTimeOfDayInSec); 181816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Same day if next day of t2's start day equals to t1's start day. (1 <= weekDay <= 7) 182816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko sameDay = (t1.weekDay == ((t2.weekDay % 7) + 1)); 183816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 184816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 185816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (!sameDay) { 186816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko score *= MULTIPLIER_FOR_UNMATCHED_DAY_OF_WEEK; 187816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 188816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return score; 189816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 190816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1912e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko private static double calculateWatchDurationScore(Program program, long durationMs) { 192816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return (double) durationMs 193816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko / (program.getEndTimeUtcMillis() - program.getStartTimeUtcMillis()); 194816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 195816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 196816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @VisibleForTesting 197816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko static int getTimeOfDayInSec(Calendar time) { 198816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return time.get(Calendar.HOUR_OF_DAY) * 60 * 60 199816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko + time.get(Calendar.MINUTE) * 60 200816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko + time.get(Calendar.SECOND); 201816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 202816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 203816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @VisibleForTesting 204816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko static List<String> splitTextToWords(String text) { 205816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko List<String> wordList = new ArrayList<>(); 206816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko BreakIterator boundary = BreakIterator.getWordInstance(); 207816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko boundary.setText(text); 208816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko int start = boundary.first(); 209816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko for (int end = boundary.next(); end != BreakIterator.DONE; 210816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko start = end, end = boundary.next()) { 211816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko String word = text.substring(start, end); 212816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (Character.isLetterOrDigit(word.charAt(0))) { 213816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko wordList.add(word); 214816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 215816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 216816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return wordList; 217816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 218816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 219816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @VisibleForTesting 220816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko static class ProgramTime { 221816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko final int startTimeOfDayInSec; 222816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko final int endTimeOfDayInSec; 223816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko final int weekDay; 224816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko final boolean dayChanged; 225816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 226816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public static ProgramTime createFromProgram(Program p) { 227816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Calendar time = Calendar.getInstance(); 228816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 229816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko time.setTimeInMillis(p.getStartTimeUtcMillis()); 230816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko int weekDay = time.get(Calendar.DAY_OF_WEEK); 231816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko int startTimeOfDayInSec = getTimeOfDayInSec(time); 232816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 233816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko time.setTimeInMillis(p.getEndTimeUtcMillis()); 234816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko boolean dayChanged = (weekDay != time.get(Calendar.DAY_OF_WEEK)); 235816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Set maximum program duration time to 12 hours. 236816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko int endTimeOfDayInSec = startTimeOfDayInSec + 237816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko (int) Math.min(p.getEndTimeUtcMillis() - p.getStartTimeUtcMillis(), 238816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko TimeUnit.HOURS.toMillis(12)) / 1000; 239816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 240816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return new ProgramTime(startTimeOfDayInSec, endTimeOfDayInSec, weekDay, dayChanged); 241816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 242816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 243816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private ProgramTime(int startTimeOfDayInSec, int endTimeOfDayInSec, int weekDay, 244816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko boolean dayChanged) { 245816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko this.startTimeOfDayInSec = startTimeOfDayInSec; 246816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko this.endTimeOfDayInSec = endTimeOfDayInSec; 247816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko this.weekDay = weekDay; 248816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko this.dayChanged = dayChanged; 249816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 250816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 251816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko} 252