/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.tv.recommendation; import android.content.Context; import com.android.tv.data.Channel; import com.android.tv.testing.Utils; import org.mockito.Matchers; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Random; import java.util.TreeMap; import java.util.concurrent.TimeUnit; public class RecommendationUtils { private static final long INVALID_CHANNEL_ID = -1; /** * Create a mock RecommendationDataManager backed by a {@link ChannelRecordSortedMapHelper}. */ public static RecommendationDataManager createMockRecommendationDataManager( final ChannelRecordSortedMapHelper channelRecordSortedMap) { RecommendationDataManager dataManager = Mockito.mock(RecommendationDataManager.class); Mockito.doAnswer(new Answer() { @Override public Integer answer(InvocationOnMock invocation) throws Throwable { return channelRecordSortedMap.size(); } }).when(dataManager).getChannelRecordCount(); Mockito.doAnswer(new Answer>() { @Override public Collection answer(InvocationOnMock invocation) throws Throwable { return channelRecordSortedMap.values(); } }).when(dataManager).getChannelRecords(); Mockito.doAnswer(new Answer() { @Override public ChannelRecord answer(InvocationOnMock invocation) throws Throwable { long channelId = (long) invocation.getArguments()[0]; return channelRecordSortedMap.get(channelId); } }).when(dataManager).getChannelRecord(Matchers.anyLong()); return dataManager; } public static class ChannelRecordSortedMapHelper extends TreeMap { private final Context mContext; private Recommender mRecommender; private Random mRandom = Utils.createTestRandom(); public ChannelRecordSortedMapHelper(Context context) { mContext = context; } public void setRecommender(Recommender recommender) { mRecommender = recommender; } public void resetRandom(Random random) { mRandom = random; } /** * Add new {@code numberOfChannels} channels by adding channel record to * {@code channelRecordMap} with no history. * This action corresponds to loading channels in the RecommendationDataManger. */ public void addChannels(int numberOfChannels) { for (int i = 0; i < numberOfChannels; ++i) { addChannel(); } } /** * Add new one channel by adding channel record to {@code channelRecordMap} with no history. * This action corresponds to loading one channel in the RecommendationDataManger. * * @return The new channel was made by this method. */ public Channel addChannel() { long channelId = size(); Channel channel = new Channel.Builder().setId(channelId).build(); ChannelRecord channelRecord = new ChannelRecord(mContext, channel, false); put(channelId, channelRecord); return channel; } /** * Add the watch logs which its durationTime is under {@code maxWatchDurationMs}. * Add until latest watch end time becomes bigger than {@code watchEndTimeMs}, * starting from {@code watchStartTimeMs}. * * @return true if adding watch log success, otherwise false. */ public boolean addRandomWatchLogs(long watchStartTimeMs, long watchEndTimeMs, long maxWatchDurationMs) { long latestWatchEndTimeMs = watchStartTimeMs; long previousChannelId = INVALID_CHANNEL_ID; List channelIdList = new ArrayList<>(keySet()); while (latestWatchEndTimeMs < watchEndTimeMs) { long channelId = channelIdList.get(mRandom.nextInt(channelIdList.size())); if (previousChannelId == channelId) { // Time hopping with random minutes. latestWatchEndTimeMs += TimeUnit.MINUTES.toMillis(mRandom.nextInt(30) + 1); } long watchedDurationMs = mRandom.nextInt((int) maxWatchDurationMs) + 1; if (!addWatchLog(channelId, latestWatchEndTimeMs, watchedDurationMs)) { return false; } latestWatchEndTimeMs += watchedDurationMs; previousChannelId = channelId; } return true; } /** * Add new watch log to channel that id is {@code ChannelId}. Add watch log starts from * {@code watchStartTimeMs} with duration {@code durationTimeMs}. If adding is finished, * notify the recommender that there's a new watch log. * * @return true if adding watch log success, otherwise false. */ public boolean addWatchLog(long channelId, long watchStartTimeMs, long durationTimeMs) { ChannelRecord channelRecord = get(channelId); if (channelRecord == null || watchStartTimeMs + durationTimeMs > System.currentTimeMillis()) { return false; } channelRecord.logWatchHistory(new WatchedProgram(null, watchStartTimeMs, watchStartTimeMs + durationTimeMs)); if (mRecommender != null) { mRecommender.onNewWatchLog(channelRecord); } return true; } } }