• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.data;
18 
19 import android.annotation.TargetApi;
20 import android.content.ContentUris;
21 import android.content.ContentValues;
22 import android.content.Context;
23 import android.database.Cursor;
24 import android.media.tv.TvContentRating;
25 import android.media.tv.TvContract.Programs.Genres;
26 import android.media.tv.TvContract.RecordedPrograms;
27 import android.net.Uri;
28 import android.os.Build;
29 import android.support.annotation.CheckResult;
30 import android.support.annotation.Nullable;
31 import android.support.annotation.WorkerThread;
32 import android.text.TextUtils;
33 import android.util.Log;
34 import com.android.tv.common.R;
35 import com.android.tv.common.TvContentRatingCache;
36 import com.android.tv.common.data.RecordedProgramState;
37 import com.android.tv.common.util.CommonUtils;
38 import com.android.tv.common.util.StringUtils;
39 import com.android.tv.data.BaseProgram;
40 import com.android.tv.data.GenreItems;
41 import com.android.tv.data.InternalDataUtils;
42 import com.android.tv.util.TvProviderUtils;
43 import com.google.auto.value.AutoValue;
44 import com.google.common.collect.ImmutableList;
45 import java.util.Collection;
46 import java.util.Comparator;
47 import java.util.concurrent.TimeUnit;
48 
49 /** Immutable instance of {@link android.media.tv.TvContract.RecordedPrograms}. */
50 @TargetApi(Build.VERSION_CODES.N)
51 @AutoValue
52 public abstract class RecordedProgram extends BaseProgram {
53     public static final int ID_NOT_SET = -1;
54     private static final String TAG = "RecordedProgram";
55 
56     public static final String[] PROJECTION = {
57         RecordedPrograms._ID,
58         RecordedPrograms.COLUMN_PACKAGE_NAME,
59         RecordedPrograms.COLUMN_INPUT_ID,
60         RecordedPrograms.COLUMN_CHANNEL_ID,
61         RecordedPrograms.COLUMN_TITLE,
62         RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER,
63         RecordedPrograms.COLUMN_SEASON_TITLE,
64         RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER,
65         RecordedPrograms.COLUMN_EPISODE_TITLE,
66         RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS,
67         RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS,
68         RecordedPrograms.COLUMN_BROADCAST_GENRE,
69         RecordedPrograms.COLUMN_CANONICAL_GENRE,
70         RecordedPrograms.COLUMN_SHORT_DESCRIPTION,
71         RecordedPrograms.COLUMN_LONG_DESCRIPTION,
72         RecordedPrograms.COLUMN_VIDEO_WIDTH,
73         RecordedPrograms.COLUMN_VIDEO_HEIGHT,
74         RecordedPrograms.COLUMN_AUDIO_LANGUAGE,
75         RecordedPrograms.COLUMN_CONTENT_RATING,
76         RecordedPrograms.COLUMN_POSTER_ART_URI,
77         RecordedPrograms.COLUMN_THUMBNAIL_URI,
78         RecordedPrograms.COLUMN_SEARCHABLE,
79         RecordedPrograms.COLUMN_RECORDING_DATA_URI,
80         RecordedPrograms.COLUMN_RECORDING_DATA_BYTES,
81         RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS,
82         RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS,
83         RecordedPrograms.COLUMN_VERSION_NUMBER,
84         RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA,
85     };
86 
fromCursor(Cursor cursor)87     public static RecordedProgram fromCursor(Cursor cursor) {
88         int index = 0;
89         Builder builder =
90                 builder()
91                         .setId(cursor.getLong(index++))
92                         .setPackageName(cursor.getString(index++))
93                         .setInputId(cursor.getString(index++))
94                         .setChannelId(cursor.getLong(index++))
95                         .setTitle(StringUtils.nullToEmpty(cursor.getString(index++)))
96                         .setSeasonNumber(StringUtils.nullToEmpty(cursor.getString(index++)))
97                         .setSeasonTitle(StringUtils.nullToEmpty(cursor.getString(index++)))
98                         .setEpisodeNumber(StringUtils.nullToEmpty(cursor.getString(index++)))
99                         .setEpisodeTitle(StringUtils.nullToEmpty(cursor.getString(index++)))
100                         .setStartTimeUtcMillis(cursor.getLong(index++))
101                         .setEndTimeUtcMillis(cursor.getLong(index++))
102                         .setBroadcastGenres(cursor.getString(index++))
103                         .setCanonicalGenres(cursor.getString(index++))
104                         .setDescription(StringUtils.nullToEmpty(cursor.getString(index++)))
105                         .setLongDescription(StringUtils.nullToEmpty(cursor.getString(index++)))
106                         .setVideoWidth(cursor.getInt(index++))
107                         .setVideoHeight(cursor.getInt(index++))
108                         .setAudioLanguage(StringUtils.nullToEmpty(cursor.getString(index++)))
109                         .setContentRatings(
110                                 TvContentRatingCache.getInstance()
111                                         .getRatings(cursor.getString(index++)))
112                         .setPosterArtUri(StringUtils.nullToEmpty(cursor.getString(index++)))
113                         .setThumbnailUri(StringUtils.nullToEmpty(cursor.getString(index++)))
114                         .setSearchable(cursor.getInt(index++) == 1)
115                         .setDataUri(cursor.getString(index++))
116                         .setDataBytes(cursor.getLong(index++))
117                         .setDurationMillis(cursor.getLong(index++))
118                         .setExpireTimeUtcMillis(cursor.getLong(index++))
119                         .setVersionNumber(cursor.getInt(index++));
120         if (CommonUtils.isInBundledPackageSet(builder.getPackageName())) {
121             InternalDataUtils.deserializeInternalProviderData(cursor.getBlob(index), builder);
122         }
123         index++;
124         if (TvProviderUtils.getRecordedProgramHasSeriesIdColumn()) {
125             builder.setSeriesId(StringUtils.nullToEmpty(cursor.getString(index++)));
126         }
127         if (TvProviderUtils.getRecordedProgramHasStateColumn()) {
128             builder.setState(cursor.getString(index++));
129         }
130         return builder.build();
131     }
132 
133     @WorkerThread
toValues(Context context, RecordedProgram recordedProgram)134     public static ContentValues toValues(Context context, RecordedProgram recordedProgram) {
135         ContentValues values = new ContentValues();
136         if (recordedProgram.getId() != ID_NOT_SET) {
137             values.put(RecordedPrograms._ID, recordedProgram.getId());
138         }
139         values.put(RecordedPrograms.COLUMN_INPUT_ID, recordedProgram.getInputId());
140         values.put(RecordedPrograms.COLUMN_CHANNEL_ID, recordedProgram.getChannelId());
141         values.put(RecordedPrograms.COLUMN_TITLE, recordedProgram.getTitle());
142         values.put(
143                 RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER, recordedProgram.getSeasonNumber());
144         values.put(RecordedPrograms.COLUMN_SEASON_TITLE, recordedProgram.getSeasonTitle());
145         values.put(
146                 RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER, recordedProgram.getEpisodeNumber());
147         values.put(RecordedPrograms.COLUMN_EPISODE_TITLE, recordedProgram.getEpisodeTitle());
148         values.put(
149                 RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS,
150                 recordedProgram.getStartTimeUtcMillis());
151         values.put(
152                 RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS, recordedProgram.getEndTimeUtcMillis());
153         values.put(
154                 RecordedPrograms.COLUMN_BROADCAST_GENRE,
155                 safeEncode(recordedProgram.getBroadcastGenres()));
156         values.put(
157                 RecordedPrograms.COLUMN_CANONICAL_GENRE,
158                 safeEncode(recordedProgram.getCanonicalGenres()));
159         values.put(RecordedPrograms.COLUMN_SHORT_DESCRIPTION, recordedProgram.getDescription());
160         values.put(RecordedPrograms.COLUMN_LONG_DESCRIPTION, recordedProgram.getLongDescription());
161         if (recordedProgram.getVideoWidth() == 0) {
162             values.putNull(RecordedPrograms.COLUMN_VIDEO_WIDTH);
163         } else {
164             values.put(RecordedPrograms.COLUMN_VIDEO_WIDTH, recordedProgram.getVideoWidth());
165         }
166         if (recordedProgram.getVideoHeight() == 0) {
167             values.putNull(RecordedPrograms.COLUMN_VIDEO_HEIGHT);
168         } else {
169             values.put(RecordedPrograms.COLUMN_VIDEO_HEIGHT, recordedProgram.getVideoHeight());
170         }
171         values.put(RecordedPrograms.COLUMN_AUDIO_LANGUAGE, recordedProgram.getAudioLanguage());
172         values.put(
173                 RecordedPrograms.COLUMN_CONTENT_RATING,
174                 TvContentRatingCache.contentRatingsToString(recordedProgram.getContentRatings()));
175         values.put(RecordedPrograms.COLUMN_POSTER_ART_URI, recordedProgram.getPosterArtUri());
176         values.put(RecordedPrograms.COLUMN_THUMBNAIL_URI, recordedProgram.getThumbnailUri());
177         values.put(RecordedPrograms.COLUMN_SEARCHABLE, recordedProgram.isSearchable() ? 1 : 0);
178         values.put(
179                 RecordedPrograms.COLUMN_RECORDING_DATA_URI,
180                 safeToString(recordedProgram.getDataUri()));
181         values.put(RecordedPrograms.COLUMN_RECORDING_DATA_BYTES, recordedProgram.getDataBytes());
182         values.put(
183                 RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS,
184                 recordedProgram.getDurationMillis());
185         values.put(
186                 RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS,
187                 recordedProgram.getExpireTimeUtcMillis());
188         values.put(
189                 RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA,
190                 InternalDataUtils.serializeInternalProviderData(recordedProgram));
191         values.put(RecordedPrograms.COLUMN_VERSION_NUMBER, recordedProgram.getVersionNumber());
192         if (TvProviderUtils.checkSeriesIdColumn(context, RecordedPrograms.CONTENT_URI)) {
193             values.put(COLUMN_SERIES_ID, recordedProgram.getSeriesId());
194         }
195         if (TvProviderUtils.checkStateColumn(context, RecordedPrograms.CONTENT_URI)) {
196             values.put(COLUMN_STATE, recordedProgram.getState().toString());
197         }
198         return values;
199     }
200 
201     /** Builder for {@link RecordedProgram}s. */
202     @AutoValue.Builder
203     public abstract static class Builder {
204 
setId(long id)205         public abstract Builder setId(long id);
206 
setPackageName(String packageName)207         public abstract Builder setPackageName(String packageName);
208 
getPackageName()209         abstract String getPackageName();
210 
setInputId(String inputId)211         public abstract Builder setInputId(String inputId);
212 
setChannelId(long channelId)213         public abstract Builder setChannelId(long channelId);
214 
getTitle()215         abstract String getTitle();
216 
setTitle(String title)217         public abstract Builder setTitle(String title);
218 
getSeriesId()219         abstract String getSeriesId();
220 
setSeriesId(String seriesId)221         public abstract Builder setSeriesId(String seriesId);
222 
setSeasonNumber(String seasonNumber)223         public abstract Builder setSeasonNumber(String seasonNumber);
224 
setSeasonTitle(String seasonTitle)225         public abstract Builder setSeasonTitle(String seasonTitle);
226 
227         @Nullable
getEpisodeNumber()228         abstract String getEpisodeNumber();
229 
setEpisodeNumber(String episodeNumber)230         public abstract Builder setEpisodeNumber(String episodeNumber);
231 
setEpisodeTitle(String episodeTitle)232         public abstract Builder setEpisodeTitle(String episodeTitle);
233 
setStartTimeUtcMillis(long startTimeUtcMillis)234         public abstract Builder setStartTimeUtcMillis(long startTimeUtcMillis);
235 
setEndTimeUtcMillis(long endTimeUtcMillis)236         public abstract Builder setEndTimeUtcMillis(long endTimeUtcMillis);
237 
setState(RecordedProgramState state)238         public abstract Builder setState(RecordedProgramState state);
239 
setState(@ullable String state)240         public Builder setState(@Nullable String state) {
241 
242             if (!TextUtils.isEmpty(state)) {
243                 try {
244                     return setState(RecordedProgramState.valueOf(state));
245                 } catch (IllegalArgumentException e) {
246                     Log.w(TAG, "Unknown recording state " + state, e);
247                 }
248             }
249             return setState(RecordedProgramState.NOT_SET);
250         }
251 
setBroadcastGenres(@ullable String broadcastGenres)252         public Builder setBroadcastGenres(@Nullable String broadcastGenres) {
253             return setBroadcastGenres(
254                     TextUtils.isEmpty(broadcastGenres)
255                             ? ImmutableList.of()
256                             : ImmutableList.copyOf(Genres.decode(broadcastGenres)));
257         }
258 
setBroadcastGenres(ImmutableList<String> broadcastGenres)259         public abstract Builder setBroadcastGenres(ImmutableList<String> broadcastGenres);
260 
setCanonicalGenres(String canonicalGenres)261         public Builder setCanonicalGenres(String canonicalGenres) {
262             return setCanonicalGenres(
263                     TextUtils.isEmpty(canonicalGenres)
264                             ? ImmutableList.of()
265                             : ImmutableList.copyOf(Genres.decode(canonicalGenres)));
266         }
267 
setCanonicalGenres(ImmutableList<String> canonicalGenres)268         public abstract Builder setCanonicalGenres(ImmutableList<String> canonicalGenres);
269 
setDescription(String shortDescription)270         public abstract Builder setDescription(String shortDescription);
271 
setLongDescription(String longDescription)272         public abstract Builder setLongDescription(String longDescription);
273 
setVideoWidth(int videoWidth)274         public abstract Builder setVideoWidth(int videoWidth);
275 
setVideoHeight(int videoHeight)276         public abstract Builder setVideoHeight(int videoHeight);
277 
setAudioLanguage(String audioLanguage)278         public abstract Builder setAudioLanguage(String audioLanguage);
279 
setContentRatings(ImmutableList<TvContentRating> contentRatings)280         public abstract Builder setContentRatings(ImmutableList<TvContentRating> contentRatings);
281 
toUri(@ullable String uriString)282         private Uri toUri(@Nullable String uriString) {
283             try {
284                 return uriString == null ? null : Uri.parse(uriString);
285             } catch (Exception e) {
286                 return Uri.EMPTY;
287             }
288         }
289 
setPosterArtUri(String posterArtUri)290         public abstract Builder setPosterArtUri(String posterArtUri);
291 
setThumbnailUri(String thumbnailUri)292         public abstract Builder setThumbnailUri(String thumbnailUri);
293 
setSearchable(boolean searchable)294         public abstract Builder setSearchable(boolean searchable);
295 
setDataUri(@ullable String dataUri)296         public Builder setDataUri(@Nullable String dataUri) {
297             return setDataUri(toUri(dataUri));
298         }
299 
setDataUri(Uri dataUri)300         public abstract Builder setDataUri(Uri dataUri);
301 
setDataBytes(long dataBytes)302         public abstract Builder setDataBytes(long dataBytes);
303 
setDurationMillis(long durationMillis)304         public abstract Builder setDurationMillis(long durationMillis);
305 
setExpireTimeUtcMillis(long expireTimeUtcMillis)306         public abstract Builder setExpireTimeUtcMillis(long expireTimeUtcMillis);
307 
setVersionNumber(int versionNumber)308         public abstract Builder setVersionNumber(int versionNumber);
309 
autoBuild()310         abstract RecordedProgram autoBuild();
311 
build()312         public RecordedProgram build() {
313             if (TextUtils.isEmpty(getTitle())) {
314                 // If title is null, series cannot be generated for this program.
315                 setSeriesId(null);
316             } else if (TextUtils.isEmpty(getSeriesId()) && !TextUtils.isEmpty(getEpisodeNumber())) {
317                 // If series ID is not set, generate it for the episodic program of other TV input.
318                 setSeriesId(BaseProgram.generateSeriesId(getPackageName(), getTitle()));
319             }
320             return (autoBuild());
321         }
322     }
323 
builder()324     public static Builder builder() {
325         return new AutoValue_RecordedProgram.Builder()
326                 .setId(ID_NOT_SET)
327                 .setChannelId(ID_NOT_SET)
328                 .setAudioLanguage("")
329                 .setBroadcastGenres("")
330                 .setCanonicalGenres("")
331                 .setContentRatings(ImmutableList.of())
332                 .setDataUri("")
333                 .setDurationMillis(0)
334                 .setDescription("")
335                 .setDataBytes(0)
336                 .setLongDescription("")
337                 .setEndTimeUtcMillis(0)
338                 .setEpisodeNumber("")
339                 .setEpisodeTitle("")
340                 .setExpireTimeUtcMillis(0)
341                 .setPackageName("")
342                 .setPosterArtUri("")
343                 .setSeasonNumber("")
344                 .setSeasonTitle("")
345                 .setSearchable(false)
346                 .setSeriesId("")
347                 .setStartTimeUtcMillis(0)
348                 .setState(RecordedProgramState.NOT_SET)
349                 .setThumbnailUri("")
350                 .setTitle("")
351                 .setVersionNumber(0)
352                 .setVideoHeight(0)
353                 .setVideoWidth(0);
354     }
355 
356     public static final Comparator<RecordedProgram> START_TIME_THEN_ID_COMPARATOR =
357             (RecordedProgram lhs, RecordedProgram rhs) -> {
358                 int res = Long.compare(lhs.getStartTimeUtcMillis(), rhs.getStartTimeUtcMillis());
359                 if (res != 0) {
360                     return res;
361                 }
362                 return Long.compare(lhs.getId(), rhs.getId());
363             };
364 
365     private static final long CLIPPED_THRESHOLD_MS = TimeUnit.MINUTES.toMillis(5);
366 
getAudioLanguage()367     public abstract String getAudioLanguage();
368 
getBroadcastGenres()369     public abstract ImmutableList<String> getBroadcastGenres();
370 
getCanonicalGenres()371     public abstract ImmutableList<String> getCanonicalGenres();
372 
373     /** Returns array of canonical genre ID's for this recorded program. */
374     @Override
getCanonicalGenreIds()375     public int[] getCanonicalGenreIds() {
376 
377         ImmutableList<String> canonicalGenres = getCanonicalGenres();
378         int[] genreIds = new int[getCanonicalGenres().size()];
379         for (int i = 0; i < canonicalGenres.size(); i++) {
380             genreIds[i] = GenreItems.getId(canonicalGenres.get(i));
381         }
382         return genreIds;
383     }
384 
getDataUri()385     public abstract Uri getDataUri();
386 
getDataBytes()387     public abstract long getDataBytes();
388 
389     @Nullable
getEpisodeDisplayNumber(Context context)390     public String getEpisodeDisplayNumber(Context context) {
391         if (!TextUtils.isEmpty(getEpisodeNumber())) {
392             if (TextUtils.equals(getSeasonNumber(), "0")) {
393                 // Do not show "S0: ".
394                 return context.getResources()
395                         .getString(
396                                 R.string.display_episode_number_format_no_season_number,
397                                 getEpisodeNumber());
398             } else {
399                 return context.getResources()
400                         .getString(
401                                 R.string.display_episode_number_format,
402                                 getSeasonNumber(),
403                                 getEpisodeNumber());
404             }
405         }
406         return null;
407     }
408 
getExpireTimeUtcMillis()409     public abstract long getExpireTimeUtcMillis();
410 
getPackageName()411     public abstract String getPackageName();
412 
getInputId()413     public abstract String getInputId();
414 
415     @Override
isValid()416     public boolean isValid() {
417         return true;
418     }
419 
isVisible()420     public boolean isVisible() {
421         switch (getState()) {
422             case NOT_SET:
423             case FINISHED:
424                 return true;
425             default:
426                 return false;
427         }
428     }
429 
isPartial()430     public boolean isPartial() {
431         return getState() == RecordedProgramState.PARTIAL;
432     }
433 
isSearchable()434     public abstract boolean isSearchable();
435 
getSeasonTitle()436     public abstract String getSeasonTitle();
437 
getState()438     public abstract RecordedProgramState getState();
439 
getUri()440     public Uri getUri() {
441         return ContentUris.withAppendedId(RecordedPrograms.CONTENT_URI, getId());
442     }
443 
getVersionNumber()444     public abstract int getVersionNumber();
445 
getVideoHeight()446     public abstract int getVideoHeight();
447 
getVideoWidth()448     public abstract int getVideoWidth();
449 
450     /** Checks whether the recording has been clipped or not. */
isClipped()451     public boolean isClipped() {
452         return getEndTimeUtcMillis() - getStartTimeUtcMillis() - getDurationMillis()
453                 > CLIPPED_THRESHOLD_MS;
454     }
455 
toBuilder()456     public abstract Builder toBuilder();
457 
458     @CheckResult
withId(long id)459     public RecordedProgram withId(long id) {
460         return toBuilder().setId(id).build();
461     }
462 
463     @Nullable
safeToString(@ullable Object o)464     private static String safeToString(@Nullable Object o) {
465         return o == null ? null : o.toString();
466     }
467 
468     @Nullable
safeEncode(@ullable ImmutableList<String> genres)469     private static String safeEncode(@Nullable ImmutableList<String> genres) {
470         return genres == null ? null : Genres.encode(genres.toArray(new String[0]));
471     }
472 
473     /** Returns an array containing all of the elements in the list. */
toArray(Collection<RecordedProgram> recordedPrograms)474     public static RecordedProgram[] toArray(Collection<RecordedProgram> recordedPrograms) {
475         return recordedPrograms.toArray(new RecordedProgram[recordedPrograms.size()]);
476     }
477 }
478