Home | History | Annotate | Download | only in data
      1 /*
      2  * Copyright (C) 2016 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.dvr.data;
     18 
     19 import android.annotation.TargetApi;
     20 import android.content.ContentUris;
     21 import android.content.ContentValues;
     22 import android.content.Context;
     23 import android.database.Cursor;
     24 import android.media.tv.TvContentRating;
     25 import android.media.tv.TvContract;
     26 import android.media.tv.TvContract.RecordedPrograms;
     27 import android.net.Uri;
     28 import android.os.Build;
     29 import android.support.annotation.Nullable;
     30 import android.text.TextUtils;
     31 
     32 import com.android.tv.common.R;
     33 import com.android.tv.common.TvContentRatingCache;
     34 import com.android.tv.data.BaseProgram;
     35 import com.android.tv.data.GenreItems;
     36 import com.android.tv.data.InternalDataUtils;
     37 import com.android.tv.util.Utils;
     38 
     39 import java.util.Arrays;
     40 import java.util.Collection;
     41 import java.util.Comparator;
     42 import java.util.Objects;
     43 import java.util.concurrent.TimeUnit;
     44 
     45 /**
     46  * Immutable instance of {@link android.media.tv.TvContract.RecordedPrograms}.
     47  */
     48 @TargetApi(Build.VERSION_CODES.N)
     49 public class RecordedProgram extends BaseProgram {
     50     public static final int ID_NOT_SET = -1;
     51 
     52     public final static String[] PROJECTION = {
     53             // These are in exactly the order listed in RecordedPrograms
     54             RecordedPrograms._ID,
     55             RecordedPrograms.COLUMN_PACKAGE_NAME,
     56             RecordedPrograms.COLUMN_INPUT_ID,
     57             RecordedPrograms.COLUMN_CHANNEL_ID,
     58             RecordedPrograms.COLUMN_TITLE,
     59             RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER,
     60             RecordedPrograms.COLUMN_SEASON_TITLE,
     61             RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER,
     62             RecordedPrograms.COLUMN_EPISODE_TITLE,
     63             RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS,
     64             RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS,
     65             RecordedPrograms.COLUMN_BROADCAST_GENRE,
     66             RecordedPrograms.COLUMN_CANONICAL_GENRE,
     67             RecordedPrograms.COLUMN_SHORT_DESCRIPTION,
     68             RecordedPrograms.COLUMN_LONG_DESCRIPTION,
     69             RecordedPrograms.COLUMN_VIDEO_WIDTH,
     70             RecordedPrograms.COLUMN_VIDEO_HEIGHT,
     71             RecordedPrograms.COLUMN_AUDIO_LANGUAGE,
     72             RecordedPrograms.COLUMN_CONTENT_RATING,
     73             RecordedPrograms.COLUMN_POSTER_ART_URI,
     74             RecordedPrograms.COLUMN_THUMBNAIL_URI,
     75             RecordedPrograms.COLUMN_SEARCHABLE,
     76             RecordedPrograms.COLUMN_RECORDING_DATA_URI,
     77             RecordedPrograms.COLUMN_RECORDING_DATA_BYTES,
     78             RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS,
     79             RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS,
     80             RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1,
     81             RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2,
     82             RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3,
     83             RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4,
     84             RecordedPrograms.COLUMN_VERSION_NUMBER,
     85             RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA,
     86     };
     87 
     88     public static RecordedProgram fromCursor(Cursor cursor) {
     89         int index = 0;
     90         Builder builder = builder()
     91                 .setId(cursor.getLong(index++))
     92                 .setPackageName(cursor.getString(index++))
     93                 .setInputId(cursor.getString(index++))
     94                 .setChannelId(cursor.getLong(index++))
     95                 .setTitle(cursor.getString(index++))
     96                 .setSeasonNumber(cursor.getString(index++))
     97                 .setSeasonTitle(cursor.getString(index++))
     98                 .setEpisodeNumber(cursor.getString(index++))
     99                 .setEpisodeTitle(cursor.getString(index++))
    100                 .setStartTimeUtcMillis(cursor.getLong(index++))
    101                 .setEndTimeUtcMillis(cursor.getLong(index++))
    102                 .setBroadcastGenres(cursor.getString(index++))
    103                 .setCanonicalGenres(cursor.getString(index++))
    104                 .setShortDescription(cursor.getString(index++))
    105                 .setLongDescription(cursor.getString(index++))
    106                 .setVideoWidth(cursor.getInt(index++))
    107                 .setVideoHeight(cursor.getInt(index++))
    108                 .setAudioLanguage(cursor.getString(index++))
    109                 .setContentRatings(
    110                         TvContentRatingCache.getInstance().getRatings(cursor.getString(index++)))
    111                 .setPosterArtUri(cursor.getString(index++))
    112                 .setThumbnailUri(cursor.getString(index++))
    113                 .setSearchable(cursor.getInt(index++) == 1)
    114                 .setDataUri(cursor.getString(index++))
    115                 .setDataBytes(cursor.getLong(index++))
    116                 .setDurationMillis(cursor.getLong(index++))
    117                 .setExpireTimeUtcMillis(cursor.getLong(index++))
    118                 .setInternalProviderFlag1(cursor.getInt(index++))
    119                 .setInternalProviderFlag2(cursor.getInt(index++))
    120                 .setInternalProviderFlag3(cursor.getInt(index++))
    121                 .setInternalProviderFlag4(cursor.getInt(index++))
    122                 .setVersionNumber(cursor.getInt(index++));
    123         if (Utils.isInBundledPackageSet(builder.mPackageName)) {
    124             InternalDataUtils.deserializeInternalProviderData(cursor.getBlob(index), builder);
    125         }
    126         return builder.build();
    127     }
    128 
    129     public static ContentValues toValues(RecordedProgram recordedProgram) {
    130         ContentValues values = new ContentValues();
    131         if (recordedProgram.mId != ID_NOT_SET) {
    132             values.put(RecordedPrograms._ID, recordedProgram.mId);
    133         }
    134         values.put(RecordedPrograms.COLUMN_INPUT_ID, recordedProgram.mInputId);
    135         values.put(RecordedPrograms.COLUMN_CHANNEL_ID, recordedProgram.mChannelId);
    136         values.put(RecordedPrograms.COLUMN_TITLE, recordedProgram.mTitle);
    137         values.put(RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER, recordedProgram.mSeasonNumber);
    138         values.put(RecordedPrograms.COLUMN_SEASON_TITLE, recordedProgram.mSeasonTitle);
    139         values.put(RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER, recordedProgram.mEpisodeNumber);
    140         values.put(RecordedPrograms.COLUMN_EPISODE_TITLE, recordedProgram.mTitle);
    141         values.put(RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS,
    142                 recordedProgram.mStartTimeUtcMillis);
    143         values.put(RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS, recordedProgram.mEndTimeUtcMillis);
    144         values.put(RecordedPrograms.COLUMN_BROADCAST_GENRE,
    145                 safeEncode(recordedProgram.mBroadcastGenres));
    146         values.put(RecordedPrograms.COLUMN_CANONICAL_GENRE,
    147                 safeEncode(recordedProgram.mCanonicalGenres));
    148         values.put(RecordedPrograms.COLUMN_SHORT_DESCRIPTION, recordedProgram.mShortDescription);
    149         values.put(RecordedPrograms.COLUMN_LONG_DESCRIPTION, recordedProgram.mLongDescription);
    150         if (recordedProgram.mVideoWidth == 0) {
    151             values.putNull(RecordedPrograms.COLUMN_VIDEO_WIDTH);
    152         } else {
    153             values.put(RecordedPrograms.COLUMN_VIDEO_WIDTH, recordedProgram.mVideoWidth);
    154         }
    155         if (recordedProgram.mVideoHeight == 0) {
    156             values.putNull(RecordedPrograms.COLUMN_VIDEO_HEIGHT);
    157         } else {
    158             values.put(RecordedPrograms.COLUMN_VIDEO_HEIGHT, recordedProgram.mVideoHeight);
    159         }
    160         values.put(RecordedPrograms.COLUMN_AUDIO_LANGUAGE, recordedProgram.mAudioLanguage);
    161         values.put(RecordedPrograms.COLUMN_CONTENT_RATING,
    162                 TvContentRatingCache.contentRatingsToString(recordedProgram.mContentRatings));
    163         values.put(RecordedPrograms.COLUMN_POSTER_ART_URI, recordedProgram.mPosterArtUri);
    164         values.put(RecordedPrograms.COLUMN_THUMBNAIL_URI, recordedProgram.mThumbnailUri);
    165         values.put(RecordedPrograms.COLUMN_SEARCHABLE, recordedProgram.mSearchable ? 1 : 0);
    166         values.put(RecordedPrograms.COLUMN_RECORDING_DATA_URI,
    167                 safeToString(recordedProgram.mDataUri));
    168         values.put(RecordedPrograms.COLUMN_RECORDING_DATA_BYTES, recordedProgram.mDataBytes);
    169         values.put(RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS,
    170                 recordedProgram.mDurationMillis);
    171         values.put(RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS,
    172                 recordedProgram.mExpireTimeUtcMillis);
    173         values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA,
    174                 InternalDataUtils.serializeInternalProviderData(recordedProgram));
    175         values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1,
    176                 recordedProgram.mInternalProviderFlag1);
    177         values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2,
    178                 recordedProgram.mInternalProviderFlag2);
    179         values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3,
    180                 recordedProgram.mInternalProviderFlag3);
    181         values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4,
    182                 recordedProgram.mInternalProviderFlag4);
    183         values.put(RecordedPrograms.COLUMN_VERSION_NUMBER, recordedProgram.mVersionNumber);
    184         return values;
    185     }
    186 
    187     public static class Builder{
    188         private long mId = ID_NOT_SET;
    189         private String mPackageName;
    190         private String mInputId;
    191         private long mChannelId;
    192         private String mTitle;
    193         private String mSeriesId;
    194         private String mSeasonNumber;
    195         private String mSeasonTitle;
    196         private String mEpisodeNumber;
    197         private String mEpisodeTitle;
    198         private long mStartTimeUtcMillis;
    199         private long mEndTimeUtcMillis;
    200         private String[] mBroadcastGenres;
    201         private String[] mCanonicalGenres;
    202         private String mShortDescription;
    203         private String mLongDescription;
    204         private int mVideoWidth;
    205         private int mVideoHeight;
    206         private String mAudioLanguage;
    207         private TvContentRating[] mContentRatings;
    208         private String mPosterArtUri;
    209         private String mThumbnailUri;
    210         private boolean mSearchable = true;
    211         private Uri mDataUri;
    212         private long mDataBytes;
    213         private long mDurationMillis;
    214         private long mExpireTimeUtcMillis;
    215         private int mInternalProviderFlag1;
    216         private int mInternalProviderFlag2;
    217         private int mInternalProviderFlag3;
    218         private int mInternalProviderFlag4;
    219         private int mVersionNumber;
    220 
    221         public Builder setId(long id) {
    222             mId = id;
    223             return this;
    224         }
    225 
    226         public Builder setPackageName(String packageName) {
    227             mPackageName = packageName;
    228             return this;
    229         }
    230 
    231         public Builder setInputId(String inputId) {
    232             mInputId = inputId;
    233             return this;
    234         }
    235 
    236         public Builder setChannelId(long channelId) {
    237             mChannelId = channelId;
    238             return this;
    239         }
    240 
    241         public Builder setTitle(String title) {
    242             mTitle = title;
    243             return this;
    244         }
    245 
    246         public Builder setSeriesId(String seriesId) {
    247             mSeriesId = seriesId;
    248             return this;
    249         }
    250 
    251         public Builder setSeasonNumber(String seasonNumber) {
    252             mSeasonNumber = seasonNumber;
    253             return this;
    254         }
    255 
    256         public Builder setSeasonTitle(String seasonTitle) {
    257             mSeasonTitle = seasonTitle;
    258             return this;
    259         }
    260 
    261         public Builder setEpisodeNumber(String episodeNumber) {
    262             mEpisodeNumber = episodeNumber;
    263             return this;
    264         }
    265 
    266         public Builder setEpisodeTitle(String episodeTitle) {
    267             mEpisodeTitle = episodeTitle;
    268             return this;
    269         }
    270 
    271         public Builder setStartTimeUtcMillis(long startTimeUtcMillis) {
    272             mStartTimeUtcMillis = startTimeUtcMillis;
    273             return this;
    274         }
    275 
    276         public Builder setEndTimeUtcMillis(long endTimeUtcMillis) {
    277             mEndTimeUtcMillis = endTimeUtcMillis;
    278             return this;
    279         }
    280 
    281         public Builder setBroadcastGenres(String broadcastGenres) {
    282             if (TextUtils.isEmpty(broadcastGenres)) {
    283                 mBroadcastGenres = null;
    284                 return this;
    285             }
    286             return setBroadcastGenres(TvContract.Programs.Genres.decode(broadcastGenres));
    287         }
    288 
    289         private Builder setBroadcastGenres(String[] broadcastGenres) {
    290             mBroadcastGenres = broadcastGenres;
    291             return this;
    292         }
    293 
    294         public Builder setCanonicalGenres(String canonicalGenres) {
    295             if (TextUtils.isEmpty(canonicalGenres)) {
    296                 mCanonicalGenres = null;
    297                 return this;
    298             }
    299             return setCanonicalGenres(TvContract.Programs.Genres.decode(canonicalGenres));
    300         }
    301 
    302         private Builder setCanonicalGenres(String[] canonicalGenres) {
    303             mCanonicalGenres = canonicalGenres;
    304             return this;
    305         }
    306 
    307         public Builder setShortDescription(String shortDescription) {
    308             mShortDescription = shortDescription;
    309             return this;
    310         }
    311 
    312         public Builder setLongDescription(String longDescription) {
    313             mLongDescription = longDescription;
    314             return this;
    315         }
    316 
    317         public Builder setVideoWidth(int videoWidth) {
    318             mVideoWidth = videoWidth;
    319             return this;
    320         }
    321 
    322         public Builder setVideoHeight(int videoHeight) {
    323             mVideoHeight = videoHeight;
    324             return this;
    325         }
    326 
    327         public Builder setAudioLanguage(String audioLanguage) {
    328             mAudioLanguage = audioLanguage;
    329             return this;
    330         }
    331 
    332         public Builder setContentRatings(TvContentRating[] contentRatings) {
    333             mContentRatings = contentRatings;
    334             return this;
    335         }
    336 
    337         private Uri toUri(String uriString) {
    338             try {
    339                 return uriString == null ? null : Uri.parse(uriString);
    340             } catch (Exception e) {
    341                 return null;
    342             }
    343         }
    344 
    345         public Builder setPosterArtUri(String posterArtUri) {
    346             mPosterArtUri = posterArtUri;
    347             return this;
    348         }
    349 
    350         public Builder setThumbnailUri(String thumbnailUri) {
    351             mThumbnailUri = thumbnailUri;
    352             return this;
    353         }
    354 
    355         public Builder setSearchable(boolean searchable) {
    356             mSearchable = searchable;
    357             return this;
    358         }
    359 
    360         public Builder setDataUri(String dataUri) {
    361             return setDataUri(toUri(dataUri));
    362         }
    363 
    364         public Builder setDataUri(Uri dataUri) {
    365             mDataUri = dataUri;
    366             return this;
    367         }
    368 
    369         public Builder setDataBytes(long dataBytes) {
    370             mDataBytes = dataBytes;
    371             return this;
    372         }
    373 
    374         public Builder setDurationMillis(long durationMillis) {
    375             mDurationMillis = durationMillis;
    376             return this;
    377         }
    378 
    379         public Builder setExpireTimeUtcMillis(long expireTimeUtcMillis) {
    380             mExpireTimeUtcMillis = expireTimeUtcMillis;
    381             return this;
    382         }
    383 
    384         public Builder setInternalProviderFlag1(int internalProviderFlag1) {
    385             mInternalProviderFlag1 = internalProviderFlag1;
    386             return this;
    387         }
    388 
    389         public Builder setInternalProviderFlag2(int internalProviderFlag2) {
    390             mInternalProviderFlag2 = internalProviderFlag2;
    391             return this;
    392         }
    393 
    394         public Builder setInternalProviderFlag3(int internalProviderFlag3) {
    395             mInternalProviderFlag3 = internalProviderFlag3;
    396             return this;
    397         }
    398 
    399         public Builder setInternalProviderFlag4(int internalProviderFlag4) {
    400             mInternalProviderFlag4 = internalProviderFlag4;
    401             return this;
    402         }
    403 
    404         public Builder setVersionNumber(int versionNumber) {
    405             mVersionNumber = versionNumber;
    406             return this;
    407         }
    408 
    409         public RecordedProgram build() {
    410             if (TextUtils.isEmpty(mTitle)) {
    411                 // If title is null, series cannot be generated for this program.
    412                 setSeriesId(null);
    413             } else if (TextUtils.isEmpty(mSeriesId) && !TextUtils.isEmpty(mEpisodeNumber)) {
    414                 // If series ID is not set, generate it for the episodic program of other TV input.
    415                 setSeriesId(BaseProgram.generateSeriesId(mPackageName, mTitle));
    416             }
    417             return new RecordedProgram(mId, mPackageName, mInputId, mChannelId, mTitle, mSeriesId,
    418                     mSeasonNumber, mSeasonTitle, mEpisodeNumber, mEpisodeTitle, mStartTimeUtcMillis,
    419                     mEndTimeUtcMillis, mBroadcastGenres, mCanonicalGenres, mShortDescription,
    420                     mLongDescription, mVideoWidth, mVideoHeight, mAudioLanguage, mContentRatings,
    421                     mPosterArtUri, mThumbnailUri, mSearchable, mDataUri, mDataBytes,
    422                     mDurationMillis, mExpireTimeUtcMillis, mInternalProviderFlag1,
    423                     mInternalProviderFlag2, mInternalProviderFlag3, mInternalProviderFlag4,
    424                     mVersionNumber);
    425         }
    426     }
    427 
    428     public static Builder builder() { return new Builder(); }
    429 
    430     public static Builder buildFrom(RecordedProgram orig) {
    431         return builder()
    432                 .setId(orig.getId())
    433                 .setPackageName(orig.getPackageName())
    434                 .setInputId(orig.getInputId())
    435                 .setChannelId(orig.getChannelId())
    436                 .setTitle(orig.getTitle())
    437                 .setSeriesId(orig.getSeriesId())
    438                 .setSeasonNumber(orig.getSeasonNumber())
    439                 .setSeasonTitle(orig.getSeasonTitle())
    440                 .setEpisodeNumber(orig.getEpisodeNumber())
    441                 .setEpisodeTitle(orig.getEpisodeTitle())
    442                 .setStartTimeUtcMillis(orig.getStartTimeUtcMillis())
    443                 .setEndTimeUtcMillis(orig.getEndTimeUtcMillis())
    444                 .setBroadcastGenres(orig.getBroadcastGenres())
    445                 .setCanonicalGenres(orig.getCanonicalGenres())
    446                 .setShortDescription(orig.getDescription())
    447                 .setLongDescription(orig.getLongDescription())
    448                 .setVideoWidth(orig.getVideoWidth())
    449                 .setVideoHeight(orig.getVideoHeight())
    450                 .setAudioLanguage(orig.getAudioLanguage())
    451                 .setContentRatings(orig.getContentRatings())
    452                 .setPosterArtUri(orig.getPosterArtUri())
    453                 .setThumbnailUri(orig.getThumbnailUri())
    454                 .setSearchable(orig.isSearchable())
    455                 .setInternalProviderFlag1(orig.getInternalProviderFlag1())
    456                 .setInternalProviderFlag2(orig.getInternalProviderFlag2())
    457                 .setInternalProviderFlag3(orig.getInternalProviderFlag3())
    458                 .setInternalProviderFlag4(orig.getInternalProviderFlag4())
    459                 .setVersionNumber(orig.getVersionNumber());
    460     }
    461 
    462     public static final Comparator<RecordedProgram> START_TIME_THEN_ID_COMPARATOR =
    463             new Comparator<RecordedProgram>() {
    464                 @Override
    465                 public int compare(RecordedProgram lhs, RecordedProgram rhs) {
    466                     int res =
    467                             Long.compare(lhs.getStartTimeUtcMillis(), rhs.getStartTimeUtcMillis());
    468                     if (res != 0) {
    469                         return res;
    470                     }
    471                     return Long.compare(lhs.mId, rhs.mId);
    472                 }
    473     };
    474 
    475     private static final long CLIPPED_THRESHOLD_MS = TimeUnit.MINUTES.toMillis(5);
    476 
    477     private final long mId;
    478     private final String mPackageName;
    479     private final String mInputId;
    480     private final long mChannelId;
    481     private final String mTitle;
    482     private final String mSeriesId;
    483     private final String mSeasonNumber;
    484     private final String mSeasonTitle;
    485     private final String mEpisodeNumber;
    486     private final String mEpisodeTitle;
    487     private final long mStartTimeUtcMillis;
    488     private final long mEndTimeUtcMillis;
    489     private final String[] mBroadcastGenres;
    490     private final String[] mCanonicalGenres;
    491     private final String mShortDescription;
    492     private final String mLongDescription;
    493     private final int mVideoWidth;
    494     private final int mVideoHeight;
    495     private final String mAudioLanguage;
    496     private final TvContentRating[] mContentRatings;
    497     private final String mPosterArtUri;
    498     private final String mThumbnailUri;
    499     private final boolean mSearchable;
    500     private final Uri mDataUri;
    501     private final long mDataBytes;
    502     private final long mDurationMillis;
    503     private final long mExpireTimeUtcMillis;
    504     private final int mInternalProviderFlag1;
    505     private final int mInternalProviderFlag2;
    506     private final int mInternalProviderFlag3;
    507     private final int mInternalProviderFlag4;
    508     private final int mVersionNumber;
    509 
    510     private RecordedProgram(long id, String packageName, String inputId, long channelId,
    511             String title, String seriesId, String seasonNumber, String seasonTitle,
    512             String episodeNumber, String episodeTitle, long startTimeUtcMillis,
    513             long endTimeUtcMillis, String[] broadcastGenres, String[] canonicalGenres,
    514             String shortDescription, String longDescription, int videoWidth, int videoHeight,
    515             String audioLanguage, TvContentRating[] contentRatings, String posterArtUri,
    516             String thumbnailUri, boolean searchable, Uri dataUri, long dataBytes,
    517             long durationMillis, long expireTimeUtcMillis, int internalProviderFlag1,
    518             int internalProviderFlag2, int internalProviderFlag3, int internalProviderFlag4,
    519             int versionNumber) {
    520         mId = id;
    521         mPackageName = packageName;
    522         mInputId = inputId;
    523         mChannelId = channelId;
    524         mTitle = title;
    525         mSeriesId = seriesId;
    526         mSeasonNumber = seasonNumber;
    527         mSeasonTitle = seasonTitle;
    528         mEpisodeNumber = episodeNumber;
    529         mEpisodeTitle = episodeTitle;
    530         mStartTimeUtcMillis = startTimeUtcMillis;
    531         mEndTimeUtcMillis = endTimeUtcMillis;
    532         mBroadcastGenres = broadcastGenres;
    533         mCanonicalGenres = canonicalGenres;
    534         mShortDescription = shortDescription;
    535         mLongDescription = longDescription;
    536         mVideoWidth = videoWidth;
    537         mVideoHeight = videoHeight;
    538 
    539         mAudioLanguage = audioLanguage;
    540         mContentRatings = contentRatings;
    541         mPosterArtUri = posterArtUri;
    542         mThumbnailUri = thumbnailUri;
    543         mSearchable = searchable;
    544         mDataUri = dataUri;
    545         mDataBytes = dataBytes;
    546         mDurationMillis = durationMillis;
    547         mExpireTimeUtcMillis = expireTimeUtcMillis;
    548         mInternalProviderFlag1 = internalProviderFlag1;
    549         mInternalProviderFlag2 = internalProviderFlag2;
    550         mInternalProviderFlag3 = internalProviderFlag3;
    551         mInternalProviderFlag4 = internalProviderFlag4;
    552         mVersionNumber = versionNumber;
    553     }
    554 
    555     public String getAudioLanguage() {
    556         return mAudioLanguage;
    557     }
    558 
    559     public String[] getBroadcastGenres() {
    560         return mBroadcastGenres;
    561     }
    562 
    563     public String[] getCanonicalGenres() {
    564         return mCanonicalGenres;
    565     }
    566 
    567     /**
    568      * Returns array of canonical genre ID's for this recorded program.
    569      */
    570     @Override
    571     public int[] getCanonicalGenreIds() {
    572         if (mCanonicalGenres == null) {
    573             return null;
    574         }
    575         int[] genreIds = new int[mCanonicalGenres.length];
    576         for (int i = 0; i < mCanonicalGenres.length; i++) {
    577             genreIds[i] = GenreItems.getId(mCanonicalGenres[i]);
    578         }
    579         return genreIds;
    580     }
    581 
    582     @Override
    583     public long getChannelId() {
    584         return mChannelId;
    585     }
    586 
    587     @Nullable
    588     @Override
    589     public TvContentRating[] getContentRatings() {
    590         return mContentRatings;
    591     }
    592 
    593     public Uri getDataUri() {
    594         return mDataUri;
    595     }
    596 
    597     public long getDataBytes() {
    598         return mDataBytes;
    599     }
    600 
    601     @Override
    602     public long getDurationMillis() {
    603         return mDurationMillis;
    604     }
    605 
    606     @Override
    607     public long getEndTimeUtcMillis() {
    608         return mEndTimeUtcMillis;
    609     }
    610 
    611     @Override
    612     public String getEpisodeNumber() {
    613         return mEpisodeNumber;
    614     }
    615 
    616     @Override
    617     public String getEpisodeTitle() {
    618         return mEpisodeTitle;
    619     }
    620 
    621     @Nullable
    622     public String getEpisodeDisplayNumber(Context context) {
    623         if (!TextUtils.isEmpty(mEpisodeNumber)) {
    624             if (TextUtils.equals(mSeasonNumber, "0")) {
    625                 // Do not show "S0: ".
    626                 return String.format(context.getResources().getString(
    627                         R.string.display_episode_number_format_no_season_number), mEpisodeNumber);
    628             } else {
    629                 return String.format(context.getResources().getString(
    630                         R.string.display_episode_number_format), mSeasonNumber, mEpisodeNumber);
    631             }
    632         }
    633         return null;
    634     }
    635 
    636     public long getExpireTimeUtcMillis() {
    637         return mExpireTimeUtcMillis;
    638     }
    639 
    640     public long getId() {
    641         return mId;
    642     }
    643 
    644     public String getPackageName() {
    645         return mPackageName;
    646     }
    647 
    648     public String getInputId() {
    649         return mInputId;
    650     }
    651 
    652     public int getInternalProviderFlag1() {
    653         return mInternalProviderFlag1;
    654     }
    655 
    656     public int getInternalProviderFlag2() {
    657         return mInternalProviderFlag2;
    658     }
    659 
    660     public int getInternalProviderFlag3() {
    661         return mInternalProviderFlag3;
    662     }
    663 
    664     public int getInternalProviderFlag4() {
    665         return mInternalProviderFlag4;
    666     }
    667 
    668     @Override
    669     public String getDescription() {
    670         return mShortDescription;
    671     }
    672 
    673     @Override
    674     public String getLongDescription() {
    675         return mLongDescription;
    676     }
    677 
    678     @Override
    679     public String getPosterArtUri() {
    680         return mPosterArtUri;
    681     }
    682 
    683     @Override
    684     public boolean isValid() {
    685         return true;
    686     }
    687 
    688     public boolean isSearchable() {
    689         return mSearchable;
    690     }
    691 
    692     @Override
    693     public String getSeriesId() {
    694         return mSeriesId;
    695     }
    696 
    697     @Override
    698     public String getSeasonNumber() {
    699         return mSeasonNumber;
    700     }
    701 
    702     public String getSeasonTitle() {
    703         return mSeasonTitle;
    704     }
    705 
    706     @Override
    707     public long getStartTimeUtcMillis() {
    708         return mStartTimeUtcMillis;
    709     }
    710 
    711     @Override
    712     public String getThumbnailUri() {
    713         return mThumbnailUri;
    714     }
    715 
    716     @Override
    717     public String getTitle() {
    718         return mTitle;
    719     }
    720 
    721     public Uri getUri() {
    722         return ContentUris.withAppendedId(RecordedPrograms.CONTENT_URI, mId);
    723     }
    724 
    725     public int getVersionNumber() {
    726         return mVersionNumber;
    727     }
    728 
    729     public int getVideoHeight() {
    730         return mVideoHeight;
    731     }
    732 
    733     public int getVideoWidth() {
    734         return mVideoWidth;
    735     }
    736 
    737     /**
    738      * Checks whether the recording has been clipped or not.
    739      */
    740     public boolean isClipped() {
    741         return mEndTimeUtcMillis - mStartTimeUtcMillis - mDurationMillis > CLIPPED_THRESHOLD_MS;
    742     }
    743 
    744     @Override
    745     public boolean equals(Object o) {
    746         if (this == o) return true;
    747         if (o == null || getClass() != o.getClass()) return false;
    748         RecordedProgram that = (RecordedProgram) o;
    749         return Objects.equals(mId, that.mId) &&
    750                 Objects.equals(mChannelId, that.mChannelId) &&
    751                 Objects.equals(mSeriesId, that.mSeriesId) &&
    752                 Objects.equals(mSeasonNumber, that.mSeasonNumber) &&
    753                 Objects.equals(mSeasonTitle, that.mSeasonTitle) &&
    754                 Objects.equals(mEpisodeNumber, that.mEpisodeNumber) &&
    755                 Objects.equals(mStartTimeUtcMillis, that.mStartTimeUtcMillis) &&
    756                 Objects.equals(mEndTimeUtcMillis, that.mEndTimeUtcMillis) &&
    757                 Objects.equals(mVideoWidth, that.mVideoWidth) &&
    758                 Objects.equals(mVideoHeight, that.mVideoHeight) &&
    759                 Objects.equals(mSearchable, that.mSearchable) &&
    760                 Objects.equals(mDataBytes, that.mDataBytes) &&
    761                 Objects.equals(mDurationMillis, that.mDurationMillis) &&
    762                 Objects.equals(mExpireTimeUtcMillis, that.mExpireTimeUtcMillis) &&
    763                 Objects.equals(mInternalProviderFlag1, that.mInternalProviderFlag1) &&
    764                 Objects.equals(mInternalProviderFlag2, that.mInternalProviderFlag2) &&
    765                 Objects.equals(mInternalProviderFlag3, that.mInternalProviderFlag3) &&
    766                 Objects.equals(mInternalProviderFlag4, that.mInternalProviderFlag4) &&
    767                 Objects.equals(mVersionNumber, that.mVersionNumber) &&
    768                 Objects.equals(mTitle, that.mTitle) &&
    769                 Objects.equals(mEpisodeTitle, that.mEpisodeTitle) &&
    770                 Arrays.equals(mBroadcastGenres, that.mBroadcastGenres) &&
    771                 Arrays.equals(mCanonicalGenres, that.mCanonicalGenres) &&
    772                 Objects.equals(mShortDescription, that.mShortDescription) &&
    773                 Objects.equals(mLongDescription, that.mLongDescription) &&
    774                 Objects.equals(mAudioLanguage, that.mAudioLanguage) &&
    775                 Arrays.equals(mContentRatings, that.mContentRatings) &&
    776                 Objects.equals(mPosterArtUri, that.mPosterArtUri) &&
    777                 Objects.equals(mThumbnailUri, that.mThumbnailUri);
    778     }
    779 
    780     /**
    781      * Hashes based on the ID.
    782      */
    783     @Override
    784     public int hashCode() {
    785         return Objects.hash(mId);
    786     }
    787 
    788     @Override
    789     public String toString() {
    790         return "RecordedProgram"
    791                 + "[" +  mId +
    792                 "]{ mPackageName=" + mPackageName +
    793                 ", mInputId='" + mInputId + '\'' +
    794                 ", mChannelId='" + mChannelId + '\'' +
    795                 ", mTitle='" + mTitle + '\'' +
    796                 ", mSeriesId='" + mSeriesId + '\'' +
    797                 ", mEpisodeNumber=" + mEpisodeNumber +
    798                 ", mEpisodeTitle='" + mEpisodeTitle + '\'' +
    799                 ", mStartTimeUtcMillis=" + mStartTimeUtcMillis +
    800                 ", mEndTimeUtcMillis=" + mEndTimeUtcMillis +
    801                 ", mBroadcastGenres=" +
    802                         (mBroadcastGenres != null ? Arrays.toString(mBroadcastGenres) : "null") +
    803                 ", mCanonicalGenres=" +
    804                         (mCanonicalGenres != null ? Arrays.toString(mCanonicalGenres) : "null") +
    805                 ", mShortDescription='" + mShortDescription + '\'' +
    806                 ", mLongDescription='" + mLongDescription + '\'' +
    807                 ", mVideoHeight=" + mVideoHeight +
    808                 ", mVideoWidth=" + mVideoWidth +
    809                 ", mAudioLanguage='" + mAudioLanguage + '\'' +
    810                 ", mContentRatings='" +
    811                         TvContentRatingCache.contentRatingsToString(mContentRatings) + '\'' +
    812                 ", mPosterArtUri=" + mPosterArtUri +
    813                 ", mThumbnailUri=" + mThumbnailUri +
    814                 ", mSearchable=" + mSearchable +
    815                 ", mDataUri=" + mDataUri +
    816                 ", mDataBytes=" + mDataBytes +
    817                 ", mDurationMillis=" + mDurationMillis +
    818                 ", mExpireTimeUtcMillis=" + mExpireTimeUtcMillis +
    819                 ", mInternalProviderFlag1=" + mInternalProviderFlag1 +
    820                 ", mInternalProviderFlag2=" + mInternalProviderFlag2 +
    821                 ", mInternalProviderFlag3=" + mInternalProviderFlag3 +
    822                 ", mInternalProviderFlag4=" + mInternalProviderFlag4 +
    823                 ", mSeasonNumber=" + mSeasonNumber +
    824                 ", mSeasonTitle=" + mSeasonTitle +
    825                 ", mVersionNumber=" + mVersionNumber +
    826                 '}';
    827     }
    828 
    829     @Nullable
    830     private static String safeToString(@Nullable Object o) {
    831         return o == null ? null : o.toString();
    832     }
    833 
    834     @Nullable
    835     private static String safeEncode(@Nullable String[] genres) {
    836         return genres == null ? null : TvContract.Programs.Genres.encode(genres);
    837     }
    838 
    839     /**
    840      * Returns an array containing all of the elements in the list.
    841      */
    842     public static RecordedProgram[] toArray(Collection<RecordedProgram> recordedPrograms) {
    843         return recordedPrograms.toArray(new RecordedProgram[recordedPrograms.size()]);
    844     }
    845 }
    846