1 /*
2  * Copyright (C) 2015 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 com.android.tv.testing.data;
18 
19 import android.content.ContentUris;
20 import android.content.ContentValues;
21 import android.content.Context;
22 import android.database.Cursor;
23 import android.database.sqlite.SQLiteException;
24 import android.media.tv.TvContract;
25 import android.media.tv.TvContract.Programs;
26 import android.net.Uri;
27 import android.util.Log;
28 import com.android.tv.common.TvContentRatingCache;
29 import com.android.tv.common.util.Clock;
30 import java.util.ArrayList;
31 import java.util.Map;
32 import java.util.concurrent.TimeUnit;
33 
34 /** Static utilities for using Programs in tests */
35 public final class ProgramUtils {
36     private static final String TAG = "ProgramUtils";
37     private static final boolean DEBUG = false;
38 
39     /** Populate program data for a week */
40     public static final long PROGRAM_INSERT_DURATION_MS = TimeUnit.DAYS.toMillis(7);
41 
42     private static final int MAX_DB_INSERT_COUNT_AT_ONCE = 500;
43 
44     /**
45      * Populate programs by repeating given program information. This method will populate programs
46      * without any gap nor overlapping starting from the current time.
47      */
populatePrograms( Context context, Uri channelUri, ProgramInfo program, Clock clock)48     public static void populatePrograms(
49             Context context, Uri channelUri, ProgramInfo program, Clock clock) {
50         populatePrograms(context, channelUri, program, clock, PROGRAM_INSERT_DURATION_MS);
51     }
52 
populatePrograms( Context context, Uri channelUri, ProgramInfo program, Clock clock, long programInsertDurationMs)53     public static void populatePrograms(
54             Context context,
55             Uri channelUri,
56             ProgramInfo program,
57             Clock clock,
58             long programInsertDurationMs) {
59         long currentTimeMs = clock.currentTimeMillis();
60         long targetEndTimeMs = currentTimeMs + programInsertDurationMs;
61         populatePrograms(context, channelUri, program, currentTimeMs, targetEndTimeMs);
62     }
63 
populatePrograms( Context context, Uri channelUri, ProgramInfo program, long currentTimeMs, long targetEndTimeMs)64     public static void populatePrograms(
65             Context context,
66             Uri channelUri,
67             ProgramInfo program,
68             long currentTimeMs,
69             long targetEndTimeMs) {
70         ContentValues values = new ContentValues();
71         long channelId = ContentUris.parseId(channelUri);
72 
73         values.put(Programs.COLUMN_CHANNEL_ID, channelId);
74         values.put(Programs.COLUMN_SHORT_DESCRIPTION, program.description);
75         values.put(
76                 Programs.COLUMN_CONTENT_RATING,
77                 TvContentRatingCache.contentRatingsToString(program.contentRatings));
78 
79         long timeMs = getLastProgramEndTimeMs(context, channelUri, currentTimeMs, targetEndTimeMs);
80         if (timeMs <= 0) {
81             timeMs = currentTimeMs;
82         }
83         int index = program.getIndex(timeMs, channelId);
84         timeMs = program.getStartTimeMs(index, channelId);
85 
86         ArrayList<ContentValues> list = new ArrayList<>();
87         while (timeMs < targetEndTimeMs) {
88             ProgramInfo programAt = program.build(context, index++);
89             values.put(Programs.COLUMN_TITLE, programAt.title);
90             values.put(Programs.COLUMN_EPISODE_TITLE, programAt.episode);
91             if (programAt.seasonNumber != 0) {
92                 values.put(Programs.COLUMN_SEASON_NUMBER, programAt.seasonNumber);
93             }
94             if (programAt.episodeNumber != 0) {
95                 values.put(Programs.COLUMN_EPISODE_NUMBER, programAt.episodeNumber);
96             }
97             values.put(Programs.COLUMN_POSTER_ART_URI, programAt.posterArtUri);
98             values.put(Programs.COLUMN_START_TIME_UTC_MILLIS, timeMs);
99             values.put(Programs.COLUMN_END_TIME_UTC_MILLIS, timeMs + programAt.durationMs);
100             values.put(Programs.COLUMN_CANONICAL_GENRE, programAt.genre);
101             values.put(Programs.COLUMN_POSTER_ART_URI, programAt.posterArtUri);
102             list.add(new ContentValues(values));
103             timeMs += programAt.durationMs;
104 
105             if (list.size() >= MAX_DB_INSERT_COUNT_AT_ONCE || timeMs >= targetEndTimeMs) {
106                 try {
107                     context.getContentResolver()
108                             .bulkInsert(
109                                     Programs.CONTENT_URI,
110                                     list.toArray(new ContentValues[list.size()]));
111                 } catch (SQLiteException e) {
112                     Log.e(TAG, "Can't insert EPG.", e);
113                     return;
114                 }
115                 if (DEBUG) Log.d(TAG, "Inserted " + list.size() + " programs for " + channelUri);
116                 list.clear();
117             }
118         }
119     }
120 
getLastProgramEndTimeMs( Context context, Uri channelUri, long startTimeMs, long endTimeMs)121     private static long getLastProgramEndTimeMs(
122             Context context, Uri channelUri, long startTimeMs, long endTimeMs) {
123         Uri uri = TvContract.buildProgramsUriForChannel(channelUri, startTimeMs, endTimeMs);
124         String[] projection = {Programs.COLUMN_END_TIME_UTC_MILLIS};
125         try (Cursor cursor =
126                 context.getContentResolver().query(uri, projection, null, null, null)) {
127             if (cursor != null && cursor.moveToLast()) {
128                 return cursor.getLong(0);
129             }
130         }
131         return 0;
132     }
133 
ProgramUtils()134     private ProgramUtils() {}
135 
updateProgramForAllChannelsOf( Context context, String inputId, Clock clock, long durationMs)136     public static void updateProgramForAllChannelsOf(
137             Context context, String inputId, Clock clock, long durationMs) {
138         // Reload channels so we have the ids.
139         Map<Long, ChannelInfo> channelIdToInfoMap =
140                 ChannelUtils.queryChannelInfoMapForTvInput(context, inputId);
141         for (Long channelId : channelIdToInfoMap.keySet()) {
142             ProgramInfo programInfo = ProgramInfo.create();
143             populatePrograms(
144                     context, TvContract.buildChannelUri(channelId), programInfo, clock, durationMs);
145         }
146     }
147 }
148