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