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.testing.data;
18 
19 import android.content.Context;
20 import android.database.Cursor;
21 import android.media.tv.TvContentRating;
22 import android.media.tv.TvContract;
23 import com.android.tv.testing.R;
24 import com.android.tv.testing.utils.Utils;
25 import com.google.common.collect.ImmutableList;
26 import java.util.Objects;
27 import java.util.concurrent.TimeUnit;
28 
29 public final class ProgramInfo {
30     /** If this is specify for title, it will be generated by adding index. */
31     public static final String GEN_TITLE = "";
32 
33     /**
34      * If this is specify for episode title, it will be generated by adding index. Also, season and
35      * episode numbers would be generated, too. see: {@link #build} for detail.
36      */
37     public static final String GEN_EPISODE = "";
38 
39     private static final int SEASON_MAX = 10;
40     private static final int EPISODE_MAX = 12;
41 
42     /**
43      * If this is specify for poster art, it will be selected one of {@link #POSTER_ARTS_RES} in
44      * order.
45      */
46     public static final String GEN_POSTER = "GEN";
47 
48     private static final int[] POSTER_ARTS_RES = {
49         0,
50         R.drawable.blue,
51         R.drawable.red_large,
52         R.drawable.green,
53         R.drawable.red,
54         R.drawable.green_large,
55         R.drawable.blue_small
56     };
57 
58     /**
59      * If this is specified for duration, it will be selected one of {@link #DURATIONS_MS} in order.
60      */
61     public static final int GEN_DURATION = -1;
62 
63     private static final long[] DURATIONS_MS = {
64         TimeUnit.MINUTES.toMillis(15),
65         TimeUnit.MINUTES.toMillis(45),
66         TimeUnit.MINUTES.toMillis(90),
67         TimeUnit.MINUTES.toMillis(60),
68         TimeUnit.MINUTES.toMillis(30),
69         TimeUnit.MINUTES.toMillis(45),
70         TimeUnit.MINUTES.toMillis(60),
71         TimeUnit.MINUTES.toMillis(90),
72         TimeUnit.HOURS.toMillis(5)
73     };
74     private static long durationsSumMs;
75 
76     static {
77         durationsSumMs = 0;
78         for (long duration : DURATIONS_MS) {
79             durationsSumMs += duration;
80         }
81     }
82 
83     /** If this is specified for genre, it will be selected one of {@link #GENRES} in order. */
84     public static final String GEN_GENRE = "GEN";
85 
86     private static final String[] GENRES = {
87         "",
88         TvContract.Programs.Genres.SPORTS,
89         TvContract.Programs.Genres.NEWS,
90         TvContract.Programs.Genres.SHOPPING,
91         TvContract.Programs.Genres.DRAMA,
92         TvContract.Programs.Genres.ENTERTAINMENT
93     };
94 
95     public final String title;
96     public final String episode;
97     public final int seasonNumber;
98     public final int episodeNumber;
99     public final String posterArtUri;
100     public final String description;
101     public final long durationMs;
102     public final String genre;
103     public final ImmutableList<TvContentRating> contentRatings;
104     public final String resourceUri;
105 
fromCursor(Cursor c)106     public static ProgramInfo fromCursor(Cursor c) {
107         // TODO: Fill other fields.
108         Builder builder = new Builder();
109         int index = c.getColumnIndex(TvContract.Programs.COLUMN_TITLE);
110         if (index >= 0) {
111             builder.setTitle(c.getString(index));
112         }
113         index = c.getColumnIndex(TvContract.Programs.COLUMN_SHORT_DESCRIPTION);
114         if (index >= 0) {
115             builder.setDescription(c.getString(index));
116         }
117         index = c.getColumnIndex(TvContract.Programs.COLUMN_EPISODE_TITLE);
118         if (index >= 0) {
119             builder.setEpisode(c.getString(index));
120         }
121         return builder.build();
122     }
123 
ProgramInfo( String title, String episode, int seasonNumber, int episodeNumber, String posterArtUri, String description, long durationMs, ImmutableList<TvContentRating> contentRatings, String genre, String resourceUri)124     public ProgramInfo(
125             String title,
126             String episode,
127             int seasonNumber,
128             int episodeNumber,
129             String posterArtUri,
130             String description,
131             long durationMs,
132             ImmutableList<TvContentRating> contentRatings,
133             String genre,
134             String resourceUri) {
135         this.title = title;
136         this.episode = episode;
137         this.seasonNumber = seasonNumber;
138         this.episodeNumber = episodeNumber;
139         this.posterArtUri = posterArtUri;
140         this.description = description;
141         this.durationMs = durationMs;
142         this.contentRatings = contentRatings;
143         this.genre = genre;
144         this.resourceUri = resourceUri;
145     }
146 
147     /**
148      * Create a instance of {@link ProgramInfo} whose content will be generated as much as possible.
149      */
create()150     public static ProgramInfo create() {
151         return new Builder().build();
152     }
153 
154     /**
155      * Get index of the program whose start time equals or less than {@code timeMs} and end time
156      * more than {@code timeMs}.
157      *
158      * @param timeMs target time in millis to find a program.
159      * @param channelId used to add complexity to the index between two consequence channels.
160      */
getIndex(long timeMs, long channelId)161     public int getIndex(long timeMs, long channelId) {
162         if (durationMs != GEN_DURATION) {
163             return Math.max((int) (timeMs / durationMs), 0);
164         }
165         long startTimeMs = channelId * DURATIONS_MS[((int) (channelId % DURATIONS_MS.length))];
166         int index = (int) ((timeMs - startTimeMs) / durationsSumMs) * DURATIONS_MS.length;
167         startTimeMs += (index / DURATIONS_MS.length) * durationsSumMs;
168         while (startTimeMs + DURATIONS_MS[index % DURATIONS_MS.length] < timeMs) {
169             startTimeMs += DURATIONS_MS[index % DURATIONS_MS.length];
170             index++;
171         }
172         return index;
173     }
174 
175     /**
176      * Returns the start time for the program with the position.
177      *
178      * @param index index returned by {@link #getIndex}
179      */
getStartTimeMs(int index, long channelId)180     public long getStartTimeMs(int index, long channelId) {
181         if (durationMs != GEN_DURATION) {
182             return index * durationMs;
183         }
184         long startTimeMs =
185                 channelId * DURATIONS_MS[((int) (channelId % DURATIONS_MS.length))]
186                         + (index / DURATIONS_MS.length) * durationsSumMs;
187         for (int i = 0; i < index % DURATIONS_MS.length; i++) {
188             startTimeMs += DURATIONS_MS[i];
189         }
190         return startTimeMs;
191     }
192 
193     /**
194      * Return complete {@link ProgramInfo} with the generated value. See: {@link #GEN_TITLE}, {@link
195      * #GEN_EPISODE}, {@link #GEN_POSTER}, {@link #GEN_DURATION}, {@link #GEN_GENRE}.
196      *
197      * @param index index returned by {@link #getIndex}
198      */
build(Context context, int index)199     public ProgramInfo build(Context context, int index) {
200         if (!GEN_TITLE.equals(title)
201                 && episode == null
202                 && !GEN_POSTER.equals(posterArtUri)
203                 && durationMs != GEN_DURATION
204                 && !GEN_GENRE.equals(genre)) {
205             return this;
206         }
207         return new ProgramInfo(
208                 GEN_TITLE.equals(title) ? "Title(" + index + ")" : title,
209                 GEN_EPISODE.equals(episode) ? "Episode(" + index + ")" : episode,
210                 episode != null ? (index % SEASON_MAX + 1) : seasonNumber,
211                 episode != null ? (index % EPISODE_MAX + 1) : episodeNumber,
212                 GEN_POSTER.equals(posterArtUri)
213                         ? Utils.getUriStringForResource(
214                                 context, POSTER_ARTS_RES[index % POSTER_ARTS_RES.length])
215                         : posterArtUri,
216                 description,
217                 durationMs == GEN_DURATION ? DURATIONS_MS[index % DURATIONS_MS.length] : durationMs,
218                 contentRatings,
219                 GEN_GENRE.equals(genre) ? GENRES[index % GENRES.length] : genre,
220                 resourceUri);
221     }
222 
223     @Override
toString()224     public String toString() {
225         return "ProgramInfo{title="
226                 + title
227                 + ", episode="
228                 + episode
229                 + ", durationMs="
230                 + durationMs
231                 + "}";
232     }
233 
234     @Override
equals(Object o)235     public boolean equals(Object o) {
236         if (this == o) {
237             return true;
238         }
239         if (o == null || getClass() != o.getClass()) {
240             return false;
241         }
242         ProgramInfo that = (ProgramInfo) o;
243         return Objects.equals(seasonNumber, that.seasonNumber)
244                 && Objects.equals(episodeNumber, that.episodeNumber)
245                 && Objects.equals(durationMs, that.durationMs)
246                 && Objects.equals(title, that.title)
247                 && Objects.equals(episode, that.episode)
248                 && Objects.equals(posterArtUri, that.posterArtUri)
249                 && Objects.equals(description, that.description)
250                 && Objects.equals(genre, that.genre)
251                 && Objects.equals(contentRatings, that.contentRatings)
252                 && Objects.equals(resourceUri, that.resourceUri);
253     }
254 
255     @Override
hashCode()256     public int hashCode() {
257         return Objects.hash(title, episode, seasonNumber, episodeNumber);
258     }
259 
260     public static class Builder {
261         private String mTitle = GEN_TITLE;
262         private String mEpisode = GEN_EPISODE;
263         private int mSeasonNumber;
264         private int mEpisodeNumber;
265         private String mPosterArtUri = GEN_POSTER;
266         private String mDescription;
267         private long mDurationMs = GEN_DURATION;
268         private ImmutableList<TvContentRating> mContentRatings;
269         private String mGenre = GEN_GENRE;
270         private String mResourceUri;
271 
setTitle(String title)272         public Builder setTitle(String title) {
273             mTitle = title;
274             return this;
275         }
276 
setEpisode(String episode)277         public Builder setEpisode(String episode) {
278             mEpisode = episode;
279             return this;
280         }
281 
setSeasonNumber(int seasonNumber)282         public Builder setSeasonNumber(int seasonNumber) {
283             mSeasonNumber = seasonNumber;
284             return this;
285         }
286 
setEpisodeNumber(int episodeNumber)287         public Builder setEpisodeNumber(int episodeNumber) {
288             mEpisodeNumber = episodeNumber;
289             return this;
290         }
291 
setPosterArtUri(String posterArtUri)292         public Builder setPosterArtUri(String posterArtUri) {
293             mPosterArtUri = posterArtUri;
294             return this;
295         }
296 
setDescription(String description)297         public Builder setDescription(String description) {
298             mDescription = description;
299             return this;
300         }
301 
setDurationMs(long durationMs)302         public Builder setDurationMs(long durationMs) {
303             mDurationMs = durationMs;
304             return this;
305         }
306 
setContentRatings(ImmutableList<TvContentRating> contentRatings)307         public Builder setContentRatings(ImmutableList<TvContentRating> contentRatings) {
308             mContentRatings = contentRatings;
309             return this;
310         }
311 
setGenre(String genre)312         public Builder setGenre(String genre) {
313             mGenre = genre;
314             return this;
315         }
316 
setResourceUri(String resourceUri)317         public Builder setResourceUri(String resourceUri) {
318             mResourceUri = resourceUri;
319             return this;
320         }
321 
build()322         public ProgramInfo build() {
323             return new ProgramInfo(
324                     mTitle,
325                     mEpisode,
326                     mSeasonNumber,
327                     mEpisodeNumber,
328                     mPosterArtUri,
329                     mDescription,
330                     mDurationMs,
331                     mContentRatings,
332                     mGenre,
333                     mResourceUri);
334         }
335     }
336 }
337