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