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