Home | History | Annotate | Download | only in data
      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 
     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. */
     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      */
    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 
    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 
    216     private Program() {
    217         // Do nothing.
    218     }
    219 
    220     public long getId() {
    221         return mId;
    222     }
    223 
    224     /** Returns the package name of this program. */
    225     public String getPackageName() {
    226         return mPackageName;
    227     }
    228 
    229     public long getChannelId() {
    230         return mChannelId;
    231     }
    232 
    233     /** Returns {@code true} if this program is valid or {@code false} otherwise. */
    234     @Override
    235     public boolean isValid() {
    236         return mChannelId >= 0;
    237     }
    238 
    239     /** Returns {@code true} if the program is valid and {@code false} otherwise. */
    240     public static boolean isProgramValid(Program program) {
    241         return program != null && program.isValid();
    242     }
    243 
    244     @Override
    245     public String getTitle() {
    246         return mTitle;
    247     }
    248 
    249     /** Returns the series ID. */
    250     @Override
    251     public String getSeriesId() {
    252         return mSeriesId;
    253     }
    254 
    255     /** Returns the episode title. */
    256     @Override
    257     public String getEpisodeTitle() {
    258         return mEpisodeTitle;
    259     }
    260 
    261     @Override
    262     public String getSeasonNumber() {
    263         return mSeasonNumber;
    264     }
    265 
    266     @Override
    267     public String getEpisodeNumber() {
    268         return mEpisodeNumber;
    269     }
    270 
    271     @Override
    272     public long getStartTimeUtcMillis() {
    273         return mStartTimeUtcMillis;
    274     }
    275 
    276     @Override
    277     public long getEndTimeUtcMillis() {
    278         return mEndTimeUtcMillis;
    279     }
    280 
    281     /** Returns the program duration. */
    282     @Override
    283     public long getDurationMillis() {
    284         return mEndTimeUtcMillis - mStartTimeUtcMillis;
    285     }
    286 
    287     @Override
    288     public String getDescription() {
    289         return mDescription;
    290     }
    291 
    292     @Override
    293     public String getLongDescription() {
    294         return mLongDescription;
    295     }
    296 
    297     public int getVideoWidth() {
    298         return mVideoWidth;
    299     }
    300 
    301     public int getVideoHeight() {
    302         return mVideoHeight;
    303     }
    304 
    305     /** Returns the list of Critic Scores for this program */
    306     @Nullable
    307     public List<CriticScore> getCriticScores() {
    308         return mCriticScores;
    309     }
    310 
    311     @Nullable
    312     @Override
    313     public TvContentRating[] getContentRatings() {
    314         return mContentRatings;
    315     }
    316 
    317     @Override
    318     public String getPosterArtUri() {
    319         return mPosterArtUri;
    320     }
    321 
    322     @Override
    323     public String getThumbnailUri() {
    324         return mThumbnailUri;
    325     }
    326 
    327     /** Returns {@code true} if the recording of this program is prohibited. */
    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
    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
    347     public int[] getCanonicalGenreIds() {
    348         return mCanonicalGenreIds;
    349     }
    350 
    351     /** Returns if this program has the genre. */
    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
    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
    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
    419     public int compareTo(@NonNull Program other) {
    420         return Long.compare(mStartTimeUtcMillis, other.mStartTimeUtcMillis);
    421     }
    422 
    423     @Override
    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")
    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 
    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 
    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 
    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 */
    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
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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      */
    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
    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 
    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
    925     public int describeContents() {
    926         return 0;
    927     }
    928 
    929     @Override
    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          */
    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
    997         public int describeContents() {
    998             return 0;
    999         }
   1000 
   1001         @Override
   1002         public void writeToParcel(Parcel out, int i) {
   1003             out.writeString(source);
   1004             out.writeString(score);
   1005             out.writeString(logoUrl);
   1006         }
   1007     }
   1008 }
   1009