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