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