1/*
2 * Copyright (C) 2017 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 */
16package android.support.media.tv;
17
18import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
19
20import android.content.ContentValues;
21import android.database.Cursor;
22import android.media.tv.TvContentRating;
23import android.net.Uri;
24import android.os.Build;
25import android.support.annotation.RestrictTo;
26import android.support.media.tv.TvContractCompat.BaseTvColumns;
27import android.support.media.tv.TvContractCompat.ProgramColumns;
28import android.support.media.tv.TvContractCompat.ProgramColumns.ReviewRatingStyle;
29import android.support.media.tv.TvContractCompat.Programs;
30import android.support.media.tv.TvContractCompat.Programs.Genres.Genre;
31
32/**
33 * Base class for derived classes that want to have common fields for programs defined in
34 * {@link TvContractCompat}.
35 */
36public abstract class BaseProgram {
37    /**
38     * @hide
39     */
40    @RestrictTo(LIBRARY_GROUP)
41    public static final String[] PROJECTION = getProjection();
42
43    private static final long INVALID_LONG_VALUE = -1;
44    private static final int INVALID_INT_VALUE = -1;
45    private static final int IS_SEARCHABLE = 1;
46
47    /** @hide */
48    @RestrictTo(LIBRARY_GROUP)
49    protected ContentValues mValues;
50
51    /* package-private */
52    BaseProgram(Builder builder) {
53        mValues = builder.mValues;
54    }
55
56    /**
57     * @return The ID for the program.
58     * @see BaseTvColumns#_ID
59     */
60    public long getId() {
61        Long l = mValues.getAsLong(BaseTvColumns._ID);
62        return l == null ? INVALID_LONG_VALUE : l;
63    }
64
65    /**
66     * @return The package name for the program.
67     * @see BaseTvColumns#COLUMN_PACKAGE_NAME
68     * @hide
69     */
70    @RestrictTo(LIBRARY_GROUP)
71    public String getPackageName() {
72        return mValues.getAsString(BaseTvColumns.COLUMN_PACKAGE_NAME);
73    }
74
75    /**
76     * @return The title for the program.
77     * @see Programs#COLUMN_TITLE
78     */
79    public String getTitle() {
80        return mValues.getAsString(Programs.COLUMN_TITLE);
81    }
82
83    /**
84     * @return The episode title for the program.
85     * @see Programs#COLUMN_EPISODE_TITLE
86     */
87    public String getEpisodeTitle() {
88        return mValues.getAsString(Programs.COLUMN_EPISODE_TITLE);
89    }
90
91    /**
92     * @return The season display number for the program.
93     * @see Programs#COLUMN_SEASON_DISPLAY_NUMBER
94     */
95    public String getSeasonNumber() {
96        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
97            return mValues.getAsString(Programs.COLUMN_SEASON_DISPLAY_NUMBER);
98        } else {
99            return mValues.getAsString(Programs.COLUMN_SEASON_NUMBER);
100        }
101    }
102
103    /**
104     * @return The episode display number for the program.
105     * @see Programs#COLUMN_EPISODE_DISPLAY_NUMBER
106     */
107    public String getEpisodeNumber() {
108        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
109            return mValues.getAsString(Programs.COLUMN_EPISODE_DISPLAY_NUMBER);
110        } else {
111            return mValues.getAsString(Programs.COLUMN_EPISODE_NUMBER);
112        }
113    }
114
115    /**
116     * @return The short description for the program.
117     * @see Programs#COLUMN_SHORT_DESCRIPTION
118     */
119    public String getDescription() {
120        return mValues.getAsString(Programs.COLUMN_SHORT_DESCRIPTION);
121    }
122
123    /**
124     * @return The long description for the program.
125     * @see Programs#COLUMN_LONG_DESCRIPTION
126     */
127    public String getLongDescription() {
128        return mValues.getAsString(Programs.COLUMN_LONG_DESCRIPTION);
129    }
130
131    /**
132     * @return The video width for the program.
133     * @see Programs#COLUMN_VIDEO_WIDTH
134     */
135    public int getVideoWidth() {
136        Integer i = mValues.getAsInteger(Programs.COLUMN_VIDEO_WIDTH);
137        return i == null ? INVALID_INT_VALUE : i;
138    }
139
140    /**
141     * @return The video height for the program.
142     * @see Programs#COLUMN_VIDEO_HEIGHT
143     */
144    public int getVideoHeight() {
145        Integer i = mValues.getAsInteger(Programs.COLUMN_VIDEO_HEIGHT);
146        return i == null ? INVALID_INT_VALUE : i;
147    }
148
149    /**
150     * @return The canonical genre for the program.
151     * @see Programs#COLUMN_CANONICAL_GENRE
152     */
153    public @Genre String[] getCanonicalGenres() {
154        return Programs.Genres.decode(mValues.getAsString(Programs.COLUMN_CANONICAL_GENRE));
155    }
156
157    /**
158     * @return The content rating for the program.
159     * @see Programs#COLUMN_CONTENT_RATING
160     */
161    public TvContentRating[] getContentRatings() {
162        return TvContractUtils.stringToContentRatings(mValues.getAsString(
163                Programs.COLUMN_CONTENT_RATING));
164    }
165
166    /**
167     * @return The poster art URI for the program.
168     * @see Programs#COLUMN_POSTER_ART_URI
169     */
170    public Uri getPosterArtUri() {
171        String uri = mValues.getAsString(Programs.COLUMN_POSTER_ART_URI);
172        return uri == null ? null : Uri.parse(uri);
173    }
174
175    /**
176     * @return The thumbnail URI for the program.
177     * @see Programs#COLUMN_THUMBNAIL_URI
178     */
179    public Uri getThumbnailUri() {
180        String uri = mValues.getAsString(Programs.COLUMN_POSTER_ART_URI);
181        return uri == null ? null : Uri.parse(uri);
182    }
183
184    /**
185     * @return The internal provider data for the program.
186     * @see Programs#COLUMN_INTERNAL_PROVIDER_DATA
187     */
188    public byte[] getInternalProviderDataByteArray() {
189        return mValues.getAsByteArray(Programs.COLUMN_INTERNAL_PROVIDER_DATA);
190    }
191
192    /**
193     * @return The audio languages for the program.
194     * @see Programs#COLUMN_AUDIO_LANGUAGE
195     */
196    public String[] getAudioLanguages() {
197        return TvContractUtils.stringToAudioLanguages(mValues.getAsString(
198                Programs.COLUMN_AUDIO_LANGUAGE));
199    }
200
201    /**
202     * @return Whether the program is searchable or not.
203     * @see Programs#COLUMN_SEARCHABLE
204     */
205    public boolean isSearchable() {
206        Integer i = mValues.getAsInteger(Programs.COLUMN_SEARCHABLE);
207        return i == null || i == IS_SEARCHABLE;
208    }
209
210    /**
211     * @return The first internal provider flag for the program.
212     * @see Programs#COLUMN_INTERNAL_PROVIDER_FLAG1
213     */
214    public Long getInternalProviderFlag1() {
215        return mValues.getAsLong(Programs.COLUMN_INTERNAL_PROVIDER_FLAG1);
216    }
217
218    /**
219     * @return The second internal provider flag for the program.
220     * @see Programs#COLUMN_INTERNAL_PROVIDER_FLAG2
221     */
222    public Long getInternalProviderFlag2() {
223        return mValues.getAsLong(Programs.COLUMN_INTERNAL_PROVIDER_FLAG2);
224    }
225
226    /**
227     * @return The third internal provider flag for the program.
228     * @see Programs#COLUMN_INTERNAL_PROVIDER_FLAG3
229     */
230    public Long getInternalProviderFlag3() {
231        return mValues.getAsLong(Programs.COLUMN_INTERNAL_PROVIDER_FLAG3);
232    }
233
234    /**
235     * @return The forth internal provider flag for the program.
236     * @see Programs#COLUMN_INTERNAL_PROVIDER_FLAG4
237     */
238    public Long getInternalProviderFlag4() {
239        return mValues.getAsLong(Programs.COLUMN_INTERNAL_PROVIDER_FLAG4);
240    }
241
242    /**
243     * @return The season title for the program.
244     * @see Programs#COLUMN_SEASON_TITLE
245     */
246    public String getSeasonTitle() {
247        return mValues.getAsString(Programs.COLUMN_SEASON_TITLE);
248    }
249
250    /**
251     * @return The review rating style for the program.
252     * @see Programs#COLUMN_REVIEW_RATING_STYLE
253     */
254    public @ReviewRatingStyle int getReviewRatingStyle() {
255        Integer i = mValues.getAsInteger(Programs.COLUMN_REVIEW_RATING_STYLE);
256        return i == null ? INVALID_INT_VALUE : i;
257    }
258
259    /**
260     * @return The review rating for the program.
261     * @see Programs#COLUMN_REVIEW_RATING
262     */
263    public String getReviewRating() {
264        return mValues.getAsString(Programs.COLUMN_REVIEW_RATING);
265    }
266
267    @Override
268    public int hashCode() {
269        return mValues.hashCode();
270    }
271
272    @Override
273    public boolean equals(Object other) {
274        if (!(other instanceof BaseProgram)) {
275            return false;
276        }
277        return mValues.equals(((BaseProgram) other).mValues);
278    }
279
280    @Override
281    public String toString() {
282        return "BaseProgram{" + mValues.toString() + "}";
283    }
284
285    /**
286     * @return The fields of the BaseProgram in {@link ContentValues} format to be easily inserted
287     * into the TV Input Framework database.
288     */
289    public ContentValues toContentValues() {
290        ContentValues values = new ContentValues(mValues);
291        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
292            values.remove(ProgramColumns.COLUMN_SEARCHABLE);
293            values.remove(ProgramColumns.COLUMN_INTERNAL_PROVIDER_FLAG1);
294            values.remove(ProgramColumns.COLUMN_INTERNAL_PROVIDER_FLAG2);
295            values.remove(ProgramColumns.COLUMN_INTERNAL_PROVIDER_FLAG3);
296            values.remove(ProgramColumns.COLUMN_INTERNAL_PROVIDER_FLAG4);
297        }
298        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
299            values.remove(ProgramColumns.COLUMN_SEASON_TITLE);
300        }
301        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
302            values.remove(ProgramColumns.COLUMN_REVIEW_RATING_STYLE);
303            values.remove(ProgramColumns.COLUMN_REVIEW_RATING);
304        }
305        return values;
306    }
307
308    /**
309     * Sets the fields in the cursor to the given builder instance.
310     *
311     * @param cursor A row from the TV Input Framework database.
312     * @param builder A Builder to set the fields.
313     */
314    static void setFieldsFromCursor(Cursor cursor, Builder builder) {
315        // TODO: Add additional API which does not use costly getColumnIndex().
316        int index;
317        if ((index = cursor.getColumnIndex(BaseTvColumns._ID)) >= 0 && !cursor.isNull(index)) {
318            builder.setId(cursor.getLong(index));
319        }
320        if ((index = cursor.getColumnIndex(BaseTvColumns.COLUMN_PACKAGE_NAME)) >= 0
321                && !cursor.isNull(index)) {
322            builder.setPackageName(cursor.getString(index));
323        }
324        if ((index = cursor.getColumnIndex(ProgramColumns.COLUMN_TITLE)) >= 0
325                && !cursor.isNull(index)) {
326            builder.setTitle(cursor.getString(index));
327        }
328        if ((index = cursor.getColumnIndex(ProgramColumns.COLUMN_EPISODE_TITLE)) >= 0
329                && !cursor.isNull(index)) {
330            builder.setEpisodeTitle(cursor.getString(index));
331        }
332        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
333            if ((index =
334                    cursor.getColumnIndex(ProgramColumns.COLUMN_SEASON_DISPLAY_NUMBER)) >= 0
335                    && !cursor.isNull(index)) {
336                builder.setSeasonNumber(cursor.getString(index), INVALID_INT_VALUE);
337            }
338        } else {
339            if ((index = cursor.getColumnIndex(Programs.COLUMN_SEASON_NUMBER)) >= 0
340                    && !cursor.isNull(index)) {
341                builder.setSeasonNumber(cursor.getInt(index));
342            }
343        }
344        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
345            if ((index =
346                    cursor.getColumnIndex(ProgramColumns.COLUMN_EPISODE_DISPLAY_NUMBER)) >= 0
347                    && !cursor.isNull(index)) {
348                builder.setEpisodeNumber(cursor.getString(index), INVALID_INT_VALUE);
349            }
350        } else {
351            if ((index = cursor.getColumnIndex(Programs.COLUMN_EPISODE_NUMBER)) >= 0
352                    && !cursor.isNull(index)) {
353                builder.setEpisodeNumber(cursor.getInt(index));
354            }
355        }
356        if ((index = cursor.getColumnIndex(ProgramColumns.COLUMN_SHORT_DESCRIPTION)) >= 0
357                && !cursor.isNull(index)) {
358            builder.setDescription(cursor.getString(index));
359        }
360        if ((index = cursor.getColumnIndex(ProgramColumns.COLUMN_LONG_DESCRIPTION)) >= 0
361                && !cursor.isNull(index)) {
362            builder.setLongDescription(cursor.getString(index));
363        }
364        if ((index = cursor.getColumnIndex(ProgramColumns.COLUMN_POSTER_ART_URI)) >= 0
365                && !cursor.isNull(index)) {
366            builder.setPosterArtUri(Uri.parse(cursor.getString(index)));
367        }
368        if ((index = cursor.getColumnIndex(ProgramColumns.COLUMN_THUMBNAIL_URI)) >= 0
369                && !cursor.isNull(index)) {
370            builder.setThumbnailUri(Uri.parse(cursor.getString(index)));
371        }
372        if ((index = cursor.getColumnIndex(ProgramColumns.COLUMN_AUDIO_LANGUAGE)) >= 0
373                && !cursor.isNull(index)) {
374            builder.setAudioLanguages(
375                    TvContractUtils.stringToAudioLanguages(cursor.getString(index)));
376        }
377        if ((index = cursor.getColumnIndex(ProgramColumns.COLUMN_CANONICAL_GENRE)) >= 0
378                && !cursor.isNull(index)) {
379            builder.setCanonicalGenres(Programs.Genres.decode(
380                    cursor.getString(index)));
381        }
382        if ((index = cursor.getColumnIndex(ProgramColumns.COLUMN_CONTENT_RATING)) >= 0
383                && !cursor.isNull(index)) {
384            builder.setContentRatings(
385                    TvContractUtils.stringToContentRatings(cursor.getString(index)));
386        }
387        if ((index = cursor.getColumnIndex(ProgramColumns.COLUMN_VIDEO_WIDTH)) >= 0
388                && !cursor.isNull(index)) {
389            builder.setVideoWidth((int) cursor.getLong(index));
390        }
391        if ((index = cursor.getColumnIndex(ProgramColumns.COLUMN_VIDEO_HEIGHT)) >= 0
392                && !cursor.isNull(index)) {
393            builder.setVideoHeight((int) cursor.getLong(index));
394        }
395        if ((index = cursor.getColumnIndex(ProgramColumns.COLUMN_INTERNAL_PROVIDER_DATA)) >= 0
396                && !cursor.isNull(index)) {
397            builder.setInternalProviderData(cursor.getBlob(index));
398        }
399        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
400            if ((index = cursor.getColumnIndex(ProgramColumns.COLUMN_SEARCHABLE)) >= 0
401                    && !cursor.isNull(index)) {
402                builder.setSearchable(cursor.getInt(index) == IS_SEARCHABLE);
403            }
404            if ((index =
405                    cursor.getColumnIndex(ProgramColumns.COLUMN_INTERNAL_PROVIDER_FLAG1)) >= 0
406                    && !cursor.isNull(index)) {
407                builder.setInternalProviderFlag1(cursor.getLong(index));
408            }
409            if ((index =
410                    cursor.getColumnIndex(ProgramColumns.COLUMN_INTERNAL_PROVIDER_FLAG2)) >= 0
411                    && !cursor.isNull(index)) {
412                builder.setInternalProviderFlag2(cursor.getLong(index));
413            }
414            if ((index =
415                    cursor.getColumnIndex(ProgramColumns.COLUMN_INTERNAL_PROVIDER_FLAG3)) >= 0
416                    && !cursor.isNull(index)) {
417                builder.setInternalProviderFlag3(cursor.getLong(index));
418            }
419            if ((index =
420                    cursor.getColumnIndex(ProgramColumns.COLUMN_INTERNAL_PROVIDER_FLAG4)) >= 0
421                    && !cursor.isNull(index)) {
422                builder.setInternalProviderFlag4(cursor.getLong(index));
423            }
424        }
425        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
426            if ((index = cursor.getColumnIndex(ProgramColumns.COLUMN_SEASON_TITLE)) >= 0
427                    && !cursor.isNull(index)) {
428                builder.setSeasonTitle(cursor.getString(index));
429            }
430        }
431        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
432            if ((index = cursor.getColumnIndex(
433                    ProgramColumns.COLUMN_REVIEW_RATING_STYLE)) >= 0
434                    && !cursor.isNull(index)) {
435                builder.setReviewRatingStyle(cursor.getInt(index));
436            }
437            if ((index = cursor.getColumnIndex(ProgramColumns.COLUMN_REVIEW_RATING)) >= 0
438                    && !cursor.isNull(index)) {
439                builder.setReviewRating(cursor.getString(index));
440            }
441        }
442    }
443
444    private static String[] getProjection() {
445        String[] baseColumns = new String[] {
446                BaseTvColumns._ID,
447                BaseTvColumns.COLUMN_PACKAGE_NAME,
448                ProgramColumns.COLUMN_TITLE,
449                ProgramColumns.COLUMN_EPISODE_TITLE,
450                (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
451                        ? ProgramColumns.COLUMN_SEASON_DISPLAY_NUMBER
452                        : Programs.COLUMN_SEASON_NUMBER,
453                (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
454                        ? ProgramColumns.COLUMN_EPISODE_DISPLAY_NUMBER
455                        : Programs.COLUMN_EPISODE_NUMBER,
456                ProgramColumns.COLUMN_SHORT_DESCRIPTION,
457                ProgramColumns.COLUMN_LONG_DESCRIPTION,
458                ProgramColumns.COLUMN_POSTER_ART_URI,
459                ProgramColumns.COLUMN_THUMBNAIL_URI,
460                ProgramColumns.COLUMN_AUDIO_LANGUAGE,
461                ProgramColumns.COLUMN_CANONICAL_GENRE,
462                ProgramColumns.COLUMN_CONTENT_RATING,
463                ProgramColumns.COLUMN_VIDEO_WIDTH,
464                ProgramColumns.COLUMN_VIDEO_HEIGHT,
465                ProgramColumns.COLUMN_INTERNAL_PROVIDER_DATA
466        };
467        String[] marshmallowColumns = new String[] {
468                ProgramColumns.COLUMN_SEARCHABLE,
469                ProgramColumns.COLUMN_INTERNAL_PROVIDER_FLAG1,
470                ProgramColumns.COLUMN_INTERNAL_PROVIDER_FLAG2,
471                ProgramColumns.COLUMN_INTERNAL_PROVIDER_FLAG3,
472                ProgramColumns.COLUMN_INTERNAL_PROVIDER_FLAG4,
473        };
474        String[] nougatColumns = new String[] {
475                ProgramColumns.COLUMN_SEASON_TITLE,
476        };
477        String[] oColumns = new String[] {
478                ProgramColumns.COLUMN_REVIEW_RATING,
479                ProgramColumns.COLUMN_REVIEW_RATING_STYLE,
480        };
481        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
482            return CollectionUtils.concatAll(baseColumns, marshmallowColumns, nougatColumns,
483                oColumns);
484        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
485            return CollectionUtils.concatAll(baseColumns, marshmallowColumns, nougatColumns);
486        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
487            return CollectionUtils.concatAll(baseColumns, marshmallowColumns);
488        } else {
489            return baseColumns;
490        }
491    }
492
493    /**
494     * This Builder class simplifies the creation of a {@link BaseProgram} object.
495     *
496     * @param <T> The Builder of the derived classe.
497     */
498    public abstract static class Builder<T extends Builder> {
499        /** @hide */
500        @RestrictTo(LIBRARY_GROUP)
501        protected ContentValues mValues;
502
503        /**
504         * Creates a new Builder object.
505         */
506        public Builder() {
507            mValues = new ContentValues();
508        }
509
510        /**
511         * Creates a new Builder object with values copied from another Program.
512         * @param other The Program you're copying from.
513         */
514        public Builder(BaseProgram other) {
515            mValues = new ContentValues(other.mValues);
516        }
517
518        /**
519         * Sets a unique id for this program.
520         *
521         * @param programId The ID for the program.
522         * @return This Builder object to allow for chaining of calls to builder methods.
523         * @see BaseTvColumns#_ID
524         */
525        public T setId(long programId) {
526            mValues.put(BaseTvColumns._ID, programId);
527            return (T) this;
528        }
529
530        /**
531         * Sets the package name for this program.
532         *
533         * @param packageName The package name for the program.
534         * @return This Builder object to allow for chaining of calls to builder methods.
535         * @see BaseTvColumns#COLUMN_PACKAGE_NAME
536         * @hide
537         */
538        @RestrictTo(LIBRARY_GROUP)
539        public T setPackageName(String packageName) {
540            mValues.put(BaseTvColumns.COLUMN_PACKAGE_NAME, packageName);
541            return (T) this;
542        }
543
544        /**
545         * Sets the title of this program. For a series, this is the series title.
546         *
547         * @param title The title for the program.
548         * @return This Builder object to allow for chaining of calls to builder methods.
549         * @see Programs#COLUMN_TITLE
550         */
551        public T setTitle(String title) {
552            mValues.put(Programs.COLUMN_TITLE, title);
553            return (T) this;
554        }
555
556        /**
557         * Sets the title of this particular episode for a series.
558         *
559         * @param episodeTitle The episode title for the program.
560         * @return This Builder object to allow for chaining of calls to builder methods.
561         * @see Programs#COLUMN_EPISODE_TITLE
562         */
563        public T setEpisodeTitle(String episodeTitle) {
564            mValues.put(Programs.COLUMN_EPISODE_TITLE, episodeTitle);
565            return (T) this;
566        }
567
568        /**
569         * Sets the season number for this episode for a series.
570         *
571         * @param seasonNumber The season display number for the program.
572         * @return This Builder object to allow for chaining of calls to builder methods.
573         * @see Programs#COLUMN_SEASON_DISPLAY_NUMBER
574         */
575        public T setSeasonNumber(int seasonNumber) {
576            setSeasonNumber(String.valueOf(seasonNumber), seasonNumber);
577            return (T) this;
578        }
579
580        /**
581         * Sets the season number for this episode for a series.
582         *
583         * @param seasonNumber The season display number for the program.
584         * @param numericalSeasonNumber An integer value for {@link Programs#COLUMN_SEASON_NUMBER}
585         *                              which will be used for API Level 23 and below.
586         * @return This Builder object to allow for chaining of calls to builder methods.
587         * @see Programs#COLUMN_SEASON_DISPLAY_NUMBER
588         * @see Programs#COLUMN_SEASON_NUMBER
589         */
590        public T setSeasonNumber(String seasonNumber, int numericalSeasonNumber) {
591            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
592                mValues.put(Programs.COLUMN_SEASON_DISPLAY_NUMBER, seasonNumber);
593            } else {
594                mValues.put(Programs.COLUMN_SEASON_NUMBER, numericalSeasonNumber);
595            }
596            return (T) this;
597        }
598
599        /**
600         * Sets the episode number in a season for this episode for a series.
601         *
602         * @param episodeNumber The value of episode display number for the program.
603         * @return This Builder object to allow for chaining of calls to builder methods.
604         * @see Programs#COLUMN_EPISODE_DISPLAY_NUMBER
605         */
606        public T setEpisodeNumber(int episodeNumber) {
607            setEpisodeNumber(String.valueOf(episodeNumber), episodeNumber);
608            return (T) this;
609        }
610
611        /**
612         * Sets the episode number in a season for this episode for a series.
613         *
614         * @param episodeNumber The value of episode display number for the program.
615         * @param numericalEpisodeNumber An integer value for {@link Programs#COLUMN_EPISODE_NUMBER}
616         *                               which will be used for API Level 23 and below.
617         * @return This Builder object to allow for chaining of calls to builder methods.
618         * @see Programs#COLUMN_EPISODE_DISPLAY_NUMBER
619         * @see Programs#COLUMN_EPISODE_NUMBER
620         */
621        public T setEpisodeNumber(String episodeNumber, int numericalEpisodeNumber) {
622            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
623                mValues.put(Programs.COLUMN_EPISODE_DISPLAY_NUMBER, episodeNumber);
624            } else {
625                mValues.put(Programs.COLUMN_EPISODE_NUMBER, numericalEpisodeNumber);
626            }
627            return (T) this;
628        }
629
630        /**
631         * Sets a brief description of the program. For a series, this would be a brief description
632         * of the episode.
633         *
634         * @param description The short description for the program.
635         * @return This Builder object to allow for chaining of calls to builder methods.
636         * @see Programs#COLUMN_SHORT_DESCRIPTION
637         */
638        public T setDescription(String description) {
639            mValues.put(Programs.COLUMN_SHORT_DESCRIPTION, description);
640            return (T) this;
641        }
642
643        /**
644         * Sets a longer description of a program if one exists.
645         *
646         * @param longDescription The long description for the program.
647         * @return This Builder object to allow for chaining of calls to builder methods.
648         * @see Programs#COLUMN_LONG_DESCRIPTION
649         */
650        public T setLongDescription(String longDescription) {
651            mValues.put(Programs.COLUMN_LONG_DESCRIPTION, longDescription);
652            return (T) this;
653        }
654
655        /**
656         * Sets the video width of the program.
657         *
658         * @param width The video width for the program.
659         * @return This Builder object to allow for chaining of calls to builder methods.
660         * @see Programs#COLUMN_VIDEO_WIDTH
661         */
662        public T setVideoWidth(int width) {
663            mValues.put(Programs.COLUMN_VIDEO_WIDTH, width);
664            return (T) this;
665        }
666
667        /**
668         * Sets the video height of the program.
669         *
670         * @param height The video height for the program.
671         * @return This Builder object to allow for chaining of calls to builder methods.
672         * @see Programs#COLUMN_VIDEO_HEIGHT
673         */
674        public T setVideoHeight(int height) {
675            mValues.put(Programs.COLUMN_VIDEO_HEIGHT, height);
676            return (T) this;
677        }
678
679        /**
680         * Sets the content ratings for this program.
681         *
682         * @param contentRatings An array of {@link TvContentRating} that apply to this program
683         *                       which will be flattened to a String to store in a database.
684         * @return This Builder object to allow for chaining of calls to builder methods.
685         * @see Programs#COLUMN_CONTENT_RATING
686         */
687        public T setContentRatings(TvContentRating[] contentRatings) {
688            mValues.put(Programs.COLUMN_CONTENT_RATING,
689                    TvContractUtils.contentRatingsToString(contentRatings));
690            return (T) this;
691        }
692
693        /**
694         * Sets the large poster art of the program.
695         *
696         * @param posterArtUri The poster art URI for the program.
697         * @return This Builder object to allow for chaining of calls to builder methods.
698         * @see Programs#COLUMN_POSTER_ART_URI
699         */
700        public T setPosterArtUri(Uri posterArtUri) {
701            mValues.put(Programs.COLUMN_POSTER_ART_URI,
702                    posterArtUri == null ? null : posterArtUri.toString());
703            return (T) this;
704        }
705
706        /**
707         * Sets a small thumbnail of the program.
708         *
709         * @param thumbnailUri The thumbnail URI for the program.
710         * @return This Builder object to allow for chaining of calls to builder methods.
711         * @see Programs#COLUMN_THUMBNAIL_URI
712         */
713        public T setThumbnailUri(Uri thumbnailUri) {
714            mValues.put(Programs.COLUMN_THUMBNAIL_URI,
715                    thumbnailUri == null ? null : thumbnailUri.toString());
716            return (T) this;
717        }
718
719        /**
720         * Sets the genres of the program.
721         *
722         * @param genres An array of {@link Programs.Genres} that apply to the program which will be
723         *               flattened to a String to store in a database.
724         * @return This Builder object to allow for chaining of calls to builder methods.
725         * @see Programs#COLUMN_CANONICAL_GENRE
726         */
727        public T setCanonicalGenres(@Genre String[] genres) {
728            mValues.put(Programs.COLUMN_CANONICAL_GENRE, Programs.Genres.encode(genres));
729            return (T) this;
730        }
731
732        /**
733         * Sets the internal provider data for the program as raw bytes.
734         *
735         * @param data The internal provider data for the program.
736         * @return This Builder object to allow for chaining of calls to builder methods.
737         * @see Programs#COLUMN_INTERNAL_PROVIDER_DATA
738         */
739        public T setInternalProviderData(byte[] data) {
740            mValues.put(ProgramColumns.COLUMN_INTERNAL_PROVIDER_DATA, data);
741            return (T) this;
742        }
743
744        /**
745         * Sets the available audio languages for this program as an array of strings.
746         *
747         * @param audioLanguages An array of audio languages, in ISO 639-1 or 639-2/T codes, that
748         *                       apply to this program which will be stored in a database.
749         * @return This Builder object to allow for chaining of calls to builder methods.
750         */
751        public T setAudioLanguages(String[] audioLanguages) {
752            mValues.put(ProgramColumns.COLUMN_AUDIO_LANGUAGE,
753                    TvContractUtils.audioLanguagesToString(audioLanguages));
754            return (T) this;
755        }
756
757        /**
758         * Sets whether this channel can be searched for in other applications.
759         *
760         * @param searchable Whether the program is searchable or not.
761         * @return This Builder object to allow for chaining of calls to builder methods.
762         * @see Programs#COLUMN_SEARCHABLE
763         */
764        public T setSearchable(boolean searchable) {
765            mValues.put(Programs.COLUMN_SEARCHABLE, searchable ? IS_SEARCHABLE : 0);
766            return (T) this;
767        }
768
769        /**
770         * Sets the internal provider flag1 for the program.
771         *
772         * @param flag The first internal provider flag for the program.
773         * @return This Builder object to allow for chaining of calls to builder methods.
774         * @see Programs#COLUMN_INTERNAL_PROVIDER_FLAG1
775         */
776        public T setInternalProviderFlag1(long flag) {
777            mValues.put(ProgramColumns.COLUMN_INTERNAL_PROVIDER_FLAG1, flag);
778            return (T) this;
779        }
780
781        /**
782         * Sets the internal provider flag2 for the program.
783         *
784         * @param flag The second internal provider flag for the program.
785         * @return This Builder object to allow for chaining of calls to builder methods.
786         * @see Programs#COLUMN_INTERNAL_PROVIDER_FLAG2
787         */
788        public T setInternalProviderFlag2(long flag) {
789            mValues.put(ProgramColumns.COLUMN_INTERNAL_PROVIDER_FLAG2, flag);
790            return (T) this;
791        }
792
793        /**
794         * Sets the internal provider flag3 for the program.
795         *
796         * @param flag The third internal provider flag for the program.
797         * @return This Builder object to allow for chaining of calls to builder methods.
798         * @see Programs#COLUMN_INTERNAL_PROVIDER_FLAG3
799         */
800        public T setInternalProviderFlag3(long flag) {
801            mValues.put(ProgramColumns.COLUMN_INTERNAL_PROVIDER_FLAG3, flag);
802            return (T) this;
803        }
804
805        /**
806         * Sets the internal provider flag4 for the program.
807         *
808         * @param flag The forth internal provider flag for the program.
809         * @return This Builder object to allow for chaining of calls to builder methods.
810         * @see Programs#COLUMN_INTERNAL_PROVIDER_FLAG4
811         */
812        public T setInternalProviderFlag4(long flag) {
813            mValues.put(ProgramColumns.COLUMN_INTERNAL_PROVIDER_FLAG4, flag);
814            return (T) this;
815        }
816
817        /**
818         * Sets the review rating score style used for {@link #setReviewRating}.
819         *
820         * @param reviewRatingStyle The reviewing rating style for the program.
821         * @return This Builder object to allow for chaining of calls to builder methods.
822         *
823         * @see Programs#COLUMN_REVIEW_RATING_STYLE
824         * @see Programs#REVIEW_RATING_STYLE_STARS
825         * @see Programs#REVIEW_RATING_STYLE_THUMBS_UP_DOWN
826         * @see Programs#REVIEW_RATING_STYLE_PERCENTAGE
827         */
828        public T setReviewRatingStyle(@ReviewRatingStyle int reviewRatingStyle) {
829            mValues.put(ProgramColumns.COLUMN_REVIEW_RATING_STYLE, reviewRatingStyle);
830            return (T) this;
831        }
832
833        /**
834         * Sets the review rating score for this program.
835         *
836         * <p>The format of the value is dependent on the review rating style. If the style is
837         * based on "stars", the value should be a real number between 0.0 and 5.0. (e.g. "4.5")
838         * If the style is based on "thumbs up/down", the value should be two integers, one for
839         * thumbs-up count and the other for thumbs-down count, with a comma between them.
840         * (e.g. "200,40") If the style is base on "percentage", the value should be a
841         * real number between 0 and 100. (e.g. "99.9")
842         *
843         * @param reviewRating The value of the review rating for the program.
844         * @return This Builder object to allow for chaining of calls to builder methods.
845         *
846         * @see Programs#COLUMN_REVIEW_RATING
847         * @see Programs#COLUMN_REVIEW_RATING_STYLE
848         * @see Programs#REVIEW_RATING_STYLE_STARS
849         * @see Programs#REVIEW_RATING_STYLE_THUMBS_UP_DOWN
850         * @see Programs#REVIEW_RATING_STYLE_PERCENTAGE
851         */
852        public T setReviewRating(String reviewRating) {
853            mValues.put(ProgramColumns.COLUMN_REVIEW_RATING, reviewRating);
854            return (T) this;
855        }
856
857        /**
858         * Sets a custom name for the season, if applicable.
859         *
860         * @param seasonTitle The season title for the program.
861         * @return This Builder object to allow for chaining of calls to builder methods.
862         * @see Programs#COLUMN_SEASON_TITLE
863         */
864        public T setSeasonTitle(String seasonTitle) {
865            mValues.put(ProgramColumns.COLUMN_SEASON_TITLE, seasonTitle);
866            return (T) this;
867        }
868    }
869}
870