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