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.ui.browse; 18 19 import android.content.Context; 20 import android.media.tv.TvContract; 21 import android.support.annotation.Nullable; 22 import android.text.TextUtils; 23 import com.android.tv.R; 24 import com.android.tv.TvSingletons; 25 import com.android.tv.data.Program; 26 import com.android.tv.data.api.Channel; 27 import com.android.tv.dvr.data.RecordedProgram; 28 import com.android.tv.dvr.data.ScheduledRecording; 29 import com.android.tv.dvr.data.SeriesRecording; 30 import com.android.tv.dvr.ui.DvrUiHelper; 31 32 /** A class for details content. */ 33 public class DetailsContent { 34 /** Constant for invalid time. */ 35 public static final long INVALID_TIME = -1; 36 37 private CharSequence mTitle; 38 private long mStartTimeUtcMillis; 39 private long mEndTimeUtcMillis; 40 private String mDescription; 41 private String mLogoImageUri; 42 private String mBackgroundImageUri; 43 private boolean mUsingChannelLogo; 44 private boolean mShowErrorMessage; 45 createFromRecordedProgram( Context context, RecordedProgram recordedProgram)46 static DetailsContent createFromRecordedProgram( 47 Context context, RecordedProgram recordedProgram) { 48 return new DetailsContent.Builder() 49 .setChannelId(recordedProgram.getChannelId()) 50 .setProgramTitle(recordedProgram.getTitle()) 51 .setSeasonNumber(recordedProgram.getSeasonNumber()) 52 .setEpisodeNumber(recordedProgram.getEpisodeNumber()) 53 .setStartTimeUtcMillis(recordedProgram.getStartTimeUtcMillis()) 54 .setEndTimeUtcMillis(recordedProgram.getEndTimeUtcMillis()) 55 .setDescription( 56 TextUtils.isEmpty(recordedProgram.getLongDescription()) 57 ? recordedProgram.getDescription() 58 : recordedProgram.getLongDescription()) 59 .setPosterArtUri(recordedProgram.getPosterArtUri()) 60 .setThumbnailUri(recordedProgram.getThumbnailUri()) 61 .build(context); 62 } 63 createFromProgram(Context context, Program program)64 public static DetailsContent createFromProgram(Context context, Program program) { 65 return new DetailsContent.Builder() 66 .setChannelId(program.getChannelId()) 67 .setProgramTitle(program.getTitle()) 68 .setSeasonNumber(program.getSeasonNumber()) 69 .setEpisodeNumber(program.getEpisodeNumber()) 70 .setStartTimeUtcMillis(program.getStartTimeUtcMillis()) 71 .setEndTimeUtcMillis(program.getEndTimeUtcMillis()) 72 .setDescription( 73 TextUtils.isEmpty(program.getLongDescription()) 74 ? program.getDescription() 75 : program.getLongDescription()) 76 .setPosterArtUri(program.getPosterArtUri()) 77 .setThumbnailUri(program.getThumbnailUri()) 78 .build(context); 79 } 80 createFromSeriesRecording( Context context, SeriesRecording seriesRecording)81 static DetailsContent createFromSeriesRecording( 82 Context context, SeriesRecording seriesRecording) { 83 return new DetailsContent.Builder() 84 .setChannelId(seriesRecording.getChannelId()) 85 .setTitle(seriesRecording.getTitle()) 86 .setDescription( 87 TextUtils.isEmpty(seriesRecording.getLongDescription()) 88 ? seriesRecording.getDescription() 89 : seriesRecording.getLongDescription()) 90 .setPosterArtUri(seriesRecording.getPosterUri()) 91 .setThumbnailUri(seriesRecording.getPhotoUri()) 92 .build(context); 93 } 94 createFromScheduledRecording( Context context, ScheduledRecording scheduledRecording)95 static DetailsContent createFromScheduledRecording( 96 Context context, ScheduledRecording scheduledRecording) { 97 Channel channel = 98 TvSingletons.getSingletons(context) 99 .getChannelDataManager() 100 .getChannel(scheduledRecording.getChannelId()); 101 String description; 102 if (scheduledRecording.getState() == ScheduledRecording.STATE_RECORDING_FAILED) { 103 description = getErrorMessage(context, scheduledRecording); 104 } else { 105 description = 106 !TextUtils.isEmpty(scheduledRecording.getProgramDescription()) 107 ? scheduledRecording.getProgramDescription() 108 : scheduledRecording.getProgramLongDescription(); 109 } 110 if (TextUtils.isEmpty(description)) { 111 description = channel != null ? channel.getDescription() : null; 112 } 113 return new DetailsContent.Builder() 114 .setChannelId(scheduledRecording.getChannelId()) 115 .setProgramTitle(scheduledRecording.getProgramTitle()) 116 .setSeasonNumber(scheduledRecording.getSeasonNumber()) 117 .setEpisodeNumber(scheduledRecording.getEpisodeNumber()) 118 .setStartTimeUtcMillis(scheduledRecording.getStartTimeMs()) 119 .setEndTimeUtcMillis(scheduledRecording.getEndTimeMs()) 120 .setDescription(description) 121 .setPosterArtUri(scheduledRecording.getProgramPosterArtUri()) 122 .setThumbnailUri(scheduledRecording.getProgramThumbnailUri()) 123 .setShowErrorMessage( 124 scheduledRecording.getState() == ScheduledRecording.STATE_RECORDING_FAILED) 125 .build(context); 126 } 127 getErrorMessage(Context context, ScheduledRecording recording)128 private static String getErrorMessage(Context context, ScheduledRecording recording) { 129 int reason = recording.getFailedReason() == null 130 ? ScheduledRecording.FAILED_REASON_OTHER 131 : recording.getFailedReason(); 132 switch (reason) { 133 case ScheduledRecording.FAILED_REASON_PROGRAM_ENDED_BEFORE_RECORDING_STARTED: 134 return context.getString(R.string.dvr_recording_failed_not_started); 135 case ScheduledRecording.FAILED_REASON_RESOURCE_BUSY: 136 return context.getString(R.string.dvr_recording_failed_resource_busy); 137 case ScheduledRecording.FAILED_REASON_INPUT_UNAVAILABLE: 138 return context.getString( 139 R.string.dvr_recording_failed_input_unavailable, 140 recording.getInputId()); 141 case ScheduledRecording.FAILED_REASON_INPUT_DVR_UNSUPPORTED: 142 return context.getString(R.string.dvr_recording_failed_input_dvr_unsupported); 143 case ScheduledRecording.FAILED_REASON_INSUFFICIENT_SPACE: 144 return context.getString(R.string.dvr_recording_failed_insufficient_space); 145 case ScheduledRecording.FAILED_REASON_OTHER: // fall through 146 case ScheduledRecording.FAILED_REASON_NOT_FINISHED: // fall through 147 case ScheduledRecording.FAILED_REASON_SCHEDULER_STOPPED: // fall through 148 case ScheduledRecording.FAILED_REASON_INVALID_CHANNEL: // fall through 149 case ScheduledRecording.FAILED_REASON_MESSAGE_NOT_SENT: // fall through 150 case ScheduledRecording.FAILED_REASON_CONNECTION_FAILED: // fall through 151 default: 152 return context.getString(R.string.dvr_recording_failed_system_failure, reason); 153 } 154 } 155 DetailsContent()156 private DetailsContent() {} 157 158 /** Returns title. */ getTitle()159 public CharSequence getTitle() { 160 return mTitle; 161 } 162 163 /** Returns start time. */ getStartTimeUtcMillis()164 public long getStartTimeUtcMillis() { 165 return mStartTimeUtcMillis; 166 } 167 168 /** Returns end time. */ getEndTimeUtcMillis()169 public long getEndTimeUtcMillis() { 170 return mEndTimeUtcMillis; 171 } 172 173 /** Returns description. */ getDescription()174 public String getDescription() { 175 return mDescription; 176 } 177 178 /** Returns Logo image URI as a String. */ getLogoImageUri()179 public String getLogoImageUri() { 180 return mLogoImageUri; 181 } 182 183 /** Returns background image URI as a String. */ getBackgroundImageUri()184 public String getBackgroundImageUri() { 185 return mBackgroundImageUri; 186 } 187 188 /** Returns if image URIs are from its channels' logo. */ isUsingChannelLogo()189 public boolean isUsingChannelLogo() { 190 return mUsingChannelLogo; 191 } 192 193 /** Returns if the error message should be shown. */ shouldShowErrorMessage()194 public boolean shouldShowErrorMessage() { 195 return mShowErrorMessage; 196 } 197 198 /** Copies other details content. */ copyFrom(DetailsContent other)199 public void copyFrom(DetailsContent other) { 200 if (this == other) { 201 return; 202 } 203 mTitle = other.mTitle; 204 mStartTimeUtcMillis = other.mStartTimeUtcMillis; 205 mEndTimeUtcMillis = other.mEndTimeUtcMillis; 206 mDescription = other.mDescription; 207 mLogoImageUri = other.mLogoImageUri; 208 mBackgroundImageUri = other.mBackgroundImageUri; 209 mUsingChannelLogo = other.mUsingChannelLogo; 210 mShowErrorMessage = other.mShowErrorMessage; 211 } 212 213 /** A class for building details content. */ 214 public static final class Builder { 215 private final DetailsContent mDetailsContent; 216 217 private long mChannelId; 218 private String mProgramTitle; 219 private String mSeasonNumber; 220 private String mEpisodeNumber; 221 private String mPosterArtUri; 222 private String mThumbnailUri; 223 Builder()224 public Builder() { 225 mDetailsContent = new DetailsContent(); 226 mDetailsContent.mStartTimeUtcMillis = INVALID_TIME; 227 mDetailsContent.mEndTimeUtcMillis = INVALID_TIME; 228 } 229 230 /** Sets title. */ setTitle(CharSequence title)231 public Builder setTitle(CharSequence title) { 232 mDetailsContent.mTitle = title; 233 return this; 234 } 235 236 /** Sets start time. */ setStartTimeUtcMillis(long startTimeUtcMillis)237 public Builder setStartTimeUtcMillis(long startTimeUtcMillis) { 238 mDetailsContent.mStartTimeUtcMillis = startTimeUtcMillis; 239 return this; 240 } 241 242 /** Sets end time. */ setEndTimeUtcMillis(long endTimeUtcMillis)243 public Builder setEndTimeUtcMillis(long endTimeUtcMillis) { 244 mDetailsContent.mEndTimeUtcMillis = endTimeUtcMillis; 245 return this; 246 } 247 248 /** Sets description. */ setDescription(String description)249 public Builder setDescription(String description) { 250 mDetailsContent.mDescription = description; 251 return this; 252 } 253 254 /** Sets logo image URI as a String. */ setLogoImageUri(String logoImageUri)255 public Builder setLogoImageUri(String logoImageUri) { 256 mDetailsContent.mLogoImageUri = logoImageUri; 257 return this; 258 } 259 260 /** Sets background image URI as a String. */ setBackgroundImageUri(String backgroundImageUri)261 public Builder setBackgroundImageUri(String backgroundImageUri) { 262 mDetailsContent.mBackgroundImageUri = backgroundImageUri; 263 return this; 264 } 265 setProgramTitle(String programTitle)266 private Builder setProgramTitle(String programTitle) { 267 mProgramTitle = programTitle; 268 return this; 269 } 270 setSeasonNumber(String seasonNumber)271 private Builder setSeasonNumber(String seasonNumber) { 272 mSeasonNumber = seasonNumber; 273 return this; 274 } 275 setEpisodeNumber(String episodeNumber)276 private Builder setEpisodeNumber(String episodeNumber) { 277 mEpisodeNumber = episodeNumber; 278 return this; 279 } 280 setChannelId(long channelId)281 private Builder setChannelId(long channelId) { 282 mChannelId = channelId; 283 return this; 284 } 285 setPosterArtUri(String posterArtUri)286 private Builder setPosterArtUri(String posterArtUri) { 287 mPosterArtUri = posterArtUri; 288 return this; 289 } 290 setThumbnailUri(String thumbnailUri)291 private Builder setThumbnailUri(String thumbnailUri) { 292 mThumbnailUri = thumbnailUri; 293 return this; 294 } 295 setShowErrorMessage(boolean showErrorMessage)296 private Builder setShowErrorMessage(boolean showErrorMessage) { 297 mDetailsContent.mShowErrorMessage = showErrorMessage; 298 return this; 299 } 300 createStyledTitle(Context context, Channel channel)301 private void createStyledTitle(Context context, Channel channel) { 302 CharSequence title = 303 DvrUiHelper.getStyledTitleWithEpisodeNumber( 304 context, 305 mProgramTitle, 306 mSeasonNumber, 307 mEpisodeNumber, 308 R.style.text_appearance_card_view_episode_number); 309 if (TextUtils.isEmpty(title)) { 310 mDetailsContent.mTitle = 311 channel != null 312 ? channel.getDisplayName() 313 : context.getResources().getString(R.string.no_program_information); 314 } else { 315 mDetailsContent.mTitle = title; 316 } 317 } 318 createImageUris(@ullable Channel channel)319 private void createImageUris(@Nullable Channel channel) { 320 mDetailsContent.mLogoImageUri = null; 321 mDetailsContent.mBackgroundImageUri = null; 322 mDetailsContent.mUsingChannelLogo = false; 323 if (!TextUtils.isEmpty(mPosterArtUri) && !TextUtils.isEmpty(mThumbnailUri)) { 324 mDetailsContent.mLogoImageUri = mPosterArtUri; 325 mDetailsContent.mBackgroundImageUri = mThumbnailUri; 326 } else if (!TextUtils.isEmpty(mPosterArtUri)) { 327 // thumbnailUri is empty 328 mDetailsContent.mLogoImageUri = mPosterArtUri; 329 mDetailsContent.mBackgroundImageUri = mPosterArtUri; 330 } else if (!TextUtils.isEmpty(mThumbnailUri)) { 331 // posterArtUri is empty 332 mDetailsContent.mLogoImageUri = mThumbnailUri; 333 mDetailsContent.mBackgroundImageUri = mThumbnailUri; 334 } 335 if (TextUtils.isEmpty(mDetailsContent.mLogoImageUri) && channel != null) { 336 String channelLogoUri = TvContract.buildChannelLogoUri(channel.getId()).toString(); 337 mDetailsContent.mLogoImageUri = channelLogoUri; 338 mDetailsContent.mBackgroundImageUri = channelLogoUri; 339 mDetailsContent.mUsingChannelLogo = true; 340 } 341 } 342 343 /** Builds details content. */ build(Context context)344 public DetailsContent build(Context context) { 345 Channel channel = 346 TvSingletons.getSingletons(context) 347 .getChannelDataManager() 348 .getChannel(mChannelId); 349 if (mDetailsContent.mTitle == null) { 350 createStyledTitle(context, channel); 351 } 352 if (mDetailsContent.mBackgroundImageUri == null 353 && mDetailsContent.mLogoImageUri == null) { 354 createImageUris(channel); 355 } 356 DetailsContent detailsContent = new DetailsContent(); 357 detailsContent.copyFrom(mDetailsContent); 358 return detailsContent; 359 } 360 } 361 } 362