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