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 org.junit.Assert.assertEquals; 19 import static org.junit.Assert.assertNotNull; 20 import static org.junit.Assert.assertTrue; 21 22 import android.content.ContentResolver; 23 import android.content.ContentUris; 24 import android.content.ContentValues; 25 import android.database.Cursor; 26 import android.database.MatrixCursor; 27 import android.media.tv.TvContentRating; 28 import android.net.Uri; 29 import android.os.Build; 30 import android.support.test.InstrumentationRegistry; 31 import android.support.test.filters.SdkSuppress; 32 import android.support.test.filters.SmallTest; 33 import android.support.test.runner.AndroidJUnit4; 34 35 import androidx.tvprovider.media.tv.TvContractCompat.Channels; 36 import androidx.tvprovider.media.tv.TvContractCompat.Programs; 37 38 import org.junit.After; 39 import org.junit.Test; 40 import org.junit.runner.RunWith; 41 42 import java.util.Arrays; 43 import java.util.Objects; 44 45 /** 46 * Tests that programs can be created using the Builder pattern and correctly obtain 47 * values from them. 48 */ 49 @SmallTest 50 @RunWith(AndroidJUnit4.class) 51 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP) 52 public class ProgramTest { 53 @After 54 public void tearDown() { 55 if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) { 56 return; 57 } 58 ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver(); 59 resolver.delete(Channels.CONTENT_URI, null, null); 60 } 61 62 @Test 63 public void testEmptyProgram() { 64 Program emptyProgram = new Program.Builder() 65 .build(); 66 ContentValues contentValues = emptyProgram.toContentValues(); 67 compareProgram(emptyProgram, 68 Program.fromCursor(getProgramCursor(Program.PROJECTION, contentValues)), true); 69 } 70 71 @Test 72 public void testSampleProgram() { 73 Program sampleProgram = new Program.Builder() 74 .setPackageName("My package") 75 .setTitle("Program Title") 76 .setDescription("This is a sample program") 77 .setEpisodeNumber(5) 78 .setSeasonNumber("The Final Season", 7) 79 .setThumbnailUri(Uri.parse("http://www.example.com/programs/poster.png")) 80 .setChannelId(3) 81 .setStartTimeUtcMillis(0) 82 .setEndTimeUtcMillis(1000) 83 .build(); 84 ContentValues contentValues = sampleProgram.toContentValues(); 85 compareProgram(sampleProgram, 86 Program.fromCursor(getProgramCursor(Program.PROJECTION, contentValues)), true); 87 88 Program clonedSampleProgram = new Program.Builder(sampleProgram).build(); 89 compareProgram(sampleProgram, clonedSampleProgram, true); 90 } 91 92 @Test 93 public void testFullyPopulatedProgram() { 94 Program fullyPopulatedProgram = createFullyPopulatedProgram(3); 95 96 ContentValues contentValues = fullyPopulatedProgram.toContentValues(); 97 compareProgram(fullyPopulatedProgram, 98 Program.fromCursor(getProgramCursor(Program.PROJECTION, contentValues)), true); 99 100 Program clonedFullyPopulatedProgram = new Program.Builder(fullyPopulatedProgram).build(); 101 compareProgram(fullyPopulatedProgram, clonedFullyPopulatedProgram, true); 102 } 103 104 @Test 105 public void testChannelWithSystemContentProvider() { 106 if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) { 107 return; 108 } 109 Channel channel = new Channel.Builder() 110 .setInputId("TestInputService") 111 .setType(TvContractCompat.Channels.TYPE_OTHER) 112 .build(); 113 ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver(); 114 Uri channelUri = resolver.insert(Channels.CONTENT_URI, channel.toContentValues()); 115 assertNotNull(channelUri); 116 117 Program fullyPopulatedProgram = 118 createFullyPopulatedProgram(ContentUris.parseId(channelUri)); 119 Uri programUri = resolver.insert(Programs.CONTENT_URI, 120 fullyPopulatedProgram.toContentValues()); 121 122 Program programFromSystemDb = loadProgramFromContentProvider(resolver, programUri); 123 compareProgram(fullyPopulatedProgram, programFromSystemDb, false); 124 } 125 126 @Test 127 public void testProgramUpdateWithContentProvider() { 128 if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) { 129 return; 130 } 131 Channel channel = new Channel.Builder() 132 .setInputId("TestInputService") 133 .setType(TvContractCompat.Channels.TYPE_OTHER) 134 .build(); 135 ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver(); 136 Uri channelUri = resolver.insert(Channels.CONTENT_URI, channel.toContentValues()); 137 assertNotNull(channelUri); 138 139 Program fullyPopulatedProgram = 140 createFullyPopulatedProgram(ContentUris.parseId(channelUri)); 141 Uri programUri = resolver.insert(Programs.CONTENT_URI, 142 fullyPopulatedProgram.toContentValues()); 143 144 Program programFromSystemDb = loadProgramFromContentProvider(resolver, programUri); 145 compareProgram(fullyPopulatedProgram, programFromSystemDb, false); 146 147 // Update a field from a fully loaded program. 148 Program updatedProgram = new Program.Builder(programFromSystemDb) 149 .setDescription("description1").build(); 150 assertEquals(1, resolver.update( 151 programUri, updatedProgram.toContentValues(), null, null)); 152 programFromSystemDb = loadProgramFromContentProvider(resolver, programUri); 153 compareProgram(updatedProgram, programFromSystemDb, false); 154 155 // Update a field with null from a fully loaded program. 156 updatedProgram = new Program.Builder(updatedProgram) 157 .setLongDescription(null).build(); 158 assertEquals(1, resolver.update( 159 programUri, updatedProgram.toContentValues(), null, null)); 160 programFromSystemDb = loadProgramFromContentProvider(resolver, programUri); 161 compareProgram(updatedProgram, programFromSystemDb, false); 162 163 // Update a field without referencing fully loaded program. 164 ContentValues values = new Program.Builder().setDescription("description2").build() 165 .toContentValues(); 166 assertEquals(1, values.size()); 167 assertEquals(1, resolver.update(programUri, values, null, null)); 168 programFromSystemDb = loadProgramFromContentProvider(resolver, programUri); 169 Program expectedProgram = new Program.Builder(programFromSystemDb) 170 .setDescription("description2").build(); 171 compareProgram(expectedProgram, programFromSystemDb, false); 172 } 173 174 @Test 175 public void testProgramEquals() { 176 assertEquals(createFullyPopulatedProgram(1), createFullyPopulatedProgram(1)); 177 } 178 179 private static Program loadProgramFromContentProvider( 180 ContentResolver resolver, Uri programUri) { 181 try (Cursor cursor = resolver.query(programUri, null, null, null, null)) { 182 assertNotNull(cursor); 183 assertEquals(1, cursor.getCount()); 184 cursor.moveToNext(); 185 return Program.fromCursor(cursor); 186 } 187 } 188 189 private static Program createFullyPopulatedProgram(long channelId) { 190 return new Program.Builder() 191 .setSearchable(false) 192 .setThumbnailUri(Uri.parse("http://example.com/thumbnail.png")) 193 .setAudioLanguages(new String [] {"eng", "kor"}) 194 .setCanonicalGenres(new String[] {TvContractCompat.Programs.Genres.MOVIES}) 195 .setContentRatings(new TvContentRating[] { 196 TvContentRating.createRating("com.android.tv", "US_TV", "US_TV_Y7")}) 197 .setDescription("This is a sample program") 198 .setEpisodeNumber("Pilot", 0) 199 .setEpisodeTitle("Hello World") 200 .setLongDescription("This is a longer description than the previous description") 201 .setPosterArtUri(Uri.parse("http://example.com/poster.png")) 202 .setSeasonNumber("The Final Season", 7) 203 .setSeasonTitle("The Final Season") 204 .setTitle("Google") 205 .setVideoHeight(1080) 206 .setVideoWidth(1920) 207 .setInternalProviderFlag1(0x4) 208 .setInternalProviderFlag2(0x3) 209 .setInternalProviderFlag3(0x2) 210 .setInternalProviderFlag4(0x1) 211 .setReviewRatingStyle(Programs.REVIEW_RATING_STYLE_PERCENTAGE) 212 .setReviewRating("83.9") 213 .setChannelId(channelId) 214 .setStartTimeUtcMillis(0) 215 .setEndTimeUtcMillis(1000) 216 .setBroadcastGenres(new String[] {"Music", "Family"}) 217 .setRecordingProhibited(false) 218 .build(); 219 } 220 221 private static void compareProgram(Program programA, Program programB, 222 boolean includeIdAndProtectedFields) { 223 assertTrue(Arrays.equals(programA.getAudioLanguages(), programB.getAudioLanguages())); 224 assertTrue(Arrays.deepEquals(programA.getBroadcastGenres(), programB.getBroadcastGenres())); 225 assertTrue(Arrays.deepEquals(programA.getCanonicalGenres(), programB.getCanonicalGenres())); 226 assertEquals(programA.getChannelId(), programB.getChannelId()); 227 assertTrue(Arrays.deepEquals(programA.getContentRatings(), programB.getContentRatings())); 228 assertEquals(programA.getDescription(), programB.getDescription()); 229 assertEquals(programA.getEndTimeUtcMillis(), programB.getEndTimeUtcMillis()); 230 assertEquals(programA.getEpisodeNumber(), programB.getEpisodeNumber()); 231 assertEquals(programA.getEpisodeTitle(), programB.getEpisodeTitle()); 232 assertEquals(programA.getLongDescription(), programB.getLongDescription()); 233 assertEquals(programA.getPosterArtUri(), programB.getPosterArtUri()); 234 assertEquals(programA.getSeasonNumber(), programB.getSeasonNumber()); 235 assertEquals(programA.getStartTimeUtcMillis(), programB.getStartTimeUtcMillis()); 236 assertEquals(programA.getThumbnailUri(), programB.getThumbnailUri()); 237 assertEquals(programA.getTitle(), programB.getTitle()); 238 assertEquals(programA.getVideoHeight(), programB.getVideoHeight()); 239 assertEquals(programA.getVideoWidth(), programB.getVideoWidth()); 240 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 241 assertEquals(programA.isSearchable(), programB.isSearchable()); 242 assertEquals(programA.getInternalProviderFlag1(), programB.getInternalProviderFlag1()); 243 assertEquals(programA.getInternalProviderFlag2(), programB.getInternalProviderFlag2()); 244 assertEquals(programA.getInternalProviderFlag3(), programB.getInternalProviderFlag3()); 245 assertEquals(programA.getInternalProviderFlag4(), programB.getInternalProviderFlag4()); 246 } 247 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 248 assertTrue(Objects.equals(programA.getSeasonTitle(), programB.getSeasonTitle())); 249 assertTrue(Objects.equals(programA.isRecordingProhibited(), 250 programB.isRecordingProhibited())); 251 } 252 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 253 assertEquals(programA.getReviewRatingStyle(), programB.getReviewRatingStyle()); 254 assertEquals(programA.getReviewRating(), programB.getReviewRating()); 255 } 256 if (includeIdAndProtectedFields) { 257 // Skip row ID since the one from system DB has the valid ID while the other does not. 258 assertEquals(programA.getId(), programB.getId()); 259 assertEquals(programA.getPackageName(), programB.getPackageName()); 260 assertEquals(programA.toContentValues(), programB.toContentValues()); 261 } 262 } 263 264 private static MatrixCursor getProgramCursor(String[] projection, ContentValues contentValues) { 265 MatrixCursor cursor = new MatrixCursor(projection); 266 MatrixCursor.RowBuilder builder = cursor.newRow(); 267 for (String row : projection) { 268 if (row != null) { 269 builder.add(row, contentValues.get(row)); 270 } 271 } 272 cursor.moveToFirst(); 273 return cursor; 274 } 275 } 276