1 /* 2 * Copyright (C) 2016 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.ContentUris; 21 import android.content.ContentValues; 22 import android.content.Context; 23 import android.database.Cursor; 24 import android.media.tv.TvContentRating; 25 import android.media.tv.TvContract; 26 import android.media.tv.TvContract.RecordedPrograms; 27 import android.net.Uri; 28 import android.os.Build; 29 import android.support.annotation.Nullable; 30 import android.text.TextUtils; 31 32 import com.android.tv.common.R; 33 import com.android.tv.common.TvContentRatingCache; 34 import com.android.tv.data.BaseProgram; 35 import com.android.tv.data.GenreItems; 36 import com.android.tv.data.InternalDataUtils; 37 import com.android.tv.util.Utils; 38 39 import java.util.Arrays; 40 import java.util.Collection; 41 import java.util.Comparator; 42 import java.util.Objects; 43 import java.util.concurrent.TimeUnit; 44 45 /** 46 * Immutable instance of {@link android.media.tv.TvContract.RecordedPrograms}. 47 */ 48 @TargetApi(Build.VERSION_CODES.N) 49 public class RecordedProgram extends BaseProgram { 50 public static final int ID_NOT_SET = -1; 51 52 public final static String[] PROJECTION = { 53 // These are in exactly the order listed in RecordedPrograms 54 RecordedPrograms._ID, 55 RecordedPrograms.COLUMN_PACKAGE_NAME, 56 RecordedPrograms.COLUMN_INPUT_ID, 57 RecordedPrograms.COLUMN_CHANNEL_ID, 58 RecordedPrograms.COLUMN_TITLE, 59 RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER, 60 RecordedPrograms.COLUMN_SEASON_TITLE, 61 RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER, 62 RecordedPrograms.COLUMN_EPISODE_TITLE, 63 RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS, 64 RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS, 65 RecordedPrograms.COLUMN_BROADCAST_GENRE, 66 RecordedPrograms.COLUMN_CANONICAL_GENRE, 67 RecordedPrograms.COLUMN_SHORT_DESCRIPTION, 68 RecordedPrograms.COLUMN_LONG_DESCRIPTION, 69 RecordedPrograms.COLUMN_VIDEO_WIDTH, 70 RecordedPrograms.COLUMN_VIDEO_HEIGHT, 71 RecordedPrograms.COLUMN_AUDIO_LANGUAGE, 72 RecordedPrograms.COLUMN_CONTENT_RATING, 73 RecordedPrograms.COLUMN_POSTER_ART_URI, 74 RecordedPrograms.COLUMN_THUMBNAIL_URI, 75 RecordedPrograms.COLUMN_SEARCHABLE, 76 RecordedPrograms.COLUMN_RECORDING_DATA_URI, 77 RecordedPrograms.COLUMN_RECORDING_DATA_BYTES, 78 RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS, 79 RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS, 80 RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1, 81 RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2, 82 RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3, 83 RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4, 84 RecordedPrograms.COLUMN_VERSION_NUMBER, 85 RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA, 86 }; 87 88 public static RecordedProgram fromCursor(Cursor cursor) { 89 int index = 0; 90 Builder builder = builder() 91 .setId(cursor.getLong(index++)) 92 .setPackageName(cursor.getString(index++)) 93 .setInputId(cursor.getString(index++)) 94 .setChannelId(cursor.getLong(index++)) 95 .setTitle(cursor.getString(index++)) 96 .setSeasonNumber(cursor.getString(index++)) 97 .setSeasonTitle(cursor.getString(index++)) 98 .setEpisodeNumber(cursor.getString(index++)) 99 .setEpisodeTitle(cursor.getString(index++)) 100 .setStartTimeUtcMillis(cursor.getLong(index++)) 101 .setEndTimeUtcMillis(cursor.getLong(index++)) 102 .setBroadcastGenres(cursor.getString(index++)) 103 .setCanonicalGenres(cursor.getString(index++)) 104 .setShortDescription(cursor.getString(index++)) 105 .setLongDescription(cursor.getString(index++)) 106 .setVideoWidth(cursor.getInt(index++)) 107 .setVideoHeight(cursor.getInt(index++)) 108 .setAudioLanguage(cursor.getString(index++)) 109 .setContentRatings( 110 TvContentRatingCache.getInstance().getRatings(cursor.getString(index++))) 111 .setPosterArtUri(cursor.getString(index++)) 112 .setThumbnailUri(cursor.getString(index++)) 113 .setSearchable(cursor.getInt(index++) == 1) 114 .setDataUri(cursor.getString(index++)) 115 .setDataBytes(cursor.getLong(index++)) 116 .setDurationMillis(cursor.getLong(index++)) 117 .setExpireTimeUtcMillis(cursor.getLong(index++)) 118 .setInternalProviderFlag1(cursor.getInt(index++)) 119 .setInternalProviderFlag2(cursor.getInt(index++)) 120 .setInternalProviderFlag3(cursor.getInt(index++)) 121 .setInternalProviderFlag4(cursor.getInt(index++)) 122 .setVersionNumber(cursor.getInt(index++)); 123 if (Utils.isInBundledPackageSet(builder.mPackageName)) { 124 InternalDataUtils.deserializeInternalProviderData(cursor.getBlob(index), builder); 125 } 126 return builder.build(); 127 } 128 129 public static ContentValues toValues(RecordedProgram recordedProgram) { 130 ContentValues values = new ContentValues(); 131 if (recordedProgram.mId != ID_NOT_SET) { 132 values.put(RecordedPrograms._ID, recordedProgram.mId); 133 } 134 values.put(RecordedPrograms.COLUMN_INPUT_ID, recordedProgram.mInputId); 135 values.put(RecordedPrograms.COLUMN_CHANNEL_ID, recordedProgram.mChannelId); 136 values.put(RecordedPrograms.COLUMN_TITLE, recordedProgram.mTitle); 137 values.put(RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER, recordedProgram.mSeasonNumber); 138 values.put(RecordedPrograms.COLUMN_SEASON_TITLE, recordedProgram.mSeasonTitle); 139 values.put(RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER, recordedProgram.mEpisodeNumber); 140 values.put(RecordedPrograms.COLUMN_EPISODE_TITLE, recordedProgram.mTitle); 141 values.put(RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS, 142 recordedProgram.mStartTimeUtcMillis); 143 values.put(RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS, recordedProgram.mEndTimeUtcMillis); 144 values.put(RecordedPrograms.COLUMN_BROADCAST_GENRE, 145 safeEncode(recordedProgram.mBroadcastGenres)); 146 values.put(RecordedPrograms.COLUMN_CANONICAL_GENRE, 147 safeEncode(recordedProgram.mCanonicalGenres)); 148 values.put(RecordedPrograms.COLUMN_SHORT_DESCRIPTION, recordedProgram.mShortDescription); 149 values.put(RecordedPrograms.COLUMN_LONG_DESCRIPTION, recordedProgram.mLongDescription); 150 if (recordedProgram.mVideoWidth == 0) { 151 values.putNull(RecordedPrograms.COLUMN_VIDEO_WIDTH); 152 } else { 153 values.put(RecordedPrograms.COLUMN_VIDEO_WIDTH, recordedProgram.mVideoWidth); 154 } 155 if (recordedProgram.mVideoHeight == 0) { 156 values.putNull(RecordedPrograms.COLUMN_VIDEO_HEIGHT); 157 } else { 158 values.put(RecordedPrograms.COLUMN_VIDEO_HEIGHT, recordedProgram.mVideoHeight); 159 } 160 values.put(RecordedPrograms.COLUMN_AUDIO_LANGUAGE, recordedProgram.mAudioLanguage); 161 values.put(RecordedPrograms.COLUMN_CONTENT_RATING, 162 TvContentRatingCache.contentRatingsToString(recordedProgram.mContentRatings)); 163 values.put(RecordedPrograms.COLUMN_POSTER_ART_URI, recordedProgram.mPosterArtUri); 164 values.put(RecordedPrograms.COLUMN_THUMBNAIL_URI, recordedProgram.mThumbnailUri); 165 values.put(RecordedPrograms.COLUMN_SEARCHABLE, recordedProgram.mSearchable ? 1 : 0); 166 values.put(RecordedPrograms.COLUMN_RECORDING_DATA_URI, 167 safeToString(recordedProgram.mDataUri)); 168 values.put(RecordedPrograms.COLUMN_RECORDING_DATA_BYTES, recordedProgram.mDataBytes); 169 values.put(RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS, 170 recordedProgram.mDurationMillis); 171 values.put(RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS, 172 recordedProgram.mExpireTimeUtcMillis); 173 values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA, 174 InternalDataUtils.serializeInternalProviderData(recordedProgram)); 175 values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1, 176 recordedProgram.mInternalProviderFlag1); 177 values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2, 178 recordedProgram.mInternalProviderFlag2); 179 values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3, 180 recordedProgram.mInternalProviderFlag3); 181 values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4, 182 recordedProgram.mInternalProviderFlag4); 183 values.put(RecordedPrograms.COLUMN_VERSION_NUMBER, recordedProgram.mVersionNumber); 184 return values; 185 } 186 187 public static class Builder{ 188 private long mId = ID_NOT_SET; 189 private String mPackageName; 190 private String mInputId; 191 private long mChannelId; 192 private String mTitle; 193 private String mSeriesId; 194 private String mSeasonNumber; 195 private String mSeasonTitle; 196 private String mEpisodeNumber; 197 private String mEpisodeTitle; 198 private long mStartTimeUtcMillis; 199 private long mEndTimeUtcMillis; 200 private String[] mBroadcastGenres; 201 private String[] mCanonicalGenres; 202 private String mShortDescription; 203 private String mLongDescription; 204 private int mVideoWidth; 205 private int mVideoHeight; 206 private String mAudioLanguage; 207 private TvContentRating[] mContentRatings; 208 private String mPosterArtUri; 209 private String mThumbnailUri; 210 private boolean mSearchable = true; 211 private Uri mDataUri; 212 private long mDataBytes; 213 private long mDurationMillis; 214 private long mExpireTimeUtcMillis; 215 private int mInternalProviderFlag1; 216 private int mInternalProviderFlag2; 217 private int mInternalProviderFlag3; 218 private int mInternalProviderFlag4; 219 private int mVersionNumber; 220 221 public Builder setId(long id) { 222 mId = id; 223 return this; 224 } 225 226 public Builder setPackageName(String packageName) { 227 mPackageName = packageName; 228 return this; 229 } 230 231 public Builder setInputId(String inputId) { 232 mInputId = inputId; 233 return this; 234 } 235 236 public Builder setChannelId(long channelId) { 237 mChannelId = channelId; 238 return this; 239 } 240 241 public Builder setTitle(String title) { 242 mTitle = title; 243 return this; 244 } 245 246 public Builder setSeriesId(String seriesId) { 247 mSeriesId = seriesId; 248 return this; 249 } 250 251 public Builder setSeasonNumber(String seasonNumber) { 252 mSeasonNumber = seasonNumber; 253 return this; 254 } 255 256 public Builder setSeasonTitle(String seasonTitle) { 257 mSeasonTitle = seasonTitle; 258 return this; 259 } 260 261 public Builder setEpisodeNumber(String episodeNumber) { 262 mEpisodeNumber = episodeNumber; 263 return this; 264 } 265 266 public Builder setEpisodeTitle(String episodeTitle) { 267 mEpisodeTitle = episodeTitle; 268 return this; 269 } 270 271 public Builder setStartTimeUtcMillis(long startTimeUtcMillis) { 272 mStartTimeUtcMillis = startTimeUtcMillis; 273 return this; 274 } 275 276 public Builder setEndTimeUtcMillis(long endTimeUtcMillis) { 277 mEndTimeUtcMillis = endTimeUtcMillis; 278 return this; 279 } 280 281 public Builder setBroadcastGenres(String broadcastGenres) { 282 if (TextUtils.isEmpty(broadcastGenres)) { 283 mBroadcastGenres = null; 284 return this; 285 } 286 return setBroadcastGenres(TvContract.Programs.Genres.decode(broadcastGenres)); 287 } 288 289 private Builder setBroadcastGenres(String[] broadcastGenres) { 290 mBroadcastGenres = broadcastGenres; 291 return this; 292 } 293 294 public Builder setCanonicalGenres(String canonicalGenres) { 295 if (TextUtils.isEmpty(canonicalGenres)) { 296 mCanonicalGenres = null; 297 return this; 298 } 299 return setCanonicalGenres(TvContract.Programs.Genres.decode(canonicalGenres)); 300 } 301 302 private Builder setCanonicalGenres(String[] canonicalGenres) { 303 mCanonicalGenres = canonicalGenres; 304 return this; 305 } 306 307 public Builder setShortDescription(String shortDescription) { 308 mShortDescription = shortDescription; 309 return this; 310 } 311 312 public Builder setLongDescription(String longDescription) { 313 mLongDescription = longDescription; 314 return this; 315 } 316 317 public Builder setVideoWidth(int videoWidth) { 318 mVideoWidth = videoWidth; 319 return this; 320 } 321 322 public Builder setVideoHeight(int videoHeight) { 323 mVideoHeight = videoHeight; 324 return this; 325 } 326 327 public Builder setAudioLanguage(String audioLanguage) { 328 mAudioLanguage = audioLanguage; 329 return this; 330 } 331 332 public Builder setContentRatings(TvContentRating[] contentRatings) { 333 mContentRatings = contentRatings; 334 return this; 335 } 336 337 private Uri toUri(String uriString) { 338 try { 339 return uriString == null ? null : Uri.parse(uriString); 340 } catch (Exception e) { 341 return null; 342 } 343 } 344 345 public Builder setPosterArtUri(String posterArtUri) { 346 mPosterArtUri = posterArtUri; 347 return this; 348 } 349 350 public Builder setThumbnailUri(String thumbnailUri) { 351 mThumbnailUri = thumbnailUri; 352 return this; 353 } 354 355 public Builder setSearchable(boolean searchable) { 356 mSearchable = searchable; 357 return this; 358 } 359 360 public Builder setDataUri(String dataUri) { 361 return setDataUri(toUri(dataUri)); 362 } 363 364 public Builder setDataUri(Uri dataUri) { 365 mDataUri = dataUri; 366 return this; 367 } 368 369 public Builder setDataBytes(long dataBytes) { 370 mDataBytes = dataBytes; 371 return this; 372 } 373 374 public Builder setDurationMillis(long durationMillis) { 375 mDurationMillis = durationMillis; 376 return this; 377 } 378 379 public Builder setExpireTimeUtcMillis(long expireTimeUtcMillis) { 380 mExpireTimeUtcMillis = expireTimeUtcMillis; 381 return this; 382 } 383 384 public Builder setInternalProviderFlag1(int internalProviderFlag1) { 385 mInternalProviderFlag1 = internalProviderFlag1; 386 return this; 387 } 388 389 public Builder setInternalProviderFlag2(int internalProviderFlag2) { 390 mInternalProviderFlag2 = internalProviderFlag2; 391 return this; 392 } 393 394 public Builder setInternalProviderFlag3(int internalProviderFlag3) { 395 mInternalProviderFlag3 = internalProviderFlag3; 396 return this; 397 } 398 399 public Builder setInternalProviderFlag4(int internalProviderFlag4) { 400 mInternalProviderFlag4 = internalProviderFlag4; 401 return this; 402 } 403 404 public Builder setVersionNumber(int versionNumber) { 405 mVersionNumber = versionNumber; 406 return this; 407 } 408 409 public RecordedProgram build() { 410 if (TextUtils.isEmpty(mTitle)) { 411 // If title is null, series cannot be generated for this program. 412 setSeriesId(null); 413 } else if (TextUtils.isEmpty(mSeriesId) && !TextUtils.isEmpty(mEpisodeNumber)) { 414 // If series ID is not set, generate it for the episodic program of other TV input. 415 setSeriesId(BaseProgram.generateSeriesId(mPackageName, mTitle)); 416 } 417 return new RecordedProgram(mId, mPackageName, mInputId, mChannelId, mTitle, mSeriesId, 418 mSeasonNumber, mSeasonTitle, mEpisodeNumber, mEpisodeTitle, mStartTimeUtcMillis, 419 mEndTimeUtcMillis, mBroadcastGenres, mCanonicalGenres, mShortDescription, 420 mLongDescription, mVideoWidth, mVideoHeight, mAudioLanguage, mContentRatings, 421 mPosterArtUri, mThumbnailUri, mSearchable, mDataUri, mDataBytes, 422 mDurationMillis, mExpireTimeUtcMillis, mInternalProviderFlag1, 423 mInternalProviderFlag2, mInternalProviderFlag3, mInternalProviderFlag4, 424 mVersionNumber); 425 } 426 } 427 428 public static Builder builder() { return new Builder(); } 429 430 public static Builder buildFrom(RecordedProgram orig) { 431 return builder() 432 .setId(orig.getId()) 433 .setPackageName(orig.getPackageName()) 434 .setInputId(orig.getInputId()) 435 .setChannelId(orig.getChannelId()) 436 .setTitle(orig.getTitle()) 437 .setSeriesId(orig.getSeriesId()) 438 .setSeasonNumber(orig.getSeasonNumber()) 439 .setSeasonTitle(orig.getSeasonTitle()) 440 .setEpisodeNumber(orig.getEpisodeNumber()) 441 .setEpisodeTitle(orig.getEpisodeTitle()) 442 .setStartTimeUtcMillis(orig.getStartTimeUtcMillis()) 443 .setEndTimeUtcMillis(orig.getEndTimeUtcMillis()) 444 .setBroadcastGenres(orig.getBroadcastGenres()) 445 .setCanonicalGenres(orig.getCanonicalGenres()) 446 .setShortDescription(orig.getDescription()) 447 .setLongDescription(orig.getLongDescription()) 448 .setVideoWidth(orig.getVideoWidth()) 449 .setVideoHeight(orig.getVideoHeight()) 450 .setAudioLanguage(orig.getAudioLanguage()) 451 .setContentRatings(orig.getContentRatings()) 452 .setPosterArtUri(orig.getPosterArtUri()) 453 .setThumbnailUri(orig.getThumbnailUri()) 454 .setSearchable(orig.isSearchable()) 455 .setInternalProviderFlag1(orig.getInternalProviderFlag1()) 456 .setInternalProviderFlag2(orig.getInternalProviderFlag2()) 457 .setInternalProviderFlag3(orig.getInternalProviderFlag3()) 458 .setInternalProviderFlag4(orig.getInternalProviderFlag4()) 459 .setVersionNumber(orig.getVersionNumber()); 460 } 461 462 public static final Comparator<RecordedProgram> START_TIME_THEN_ID_COMPARATOR = 463 new Comparator<RecordedProgram>() { 464 @Override 465 public int compare(RecordedProgram lhs, RecordedProgram rhs) { 466 int res = 467 Long.compare(lhs.getStartTimeUtcMillis(), rhs.getStartTimeUtcMillis()); 468 if (res != 0) { 469 return res; 470 } 471 return Long.compare(lhs.mId, rhs.mId); 472 } 473 }; 474 475 private static final long CLIPPED_THRESHOLD_MS = TimeUnit.MINUTES.toMillis(5); 476 477 private final long mId; 478 private final String mPackageName; 479 private final String mInputId; 480 private final long mChannelId; 481 private final String mTitle; 482 private final String mSeriesId; 483 private final String mSeasonNumber; 484 private final String mSeasonTitle; 485 private final String mEpisodeNumber; 486 private final String mEpisodeTitle; 487 private final long mStartTimeUtcMillis; 488 private final long mEndTimeUtcMillis; 489 private final String[] mBroadcastGenres; 490 private final String[] mCanonicalGenres; 491 private final String mShortDescription; 492 private final String mLongDescription; 493 private final int mVideoWidth; 494 private final int mVideoHeight; 495 private final String mAudioLanguage; 496 private final TvContentRating[] mContentRatings; 497 private final String mPosterArtUri; 498 private final String mThumbnailUri; 499 private final boolean mSearchable; 500 private final Uri mDataUri; 501 private final long mDataBytes; 502 private final long mDurationMillis; 503 private final long mExpireTimeUtcMillis; 504 private final int mInternalProviderFlag1; 505 private final int mInternalProviderFlag2; 506 private final int mInternalProviderFlag3; 507 private final int mInternalProviderFlag4; 508 private final int mVersionNumber; 509 510 private RecordedProgram(long id, String packageName, String inputId, long channelId, 511 String title, String seriesId, String seasonNumber, String seasonTitle, 512 String episodeNumber, String episodeTitle, long startTimeUtcMillis, 513 long endTimeUtcMillis, String[] broadcastGenres, String[] canonicalGenres, 514 String shortDescription, String longDescription, int videoWidth, int videoHeight, 515 String audioLanguage, TvContentRating[] contentRatings, String posterArtUri, 516 String thumbnailUri, boolean searchable, Uri dataUri, long dataBytes, 517 long durationMillis, long expireTimeUtcMillis, int internalProviderFlag1, 518 int internalProviderFlag2, int internalProviderFlag3, int internalProviderFlag4, 519 int versionNumber) { 520 mId = id; 521 mPackageName = packageName; 522 mInputId = inputId; 523 mChannelId = channelId; 524 mTitle = title; 525 mSeriesId = seriesId; 526 mSeasonNumber = seasonNumber; 527 mSeasonTitle = seasonTitle; 528 mEpisodeNumber = episodeNumber; 529 mEpisodeTitle = episodeTitle; 530 mStartTimeUtcMillis = startTimeUtcMillis; 531 mEndTimeUtcMillis = endTimeUtcMillis; 532 mBroadcastGenres = broadcastGenres; 533 mCanonicalGenres = canonicalGenres; 534 mShortDescription = shortDescription; 535 mLongDescription = longDescription; 536 mVideoWidth = videoWidth; 537 mVideoHeight = videoHeight; 538 539 mAudioLanguage = audioLanguage; 540 mContentRatings = contentRatings; 541 mPosterArtUri = posterArtUri; 542 mThumbnailUri = thumbnailUri; 543 mSearchable = searchable; 544 mDataUri = dataUri; 545 mDataBytes = dataBytes; 546 mDurationMillis = durationMillis; 547 mExpireTimeUtcMillis = expireTimeUtcMillis; 548 mInternalProviderFlag1 = internalProviderFlag1; 549 mInternalProviderFlag2 = internalProviderFlag2; 550 mInternalProviderFlag3 = internalProviderFlag3; 551 mInternalProviderFlag4 = internalProviderFlag4; 552 mVersionNumber = versionNumber; 553 } 554 555 public String getAudioLanguage() { 556 return mAudioLanguage; 557 } 558 559 public String[] getBroadcastGenres() { 560 return mBroadcastGenres; 561 } 562 563 public String[] getCanonicalGenres() { 564 return mCanonicalGenres; 565 } 566 567 /** 568 * Returns array of canonical genre ID's for this recorded program. 569 */ 570 @Override 571 public int[] getCanonicalGenreIds() { 572 if (mCanonicalGenres == null) { 573 return null; 574 } 575 int[] genreIds = new int[mCanonicalGenres.length]; 576 for (int i = 0; i < mCanonicalGenres.length; i++) { 577 genreIds[i] = GenreItems.getId(mCanonicalGenres[i]); 578 } 579 return genreIds; 580 } 581 582 @Override 583 public long getChannelId() { 584 return mChannelId; 585 } 586 587 @Nullable 588 @Override 589 public TvContentRating[] getContentRatings() { 590 return mContentRatings; 591 } 592 593 public Uri getDataUri() { 594 return mDataUri; 595 } 596 597 public long getDataBytes() { 598 return mDataBytes; 599 } 600 601 @Override 602 public long getDurationMillis() { 603 return mDurationMillis; 604 } 605 606 @Override 607 public long getEndTimeUtcMillis() { 608 return mEndTimeUtcMillis; 609 } 610 611 @Override 612 public String getEpisodeNumber() { 613 return mEpisodeNumber; 614 } 615 616 @Override 617 public String getEpisodeTitle() { 618 return mEpisodeTitle; 619 } 620 621 @Nullable 622 public String getEpisodeDisplayNumber(Context context) { 623 if (!TextUtils.isEmpty(mEpisodeNumber)) { 624 if (TextUtils.equals(mSeasonNumber, "0")) { 625 // Do not show "S0: ". 626 return String.format(context.getResources().getString( 627 R.string.display_episode_number_format_no_season_number), mEpisodeNumber); 628 } else { 629 return String.format(context.getResources().getString( 630 R.string.display_episode_number_format), mSeasonNumber, mEpisodeNumber); 631 } 632 } 633 return null; 634 } 635 636 public long getExpireTimeUtcMillis() { 637 return mExpireTimeUtcMillis; 638 } 639 640 public long getId() { 641 return mId; 642 } 643 644 public String getPackageName() { 645 return mPackageName; 646 } 647 648 public String getInputId() { 649 return mInputId; 650 } 651 652 public int getInternalProviderFlag1() { 653 return mInternalProviderFlag1; 654 } 655 656 public int getInternalProviderFlag2() { 657 return mInternalProviderFlag2; 658 } 659 660 public int getInternalProviderFlag3() { 661 return mInternalProviderFlag3; 662 } 663 664 public int getInternalProviderFlag4() { 665 return mInternalProviderFlag4; 666 } 667 668 @Override 669 public String getDescription() { 670 return mShortDescription; 671 } 672 673 @Override 674 public String getLongDescription() { 675 return mLongDescription; 676 } 677 678 @Override 679 public String getPosterArtUri() { 680 return mPosterArtUri; 681 } 682 683 @Override 684 public boolean isValid() { 685 return true; 686 } 687 688 public boolean isSearchable() { 689 return mSearchable; 690 } 691 692 @Override 693 public String getSeriesId() { 694 return mSeriesId; 695 } 696 697 @Override 698 public String getSeasonNumber() { 699 return mSeasonNumber; 700 } 701 702 public String getSeasonTitle() { 703 return mSeasonTitle; 704 } 705 706 @Override 707 public long getStartTimeUtcMillis() { 708 return mStartTimeUtcMillis; 709 } 710 711 @Override 712 public String getThumbnailUri() { 713 return mThumbnailUri; 714 } 715 716 @Override 717 public String getTitle() { 718 return mTitle; 719 } 720 721 public Uri getUri() { 722 return ContentUris.withAppendedId(RecordedPrograms.CONTENT_URI, mId); 723 } 724 725 public int getVersionNumber() { 726 return mVersionNumber; 727 } 728 729 public int getVideoHeight() { 730 return mVideoHeight; 731 } 732 733 public int getVideoWidth() { 734 return mVideoWidth; 735 } 736 737 /** 738 * Checks whether the recording has been clipped or not. 739 */ 740 public boolean isClipped() { 741 return mEndTimeUtcMillis - mStartTimeUtcMillis - mDurationMillis > CLIPPED_THRESHOLD_MS; 742 } 743 744 @Override 745 public boolean equals(Object o) { 746 if (this == o) return true; 747 if (o == null || getClass() != o.getClass()) return false; 748 RecordedProgram that = (RecordedProgram) o; 749 return Objects.equals(mId, that.mId) && 750 Objects.equals(mChannelId, that.mChannelId) && 751 Objects.equals(mSeriesId, that.mSeriesId) && 752 Objects.equals(mSeasonNumber, that.mSeasonNumber) && 753 Objects.equals(mSeasonTitle, that.mSeasonTitle) && 754 Objects.equals(mEpisodeNumber, that.mEpisodeNumber) && 755 Objects.equals(mStartTimeUtcMillis, that.mStartTimeUtcMillis) && 756 Objects.equals(mEndTimeUtcMillis, that.mEndTimeUtcMillis) && 757 Objects.equals(mVideoWidth, that.mVideoWidth) && 758 Objects.equals(mVideoHeight, that.mVideoHeight) && 759 Objects.equals(mSearchable, that.mSearchable) && 760 Objects.equals(mDataBytes, that.mDataBytes) && 761 Objects.equals(mDurationMillis, that.mDurationMillis) && 762 Objects.equals(mExpireTimeUtcMillis, that.mExpireTimeUtcMillis) && 763 Objects.equals(mInternalProviderFlag1, that.mInternalProviderFlag1) && 764 Objects.equals(mInternalProviderFlag2, that.mInternalProviderFlag2) && 765 Objects.equals(mInternalProviderFlag3, that.mInternalProviderFlag3) && 766 Objects.equals(mInternalProviderFlag4, that.mInternalProviderFlag4) && 767 Objects.equals(mVersionNumber, that.mVersionNumber) && 768 Objects.equals(mTitle, that.mTitle) && 769 Objects.equals(mEpisodeTitle, that.mEpisodeTitle) && 770 Arrays.equals(mBroadcastGenres, that.mBroadcastGenres) && 771 Arrays.equals(mCanonicalGenres, that.mCanonicalGenres) && 772 Objects.equals(mShortDescription, that.mShortDescription) && 773 Objects.equals(mLongDescription, that.mLongDescription) && 774 Objects.equals(mAudioLanguage, that.mAudioLanguage) && 775 Arrays.equals(mContentRatings, that.mContentRatings) && 776 Objects.equals(mPosterArtUri, that.mPosterArtUri) && 777 Objects.equals(mThumbnailUri, that.mThumbnailUri); 778 } 779 780 /** 781 * Hashes based on the ID. 782 */ 783 @Override 784 public int hashCode() { 785 return Objects.hash(mId); 786 } 787 788 @Override 789 public String toString() { 790 return "RecordedProgram" 791 + "[" + mId + 792 "]{ mPackageName=" + mPackageName + 793 ", mInputId='" + mInputId + '\'' + 794 ", mChannelId='" + mChannelId + '\'' + 795 ", mTitle='" + mTitle + '\'' + 796 ", mSeriesId='" + mSeriesId + '\'' + 797 ", mEpisodeNumber=" + mEpisodeNumber + 798 ", mEpisodeTitle='" + mEpisodeTitle + '\'' + 799 ", mStartTimeUtcMillis=" + mStartTimeUtcMillis + 800 ", mEndTimeUtcMillis=" + mEndTimeUtcMillis + 801 ", mBroadcastGenres=" + 802 (mBroadcastGenres != null ? Arrays.toString(mBroadcastGenres) : "null") + 803 ", mCanonicalGenres=" + 804 (mCanonicalGenres != null ? Arrays.toString(mCanonicalGenres) : "null") + 805 ", mShortDescription='" + mShortDescription + '\'' + 806 ", mLongDescription='" + mLongDescription + '\'' + 807 ", mVideoHeight=" + mVideoHeight + 808 ", mVideoWidth=" + mVideoWidth + 809 ", mAudioLanguage='" + mAudioLanguage + '\'' + 810 ", mContentRatings='" + 811 TvContentRatingCache.contentRatingsToString(mContentRatings) + '\'' + 812 ", mPosterArtUri=" + mPosterArtUri + 813 ", mThumbnailUri=" + mThumbnailUri + 814 ", mSearchable=" + mSearchable + 815 ", mDataUri=" + mDataUri + 816 ", mDataBytes=" + mDataBytes + 817 ", mDurationMillis=" + mDurationMillis + 818 ", mExpireTimeUtcMillis=" + mExpireTimeUtcMillis + 819 ", mInternalProviderFlag1=" + mInternalProviderFlag1 + 820 ", mInternalProviderFlag2=" + mInternalProviderFlag2 + 821 ", mInternalProviderFlag3=" + mInternalProviderFlag3 + 822 ", mInternalProviderFlag4=" + mInternalProviderFlag4 + 823 ", mSeasonNumber=" + mSeasonNumber + 824 ", mSeasonTitle=" + mSeasonTitle + 825 ", mVersionNumber=" + mVersionNumber + 826 '}'; 827 } 828 829 @Nullable 830 private static String safeToString(@Nullable Object o) { 831 return o == null ? null : o.toString(); 832 } 833 834 @Nullable 835 private static String safeEncode(@Nullable String[] genres) { 836 return genres == null ? null : TvContract.Programs.Genres.encode(genres); 837 } 838 839 /** 840 * Returns an array containing all of the elements in the list. 841 */ 842 public static RecordedProgram[] toArray(Collection<RecordedProgram> recordedPrograms) { 843 return recordedPrograms.toArray(new RecordedProgram[recordedPrograms.size()]); 844 } 845 } 846