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 17package androidx.tvprovider.media.tv; 18 19import static org.junit.Assert.assertEquals; 20import static org.junit.Assert.assertNotNull; 21import static org.junit.Assert.assertTrue; 22 23import android.content.ContentResolver; 24import android.content.ContentValues; 25import android.content.Intent; 26import android.database.Cursor; 27import android.database.MatrixCursor; 28import android.media.tv.TvContentRating; 29import android.net.Uri; 30import android.support.test.InstrumentationRegistry; 31import android.support.test.filters.SdkSuppress; 32import android.support.test.filters.SmallTest; 33import android.support.test.runner.AndroidJUnit4; 34 35import androidx.tvprovider.media.tv.TvContractCompat.WatchNextPrograms; 36 37import org.junit.Before; 38import org.junit.Test; 39import org.junit.runner.RunWith; 40 41import java.util.Arrays; 42import java.util.Date; 43import java.util.Objects; 44 45/** 46 * Tests that watch next programs can be created using the Builder pattern and correctly obtain 47 * values from them. 48 */ 49@SmallTest 50@SdkSuppress(minSdkVersion = 26) 51@RunWith(AndroidJUnit4.class) 52public class WatchNextProgramTest { 53 54 @Before 55 public void tearDown() { 56 if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) { 57 return; 58 } 59 ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver(); 60 resolver.delete(WatchNextPrograms.CONTENT_URI, null, null); 61 } 62 63 @Test 64 public void testEmptyPreviewProgram() { 65 WatchNextProgram emptyProgram = new WatchNextProgram.Builder().build(); 66 ContentValues contentValues = emptyProgram.toContentValues(true); 67 compareProgram(emptyProgram, 68 WatchNextProgram.fromCursor(getProgramCursor(Program.PROJECTION, contentValues)), 69 true); 70 } 71 72 @Test 73 public void testSampleProgram() { 74 WatchNextProgram sampleProgram = new WatchNextProgram.Builder() 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 .build(); 81 ContentValues contentValues = sampleProgram.toContentValues(true); 82 compareProgram(sampleProgram, 83 WatchNextProgram.fromCursor( 84 getProgramCursor(WatchNextProgram.PROJECTION, contentValues)), true); 85 86 WatchNextProgram clonedSampleProgram = new WatchNextProgram.Builder(sampleProgram).build(); 87 compareProgram(sampleProgram, clonedSampleProgram, true); 88 } 89 90 @Test 91 public void testFullyPopulatedProgram() { 92 WatchNextProgram fullyPopulatedProgram = createFullyPopulatedWatchNextProgram(); 93 ContentValues contentValues = fullyPopulatedProgram.toContentValues(true); 94 compareProgram(fullyPopulatedProgram, 95 WatchNextProgram.fromCursor( 96 getProgramCursor(WatchNextProgram.PROJECTION, contentValues)), true); 97 98 WatchNextProgram clonedFullyPopulatedProgram = 99 new WatchNextProgram.Builder(fullyPopulatedProgram).build(); 100 compareProgram(fullyPopulatedProgram, clonedFullyPopulatedProgram, true); 101 } 102 103 @Test 104 public void testChannelWithSystemContentProvider() { 105 if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) { 106 return; 107 } 108 WatchNextProgram fullyPopulatedProgram = createFullyPopulatedWatchNextProgram(); 109 ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver(); 110 Uri watchNextProgramUri = resolver.insert(WatchNextPrograms.CONTENT_URI, 111 fullyPopulatedProgram.toContentValues()); 112 113 WatchNextProgram programFromSystemDb = 114 loadWatchNextProgramFromContentProvider(resolver, watchNextProgramUri); 115 compareProgram(fullyPopulatedProgram, programFromSystemDb, false); 116 } 117 118 @Test 119 public void testWatchNextProgramUpdateWithContentProvider() { 120 if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) { 121 return; 122 } 123 124 WatchNextProgram fullyPopulatedProgram = createFullyPopulatedWatchNextProgram(); 125 ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver(); 126 Uri watchNextProgramUri = resolver.insert(WatchNextPrograms.CONTENT_URI, 127 fullyPopulatedProgram.toContentValues()); 128 129 WatchNextProgram programFromSystemDb = 130 loadWatchNextProgramFromContentProvider(resolver, watchNextProgramUri); 131 compareProgram(fullyPopulatedProgram, programFromSystemDb, false); 132 133 // Update a field from a fully loaded watch-next program. 134 WatchNextProgram updatedProgram = new WatchNextProgram.Builder(programFromSystemDb) 135 .setInteractionCount(programFromSystemDb.getInteractionCount() + 1).build(); 136 assertEquals(1, resolver.update( 137 watchNextProgramUri, updatedProgram.toContentValues(), null, null)); 138 programFromSystemDb = 139 loadWatchNextProgramFromContentProvider(resolver, watchNextProgramUri); 140 compareProgram(updatedProgram, programFromSystemDb, false); 141 142 // Update a field with null from a fully loaded watch-next program. 143 updatedProgram = new WatchNextProgram.Builder(updatedProgram) 144 .setPreviewVideoUri(null).build(); 145 assertEquals(1, resolver.update( 146 watchNextProgramUri, updatedProgram.toContentValues(), null, null)); 147 programFromSystemDb = loadWatchNextProgramFromContentProvider( 148 resolver, watchNextProgramUri); 149 compareProgram(updatedProgram, programFromSystemDb, false); 150 151 // Update a field without referencing fully watch-next program. 152 ContentValues values = new PreviewProgram.Builder().setInteractionCount(1).build() 153 .toContentValues(); 154 assertEquals(1, values.size()); 155 assertEquals(1, resolver.update(watchNextProgramUri, values, null, null)); 156 programFromSystemDb = loadWatchNextProgramFromContentProvider( 157 resolver, watchNextProgramUri); 158 WatchNextProgram expectedProgram = new WatchNextProgram.Builder(programFromSystemDb) 159 .setInteractionCount(1).build(); 160 compareProgram(expectedProgram, programFromSystemDb, false); 161 } 162 163 @Test 164 public void testWatchNextProgramEquals() { 165 assertEquals(createFullyPopulatedWatchNextProgram(), 166 createFullyPopulatedWatchNextProgram()); 167 } 168 169 private static WatchNextProgram loadWatchNextProgramFromContentProvider( 170 ContentResolver resolver, Uri watchNextProgramUri) { 171 try (Cursor cursor = resolver.query(watchNextProgramUri, null, null, null, null)) { 172 assertNotNull(cursor); 173 assertEquals(1, cursor.getCount()); 174 cursor.moveToNext(); 175 return WatchNextProgram.fromCursor(cursor); 176 } 177 } 178 179 @Test 180 public void testWatchNextProgramWithPartialData() { 181 WatchNextProgram previewProgram = new WatchNextProgram.Builder() 182 .setInternalProviderId("ID-4321") 183 .setPreviewVideoUri(Uri.parse("http://example.com/preview-video.mpg")) 184 .setLastPlaybackPositionMillis(0) 185 .setDurationMillis(60 * 1000) 186 .setIntentUri(Uri.parse(new Intent(Intent.ACTION_VIEW).toUri( 187 Intent.URI_INTENT_SCHEME))) 188 .setTransient(false) 189 .setType(WatchNextPrograms.TYPE_TV_EPISODE) 190 .setPosterArtAspectRatio(WatchNextPrograms.ASPECT_RATIO_3_2) 191 .setThumbnailAspectRatio(WatchNextPrograms.ASPECT_RATIO_16_9) 192 .setLogoUri(Uri.parse("http://example.com/program-logo.mpg")) 193 .setAvailability(WatchNextPrograms.AVAILABILITY_FREE_WITH_SUBSCRIPTION) 194 .setStartingPrice("9.99 USD") 195 .setOfferPrice("3.99 USD") 196 .setReleaseDate(new Date(97, 2, 8)) 197 .setLive(false) 198 .setInteractionType(WatchNextPrograms.INTERACTION_TYPE_VIEWS) 199 .setInteractionCount(99200) 200 .setAuthor("author_name") 201 .setReviewRatingStyle(WatchNextPrograms.REVIEW_RATING_STYLE_PERCENTAGE) 202 .setReviewRating("83.9") 203 .setId(10) 204 .setTitle("Recommended Video 1") 205 .setDescription("You should watch this!") 206 .setPosterArtUri(Uri.parse("http://example.com/poster.png")) 207 .setInternalProviderFlag2(0x0010010084108410L) 208 .build(); 209 210 String[] partialProjection = { 211 WatchNextPrograms._ID, 212 WatchNextPrograms.COLUMN_TITLE, 213 WatchNextPrograms.COLUMN_SHORT_DESCRIPTION, 214 WatchNextPrograms.COLUMN_POSTER_ART_URI, 215 WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2, 216 WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_ID, 217 WatchNextPrograms.COLUMN_PREVIEW_VIDEO_URI, 218 WatchNextPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS, 219 WatchNextPrograms.COLUMN_DURATION_MILLIS, 220 WatchNextPrograms.COLUMN_INTENT_URI, 221 WatchNextPrograms.COLUMN_TRANSIENT, 222 WatchNextPrograms.COLUMN_TYPE, 223 WatchNextPrograms.COLUMN_POSTER_ART_ASPECT_RATIO, 224 WatchNextPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO, 225 WatchNextPrograms.COLUMN_LOGO_URI, 226 WatchNextPrograms.COLUMN_AVAILABILITY, 227 WatchNextPrograms.COLUMN_STARTING_PRICE, 228 WatchNextPrograms.COLUMN_OFFER_PRICE, 229 WatchNextPrograms.COLUMN_RELEASE_DATE, 230 WatchNextPrograms.COLUMN_ITEM_COUNT, 231 WatchNextPrograms.COLUMN_LIVE, 232 WatchNextPrograms.COLUMN_INTERACTION_TYPE, 233 WatchNextPrograms.COLUMN_INTERACTION_COUNT, 234 WatchNextPrograms.COLUMN_AUTHOR, 235 WatchNextPrograms.COLUMN_REVIEW_RATING_STYLE, 236 WatchNextPrograms.COLUMN_REVIEW_RATING, 237 }; 238 239 ContentValues contentValues = previewProgram.toContentValues(true); 240 compareProgram(previewProgram, 241 WatchNextProgram.fromCursor(getProgramCursor(partialProjection, contentValues)), 242 true); 243 244 WatchNextProgram clonedFullyPopulatedProgram = 245 new WatchNextProgram.Builder(previewProgram).build(); 246 compareProgram(previewProgram, clonedFullyPopulatedProgram, true); 247 } 248 249 private static WatchNextProgram createFullyPopulatedWatchNextProgram() { 250 return new WatchNextProgram.Builder() 251 .setTitle("Google") 252 .setInternalProviderId("ID-4321") 253 .setPreviewVideoUri(Uri.parse("http://example.com/preview-video.mpg")) 254 .setLastPlaybackPositionMillis(0) 255 .setDurationMillis(60 * 1000) 256 .setIntentUri(Uri.parse(new Intent(Intent.ACTION_VIEW).toUri( 257 Intent.URI_INTENT_SCHEME))) 258 .setTransient(false) 259 .setType(WatchNextPrograms.TYPE_MOVIE) 260 .setWatchNextType(WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE) 261 .setPosterArtAspectRatio(WatchNextPrograms.ASPECT_RATIO_2_3) 262 .setThumbnailAspectRatio(WatchNextPrograms.ASPECT_RATIO_16_9) 263 .setLogoUri(Uri.parse("http://example.com/program-logo.mpg")) 264 .setAvailability(WatchNextPrograms.AVAILABILITY_AVAILABLE) 265 .setStartingPrice("12.99 USD") 266 .setOfferPrice("4.99 USD") 267 .setReleaseDate("1997") 268 .setItemCount(3) 269 .setLive(false) 270 .setInteractionType(WatchNextPrograms.INTERACTION_TYPE_LIKES) 271 .setInteractionCount(10200) 272 .setAuthor("author_name") 273 .setReviewRatingStyle(WatchNextPrograms.REVIEW_RATING_STYLE_STARS) 274 .setReviewRating("4.5") 275 .setSearchable(false) 276 .setThumbnailUri(Uri.parse("http://example.com/thumbnail.png")) 277 .setAudioLanguages(new String [] {"eng", "kor"}) 278 .setCanonicalGenres(new String[] {TvContractCompat.Programs.Genres.MOVIES}) 279 .setContentRatings(new TvContentRating[] { 280 TvContentRating.createRating("com.android.tv", "US_TV", "US_TV_Y7")}) 281 .setDescription("This is a sample program") 282 .setEpisodeNumber("Pilot", 0) 283 .setEpisodeTitle("Hello World") 284 .setLongDescription("This is a longer description than the previous description") 285 .setPosterArtUri(Uri.parse("http://example.com/poster.png")) 286 .setSeasonNumber("The Final Season", 7) 287 .setSeasonTitle("The Final Season") 288 .setVideoHeight(1080) 289 .setVideoWidth(1920) 290 .setInternalProviderFlag1(0x4) 291 .setInternalProviderFlag2(0x3) 292 .setInternalProviderFlag3(0x2) 293 .setInternalProviderFlag4(0x1) 294 .setBrowsable(true) 295 .setContentId("CID-8442") 296 .build(); 297 } 298 299 private static void compareProgram(WatchNextProgram programA, WatchNextProgram programB, 300 boolean includeIdAndProtectedFields) { 301 assertTrue(Arrays.equals(programA.getAudioLanguages(), programB.getAudioLanguages())); 302 assertTrue(Arrays.deepEquals(programA.getCanonicalGenres(), programB.getCanonicalGenres())); 303 assertTrue(Arrays.deepEquals(programA.getContentRatings(), programB.getContentRatings())); 304 assertEquals(programA.getDescription(), programB.getDescription()); 305 assertEquals(programA.getEpisodeNumber(), programB.getEpisodeNumber()); 306 assertEquals(programA.getEpisodeTitle(), programB.getEpisodeTitle()); 307 assertEquals(programA.getLongDescription(), programB.getLongDescription()); 308 assertEquals(programA.getPosterArtUri(), programB.getPosterArtUri()); 309 assertEquals(programA.getSeasonNumber(), programB.getSeasonNumber()); 310 assertEquals(programA.getThumbnailUri(), programB.getThumbnailUri()); 311 assertEquals(programA.getTitle(), programB.getTitle()); 312 assertEquals(programA.getVideoHeight(), programB.getVideoHeight()); 313 assertEquals(programA.getVideoWidth(), programB.getVideoWidth()); 314 assertEquals(programA.isSearchable(), programB.isSearchable()); 315 assertEquals(programA.getInternalProviderFlag1(), programB.getInternalProviderFlag1()); 316 assertEquals(programA.getInternalProviderFlag2(), programB.getInternalProviderFlag2()); 317 assertEquals(programA.getInternalProviderFlag3(), programB.getInternalProviderFlag3()); 318 assertEquals(programA.getInternalProviderFlag4(), programB.getInternalProviderFlag4()); 319 assertTrue(Objects.equals(programA.getSeasonTitle(), programB.getSeasonTitle())); 320 assertEquals(programA.getInternalProviderId(), programB.getInternalProviderId()); 321 assertEquals(programA.getPreviewVideoUri(), programB.getPreviewVideoUri()); 322 assertEquals(programA.getLastPlaybackPositionMillis(), 323 programB.getLastPlaybackPositionMillis()); 324 assertEquals(programA.getDurationMillis(), programB.getDurationMillis()); 325 assertEquals(programA.getIntentUri(), programB.getIntentUri()); 326 assertEquals(programA.isTransient(), programB.isTransient()); 327 assertEquals(programA.getType(), programB.getType()); 328 assertEquals(programA.getWatchNextType(), programB.getWatchNextType()); 329 assertEquals(programA.getPosterArtAspectRatio(), programB.getPosterArtAspectRatio()); 330 assertEquals(programA.getThumbnailAspectRatio(), programB.getThumbnailAspectRatio()); 331 assertEquals(programA.getLogoUri(), programB.getLogoUri()); 332 assertEquals(programA.getAvailability(), programB.getAvailability()); 333 assertEquals(programA.getStartingPrice(), programB.getStartingPrice()); 334 assertEquals(programA.getOfferPrice(), programB.getOfferPrice()); 335 assertEquals(programA.getReleaseDate(), programB.getReleaseDate()); 336 assertEquals(programA.getItemCount(), programB.getItemCount()); 337 assertEquals(programA.isLive(), programB.isLive()); 338 assertEquals(programA.getInteractionType(), programB.getInteractionType()); 339 assertEquals(programA.getInteractionCount(), programB.getInteractionCount()); 340 assertEquals(programA.getAuthor(), programB.getAuthor()); 341 assertEquals(programA.getReviewRatingStyle(), programB.getReviewRatingStyle()); 342 assertEquals(programA.getReviewRating(), programB.getReviewRating()); 343 assertEquals(programA.getContentId(), programB.getContentId()); 344 if (includeIdAndProtectedFields) { 345 // Skip row ID since the one from system DB has the valid ID while the other does not. 346 assertEquals(programA.getId(), programB.getId()); 347 // When we insert a channel using toContentValues() to the system, we drop some 348 // protected fields since they only can be modified by system apps. 349 assertEquals(programA.isBrowsable(), programB.isBrowsable()); 350 assertEquals(programA.toContentValues(), programB.toContentValues()); 351 assertEquals(programA, programB); 352 } 353 } 354 355 private static MatrixCursor getProgramCursor(String[] projection, ContentValues contentValues) { 356 MatrixCursor cursor = new MatrixCursor(projection); 357 MatrixCursor.RowBuilder builder = cursor.newRow(); 358 for (String col : projection) { 359 if (col != null) { 360 builder.add(col, contentValues.get(col)); 361 } 362 } 363 cursor.moveToFirst(); 364 return cursor; 365 } 366} 367