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