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