• 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.data;
18 
19 import android.annotation.SuppressLint;
20 import android.content.ContentValues;
21 import android.content.Context;
22 import android.database.Cursor;
23 import android.media.tv.TvContentRating;
24 import android.media.tv.TvContract;
25 import android.media.tv.TvContract.Programs;
26 import android.os.Build;
27 import android.os.Parcel;
28 import android.os.Parcelable;
29 import android.support.annotation.NonNull;
30 import android.support.annotation.Nullable;
31 import android.support.annotation.UiThread;
32 import android.support.annotation.VisibleForTesting;
33 import android.text.TextUtils;
34 import android.util.Log;
35 
36 import com.android.tv.common.BuildConfig;
37 import com.android.tv.common.CollectionUtils;
38 import com.android.tv.common.TvContentRatingCache;
39 import com.android.tv.util.ImageLoader;
40 import com.android.tv.util.Utils;
41 
42 import java.io.Serializable;
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 import java.util.List;
46 import java.util.Objects;
47 
48 /**
49  * A convenience class to create and insert program information entries into the database.
50  */
51 public final class Program extends BaseProgram implements Comparable<Program>, Parcelable {
52     private static final boolean DEBUG = false;
53     private static final boolean DEBUG_DUMP_DESCRIPTION = false;
54     private static final String TAG = "Program";
55 
56     private static final String[] PROJECTION_BASE = {
57             // Columns must match what is read in Program.fromCursor()
58             TvContract.Programs._ID,
59             TvContract.Programs.COLUMN_PACKAGE_NAME,
60             TvContract.Programs.COLUMN_CHANNEL_ID,
61             TvContract.Programs.COLUMN_TITLE,
62             TvContract.Programs.COLUMN_EPISODE_TITLE,
63             TvContract.Programs.COLUMN_SHORT_DESCRIPTION,
64             TvContract.Programs.COLUMN_LONG_DESCRIPTION,
65             TvContract.Programs.COLUMN_POSTER_ART_URI,
66             TvContract.Programs.COLUMN_THUMBNAIL_URI,
67             TvContract.Programs.COLUMN_CANONICAL_GENRE,
68             TvContract.Programs.COLUMN_CONTENT_RATING,
69             TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
70             TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
71             TvContract.Programs.COLUMN_VIDEO_WIDTH,
72             TvContract.Programs.COLUMN_VIDEO_HEIGHT,
73             TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA
74     };
75 
76     // Columns which is deprecated in NYC
77     @SuppressWarnings("deprecation")
78     private static final String[] PROJECTION_DEPRECATED_IN_NYC = {
79             TvContract.Programs.COLUMN_SEASON_NUMBER,
80             TvContract.Programs.COLUMN_EPISODE_NUMBER
81     };
82 
83     private static final String[] PROJECTION_ADDED_IN_NYC = {
84             TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER,
85             TvContract.Programs.COLUMN_SEASON_TITLE,
86             TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER,
87             TvContract.Programs.COLUMN_RECORDING_PROHIBITED
88     };
89 
90     public static final String[] PROJECTION = createProjection();
91 
createProjection()92     private static String[] createProjection() {
93         return CollectionUtils.concatAll(
94                 PROJECTION_BASE,
95                 Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
96                         ? PROJECTION_ADDED_IN_NYC
97                         : PROJECTION_DEPRECATED_IN_NYC);
98     }
99 
100     /**
101      * Returns the column index for {@code column}, -1 if the column doesn't exist.
102      */
getColumnIndex(String column)103     public static int getColumnIndex(String column) {
104         for (int i = 0; i < PROJECTION.length; ++i) {
105             if (PROJECTION[i].equals(column)) {
106                 return i;
107             }
108         }
109         return -1;
110     }
111 
112     /**
113      * Creates {@code Program} object from cursor.
114      *
115      * <p>The query that created the cursor MUST use {@link #PROJECTION}.
116      */
fromCursor(Cursor cursor)117     public static Program fromCursor(Cursor cursor) {
118         // Columns read must match the order of match {@link #PROJECTION}
119         Builder builder = new Builder();
120         int index = 0;
121         builder.setId(cursor.getLong(index++));
122         String packageName = cursor.getString(index++);
123         builder.setPackageName(packageName);
124         builder.setChannelId(cursor.getLong(index++));
125         builder.setTitle(cursor.getString(index++));
126         builder.setEpisodeTitle(cursor.getString(index++));
127         builder.setDescription(cursor.getString(index++));
128         builder.setLongDescription(cursor.getString(index++));
129         builder.setPosterArtUri(cursor.getString(index++));
130         builder.setThumbnailUri(cursor.getString(index++));
131         builder.setCanonicalGenres(cursor.getString(index++));
132         builder.setContentRatings(
133                 TvContentRatingCache.getInstance().getRatings(cursor.getString(index++)));
134         builder.setStartTimeUtcMillis(cursor.getLong(index++));
135         builder.setEndTimeUtcMillis(cursor.getLong(index++));
136         builder.setVideoWidth((int) cursor.getLong(index++));
137         builder.setVideoHeight((int) cursor.getLong(index++));
138         if (Utils.isInBundledPackageSet(packageName)) {
139             InternalDataUtils.deserializeInternalProviderData(cursor.getBlob(index), builder);
140         }
141         index++;
142         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
143             builder.setSeasonNumber(cursor.getString(index++));
144             builder.setSeasonTitle(cursor.getString(index++));
145             builder.setEpisodeNumber(cursor.getString(index++));
146             builder.setRecordingProhibited(cursor.getInt(index++) == 1);
147         } else {
148             builder.setSeasonNumber(cursor.getString(index++));
149             builder.setEpisodeNumber(cursor.getString(index++));
150         }
151         return builder.build();
152     }
153 
fromParcel(Parcel in)154     public static Program fromParcel(Parcel in) {
155         Program program = new Program();
156         program.mId = in.readLong();
157         program.mPackageName = in.readString();
158         program.mChannelId = in.readLong();
159         program.mTitle = in.readString();
160         program.mSeriesId = in.readString();
161         program.mEpisodeTitle = in.readString();
162         program.mSeasonNumber = in.readString();
163         program.mSeasonTitle = in.readString();
164         program.mEpisodeNumber = in.readString();
165         program.mStartTimeUtcMillis = in.readLong();
166         program.mEndTimeUtcMillis = in.readLong();
167         program.mDescription = in.readString();
168         program.mLongDescription = in.readString();
169         program.mVideoWidth = in.readInt();
170         program.mVideoHeight = in.readInt();
171         program.mCriticScores = in.readArrayList(Thread.currentThread().getContextClassLoader());
172         program.mPosterArtUri = in.readString();
173         program.mThumbnailUri = in.readString();
174         program.mCanonicalGenreIds = in.createIntArray();
175         int length = in.readInt();
176         if (length > 0) {
177             program.mContentRatings = new TvContentRating[length];
178             for (int i = 0; i < length; ++i) {
179                 program.mContentRatings[i] = TvContentRating.unflattenFromString(in.readString());
180             }
181         }
182         program.mRecordingProhibited = in.readByte() != (byte) 0;
183         return program;
184     }
185 
186     public static final Parcelable.Creator<Program> CREATOR = new Parcelable.Creator<Program>() {
187         @Override
188         public Program createFromParcel(Parcel in) {
189           return Program.fromParcel(in);
190         }
191 
192         @Override
193         public Program[] newArray(int size) {
194           return new Program[size];
195         }
196     };
197 
198     private long mId;
199     private String mPackageName;
200     private long mChannelId;
201     private String mTitle;
202     private String mSeriesId;
203     private String mEpisodeTitle;
204     private String mSeasonNumber;
205     private String mSeasonTitle;
206     private String mEpisodeNumber;
207     private long mStartTimeUtcMillis;
208     private long mEndTimeUtcMillis;
209     private String mDescription;
210     private String mLongDescription;
211     private int mVideoWidth;
212     private int mVideoHeight;
213     private List<CriticScore> mCriticScores;
214     private String mPosterArtUri;
215     private String mThumbnailUri;
216     private int[] mCanonicalGenreIds;
217     private TvContentRating[] mContentRatings;
218     private boolean mRecordingProhibited;
219 
Program()220     private Program() {
221         // Do nothing.
222     }
223 
getId()224     public long getId() {
225         return mId;
226     }
227 
228     /**
229      * Returns the package name of this program.
230      */
getPackageName()231     public String getPackageName() {
232         return mPackageName;
233     }
234 
getChannelId()235     public long getChannelId() {
236         return mChannelId;
237     }
238 
239     /**
240      * Returns {@code true} if this program is valid or {@code false} otherwise.
241      */
242     @Override
isValid()243     public boolean isValid() {
244         return mChannelId >= 0;
245     }
246 
247     /**
248      * Returns {@code true} if the program is valid and {@code false} otherwise.
249      */
isValid(Program program)250     public static boolean isValid(Program program) {
251         return program != null && program.isValid();
252     }
253 
254     @Override
getTitle()255     public String getTitle() {
256         return mTitle;
257     }
258 
259     /**
260      * Returns the series ID.
261      */
262     @Override
getSeriesId()263     public String getSeriesId() {
264         return mSeriesId;
265     }
266 
267     /**
268      * Returns the episode title.
269      */
270     @Override
getEpisodeTitle()271     public String getEpisodeTitle() {
272         return mEpisodeTitle;
273     }
274 
275     @Override
getSeasonNumber()276     public String getSeasonNumber() {
277         return mSeasonNumber;
278     }
279 
280     @Override
getEpisodeNumber()281     public String getEpisodeNumber() {
282         return mEpisodeNumber;
283     }
284 
285     @Override
getStartTimeUtcMillis()286     public long getStartTimeUtcMillis() {
287         return mStartTimeUtcMillis;
288     }
289 
290     @Override
getEndTimeUtcMillis()291     public long getEndTimeUtcMillis() {
292         return mEndTimeUtcMillis;
293     }
294 
295     /**
296      * Returns the program duration.
297      */
298     @Override
getDurationMillis()299     public long getDurationMillis() {
300         return mEndTimeUtcMillis - mStartTimeUtcMillis;
301     }
302 
303     @Override
getDescription()304     public String getDescription() {
305         return mDescription;
306     }
307 
308     @Override
getLongDescription()309     public String getLongDescription() {
310         return mLongDescription;
311     }
312 
getVideoWidth()313     public int getVideoWidth() {
314         return mVideoWidth;
315     }
316 
getVideoHeight()317     public int getVideoHeight() {
318         return mVideoHeight;
319     }
320 
321     /**
322      * Returns the list of Critic Scores for this program
323      */
324     @Nullable
getCriticScores()325     public List<CriticScore> getCriticScores() {
326         return mCriticScores;
327     }
328 
329     @Nullable
330     @Override
getContentRatings()331     public TvContentRating[] getContentRatings() {
332         return mContentRatings;
333     }
334 
335     @Override
getPosterArtUri()336     public String getPosterArtUri() {
337         return mPosterArtUri;
338     }
339 
340     @Override
getThumbnailUri()341     public String getThumbnailUri() {
342         return mThumbnailUri;
343     }
344 
345     /**
346      * Returns {@code true} if the recording of this program is prohibited.
347      */
isRecordingProhibited()348     public boolean isRecordingProhibited() {
349         return mRecordingProhibited;
350     }
351 
352     /**
353      * Returns array of canonical genres for this program.
354      * This is expected to be called rarely.
355      */
356     @Nullable
getCanonicalGenres()357     public String[] getCanonicalGenres() {
358         if (mCanonicalGenreIds == null) {
359             return null;
360         }
361         String[] genres = new String[mCanonicalGenreIds.length];
362         for (int i = 0; i < mCanonicalGenreIds.length; i++) {
363             genres[i] = GenreItems.getCanonicalGenre(mCanonicalGenreIds[i]);
364         }
365         return genres;
366     }
367 
368     /**
369      * Returns array of canonical genre ID's for this program.
370      */
371     @Override
getCanonicalGenreIds()372     public int[] getCanonicalGenreIds() {
373         return mCanonicalGenreIds;
374     }
375 
376     /**
377      * Returns if this program has the genre.
378      */
hasGenre(int genreId)379     public boolean hasGenre(int genreId) {
380         if (genreId == GenreItems.ID_ALL_CHANNELS) {
381             return true;
382         }
383         if (mCanonicalGenreIds != null) {
384             for (int id : mCanonicalGenreIds) {
385                 if (id == genreId) {
386                     return true;
387                 }
388             }
389         }
390         return false;
391     }
392 
393     @Override
hashCode()394     public int hashCode() {
395         // Hash with all the properties because program ID can be invalid for the dummy programs.
396         return Objects.hash(mChannelId, mStartTimeUtcMillis, mEndTimeUtcMillis,
397                 mTitle, mSeriesId, mEpisodeTitle, mDescription, mLongDescription, mVideoWidth,
398                 mVideoHeight, mPosterArtUri, mThumbnailUri, Arrays.hashCode(mContentRatings),
399                 Arrays.hashCode(mCanonicalGenreIds), mSeasonNumber, mSeasonTitle, mEpisodeNumber,
400                 mRecordingProhibited);
401     }
402 
403     @Override
equals(Object other)404     public boolean equals(Object other) {
405         if (!(other instanceof Program)) {
406             return false;
407         }
408         // Compare all the properties because program ID can be invalid for the dummy programs.
409         Program program = (Program) other;
410         return Objects.equals(mPackageName, program.mPackageName)
411                 && mChannelId == program.mChannelId
412                 && mStartTimeUtcMillis == program.mStartTimeUtcMillis
413                 && mEndTimeUtcMillis == program.mEndTimeUtcMillis
414                 && Objects.equals(mTitle, program.mTitle)
415                 && Objects.equals(mSeriesId, program.mSeriesId)
416                 && Objects.equals(mEpisodeTitle, program.mEpisodeTitle)
417                 && Objects.equals(mDescription, program.mDescription)
418                 && Objects.equals(mLongDescription, program.mLongDescription)
419                 && mVideoWidth == program.mVideoWidth
420                 && mVideoHeight == program.mVideoHeight
421                 && Objects.equals(mPosterArtUri, program.mPosterArtUri)
422                 && Objects.equals(mThumbnailUri, program.mThumbnailUri)
423                 && Arrays.equals(mContentRatings, program.mContentRatings)
424                 && Arrays.equals(mCanonicalGenreIds, program.mCanonicalGenreIds)
425                 && Objects.equals(mSeasonNumber, program.mSeasonNumber)
426                 && Objects.equals(mSeasonTitle, program.mSeasonTitle)
427                 && Objects.equals(mEpisodeNumber, program.mEpisodeNumber)
428                 && mRecordingProhibited == program.mRecordingProhibited;
429     }
430 
431     @Override
compareTo(@onNull Program other)432     public int compareTo(@NonNull Program other) {
433         return Long.compare(mStartTimeUtcMillis, other.mStartTimeUtcMillis);
434     }
435 
436     @Override
toString()437     public String toString() {
438         StringBuilder builder = new StringBuilder();
439         builder.append("Program[").append(mId)
440                 .append("]{channelId=").append(mChannelId)
441                 .append(", packageName=").append(mPackageName)
442                 .append(", title=").append(mTitle)
443                 .append(", seriesId=").append(mSeriesId)
444                 .append(", episodeTitle=").append(mEpisodeTitle)
445                 .append(", seasonNumber=").append(mSeasonNumber)
446                 .append(", seasonTitle=").append(mSeasonTitle)
447                 .append(", episodeNumber=").append(mEpisodeNumber)
448                 .append(", startTimeUtcSec=").append(Utils.toTimeString(mStartTimeUtcMillis))
449                 .append(", endTimeUtcSec=").append(Utils.toTimeString(mEndTimeUtcMillis))
450                 .append(", videoWidth=").append(mVideoWidth)
451                 .append(", videoHeight=").append(mVideoHeight)
452                 .append(", contentRatings=")
453                 .append(TvContentRatingCache.contentRatingsToString(mContentRatings))
454                 .append(", posterArtUri=").append(mPosterArtUri)
455                 .append(", thumbnailUri=").append(mThumbnailUri)
456                 .append(", canonicalGenres=").append(Arrays.toString(mCanonicalGenreIds))
457                 .append(", recordingProhibited=").append(mRecordingProhibited);
458         if (DEBUG_DUMP_DESCRIPTION) {
459             builder.append(", description=").append(mDescription)
460                     .append(", longDescription=").append(mLongDescription);
461         }
462         return builder.append("}").toString();
463     }
464 
465     /**
466      * Translates a {@link Program} to {@link ContentValues} that are ready to be written into
467      * Database.
468      */
469     @SuppressLint("InlinedApi")
470     @SuppressWarnings("deprecation")
toContentValues(Program program)471     public static ContentValues toContentValues(Program program) {
472         ContentValues values = new ContentValues();
473         values.put(TvContract.Programs.COLUMN_CHANNEL_ID, program.getChannelId());
474         putValue(values, TvContract.Programs.COLUMN_TITLE, program.getTitle());
475         putValue(values, TvContract.Programs.COLUMN_EPISODE_TITLE, program.getEpisodeTitle());
476         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
477             putValue(values, TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER,
478                     program.getSeasonNumber());
479             putValue(values, TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER,
480                     program.getEpisodeNumber());
481         } else {
482             putValue(values, TvContract.Programs.COLUMN_SEASON_NUMBER, program.getSeasonNumber());
483             putValue(values, TvContract.Programs.COLUMN_EPISODE_NUMBER, program.getEpisodeNumber());
484         }
485         putValue(values, TvContract.Programs.COLUMN_SHORT_DESCRIPTION, program.getDescription());
486         putValue(values, TvContract.Programs.COLUMN_LONG_DESCRIPTION, program.getLongDescription());
487         putValue(values, TvContract.Programs.COLUMN_POSTER_ART_URI, program.getPosterArtUri());
488         putValue(values, TvContract.Programs.COLUMN_THUMBNAIL_URI, program.getThumbnailUri());
489         String[] canonicalGenres = program.getCanonicalGenres();
490         if (canonicalGenres != null && canonicalGenres.length > 0) {
491             putValue(values, TvContract.Programs.COLUMN_CANONICAL_GENRE,
492                     TvContract.Programs.Genres.encode(canonicalGenres));
493         } else {
494             putValue(values, TvContract.Programs.COLUMN_CANONICAL_GENRE, "");
495         }
496         putValue(values, Programs.COLUMN_CONTENT_RATING,
497                 TvContentRatingCache.contentRatingsToString(program.getContentRatings()));
498         values.put(TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
499                 program.getStartTimeUtcMillis());
500         values.put(TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, program.getEndTimeUtcMillis());
501         putValue(values, TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA,
502                 InternalDataUtils.serializeInternalProviderData(program));
503         return values;
504     }
505 
putValue(ContentValues contentValues, String key, String value)506     private static void putValue(ContentValues contentValues, String key, String value) {
507         if (TextUtils.isEmpty(value)) {
508             contentValues.putNull(key);
509         } else {
510             contentValues.put(key, value);
511         }
512     }
513 
putValue(ContentValues contentValues, String key, byte[] value)514     private static void putValue(ContentValues contentValues, String key, byte[] value) {
515         if (value == null || value.length == 0) {
516             contentValues.putNull(key);
517         } else {
518             contentValues.put(key, value);
519         }
520     }
521 
copyFrom(Program other)522     public void copyFrom(Program other) {
523         if (this == other) {
524             return;
525         }
526 
527         mId = other.mId;
528         mPackageName = other.mPackageName;
529         mChannelId = other.mChannelId;
530         mTitle = other.mTitle;
531         mSeriesId = other.mSeriesId;
532         mEpisodeTitle = other.mEpisodeTitle;
533         mSeasonNumber = other.mSeasonNumber;
534         mSeasonTitle = other.mSeasonTitle;
535         mEpisodeNumber = other.mEpisodeNumber;
536         mStartTimeUtcMillis = other.mStartTimeUtcMillis;
537         mEndTimeUtcMillis = other.mEndTimeUtcMillis;
538         mDescription = other.mDescription;
539         mLongDescription = other.mLongDescription;
540         mVideoWidth = other.mVideoWidth;
541         mVideoHeight = other.mVideoHeight;
542         mCriticScores = other.mCriticScores;
543         mPosterArtUri = other.mPosterArtUri;
544         mThumbnailUri = other.mThumbnailUri;
545         mCanonicalGenreIds = other.mCanonicalGenreIds;
546         mContentRatings = other.mContentRatings;
547         mRecordingProhibited = other.mRecordingProhibited;
548     }
549 
550     /**
551      * A Builder for the Program class
552      */
553     public static final class Builder {
554         private final Program mProgram;
555 
556         /**
557          * Creates a Builder for this Program class
558          */
Builder()559         public Builder() {
560             mProgram = new Program();
561             // Fill initial data.
562             mProgram.mPackageName = null;
563             mProgram.mChannelId = Channel.INVALID_ID;
564             mProgram.mTitle = null;
565             mProgram.mSeasonNumber = null;
566             mProgram.mSeasonTitle = null;
567             mProgram.mEpisodeNumber = null;
568             mProgram.mStartTimeUtcMillis = -1;
569             mProgram.mEndTimeUtcMillis = -1;
570             mProgram.mDescription = null;
571             mProgram.mLongDescription = null;
572             mProgram.mRecordingProhibited = false;
573             mProgram.mCriticScores = null;
574         }
575 
576         /**
577          * Creates a builder for this Program class
578          * by setting default values equivalent to another Program
579          * @param other the program to be copied
580          */
581         @VisibleForTesting
Builder(Program other)582         public Builder(Program other) {
583             mProgram = new Program();
584             mProgram.copyFrom(other);
585         }
586 
587         /**
588          * Sets the ID of this program
589          * @param id the ID
590          * @return a reference to this object
591          */
setId(long id)592         public Builder setId(long id) {
593             mProgram.mId = id;
594             return this;
595         }
596 
597         /**
598          * Sets the package name for this program
599          * @param packageName the package name
600          * @return a reference to this object
601          */
setPackageName(String packageName)602         public Builder setPackageName(String packageName){
603             mProgram.mPackageName = packageName;
604             return this;
605         }
606 
607         /**
608          * Sets the channel ID for this program
609          * @param channelId the channel ID
610          * @return a reference to this object
611          */
setChannelId(long channelId)612         public Builder setChannelId(long channelId) {
613             mProgram.mChannelId = channelId;
614             return this;
615         }
616 
617         /**
618          * Sets the program title
619          * @param title the title
620          * @return a reference to this object
621          */
setTitle(String title)622         public Builder setTitle(String title) {
623             mProgram.mTitle = title;
624             return this;
625         }
626 
627         /**
628          * Sets the series ID.
629          * @param seriesId the series ID
630          * @return a reference to this object
631          */
setSeriesId(String seriesId)632         public Builder setSeriesId(String seriesId) {
633             mProgram.mSeriesId = seriesId;
634             return this;
635         }
636 
637         /**
638          * Sets the episode title if this is a series program
639          * @param episodeTitle the episode title
640          * @return a reference to this object
641          */
setEpisodeTitle(String episodeTitle)642         public Builder setEpisodeTitle(String episodeTitle) {
643             mProgram.mEpisodeTitle = episodeTitle;
644             return this;
645         }
646 
647         /**
648          * Sets the season number if this is a series program
649          * @param seasonNumber the season number
650          * @return a reference to this object
651          */
setSeasonNumber(String seasonNumber)652         public Builder setSeasonNumber(String seasonNumber) {
653             mProgram.mSeasonNumber = seasonNumber;
654             return this;
655         }
656 
657 
658         /**
659          * Sets the season title if this is a series program
660          * @param seasonTitle the season title
661          * @return a reference to this object
662          */
setSeasonTitle(String seasonTitle)663         public Builder setSeasonTitle(String seasonTitle) {
664             mProgram.mSeasonTitle = seasonTitle;
665             return this;
666         }
667 
668         /**
669          * Sets the episode number if this is a series program
670          * @param episodeNumber the episode number
671          * @return a reference to this object
672          */
setEpisodeNumber(String episodeNumber)673         public Builder setEpisodeNumber(String episodeNumber) {
674             mProgram.mEpisodeNumber = episodeNumber;
675             return this;
676         }
677 
678         /**
679          * Sets the start time of this program
680          * @param startTimeUtcMillis the start time in UTC milliseconds
681          * @return a reference to this object
682          */
setStartTimeUtcMillis(long startTimeUtcMillis)683         public Builder setStartTimeUtcMillis(long startTimeUtcMillis) {
684             mProgram.mStartTimeUtcMillis = startTimeUtcMillis;
685             return this;
686         }
687 
688         /**
689          * Sets the end time of this program
690          * @param endTimeUtcMillis the end time in UTC milliseconds
691          * @return a reference to this object
692          */
setEndTimeUtcMillis(long endTimeUtcMillis)693         public Builder setEndTimeUtcMillis(long endTimeUtcMillis) {
694             mProgram.mEndTimeUtcMillis = endTimeUtcMillis;
695             return this;
696         }
697 
698         /**
699          * Sets a description
700          * @param description the description
701          * @return a reference to this object
702          */
setDescription(String description)703         public Builder setDescription(String description) {
704             mProgram.mDescription = description;
705             return this;
706         }
707 
708         /**
709          * Sets a long description
710          * @param longDescription the long description
711          * @return a reference to this object
712          */
setLongDescription(String longDescription)713         public Builder setLongDescription(String longDescription) {
714             mProgram.mLongDescription = longDescription;
715             return this;
716         }
717 
718         /**
719          * Defines the video width of this program
720          * @param width
721          * @return a reference to this object
722          */
setVideoWidth(int width)723         public Builder setVideoWidth(int width) {
724             mProgram.mVideoWidth = width;
725             return this;
726         }
727 
728         /**
729          * Defines the video height of this program
730          * @param height
731          * @return a reference to this object
732          */
setVideoHeight(int height)733         public Builder setVideoHeight(int height) {
734             mProgram.mVideoHeight = height;
735             return this;
736         }
737 
738         /**
739          * Sets the content ratings for this program
740          * @param contentRatings the content ratings
741          * @return a reference to this object
742          */
setContentRatings(TvContentRating[] contentRatings)743         public Builder setContentRatings(TvContentRating[] contentRatings) {
744             mProgram.mContentRatings = contentRatings;
745             return this;
746         }
747 
748         /**
749          * Sets the poster art URI
750          * @param posterArtUri the poster art URI
751          * @return a reference to this object
752          */
setPosterArtUri(String posterArtUri)753         public Builder setPosterArtUri(String posterArtUri) {
754             mProgram.mPosterArtUri = posterArtUri;
755             return this;
756         }
757 
758         /**
759          * Sets the thumbnail URI
760          * @param thumbnailUri the thumbnail URI
761          * @return a reference to this object
762          */
setThumbnailUri(String thumbnailUri)763         public Builder setThumbnailUri(String thumbnailUri) {
764             mProgram.mThumbnailUri = thumbnailUri;
765             return this;
766         }
767 
768         /**
769          * Sets the canonical genres by id
770          * @param genres the genres
771          * @return a reference to this object
772          */
setCanonicalGenres(String genres)773         public Builder setCanonicalGenres(String genres) {
774             mProgram.mCanonicalGenreIds = Utils.getCanonicalGenreIds(genres);
775             return this;
776         }
777 
778         /**
779          * Sets the recording prohibited flag
780          * @param recordingProhibited recording prohibited flag
781          * @return a reference to this object
782          */
setRecordingProhibited(boolean recordingProhibited)783         public Builder setRecordingProhibited(boolean recordingProhibited) {
784             mProgram.mRecordingProhibited = recordingProhibited;
785             return this;
786         }
787 
788         /**
789          * Adds a critic score
790          * @param criticScore the critic score
791          * @return a reference to this object
792          */
addCriticScore(CriticScore criticScore)793         public Builder addCriticScore(CriticScore criticScore) {
794             if (criticScore.score != null) {
795                 if (mProgram.mCriticScores == null) {
796                     mProgram.mCriticScores = new ArrayList<>();
797                 }
798                 mProgram.mCriticScores.add(criticScore);
799             }
800             return this;
801         }
802 
803         /**
804          * Sets the critic scores
805          * @param criticScores the critic scores
806          * @return a reference to this objects
807          */
setCriticScores(List<CriticScore> criticScores)808         public Builder setCriticScores(List<CriticScore> criticScores) {
809             mProgram.mCriticScores = criticScores;
810             return this;
811         }
812 
813         /**
814          * Returns a reference to the Program object being constructed
815          * @return the Program object constructed
816          */
build()817         public Program build() {
818             // Generate the series ID for the episodic program of other TV input.
819             if (TextUtils.isEmpty(mProgram.mTitle)) {
820                 // If title is null, series cannot be generated for this program.
821                 setSeriesId(null);
822             } else if (TextUtils.isEmpty(mProgram.mSeriesId)
823                     && !TextUtils.isEmpty(mProgram.mEpisodeNumber)) {
824                 // If series ID is not set, generate it for the episodic program of other TV input.
825                 setSeriesId(BaseProgram.generateSeriesId(mProgram.mPackageName, mProgram.mTitle));
826             }
827             Program program = new Program();
828             program.copyFrom(mProgram);
829             return program;
830         }
831     }
832 
833     /**
834      * Prefetches the program poster art.<p>
835      */
prefetchPosterArt(Context context, int posterArtWidth, int posterArtHeight)836     public void prefetchPosterArt(Context context, int posterArtWidth, int posterArtHeight) {
837         if (mPosterArtUri == null) {
838             return;
839         }
840         ImageLoader.prefetchBitmap(context, mPosterArtUri, posterArtWidth, posterArtHeight);
841     }
842 
843     /**
844      * Loads the program poster art and returns it via {@code callback}.
845      * <p>
846      * Note that it may directly call {@code callback} if the program poster art already is loaded.
847      *
848      * @return {@code true} if the load is complete and the callback is executed.
849      */
850     @UiThread
loadPosterArt(Context context, int posterArtWidth, int posterArtHeight, ImageLoader.ImageLoaderCallback callback)851     public boolean loadPosterArt(Context context, int posterArtWidth, int posterArtHeight,
852             ImageLoader.ImageLoaderCallback callback) {
853         if (mPosterArtUri == null) {
854             return false;
855         }
856         return ImageLoader.loadBitmap(
857                 context, mPosterArtUri, posterArtWidth, posterArtHeight, callback);
858     }
859 
isDuplicate(Program p1, Program p2)860     public static boolean isDuplicate(Program p1, Program p2) {
861         if (p1 == null || p2 == null) {
862             return false;
863         }
864         boolean isDuplicate = p1.getChannelId() == p2.getChannelId()
865                 && p1.getStartTimeUtcMillis() == p2.getStartTimeUtcMillis()
866                 && p1.getEndTimeUtcMillis() == p2.getEndTimeUtcMillis();
867         if (DEBUG && BuildConfig.ENG && isDuplicate) {
868             Log.w(TAG, "Duplicate programs detected! - \"" + p1.getTitle() + "\" and \""
869                     + p2.getTitle() + "\"");
870         }
871         return isDuplicate;
872     }
873 
874     @Override
describeContents()875     public int describeContents() {
876         return 0;
877     }
878 
879     @Override
writeToParcel(Parcel out, int paramInt)880     public void writeToParcel(Parcel out, int paramInt) {
881         out.writeLong(mId);
882         out.writeString(mPackageName);
883         out.writeLong(mChannelId);
884         out.writeString(mTitle);
885         out.writeString(mSeriesId);
886         out.writeString(mEpisodeTitle);
887         out.writeString(mSeasonNumber);
888         out.writeString(mSeasonTitle);
889         out.writeString(mEpisodeNumber);
890         out.writeLong(mStartTimeUtcMillis);
891         out.writeLong(mEndTimeUtcMillis);
892         out.writeString(mDescription);
893         out.writeString(mLongDescription);
894         out.writeInt(mVideoWidth);
895         out.writeInt(mVideoHeight);
896         out.writeList(mCriticScores);
897         out.writeString(mPosterArtUri);
898         out.writeString(mThumbnailUri);
899         out.writeIntArray(mCanonicalGenreIds);
900         out.writeInt(mContentRatings == null ? 0 : mContentRatings.length);
901         if (mContentRatings != null) {
902             for (TvContentRating rating : mContentRatings) {
903                 out.writeString(rating.flattenToString());
904             }
905         }
906         out.writeByte((byte) (mRecordingProhibited ? 1 : 0));
907     }
908 
909     /**
910      * Holds one type of critic score and its source.
911      */
912     public static final class CriticScore implements Serializable, Parcelable {
913         /**
914          * The source of the rating.
915          */
916         public final String source;
917         /**
918          * The score.
919          */
920         public final String score;
921         /**
922          * The url of the logo image
923          */
924         public final String logoUrl;
925 
926         public static final Parcelable.Creator<CriticScore> CREATOR =
927                 new Parcelable.Creator<CriticScore>() {
928                     @Override
929                     public CriticScore createFromParcel(Parcel in) {
930                         String source = in.readString();
931                         String score = in.readString();
932                         String logoUri  = in.readString();
933                         return new CriticScore(source, score, logoUri);
934                     }
935 
936                     @Override
937                     public CriticScore[] newArray(int size) {
938                         return new CriticScore[size];
939                     }
940                 };
941 
942         /**
943          * Constructor for this class.
944          * @param source the source of the rating
945          * @param score the score
946          */
CriticScore(String source, String score, String logoUrl)947         public CriticScore(String source, String score, String logoUrl) {
948             this.source = source;
949             this.score = score;
950             this.logoUrl = logoUrl;
951         }
952 
953         @Override
describeContents()954         public int describeContents() {
955             return 0;
956         }
957 
958         @Override
writeToParcel(Parcel out, int i)959         public void writeToParcel(Parcel out, int i) {
960             out.writeString(source);
961             out.writeString(score);
962             out.writeString(logoUrl);
963         }
964     }
965 }
966