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