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      */
builder(String inputId, Program p)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 
builder(String inputId, long channelId, long startTime, long endTime)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
builder(RecordedProgram p)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 
Builder()184         private Builder() { }
185 
setId(long id)186         public Builder setId(long id) {
187             mId = id;
188             return this;
189         }
190 
setPriority(long priority)191         public Builder setPriority(long priority) {
192             mPriority = priority;
193             return this;
194         }
195 
setInputId(String inputId)196         public Builder setInputId(String inputId) {
197             mInputId = inputId;
198             return this;
199         }
200 
setChannelId(long channelId)201         public Builder setChannelId(long channelId) {
202             mChannelId = channelId;
203             return this;
204         }
205 
setProgramId(long programId)206         public Builder setProgramId(long programId) {
207             mProgramId = programId;
208             return this;
209         }
210 
setProgramTitle(String programTitle)211         public Builder setProgramTitle(String programTitle) {
212             mProgramTitle = programTitle;
213             return this;
214         }
215 
setType(@ecordingType int type)216         private Builder setType(@RecordingType int type) {
217             mType = type;
218             return this;
219         }
220 
setStartTimeMs(long startTimeMs)221         public Builder setStartTimeMs(long startTimeMs) {
222             mStartTimeMs = startTimeMs;
223             return this;
224         }
225 
setEndTimeMs(long endTimeMs)226         public Builder setEndTimeMs(long endTimeMs) {
227             mEndTimeMs = endTimeMs;
228             return this;
229         }
230 
setSeasonNumber(String seasonNumber)231         public Builder setSeasonNumber(String seasonNumber) {
232             mSeasonNumber = seasonNumber;
233             return this;
234         }
235 
setEpisodeNumber(String episodeNumber)236         public Builder setEpisodeNumber(String episodeNumber) {
237             mEpisodeNumber = episodeNumber;
238             return this;
239         }
240 
setEpisodeTitle(String episodeTitle)241         public Builder setEpisodeTitle(String episodeTitle) {
242             mEpisodeTitle = episodeTitle;
243             return this;
244         }
245 
setProgramDescription(String description)246         public Builder setProgramDescription(String description) {
247             mProgramDescription = description;
248             return this;
249         }
250 
setProgramLongDescription(String longDescription)251         public Builder setProgramLongDescription(String longDescription) {
252             mProgramLongDescription = longDescription;
253             return this;
254         }
255 
setProgramPosterArtUri(String programPosterArtUri)256         public Builder setProgramPosterArtUri(String programPosterArtUri) {
257             mProgramPosterArtUri = programPosterArtUri;
258             return this;
259         }
260 
setProgramThumbnailUri(String programThumbnailUri)261         public Builder setProgramThumbnailUri(String programThumbnailUri) {
262             mProgramThumbnailUri = programThumbnailUri;
263             return this;
264         }
265 
setState(@ecordingState int state)266         public Builder setState(@RecordingState int state) {
267             mState = state;
268             return this;
269         }
270 
setSeriesRecordingId(long seriesRecordingId)271         public Builder setSeriesRecordingId(long seriesRecordingId) {
272             mSeriesRecordingId = seriesRecordingId;
273             return this;
274         }
275 
build()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      */
buildFrom(ScheduledRecording orig)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      */
fromCursor(Cursor c)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 
toContentValues(ScheduledRecording r)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 
fromParcel(Parcel in)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 
ScheduledRecording(long id, long priority, String inputId, long channelId, long programId, String programTitle, @RecordingType int type, long startTime, long endTime, String seasonNumber, String episodeNumber, String episodeTitle, String programDescription, String programLongDescription, String programPosterArtUri, String programThumbnailUri, @RecordingState int state, long seriesRecordingId)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
getType()515     public int getType() {
516         return mType;
517     }
518 
519     /**
520      * Returns schedules' input id.
521      */
getInputId()522     public String getInputId() {
523         return mInputId;
524     }
525 
526     /**
527      * Returns recorded {@link Channel}.
528      */
getChannelId()529     public long getChannelId() {
530         return mChannelId;
531     }
532 
533     /**
534      * Return the optional program id
535      */
getProgramId()536     public long getProgramId() {
537         return mProgramId;
538     }
539 
540     /**
541      * Return the optional program Title
542      */
getProgramTitle()543     public String getProgramTitle() {
544         return mProgramTitle;
545     }
546 
547     /**
548      * Returns started time.
549      */
getStartTimeMs()550     public long getStartTimeMs() {
551         return mStartTimeMs;
552     }
553 
554     /**
555      * Returns ended time.
556      */
getEndTimeMs()557     public long getEndTimeMs() {
558         return mEndTimeMs;
559     }
560 
561     /**
562      * Returns the season number.
563      */
getSeasonNumber()564     public String getSeasonNumber() {
565         return mSeasonNumber;
566     }
567 
568     /**
569      * Returns the episode number.
570      */
getEpisodeNumber()571     public String getEpisodeNumber() {
572         return mEpisodeNumber;
573     }
574 
575     /**
576      * Returns the episode title.
577      */
getEpisodeTitle()578     public String getEpisodeTitle() {
579         return mEpisodeTitle;
580     }
581 
582     /**
583      * Returns the description of program.
584      */
getProgramDescription()585     public String getProgramDescription() {
586         return mProgramDescription;
587     }
588 
589     /**
590      * Returns the long description of program.
591      */
getProgramLongDescription()592     public String getProgramLongDescription() {
593         return mProgramLongDescription;
594     }
595 
596     /**
597      * Returns the poster uri of program.
598      */
getProgramPosterArtUri()599     public String getProgramPosterArtUri() {
600         return mProgramPosterArtUri;
601     }
602 
603     /**
604      * Returns the thumb nail uri of program.
605      */
getProgramThumbnailUri()606     public String getProgramThumbnailUri() {
607         return mProgramThumbnailUri;
608     }
609 
610     /**
611      * Returns duration.
612      */
getDuration()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      */
getState()623     @RecordingState public int getState() {
624         return mState;
625     }
626 
627     /**
628      * Returns the ID of the {@link SeriesRecording} including this schedule.
629      */
getSeriesRecordingId()630     public long getSeriesRecordingId() {
631         return mSeriesRecordingId;
632     }
633 
getId()634     public long getId() {
635         return mId;
636     }
637 
638     /**
639      * Sets the ID;
640      */
setId(long id)641     public void setId(long id) {
642         mId = id;
643     }
644 
getPriority()645     public long getPriority() {
646         return mPriority;
647     }
648 
649     /**
650      * Returns season number, episode number and episode title for display.
651      */
getEpisodeDisplayTitle(Context context)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      */
getProgramTitleWithEpisodeNumber(Context context)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      */
recordingType(String type)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      */
recordingType(@ecordingType int type)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      */
recordingState(String state)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      */
recordingState(@ecordingState int state)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      */
isOverLapping(Range<Long> period)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      */
isOverLapping(ScheduledRecording schedule)779     public boolean isOverLapping(ScheduledRecording schedule) {
780         return mStartTimeMs < schedule.getEndTimeMs() && mEndTimeMs > schedule.getStartTimeMs();
781     }
782 
783     @Override
toString()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
describeContents()808     public int describeContents() {
809         return 0;
810     }
811 
812     @Override
writeToParcel(Parcel out, int paramInt)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      */
isNotStarted()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      */
isInProgress()844     public boolean isInProgress() {
845         return mState == STATE_RECORDING_IN_PROGRESS;
846     }
847 
848     @Override
equals(Object obj)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
hashCode()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      */
toArray(Collection<ScheduledRecording> schedules)884     public static ScheduledRecording[] toArray(Collection<ScheduledRecording> schedules) {
885         return schedules.toArray(new ScheduledRecording[schedules.size()]);
886     }
887 }
888