• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 import com.android.tv.data.ChannelImpl;
21 import com.android.tv.data.api.Channel;
22 import com.android.tv.testing.utils.Utils;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.List;
26 import java.util.Random;
27 import java.util.TreeMap;
28 import java.util.concurrent.TimeUnit;
29 import org.mockito.Matchers;
30 import org.mockito.Mockito;
31 import org.mockito.invocation.InvocationOnMock;
32 import org.mockito.stubbing.Answer;
33 
34 public class RecommendationUtils {
35     private static final long INVALID_CHANNEL_ID = -1;
36 
37     /** Create a mock RecommendationDataManager backed by a {@link ChannelRecordSortedMapHelper}. */
createMockRecommendationDataManager( final ChannelRecordSortedMapHelper channelRecordSortedMap)38     public static RecommendationDataManager createMockRecommendationDataManager(
39             final ChannelRecordSortedMapHelper channelRecordSortedMap) {
40         RecommendationDataManager dataManager = Mockito.mock(RecommendationDataManager.class);
41         Mockito.doAnswer(
42                         new Answer<Integer>() {
43                             @Override
44                             public Integer answer(InvocationOnMock invocation) throws Throwable {
45                                 return channelRecordSortedMap.size();
46                             }
47                         })
48                 .when(dataManager)
49                 .getChannelRecordCount();
50         Mockito.doAnswer(
51                         new Answer<Collection<ChannelRecord>>() {
52                             @Override
53                             public Collection<ChannelRecord> answer(InvocationOnMock invocation)
54                                     throws Throwable {
55                                 return channelRecordSortedMap.values();
56                             }
57                         })
58                 .when(dataManager)
59                 .getChannelRecords();
60         Mockito.doAnswer(
61                         new Answer<ChannelRecord>() {
62                             @Override
63                             public ChannelRecord answer(InvocationOnMock invocation)
64                                     throws Throwable {
65                                 long channelId = (long) invocation.getArguments()[0];
66                                 return channelRecordSortedMap.get(channelId);
67                             }
68                         })
69                 .when(dataManager)
70                 .getChannelRecord(Matchers.anyLong());
71         return dataManager;
72     }
73 
74     public static class ChannelRecordSortedMapHelper extends TreeMap<Long, ChannelRecord> {
75         private final Context mContext;
76         private Recommender mRecommender;
77         private Random mRandom = Utils.createTestRandom();
78 
ChannelRecordSortedMapHelper(Context context)79         public ChannelRecordSortedMapHelper(Context context) {
80             mContext = context;
81         }
82 
setRecommender(Recommender recommender)83         public void setRecommender(Recommender recommender) {
84             mRecommender = recommender;
85         }
86 
resetRandom(Random random)87         public void resetRandom(Random random) {
88             mRandom = random;
89         }
90 
91         /**
92          * Add new {@code numberOfChannels} channels by adding channel record to {@code
93          * channelRecordMap} with no history. This action corresponds to loading channels in the
94          * RecommendationDataManger.
95          */
addChannels(int numberOfChannels)96         public void addChannels(int numberOfChannels) {
97             for (int i = 0; i < numberOfChannels; ++i) {
98                 addChannel();
99             }
100         }
101 
102         /**
103          * Add new one channel by adding channel record to {@code channelRecordMap} with no history.
104          * This action corresponds to loading one channel in the RecommendationDataManger.
105          *
106          * @return The new channel was made by this method.
107          */
addChannel()108         public Channel addChannel() {
109             long channelId = size();
110             ChannelImpl channel = new ChannelImpl.Builder().setId(channelId).build();
111             ChannelRecord channelRecord = new ChannelRecord(mContext, channel, false);
112             put(channelId, channelRecord);
113             return channel;
114         }
115 
116         /**
117          * Add the watch logs which its durationTime is under {@code maxWatchDurationMs}. Add until
118          * latest watch end time becomes bigger than {@code watchEndTimeMs}, starting from {@code
119          * watchStartTimeMs}.
120          *
121          * @return true if adding watch log success, otherwise false.
122          */
addRandomWatchLogs( long watchStartTimeMs, long watchEndTimeMs, long maxWatchDurationMs)123         public boolean addRandomWatchLogs(
124                 long watchStartTimeMs, long watchEndTimeMs, long maxWatchDurationMs) {
125             long latestWatchEndTimeMs = watchStartTimeMs;
126             long previousChannelId = INVALID_CHANNEL_ID;
127             List<Long> channelIdList = new ArrayList<>(keySet());
128             while (latestWatchEndTimeMs < watchEndTimeMs) {
129                 long channelId = channelIdList.get(mRandom.nextInt(channelIdList.size()));
130                 if (previousChannelId == channelId) {
131                     // Time hopping with random minutes.
132                     latestWatchEndTimeMs += TimeUnit.MINUTES.toMillis(mRandom.nextInt(30) + 1);
133                 }
134                 long watchedDurationMs = mRandom.nextInt((int) maxWatchDurationMs) + 1;
135                 if (!addWatchLog(channelId, latestWatchEndTimeMs, watchedDurationMs)) {
136                     return false;
137                 }
138                 latestWatchEndTimeMs += watchedDurationMs;
139                 previousChannelId = channelId;
140             }
141             return true;
142         }
143 
144         /**
145          * Add new watch log to channel that id is {@code ChannelId}. Add watch log starts from
146          * {@code watchStartTimeMs} with duration {@code durationTimeMs}. If adding is finished,
147          * notify the recommender that there's a new watch log.
148          *
149          * @return true if adding watch log success, otherwise false.
150          */
addWatchLog(long channelId, long watchStartTimeMs, long durationTimeMs)151         public boolean addWatchLog(long channelId, long watchStartTimeMs, long durationTimeMs) {
152             ChannelRecord channelRecord = get(channelId);
153             if (channelRecord == null
154                     || watchStartTimeMs + durationTimeMs > System.currentTimeMillis()) {
155                 return false;
156             }
157 
158             channelRecord.logWatchHistory(
159                     new WatchedProgram(null, watchStartTimeMs, watchStartTimeMs + durationTimeMs));
160             if (mRecommender != null) {
161                 mRecommender.onNewWatchLog(channelRecord);
162             }
163             return true;
164         }
165     }
166 }
167