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 196ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport static android.support.test.InstrumentationRegistry.getContext; 206ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport static org.junit.Assert.assertEquals; 216ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport static org.junit.Assert.assertFalse; 226ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport static org.junit.Assert.assertTrue; 236ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko 2465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.support.test.filters.SmallTest; 25816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.test.MoreAsserts; 26816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 27816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.data.Channel; 28816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.recommendation.RecommendationUtils.ChannelRecordSortedMapHelper; 297d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalkoimport com.android.tv.testing.Utils; 30816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 316ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport org.junit.Before; 326ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport org.junit.Test; 336ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko 34816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.ArrayList; 35816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Arrays; 36816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Collections; 37816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Comparator; 38816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.HashMap; 39816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.List; 40816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Map; 41816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.concurrent.TimeUnit; 42816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 4307b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko@SmallTest 446ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkopublic class RecommenderTest { 45816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final int DEFAULT_NUMBER_OF_CHANNELS = 5; 46816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final long DEFAULT_WATCH_START_TIME_MS = 47816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko System.currentTimeMillis() - TimeUnit.DAYS.toMillis(2); 48816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final long DEFAULT_WATCH_END_TIME_MS = 49816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1); 50816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final long DEFAULT_MAX_WATCH_DURATION_MS = TimeUnit.HOURS.toMillis(1); 51816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 52816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private final Comparator<Channel> CHANNEL_SORT_KEY_COMPARATOR = new Comparator<Channel>() { 53816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 54816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public int compare(Channel lhs, Channel rhs) { 55816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return mRecommender.getChannelSortKey(lhs.getId()) 56816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko .compareTo(mRecommender.getChannelSortKey(rhs.getId())); 57816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 58816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko }; 59816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private final Runnable START_DATAMANAGER_RUNNABLE_ADD_FOUR_CHANNELS = new Runnable() { 60816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 61816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void run() { 62816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Add 4 channels in ChannelRecordMap for testing. Store the added channels to 63816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // mChannels_1 ~ mChannels_4. They are sorted by channel id in increasing order. 64816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mChannel_1 = mChannelRecordSortedMap.addChannel(); 65816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mChannel_2 = mChannelRecordSortedMap.addChannel(); 66816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mChannel_3 = mChannelRecordSortedMap.addChannel(); 67816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mChannel_4 = mChannelRecordSortedMap.addChannel(); 68816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 69816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko }; 70816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 71816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private RecommendationDataManager mDataManager; 72816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private Recommender mRecommender; 73816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private FakeEvaluator mEvaluator; 74816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private ChannelRecordSortedMapHelper mChannelRecordSortedMap; 75816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private boolean mOnRecommenderReady; 76816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private boolean mOnRecommendationChanged; 77816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private Channel mChannel_1; 78816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private Channel mChannel_2; 79816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private Channel mChannel_3; 80816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private Channel mChannel_4; 81816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 826ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko @Before 836ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko public void setUp() { 84816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mChannelRecordSortedMap = new ChannelRecordSortedMapHelper(getContext()); 85816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mDataManager = RecommendationUtils 86816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko .createMockRecommendationDataManager(mChannelRecordSortedMap); 877d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko mChannelRecordSortedMap.resetRandom(Utils.createTestRandom()); 88816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 89816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 906ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko @Test 91816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void testRecommendChannels_includeRecommendedOnly_allChannelsHaveNoScore() { 92816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko createRecommender(true, START_DATAMANAGER_RUNNABLE_ADD_FOUR_CHANNELS); 93816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 94816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Recommender doesn't recommend any channels because all channels are not recommended. 95816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko assertEquals(0, mRecommender.recommendChannels().size()); 96816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko assertEquals(0, mRecommender.recommendChannels(-5).size()); 97816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko assertEquals(0, mRecommender.recommendChannels(0).size()); 98816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko assertEquals(0, mRecommender.recommendChannels(3).size()); 99816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko assertEquals(0, mRecommender.recommendChannels(4).size()); 100816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko assertEquals(0, mRecommender.recommendChannels(5).size()); 101816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 102816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1036ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko @Test 104816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void testRecommendChannels_notIncludeRecommendedOnly_allChannelsHaveNoScore() { 105816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko createRecommender(false, START_DATAMANAGER_RUNNABLE_ADD_FOUR_CHANNELS); 106816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 107816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Recommender recommends every channel because it recommends not-recommended channels too. 108816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko assertEquals(4, mRecommender.recommendChannels().size()); 109816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko assertEquals(0, mRecommender.recommendChannels(-5).size()); 110816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko assertEquals(0, mRecommender.recommendChannels(0).size()); 111816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko assertEquals(3, mRecommender.recommendChannels(3).size()); 112816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko assertEquals(4, mRecommender.recommendChannels(4).size()); 113816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko assertEquals(4, mRecommender.recommendChannels(5).size()); 114816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 115816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1166ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko @Test 117816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void testRecommendChannels_includeRecommendedOnly_allChannelsHaveScore() { 118816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko createRecommender(true, START_DATAMANAGER_RUNNABLE_ADD_FOUR_CHANNELS); 119816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 120816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko setChannelScores_scoreIncreasesAsChannelIdIncreases(); 121816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 122816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // recommendChannels must be sorted by score in decreasing order. 123816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // (i.e. sorted by channel ID in decreasing order in this case) 124816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko MoreAsserts.assertContentsInOrder(mRecommender.recommendChannels(), 125816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mChannel_4, mChannel_3, mChannel_2, mChannel_1); 126816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko assertEquals(0, mRecommender.recommendChannels(-5).size()); 127816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko assertEquals(0, mRecommender.recommendChannels(0).size()); 128816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko MoreAsserts.assertContentsInOrder(mRecommender.recommendChannels(3), 129816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mChannel_4, mChannel_3, mChannel_2); 130816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko MoreAsserts.assertContentsInOrder(mRecommender.recommendChannels(4), 131816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mChannel_4, mChannel_3, mChannel_2, mChannel_1); 132816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko MoreAsserts.assertContentsInOrder(mRecommender.recommendChannels(5), 133816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mChannel_4, mChannel_3, mChannel_2, mChannel_1); 134816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 135816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1366ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko @Test 137816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void testRecommendChannels_notIncludeRecommendedOnly_allChannelsHaveScore() { 138816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko createRecommender(false, START_DATAMANAGER_RUNNABLE_ADD_FOUR_CHANNELS); 139816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 140816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko setChannelScores_scoreIncreasesAsChannelIdIncreases(); 141816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 142816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // recommendChannels must be sorted by score in decreasing order. 143816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // (i.e. sorted by channel ID in decreasing order in this case) 144816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko MoreAsserts.assertContentsInOrder(mRecommender.recommendChannels(), 145816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mChannel_4, mChannel_3, mChannel_2, mChannel_1); 146816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko assertEquals(0, mRecommender.recommendChannels(-5).size()); 147816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko assertEquals(0, mRecommender.recommendChannels(0).size()); 148816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko MoreAsserts.assertContentsInOrder(mRecommender.recommendChannels(3), 149816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mChannel_4, mChannel_3, mChannel_2); 150816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko MoreAsserts.assertContentsInOrder(mRecommender.recommendChannels(4), 151816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mChannel_4, mChannel_3, mChannel_2, mChannel_1); 152816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko MoreAsserts.assertContentsInOrder(mRecommender.recommendChannels(5), 153816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mChannel_4, mChannel_3, mChannel_2, mChannel_1); 154816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 155816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1566ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko @Test 157816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void testRecommendChannels_includeRecommendedOnly_fewChannelsHaveScore() { 158816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko createRecommender(true, START_DATAMANAGER_RUNNABLE_ADD_FOUR_CHANNELS); 159816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 160816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mEvaluator.setChannelScore(mChannel_1.getId(), 1.0); 161816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mEvaluator.setChannelScore(mChannel_2.getId(), 1.0); 162816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 163816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Only two channels are recommended because recommender doesn't recommend other channels. 164816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko MoreAsserts.assertContentsInAnyOrder(mRecommender.recommendChannels(), 165816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mChannel_1, mChannel_2); 166816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko assertEquals(0, mRecommender.recommendChannels(-5).size()); 167816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko assertEquals(0, mRecommender.recommendChannels(0).size()); 168816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko MoreAsserts.assertContentsInAnyOrder(mRecommender.recommendChannels(3), 169816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mChannel_1, mChannel_2); 170816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko MoreAsserts.assertContentsInAnyOrder(mRecommender.recommendChannels(4), 171816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mChannel_1, mChannel_2); 172816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko MoreAsserts.assertContentsInAnyOrder(mRecommender.recommendChannels(5), 173816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mChannel_1, mChannel_2); 174816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 175816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 1766ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko @Test 177816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void testRecommendChannels_notIncludeRecommendedOnly_fewChannelsHaveScore() { 178816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko createRecommender(false, START_DATAMANAGER_RUNNABLE_ADD_FOUR_CHANNELS); 179816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 180816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mEvaluator.setChannelScore(mChannel_1.getId(), 1.0); 181816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mEvaluator.setChannelScore(mChannel_2.getId(), 1.0); 182816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 183816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko assertEquals(4, mRecommender.recommendChannels().size()); 184816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko MoreAsserts.assertContentsInAnyOrder(mRecommender.recommendChannels().subList(0, 2), 185816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mChannel_1, mChannel_2); 186816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 187816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko assertEquals(0, mRecommender.recommendChannels(-5).size()); 188816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko assertEquals(0, mRecommender.recommendChannels(0).size()); 189816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 190816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko assertEquals(3, mRecommender.recommendChannels(3).size()); 191816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko MoreAsserts.assertContentsInAnyOrder(mRecommender.recommendChannels(3).subList(0, 2), 192816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mChannel_1, mChannel_2); 193816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 194816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko assertEquals(4, mRecommender.recommendChannels(4).size()); 195816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko MoreAsserts.assertContentsInAnyOrder(mRecommender.recommendChannels(4).subList(0, 2), 196816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mChannel_1, mChannel_2); 197816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 198816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko assertEquals(4, mRecommender.recommendChannels(5).size()); 199816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko MoreAsserts.assertContentsInAnyOrder(mRecommender.recommendChannels(5).subList(0, 2), 200816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mChannel_1, mChannel_2); 201816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 202816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 2036ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko @Test 204816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void testGetChannelSortKey_recommendAllChannels() { 205816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko createRecommender(true, START_DATAMANAGER_RUNNABLE_ADD_FOUR_CHANNELS); 206816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 207816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko setChannelScores_scoreIncreasesAsChannelIdIncreases(); 208816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 209816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko List<Channel> expectedChannelList = mRecommender.recommendChannels(); 210816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko List<Channel> channelList = Arrays.asList(mChannel_1, mChannel_2, mChannel_3, mChannel_4); 211816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Collections.sort(channelList, CHANNEL_SORT_KEY_COMPARATOR); 212816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 213816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Recommended channel list and channel list sorted by sort key must be the same. 214816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko MoreAsserts.assertContentsInOrder(channelList, expectedChannelList.toArray()); 215816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko assertSortKeyNotInvalid(channelList); 216816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 217816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 2186ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko @Test 219816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void testGetChannelSortKey_recommendFewChannels() { 220816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Test with recommending 3 channels. 221816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko createRecommender(true, START_DATAMANAGER_RUNNABLE_ADD_FOUR_CHANNELS); 222816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 223816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko setChannelScores_scoreIncreasesAsChannelIdIncreases(); 224816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 225816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko List<Channel> expectedChannelList = mRecommender.recommendChannels(3); 226816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // A channel which is not recommended by the recommender has to get an invalid sort key. 227816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko assertEquals(Recommender.INVALID_CHANNEL_SORT_KEY, 228816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mRecommender.getChannelSortKey(mChannel_1.getId())); 229816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 230816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko List<Channel> channelList = Arrays.asList(mChannel_2, mChannel_3, mChannel_4); 231816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Collections.sort(channelList, CHANNEL_SORT_KEY_COMPARATOR); 232816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 233816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko MoreAsserts.assertContentsInOrder(channelList, expectedChannelList.toArray()); 234816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko assertSortKeyNotInvalid(channelList); 235816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 236816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 2376ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko @Test 238816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void testListener_onRecommendationChanged() { 239816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko createRecommender(true, START_DATAMANAGER_RUNNABLE_ADD_FOUR_CHANNELS); 240816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // FakeEvaluator doesn't recommend a channel with empty watch log. As every channel 241816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // doesn't have a watch log, nothing is recommended and recommendation isn't changed. 242816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko assertFalse(mOnRecommendationChanged); 243816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 244816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Set lastRecommendationUpdatedTimeUtcMs to check recommendation changed because, 245816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // recommender has a minimum recommendation update period. 246816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mRecommender.setLastRecommendationUpdatedTimeUtcMs( 247816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(10)); 248816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko long latestWatchEndTimeMs = DEFAULT_WATCH_START_TIME_MS; 249816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko for (long channelId : mChannelRecordSortedMap.keySet()) { 250816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mEvaluator.setChannelScore(channelId, 1.0); 251816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Add a log to recalculate the recommendation score. 252816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko assertTrue(mChannelRecordSortedMap.addWatchLog(channelId, latestWatchEndTimeMs, 253816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko TimeUnit.MINUTES.toMillis(10))); 254816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko latestWatchEndTimeMs += TimeUnit.MINUTES.toMillis(10); 255816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 256816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 257816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // onRecommendationChanged must be called, because recommend channels are not empty, 258816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // by setting score to each channel. 259816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko assertTrue(mOnRecommendationChanged); 260816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 261816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 2626ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko @Test 263816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void testListener_onRecommenderReady() { 264816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko createRecommender(true, new Runnable() { 265816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 266816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void run() { 267816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mChannelRecordSortedMap.addChannels(DEFAULT_NUMBER_OF_CHANNELS); 268816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mChannelRecordSortedMap.addRandomWatchLogs(DEFAULT_WATCH_START_TIME_MS, 269816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko DEFAULT_WATCH_END_TIME_MS, DEFAULT_MAX_WATCH_DURATION_MS); 270816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 271816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko }); 272816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 273816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // After loading channels and watch logs are finished, recommender must be available to use. 274816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko assertTrue(mOnRecommenderReady); 275816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 276816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 277816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private void assertSortKeyNotInvalid(List<Channel> channelList) { 278816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko for (Channel channel : channelList) { 279816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko MoreAsserts.assertNotEqual(Recommender.INVALID_CHANNEL_SORT_KEY, 280816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mRecommender.getChannelSortKey(channel.getId())); 281816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 282816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 283816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 284816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private void createRecommender(boolean includeRecommendedOnly, 285816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Runnable startDataManagerRunnable) { 286816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mRecommender = new Recommender(new Recommender.Listener() { 287816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 288816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void onRecommenderReady() { 289816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mOnRecommenderReady = true; 290816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 291816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 292816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void onRecommendationChanged() { 293816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mOnRecommendationChanged = true; 294816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 295816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko }, includeRecommendedOnly, mDataManager); 296816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 297816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mEvaluator = new FakeEvaluator(); 298816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mRecommender.registerEvaluator(mEvaluator); 299816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mChannelRecordSortedMap.setRecommender(mRecommender); 300816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 301816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // When mRecommender is instantiated, its dataManager will be started, and load channels 302816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // and watch history data if it is not started. 303816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (startDataManagerRunnable != null) { 304816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko startDataManagerRunnable.run(); 305816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mRecommender.onChannelRecordChanged(); 306816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 307816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // After loading channels and watch history data are finished, 308816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // RecommendationDataManager calls listener.onChannelRecordLoaded() 309816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // which will be mRecommender.onChannelRecordLoaded(). 310816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mRecommender.onChannelRecordLoaded(); 311816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 312816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 313816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private List<Long> getChannelIdListSorted() { 314816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return new ArrayList<>(mChannelRecordSortedMap.keySet()); 315816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 316816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 317816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private void setChannelScores_scoreIncreasesAsChannelIdIncreases() { 318816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko List<Long> channelIdList = getChannelIdListSorted(); 319816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko double score = Math.pow(0.5, channelIdList.size()); 320816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko for (long channelId : channelIdList) { 321816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Channel with smaller id has smaller score than channel with higher id. 322816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mEvaluator.setChannelScore(channelId, score); 323816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko score *= 2.0; 324816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 325816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 326816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 327816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private class FakeEvaluator extends Recommender.Evaluator { 32807b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko private final Map<Long, Double> mChannelScore = new HashMap<>(); 329816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 330816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 331816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public double evaluateChannel(long channelId) { 332816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (getRecommender().getChannelRecord(channelId) == null) { 333816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return NOT_RECOMMENDED; 334816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 335816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Double score = mChannelScore.get(channelId); 336816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return score == null ? NOT_RECOMMENDED : score; 337816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 338816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 339816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void setChannelScore(long channelId, double score) { 340816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mChannelScore.put(channelId, score); 341816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 342816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 343816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko} 344