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 19816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.content.Context; 20816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 21816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.data.Channel; 227d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalkoimport com.android.tv.testing.Utils; 23816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 24816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport org.mockito.Matchers; 25816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport org.mockito.Mockito; 26816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport org.mockito.invocation.InvocationOnMock; 27816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport org.mockito.stubbing.Answer; 28816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 29816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.ArrayList; 30816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Collection; 31816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.List; 32816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Random; 33816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.TreeMap; 34816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.concurrent.TimeUnit; 35816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 36816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkopublic class RecommendationUtils { 37816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final String TAG = "RecommendationUtils"; 38816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final long INVALID_CHANNEL_ID = -1; 39816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 40816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 41816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Create a mock RecommendationDataManager backed by a {@link ChannelRecordSortedMapHelper}. 42816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 43816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public static RecommendationDataManager createMockRecommendationDataManager( 44816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko final ChannelRecordSortedMapHelper channelRecordSortedMap) { 45816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko RecommendationDataManager dataManager = Mockito.mock(RecommendationDataManager.class); 46816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Mockito.doAnswer(new Answer<Integer>() { 47816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 48816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public Integer answer(InvocationOnMock invocation) throws Throwable { 49816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return channelRecordSortedMap.size(); 50816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 51816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko }).when(dataManager).getChannelRecordCount(); 52816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Mockito.doAnswer(new Answer<Collection<ChannelRecord>>() { 53816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 54816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public Collection<ChannelRecord> answer(InvocationOnMock invocation) throws Throwable { 55816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return channelRecordSortedMap.values(); 56816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 57816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko }).when(dataManager).getChannelRecords(); 58816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Mockito.doAnswer(new Answer<ChannelRecord>() { 59816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 60816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public ChannelRecord answer(InvocationOnMock invocation) throws Throwable { 61816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko long channelId = (long) invocation.getArguments()[0]; 62816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return channelRecordSortedMap.get(channelId); 63816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 64816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko }).when(dataManager).getChannelRecord(Matchers.anyLong()); 65816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return dataManager; 66816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 67816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 68816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public static class ChannelRecordSortedMapHelper extends TreeMap<Long, ChannelRecord> { 6907b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko private final Context mContext; 70816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private Recommender mRecommender; 717d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko private Random mRandom = Utils.createTestRandom(); 72816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 73816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public ChannelRecordSortedMapHelper(Context context) { 74816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mContext = context; 75816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 76816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 77816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void setRecommender(Recommender recommender) { 78816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mRecommender = recommender; 79816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 80816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 81816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void resetRandom(Random random) { 82816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mRandom = random; 83816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 84816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 85816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 86816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Add new {@code numberOfChannels} channels by adding channel record to 87816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * {@code channelRecordMap} with no history. 88816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * This action corresponds to loading channels in the RecommendationDataManger. 89816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 90816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public void addChannels(int numberOfChannels) { 91816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko for (int i = 0; i < numberOfChannels; ++i) { 92816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko addChannel(); 93816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 94816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 95816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 96816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 97816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Add new one channel by adding channel record to {@code channelRecordMap} with no history. 98816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * This action corresponds to loading one channel in the RecommendationDataManger. 99816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 100816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * @return The new channel was made by this method. 101816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 102816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public Channel addChannel() { 103816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko long channelId = size(); 104816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Channel channel = new Channel.Builder().setId(channelId).build(); 105816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko ChannelRecord channelRecord = new ChannelRecord(mContext, channel, false); 106816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko put(channelId, channelRecord); 107816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return channel; 108816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 109816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 110816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 111816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Add the watch logs which its durationTime is under {@code maxWatchDurationMs}. 112816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Add until latest watch end time becomes bigger than {@code watchEndTimeMs}, 113816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * starting from {@code watchStartTimeMs}. 114816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 115816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * @return true if adding watch log success, otherwise false. 116816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 117816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public boolean addRandomWatchLogs(long watchStartTimeMs, long watchEndTimeMs, 118816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko long maxWatchDurationMs) { 119816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko long latestWatchEndTimeMs = watchStartTimeMs; 120816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko long previousChannelId = INVALID_CHANNEL_ID; 121816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko List<Long> channelIdList = new ArrayList<>(keySet()); 122816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko while (latestWatchEndTimeMs < watchEndTimeMs) { 123816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko long channelId = channelIdList.get(mRandom.nextInt(channelIdList.size())); 124816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (previousChannelId == channelId) { 125816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Time hopping with random minutes. 126816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko latestWatchEndTimeMs += TimeUnit.MINUTES.toMillis(mRandom.nextInt(30) + 1); 127816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 128816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko long watchedDurationMs = mRandom.nextInt((int) maxWatchDurationMs) + 1; 129816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (!addWatchLog(channelId, latestWatchEndTimeMs, watchedDurationMs)) { 130816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return false; 131816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 132816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko latestWatchEndTimeMs += watchedDurationMs; 133816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko previousChannelId = channelId; 134816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 135816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return true; 136816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 137816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 138816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 139816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Add new watch log to channel that id is {@code ChannelId}. Add watch log starts from 140816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * {@code watchStartTimeMs} with duration {@code durationTimeMs}. If adding is finished, 141816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * notify the recommender that there's a new watch log. 142816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 143816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * @return true if adding watch log success, otherwise false. 144816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 145816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public boolean addWatchLog(long channelId, long watchStartTimeMs, long durationTimeMs) { 146816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko ChannelRecord channelRecord = get(channelId); 147816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (channelRecord == null || 148816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko watchStartTimeMs + durationTimeMs > System.currentTimeMillis()) { 149816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return false; 150816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 151816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 152816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko channelRecord.logWatchHistory(new WatchedProgram(null, watchStartTimeMs, 153816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko watchStartTimeMs + durationTimeMs)); 154816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (mRecommender != null) { 155816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mRecommender.onNewWatchLog(channelRecord); 156816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 157816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return true; 158816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 159816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 160816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko} 161