Home | History | Annotate | Download | only in dvr
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.tv.dvr;
     18 
     19 import android.content.ContentValues;
     20 import android.database.Cursor;
     21 import android.support.annotation.IntDef;
     22 import android.support.annotation.VisibleForTesting;
     23 import android.util.Range;
     24 
     25 import com.android.tv.common.SoftPreconditions;
     26 import com.android.tv.data.Channel;
     27 import com.android.tv.data.Program;
     28 import com.android.tv.dvr.provider.DvrContract;
     29 import com.android.tv.util.Utils;
     30 
     31 import java.lang.annotation.Retention;
     32 import java.lang.annotation.RetentionPolicy;
     33 import java.util.Comparator;
     34 
     35 /**
     36  * A data class for one recording contents.
     37  */
     38 @VisibleForTesting
     39 public final class ScheduledRecording {
     40     private static final String TAG = "Recording";
     41 
     42     public static final String RECORDING_ID_EXTRA = "extra.dvr.recording.id";  //TODO(DVR) move
     43     public static final String PARAM_INPUT_ID = "input_id";
     44 
     45     public static final long ID_NOT_SET = -1;
     46 
     47     public static final Comparator<ScheduledRecording> START_TIME_COMPARATOR = new Comparator<ScheduledRecording>() {
     48         @Override
     49         public int compare(ScheduledRecording lhs, ScheduledRecording rhs) {
     50             return Long.compare(lhs.mStartTimeMs, rhs.mStartTimeMs);
     51         }
     52     };
     53 
     54     public static final Comparator<ScheduledRecording> PRIORITY_COMPARATOR = new Comparator<ScheduledRecording>() {
     55         @Override
     56         public int compare(ScheduledRecording lhs, ScheduledRecording rhs) {
     57             int value = Long.compare(lhs.mPriority, rhs.mPriority);
     58             if (value == 0) {
     59                 value = Long.compare(lhs.mId, rhs.mId);
     60             }
     61             return value;
     62         }
     63     };
     64 
     65     public static final Comparator<ScheduledRecording> START_TIME_THEN_PRIORITY_COMPARATOR
     66             = new Comparator<ScheduledRecording>() {
     67         @Override
     68         public int compare(ScheduledRecording lhs, ScheduledRecording rhs) {
     69             int value = START_TIME_COMPARATOR.compare(lhs, rhs);
     70             if (value == 0) {
     71                 value = PRIORITY_COMPARATOR.compare(lhs, rhs);
     72             }
     73             return value;
     74         }
     75     };
     76 
     77     public static Builder builder(Program p) {
     78         return new Builder()
     79                 .setStartTime(p.getStartTimeUtcMillis()).setEndTime(p.getEndTimeUtcMillis())
     80                 .setProgramId(p.getId())
     81                 .setType(TYPE_PROGRAM);
     82     }
     83 
     84     public static Builder builder(long startTime, long endTime) {
     85         return new Builder()
     86                 .setStartTime(startTime)
     87                 .setEndTime(endTime)
     88                 .setType(TYPE_TIMED);
     89     }
     90 
     91     public static final class Builder {
     92         private long mId = ID_NOT_SET;
     93         private long mPriority = Long.MAX_VALUE;
     94         private long mChannelId;
     95         private long mProgramId = ID_NOT_SET;
     96         private @RecordingType int mType;
     97         private long mStartTime;
     98         private long mEndTime;
     99         private @RecordingState int mState;
    100         private SeasonRecording mParentSeasonRecording;
    101 
    102         private Builder() { }
    103 
    104         public Builder setId(long id) {
    105             mId = id;
    106             return this;
    107         }
    108 
    109         public Builder setPriority(long priority) {
    110             mPriority = priority;
    111             return this;
    112         }
    113 
    114         public Builder setChannelId(long channelId) {
    115             mChannelId = channelId;
    116             return this;
    117         }
    118 
    119         public Builder setProgramId(long programId) {
    120             mProgramId = programId;
    121             return this;
    122         }
    123 
    124         private Builder setType(@RecordingType int type) {
    125             mType = type;
    126             return this;
    127         }
    128 
    129         public Builder setStartTime(long startTime) {
    130             mStartTime = startTime;
    131             return this;
    132         }
    133 
    134         public Builder setEndTime(long endTime) {
    135             mEndTime = endTime;
    136             return this;
    137         }
    138 
    139         public Builder setState(@RecordingState int state) {
    140             mState = state;
    141             return this;
    142         }
    143 
    144         public Builder setParentSeasonRecording(SeasonRecording parentSeasonRecording) {
    145             mParentSeasonRecording = parentSeasonRecording;
    146             return this;
    147         }
    148 
    149         public ScheduledRecording build() {
    150             return new ScheduledRecording(mId, mPriority, mChannelId, mProgramId, mType, mStartTime,
    151                     mEndTime, mState, mParentSeasonRecording);
    152         }
    153     }
    154 
    155     /**
    156      * Creates {@link Builder} object from the given original {@code Recording}.
    157      */
    158     public static Builder buildFrom(ScheduledRecording orig) {
    159         return new Builder()
    160                 .setId(orig.mId).setChannelId(orig.mChannelId)
    161                 .setEndTime(orig.mEndTimeMs).setParentSeasonRecording(orig.mParentSeasonRecording)
    162                 .setProgramId(orig.mProgramId)
    163                 .setStartTime(orig.mStartTimeMs).setState(orig.mState).setType(orig.mType);
    164     }
    165 
    166     @Retention(RetentionPolicy.SOURCE)
    167     @IntDef({STATE_RECORDING_NOT_STARTED, STATE_RECORDING_IN_PROGRESS,
    168         STATE_RECORDING_UNEXPECTEDLY_STOPPED, STATE_RECORDING_FINISHED, STATE_RECORDING_FAILED})
    169     public @interface RecordingState {}
    170     public static final int STATE_RECORDING_NOT_STARTED = 0;
    171     public static final int STATE_RECORDING_IN_PROGRESS = 1;
    172     @Deprecated // It is not used.
    173     public static final int STATE_RECORDING_UNEXPECTEDLY_STOPPED = 2;
    174     public static final int STATE_RECORDING_FINISHED = 3;
    175     public static final int STATE_RECORDING_FAILED = 4;
    176 
    177     @Retention(RetentionPolicy.SOURCE)
    178     @IntDef({TYPE_TIMED, TYPE_PROGRAM})
    179     public @interface RecordingType {}
    180     /**
    181      * Record with given time range.
    182      */
    183     static final int TYPE_TIMED = 1;
    184     /**
    185      * Record with a given program.
    186      */
    187     static final int TYPE_PROGRAM = 2;
    188 
    189     @RecordingType private final int mType;
    190 
    191     /**
    192      * Use this projection if you want to create {@link ScheduledRecording} object using {@link #fromCursor}.
    193      */
    194     public static final String[] PROJECTION = {
    195             // Columns must match what is read in Recording.fromCursor()
    196             DvrContract.Recordings._ID,
    197             DvrContract.Recordings.COLUMN_PRIORITY,
    198             DvrContract.Recordings.COLUMN_TYPE,
    199             DvrContract.Recordings.COLUMN_CHANNEL_ID,
    200             DvrContract.Recordings.COLUMN_PROGRAM_ID,
    201             DvrContract.Recordings.COLUMN_START_TIME_UTC_MILLIS,
    202             DvrContract.Recordings.COLUMN_END_TIME_UTC_MILLIS,
    203             DvrContract.Recordings.COLUMN_STATE};
    204     /**
    205      * Creates {@link ScheduledRecording} object from the given {@link Cursor}.
    206      */
    207     public static ScheduledRecording fromCursor(Cursor c) {
    208         int index = -1;
    209         return new Builder()
    210                 .setId(c.getLong(++index))
    211                 .setPriority(c.getLong(++index))
    212                 .setType(recordingType(c.getString(++index)))
    213                 .setChannelId(c.getLong(++index))
    214                 .setProgramId(c.getLong(++index))
    215                 .setStartTime(c.getLong(++index))
    216                 .setEndTime(c.getLong(++index))
    217                 .setState(recordingState(c.getString(++index)))
    218                 .build();
    219     }
    220 
    221     public static ContentValues toContentValues(ScheduledRecording r) {
    222         ContentValues values = new ContentValues();
    223         values.put(DvrContract.Recordings.COLUMN_CHANNEL_ID, r.getChannelId());
    224         values.put(DvrContract.Recordings.COLUMN_PROGRAM_ID, r.getProgramId());
    225         values.put(DvrContract.Recordings.COLUMN_PRIORITY, r.getPriority());
    226         values.put(DvrContract.Recordings.COLUMN_START_TIME_UTC_MILLIS, r.getStartTimeMs());
    227         values.put(DvrContract.Recordings.COLUMN_END_TIME_UTC_MILLIS, r.getEndTimeMs());
    228         values.put(DvrContract.Recordings.COLUMN_STATE, r.getState());
    229         values.put(DvrContract.Recordings.COLUMN_TYPE, r.getType());
    230         return values;
    231     }
    232 
    233     /**
    234      * The ID internal to Live TV
    235      */
    236     private final long mId;
    237 
    238     /**
    239      * The priority of this recording.
    240      *
    241      * <p> The lowest number is recorded first. If there is a tie in priority then the lower id
    242      * wins.
    243      */
    244     private final long mPriority;
    245 
    246 
    247     private final long mChannelId;
    248     /**
    249      * Optional id of the associated program.
    250      *
    251      */
    252     private final long mProgramId;
    253 
    254     private final long mStartTimeMs;
    255     private final long mEndTimeMs;
    256     @RecordingState private final int mState;
    257 
    258     private final SeasonRecording mParentSeasonRecording;
    259 
    260     private ScheduledRecording(long id, long priority, long channelId, long programId,
    261             @RecordingType int type, long startTime, long endTime,
    262             @RecordingState int state, SeasonRecording parentSeasonRecording) {
    263         mId = id;
    264         mPriority = priority;
    265         mChannelId = channelId;
    266         mProgramId = programId;
    267         mType = type;
    268         mStartTimeMs = startTime;
    269         mEndTimeMs = endTime;
    270         mState = state;
    271         mParentSeasonRecording = parentSeasonRecording;
    272     }
    273 
    274     /**
    275      * Returns recording schedule type. The possible types are {@link #TYPE_PROGRAM} and
    276      * {@link #TYPE_TIMED}.
    277      */
    278     @RecordingType
    279     public int getType() {
    280         return mType;
    281     }
    282 
    283     /**
    284      * Returns recorded {@link Channel}.
    285      */
    286     public long getChannelId() {
    287         return mChannelId;
    288     }
    289 
    290     /**
    291      * Return the optional program id
    292      */
    293     public long getProgramId() {
    294         return mProgramId;
    295     }
    296 
    297     /**
    298      * Returns started time.
    299      */
    300     public long getStartTimeMs() {
    301         return mStartTimeMs;
    302     }
    303 
    304     /**
    305      * Returns ended time.
    306      */
    307     public long getEndTimeMs() {
    308         return mEndTimeMs;
    309     }
    310 
    311     /**
    312      * Returns duration.
    313      */
    314     public long getDuration() {
    315         return mEndTimeMs - mStartTimeMs;
    316     }
    317 
    318     /**
    319      * Returns the state. The possible states are {@link #STATE_RECORDING_FINISHED},
    320      * {@link #STATE_RECORDING_IN_PROGRESS} and {@link #STATE_RECORDING_UNEXPECTEDLY_STOPPED}.
    321      */
    322     @RecordingState public int getState() {
    323         return mState;
    324     }
    325 
    326     /**
    327      * Returns {@link SeasonRecording} including this schedule.
    328      */
    329     public SeasonRecording getParentSeasonRecording() {
    330         return mParentSeasonRecording;
    331     }
    332 
    333     public long getId() {
    334         return mId;
    335     }
    336 
    337     public long getPriority() {
    338         return mPriority;
    339     }
    340 
    341     /**
    342      * Converts a string to a @RecordingType int, defaulting to {@link #TYPE_TIMED}.
    343      */
    344     private static @RecordingType int recordingType(String type) {
    345         int t;
    346         try {
    347             t = Integer.valueOf(type);
    348         } catch (NullPointerException | NumberFormatException e) {
    349             SoftPreconditions.checkArgument(false, TAG, "Unknown recording type " + type);
    350             return TYPE_TIMED;
    351         }
    352         switch (t) {
    353             case TYPE_TIMED:
    354                 return TYPE_TIMED;
    355             case TYPE_PROGRAM:
    356                 return TYPE_PROGRAM;
    357             default:
    358                 SoftPreconditions.checkArgument(false, TAG, "Unknown recording type " + type);
    359                 return TYPE_TIMED;
    360         }
    361     }
    362 
    363     /**
    364      * Converts a string to a @RecordingState int, defaulting to
    365      * {@link #STATE_RECORDING_NOT_STARTED}.
    366      */
    367     private static @RecordingState int recordingState(String state) {
    368         int s;
    369         try {
    370             s = Integer.valueOf(state);
    371         } catch (NullPointerException | NumberFormatException e) {
    372             SoftPreconditions.checkArgument(false, TAG, "Unknown recording state" + state);
    373             return STATE_RECORDING_NOT_STARTED;
    374         }
    375         switch (s) {
    376             case STATE_RECORDING_NOT_STARTED:
    377                 return STATE_RECORDING_NOT_STARTED;
    378             case STATE_RECORDING_IN_PROGRESS:
    379                 return STATE_RECORDING_IN_PROGRESS;
    380             case STATE_RECORDING_FINISHED:
    381                 return STATE_RECORDING_FINISHED;
    382             case STATE_RECORDING_UNEXPECTEDLY_STOPPED:
    383                 return STATE_RECORDING_UNEXPECTEDLY_STOPPED;
    384             case STATE_RECORDING_FAILED:
    385                 return STATE_RECORDING_FAILED;
    386             default:
    387                 SoftPreconditions.checkArgument(false, TAG, "Unknown recording state" + state);
    388                 return STATE_RECORDING_NOT_STARTED;
    389         }
    390     }
    391 
    392     /**
    393      * Checks if the {@code period} overlaps with the recording time.
    394      */
    395     public boolean isOverLapping(Range<Long> period) {
    396         return mStartTimeMs <= period.getUpper() && mEndTimeMs >= period.getLower();
    397     }
    398 
    399     @Override
    400     public String toString() {
    401         return "ScheduledRecording[" + mId
    402                 + "]"
    403                 + "(startTime=" + Utils.toIsoDateTimeString(mStartTimeMs)
    404                 + ",endTime=" + Utils.toIsoDateTimeString(mEndTimeMs)
    405                 + ",state=" + mState
    406                 + ",priority=" + mPriority
    407                 + ")";
    408     }
    409 }
    410