Home | History | Annotate | Download | only in dvr
      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.dvr;
     18 
     19 import android.content.ContentValues;
     20 import android.content.Context;
     21 import android.database.Cursor;
     22 import android.os.Parcel;
     23 import android.os.Parcelable;
     24 import android.support.annotation.IntDef;
     25 import android.support.annotation.VisibleForTesting;
     26 import android.text.TextUtils;
     27 import android.util.Range;
     28 
     29 import com.android.tv.R;
     30 import com.android.tv.common.SoftPreconditions;
     31 import com.android.tv.data.Channel;
     32 import com.android.tv.data.Program;
     33 import com.android.tv.dvr.provider.DvrContract.Schedules;
     34 import com.android.tv.util.CompositeComparator;
     35 import com.android.tv.util.Utils;
     36 
     37 import java.lang.annotation.Retention;
     38 import java.lang.annotation.RetentionPolicy;
     39 import java.util.Collection;
     40 import java.util.Comparator;
     41 import java.util.Objects;
     42 
     43 /**
     44  * A data class for one recording contents.
     45  */
     46 @VisibleForTesting
     47 public final class ScheduledRecording implements Parcelable {
     48     private static final String TAG = "ScheduledRecording";
     49 
     50     /**
     51      * Indicates that the ID is not assigned yet.
     52      */
     53     public static final long ID_NOT_SET = 0;
     54 
     55     /**
     56      * The default priority of the recording.
     57      */
     58     public static final long DEFAULT_PRIORITY = Long.MAX_VALUE >> 1;
     59 
     60     /**
     61      * Compares the start time in ascending order.
     62      */
     63     public static final Comparator<ScheduledRecording> START_TIME_COMPARATOR
     64             = new Comparator<ScheduledRecording>() {
     65         @Override
     66         public int compare(ScheduledRecording lhs, ScheduledRecording rhs) {
     67             return Long.compare(lhs.mStartTimeMs, rhs.mStartTimeMs);
     68         }
     69     };
     70 
     71     /**
     72      * Compares the end time in ascending order.
     73      */
     74     public static final Comparator<ScheduledRecording> END_TIME_COMPARATOR
     75             = new Comparator<ScheduledRecording>() {
     76         @Override
     77         public int compare(ScheduledRecording lhs, ScheduledRecording rhs) {
     78             return Long.compare(lhs.mEndTimeMs, rhs.mEndTimeMs);
     79         }
     80     };
     81 
     82     /**
     83      * Compares ID in ascending order. The schedule with the larger ID was created later.
     84      */
     85     public static final Comparator<ScheduledRecording> ID_COMPARATOR
     86             = new Comparator<ScheduledRecording>() {
     87         @Override
     88         public int compare(ScheduledRecording lhs, ScheduledRecording rhs) {
     89             return Long.compare(lhs.mId, rhs.mId);
     90         }
     91     };
     92 
     93     /**
     94      * Compares the priority in ascending order.
     95      */
     96     public static final Comparator<ScheduledRecording> PRIORITY_COMPARATOR
     97             = new Comparator<ScheduledRecording>() {
     98         @Override
     99         public int compare(ScheduledRecording lhs, ScheduledRecording rhs) {
    100             return Long.compare(lhs.mPriority, rhs.mPriority);
    101         }
    102     };
    103 
    104     /**
    105      * Compares start time in ascending order and then priority in descending order and then ID in
    106      * descending order.
    107      */
    108     public static final Comparator<ScheduledRecording> START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR
    109             = new CompositeComparator<>(START_TIME_COMPARATOR, PRIORITY_COMPARATOR.reversed(),
    110             ID_COMPARATOR.reversed());
    111 
    112     /**
    113      * Builds scheduled recordings from programs.
    114      */
    115     public static Builder builder(String inputId, Program p) {
    116         return new Builder()
    117                 .setInputId(inputId)
    118                 .setChannelId(p.getChannelId())
    119                 .setStartTimeMs(p.getStartTimeUtcMillis()).setEndTimeMs(p.getEndTimeUtcMillis())
    120                 .setProgramId(p.getId())
    121                 .setProgramTitle(p.getTitle())
    122                 .setSeasonNumber(p.getSeasonNumber())
    123                 .setEpisodeNumber(p.getEpisodeNumber())
    124                 .setEpisodeTitle(p.getEpisodeTitle())
    125                 .setProgramDescription(p.getDescription())
    126                 .setProgramLongDescription(p.getLongDescription())
    127                 .setProgramPosterArtUri(p.getPosterArtUri())
    128                 .setProgramThumbnailUri(p.getThumbnailUri())
    129                 .setType(TYPE_PROGRAM);
    130     }
    131 
    132     public static Builder builder(String inputId, long channelId, long startTime, long endTime) {
    133         return new Builder()
    134                 .setInputId(inputId)
    135                 .setChannelId(channelId)
    136                 .setStartTimeMs(startTime)
    137                 .setEndTimeMs(endTime)
    138                 .setType(TYPE_TIMED);
    139     }
    140 
    141     /**
    142      * Creates a new Builder with the values set from the {@link RecordedProgram}.
    143      */
    144     @VisibleForTesting
    145     public static Builder builder(RecordedProgram p) {
    146         boolean isProgramRecording = !TextUtils.isEmpty(p.getTitle());
    147         return new Builder()
    148                 .setInputId(p.getInputId())
    149                 .setChannelId(p.getChannelId())
    150                 .setType(isProgramRecording ? TYPE_PROGRAM : TYPE_TIMED)
    151                 .setStartTimeMs(p.getStartTimeUtcMillis())
    152                 .setEndTimeMs(p.getEndTimeUtcMillis())
    153                 .setProgramTitle(p.getTitle())
    154                 .setSeasonNumber(p.getSeasonNumber())
    155                 .setEpisodeNumber(p.getEpisodeNumber())
    156                 .setEpisodeTitle(p.getEpisodeTitle())
    157                 .setProgramDescription(p.getDescription())
    158                 .setProgramLongDescription(p.getLongDescription())
    159                 .setProgramPosterArtUri(p.getPosterArtUri())
    160                 .setProgramThumbnailUri(p.getThumbnailUri())
    161                 .setState(STATE_RECORDING_FINISHED);
    162     }
    163 
    164     public static final class Builder {
    165         private long mId = ID_NOT_SET;
    166         private long mPriority = DvrScheduleManager.DEFAULT_PRIORITY;
    167         private String mInputId;
    168         private long mChannelId;
    169         private long mProgramId = ID_NOT_SET;
    170         private String mProgramTitle;
    171         private @RecordingType int mType;
    172         private long mStartTimeMs;
    173         private long mEndTimeMs;
    174         private String mSeasonNumber;
    175         private String mEpisodeNumber;
    176         private String mEpisodeTitle;
    177         private String mProgramDescription;
    178         private String mProgramLongDescription;
    179         private String mProgramPosterArtUri;
    180         private String mProgramThumbnailUri;
    181         private @RecordingState int mState;
    182         private long mSeriesRecordingId = ID_NOT_SET;
    183 
    184         private Builder() { }
    185 
    186         public Builder setId(long id) {
    187             mId = id;
    188             return this;
    189         }
    190 
    191         public Builder setPriority(long priority) {
    192             mPriority = priority;
    193             return this;
    194         }
    195 
    196         public Builder setInputId(String inputId) {
    197             mInputId = inputId;
    198             return this;
    199         }
    200 
    201         public Builder setChannelId(long channelId) {
    202             mChannelId = channelId;
    203             return this;
    204         }
    205 
    206         public Builder setProgramId(long programId) {
    207             mProgramId = programId;
    208             return this;
    209         }
    210 
    211         public Builder setProgramTitle(String programTitle) {
    212             mProgramTitle = programTitle;
    213             return this;
    214         }
    215 
    216         private Builder setType(@RecordingType int type) {
    217             mType = type;
    218             return this;
    219         }
    220 
    221         public Builder setStartTimeMs(long startTimeMs) {
    222             mStartTimeMs = startTimeMs;
    223             return this;
    224         }
    225 
    226         public Builder setEndTimeMs(long endTimeMs) {
    227             mEndTimeMs = endTimeMs;
    228             return this;
    229         }
    230 
    231         public Builder setSeasonNumber(String seasonNumber) {
    232             mSeasonNumber = seasonNumber;
    233             return this;
    234         }
    235 
    236         public Builder setEpisodeNumber(String episodeNumber) {
    237             mEpisodeNumber = episodeNumber;
    238             return this;
    239         }
    240 
    241         public Builder setEpisodeTitle(String episodeTitle) {
    242             mEpisodeTitle = episodeTitle;
    243             return this;
    244         }
    245 
    246         public Builder setProgramDescription(String description) {
    247             mProgramDescription = description;
    248             return this;
    249         }
    250 
    251         public Builder setProgramLongDescription(String longDescription) {
    252             mProgramLongDescription = longDescription;
    253             return this;
    254         }
    255 
    256         public Builder setProgramPosterArtUri(String programPosterArtUri) {
    257             mProgramPosterArtUri = programPosterArtUri;
    258             return this;
    259         }
    260 
    261         public Builder setProgramThumbnailUri(String programThumbnailUri) {
    262             mProgramThumbnailUri = programThumbnailUri;
    263             return this;
    264         }
    265 
    266         public Builder setState(@RecordingState int state) {
    267             mState = state;
    268             return this;
    269         }
    270 
    271         public Builder setSeriesRecordingId(long seriesRecordingId) {
    272             mSeriesRecordingId = seriesRecordingId;
    273             return this;
    274         }
    275 
    276         public ScheduledRecording build() {
    277             return new ScheduledRecording(mId, mPriority, mInputId, mChannelId, mProgramId,
    278                     mProgramTitle, mType, mStartTimeMs, mEndTimeMs, mSeasonNumber, mEpisodeNumber,
    279                     mEpisodeTitle, mProgramDescription, mProgramLongDescription,
    280                     mProgramPosterArtUri, mProgramThumbnailUri, mState, mSeriesRecordingId);
    281         }
    282     }
    283 
    284     /**
    285      * Creates {@link Builder} object from the given original {@code Recording}.
    286      */
    287     public static Builder buildFrom(ScheduledRecording orig) {
    288         return new Builder()
    289                 .setId(orig.mId)
    290                 .setInputId(orig.mInputId)
    291                 .setChannelId(orig.mChannelId)
    292                 .setEndTimeMs(orig.mEndTimeMs)
    293                 .setSeriesRecordingId(orig.mSeriesRecordingId)
    294                 .setPriority(orig.mPriority)
    295                 .setProgramId(orig.mProgramId)
    296                 .setProgramTitle(orig.mProgramTitle)
    297                 .setStartTimeMs(orig.mStartTimeMs)
    298                 .setSeasonNumber(orig.getSeasonNumber())
    299                 .setEpisodeNumber(orig.getEpisodeNumber())
    300                 .setEpisodeTitle(orig.getEpisodeTitle())
    301                 .setProgramDescription(orig.getProgramDescription())
    302                 .setProgramLongDescription(orig.getProgramLongDescription())
    303                 .setProgramPosterArtUri(orig.getProgramPosterArtUri())
    304                 .setProgramThumbnailUri(orig.getProgramThumbnailUri())
    305                 .setState(orig.mState).setType(orig.mType);
    306     }
    307 
    308     @Retention(RetentionPolicy.SOURCE)
    309     @IntDef({STATE_RECORDING_NOT_STARTED, STATE_RECORDING_IN_PROGRESS, STATE_RECORDING_FINISHED,
    310             STATE_RECORDING_FAILED, STATE_RECORDING_CLIPPED, STATE_RECORDING_DELETED,
    311             STATE_RECORDING_CANCELED})
    312     public @interface RecordingState {}
    313     public static final int STATE_RECORDING_NOT_STARTED = 0;
    314     public static final int STATE_RECORDING_IN_PROGRESS = 1;
    315     public static final int STATE_RECORDING_FINISHED = 2;
    316     public static final int STATE_RECORDING_FAILED = 3;
    317     public static final int STATE_RECORDING_CLIPPED = 4;
    318     public static final int STATE_RECORDING_DELETED = 5;
    319     public static final int STATE_RECORDING_CANCELED = 6;
    320 
    321     @Retention(RetentionPolicy.SOURCE)
    322     @IntDef({TYPE_TIMED, TYPE_PROGRAM})
    323     public @interface RecordingType {}
    324     /**
    325      * Record with given time range.
    326      */
    327     public static final int TYPE_TIMED = 1;
    328     /**
    329      * Record with a given program.
    330      */
    331     public static final int TYPE_PROGRAM = 2;
    332 
    333     @RecordingType private final int mType;
    334 
    335     /**
    336      * Use this projection if you want to create {@link ScheduledRecording} object using
    337      * {@link #fromCursor}.
    338      */
    339     public static final String[] PROJECTION = {
    340             // Columns must match what is read in #fromCursor
    341             Schedules._ID,
    342             Schedules.COLUMN_PRIORITY,
    343             Schedules.COLUMN_TYPE,
    344             Schedules.COLUMN_INPUT_ID,
    345             Schedules.COLUMN_CHANNEL_ID,
    346             Schedules.COLUMN_PROGRAM_ID,
    347             Schedules.COLUMN_PROGRAM_TITLE,
    348             Schedules.COLUMN_START_TIME_UTC_MILLIS,
    349             Schedules.COLUMN_END_TIME_UTC_MILLIS,
    350             Schedules.COLUMN_SEASON_NUMBER,
    351             Schedules.COLUMN_EPISODE_NUMBER,
    352             Schedules.COLUMN_EPISODE_TITLE,
    353             Schedules.COLUMN_PROGRAM_DESCRIPTION,
    354             Schedules.COLUMN_PROGRAM_LONG_DESCRIPTION,
    355             Schedules.COLUMN_PROGRAM_POST_ART_URI,
    356             Schedules.COLUMN_PROGRAM_THUMBNAIL_URI,
    357             Schedules.COLUMN_STATE,
    358             Schedules.COLUMN_SERIES_RECORDING_ID};
    359 
    360     /**
    361      * Creates {@link ScheduledRecording} object from the given {@link Cursor}.
    362      */
    363     public static ScheduledRecording fromCursor(Cursor c) {
    364         int index = -1;
    365         return new Builder()
    366                 .setId(c.getLong(++index))
    367                 .setPriority(c.getLong(++index))
    368                 .setType(recordingType(c.getString(++index)))
    369                 .setInputId(c.getString(++index))
    370                 .setChannelId(c.getLong(++index))
    371                 .setProgramId(c.getLong(++index))
    372                 .setProgramTitle(c.getString(++index))
    373                 .setStartTimeMs(c.getLong(++index))
    374                 .setEndTimeMs(c.getLong(++index))
    375                 .setSeasonNumber(c.getString(++index))
    376                 .setEpisodeNumber(c.getString(++index))
    377                 .setEpisodeTitle(c.getString(++index))
    378                 .setProgramDescription(c.getString(++index))
    379                 .setProgramLongDescription(c.getString(++index))
    380                 .setProgramPosterArtUri(c.getString(++index))
    381                 .setProgramThumbnailUri(c.getString(++index))
    382                 .setState(recordingState(c.getString(++index)))
    383                 .setSeriesRecordingId(c.getLong(++index))
    384                 .build();
    385     }
    386 
    387     public static ContentValues toContentValues(ScheduledRecording r) {
    388         ContentValues values = new ContentValues();
    389         if (r.getId() != ID_NOT_SET) {
    390             values.put(Schedules._ID, r.getId());
    391         }
    392         values.put(Schedules.COLUMN_INPUT_ID, r.getInputId());
    393         values.put(Schedules.COLUMN_CHANNEL_ID, r.getChannelId());
    394         values.put(Schedules.COLUMN_PROGRAM_ID, r.getProgramId());
    395         values.put(Schedules.COLUMN_PROGRAM_TITLE, r.getProgramTitle());
    396         values.put(Schedules.COLUMN_PRIORITY, r.getPriority());
    397         values.put(Schedules.COLUMN_START_TIME_UTC_MILLIS, r.getStartTimeMs());
    398         values.put(Schedules.COLUMN_END_TIME_UTC_MILLIS, r.getEndTimeMs());
    399         values.put(Schedules.COLUMN_SEASON_NUMBER, r.getSeasonNumber());
    400         values.put(Schedules.COLUMN_EPISODE_NUMBER, r.getEpisodeNumber());
    401         values.put(Schedules.COLUMN_EPISODE_TITLE, r.getEpisodeTitle());
    402         values.put(Schedules.COLUMN_PROGRAM_DESCRIPTION, r.getProgramDescription());
    403         values.put(Schedules.COLUMN_PROGRAM_LONG_DESCRIPTION, r.getProgramLongDescription());
    404         values.put(Schedules.COLUMN_PROGRAM_POST_ART_URI, r.getProgramPosterArtUri());
    405         values.put(Schedules.COLUMN_PROGRAM_THUMBNAIL_URI, r.getProgramThumbnailUri());
    406         values.put(Schedules.COLUMN_STATE, recordingState(r.getState()));
    407         values.put(Schedules.COLUMN_TYPE, recordingType(r.getType()));
    408         if (r.getSeriesRecordingId() != ID_NOT_SET) {
    409             values.put(Schedules.COLUMN_SERIES_RECORDING_ID, r.getSeriesRecordingId());
    410         } else {
    411             values.putNull(Schedules.COLUMN_SERIES_RECORDING_ID);
    412         }
    413         return values;
    414     }
    415 
    416     public static ScheduledRecording fromParcel(Parcel in) {
    417         return new Builder()
    418                 .setId(in.readLong())
    419                 .setPriority(in.readLong())
    420                 .setInputId(in.readString())
    421                 .setChannelId(in.readLong())
    422                 .setProgramId(in.readLong())
    423                 .setProgramTitle(in.readString())
    424                 .setType(in.readInt())
    425                 .setStartTimeMs(in.readLong())
    426                 .setEndTimeMs(in.readLong())
    427                 .setSeasonNumber(in.readString())
    428                 .setEpisodeNumber(in.readString())
    429                 .setEpisodeTitle(in.readString())
    430                 .setProgramDescription(in.readString())
    431                 .setProgramLongDescription(in.readString())
    432                 .setProgramPosterArtUri(in.readString())
    433                 .setProgramThumbnailUri(in.readString())
    434                 .setState(in.readInt())
    435                 .setSeriesRecordingId(in.readLong())
    436                 .build();
    437     }
    438 
    439     public static final Parcelable.Creator<ScheduledRecording> CREATOR =
    440             new Parcelable.Creator<ScheduledRecording>() {
    441         @Override
    442         public ScheduledRecording createFromParcel(Parcel in) {
    443           return ScheduledRecording.fromParcel(in);
    444         }
    445 
    446         @Override
    447         public ScheduledRecording[] newArray(int size) {
    448           return new ScheduledRecording[size];
    449         }
    450     };
    451 
    452     /**
    453      * The ID internal to Live TV
    454      */
    455     private long mId;
    456 
    457     /**
    458      * The priority of this recording.
    459      *
    460      * <p> The highest number is recorded first. If there is a tie in priority then the higher id
    461      * wins.
    462      */
    463     private final long mPriority;
    464 
    465     private final String mInputId;
    466     private final long mChannelId;
    467     /**
    468      * Optional id of the associated program.
    469      */
    470     private final long mProgramId;
    471     private final String mProgramTitle;
    472 
    473     private final long mStartTimeMs;
    474     private final long mEndTimeMs;
    475     private final String mSeasonNumber;
    476     private final String mEpisodeNumber;
    477     private final String mEpisodeTitle;
    478     private final String mProgramDescription;
    479     private final String mProgramLongDescription;
    480     private final String mProgramPosterArtUri;
    481     private final String mProgramThumbnailUri;
    482     @RecordingState private final int mState;
    483     private final long mSeriesRecordingId;
    484 
    485     private ScheduledRecording(long id, long priority, String inputId, long channelId, long programId,
    486             String programTitle, @RecordingType int type, long startTime, long endTime,
    487             String seasonNumber, String episodeNumber, String episodeTitle,
    488             String programDescription, String programLongDescription, String programPosterArtUri,
    489             String programThumbnailUri, @RecordingState int state, long seriesRecordingId) {
    490         mId = id;
    491         mPriority = priority;
    492         mInputId = inputId;
    493         mChannelId = channelId;
    494         mProgramId = programId;
    495         mProgramTitle = programTitle;
    496         mType = type;
    497         mStartTimeMs = startTime;
    498         mEndTimeMs = endTime;
    499         mSeasonNumber = seasonNumber;
    500         mEpisodeNumber = episodeNumber;
    501         mEpisodeTitle = episodeTitle;
    502         mProgramDescription = programDescription;
    503         mProgramLongDescription = programLongDescription;
    504         mProgramPosterArtUri = programPosterArtUri;
    505         mProgramThumbnailUri = programThumbnailUri;
    506         mState = state;
    507         mSeriesRecordingId = seriesRecordingId;
    508     }
    509 
    510     /**
    511      * Returns recording schedule type. The possible types are {@link #TYPE_PROGRAM} and
    512      * {@link #TYPE_TIMED}.
    513      */
    514     @RecordingType
    515     public int getType() {
    516         return mType;
    517     }
    518 
    519     /**
    520      * Returns schedules' input id.
    521      */
    522     public String getInputId() {
    523         return mInputId;
    524     }
    525 
    526     /**
    527      * Returns recorded {@link Channel}.
    528      */
    529     public long getChannelId() {
    530         return mChannelId;
    531     }
    532 
    533     /**
    534      * Return the optional program id
    535      */
    536     public long getProgramId() {
    537         return mProgramId;
    538     }
    539 
    540     /**
    541      * Return the optional program Title
    542      */
    543     public String getProgramTitle() {
    544         return mProgramTitle;
    545     }
    546 
    547     /**
    548      * Returns started time.
    549      */
    550     public long getStartTimeMs() {
    551         return mStartTimeMs;
    552     }
    553 
    554     /**
    555      * Returns ended time.
    556      */
    557     public long getEndTimeMs() {
    558         return mEndTimeMs;
    559     }
    560 
    561     /**
    562      * Returns the season number.
    563      */
    564     public String getSeasonNumber() {
    565         return mSeasonNumber;
    566     }
    567 
    568     /**
    569      * Returns the episode number.
    570      */
    571     public String getEpisodeNumber() {
    572         return mEpisodeNumber;
    573     }
    574 
    575     /**
    576      * Returns the episode title.
    577      */
    578     public String getEpisodeTitle() {
    579         return mEpisodeTitle;
    580     }
    581 
    582     /**
    583      * Returns the description of program.
    584      */
    585     public String getProgramDescription() {
    586         return mProgramDescription;
    587     }
    588 
    589     /**
    590      * Returns the long description of program.
    591      */
    592     public String getProgramLongDescription() {
    593         return mProgramLongDescription;
    594     }
    595 
    596     /**
    597      * Returns the poster uri of program.
    598      */
    599     public String getProgramPosterArtUri() {
    600         return mProgramPosterArtUri;
    601     }
    602 
    603     /**
    604      * Returns the thumb nail uri of program.
    605      */
    606     public String getProgramThumbnailUri() {
    607         return mProgramThumbnailUri;
    608     }
    609 
    610     /**
    611      * Returns duration.
    612      */
    613     public long getDuration() {
    614         return mEndTimeMs - mStartTimeMs;
    615     }
    616 
    617     /**
    618      * Returns the state. The possible states are {@link #STATE_RECORDING_NOT_STARTED},
    619      * {@link #STATE_RECORDING_IN_PROGRESS}, {@link #STATE_RECORDING_FINISHED},
    620      * {@link #STATE_RECORDING_FAILED}, {@link #STATE_RECORDING_CLIPPED} and
    621      * {@link #STATE_RECORDING_DELETED}.
    622      */
    623     @RecordingState public int getState() {
    624         return mState;
    625     }
    626 
    627     /**
    628      * Returns the ID of the {@link SeriesRecording} including this schedule.
    629      */
    630     public long getSeriesRecordingId() {
    631         return mSeriesRecordingId;
    632     }
    633 
    634     public long getId() {
    635         return mId;
    636     }
    637 
    638     /**
    639      * Sets the ID;
    640      */
    641     public void setId(long id) {
    642         mId = id;
    643     }
    644 
    645     public long getPriority() {
    646         return mPriority;
    647     }
    648 
    649     /**
    650      * Returns season number, episode number and episode title for display.
    651      */
    652     public String getEpisodeDisplayTitle(Context context) {
    653         if (!TextUtils.isEmpty(mEpisodeNumber)) {
    654             String episodeTitle = mEpisodeTitle == null ? "" : mEpisodeTitle;
    655             if (TextUtils.equals(mSeasonNumber, "0")) {
    656                 // Do not show "S0: ".
    657                 return String.format(context.getResources().getString(
    658                         R.string.display_episode_title_format_no_season_number),
    659                         mEpisodeNumber, episodeTitle);
    660             } else {
    661                 return String.format(context.getResources().getString(
    662                         R.string.display_episode_title_format),
    663                         mSeasonNumber, mEpisodeNumber, episodeTitle);
    664             }
    665         }
    666         return mEpisodeTitle;
    667     }
    668 
    669     /**
    670      * Returns the program's title withe its season and episode number.
    671      */
    672     public String getProgramTitleWithEpisodeNumber(Context context) {
    673         if (TextUtils.isEmpty(mProgramTitle)) {
    674             return mProgramTitle;
    675         }
    676         if (TextUtils.isEmpty(mSeasonNumber) || mSeasonNumber.equals("0")) {
    677             return TextUtils.isEmpty(mEpisodeNumber) ? mProgramTitle : context.getString(
    678                     R.string.program_title_with_episode_number_no_season, mProgramTitle,
    679                     mEpisodeNumber);
    680         } else {
    681             return context.getString(R.string.program_title_with_episode_number, mProgramTitle,
    682                     mSeasonNumber, mEpisodeNumber);
    683         }
    684     }
    685 
    686 
    687     /**
    688      * Converts a string to a @RecordingType int, defaulting to {@link #TYPE_TIMED}.
    689      */
    690     private static @RecordingType int recordingType(String type) {
    691         switch (type) {
    692             case Schedules.TYPE_TIMED:
    693                 return TYPE_TIMED;
    694             case Schedules.TYPE_PROGRAM:
    695                 return TYPE_PROGRAM;
    696             default:
    697                 SoftPreconditions.checkArgument(false, TAG, "Unknown recording type " + type);
    698                 return TYPE_TIMED;
    699         }
    700     }
    701 
    702     /**
    703      * Converts a @RecordingType int to a string, defaulting to {@link Schedules#TYPE_TIMED}.
    704      */
    705     private static String recordingType(@RecordingType int type) {
    706         switch (type) {
    707             case TYPE_TIMED:
    708                 return Schedules.TYPE_TIMED;
    709             case TYPE_PROGRAM:
    710                 return Schedules.TYPE_PROGRAM;
    711             default:
    712                 SoftPreconditions.checkArgument(false, TAG, "Unknown recording type " + type);
    713                 return Schedules.TYPE_TIMED;
    714         }
    715     }
    716 
    717     /**
    718      * Converts a string to a @RecordingState int, defaulting to
    719      * {@link #STATE_RECORDING_NOT_STARTED}.
    720      */
    721     private static @RecordingState int recordingState(String state) {
    722         switch (state) {
    723             case Schedules.STATE_RECORDING_NOT_STARTED:
    724                 return STATE_RECORDING_NOT_STARTED;
    725             case Schedules.STATE_RECORDING_IN_PROGRESS:
    726                 return STATE_RECORDING_IN_PROGRESS;
    727             case Schedules.STATE_RECORDING_FINISHED:
    728                 return STATE_RECORDING_FINISHED;
    729             case Schedules.STATE_RECORDING_FAILED:
    730                 return STATE_RECORDING_FAILED;
    731             case Schedules.STATE_RECORDING_CLIPPED:
    732                 return STATE_RECORDING_CLIPPED;
    733             case Schedules.STATE_RECORDING_DELETED:
    734                 return STATE_RECORDING_DELETED;
    735             case Schedules.STATE_RECORDING_CANCELED:
    736                 return STATE_RECORDING_CANCELED;
    737             default:
    738                 SoftPreconditions.checkArgument(false, TAG, "Unknown recording state" + state);
    739                 return STATE_RECORDING_NOT_STARTED;
    740         }
    741     }
    742 
    743     /**
    744      * Converts a @RecordingState int to string, defaulting to
    745      * {@link Schedules#STATE_RECORDING_NOT_STARTED}.
    746      */
    747     private static String recordingState(@RecordingState int state) {
    748         switch (state) {
    749             case STATE_RECORDING_NOT_STARTED:
    750                 return Schedules.STATE_RECORDING_NOT_STARTED;
    751             case STATE_RECORDING_IN_PROGRESS:
    752                 return Schedules.STATE_RECORDING_IN_PROGRESS;
    753             case STATE_RECORDING_FINISHED:
    754                 return Schedules.STATE_RECORDING_FINISHED;
    755             case STATE_RECORDING_FAILED:
    756                 return Schedules.STATE_RECORDING_FAILED;
    757             case STATE_RECORDING_CLIPPED:
    758                 return Schedules.STATE_RECORDING_CLIPPED;
    759             case STATE_RECORDING_DELETED:
    760                 return Schedules.STATE_RECORDING_DELETED;
    761             case STATE_RECORDING_CANCELED:
    762                 return Schedules.STATE_RECORDING_CANCELED;
    763             default:
    764                 SoftPreconditions.checkArgument(false, TAG, "Unknown recording state" + state);
    765                 return Schedules.STATE_RECORDING_NOT_STARTED;
    766         }
    767     }
    768 
    769     /**
    770      * Checks if the {@code period} overlaps with the recording time.
    771      */
    772     public boolean isOverLapping(Range<Long> period) {
    773         return mStartTimeMs < period.getUpper() && mEndTimeMs > period.getLower();
    774     }
    775 
    776     /**
    777      * Checks if the {@code schedule} overlaps with this schedule.
    778      */
    779     public boolean isOverLapping(ScheduledRecording schedule) {
    780         return mStartTimeMs < schedule.getEndTimeMs() && mEndTimeMs > schedule.getStartTimeMs();
    781     }
    782 
    783     @Override
    784     public String toString() {
    785         return "ScheduledRecording[" + mId
    786                 + "]"
    787                 + "(inputId=" + mInputId
    788                 + ",channelId=" + mChannelId
    789                 + ",programId=" + mProgramId
    790                 + ",programTitle=" + mProgramTitle
    791                 + ",type=" + mType
    792                 + ",startTime=" + Utils.toIsoDateTimeString(mStartTimeMs) + "(" + mStartTimeMs + ")"
    793                 + ",endTime=" + Utils.toIsoDateTimeString(mEndTimeMs) + "(" + mEndTimeMs + ")"
    794                 + ",seasonNumber=" + mSeasonNumber
    795                 + ",episodeNumber=" + mEpisodeNumber
    796                 + ",episodeTitle=" + mEpisodeTitle
    797                 + ",programDescription=" + mProgramDescription
    798                 + ",programLongDescription=" + mProgramLongDescription
    799                 + ",programPosterArtUri=" + mProgramPosterArtUri
    800                 + ",programThumbnailUri=" + mProgramThumbnailUri
    801                 + ",state=" + mState
    802                 + ",priority=" + mPriority
    803                 + ",seriesRecordingId=" + mSeriesRecordingId
    804                 + ")";
    805     }
    806 
    807     @Override
    808     public int describeContents() {
    809         return 0;
    810     }
    811 
    812     @Override
    813     public void writeToParcel(Parcel out, int paramInt) {
    814         out.writeLong(mId);
    815         out.writeLong(mPriority);
    816         out.writeString(mInputId);
    817         out.writeLong(mChannelId);
    818         out.writeLong(mProgramId);
    819         out.writeString(mProgramTitle);
    820         out.writeInt(mType);
    821         out.writeLong(mStartTimeMs);
    822         out.writeLong(mEndTimeMs);
    823         out.writeString(mSeasonNumber);
    824         out.writeString(mEpisodeNumber);
    825         out.writeString(mEpisodeTitle);
    826         out.writeString(mProgramDescription);
    827         out.writeString(mProgramLongDescription);
    828         out.writeString(mProgramPosterArtUri);
    829         out.writeString(mProgramThumbnailUri);
    830         out.writeInt(mState);
    831         out.writeLong(mSeriesRecordingId);
    832     }
    833 
    834     /**
    835      * Returns {@code true} if the recording is not started yet, otherwise @{code false}.
    836      */
    837     public boolean isNotStarted() {
    838         return mState == STATE_RECORDING_NOT_STARTED;
    839     }
    840 
    841     /**
    842      * Returns {@code true} if the recording is in progress, otherwise @{code false}.
    843      */
    844     public boolean isInProgress() {
    845         return mState == STATE_RECORDING_IN_PROGRESS;
    846     }
    847 
    848     @Override
    849     public boolean equals(Object obj) {
    850         if (!(obj instanceof ScheduledRecording)) {
    851             return false;
    852         }
    853         ScheduledRecording r = (ScheduledRecording) obj;
    854         return mId == r.mId
    855                 && mPriority == r.mPriority
    856                 && mChannelId == r.mChannelId
    857                 && mProgramId == r.mProgramId
    858                 && Objects.equals(mProgramTitle, r.mProgramTitle)
    859                 && mType == r.mType
    860                 && mStartTimeMs == r.mStartTimeMs
    861                 && mEndTimeMs == r.mEndTimeMs
    862                 && Objects.equals(mSeasonNumber, r.mSeasonNumber)
    863                 && Objects.equals(mEpisodeNumber, r.mEpisodeNumber)
    864                 && Objects.equals(mEpisodeTitle, r.mEpisodeTitle)
    865                 && Objects.equals(mProgramDescription, r.getProgramDescription())
    866                 && Objects.equals(mProgramLongDescription, r.getProgramLongDescription())
    867                 && Objects.equals(mProgramPosterArtUri, r.getProgramPosterArtUri())
    868                 && Objects.equals(mProgramThumbnailUri, r.getProgramThumbnailUri())
    869                 && mState == r.mState
    870                 && mSeriesRecordingId == r.mSeriesRecordingId;
    871     }
    872 
    873     @Override
    874     public int hashCode() {
    875         return Objects.hash(mId, mPriority, mChannelId, mProgramId, mProgramTitle, mType,
    876                 mStartTimeMs, mEndTimeMs, mSeasonNumber, mEpisodeNumber, mEpisodeTitle,
    877                 mProgramDescription, mProgramLongDescription, mProgramPosterArtUri,
    878                 mProgramThumbnailUri, mState, mSeriesRecordingId);
    879     }
    880 
    881     /**
    882      * Returns an array containing all of the elements in the list.
    883      */
    884     public static ScheduledRecording[] toArray(Collection<ScheduledRecording> schedules) {
    885         return schedules.toArray(new ScheduledRecording[schedules.size()]);
    886     }
    887 }
    888