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.data; 18 19 import android.content.Context; 20 import android.database.Cursor; 21 import android.media.tv.TvContentRating; 22 import android.media.tv.TvContract; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 import android.support.annotation.NonNull; 26 import android.support.annotation.Nullable; 27 import android.support.annotation.UiThread; 28 import android.support.annotation.VisibleForTesting; 29 import android.support.v4.os.BuildCompat; 30 import android.text.TextUtils; 31 import android.util.Log; 32 33 import com.android.tv.R; 34 import com.android.tv.common.BuildConfig; 35 import com.android.tv.common.CollectionUtils; 36 import com.android.tv.common.TvContentRatingCache; 37 import com.android.tv.util.ImageLoader; 38 import com.android.tv.util.Utils; 39 40 import java.io.Serializable; 41 import java.util.ArrayList; 42 import java.util.Arrays; 43 import java.util.List; 44 import java.util.Objects; 45 46 /** 47 * A convenience class to create and insert program information entries into the database. 48 */ 49 public final class Program extends BaseProgram implements Comparable<Program>, Parcelable { 50 private static final boolean DEBUG = false; 51 private static final boolean DEBUG_DUMP_DESCRIPTION = false; 52 private static final String TAG = "Program"; 53 54 private static final String[] PROJECTION_BASE = { 55 // Columns must match what is read in Program.fromCursor() 56 TvContract.Programs._ID, 57 TvContract.Programs.COLUMN_PACKAGE_NAME, 58 TvContract.Programs.COLUMN_CHANNEL_ID, 59 TvContract.Programs.COLUMN_TITLE, 60 TvContract.Programs.COLUMN_EPISODE_TITLE, 61 TvContract.Programs.COLUMN_SHORT_DESCRIPTION, 62 TvContract.Programs.COLUMN_LONG_DESCRIPTION, 63 TvContract.Programs.COLUMN_POSTER_ART_URI, 64 TvContract.Programs.COLUMN_THUMBNAIL_URI, 65 TvContract.Programs.COLUMN_CANONICAL_GENRE, 66 TvContract.Programs.COLUMN_CONTENT_RATING, 67 TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, 68 TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, 69 TvContract.Programs.COLUMN_VIDEO_WIDTH, 70 TvContract.Programs.COLUMN_VIDEO_HEIGHT, 71 TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA 72 }; 73 74 // Columns which is deprecated in NYC 75 @SuppressWarnings("deprecation") 76 private static final String[] PROJECTION_DEPRECATED_IN_NYC = { 77 TvContract.Programs.COLUMN_SEASON_NUMBER, 78 TvContract.Programs.COLUMN_EPISODE_NUMBER 79 }; 80 81 private static final String[] PROJECTION_ADDED_IN_NYC = { 82 TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER, 83 TvContract.Programs.COLUMN_SEASON_TITLE, 84 TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER, 85 TvContract.Programs.COLUMN_RECORDING_PROHIBITED 86 }; 87 88 public static final String[] PROJECTION = createProjection(); 89 createProjection()90 private static String[] createProjection() { 91 return CollectionUtils 92 .concatAll(PROJECTION_BASE, BuildCompat.isAtLeastN() ? PROJECTION_ADDED_IN_NYC 93 : PROJECTION_DEPRECATED_IN_NYC); 94 } 95 96 /** 97 * Returns the column index for {@code column}, -1 if the column doesn't exist. 98 */ getColumnIndex(String column)99 public static int getColumnIndex(String column) { 100 for (int i = 0; i < PROJECTION.length; ++i) { 101 if (PROJECTION[i].equals(column)) { 102 return i; 103 } 104 } 105 return -1; 106 } 107 108 /** 109 * Creates {@code Program} object from cursor. 110 * 111 * <p>The query that created the cursor MUST use {@link #PROJECTION}. 112 */ fromCursor(Cursor cursor)113 public static Program fromCursor(Cursor cursor) { 114 // Columns read must match the order of match {@link #PROJECTION} 115 Builder builder = new Builder(); 116 int index = 0; 117 builder.setId(cursor.getLong(index++)); 118 String packageName = cursor.getString(index++); 119 builder.setPackageName(packageName); 120 builder.setChannelId(cursor.getLong(index++)); 121 builder.setTitle(cursor.getString(index++)); 122 builder.setEpisodeTitle(cursor.getString(index++)); 123 builder.setDescription(cursor.getString(index++)); 124 builder.setLongDescription(cursor.getString(index++)); 125 builder.setPosterArtUri(cursor.getString(index++)); 126 builder.setThumbnailUri(cursor.getString(index++)); 127 builder.setCanonicalGenres(cursor.getString(index++)); 128 builder.setContentRatings( 129 TvContentRatingCache.getInstance().getRatings(cursor.getString(index++))); 130 builder.setStartTimeUtcMillis(cursor.getLong(index++)); 131 builder.setEndTimeUtcMillis(cursor.getLong(index++)); 132 builder.setVideoWidth((int) cursor.getLong(index++)); 133 builder.setVideoHeight((int) cursor.getLong(index++)); 134 if (Utils.isInBundledPackageSet(packageName)) { 135 InternalDataUtils.deserializeInternalProviderData(cursor.getBlob(index), builder); 136 } 137 index++; 138 if (BuildCompat.isAtLeastN()) { 139 builder.setSeasonNumber(cursor.getString(index++)); 140 builder.setSeasonTitle(cursor.getString(index++)); 141 builder.setEpisodeNumber(cursor.getString(index++)); 142 builder.setRecordingProhibited(cursor.getInt(index++) == 1); 143 } else { 144 builder.setSeasonNumber(cursor.getString(index++)); 145 builder.setEpisodeNumber(cursor.getString(index++)); 146 } 147 return builder.build(); 148 } 149 fromParcel(Parcel in)150 public static Program fromParcel(Parcel in) { 151 Program program = new Program(); 152 program.mId = in.readLong(); 153 program.mPackageName = in.readString(); 154 program.mChannelId = in.readLong(); 155 program.mTitle = in.readString(); 156 program.mSeriesId = in.readString(); 157 program.mEpisodeTitle = in.readString(); 158 program.mSeasonNumber = in.readString(); 159 program.mSeasonTitle = in.readString(); 160 program.mEpisodeNumber = in.readString(); 161 program.mStartTimeUtcMillis = in.readLong(); 162 program.mEndTimeUtcMillis = in.readLong(); 163 program.mDescription = in.readString(); 164 program.mLongDescription = in.readString(); 165 program.mVideoWidth = in.readInt(); 166 program.mVideoHeight = in.readInt(); 167 program.mCriticScores = in.readArrayList(Thread.currentThread().getContextClassLoader()); 168 program.mPosterArtUri = in.readString(); 169 program.mThumbnailUri = in.readString(); 170 program.mCanonicalGenreIds = in.createIntArray(); 171 int length = in.readInt(); 172 if (length > 0) { 173 program.mContentRatings = new TvContentRating[length]; 174 for (int i = 0; i < length; ++i) { 175 program.mContentRatings[i] = TvContentRating.unflattenFromString(in.readString()); 176 } 177 } 178 program.mRecordingProhibited = in.readByte() != (byte) 0; 179 return program; 180 } 181 182 public static final Parcelable.Creator<Program> CREATOR = new Parcelable.Creator<Program>() { 183 @Override 184 public Program createFromParcel(Parcel in) { 185 return Program.fromParcel(in); 186 } 187 188 @Override 189 public Program[] newArray(int size) { 190 return new Program[size]; 191 } 192 }; 193 194 private long mId; 195 private String mPackageName; 196 private long mChannelId; 197 private String mTitle; 198 private String mSeriesId; 199 private String mEpisodeTitle; 200 private String mSeasonNumber; 201 private String mSeasonTitle; 202 private String mEpisodeNumber; 203 private long mStartTimeUtcMillis; 204 private long mEndTimeUtcMillis; 205 private String mDescription; 206 private String mLongDescription; 207 private int mVideoWidth; 208 private int mVideoHeight; 209 private List<CriticScore> mCriticScores; 210 private String mPosterArtUri; 211 private String mThumbnailUri; 212 private int[] mCanonicalGenreIds; 213 private TvContentRating[] mContentRatings; 214 private boolean mRecordingProhibited; 215 216 /** 217 * TODO(DVR): Need to fill the following data. 218 */ 219 private boolean mRecordingScheduled; 220 Program()221 private Program() { 222 // Do nothing. 223 } 224 getId()225 public long getId() { 226 return mId; 227 } 228 229 /** 230 * Returns the package name of this program. 231 */ getPackageName()232 public String getPackageName() { 233 return mPackageName; 234 } 235 getChannelId()236 public long getChannelId() { 237 return mChannelId; 238 } 239 240 /** 241 * Returns {@code true} if this program is valid or {@code false} otherwise. 242 */ 243 @Override isValid()244 public boolean isValid() { 245 return mChannelId >= 0; 246 } 247 248 /** 249 * Returns {@code true} if the program is valid and {@code false} otherwise. 250 */ isValid(Program program)251 public static boolean isValid(Program program) { 252 return program != null && program.isValid(); 253 } 254 255 @Override getTitle()256 public String getTitle() { 257 return mTitle; 258 } 259 260 /** 261 * Returns the series ID. 262 */ 263 @Override getSeriesId()264 public String getSeriesId() { 265 return mSeriesId; 266 } 267 268 /** 269 * Returns the episode title. 270 */ getEpisodeTitle()271 public String getEpisodeTitle() { 272 return mEpisodeTitle; 273 } 274 275 /** 276 * Returns season number, episode number and episode title for display. 277 */ 278 @Override getEpisodeDisplayTitle(Context context)279 public String getEpisodeDisplayTitle(Context context) { 280 if (!TextUtils.isEmpty(mEpisodeNumber)) { 281 String episodeTitle = mEpisodeTitle == null ? "" : mEpisodeTitle; 282 if (TextUtils.equals(mSeasonNumber, "0")) { 283 // Do not show "S0: ". 284 return String.format(context.getResources().getString( 285 R.string.display_episode_title_format_no_season_number), 286 mEpisodeNumber, episodeTitle); 287 } else { 288 return String.format(context.getResources().getString( 289 R.string.display_episode_title_format), 290 mSeasonNumber, mEpisodeNumber, episodeTitle); 291 } 292 } 293 return mEpisodeTitle; 294 } 295 296 @Override getTitleWithEpisodeNumber(Context context)297 public String getTitleWithEpisodeNumber(Context context) { 298 if (TextUtils.isEmpty(mTitle)) { 299 return mTitle; 300 } 301 if (TextUtils.isEmpty(mSeasonNumber) || mSeasonNumber.equals("0")) { 302 return TextUtils.isEmpty(mEpisodeNumber) ? mTitle : context.getString( 303 R.string.program_title_with_episode_number_no_season, mTitle, mEpisodeNumber); 304 } else { 305 return context.getString(R.string.program_title_with_episode_number, mTitle, 306 mSeasonNumber, mEpisodeNumber); 307 } 308 } 309 310 @Override getSeasonNumber()311 public String getSeasonNumber() { 312 return mSeasonNumber; 313 } 314 315 @Override getEpisodeNumber()316 public String getEpisodeNumber() { 317 return mEpisodeNumber; 318 } 319 320 @Override getStartTimeUtcMillis()321 public long getStartTimeUtcMillis() { 322 return mStartTimeUtcMillis; 323 } 324 325 @Override getEndTimeUtcMillis()326 public long getEndTimeUtcMillis() { 327 return mEndTimeUtcMillis; 328 } 329 330 /** 331 * Returns the program duration. 332 */ 333 @Override getDurationMillis()334 public long getDurationMillis() { 335 return mEndTimeUtcMillis - mStartTimeUtcMillis; 336 } 337 338 @Override getDescription()339 public String getDescription() { 340 return mDescription; 341 } 342 343 @Override getLongDescription()344 public String getLongDescription() { 345 return mLongDescription; 346 } 347 getVideoWidth()348 public int getVideoWidth() { 349 return mVideoWidth; 350 } 351 getVideoHeight()352 public int getVideoHeight() { 353 return mVideoHeight; 354 } 355 356 /** 357 * Returns the list of Critic Scores for this program 358 */ 359 @Nullable getCriticScores()360 public List<CriticScore> getCriticScores() { 361 return mCriticScores; 362 } 363 getContentRatings()364 public TvContentRating[] getContentRatings() { 365 return mContentRatings; 366 } 367 368 @Override getPosterArtUri()369 public String getPosterArtUri() { 370 return mPosterArtUri; 371 } 372 373 @Override getThumbnailUri()374 public String getThumbnailUri() { 375 return mThumbnailUri; 376 } 377 378 /** 379 * Returns {@code true} if the recording of this program is prohibited. 380 */ isRecordingProhibited()381 public boolean isRecordingProhibited() { 382 return mRecordingProhibited; 383 } 384 385 /** 386 * Returns array of canonical genres for this program. 387 * This is expected to be called rarely. 388 */ 389 @Nullable getCanonicalGenres()390 public String[] getCanonicalGenres() { 391 if (mCanonicalGenreIds == null) { 392 return null; 393 } 394 String[] genres = new String[mCanonicalGenreIds.length]; 395 for (int i = 0; i < mCanonicalGenreIds.length; i++) { 396 genres[i] = GenreItems.getCanonicalGenre(mCanonicalGenreIds[i]); 397 } 398 return genres; 399 } 400 401 /** 402 * Returns array of canonical genre ID's for this program. 403 */ 404 @Override getCanonicalGenreIds()405 public int[] getCanonicalGenreIds() { 406 return mCanonicalGenreIds; 407 } 408 409 /** 410 * Returns if this program has the genre. 411 */ hasGenre(int genreId)412 public boolean hasGenre(int genreId) { 413 if (genreId == GenreItems.ID_ALL_CHANNELS) { 414 return true; 415 } 416 if (mCanonicalGenreIds != null) { 417 for (int id : mCanonicalGenreIds) { 418 if (id == genreId) { 419 return true; 420 } 421 } 422 } 423 return false; 424 } 425 426 @Override hashCode()427 public int hashCode() { 428 // Hash with all the properties because program ID can be invalid for the dummy programs. 429 return Objects.hash(mChannelId, mStartTimeUtcMillis, mEndTimeUtcMillis, 430 mTitle, mSeriesId, mEpisodeTitle, mDescription, mLongDescription, mVideoWidth, 431 mVideoHeight, mPosterArtUri, mThumbnailUri, Arrays.hashCode(mContentRatings), 432 Arrays.hashCode(mCanonicalGenreIds), mSeasonNumber, mSeasonTitle, mEpisodeNumber, 433 mRecordingProhibited); 434 } 435 436 @Override equals(Object other)437 public boolean equals(Object other) { 438 if (!(other instanceof Program)) { 439 return false; 440 } 441 // Compare all the properties because program ID can be invalid for the dummy programs. 442 Program program = (Program) other; 443 return Objects.equals(mPackageName, program.mPackageName) 444 && mChannelId == program.mChannelId 445 && mStartTimeUtcMillis == program.mStartTimeUtcMillis 446 && mEndTimeUtcMillis == program.mEndTimeUtcMillis 447 && Objects.equals(mTitle, program.mTitle) 448 && Objects.equals(mSeriesId, program.mSeriesId) 449 && Objects.equals(mEpisodeTitle, program.mEpisodeTitle) 450 && Objects.equals(mDescription, program.mDescription) 451 && Objects.equals(mLongDescription, program.mLongDescription) 452 && mVideoWidth == program.mVideoWidth 453 && mVideoHeight == program.mVideoHeight 454 && Objects.equals(mPosterArtUri, program.mPosterArtUri) 455 && Objects.equals(mThumbnailUri, program.mThumbnailUri) 456 && Arrays.equals(mContentRatings, program.mContentRatings) 457 && Arrays.equals(mCanonicalGenreIds, program.mCanonicalGenreIds) 458 && Objects.equals(mSeasonNumber, program.mSeasonNumber) 459 && Objects.equals(mSeasonTitle, program.mSeasonTitle) 460 && Objects.equals(mEpisodeNumber, program.mEpisodeNumber) 461 && mRecordingProhibited == program.mRecordingProhibited; 462 } 463 464 @Override compareTo(@onNull Program other)465 public int compareTo(@NonNull Program other) { 466 return Long.compare(mStartTimeUtcMillis, other.mStartTimeUtcMillis); 467 } 468 469 @Override toString()470 public String toString() { 471 StringBuilder builder = new StringBuilder(); 472 builder.append("Program[").append(mId) 473 .append("]{channelId=").append(mChannelId) 474 .append(", packageName=").append(mPackageName) 475 .append(", title=").append(mTitle) 476 .append(", seriesId=").append(mSeriesId) 477 .append(", episodeTitle=").append(mEpisodeTitle) 478 .append(", seasonNumber=").append(mSeasonNumber) 479 .append(", seasonTitle=").append(mSeasonTitle) 480 .append(", episodeNumber=").append(mEpisodeNumber) 481 .append(", startTimeUtcSec=").append(Utils.toTimeString(mStartTimeUtcMillis)) 482 .append(", endTimeUtcSec=").append(Utils.toTimeString(mEndTimeUtcMillis)) 483 .append(", videoWidth=").append(mVideoWidth) 484 .append(", videoHeight=").append(mVideoHeight) 485 .append(", contentRatings=") 486 .append(TvContentRatingCache.contentRatingsToString(mContentRatings)) 487 .append(", posterArtUri=").append(mPosterArtUri) 488 .append(", thumbnailUri=").append(mThumbnailUri) 489 .append(", canonicalGenres=").append(Arrays.toString(mCanonicalGenreIds)) 490 .append(", recordingProhibited=").append(mRecordingProhibited); 491 if (DEBUG_DUMP_DESCRIPTION) { 492 builder.append(", description=").append(mDescription) 493 .append(", longDescription=").append(mLongDescription); 494 } 495 return builder.append("}").toString(); 496 } 497 copyFrom(Program other)498 public void copyFrom(Program other) { 499 if (this == other) { 500 return; 501 } 502 503 mId = other.mId; 504 mPackageName = other.mPackageName; 505 mChannelId = other.mChannelId; 506 mTitle = other.mTitle; 507 mSeriesId = other.mSeriesId; 508 mEpisodeTitle = other.mEpisodeTitle; 509 mSeasonNumber = other.mSeasonNumber; 510 mSeasonTitle = other.mSeasonTitle; 511 mEpisodeNumber = other.mEpisodeNumber; 512 mStartTimeUtcMillis = other.mStartTimeUtcMillis; 513 mEndTimeUtcMillis = other.mEndTimeUtcMillis; 514 mDescription = other.mDescription; 515 mLongDescription = other.mLongDescription; 516 mVideoWidth = other.mVideoWidth; 517 mVideoHeight = other.mVideoHeight; 518 mCriticScores = other.mCriticScores; 519 mPosterArtUri = other.mPosterArtUri; 520 mThumbnailUri = other.mThumbnailUri; 521 mCanonicalGenreIds = other.mCanonicalGenreIds; 522 mContentRatings = other.mContentRatings; 523 mRecordingProhibited = other.mRecordingProhibited; 524 } 525 526 /** 527 * Checks whether the program is episodic or not. 528 */ isEpisodic()529 public boolean isEpisodic() { 530 return mSeriesId != null; 531 } 532 533 /** 534 * A Builder for the Program class 535 */ 536 public static final class Builder { 537 private final Program mProgram; 538 539 /** 540 * Creates a Builder for this Program class 541 */ Builder()542 public Builder() { 543 mProgram = new Program(); 544 // Fill initial data. 545 mProgram.mPackageName = null; 546 mProgram.mChannelId = Channel.INVALID_ID; 547 mProgram.mTitle = null; 548 mProgram.mSeasonNumber = null; 549 mProgram.mSeasonTitle = null; 550 mProgram.mEpisodeNumber = null; 551 mProgram.mStartTimeUtcMillis = -1; 552 mProgram.mEndTimeUtcMillis = -1; 553 mProgram.mDescription = null; 554 mProgram.mLongDescription = null; 555 mProgram.mRecordingProhibited = false; 556 mProgram.mCriticScores = null; 557 } 558 559 /** 560 * Creates a builder for this Program class 561 * by setting default values equivalent to another Program 562 * @param other the program to be copied 563 */ 564 @VisibleForTesting Builder(Program other)565 public Builder(Program other) { 566 mProgram = new Program(); 567 mProgram.copyFrom(other); 568 } 569 570 /** 571 * Sets the ID of this program 572 * @param id the ID 573 * @return a reference to this object 574 */ setId(long id)575 public Builder setId(long id) { 576 mProgram.mId = id; 577 return this; 578 } 579 580 /** 581 * Sets the package name for this program 582 * @param packageName the package name 583 * @return a reference to this object 584 */ setPackageName(String packageName)585 public Builder setPackageName(String packageName){ 586 mProgram.mPackageName = packageName; 587 return this; 588 } 589 590 /** 591 * Sets the channel ID for this program 592 * @param channelId the channel ID 593 * @return a reference to this object 594 */ setChannelId(long channelId)595 public Builder setChannelId(long channelId) { 596 mProgram.mChannelId = channelId; 597 return this; 598 } 599 600 /** 601 * Sets the program title 602 * @param title the title 603 * @return a reference to this object 604 */ setTitle(String title)605 public Builder setTitle(String title) { 606 mProgram.mTitle = title; 607 return this; 608 } 609 610 /** 611 * Sets the series ID. 612 * @param seriesId the series ID 613 * @return a reference to this object 614 */ setSeriesId(String seriesId)615 public Builder setSeriesId(String seriesId) { 616 mProgram.mSeriesId = seriesId; 617 return this; 618 } 619 620 /** 621 * Sets the episode title if this is a series program 622 * @param episodeTitle the episode title 623 * @return a reference to this object 624 */ setEpisodeTitle(String episodeTitle)625 public Builder setEpisodeTitle(String episodeTitle) { 626 mProgram.mEpisodeTitle = episodeTitle; 627 return this; 628 } 629 630 /** 631 * Sets the season number if this is a series program 632 * @param seasonNumber the season number 633 * @return a reference to this object 634 */ setSeasonNumber(String seasonNumber)635 public Builder setSeasonNumber(String seasonNumber) { 636 mProgram.mSeasonNumber = seasonNumber; 637 return this; 638 } 639 640 641 /** 642 * Sets the season title if this is a series program 643 * @param seasonTitle the season title 644 * @return a reference to this object 645 */ setSeasonTitle(String seasonTitle)646 public Builder setSeasonTitle(String seasonTitle) { 647 mProgram.mSeasonTitle = seasonTitle; 648 return this; 649 } 650 651 /** 652 * Sets the episode number if this is a series program 653 * @param episodeNumber the episode number 654 * @return a reference to this object 655 */ setEpisodeNumber(String episodeNumber)656 public Builder setEpisodeNumber(String episodeNumber) { 657 mProgram.mEpisodeNumber = episodeNumber; 658 return this; 659 } 660 661 /** 662 * Sets the start time of this program 663 * @param startTimeUtcMillis the start time in UTC milliseconds 664 * @return a reference to this object 665 */ setStartTimeUtcMillis(long startTimeUtcMillis)666 public Builder setStartTimeUtcMillis(long startTimeUtcMillis) { 667 mProgram.mStartTimeUtcMillis = startTimeUtcMillis; 668 return this; 669 } 670 671 /** 672 * Sets the end time of this program 673 * @param endTimeUtcMillis the end time in UTC milliseconds 674 * @return a reference to this object 675 */ setEndTimeUtcMillis(long endTimeUtcMillis)676 public Builder setEndTimeUtcMillis(long endTimeUtcMillis) { 677 mProgram.mEndTimeUtcMillis = endTimeUtcMillis; 678 return this; 679 } 680 681 /** 682 * Sets a description 683 * @param description the description 684 * @return a reference to this object 685 */ setDescription(String description)686 public Builder setDescription(String description) { 687 mProgram.mDescription = description; 688 return this; 689 } 690 691 /** 692 * Sets a long description 693 * @param longDescription the long description 694 * @return a reference to this object 695 */ setLongDescription(String longDescription)696 public Builder setLongDescription(String longDescription) { 697 mProgram.mLongDescription = longDescription; 698 return this; 699 } 700 701 /** 702 * Defines the video width of this program 703 * @param width 704 * @return a reference to this object 705 */ setVideoWidth(int width)706 public Builder setVideoWidth(int width) { 707 mProgram.mVideoWidth = width; 708 return this; 709 } 710 711 /** 712 * Defines the video height of this program 713 * @param height 714 * @return a reference to this object 715 */ setVideoHeight(int height)716 public Builder setVideoHeight(int height) { 717 mProgram.mVideoHeight = height; 718 return this; 719 } 720 721 /** 722 * Sets the content ratings for this program 723 * @param contentRatings the content ratings 724 * @return a reference to this object 725 */ setContentRatings(TvContentRating[] contentRatings)726 public Builder setContentRatings(TvContentRating[] contentRatings) { 727 mProgram.mContentRatings = contentRatings; 728 return this; 729 } 730 731 /** 732 * Sets the poster art URI 733 * @param posterArtUri the poster art URI 734 * @return a reference to this object 735 */ setPosterArtUri(String posterArtUri)736 public Builder setPosterArtUri(String posterArtUri) { 737 mProgram.mPosterArtUri = posterArtUri; 738 return this; 739 } 740 741 /** 742 * Sets the thumbnail URI 743 * @param thumbnailUri the thumbnail URI 744 * @return a reference to this object 745 */ setThumbnailUri(String thumbnailUri)746 public Builder setThumbnailUri(String thumbnailUri) { 747 mProgram.mThumbnailUri = thumbnailUri; 748 return this; 749 } 750 751 /** 752 * Sets the canonical genres by id 753 * @param genres the genres 754 * @return a reference to this object 755 */ setCanonicalGenres(String genres)756 public Builder setCanonicalGenres(String genres) { 757 mProgram.mCanonicalGenreIds = Utils.getCanonicalGenreIds(genres); 758 return this; 759 } 760 761 /** 762 * Sets the recording prohibited flag 763 * @param recordingProhibited recording prohibited flag 764 * @return a reference to this object 765 */ setRecordingProhibited(boolean recordingProhibited)766 public Builder setRecordingProhibited(boolean recordingProhibited) { 767 mProgram.mRecordingProhibited = recordingProhibited; 768 return this; 769 } 770 771 /** 772 * Adds a critic score 773 * @param criticScore the critic score 774 * @return a reference to this object 775 */ addCriticScore(CriticScore criticScore)776 public Builder addCriticScore(CriticScore criticScore) { 777 if (criticScore.score != null) { 778 if (mProgram.mCriticScores == null) { 779 mProgram.mCriticScores = new ArrayList<>(); 780 } 781 mProgram.mCriticScores.add(criticScore); 782 } 783 return this; 784 } 785 786 /** 787 * Sets the critic scores 788 * @param criticScores the critic scores 789 * @return a reference to this objects 790 */ setCriticScores(List<CriticScore> criticScores)791 public Builder setCriticScores(List<CriticScore> criticScores) { 792 mProgram.mCriticScores = criticScores; 793 return this; 794 } 795 796 /** 797 * Returns a reference to the Program object being constructed 798 * @return the Program object constructed 799 */ build()800 public Program build() { 801 // Generate the series ID for the episodic program of other TV input. 802 if (TextUtils.isEmpty(mProgram.mSeriesId) 803 && !TextUtils.isEmpty(mProgram.mEpisodeNumber)) { 804 setSeriesId(BaseProgram.generateSeriesId(mProgram.mPackageName, mProgram.mTitle)); 805 } 806 Program program = new Program(); 807 program.copyFrom(mProgram); 808 return program; 809 } 810 } 811 812 /** 813 * Prefetches the program poster art.<p> 814 */ prefetchPosterArt(Context context, int posterArtWidth, int posterArtHeight)815 public void prefetchPosterArt(Context context, int posterArtWidth, int posterArtHeight) { 816 if (mPosterArtUri == null) { 817 return; 818 } 819 ImageLoader.prefetchBitmap(context, mPosterArtUri, posterArtWidth, posterArtHeight); 820 } 821 822 /** 823 * Loads the program poster art and returns it via {@code callback}.<p> 824 * <p> 825 * Note that it may directly call {@code callback} if the program poster art already is loaded. 826 */ 827 @UiThread loadPosterArt(Context context, int posterArtWidth, int posterArtHeight, ImageLoader.ImageLoaderCallback callback)828 public void loadPosterArt(Context context, int posterArtWidth, int posterArtHeight, 829 ImageLoader.ImageLoaderCallback callback) { 830 if (mPosterArtUri == null) { 831 return; 832 } 833 ImageLoader.loadBitmap(context, mPosterArtUri, posterArtWidth, posterArtHeight, callback); 834 } 835 isDuplicate(Program p1, Program p2)836 public static boolean isDuplicate(Program p1, Program p2) { 837 if (p1 == null || p2 == null) { 838 return false; 839 } 840 boolean isDuplicate = p1.getChannelId() == p2.getChannelId() 841 && p1.getStartTimeUtcMillis() == p2.getStartTimeUtcMillis() 842 && p1.getEndTimeUtcMillis() == p2.getEndTimeUtcMillis(); 843 if (DEBUG && BuildConfig.ENG && isDuplicate) { 844 Log.w(TAG, "Duplicate programs detected! - \"" + p1.getTitle() + "\" and \"" 845 + p2.getTitle() + "\""); 846 } 847 return isDuplicate; 848 } 849 850 @Override describeContents()851 public int describeContents() { 852 return 0; 853 } 854 855 @Override writeToParcel(Parcel out, int paramInt)856 public void writeToParcel(Parcel out, int paramInt) { 857 out.writeLong(mId); 858 out.writeString(mPackageName); 859 out.writeLong(mChannelId); 860 out.writeString(mTitle); 861 out.writeString(mSeriesId); 862 out.writeString(mEpisodeTitle); 863 out.writeString(mSeasonNumber); 864 out.writeString(mSeasonTitle); 865 out.writeString(mEpisodeNumber); 866 out.writeLong(mStartTimeUtcMillis); 867 out.writeLong(mEndTimeUtcMillis); 868 out.writeString(mDescription); 869 out.writeString(mLongDescription); 870 out.writeInt(mVideoWidth); 871 out.writeInt(mVideoHeight); 872 out.writeList(mCriticScores); 873 out.writeString(mPosterArtUri); 874 out.writeString(mThumbnailUri); 875 out.writeIntArray(mCanonicalGenreIds); 876 out.writeInt(mContentRatings == null ? 0 : mContentRatings.length); 877 if (mContentRatings != null) { 878 for (TvContentRating rating : mContentRatings) { 879 out.writeString(rating.flattenToString()); 880 } 881 } 882 out.writeByte((byte) (mRecordingProhibited ? 1 : 0)); 883 } 884 885 /** 886 * Holds one type of critic score and its source. 887 */ 888 public static final class CriticScore implements Serializable, Parcelable { 889 /** 890 * The source of the rating. 891 */ 892 public final String source; 893 /** 894 * The score. 895 */ 896 public final String score; 897 /** 898 * The url of the logo image 899 */ 900 public final String logoUrl; 901 902 public static final Parcelable.Creator<CriticScore> CREATOR = 903 new Parcelable.Creator<CriticScore>() { 904 @Override 905 public CriticScore createFromParcel(Parcel in) { 906 String source = in.readString(); 907 String score = in.readString(); 908 String logoUri = in.readString(); 909 return new CriticScore(source, score, logoUri); 910 } 911 912 @Override 913 public CriticScore[] newArray(int size) { 914 return new CriticScore[size]; 915 } 916 }; 917 918 /** 919 * Constructor for this class. 920 * @param source the source of the rating 921 * @param score the score 922 */ CriticScore(String source, String score, String logoUrl)923 public CriticScore(String source, String score, String logoUrl) { 924 this.source = source; 925 this.score = score; 926 this.logoUrl = logoUrl; 927 } 928 929 @Override describeContents()930 public int describeContents() { 931 return 0; 932 } 933 934 @Override writeToParcel(Parcel out, int i)935 public void writeToParcel(Parcel out, int i) { 936 out.writeString(source); 937 out.writeString(score); 938 out.writeString(logoUrl); 939 } 940 } 941 } 942