Home | History | Annotate | Download | only in recommendation
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.tv.recommendation;
     18 
     19 import android.content.Context;
     20 
     21 import com.android.tv.data.Channel;
     22 import com.android.tv.testing.Utils;
     23 
     24 import org.mockito.Matchers;
     25 import org.mockito.Mockito;
     26 import org.mockito.invocation.InvocationOnMock;
     27 import org.mockito.stubbing.Answer;
     28 
     29 import java.util.ArrayList;
     30 import java.util.Collection;
     31 import java.util.List;
     32 import java.util.Random;
     33 import java.util.TreeMap;
     34 import java.util.concurrent.TimeUnit;
     35 
     36 public class RecommendationUtils {
     37     private static final String TAG = "RecommendationUtils";
     38     private static final long INVALID_CHANNEL_ID = -1;
     39 
     40     /**
     41      * Create a mock RecommendationDataManager backed by a {@link ChannelRecordSortedMapHelper}.
     42      */
     43     public static RecommendationDataManager createMockRecommendationDataManager(
     44             final ChannelRecordSortedMapHelper channelRecordSortedMap) {
     45         RecommendationDataManager dataManager = Mockito.mock(RecommendationDataManager.class);
     46         Mockito.doAnswer(new Answer<Integer>() {
     47             @Override
     48             public Integer answer(InvocationOnMock invocation) throws Throwable {
     49                 return channelRecordSortedMap.size();
     50             }
     51         }).when(dataManager).getChannelRecordCount();
     52         Mockito.doAnswer(new Answer<Collection<ChannelRecord>>() {
     53             @Override
     54             public Collection<ChannelRecord> answer(InvocationOnMock invocation) throws Throwable {
     55                 return channelRecordSortedMap.values();
     56             }
     57         }).when(dataManager).getChannelRecords();
     58         Mockito.doAnswer(new Answer<ChannelRecord>() {
     59             @Override
     60             public ChannelRecord answer(InvocationOnMock invocation) throws Throwable {
     61                 long channelId = (long) invocation.getArguments()[0];
     62                 return channelRecordSortedMap.get(channelId);
     63             }
     64         }).when(dataManager).getChannelRecord(Matchers.anyLong());
     65         return dataManager;
     66     }
     67 
     68     public static class ChannelRecordSortedMapHelper extends TreeMap<Long, ChannelRecord> {
     69         private final Context mContext;
     70         private Recommender mRecommender;
     71         private Random mRandom = Utils.createTestRandom();
     72 
     73         public ChannelRecordSortedMapHelper(Context context) {
     74             mContext = context;
     75         }
     76 
     77         public void setRecommender(Recommender recommender) {
     78             mRecommender = recommender;
     79         }
     80 
     81         public void resetRandom(Random random) {
     82             mRandom = random;
     83         }
     84 
     85         /**
     86          * Add new {@code numberOfChannels} channels by adding channel record to
     87          * {@code channelRecordMap} with no history.
     88          * This action corresponds to loading channels in the RecommendationDataManger.
     89          */
     90         public void addChannels(int numberOfChannels) {
     91             for (int i = 0; i < numberOfChannels; ++i) {
     92                 addChannel();
     93             }
     94         }
     95 
     96         /**
     97          * Add new one channel by adding channel record to {@code channelRecordMap} with no history.
     98          * This action corresponds to loading one channel in the RecommendationDataManger.
     99          *
    100          * @return The new channel was made by this method.
    101          */
    102         public Channel addChannel() {
    103             long channelId = size();
    104             Channel channel = new Channel.Builder().setId(channelId).build();
    105             ChannelRecord channelRecord = new ChannelRecord(mContext, channel, false);
    106             put(channelId, channelRecord);
    107             return channel;
    108         }
    109 
    110         /**
    111          * Add the watch logs which its durationTime is under {@code maxWatchDurationMs}.
    112          * Add until latest watch end time becomes bigger than {@code watchEndTimeMs},
    113          * starting from {@code watchStartTimeMs}.
    114          *
    115          * @return true if adding watch log success, otherwise false.
    116          */
    117         public boolean addRandomWatchLogs(long watchStartTimeMs, long watchEndTimeMs,
    118                 long maxWatchDurationMs) {
    119             long latestWatchEndTimeMs = watchStartTimeMs;
    120             long previousChannelId = INVALID_CHANNEL_ID;
    121             List<Long> channelIdList = new ArrayList<>(keySet());
    122             while (latestWatchEndTimeMs < watchEndTimeMs) {
    123                 long channelId = channelIdList.get(mRandom.nextInt(channelIdList.size()));
    124                 if (previousChannelId == channelId) {
    125                     // Time hopping with random minutes.
    126                     latestWatchEndTimeMs += TimeUnit.MINUTES.toMillis(mRandom.nextInt(30) + 1);
    127                 }
    128                 long watchedDurationMs = mRandom.nextInt((int) maxWatchDurationMs) + 1;
    129                 if (!addWatchLog(channelId, latestWatchEndTimeMs, watchedDurationMs)) {
    130                     return false;
    131                 }
    132                 latestWatchEndTimeMs += watchedDurationMs;
    133                 previousChannelId = channelId;
    134             }
    135             return true;
    136         }
    137 
    138         /**
    139          * Add new watch log to channel that id is {@code ChannelId}. Add watch log starts from
    140          * {@code watchStartTimeMs} with duration {@code durationTimeMs}. If adding is finished,
    141          * notify the recommender that there's a new watch log.
    142          *
    143          * @return true if adding watch log success, otherwise false.
    144          */
    145         public boolean addWatchLog(long channelId, long watchStartTimeMs, long durationTimeMs) {
    146             ChannelRecord channelRecord = get(channelId);
    147             if (channelRecord == null ||
    148                     watchStartTimeMs + durationTimeMs > System.currentTimeMillis()) {
    149                 return false;
    150             }
    151 
    152             channelRecord.logWatchHistory(new WatchedProgram(null, watchStartTimeMs,
    153                     watchStartTimeMs + durationTimeMs));
    154             if (mRecommender != null) {
    155                 mRecommender.onNewWatchLog(channelRecord);
    156             }
    157             return true;
    158         }
    159     }
    160 }
    161