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 builder(Program p)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 builder(long startTime, long endTime)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 Builder()102 private Builder() { } 103 setId(long id)104 public Builder setId(long id) { 105 mId = id; 106 return this; 107 } 108 setPriority(long priority)109 public Builder setPriority(long priority) { 110 mPriority = priority; 111 return this; 112 } 113 setChannelId(long channelId)114 public Builder setChannelId(long channelId) { 115 mChannelId = channelId; 116 return this; 117 } 118 setProgramId(long programId)119 public Builder setProgramId(long programId) { 120 mProgramId = programId; 121 return this; 122 } 123 setType(@ecordingType int type)124 private Builder setType(@RecordingType int type) { 125 mType = type; 126 return this; 127 } 128 setStartTime(long startTime)129 public Builder setStartTime(long startTime) { 130 mStartTime = startTime; 131 return this; 132 } 133 setEndTime(long endTime)134 public Builder setEndTime(long endTime) { 135 mEndTime = endTime; 136 return this; 137 } 138 setState(@ecordingState int state)139 public Builder setState(@RecordingState int state) { 140 mState = state; 141 return this; 142 } 143 setParentSeasonRecording(SeasonRecording parentSeasonRecording)144 public Builder setParentSeasonRecording(SeasonRecording parentSeasonRecording) { 145 mParentSeasonRecording = parentSeasonRecording; 146 return this; 147 } 148 build()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 */ buildFrom(ScheduledRecording orig)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 */ fromCursor(Cursor c)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 toContentValues(ScheduledRecording r)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 ScheduledRecording(long id, long priority, long channelId, long programId, @RecordingType int type, long startTime, long endTime, @RecordingState int state, SeasonRecording parentSeasonRecording)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 getType()279 public int getType() { 280 return mType; 281 } 282 283 /** 284 * Returns recorded {@link Channel}. 285 */ getChannelId()286 public long getChannelId() { 287 return mChannelId; 288 } 289 290 /** 291 * Return the optional program id 292 */ getProgramId()293 public long getProgramId() { 294 return mProgramId; 295 } 296 297 /** 298 * Returns started time. 299 */ getStartTimeMs()300 public long getStartTimeMs() { 301 return mStartTimeMs; 302 } 303 304 /** 305 * Returns ended time. 306 */ getEndTimeMs()307 public long getEndTimeMs() { 308 return mEndTimeMs; 309 } 310 311 /** 312 * Returns duration. 313 */ getDuration()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 */ getState()322 @RecordingState public int getState() { 323 return mState; 324 } 325 326 /** 327 * Returns {@link SeasonRecording} including this schedule. 328 */ getParentSeasonRecording()329 public SeasonRecording getParentSeasonRecording() { 330 return mParentSeasonRecording; 331 } 332 getId()333 public long getId() { 334 return mId; 335 } 336 getPriority()337 public long getPriority() { 338 return mPriority; 339 } 340 341 /** 342 * Converts a string to a @RecordingType int, defaulting to {@link #TYPE_TIMED}. 343 */ recordingType(String type)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 */ recordingState(String state)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 */ isOverLapping(Range<Long> period)395 public boolean isOverLapping(Range<Long> period) { 396 return mStartTimeMs <= period.getUpper() && mEndTimeMs >= period.getLower(); 397 } 398 399 @Override toString()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