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