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