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