Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright (C) 2014 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 android.media.tv.cts;
     18 
     19 import android.content.ComponentName;
     20 import android.content.ContentResolver;
     21 import android.content.ContentUris;
     22 import android.content.ContentValues;
     23 import android.content.res.AssetFileDescriptor;
     24 import android.database.Cursor;
     25 import android.graphics.Bitmap;
     26 import android.graphics.BitmapFactory;
     27 import android.media.tv.TvContentRating;
     28 import android.media.tv.TvContract;
     29 import android.media.tv.TvContract.Channels;
     30 import android.media.tv.TvContract.PreviewPrograms;
     31 import android.media.tv.TvContract.Programs;
     32 import android.media.tv.TvContract.Programs.Genres;
     33 import android.media.tv.TvContract.RecordedPrograms;
     34 import android.media.tv.TvContract.WatchNextPrograms;
     35 import android.net.Uri;
     36 import android.test.AndroidTestCase;
     37 import android.test.MoreAsserts;
     38 import android.tv.cts.R;
     39 
     40 import java.io.InputStream;
     41 import java.io.OutputStream;
     42 import java.util.Arrays;
     43 import java.util.List;
     44 
     45 /**
     46  * Test for {@link android.media.tv.TvContract}.
     47  */
     48 public class TvContractTest extends AndroidTestCase {
     49     private static final String[] CHANNELS_PROJECTION = {
     50         Channels._ID,
     51         Channels.COLUMN_INPUT_ID,
     52         Channels.COLUMN_TYPE,
     53         Channels.COLUMN_SERVICE_TYPE,
     54         Channels.COLUMN_ORIGINAL_NETWORK_ID,
     55         Channels.COLUMN_TRANSPORT_STREAM_ID,
     56         Channels.COLUMN_SERVICE_ID,
     57         Channels.COLUMN_DISPLAY_NUMBER,
     58         Channels.COLUMN_DISPLAY_NAME,
     59         Channels.COLUMN_NETWORK_AFFILIATION,
     60         Channels.COLUMN_DESCRIPTION,
     61         Channels.COLUMN_VIDEO_FORMAT,
     62         Channels.COLUMN_INTERNAL_PROVIDER_DATA,
     63         Channels.COLUMN_VERSION_NUMBER,
     64         Channels.COLUMN_INTERNAL_PROVIDER_ID,
     65     };
     66 
     67     private static final String[] PROGRAMS_PROJECTION = {
     68         Programs._ID,
     69         Programs.COLUMN_CHANNEL_ID,
     70         Programs.COLUMN_TITLE,
     71         Programs.COLUMN_SEASON_DISPLAY_NUMBER,
     72         Programs.COLUMN_SEASON_TITLE,
     73         Programs.COLUMN_EPISODE_DISPLAY_NUMBER,
     74         Programs.COLUMN_EPISODE_TITLE,
     75         Programs.COLUMN_START_TIME_UTC_MILLIS,
     76         Programs.COLUMN_END_TIME_UTC_MILLIS,
     77         Programs.COLUMN_BROADCAST_GENRE,
     78         Programs.COLUMN_CANONICAL_GENRE,
     79         Programs.COLUMN_SHORT_DESCRIPTION,
     80         Programs.COLUMN_LONG_DESCRIPTION,
     81         Programs.COLUMN_VIDEO_WIDTH,
     82         Programs.COLUMN_VIDEO_HEIGHT,
     83         Programs.COLUMN_AUDIO_LANGUAGE,
     84         Programs.COLUMN_CONTENT_RATING,
     85         Programs.COLUMN_POSTER_ART_URI,
     86         Programs.COLUMN_THUMBNAIL_URI,
     87         Programs.COLUMN_INTERNAL_PROVIDER_DATA,
     88         Programs.COLUMN_VERSION_NUMBER,
     89     };
     90 
     91     private static long OPERATION_TIME = 1000l;
     92 
     93     private static final String ENCODED_GENRE_STRING = Genres.ANIMAL_WILDLIFE + "," + Genres.COMEDY
     94             + "," + Genres.DRAMA + "," + Genres.EDUCATION + "," + Genres.FAMILY_KIDS + ","
     95             + Genres.GAMING + "," + Genres.MOVIES + "," + Genres.NEWS + "," + Genres.SHOPPING + ","
     96             + Genres.SPORTS + "," + Genres.TRAVEL;
     97 
     98     // Delimiter for genre.
     99     private static final String DELIMITER = ",";
    100     private static final String EMPTY_GENRE = "";
    101     private static final String COMMA = ",";
    102     private static final String COMMA_ENCODED = "\",";
    103     private static final String QUOTE = "\"";
    104     private static final String QUOTE_ENCODED = "\"\"";
    105     private static final String WHITE_SPACES = " \r \n \t \f ";
    106 
    107     private static final String PARAM_CANONICAL_GENRE = "canonical_genre";
    108     private static final String NON_EXISTING_COLUMN_NAME = "non_existing_column";
    109 
    110     private String mInputId;
    111     private ContentResolver mContentResolver;
    112     private Uri mChannelsUri;
    113 
    114     @Override
    115     protected void setUp() throws Exception {
    116         super.setUp();
    117         if (!Utils.hasTvInputFramework(getContext())) {
    118             return;
    119         }
    120         mInputId = TvContract.buildInputId(
    121                 new ComponentName(getContext(), StubTunerTvInputService.class));
    122         mContentResolver = getContext().getContentResolver();
    123         mChannelsUri = TvContract.buildChannelsUriForInput(mInputId);
    124     }
    125 
    126     @Override
    127     protected void tearDown() throws Exception {
    128         if (!Utils.hasTvInputFramework(getContext())) {
    129             super.tearDown();
    130             return;
    131         }
    132         mContentResolver.delete(Channels.CONTENT_URI, null, null);
    133         mContentResolver.delete(RecordedPrograms.CONTENT_URI, null, null);
    134         mContentResolver.delete(WatchNextPrograms.CONTENT_URI, null, null);
    135         super.tearDown();
    136     }
    137 
    138     private static ContentValues createDummyChannelValues(String inputId, boolean preview) {
    139         ContentValues values = new ContentValues();
    140         values.put(Channels.COLUMN_INPUT_ID, inputId);
    141         values.put(Channels.COLUMN_INTERNAL_PROVIDER_ID, "ID-4321");
    142         values.put(Channels.COLUMN_TYPE, preview ? Channels.TYPE_PREVIEW : Channels.TYPE_OTHER);
    143         values.put(Channels.COLUMN_SERVICE_TYPE, Channels.SERVICE_TYPE_AUDIO_VIDEO);
    144         values.put(Channels.COLUMN_DISPLAY_NUMBER, "1");
    145         values.put(Channels.COLUMN_VIDEO_FORMAT, Channels.VIDEO_FORMAT_480P);
    146 
    147         return values;
    148     }
    149 
    150     private static ContentValues createDummyProgramValues(long channelId) {
    151         ContentValues values = new ContentValues();
    152         values.put(Programs.COLUMN_CHANNEL_ID, channelId);
    153         values.put(Programs.COLUMN_EPISODE_DISPLAY_NUMBER , "1A");
    154         values.put(Programs.COLUMN_EPISODE_TITLE, "episode_title");
    155         values.put(Programs.COLUMN_SEASON_DISPLAY_NUMBER , "2B");
    156         values.put(Programs.COLUMN_SEASON_TITLE, "season_title");
    157         values.put(Programs.COLUMN_CANONICAL_GENRE, Programs.Genres.encode(
    158                 Programs.Genres.MOVIES, Programs.Genres.DRAMA));
    159         TvContentRating rating = TvContentRating.createRating("android.media.tv", "US_TVPG",
    160                 "US_TVPG_TV_MA", "US_TVPG_S", "US_TVPG_V");
    161         values.put(Programs.COLUMN_CONTENT_RATING, rating.flattenToString());
    162         values.put(Programs.COLUMN_REVIEW_RATING_STYLE,
    163                 RecordedPrograms.REVIEW_RATING_STYLE_STARS);
    164         values.put(Programs.COLUMN_REVIEW_RATING, "4.5");
    165 
    166         return values;
    167     }
    168 
    169     private static ContentValues createDummyPreviewProgramValues(long channelId) {
    170         ContentValues values = createDummyBasePreviewProgramValues();
    171         values.put(PreviewPrograms.COLUMN_CHANNEL_ID, channelId);
    172         values.put(PreviewPrograms.COLUMN_WEIGHT, 100);
    173         return values;
    174     }
    175 
    176     private static ContentValues createDummyWatchNextProgramValues() {
    177         ContentValues values = createDummyBasePreviewProgramValues();
    178         values.put(WatchNextPrograms.COLUMN_WATCH_NEXT_TYPE,
    179                 WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE);
    180         values.put(WatchNextPrograms.COLUMN_LAST_ENGAGEMENT_TIME_UTC_MILLIS,
    181                 System.currentTimeMillis());
    182         return values;
    183     }
    184 
    185     private static ContentValues createDummyBasePreviewProgramValues() {
    186         ContentValues values = new ContentValues();
    187         values.put(PreviewPrograms.COLUMN_INTERNAL_PROVIDER_ID, "ID-4321");
    188         values.put(PreviewPrograms.COLUMN_PREVIEW_VIDEO_URI, "http://test.com/preview.mp4");
    189         values.put(PreviewPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS, 5000);
    190         values.put(PreviewPrograms.COLUMN_DURATION_MILLIS, 60000);
    191         values.put(PreviewPrograms.COLUMN_INTENT_URI, "intent_for_video");
    192         values.put(PreviewPrograms.COLUMN_TITLE, "program_title");
    193         values.put(PreviewPrograms.COLUMN_SHORT_DESCRIPTION, "short_description");
    194         values.put(PreviewPrograms.COLUMN_EPISODE_DISPLAY_NUMBER , "1A");
    195         values.put(PreviewPrograms.COLUMN_EPISODE_TITLE, "episode_title");
    196         values.put(PreviewPrograms.COLUMN_SEASON_DISPLAY_NUMBER , "2B");
    197         values.put(PreviewPrograms.COLUMN_SEASON_TITLE, "season_title");
    198         values.put(PreviewPrograms.COLUMN_CANONICAL_GENRE, Programs.Genres.encode(
    199                 Programs.Genres.SPORTS, Programs.Genres.DRAMA));
    200         TvContentRating rating = TvContentRating.createRating("android.media.tv", "US_TVPG",
    201                 "US_TVPG_TV_MA", "US_TVPG_S", "US_TVPG_V");
    202         values.put(PreviewPrograms.COLUMN_CONTENT_RATING, rating.flattenToString());
    203         values.put(PreviewPrograms.COLUMN_TYPE, PreviewPrograms.TYPE_MOVIE);
    204         values.put(PreviewPrograms.COLUMN_POSTER_ART_URI, "http://foo.com/artwork.png");
    205         values.put(PreviewPrograms.COLUMN_POSTER_ART_ASPECT_RATIO,
    206                 PreviewPrograms.ASPECT_RATIO_2_3);
    207         values.put(PreviewPrograms.COLUMN_THUMBNAIL_URI, "http://foo.com/thumbnail.jpg");
    208         values.put(PreviewPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO,
    209                 PreviewPrograms.ASPECT_RATIO_16_9);
    210         values.put(PreviewPrograms.COLUMN_LOGO_URI, "http://foo.com/logo.jpg");
    211         values.put(PreviewPrograms.COLUMN_AVAILABILITY, PreviewPrograms.AVAILABILITY_AVAILABLE);
    212         values.put(PreviewPrograms.COLUMN_STARTING_PRICE, "10.99 USD");
    213         values.put(PreviewPrograms.COLUMN_OFFER_PRICE, "3.99 USD");
    214         values.put(PreviewPrograms.COLUMN_RELEASE_DATE, "1985");
    215         values.put(PreviewPrograms.COLUMN_ITEM_COUNT, 1);
    216         values.put(PreviewPrograms.COLUMN_LIVE, 0);
    217         values.put(PreviewPrograms.COLUMN_INTERACTION_TYPE, PreviewPrograms.INTERACTION_TYPE_LIKES);
    218         values.put(PreviewPrograms.COLUMN_INTERACTION_COUNT, 4000);
    219         values.put(PreviewPrograms.COLUMN_AUTHOR, "author_name1");
    220         values.put(PreviewPrograms.COLUMN_REVIEW_RATING_STYLE,
    221                 PreviewPrograms.REVIEW_RATING_STYLE_STARS);
    222         values.put(PreviewPrograms.COLUMN_REVIEW_RATING, "4.5");
    223         values.put(WatchNextPrograms.COLUMN_CONTENT_ID, "CID-125-6335");
    224 
    225         return values;
    226     }
    227 
    228 
    229     private static ContentValues createDummyRecordedProgramValues(String inputId, long channelId) {
    230         ContentValues values = new ContentValues();
    231         values.put(RecordedPrograms.COLUMN_INPUT_ID, inputId);
    232         values.put(RecordedPrograms.COLUMN_CHANNEL_ID, channelId);
    233         values.put(RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER , "3B");
    234         values.put(RecordedPrograms.COLUMN_SEASON_TITLE, "season_title");
    235         values.put(RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER , "2A");
    236         values.put(RecordedPrograms.COLUMN_EPISODE_TITLE, "episode_title");
    237         values.put(RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS, 1000);
    238         values.put(RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS, 2000);
    239         values.put(RecordedPrograms.COLUMN_CANONICAL_GENRE,
    240                 Programs.Genres.encode(Programs.Genres.MOVIES, Programs.Genres.DRAMA));
    241         values.put(RecordedPrograms.COLUMN_SHORT_DESCRIPTION, "short_description");
    242         values.put(RecordedPrograms.COLUMN_LONG_DESCRIPTION, "long_description");
    243         values.put(RecordedPrograms.COLUMN_VIDEO_WIDTH, 1920);
    244         values.put(RecordedPrograms.COLUMN_VIDEO_HEIGHT, 1080);
    245         values.put(RecordedPrograms.COLUMN_AUDIO_LANGUAGE, "en");
    246         TvContentRating rating = TvContentRating.createRating("android.media.tv", "US_TVPG",
    247                 "US_TVPG_TV_MA", "US_TVPG_S", "US_TVPG_V");
    248         values.put(RecordedPrograms.COLUMN_CONTENT_RATING, rating.flattenToString());
    249         values.put(RecordedPrograms.COLUMN_POSTER_ART_URI, "http://foo.com/artwork.png");
    250         values.put(RecordedPrograms.COLUMN_THUMBNAIL_URI, "http://foo.com/thumbnail.jpg");
    251         values.put(RecordedPrograms.COLUMN_SEARCHABLE, 1);
    252         values.put(RecordedPrograms.COLUMN_RECORDING_DATA_URI, "file:///sdcard/foo/");
    253         values.put(RecordedPrograms.COLUMN_RECORDING_DATA_BYTES, 1024 * 1024);
    254         values.put(RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS, 60 * 60 * 1000);
    255         values.put(RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS, 1454480880L);
    256         values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA,
    257                 "internal_provider_data".getBytes());
    258         values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1, 4);
    259         values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2, 3);
    260         values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3, 2);
    261         values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4, 1);
    262         values.put(RecordedPrograms.COLUMN_REVIEW_RATING_STYLE,
    263                 RecordedPrograms.REVIEW_RATING_STYLE_STARS);
    264         values.put(RecordedPrograms.COLUMN_REVIEW_RATING, "4.5");
    265 
    266         return values;
    267     }
    268 
    269     private static void verifyStringColumn(Cursor cursor, ContentValues expectedValues,
    270             String columnName) {
    271         if (expectedValues.containsKey(columnName)) {
    272             assertEquals(expectedValues.getAsString(columnName),
    273                     cursor.getString(cursor.getColumnIndex(columnName)));
    274         }
    275     }
    276 
    277     private static void verifyIntegerColumn(Cursor cursor, ContentValues expectedValues,
    278             String columnName) {
    279         if (expectedValues.containsKey(columnName)) {
    280             assertEquals(expectedValues.getAsInteger(columnName).intValue(),
    281                     cursor.getInt(cursor.getColumnIndex(columnName)));
    282         }
    283     }
    284 
    285     private static void verifyLongColumn(Cursor cursor, ContentValues expectedValues,
    286             String columnName) {
    287         if (expectedValues.containsKey(columnName)) {
    288             assertEquals(expectedValues.getAsLong(columnName).longValue(),
    289                     cursor.getLong(cursor.getColumnIndex(columnName)));
    290         }
    291     }
    292 
    293     private static void verifyBlobColumn(Cursor cursor, ContentValues expectedValues,
    294             String columnName) {
    295         if (expectedValues.containsKey(columnName)) {
    296             byte[] expected = expectedValues.getAsByteArray(columnName);
    297             byte[] actual = cursor.getBlob(cursor.getColumnIndex(columnName));
    298             assertEquals(expected.length, actual.length);
    299             for (int i = 0; i < expected.length; ++i) {
    300                 assertEquals(expected[i], actual[i]);
    301             }
    302         }
    303     }
    304 
    305     private void verifyChannel(Uri channelUri, ContentValues expectedValues, long channelId) {
    306         verifyChannel(channelUri, expectedValues, channelId, true);
    307     }
    308 
    309     private void verifyChannel(Uri channelUri, ContentValues expectedValues, long channelId,
    310             boolean verifyInputId) {
    311         try (Cursor cursor = mContentResolver.query(
    312                 channelUri, CHANNELS_PROJECTION, null, null, null)) {
    313             assertNotNull(cursor);
    314             assertEquals(cursor.getCount(), 1);
    315             assertTrue(cursor.moveToNext());
    316             assertEquals(channelId, cursor.getLong(cursor.getColumnIndex(Channels._ID)));
    317             if (verifyInputId) {
    318                 verifyStringColumn(cursor, expectedValues, Channels.COLUMN_INPUT_ID);
    319             }
    320             verifyStringColumn(cursor, expectedValues, Channels.COLUMN_TYPE);
    321             verifyStringColumn(cursor, expectedValues, Channels.COLUMN_SERVICE_TYPE);
    322             verifyIntegerColumn(cursor, expectedValues, Channels.COLUMN_ORIGINAL_NETWORK_ID);
    323             verifyIntegerColumn(cursor, expectedValues, Channels.COLUMN_TRANSPORT_STREAM_ID);
    324             verifyIntegerColumn(cursor, expectedValues, Channels.COLUMN_SERVICE_ID);
    325             verifyStringColumn(cursor, expectedValues, Channels.COLUMN_DISPLAY_NUMBER);
    326             verifyStringColumn(cursor, expectedValues, Channels.COLUMN_DISPLAY_NAME);
    327             verifyStringColumn(cursor, expectedValues, Channels.COLUMN_NETWORK_AFFILIATION);
    328             verifyStringColumn(cursor, expectedValues, Channels.COLUMN_DESCRIPTION);
    329             verifyStringColumn(cursor, expectedValues, Channels.COLUMN_VIDEO_FORMAT);
    330             verifyStringColumn(cursor, expectedValues, Channels.COLUMN_INTERNAL_PROVIDER_ID);
    331             verifyBlobColumn(cursor, expectedValues, Channels.COLUMN_INTERNAL_PROVIDER_DATA);
    332             verifyIntegerColumn(cursor, expectedValues, Channels.COLUMN_VERSION_NUMBER);
    333         }
    334     }
    335 
    336     private void verifyNonExistingColumn(Uri channelUri, long channelId) {
    337         String[] projection = {
    338                 Channels._ID,
    339                 NON_EXISTING_COLUMN_NAME
    340         };
    341         try (Cursor cursor = mContentResolver.query(channelUri, projection, null, null, null)) {
    342             assertNotNull(cursor);
    343             assertEquals(cursor.getCount(), 1);
    344             assertTrue(cursor.moveToNext());
    345             assertEquals(channelId, cursor.getLong(0));
    346             assertNull(cursor.getString(1));
    347             assertEquals(0, cursor.getInt(1));
    348         }
    349     }
    350 
    351     public void testChannelsTable() throws Exception {
    352         if (!Utils.hasTvInputFramework(getContext())) {
    353             return;
    354         }
    355         // Test: insert
    356         ContentValues values = createDummyChannelValues(mInputId, false);
    357 
    358         Uri rowUri = mContentResolver.insert(mChannelsUri, values);
    359         long channelId = ContentUris.parseId(rowUri);
    360         Uri channelUri = TvContract.buildChannelUri(channelId);
    361         verifyChannel(channelUri, values, channelId);
    362 
    363         // Test: update
    364         values.put(Channels.COLUMN_DISPLAY_NUMBER, "1-1");
    365         values.put(Channels.COLUMN_DISPLAY_NAME, "One dash one");
    366         values.put(Channels.COLUMN_INTERNAL_PROVIDER_DATA, "Coffee".getBytes());
    367 
    368         mContentResolver.update(channelUri, values, null, null);
    369         verifyChannel(channelUri, values, channelId);
    370 
    371         // Test: delete
    372         mContentResolver.delete(mChannelsUri, null, null);
    373         try (Cursor cursor = mContentResolver.query(
    374                 mChannelsUri, CHANNELS_PROJECTION, null, null, null)) {
    375             assertEquals(0, cursor.getCount());
    376         }
    377     }
    378 
    379     public void testChannelsTableForModifyChannelType() throws Exception {
    380         if (!Utils.hasTvInputFramework(getContext())) {
    381             return;
    382         }
    383         ContentValues values = createDummyChannelValues(mInputId, true);
    384         Uri channelUri = mContentResolver.insert(mChannelsUri, values);
    385         long channelId = ContentUris.parseId(channelUri);
    386 
    387         // Test: try to modify channel type should fail
    388         values.put(Channels.COLUMN_TYPE, Channels.TYPE_OTHER);
    389         values.put(Channels.COLUMN_DISPLAY_NAME, "One dash one");
    390         int result = mContentResolver.update(channelUri, values, null, null);
    391         assertEquals(0, result);
    392 
    393         // Test: update with same channel type should succeed
    394         values.put(Channels.COLUMN_TYPE, Channels.TYPE_PREVIEW);
    395         result = mContentResolver.update(channelUri, values, null, null);
    396         assertEquals(1, result);
    397         verifyChannel(channelUri, values, channelId);
    398 
    399         // Test: update channel type for all channels should fail
    400         result = mContentResolver.update(Channels.CONTENT_URI, values, null, null);
    401         assertEquals(0, result);
    402     }
    403 
    404     public void testChannelsTableForInputId() throws Exception {
    405         if (!Utils.hasTvInputFramework(getContext())) {
    406             return;
    407         }
    408         // Non-preview channels should not be inserted without input ID
    409         ContentValues values = createDummyChannelValues(mInputId, false);
    410         values.remove(Channels.COLUMN_INPUT_ID);
    411         Uri rowUri = mContentResolver.insert(Channels.CONTENT_URI, values);
    412         assertNull(rowUri);
    413 
    414         // Non-preview channels should not be inserted with null input ID
    415         values.putNull(Channels.COLUMN_INPUT_ID);
    416         rowUri = mContentResolver.insert(Channels.CONTENT_URI, values);
    417         assertNull(rowUri);
    418 
    419         // Preview channels can be inserted with null input ID
    420         values.put(Channels.COLUMN_TYPE, Channels.TYPE_PREVIEW);
    421         rowUri = mContentResolver.insert(Channels.CONTENT_URI, values);
    422         long channelId = ContentUris.parseId(rowUri);
    423         Uri channelUri = TvContract.buildChannelUri(channelId);
    424         verifyChannel(channelUri, values, channelId, false);
    425 
    426         // Preview channels can be inserted without input ID
    427         values.remove(Channels.COLUMN_INPUT_ID);
    428         rowUri = mContentResolver.insert(Channels.CONTENT_URI, values);
    429         channelId = ContentUris.parseId(rowUri);
    430         channelUri = TvContract.buildChannelUri(channelId);
    431         verifyChannel(channelUri, values, channelId, false);
    432     }
    433 
    434     public void testChannelsTableForModifyIdAndPackageName() throws Exception {
    435         if (!Utils.hasTvInputFramework(getContext())) {
    436             return;
    437         }
    438         ContentValues baseValues = createDummyChannelValues(mInputId, false);
    439         Uri channelUri = mContentResolver.insert(mChannelsUri, baseValues);
    440         long channelId = ContentUris.parseId(channelUri);
    441 
    442         ContentValues values = new ContentValues(baseValues);
    443         values.put(Channels._ID, channelId);
    444         int result = mContentResolver.update(channelUri, values, null, null);
    445         assertEquals(1, result);
    446         values.put(Channels._ID, channelId + 1);
    447         try {
    448             mContentResolver.update(channelUri, values, null, null);
    449             fail("Channels._ID should not be changed.");
    450         } catch (Exception e) {
    451             // Expected.
    452         }
    453 
    454         values = new ContentValues(baseValues);
    455         values.put(Channels.COLUMN_PACKAGE_NAME, getContext().getPackageName());
    456         result = mContentResolver.update(channelUri, values, null, null);
    457         assertEquals(1, result);
    458         values.put(Channels.COLUMN_PACKAGE_NAME, "");
    459         try {
    460             mContentResolver.update(channelUri, values, null, null);
    461             fail("Channels.COLUMN_PACKAGE_NAME should not be changed.");
    462         } catch (Exception e) {
    463             // Expected.
    464         }
    465     }
    466 
    467     public void testChannelsTableForIllegalAccess() throws Exception {
    468         if (!Utils.hasTvInputFramework(getContext())) {
    469             return;
    470         }
    471         ContentValues baseValues = createDummyChannelValues(mInputId, false);
    472         Uri channelUri = mContentResolver.insert(mChannelsUri, baseValues);
    473 
    474         // Test: insert
    475         ContentValues values = new ContentValues(baseValues);
    476         values.put(Channels.COLUMN_BROWSABLE, 1);
    477         try {
    478             mContentResolver.insert(mChannelsUri, values);
    479             fail("Channels.COLUMN_BROWSABLE should be read-only.");
    480         } catch (Exception e) {
    481             // Expected.
    482         }
    483         values = new ContentValues(baseValues);
    484         values.put(Channels.COLUMN_LOCKED, 1);
    485         try {
    486             mContentResolver.insert(mChannelsUri, values);
    487             fail("Channels.COLUMN_LOCKED should be read-only.");
    488         } catch (Exception e) {
    489             // Expected.
    490         }
    491 
    492         // Test: update
    493         values = new ContentValues(baseValues);
    494         values.put(Channels.COLUMN_BROWSABLE, 1);
    495         try {
    496             mContentResolver.update(channelUri, values, null, null);
    497             fail("Channels.COLUMN_BROWSABLE should be read-only.");
    498         } catch (Exception e) {
    499             // Expected.
    500         }
    501         values = new ContentValues(baseValues);
    502         values.put(Channels.COLUMN_LOCKED, 1);
    503         try {
    504             mContentResolver.update(channelUri, values, null, null);
    505             fail("Channels.COLUMN_LOCKED should be read-only.");
    506         } catch (Exception e) {
    507             // Expected.
    508         }
    509 
    510         // Test: query
    511         try (Cursor cursor = mContentResolver.query(
    512                 channelUri,
    513                 new String[]{ Channels.COLUMN_BROWSABLE }, null, null, null)) {
    514             // Channels.COLUMN_BROWSABLE should be readable from application.
    515             assertEquals(1, cursor.getCount());
    516         }
    517         try (Cursor cursor = mContentResolver.query(
    518                 channelUri,
    519                 new String[]{ Channels.COLUMN_LOCKED }, null, null, null)) {
    520             // Channels.COLUMN_LOCKED should be readable from application.
    521             assertEquals(1, cursor.getCount());
    522         }
    523 
    524         mContentResolver.delete(mChannelsUri, null, null);
    525         try (Cursor cursor = mContentResolver.query(
    526                 mChannelsUri, CHANNELS_PROJECTION, null, null, null)) {
    527             assertEquals(0, cursor.getCount());
    528         }
    529     }
    530 
    531     public void testChannelsTableForNonExistingColumns() throws Exception {
    532         if (!Utils.hasTvInputFramework(getContext())) {
    533             return;
    534         }
    535         ContentValues values = createDummyChannelValues(mInputId, false);
    536         values.put(NON_EXISTING_COLUMN_NAME, "dummy value");
    537         Uri rowUri = mContentResolver.insert(mChannelsUri, values);
    538         long channelId = ContentUris.parseId(rowUri);
    539         Uri channelUri = TvContract.buildChannelUri(channelId);
    540         verifyChannel(channelUri, values, channelId);
    541         verifyNonExistingColumn(channelUri, channelId);
    542 
    543         // Test: update
    544         mContentResolver.update(channelUri, values, null, null);
    545         verifyChannel(channelUri, values, channelId);
    546         verifyNonExistingColumn(channelUri, channelId);
    547 
    548         // Test: delete
    549         mContentResolver.delete(mChannelsUri, null, null);
    550         try (Cursor cursor = mContentResolver.query(
    551                 mChannelsUri, CHANNELS_PROJECTION, null, null, null)) {
    552             assertEquals(0, cursor.getCount());
    553         }
    554     }
    555 
    556     private void verifyProgram(Uri programUri, ContentValues expectedValues, long programId) {
    557         try (Cursor cursor = mContentResolver.query(
    558                 programUri, null, null, null, null)) {
    559             assertNotNull(cursor);
    560             assertEquals(cursor.getCount(), 1);
    561             assertTrue(cursor.moveToNext());
    562             assertEquals(programId, cursor.getLong(cursor.getColumnIndex(Programs._ID)));
    563             verifyLongColumn(cursor, expectedValues, Programs.COLUMN_CHANNEL_ID);
    564             verifyStringColumn(cursor, expectedValues, Programs.COLUMN_TITLE);
    565             verifyStringColumn(cursor, expectedValues, Programs.COLUMN_SEASON_DISPLAY_NUMBER);
    566             verifyStringColumn(cursor, expectedValues, Programs.COLUMN_SEASON_TITLE);
    567             verifyStringColumn(cursor, expectedValues, Programs.COLUMN_EPISODE_DISPLAY_NUMBER);
    568             verifyStringColumn(cursor, expectedValues, Programs.COLUMN_EPISODE_TITLE);
    569             verifyLongColumn(cursor, expectedValues, Programs.COLUMN_START_TIME_UTC_MILLIS);
    570             verifyLongColumn(cursor, expectedValues, Programs.COLUMN_END_TIME_UTC_MILLIS);
    571             verifyStringColumn(cursor, expectedValues, Programs.COLUMN_BROADCAST_GENRE);
    572             verifyStringColumn(cursor, expectedValues, Programs.COLUMN_CANONICAL_GENRE);
    573             verifyStringColumn(cursor, expectedValues, Programs.COLUMN_SHORT_DESCRIPTION);
    574             verifyStringColumn(cursor, expectedValues, Programs.COLUMN_LONG_DESCRIPTION);
    575             verifyIntegerColumn(cursor, expectedValues, Programs.COLUMN_VIDEO_WIDTH);
    576             verifyIntegerColumn(cursor, expectedValues, Programs.COLUMN_VIDEO_HEIGHT);
    577             verifyStringColumn(cursor, expectedValues, Programs.COLUMN_AUDIO_LANGUAGE);
    578             verifyStringColumn(cursor, expectedValues, Programs.COLUMN_CONTENT_RATING);
    579             verifyStringColumn(cursor, expectedValues, Programs.COLUMN_POSTER_ART_URI);
    580             verifyStringColumn(cursor, expectedValues, Programs.COLUMN_THUMBNAIL_URI);
    581             verifyBlobColumn(cursor, expectedValues, Programs.COLUMN_INTERNAL_PROVIDER_DATA);
    582             verifyIntegerColumn(cursor, expectedValues, Programs.COLUMN_VERSION_NUMBER);
    583             verifyStringColumn(cursor, expectedValues, Programs.COLUMN_REVIEW_RATING_STYLE);
    584             verifyStringColumn(cursor, expectedValues, Programs.COLUMN_REVIEW_RATING);
    585         }
    586     }
    587 
    588     private void verifyPreviewProgram(Uri programUri, ContentValues expectedValues,
    589             long programId) {
    590         try (Cursor cursor = mContentResolver.query(
    591                 programUri, null, null, null, null)) {
    592             assertNotNull(cursor);
    593             assertEquals(cursor.getCount(), 1);
    594             assertTrue(cursor.moveToNext());
    595             assertEquals(programId, cursor.getLong(cursor.getColumnIndex(PreviewPrograms._ID)));
    596             verifyLongColumn(cursor, expectedValues, PreviewPrograms.COLUMN_CHANNEL_ID);
    597             verifyStringColumn(cursor, expectedValues, PreviewPrograms.COLUMN_TITLE);
    598             verifyStringColumn(cursor, expectedValues,
    599                     PreviewPrograms.COLUMN_SEASON_DISPLAY_NUMBER);
    600             verifyStringColumn(cursor, expectedValues, PreviewPrograms.COLUMN_SEASON_TITLE);
    601             verifyStringColumn(cursor, expectedValues,
    602                     PreviewPrograms.COLUMN_EPISODE_DISPLAY_NUMBER);
    603             verifyStringColumn(cursor, expectedValues, PreviewPrograms.COLUMN_EPISODE_TITLE);
    604             verifyStringColumn(cursor, expectedValues, PreviewPrograms.COLUMN_CANONICAL_GENRE);
    605             verifyStringColumn(cursor, expectedValues, PreviewPrograms.COLUMN_SHORT_DESCRIPTION);
    606             verifyStringColumn(cursor, expectedValues, PreviewPrograms.COLUMN_LONG_DESCRIPTION);
    607             verifyIntegerColumn(cursor, expectedValues, PreviewPrograms.COLUMN_VIDEO_WIDTH);
    608             verifyIntegerColumn(cursor, expectedValues, PreviewPrograms.COLUMN_VIDEO_HEIGHT);
    609             verifyStringColumn(cursor, expectedValues, PreviewPrograms.COLUMN_AUDIO_LANGUAGE);
    610             verifyStringColumn(cursor, expectedValues, PreviewPrograms.COLUMN_CONTENT_RATING);
    611             verifyStringColumn(cursor, expectedValues, PreviewPrograms.COLUMN_POSTER_ART_URI);
    612             verifyStringColumn(cursor, expectedValues, PreviewPrograms.COLUMN_THUMBNAIL_URI);
    613             verifyBlobColumn(cursor, expectedValues, PreviewPrograms.COLUMN_INTERNAL_PROVIDER_DATA);
    614             verifyIntegerColumn(cursor, expectedValues, PreviewPrograms.COLUMN_VERSION_NUMBER);
    615             verifyStringColumn(cursor, expectedValues, PreviewPrograms.COLUMN_INTERNAL_PROVIDER_ID);
    616             verifyStringColumn(cursor, expectedValues, PreviewPrograms.COLUMN_PREVIEW_VIDEO_URI);
    617             verifyIntegerColumn(cursor, expectedValues,
    618                     PreviewPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS);
    619             verifyIntegerColumn(cursor, expectedValues, PreviewPrograms.COLUMN_DURATION_MILLIS);
    620             verifyStringColumn(cursor, expectedValues, PreviewPrograms.COLUMN_INTENT_URI);
    621             verifyIntegerColumn(cursor, expectedValues, PreviewPrograms.COLUMN_WEIGHT);
    622             verifyIntegerColumn(cursor, expectedValues, PreviewPrograms.COLUMN_TYPE);
    623             verifyIntegerColumn(cursor, expectedValues,
    624                     PreviewPrograms.COLUMN_POSTER_ART_ASPECT_RATIO);
    625             verifyIntegerColumn(cursor, expectedValues,
    626                     PreviewPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO);
    627             verifyStringColumn(cursor, expectedValues, PreviewPrograms.COLUMN_LOGO_URI);
    628             verifyIntegerColumn(cursor, expectedValues, PreviewPrograms.COLUMN_AVAILABILITY);
    629             verifyStringColumn(cursor, expectedValues, PreviewPrograms.COLUMN_STARTING_PRICE);
    630             verifyStringColumn(cursor, expectedValues, PreviewPrograms.COLUMN_OFFER_PRICE);
    631             verifyStringColumn(cursor, expectedValues, PreviewPrograms.COLUMN_RELEASE_DATE);
    632             verifyIntegerColumn(cursor, expectedValues, PreviewPrograms.COLUMN_ITEM_COUNT);
    633             verifyIntegerColumn(cursor, expectedValues, PreviewPrograms.COLUMN_LIVE);
    634             verifyIntegerColumn(cursor, expectedValues, PreviewPrograms.COLUMN_INTERACTION_TYPE);
    635             verifyIntegerColumn(cursor, expectedValues, PreviewPrograms.COLUMN_INTERACTION_COUNT);
    636             verifyStringColumn(cursor, expectedValues, PreviewPrograms.COLUMN_AUTHOR);
    637             verifyIntegerColumn(cursor, expectedValues, PreviewPrograms.COLUMN_REVIEW_RATING_STYLE);
    638             verifyStringColumn(cursor, expectedValues, PreviewPrograms.COLUMN_REVIEW_RATING);
    639         }
    640     }
    641 
    642     private void verifyWatchNextProgram(Uri programUri, ContentValues expectedValues,
    643             long programId) {
    644         verifyPreviewProgram(programUri, expectedValues, programId);
    645         try (Cursor cursor = mContentResolver.query(programUri, null, null, null, null)) {
    646             assertNotNull(cursor);
    647             assertEquals(cursor.getCount(), 1);
    648             assertTrue(cursor.moveToNext());
    649 
    650             verifyIntegerColumn(cursor, expectedValues, WatchNextPrograms.COLUMN_WATCH_NEXT_TYPE);
    651             verifyLongColumn(cursor, expectedValues,
    652                     WatchNextPrograms.COLUMN_LAST_ENGAGEMENT_TIME_UTC_MILLIS);
    653         }
    654     }
    655 
    656     private void verifyDeprecatedColumsInProgram(Uri programUri, ContentValues expectedValues) {
    657         final String[] DEPRECATED_COLUMNS_PROJECTION = {
    658             Programs.COLUMN_SEASON_NUMBER,
    659             Programs.COLUMN_EPISODE_NUMBER,
    660         };
    661         try (Cursor cursor = mContentResolver.query(
    662                 programUri, DEPRECATED_COLUMNS_PROJECTION, null, null, null)) {
    663             assertNotNull(cursor);
    664             assertEquals(cursor.getCount(), 1);
    665             assertTrue(cursor.moveToNext());
    666             verifyIntegerColumn(cursor, expectedValues, Programs.COLUMN_SEASON_NUMBER);
    667             verifyIntegerColumn(cursor, expectedValues, Programs.COLUMN_EPISODE_NUMBER);
    668         }
    669     }
    670 
    671     private void verifyLogoIsReadable(Uri logoUri) throws Exception {
    672         try (AssetFileDescriptor fd = mContentResolver.openAssetFileDescriptor(logoUri, "r")) {
    673             try (InputStream is = fd.createInputStream()) {
    674                 // Assure that the stream is decodable as a Bitmap.
    675                 BitmapFactory.decodeStream(is);
    676             }
    677         }
    678     }
    679 
    680     public void testChannelLogo() throws Exception {
    681         if (!Utils.hasTvInputFramework(getContext())) {
    682             return;
    683         }
    684         // Set-up: add a channel.
    685         ContentValues values = createDummyChannelValues(mInputId, false);
    686         Uri channelUri = mContentResolver.insert(mChannelsUri, values);
    687         Uri logoUri = TvContract.buildChannelLogoUri(channelUri);
    688         Bitmap logo = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.robot);
    689 
    690         // Write
    691         try (AssetFileDescriptor fd = mContentResolver.openAssetFileDescriptor(logoUri, "w")) {
    692             try (OutputStream os = fd.createOutputStream()) {
    693                 logo.compress(Bitmap.CompressFormat.PNG, 100, os);
    694             }
    695         }
    696 
    697         // Give some time for TvProvider to process the logo.
    698         Thread.sleep(OPERATION_TIME);
    699 
    700         // Read and verify
    701         verifyLogoIsReadable(logoUri);
    702 
    703         // Read and verify using alternative logo URI.
    704         verifyLogoIsReadable(TvContract.buildChannelLogoUri(ContentUris.parseId(channelUri)));
    705     }
    706 
    707     public void verifyProgramsTable(Uri programsUri, long channelId) {
    708         if (!Utils.hasTvInputFramework(getContext())) {
    709             return;
    710         }
    711         // Test: insert
    712         ContentValues values = createDummyProgramValues(channelId);
    713 
    714         Uri rowUri = mContentResolver.insert(programsUri, values);
    715         long programId = ContentUris.parseId(rowUri);
    716         Uri programUri = TvContract.buildProgramUri(programId);
    717         verifyProgram(programUri, values, programId);
    718 
    719         // Test: update
    720         values.put(Programs.COLUMN_TITLE, "new_program_title");
    721         values.put(Programs.COLUMN_SHORT_DESCRIPTION, "");
    722         values.put(Programs.COLUMN_INTERNAL_PROVIDER_DATA, "Coffee".getBytes());
    723 
    724         mContentResolver.update(programUri, values, null, null);
    725         verifyProgram(programUri, values, programId);
    726 
    727         // Test: delete
    728         mContentResolver.delete(programsUri, null, null);
    729         try (Cursor cursor = mContentResolver.query(programsUri, null, null, null, null)) {
    730             assertEquals(0, cursor.getCount());
    731         }
    732     }
    733 
    734     public void verifyPreviewProgramsTable(Uri previewProgramsUri, long channelId) {
    735         if (!Utils.hasTvInputFramework(getContext())) {
    736             return;
    737         }
    738         // Test: insert
    739         ContentValues values = createDummyPreviewProgramValues(channelId);
    740 
    741         Uri rowUri = mContentResolver.insert(previewProgramsUri, values);
    742         long programId = ContentUris.parseId(rowUri);
    743         Uri programUri = TvContract.buildPreviewProgramUri(programId);
    744         verifyPreviewProgram(programUri, values, programId);
    745 
    746         values.remove(PreviewPrograms.COLUMN_TYPE);
    747         try {
    748             mContentResolver.insert(previewProgramsUri, values);
    749             fail("Type should be a required column.");
    750         } catch (IllegalArgumentException e) {
    751             // Expected.
    752         }
    753 
    754         // Test: update
    755         values.put(PreviewPrograms.COLUMN_EPISODE_TITLE, "Sample title");
    756         values.put(PreviewPrograms.COLUMN_SHORT_DESCRIPTION, "Short description");
    757         values.put(PreviewPrograms.COLUMN_INTERNAL_PROVIDER_DATA, "Coffee".getBytes());
    758 
    759         mContentResolver.update(programUri, values, null, null);
    760         verifyPreviewProgram(programUri, values, programId);
    761 
    762         // Test: delete
    763         mContentResolver.delete(previewProgramsUri, null, null);
    764         try (Cursor cursor = mContentResolver.query(previewProgramsUri, null, null, null, null)) {
    765             assertEquals(0, cursor.getCount());
    766         }
    767     }
    768 
    769     public void verifyWatchNextProgramsTable(Uri watchNextProgramsUri) {
    770         if (!Utils.hasTvInputFramework(getContext())) {
    771             return;
    772         }
    773         // Test: insert
    774         ContentValues values = createDummyWatchNextProgramValues();
    775 
    776         Uri rowUri = mContentResolver.insert(watchNextProgramsUri, values);
    777         long programId = ContentUris.parseId(rowUri);
    778         Uri programUri = TvContract.buildWatchNextProgramUri(programId);
    779         verifyWatchNextProgram(programUri, values, programId);
    780 
    781         values.remove(WatchNextPrograms.COLUMN_TYPE);
    782         try {
    783             mContentResolver.insert(watchNextProgramsUri, values);
    784             fail("Type should be a required column.");
    785         } catch (IllegalArgumentException e) {
    786             // Expected.
    787         }
    788 
    789         // Test: update
    790         values.put(WatchNextPrograms.COLUMN_EPISODE_TITLE, "Sample title");
    791         values.put(WatchNextPrograms.COLUMN_SHORT_DESCRIPTION, "Short description");
    792         values.put(WatchNextPrograms.COLUMN_CONTENT_ID, "CID-4328-2548");
    793 
    794         mContentResolver.update(programUri, values, null, null);
    795         verifyWatchNextProgram(programUri, values, programId);
    796 
    797         // Test: delete
    798         mContentResolver.delete(watchNextProgramsUri, null, null);
    799         try (Cursor cursor = mContentResolver.query(watchNextProgramsUri, null, null, null, null)) {
    800             assertEquals(0, cursor.getCount());
    801         }
    802     }
    803 
    804     public void verifyProgramsTableWithDeprecatedColumns(Uri programsUri, long channelId) {
    805         if (!Utils.hasTvInputFramework(getContext())) {
    806             return;
    807         }
    808         // Test: insert
    809         ContentValues expected = createDummyProgramValues(channelId);
    810         expected.put(Programs.COLUMN_SEASON_DISPLAY_NUMBER, "3");
    811         expected.put(Programs.COLUMN_EPISODE_DISPLAY_NUMBER, "9");
    812 
    813         ContentValues input = new ContentValues(expected);
    814         input.remove(Programs.COLUMN_SEASON_DISPLAY_NUMBER);
    815         input.remove(Programs.COLUMN_EPISODE_DISPLAY_NUMBER);
    816         input.put(Programs.COLUMN_SEASON_NUMBER, 3);
    817         input.put(Programs.COLUMN_EPISODE_NUMBER, 9);
    818 
    819         Uri rowUri = mContentResolver.insert(programsUri, input);
    820         long programId = ContentUris.parseId(rowUri);
    821         Uri programUri = TvContract.buildProgramUri(programId);
    822         verifyProgram(programUri, expected, programId);
    823         verifyDeprecatedColumsInProgram(programUri, input);
    824 
    825         // Test: update
    826         expected.put(Programs.COLUMN_SEASON_DISPLAY_NUMBER, "4");
    827         expected.put(Programs.COLUMN_EPISODE_DISPLAY_NUMBER, "10");
    828         input.put(Programs.COLUMN_SEASON_NUMBER, 4);
    829         input.put(Programs.COLUMN_EPISODE_NUMBER, 10);
    830 
    831         mContentResolver.update(programUri, input, null, null);
    832         verifyProgram(programUri, expected, programId);
    833         verifyDeprecatedColumsInProgram(programUri, input);
    834 
    835         // Test: delete
    836         mContentResolver.delete(programsUri, null, null);
    837         try (Cursor cursor = mContentResolver.query(
    838                 programsUri, PROGRAMS_PROJECTION, null, null, null)) {
    839             assertEquals(0, cursor.getCount());
    840         }
    841     }
    842 
    843     public void testProgramsTable() throws Exception {
    844         if (!Utils.hasTvInputFramework(getContext())) {
    845             return;
    846         }
    847         // Set-up: add a channel.
    848         ContentValues values = createDummyChannelValues(mInputId, false);
    849         Uri channelUri = mContentResolver.insert(mChannelsUri, values);
    850         long channelId = ContentUris.parseId(channelUri);
    851 
    852         verifyProgramsTable(TvContract.buildProgramsUriForChannel(channelId), channelId);
    853         verifyProgramsTable(TvContract.buildProgramsUriForChannel(channelUri), channelId);
    854         verifyProgramsTableWithDeprecatedColumns(TvContract.buildProgramsUriForChannel(channelId),
    855                 channelId);
    856         verifyProgramsTableWithDeprecatedColumns(TvContract.buildProgramsUriForChannel(channelUri),
    857                 channelId);
    858     }
    859 
    860     public void testPreviewProgramsTable() throws Exception {
    861         if (!Utils.hasTvInputFramework(getContext())) {
    862             return;
    863         }
    864         // Set-up: add a preview type channel.
    865         ContentValues values = createDummyChannelValues(mInputId, true);
    866         Uri channelUri = mContentResolver.insert(mChannelsUri, values);
    867         long channelId = ContentUris.parseId(channelUri);
    868 
    869         verifyPreviewProgramsTable(PreviewPrograms.CONTENT_URI, channelId);
    870     }
    871 
    872     public void testWatchNextProgramsTable() throws Exception {
    873         if (!Utils.hasTvInputFramework(getContext())) {
    874             return;
    875         }
    876         verifyWatchNextProgramsTable(WatchNextPrograms.CONTENT_URI);
    877     }
    878 
    879     public void testPreviewProgramsTableForIllegalAccess() throws Exception {
    880         if (!Utils.hasTvInputFramework(getContext())) {
    881             return;
    882         }
    883         // Set-up: add a channel and preview program.
    884         ContentValues values = createDummyChannelValues(mInputId, true);
    885         Uri channelUri = mContentResolver.insert(mChannelsUri, values);
    886         long channelId = ContentUris.parseId(channelUri);
    887         Uri previewProgramsUri = TvContract.buildPreviewProgramsUriForChannel(channelId);
    888         values = createDummyPreviewProgramValues(channelId);
    889         Uri previewProgramUri = mContentResolver.insert(previewProgramsUri, values);
    890 
    891         values.put(PreviewPrograms.COLUMN_BROWSABLE, 1);
    892         try {
    893             mContentResolver.insert(previewProgramUri, values);
    894             fail("PreviewPrograms.COLUMN_BROWSABLE should not be accessible.");
    895         } catch (Exception e) {
    896             // Expected.
    897         }
    898 
    899         mContentResolver.delete(previewProgramUri, null, null);
    900         try (Cursor cursor = mContentResolver.query(
    901                 previewProgramUri, PROGRAMS_PROJECTION, null, null, null)) {
    902             assertEquals(0, cursor.getCount());
    903         }
    904     }
    905 
    906     public void testPreviewProgramsTableForModifyChannelId() throws Exception {
    907         if (!Utils.hasTvInputFramework(getContext())) {
    908             return;
    909         }
    910         // Set-up: add a channel and preview program.
    911         ContentValues values = createDummyChannelValues(mInputId, true);
    912         Uri channelUri = mContentResolver.insert(mChannelsUri, values);
    913         long channelId = ContentUris.parseId(channelUri);
    914         Uri previewProgramsUri = TvContract.buildPreviewProgramsUriForChannel(channelId);
    915         values = createDummyPreviewProgramValues(channelId);
    916         Uri previewProgramUri = mContentResolver.insert(previewProgramsUri, values);
    917 
    918         // Channel ID cannot be changed
    919         values.put(PreviewPrograms.COLUMN_ITEM_COUNT, 1);
    920         values.put(PreviewPrograms.COLUMN_CHANNEL_ID, channelId + 1);
    921         int result = mContentResolver.update(previewProgramUri, values, null, null);
    922         assertEquals(0, result);
    923 
    924         // Same Channel ID should not fail updating
    925         values.put(PreviewPrograms.COLUMN_CHANNEL_ID, channelId);
    926         result = mContentResolver.update(previewProgramUri, values, null, null);
    927         assertEquals(1, result);
    928 
    929         // Update without Channel ID should not fail
    930         values.put(PreviewPrograms.COLUMN_ITEM_COUNT, 2);
    931         values.remove(PreviewPrograms.COLUMN_CHANNEL_ID);
    932         result = mContentResolver.update(previewProgramUri, values, null, null);
    933         assertEquals(1, result);
    934 
    935         // Update channel ID with CONTENT_URI should fail
    936         values.put(PreviewPrograms.COLUMN_CHANNEL_ID, channelId);
    937         result = mContentResolver.update(PreviewPrograms.CONTENT_URI, values, null, null);
    938         assertEquals(0, result);
    939 
    940         mContentResolver.delete(previewProgramUri, null, null);
    941         try (Cursor cursor = mContentResolver.query(
    942                 previewProgramUri, PROGRAMS_PROJECTION, null, null, null)) {
    943             assertEquals(0, cursor.getCount());
    944         }
    945     }
    946 
    947     private void verifyOverlap(long startMillis, long endMillis, int expectedCount,
    948             long channelId, Uri channelUri) {
    949         try (Cursor cursor = mContentResolver.query(TvContract.buildProgramsUriForChannel(
    950                 channelId, startMillis, endMillis), PROGRAMS_PROJECTION, null, null, null)) {
    951             assertEquals(expectedCount, cursor.getCount());
    952         }
    953         try (Cursor cursor = mContentResolver.query(TvContract.buildProgramsUriForChannel(
    954                 channelUri, startMillis, endMillis), PROGRAMS_PROJECTION, null, null, null)) {
    955             assertEquals(expectedCount, cursor.getCount());
    956         }
    957     }
    958 
    959     public void testProgramsScheduleOverlap() throws Exception {
    960         if (!Utils.hasTvInputFramework(getContext())) {
    961             return;
    962         }
    963         final long programStartMillis = 1403712000000l;  // Jun 25 2014 16:00 UTC
    964         final long programEndMillis = 1403719200000l;  // Jun 25 2014 18:00 UTC
    965         final long hour = 3600000l;
    966 
    967         // Set-up: add a channel and program.
    968         ContentValues values = createDummyChannelValues(mInputId, false);
    969         Uri channelUri = mContentResolver.insert(mChannelsUri, values);
    970         long channelId = ContentUris.parseId(channelUri);
    971         Uri programsUri = TvContract.buildProgramsUriForChannel(channelId);
    972         values = createDummyProgramValues(channelId);
    973         values.put(Programs.COLUMN_START_TIME_UTC_MILLIS, programStartMillis);
    974         values.put(Programs.COLUMN_END_TIME_UTC_MILLIS, programEndMillis);
    975         mContentResolver.insert(programsUri, values);
    976 
    977         // Overlap 1: starts early, ends early.
    978         verifyOverlap(programStartMillis - hour, programEndMillis - hour, 1, channelId, channelUri);
    979 
    980         // Overlap 2: starts early, ends late.
    981         verifyOverlap(programStartMillis - hour, programEndMillis + hour, 1, channelId, channelUri);
    982 
    983         // Overlap 3: starts early, ends late.
    984         verifyOverlap(programStartMillis + hour / 2, programEndMillis - hour / 2, 1,
    985                 channelId, channelUri);
    986 
    987         // Overlap 4: starts late, ends late.
    988         verifyOverlap(programStartMillis + hour, programEndMillis + hour, 1, channelId, channelUri);
    989 
    990         // Non-overlap 1: ends too early.
    991         verifyOverlap(programStartMillis - hour, programStartMillis - hour / 2, 0,
    992                 channelId, channelUri);
    993 
    994         // Non-overlap 2: starts too late.
    995         verifyOverlap(programEndMillis + hour, programEndMillis + hour * 2, 0,
    996                 channelId, channelUri);
    997 
    998         // Non-overlap 3: invalid start and end times.
    999         verifyOverlap(programEndMillis, programStartMillis, 0, channelId, channelUri);
   1000     }
   1001 
   1002     private void verifyRecordedProgram(Uri recordedProgramUri, ContentValues expectedValues,
   1003                 long recordedProgramId) {
   1004         try (Cursor cursor = mContentResolver.query(recordedProgramUri, null, null, null, null)) {
   1005             assertNotNull(cursor);
   1006             assertEquals(cursor.getCount(), 1);
   1007             assertTrue(cursor.moveToNext());
   1008             assertEquals(recordedProgramId, cursor.getLong(cursor.getColumnIndex(
   1009                     RecordedPrograms._ID)));
   1010             verifyLongColumn(cursor, expectedValues, RecordedPrograms.COLUMN_CHANNEL_ID);
   1011             verifyStringColumn(cursor, expectedValues,
   1012                     RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER);
   1013             verifyStringColumn(cursor, expectedValues, RecordedPrograms.COLUMN_SEASON_TITLE);
   1014             verifyStringColumn(cursor, expectedValues,
   1015                     RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER);
   1016             verifyStringColumn(cursor, expectedValues, RecordedPrograms.COLUMN_EPISODE_TITLE);
   1017             verifyLongColumn(cursor, expectedValues, RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS);
   1018             verifyLongColumn(cursor, expectedValues, RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS);
   1019             verifyStringColumn(cursor, expectedValues, RecordedPrograms.COLUMN_BROADCAST_GENRE);
   1020             verifyStringColumn(cursor, expectedValues, RecordedPrograms.COLUMN_CANONICAL_GENRE);
   1021             verifyStringColumn(cursor, expectedValues, RecordedPrograms.COLUMN_SHORT_DESCRIPTION);
   1022             verifyStringColumn(cursor, expectedValues, RecordedPrograms.COLUMN_LONG_DESCRIPTION);
   1023             verifyIntegerColumn(cursor, expectedValues, RecordedPrograms.COLUMN_VIDEO_WIDTH);
   1024             verifyIntegerColumn(cursor, expectedValues, RecordedPrograms.COLUMN_VIDEO_HEIGHT);
   1025             verifyStringColumn(cursor, expectedValues, RecordedPrograms.COLUMN_AUDIO_LANGUAGE);
   1026             verifyStringColumn(cursor, expectedValues, RecordedPrograms.COLUMN_CONTENT_RATING);
   1027             verifyStringColumn(cursor, expectedValues, RecordedPrograms.COLUMN_POSTER_ART_URI);
   1028             verifyStringColumn(cursor, expectedValues, RecordedPrograms.COLUMN_THUMBNAIL_URI);
   1029             verifyIntegerColumn(cursor, expectedValues, RecordedPrograms.COLUMN_SEARCHABLE);
   1030             verifyStringColumn(cursor, expectedValues, RecordedPrograms.COLUMN_RECORDING_DATA_URI);
   1031             verifyIntegerColumn(cursor, expectedValues,
   1032                     RecordedPrograms.COLUMN_RECORDING_DATA_BYTES);
   1033             verifyIntegerColumn(cursor, expectedValues,
   1034                     RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS);
   1035             verifyIntegerColumn(cursor, expectedValues,
   1036                     RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS);
   1037             verifyBlobColumn(cursor, expectedValues,
   1038                     RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA);
   1039             verifyIntegerColumn(cursor, expectedValues,
   1040                     RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1);
   1041             verifyIntegerColumn(cursor, expectedValues,
   1042                     RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2);
   1043             verifyIntegerColumn(cursor, expectedValues,
   1044                     RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3);
   1045             verifyIntegerColumn(cursor, expectedValues,
   1046                     RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4);
   1047             verifyIntegerColumn(cursor, expectedValues, RecordedPrograms.COLUMN_VERSION_NUMBER);
   1048             verifyStringColumn(cursor, expectedValues, RecordedPrograms.COLUMN_REVIEW_RATING_STYLE);
   1049             verifyStringColumn(cursor, expectedValues, RecordedPrograms.COLUMN_REVIEW_RATING);
   1050         }
   1051     }
   1052 
   1053     private void verifyRecordedProgramsTable(Uri recordedProgramsUri, long channelId) {
   1054         // Test: insert
   1055         ContentValues values = createDummyRecordedProgramValues(mInputId, channelId);
   1056 
   1057         Uri rowUri = mContentResolver.insert(recordedProgramsUri, values);
   1058         long recordedProgramId = ContentUris.parseId(rowUri);
   1059         Uri recordedProgramUri = TvContract.buildRecordedProgramUri(recordedProgramId);
   1060         verifyRecordedProgram(recordedProgramUri, values, recordedProgramId);
   1061 
   1062         // Test: update
   1063         values.put(RecordedPrograms.COLUMN_EPISODE_TITLE, "episode_title1");
   1064         values.put(RecordedPrograms.COLUMN_SHORT_DESCRIPTION, "short_description1");
   1065         values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA,
   1066                 "internal_provider_data1".getBytes());
   1067 
   1068         mContentResolver.update(recordedProgramUri, values, null, null);
   1069         verifyRecordedProgram(recordedProgramUri, values, recordedProgramId);
   1070 
   1071         // Test: delete
   1072         mContentResolver.delete(recordedProgramUri, null, null);
   1073         try (Cursor cursor = mContentResolver.query(recordedProgramsUri, null, null, null, null)) {
   1074             assertEquals(0, cursor.getCount());
   1075         }
   1076     }
   1077 
   1078     public void testRecordedProgramsTable() throws Exception {
   1079         if (!Utils.hasTvInputFramework(getContext())) {
   1080             return;
   1081         }
   1082         // Set-up: add a channel.
   1083         ContentValues values = createDummyChannelValues(mInputId, false);
   1084         Uri channelUri = mContentResolver.insert(mChannelsUri, values);
   1085         long channelId = ContentUris.parseId(channelUri);
   1086 
   1087         verifyRecordedProgramsTable(RecordedPrograms.CONTENT_URI, channelId);
   1088     }
   1089 
   1090     private void verifyQueryWithSortOrder(Uri uri, final String[] projection,
   1091             String sortOrder) throws Exception {
   1092         try {
   1093             getContext().getContentResolver().query(uri, projection, null, null, sortOrder);
   1094         } catch (SecurityException e) {
   1095             fail("Setting sort order shoud be allowed for " + uri);
   1096         }
   1097     }
   1098 
   1099     private void verifyQueryWithSelection(Uri uri, final String[] projection,
   1100             String selection) throws Exception {
   1101         try {
   1102             getContext().getContentResolver().query(uri, projection, selection, null, null);
   1103             fail("Setting selection should fail without ACCESS_ALL_EPG_DATA permission for " + uri);
   1104         } catch (SecurityException e) {
   1105             // Expected exception
   1106         }
   1107     }
   1108 
   1109     private void verifyUpdateWithSelection(Uri uri, String selection) throws Exception {
   1110         try {
   1111             ContentValues values = new ContentValues();
   1112             getContext().getContentResolver().update(uri, values, selection, null);
   1113             fail("Setting selection should fail without ACCESS_ALL_EPG_DATA permission for " + uri);
   1114         } catch (SecurityException e) {
   1115             // Expected exception
   1116         }
   1117     }
   1118 
   1119     private void verifyDeleteWithSelection(Uri uri, String selection) throws Exception {
   1120         try {
   1121             getContext().getContentResolver().delete(uri, selection, null);
   1122             fail("Setting selection should fail without ACCESS_ALL_EPG_DATA permission for " + uri);
   1123         } catch (SecurityException e) {
   1124             // Expected exception
   1125         }
   1126     }
   1127 
   1128     public void testAllEpgPermissionBlocksSortOrderOnQuery_Channels() throws Exception {
   1129         if (!Utils.hasTvInputFramework(getContext())) {
   1130             return;
   1131         }
   1132         final String[] projection = { Channels._ID };
   1133         verifyQueryWithSortOrder(Channels.CONTENT_URI, projection, Channels._ID + " ASC");
   1134     }
   1135 
   1136     public void testAllEpgPermissionBlocksSelectionOnQuery_Channels() throws Exception {
   1137         if (!Utils.hasTvInputFramework(getContext())) {
   1138             return;
   1139         }
   1140         final String[] projection = { Channels._ID };
   1141         verifyQueryWithSelection(Channels.CONTENT_URI, projection, Channels._ID + ">0");
   1142     }
   1143 
   1144     public void testAllEpgPermissionBlocksSelectionOnUpdate_Channels() throws Exception {
   1145         if (!Utils.hasTvInputFramework(getContext())) {
   1146             return;
   1147         }
   1148         verifyUpdateWithSelection(Channels.CONTENT_URI, Channels._ID + ">0");
   1149     }
   1150 
   1151     public void testAllEpgPermissionBlocksSelectionOnDelete_Channels() throws Exception {
   1152         if (!Utils.hasTvInputFramework(getContext())) {
   1153             return;
   1154         }
   1155         verifyDeleteWithSelection(Channels.CONTENT_URI, Channels._ID + ">0");
   1156     }
   1157 
   1158     public void testAllEpgPermissionBlocksSortOrderOnQuery_Programs() throws Exception {
   1159         if (!Utils.hasTvInputFramework(getContext())) {
   1160             return;
   1161         }
   1162         final String[] projection = { Programs._ID };
   1163         verifyQueryWithSortOrder(Programs.CONTENT_URI, projection, Programs._ID + " ASC");
   1164     }
   1165 
   1166     public void testAllEpgPermissionBlocksSelectionOnQuery_Programs() throws Exception {
   1167         if (!Utils.hasTvInputFramework(getContext())) {
   1168             return;
   1169         }
   1170         final String[] projection = { Channels._ID };
   1171         verifyQueryWithSelection(Programs.CONTENT_URI, projection, Programs._ID + ">0");
   1172     }
   1173 
   1174     public void testAllEpgPermissionBlocksSelectionOnUpdate_Programs() throws Exception {
   1175         if (!Utils.hasTvInputFramework(getContext())) {
   1176             return;
   1177         }
   1178         verifyUpdateWithSelection(Programs.CONTENT_URI, Programs._ID + ">0");
   1179     }
   1180 
   1181     public void testAllEpgPermissionBlocksSelectionOnDelete_Programs() throws Exception {
   1182         if (!Utils.hasTvInputFramework(getContext())) {
   1183             return;
   1184         }
   1185         verifyDeleteWithSelection(Programs.CONTENT_URI, Programs._ID + ">0");
   1186     }
   1187 
   1188     public void testDefaultValues() throws Exception {
   1189         if (!Utils.hasTvInputFramework(getContext())) {
   1190             return;
   1191         }
   1192         ContentValues values = new ContentValues();
   1193         values.put(Channels.COLUMN_INPUT_ID, mInputId);
   1194         Uri channelUri = mContentResolver.insert(mChannelsUri, values);
   1195         assertNotNull(channelUri);
   1196         try (Cursor cursor = mContentResolver.query(
   1197                 channelUri, CHANNELS_PROJECTION, null, null, null)) {
   1198             cursor.moveToNext();
   1199             assertEquals(Channels.TYPE_OTHER,
   1200                     cursor.getString(cursor.getColumnIndex(Channels.COLUMN_TYPE)));
   1201             assertEquals(Channels.SERVICE_TYPE_AUDIO_VIDEO,
   1202                     cursor.getString(cursor.getColumnIndex(Channels.COLUMN_SERVICE_TYPE)));
   1203         }
   1204         values.clear();
   1205     }
   1206 
   1207     public void testChannelsGetVideoResolution() {
   1208         if (!Utils.hasTvInputFramework(getContext())) {
   1209             return;
   1210         }
   1211         assertEquals(Channels.VIDEO_RESOLUTION_SD, Channels.getVideoResolution(
   1212                 Channels.VIDEO_FORMAT_480I));
   1213         assertEquals(Channels.VIDEO_RESOLUTION_ED, Channels.getVideoResolution(
   1214                 Channels.VIDEO_FORMAT_480P));
   1215         assertEquals(Channels.VIDEO_RESOLUTION_SD, Channels.getVideoResolution(
   1216                 Channels.VIDEO_FORMAT_576I));
   1217         assertEquals(Channels.VIDEO_RESOLUTION_ED, Channels.getVideoResolution(
   1218                 Channels.VIDEO_FORMAT_576P));
   1219         assertEquals(Channels.VIDEO_RESOLUTION_HD, Channels.getVideoResolution(
   1220                 Channels.VIDEO_FORMAT_720P));
   1221         assertEquals(Channels.VIDEO_RESOLUTION_HD, Channels.getVideoResolution(
   1222                 Channels.VIDEO_FORMAT_1080I));
   1223         assertEquals(Channels.VIDEO_RESOLUTION_FHD, Channels.getVideoResolution(
   1224                 Channels.VIDEO_FORMAT_1080P));
   1225         assertEquals(Channels.VIDEO_RESOLUTION_UHD, Channels.getVideoResolution(
   1226                 Channels.VIDEO_FORMAT_2160P));
   1227         assertEquals(Channels.VIDEO_RESOLUTION_UHD, Channels.getVideoResolution(
   1228                 Channels.VIDEO_FORMAT_4320P));
   1229         assertEquals(null, Channels.getVideoResolution("Unknown format"));
   1230     }
   1231 
   1232     public void testProgramsGenresDecode() {
   1233         if (!Utils.hasTvInputFramework(getContext())) {
   1234             return;
   1235         }
   1236         List genres = Arrays.asList(Genres.decode(ENCODED_GENRE_STRING));
   1237         assertEquals(11, genres.size());
   1238         assertTrue(genres.contains(Genres.ANIMAL_WILDLIFE));
   1239         assertTrue(genres.contains(Genres.COMEDY));
   1240         assertTrue(genres.contains(Genres.DRAMA));
   1241         assertTrue(genres.contains(Genres.EDUCATION));
   1242         assertTrue(genres.contains(Genres.FAMILY_KIDS));
   1243         assertTrue(genres.contains(Genres.GAMING));
   1244         assertTrue(genres.contains(Genres.MOVIES));
   1245         assertTrue(genres.contains(Genres.NEWS));
   1246         assertTrue(genres.contains(Genres.SHOPPING));
   1247         assertTrue(genres.contains(Genres.SPORTS));
   1248         assertTrue(genres.contains(Genres.TRAVEL));
   1249         assertFalse(genres.contains(","));
   1250     }
   1251 
   1252     public void testProgramsGenresEncode() {
   1253         if (!Utils.hasTvInputFramework(getContext())) {
   1254             return;
   1255         }
   1256         assertEquals(ENCODED_GENRE_STRING, Genres.encode(Genres.ANIMAL_WILDLIFE,
   1257                 Genres.COMEDY, Genres.DRAMA, Genres.EDUCATION, Genres.FAMILY_KIDS, Genres.GAMING,
   1258                 Genres.MOVIES, Genres.NEWS, Genres.SHOPPING, Genres.SPORTS, Genres.TRAVEL));
   1259     }
   1260 
   1261     public void testProgramsGenresEncodeDecode_empty() {
   1262         if (!Utils.hasTvInputFramework(getContext())) {
   1263             return;
   1264         }
   1265         String[] genres = new String[] {EMPTY_GENRE};
   1266         String expectedEncoded = EMPTY_GENRE;
   1267         checkGenreEncodeDecode(genres, expectedEncoded, 0);
   1268 
   1269         genres = new String[] {EMPTY_GENRE, EMPTY_GENRE, EMPTY_GENRE};
   1270         expectedEncoded = DELIMITER + DELIMITER;
   1271         checkGenreEncodeDecode(genres, expectedEncoded, 0);
   1272     }
   1273 
   1274     public void testProgramsGenresEncodeDecode_simpleDelimiter() {
   1275         if (!Utils.hasTvInputFramework(getContext())) {
   1276             return;
   1277         }
   1278         String[] genres = new String[] {EMPTY_GENRE,
   1279                 COMMA,
   1280                 QUOTE,
   1281                 COMMA + QUOTE,
   1282                 QUOTE + COMMA,
   1283                 COMMA + COMMA,
   1284                 QUOTE + QUOTE,
   1285                 COMMA + QUOTE + COMMA,
   1286                 QUOTE + COMMA + QUOTE};
   1287         String expectedEncoded =
   1288                 DELIMITER + COMMA_ENCODED
   1289                 + DELIMITER + QUOTE_ENCODED
   1290                 + DELIMITER + COMMA_ENCODED + QUOTE_ENCODED
   1291                 + DELIMITER + QUOTE_ENCODED + COMMA_ENCODED
   1292                 + DELIMITER + COMMA_ENCODED + COMMA_ENCODED
   1293                 + DELIMITER + QUOTE_ENCODED + QUOTE_ENCODED
   1294                 + DELIMITER + COMMA_ENCODED + QUOTE_ENCODED + COMMA_ENCODED
   1295                 + DELIMITER + QUOTE_ENCODED + COMMA_ENCODED + QUOTE_ENCODED;
   1296         checkGenreEncodeDecode(genres, expectedEncoded, genres.length - 1);
   1297     }
   1298 
   1299     public void testProgramsGenresEncodeDecode_delimiterWithWhiteSpace() {
   1300         if (!Utils.hasTvInputFramework(getContext())) {
   1301             return;
   1302         }
   1303         String[] genres = new String[] {EMPTY_GENRE,
   1304                 COMMA + WHITE_SPACES,
   1305                 QUOTE + WHITE_SPACES,
   1306                 WHITE_SPACES + COMMA,
   1307                 WHITE_SPACES + QUOTE,
   1308                 WHITE_SPACES + COMMA + WHITE_SPACES,
   1309                 WHITE_SPACES + QUOTE + WHITE_SPACES};
   1310         String expectedEncoded =
   1311                 DELIMITER + COMMA_ENCODED + WHITE_SPACES
   1312                 + DELIMITER + QUOTE_ENCODED + WHITE_SPACES
   1313                 + DELIMITER + WHITE_SPACES + COMMA_ENCODED
   1314                 + DELIMITER + WHITE_SPACES + QUOTE_ENCODED
   1315                 + DELIMITER + WHITE_SPACES + COMMA_ENCODED + WHITE_SPACES
   1316                 + DELIMITER + WHITE_SPACES + QUOTE_ENCODED + WHITE_SPACES;
   1317         checkGenreEncodeDecode(genres, expectedEncoded, genres.length - 1);
   1318     }
   1319 
   1320     public void testProgramsGenresEncodeDecode_all() {
   1321         if (!Utils.hasTvInputFramework(getContext())) {
   1322             return;
   1323         }
   1324         String[] genres = new String[] {EMPTY_GENRE,
   1325                 Genres.COMEDY,
   1326                 Genres.COMEDY + COMMA,
   1327                 Genres.COMEDY + COMMA + Genres.COMEDY,
   1328                 COMMA + Genres.COMEDY + COMMA,
   1329                 QUOTE + Genres.COMEDY + QUOTE,
   1330                 Genres.COMEDY + COMMA + WHITE_SPACES,
   1331                 Genres.COMEDY + COMMA + Genres.COMEDY + WHITE_SPACES,
   1332                 COMMA + Genres.COMEDY + COMMA + WHITE_SPACES,
   1333                 QUOTE + Genres.COMEDY + QUOTE + WHITE_SPACES
   1334         };
   1335         String expectedEncoded =
   1336                 DELIMITER + Genres.COMEDY
   1337                 + DELIMITER + Genres.COMEDY + COMMA_ENCODED
   1338                 + DELIMITER + Genres.COMEDY + COMMA_ENCODED + Genres.COMEDY
   1339                 + DELIMITER + COMMA_ENCODED + Genres.COMEDY + COMMA_ENCODED
   1340                 + DELIMITER + QUOTE_ENCODED + Genres.COMEDY + QUOTE_ENCODED
   1341                 + DELIMITER + Genres.COMEDY + COMMA_ENCODED + WHITE_SPACES
   1342                 + DELIMITER + Genres.COMEDY + COMMA_ENCODED + Genres.COMEDY + WHITE_SPACES
   1343                 + DELIMITER + COMMA_ENCODED + Genres.COMEDY + COMMA_ENCODED + WHITE_SPACES
   1344                 + DELIMITER + QUOTE_ENCODED + Genres.COMEDY + QUOTE_ENCODED + WHITE_SPACES;
   1345         checkGenreEncodeDecode(genres, expectedEncoded, genres.length - 1);
   1346     }
   1347 
   1348     private void checkGenreEncodeDecode(String[] genres, String expectedEncoded,
   1349             int expectedDecodedLength) {
   1350         String encoded = Genres.encode(genres);
   1351         assertEquals(expectedEncoded, encoded);
   1352         String[] decoded = Genres.decode(encoded);
   1353         assertEquals(expectedDecodedLength, decoded.length);
   1354         int decodedIndex = 0;
   1355         for (int i = 0; i < genres.length; ++i) {
   1356             String original = genres[i].trim();
   1357             if (!original.isEmpty()) {
   1358                 assertEquals(original, decoded[decodedIndex++]);
   1359             }
   1360         }
   1361     }
   1362 
   1363     private Uri insertProgramWithBroadcastGenre(String[] broadcastGenre) {
   1364         ContentValues values = createDummyChannelValues(mInputId, false);
   1365         Uri channelUri = mContentResolver.insert(Channels.CONTENT_URI, values);
   1366         long channelId = ContentUris.parseId(channelUri);
   1367         long curTime = System.currentTimeMillis();
   1368         values = new ContentValues();
   1369         values.put(Programs.COLUMN_CHANNEL_ID, channelId);
   1370         values.put(Programs.COLUMN_BROADCAST_GENRE, Genres.encode(broadcastGenre));
   1371         values.put(Programs.COLUMN_START_TIME_UTC_MILLIS, curTime - 60000);
   1372         values.put(Programs.COLUMN_END_TIME_UTC_MILLIS, curTime + 60000);
   1373         Uri programUri = mContentResolver.insert(Programs.CONTENT_URI, values);
   1374         assertNotNull(programUri);
   1375         return programUri;
   1376     }
   1377 
   1378     private void verifyChannelCountWithCanonicalGenre(String canonicalGenre, int expectedCount) {
   1379         Uri channelUri = TvContract.buildChannelsUriForInput(mInputId).buildUpon()
   1380                 .appendQueryParameter(PARAM_CANONICAL_GENRE, canonicalGenre).build();
   1381         try (Cursor c = mContentResolver.query(channelUri, new String[] {Channels._ID}, null, null,
   1382                 null)) {
   1383             assertNotNull(c);
   1384             assertEquals("Query:{Uri=" + channelUri + "}", expectedCount, c.getCount());
   1385         }
   1386     }
   1387 
   1388     public void testBroadcastGenreEncodeDecode() {
   1389         if (!Utils.hasTvInputFramework(getContext())) {
   1390             return;
   1391         }
   1392         String[] broadcastGenre = new String[] {"Animation", "Classic, opera"};
   1393         insertProgramWithBroadcastGenre(broadcastGenre);
   1394         try (Cursor c = mContentResolver.query(Programs.CONTENT_URI,
   1395                 new String[] {Programs.COLUMN_BROADCAST_GENRE}, null, null, null)) {
   1396             assertNotNull(c);
   1397             assertEquals(1, c.getCount());
   1398             c.moveToNext();
   1399             MoreAsserts.assertEquals(broadcastGenre, Genres.decode(c.getString(0)));
   1400         }
   1401     }
   1402 
   1403     public void testBroadcastGenreQueryChannel() {
   1404         if (!Utils.hasTvInputFramework(getContext())) {
   1405             return;
   1406         }
   1407         // "Animation" is mapped to Genres.MOVIES
   1408         // "Classic, opera" is mapped to Genres.MUSIC
   1409         insertProgramWithBroadcastGenre(new String[]{"Animation"});
   1410         insertProgramWithBroadcastGenre(new String[] {"Classic, opera"});
   1411         insertProgramWithBroadcastGenre(new String[]{"Animation", "Classic, opera"});
   1412         // There are two channels which belong to MOVIES genre - channel 1 and 3.
   1413         verifyChannelCountWithCanonicalGenre(Genres.MOVIES, 2);
   1414         // There are two channels which belong to MUSIC genre - channel 2 and 3.
   1415         verifyChannelCountWithCanonicalGenre(Genres.MUSIC, 2);
   1416     }
   1417 
   1418     public void testGenresIsCanonical() {
   1419         if (!Utils.hasTvInputFramework(getContext())) {
   1420             return;
   1421         }
   1422         assertTrue(Genres.isCanonical(Genres.DRAMA));
   1423         assertFalse(Genres.isCanonical("Not a genre"));
   1424     }
   1425 
   1426     public void testUriUtils() {
   1427         if (!Utils.hasTvInputFramework(getContext())) {
   1428             return;
   1429         }
   1430         final Uri CHANNEL_URI_FOR_TUNER = TvContract.buildChannelUri(0);
   1431         final Uri CHANNEL_URI_FOR_PASSTHROUGH_INPUT =
   1432                 TvContract.buildChannelUriForPassthroughInput("inputId");
   1433         final Uri PROGRAM_URI = TvContract.buildProgramUri(0);
   1434         final Uri RECORDED_PROGRAM_URI = TvContract.buildRecordedProgramUri(0);
   1435 
   1436         // Test isChannelUri
   1437         assertTrue(TvContract.isChannelUri(CHANNEL_URI_FOR_TUNER));
   1438         assertTrue(TvContract.isChannelUri(CHANNEL_URI_FOR_PASSTHROUGH_INPUT));
   1439         assertFalse(TvContract.isChannelUri(PROGRAM_URI));
   1440         assertFalse(TvContract.isChannelUri(RECORDED_PROGRAM_URI));
   1441         assertFalse(TvContract.isChannelUri(null));
   1442 
   1443         // Test isChannelUriForPassthroughInput
   1444         assertFalse(TvContract.isChannelUriForPassthroughInput(CHANNEL_URI_FOR_TUNER));
   1445         assertTrue(TvContract.isChannelUriForPassthroughInput(CHANNEL_URI_FOR_PASSTHROUGH_INPUT));
   1446         assertFalse(TvContract.isChannelUriForPassthroughInput(PROGRAM_URI));
   1447         assertFalse(TvContract.isChannelUriForPassthroughInput(RECORDED_PROGRAM_URI));
   1448         assertFalse(TvContract.isChannelUriForPassthroughInput(null));
   1449 
   1450         // Test isChannelUriForTunerInput
   1451         assertTrue(TvContract.isChannelUriForTunerInput(CHANNEL_URI_FOR_TUNER));
   1452         assertFalse(TvContract.isChannelUriForTunerInput(CHANNEL_URI_FOR_PASSTHROUGH_INPUT));
   1453         assertFalse(TvContract.isChannelUriForTunerInput(PROGRAM_URI));
   1454         assertFalse(TvContract.isChannelUriForTunerInput(RECORDED_PROGRAM_URI));
   1455         assertFalse(TvContract.isChannelUriForTunerInput(null));
   1456 
   1457         // Test isProgramUri
   1458         assertFalse(TvContract.isProgramUri(CHANNEL_URI_FOR_TUNER));
   1459         assertFalse(TvContract.isProgramUri(CHANNEL_URI_FOR_PASSTHROUGH_INPUT));
   1460         assertTrue(TvContract.isProgramUri(PROGRAM_URI));
   1461         assertFalse(TvContract.isProgramUri(RECORDED_PROGRAM_URI));
   1462         assertFalse(TvContract.isProgramUri(null));
   1463 
   1464         // Test isRecordedProgramUri
   1465         assertFalse(TvContract.isRecordedProgramUri(CHANNEL_URI_FOR_TUNER));
   1466         assertFalse(TvContract.isRecordedProgramUri(CHANNEL_URI_FOR_PASSTHROUGH_INPUT));
   1467         assertFalse(TvContract.isRecordedProgramUri(PROGRAM_URI));
   1468         assertTrue(TvContract.isRecordedProgramUri(RECORDED_PROGRAM_URI));
   1469         assertFalse(TvContract.isRecordedProgramUri(null));
   1470     }
   1471 }
   1472