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