programForGivenTime = new ArrayList<>();
132 | if (!channel.repeatPrograms) {
133 | for (XmlTvParser.XmlTvProgram program : channelPrograms) {
134 | if (program.startTimeUtcMillis <= endTimeMs
135 | && program.endTimeUtcMillis >= startTimeMs) {
136 | programForGivenTime.add(new Program.Builder()
137 | .setChannelId(ContentUris.parseId(channelUri))
138 | .setTitle(program.title)
139 | .setDescription(program.description)
140 | .setContentRatings(XmlTvParser.xmlTvRatingToTvContentRating(
141 | program.rating))
142 | .setCanonicalGenres(program.category)
143 | .setPosterArtUri(program.icon != null ? program.icon.src : null)
144 | .setInternalProviderData(TvContractUtil.
145 | convertVideoInfoToInternalProviderData(
146 | program.videoType,
147 | program.videoSrc != null ? program.videoSrc : channel.url))
148 | .setStartTimeUtcMillis(program.startTimeUtcMillis)
149 | .setEndTimeUtcMillis(program.endTimeUtcMillis)
150 | .build()
151 | );
152 | }
153 | }
154 | return programForGivenTime;
155 | }
156 |
157 | // If repeat-programs is on, schedule the programs sequentially in a loop. To make every
158 | // device play the same program in a given channel and time, we assumes the loop started
159 | // from the epoch time.
160 | long totalDurationMs = 0;
161 | for (XmlTvParser.XmlTvProgram program : channelPrograms) {
162 | totalDurationMs += program.getDurationMillis();
163 | }
164 |
165 | long programStartTimeMs = startTimeMs - startTimeMs % totalDurationMs;
166 | int i = 0;
167 | final int programCount = channelPrograms.size();
168 | while (programStartTimeMs < endTimeMs) {
169 | XmlTvParser.XmlTvProgram programInfo = channelPrograms.get(i++ % programCount);
170 | long programEndTimeMs = programStartTimeMs + programInfo.getDurationMillis();
171 | if (programEndTimeMs < startTimeMs) {
172 | programStartTimeMs = programEndTimeMs;
173 | continue;
174 | }
175 | programForGivenTime.add(new Program.Builder()
176 | .setChannelId(ContentUris.parseId(channelUri))
177 | .setTitle(programInfo.title)
178 | .setDescription(programInfo.description)
179 | .setContentRatings(XmlTvParser.xmlTvRatingToTvContentRating(
180 | programInfo.rating))
181 | .setCanonicalGenres(programInfo.category)
182 | .setPosterArtUri(programInfo.icon.src)
183 | // NOTE: {@code COLUMN_INTERNAL_PROVIDER_DATA} is a private field where
184 | // TvInputService can store anything it wants. Here, we store video type and
185 | // video URL so that TvInputService can play the video later with this field.
186 | .setInternalProviderData(TvContractUtil.convertVideoInfoToInternalProviderData(
187 | programInfo.videoType,
188 | programInfo.videoSrc != null ? programInfo.videoSrc : channel.url))
189 | .setStartTimeUtcMillis(programStartTimeMs)
190 | .setEndTimeUtcMillis(programEndTimeMs)
191 | .build()
192 | );
193 | programStartTimeMs = programEndTimeMs;
194 | }
195 | return programForGivenTime;
196 | }
197 |
198 | /**
199 | * Updates the system database, TvProvider, with the given programs.
200 | *
201 | * If there is any overlap between the given and existing programs, the existing ones
202 | * will be updated with the given ones if they have the same title or replaced.
203 | *
204 | * @param channelUri The channel where the program info will be added.
205 | * @param newPrograms A list of {@link Program} instances which includes program
206 | * information.
207 | */
208 | private void updatePrograms(Uri channelUri, List newPrograms) {
209 | final int fetchedProgramsCount = newPrograms.size();
210 | if (fetchedProgramsCount == 0) {
211 | return;
212 | }
213 | List oldPrograms = TvContractUtil.getPrograms(context.getContentResolver(),
214 | channelUri);
215 | Program firstNewProgram = newPrograms.get(0);
216 | int oldProgramsIndex = 0;
217 | int newProgramsIndex = 0;
218 | // Skip the past programs. They will be automatically removed by the system.
219 | for (Program program : oldPrograms) {
220 | oldProgramsIndex++;
221 | if(program.getEndTimeUtcMillis() > firstNewProgram.getStartTimeUtcMillis()) {
222 | break;
223 | }
224 | }
225 | // Compare the new programs with old programs one by one and update/delete the old one or
226 | // insert new program if there is no matching program in the database.
227 | ArrayList ops = new ArrayList<>();
228 | while (newProgramsIndex < fetchedProgramsCount) {
229 | Program oldProgram = oldProgramsIndex < oldPrograms.size()
230 | ? oldPrograms.get(oldProgramsIndex) : null;
231 | Program newProgram = newPrograms.get(newProgramsIndex);
232 | boolean addNewProgram = false;
233 | if (oldProgram != null) {
234 | if (oldProgram.equals(newProgram)) {
235 | // Exact match. No need to update. Move on to the next programs.
236 | oldProgramsIndex++;
237 | newProgramsIndex++;
238 | } else if (needsUpdate(oldProgram, newProgram)) {
239 | // Partial match. Update the old program with the new one.
240 | // NOTE: Use 'update' in this case instead of 'insert' and 'delete'. There could
241 | // be application specific settings which belong to the old program.
242 | ops.add(ContentProviderOperation.newUpdate(
243 | TvContract.buildProgramUri(oldProgram.getProgramId()))
244 | .withValues(newProgram.toContentValues())
245 | .build());
246 | oldProgramsIndex++;
247 | newProgramsIndex++;
248 | } else if (oldProgram.getEndTimeUtcMillis() < newProgram.getEndTimeUtcMillis()) {
249 | // No match. Remove the old program first to see if the next program in
250 | // {@code oldPrograms} partially matches the new program.
251 | ops.add(ContentProviderOperation.newDelete(
252 | TvContract.buildProgramUri(oldProgram.getProgramId()))
253 | .build());
254 | oldProgramsIndex++;
255 | } else {
256 | // No match. The new program does not match any of the old programs. Insert it
257 | // as a new program.
258 | addNewProgram = true;
259 | newProgramsIndex++;
260 | }
261 | } else {
262 | // No old programs. Just insert new programs.
263 | addNewProgram = true;
264 | newProgramsIndex++;
265 | }
266 | if (addNewProgram) {
267 | ops.add(ContentProviderOperation
268 | .newInsert(TvContract.Programs.CONTENT_URI)
269 | .withValues(newProgram.toContentValues())
270 | .build());
271 | }
272 | // Throttle the batch operation not to cause TransactionTooLargeException.
273 | if (ops.size() > BATCH_OPERATION_COUNT
274 | || newProgramsIndex >= fetchedProgramsCount) {
275 | try {
276 | context.getContentResolver().applyBatch(TvContract.AUTHORITY, ops);
277 | } catch (RemoteException | OperationApplicationException e) {
278 | Log.e(TAG, "Failed to insert programs.", e);
279 | return;
280 | }
281 | ops.clear();
282 | }
283 | }
284 | }
285 |
286 | /**
287 | * Returns {@code true} if the {@code oldProgram} program needs to be updated with the
288 | * {@code newProgram} program.
289 | */
290 | private boolean needsUpdate(Program oldProgram, Program newProgram) {
291 | // NOTE: Here, we update the old program if it has the same title and overlaps with the new
292 | // program. The test logic is just an example and you can modify this. E.g. check whether
293 | // the both programs have the same program ID if your EPG supports any ID for the programs.
294 | return oldProgram.getTitle().equals(newProgram.getTitle())
295 | && oldProgram.getStartTimeUtcMillis() <= newProgram.getEndTimeUtcMillis()
296 | && newProgram.getStartTimeUtcMillis() <= oldProgram.getEndTimeUtcMillis();
297 | }
298 | }
299 |
--------------------------------------------------------------------------------
/app/src/main/java/at/pansy/iptv/util/IptvUtil.java:
--------------------------------------------------------------------------------
1 | package at.pansy.iptv.util;
2 |
3 | import android.content.ContentResolver;
4 | import android.content.Context;
5 | import android.net.Uri;
6 | import android.util.Log;
7 |
8 | import java.io.BufferedReader;
9 | import java.io.IOException;
10 | import java.io.BufferedInputStream;
11 | import java.io.InputStream;
12 | import java.io.InputStreamReader;
13 | import java.net.URL;
14 | import java.net.URLConnection;
15 | import java.util.ArrayList;
16 | import java.util.HashMap;
17 | import java.util.List;
18 | import java.util.Map;
19 | import java.util.zip.GZIPInputStream;
20 |
21 | import at.pansy.iptv.xmltv.XmlTvParser;
22 |
23 | /**
24 | * Static helper methods for fetching the channel feed.
25 | */
26 | public class IptvUtil {
27 |
28 | public static final int FORMAT_XMLTV = 0;
29 | public static final int FORMAT_M3U = 1;
30 |
31 | private static final String TAG = "IptvUtil";
32 | private static HashMap sampleTvListings = new HashMap<>();
33 |
34 | private static final int URLCONNECTION_CONNECTION_TIMEOUT_MS = 3000; // 3 sec
35 | private static final int URLCONNECTION_READ_TIMEOUT_MS = 10000; // 10 sec
36 |
37 | private IptvUtil() {
38 | }
39 |
40 | public static XmlTvParser.TvListing getTvListings(Context context, String url, int format) {
41 |
42 | if (sampleTvListings.containsKey(url)) {
43 | return sampleTvListings.get(url);
44 | }
45 |
46 | Uri catalogUri =
47 | Uri.parse(url).normalizeScheme();
48 |
49 | XmlTvParser.TvListing sampleTvListing = null;
50 | try {
51 | InputStream inputStream = getInputStream(context, catalogUri);
52 | if (url.endsWith(".gz")) {
53 | inputStream = new GZIPInputStream(inputStream);
54 | }
55 | if (format == FORMAT_M3U) {
56 | sampleTvListing = parse(inputStream);
57 | } else {
58 | sampleTvListing = XmlTvParser.parse(inputStream);
59 | }
60 | } catch (IOException e) {
61 | Log.e(TAG, "Error in fetching " + catalogUri, e);
62 | }
63 | if (sampleTvListing != null) {
64 | sampleTvListings.put(url, sampleTvListing);
65 | }
66 | return sampleTvListing;
67 | }
68 |
69 | public static InputStream getInputStream(Context context, Uri uri) throws IOException {
70 | InputStream inputStream;
71 | if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme())
72 | || ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme())
73 | || ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
74 | inputStream = context.getContentResolver().openInputStream(uri);
75 | } else {
76 | URLConnection urlConnection = new URL(uri.toString()).openConnection();
77 | urlConnection.setConnectTimeout(URLCONNECTION_CONNECTION_TIMEOUT_MS);
78 | urlConnection.setReadTimeout(URLCONNECTION_READ_TIMEOUT_MS);
79 | inputStream = urlConnection.getInputStream();
80 | }
81 | return new BufferedInputStream(inputStream);
82 | }
83 |
84 | private static XmlTvParser.TvListing parse(InputStream inputStream) throws IOException {
85 | BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));
86 | String line;
87 | List channels = new ArrayList<>();
88 | List programs = new ArrayList<>();
89 | Map channelMap = new HashMap<>();
90 |
91 | while ((line = in.readLine()) != null) {
92 | if (line.startsWith("#EXTINF:")) {
93 | // #EXTINF:0051 tvg-id="blizz.de" group-title="DE Spartensender" tvg-logo="897815.png", [COLOR orangered]blizz TV HD[/COLOR]
94 |
95 | String id = null;
96 | String displayName = null;
97 | String displayNumber = null;
98 | int originalNetworkId = 0;
99 | XmlTvParser.XmlTvIcon icon = null;
100 |
101 | String[] parts = line.split(", ", 2);
102 | if (parts.length == 2) {
103 | for (String part : parts[0].split(" ")) {
104 | if (part.startsWith("#EXTINF:")) {
105 | displayNumber = part.substring(8).replaceAll("^0+", "");
106 | originalNetworkId = Integer.parseInt(displayNumber);
107 | } else if (part.startsWith("tvg-id=")) {
108 | int end = part.indexOf("\"", 8);
109 | if (end > 8) {
110 | id = part.substring(8, end);
111 | }
112 | } else if (part.startsWith("tvg-logo=")) {
113 | int end = part.indexOf("\"", 10);
114 | if (end > 10) {
115 | icon = new XmlTvParser.XmlTvIcon("http://logo.iptv.ink/"
116 | + part.substring(10, end));
117 | }
118 | }
119 | }
120 | displayName = parts[1].replaceAll("\\[\\/?COLOR[^\\]]*\\]", "");
121 | }
122 |
123 | if (originalNetworkId != 0 && displayName != null) {
124 | XmlTvParser.XmlTvChannel channel =
125 | new XmlTvParser.XmlTvChannel(id, displayName, displayNumber, icon,
126 | originalNetworkId, 0, 0, false);
127 | if (channelMap.containsKey(originalNetworkId)) {
128 | channels.set(channelMap.get(originalNetworkId), channel);
129 | } else {
130 | channelMap.put(originalNetworkId, channels.size());
131 | channels.add(channel);
132 | }
133 | }
134 | } else if (line.startsWith("http") && channels.size() > 0) {
135 | channels.get(channels.size()-1).url = line;
136 | }
137 | }
138 | return new XmlTvParser.TvListing(channels, programs);
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/app/src/main/java/at/pansy/iptv/util/RendererUtil.java:
--------------------------------------------------------------------------------
1 | package at.pansy.iptv.util;
2 |
3 | import android.content.Context;
4 | ;
5 | import com.google.android.exoplayer.upstream.DefaultHttpDataSource;
6 | import com.google.android.exoplayer.upstream.DefaultUriDataSource;
7 |
8 | import java.util.HashMap;
9 | import java.util.Map;
10 |
11 | /**
12 | * Created by notz.
13 | */
14 | public class RendererUtil {
15 |
16 | public static String processUrlParameters(String url, HashMap httpHeaders) {
17 | String[] parameters = url.split("\\|");
18 | for (int i = 1; i < parameters.length; i++) {
19 | String[] pair = parameters[i].split("=", 2);
20 | if (pair.length == 2) {
21 | httpHeaders.put(pair[0], pair[1]);
22 | }
23 | }
24 |
25 | return parameters[0];
26 | }
27 |
28 | public static DefaultUriDataSource createDefaultUriDataSource(Context context, String userAgent,
29 | HashMap httpHeaders) {
30 |
31 | DefaultHttpDataSource httpDataSource = new DefaultHttpDataSource(userAgent, null, null,
32 | DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS,
33 | DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS, false);
34 |
35 | for (Map.Entry header : httpHeaders.entrySet()) {
36 | httpDataSource.setRequestProperty(header.getKey(), header.getValue());
37 | }
38 |
39 | return new DefaultUriDataSource(context, null, httpDataSource);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/at/pansy/iptv/util/SyncUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 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 at.pansy.iptv.util;
18 |
19 | import android.accounts.Account;
20 | import android.accounts.AccountManager;
21 | import android.content.ContentResolver;
22 | import android.content.Context;
23 | import android.media.tv.TvContract;
24 | import android.os.Bundle;
25 | import android.util.Log;
26 |
27 | import at.pansy.iptv.service.AccountService;
28 | import at.pansy.iptv.sync.SyncAdapter;
29 |
30 | /**
31 | * Static helper methods for working with the SyncAdapter framework.
32 | */
33 | public class SyncUtil {
34 |
35 | public static final String ACCOUNT_TYPE = "at.pansy.iptv.account";
36 |
37 | private static final String TAG = "SyncUtil";
38 | private static final String CONTENT_AUTHORITY = TvContract.AUTHORITY;
39 |
40 | public static void setUpPeriodicSync(Context context, String inputId) {
41 | Account account = AccountService.getAccount(ACCOUNT_TYPE);
42 | AccountManager accountManager =
43 | (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
44 | if (!accountManager.addAccountExplicitly(account, null, null)) {
45 | Log.e(TAG, "Account already exists.");
46 | }
47 | ContentResolver.setIsSyncable(account, CONTENT_AUTHORITY, 1);
48 | ContentResolver.setSyncAutomatically(account, CONTENT_AUTHORITY, true);
49 | Bundle bundle = new Bundle();
50 | bundle.putString(SyncAdapter.BUNDLE_KEY_INPUT_ID, inputId);
51 | ContentResolver.addPeriodicSync(account, CONTENT_AUTHORITY, bundle,
52 | SyncAdapter.FULL_SYNC_FREQUENCY_SEC);
53 | }
54 |
55 | public static void requestSync(String inputId, boolean currentProgramOnly) {
56 | Bundle bundle = new Bundle();
57 | bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
58 | bundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
59 | bundle.putString(SyncAdapter.BUNDLE_KEY_INPUT_ID, inputId);
60 | bundle.putBoolean(SyncAdapter.BUNDLE_KEY_CURRENT_PROGRAM_ONLY, currentProgramOnly);
61 | ContentResolver.requestSync(AccountService.getAccount(ACCOUNT_TYPE), CONTENT_AUTHORITY,
62 | bundle);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/app/src/main/java/at/pansy/iptv/util/TvContractUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 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 at.pansy.iptv.util;
18 |
19 | import android.content.ContentResolver;
20 | import android.content.ContentValues;
21 | import android.content.Context;
22 | import android.database.Cursor;
23 | import android.media.tv.TvContentRating;
24 | import android.media.tv.TvContract;
25 | import android.media.tv.TvContract.Channels;
26 | import android.media.tv.TvContract.Programs;
27 | import android.net.Uri;
28 | import android.os.AsyncTask;
29 | import android.text.TextUtils;
30 | import android.util.Log;
31 | import android.util.LongSparseArray;
32 | import android.util.Pair;
33 | import android.util.SparseArray;
34 |
35 | import java.io.IOException;
36 | import java.io.InputStream;
37 | import java.io.OutputStream;
38 | import java.net.MalformedURLException;
39 | import java.net.URL;
40 | import java.util.ArrayList;
41 | import java.util.HashMap;
42 | import java.util.List;
43 | import java.util.Map;
44 |
45 | import at.pansy.iptv.domain.Channel;
46 | import at.pansy.iptv.domain.PlaybackInfo;
47 | import at.pansy.iptv.domain.Program;
48 | import at.pansy.iptv.xmltv.XmlTvParser;
49 |
50 | /**
51 | * Static helper methods for working with {@link TvContract}.
52 | */
53 | public class TvContractUtil {
54 | private static final String TAG = "TvContractUtils";
55 | private static final boolean DEBUG = true;
56 |
57 | private static final SparseArray VIDEO_HEIGHT_TO_FORMAT_MAP = new SparseArray<>();
58 |
59 | static {
60 | VIDEO_HEIGHT_TO_FORMAT_MAP.put(480, Channels.VIDEO_FORMAT_480P);
61 | VIDEO_HEIGHT_TO_FORMAT_MAP.put(576, Channels.VIDEO_FORMAT_576P);
62 | VIDEO_HEIGHT_TO_FORMAT_MAP.put(720, Channels.VIDEO_FORMAT_720P);
63 | VIDEO_HEIGHT_TO_FORMAT_MAP.put(1080, Channels.VIDEO_FORMAT_1080P);
64 | VIDEO_HEIGHT_TO_FORMAT_MAP.put(2160, Channels.VIDEO_FORMAT_2160P);
65 | VIDEO_HEIGHT_TO_FORMAT_MAP.put(4320, Channels.VIDEO_FORMAT_4320P);
66 | }
67 |
68 | private TvContractUtil() {}
69 |
70 | public static void updateChannels(
71 | Context context, String inputId, List channels) {
72 | // Create a map from original network ID to channel row ID for existing channels.
73 | SparseArray mExistingChannelsMap = new SparseArray<>();
74 | Uri channelsUri = TvContract.buildChannelsUriForInput(inputId);
75 | String[] projection = {Channels._ID, Channels.COLUMN_ORIGINAL_NETWORK_ID};
76 | Cursor cursor = null;
77 | ContentResolver resolver = context.getContentResolver();
78 | try {
79 | cursor = resolver.query(channelsUri, projection, null, null, null);
80 | while (cursor != null && cursor.moveToNext()) {
81 | long rowId = cursor.getLong(0);
82 | int originalNetworkId = cursor.getInt(1);
83 | mExistingChannelsMap.put(originalNetworkId, rowId);
84 | }
85 | } finally {
86 | if (cursor != null) {
87 | cursor.close();
88 | }
89 | }
90 |
91 | // If a channel exists, update it. If not, insert a new one.
92 | ContentValues values = new ContentValues();
93 | values.put(Channels.COLUMN_INPUT_ID, inputId);
94 | Map logos = new HashMap<>();
95 | for (XmlTvParser.XmlTvChannel channel : channels) {
96 | values.put(Channels.COLUMN_DISPLAY_NUMBER, channel.displayNumber);
97 | values.put(Channels.COLUMN_DISPLAY_NAME, channel.displayName);
98 | values.put(Channels.COLUMN_ORIGINAL_NETWORK_ID, channel.originalNetworkId);
99 | values.put(Channels.COLUMN_TRANSPORT_STREAM_ID, channel.transportStreamId);
100 | values.put(Channels.COLUMN_SERVICE_ID, channel.serviceId);
101 | values.put(Channels.COLUMN_INTERNAL_PROVIDER_DATA, channel.url);
102 | Long rowId = mExistingChannelsMap.get(channel.originalNetworkId);
103 | Uri uri;
104 | if (rowId == null) {
105 | uri = resolver.insert(Channels.CONTENT_URI, values);
106 | } else {
107 | uri = TvContract.buildChannelUri(rowId);
108 | resolver.update(uri, values, null, null);
109 | mExistingChannelsMap.remove(channel.originalNetworkId);
110 | }
111 | if (!TextUtils.isEmpty(channel.icon.src)) {
112 | logos.put(TvContract.buildChannelLogoUri(uri), channel.icon.src);
113 | }
114 | }
115 | if (!logos.isEmpty()) {
116 | new InsertLogosTask(context).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, logos);
117 | }
118 |
119 | // Deletes channels which don't exist in the new feed.
120 | int size = mExistingChannelsMap.size();
121 | for (int i = 0; i < size; i++) {
122 | Long rowId = mExistingChannelsMap.valueAt(i);
123 | resolver.delete(TvContract.buildChannelUri(rowId), null, null);
124 | }
125 | }
126 |
127 | private static String getVideoFormat(int videoHeight) {
128 | return VIDEO_HEIGHT_TO_FORMAT_MAP.get(videoHeight);
129 | }
130 |
131 | public static LongSparseArray buildChannelMap(
132 | ContentResolver resolver, String inputId, List channels) {
133 | Uri uri = TvContract.buildChannelsUriForInput(inputId);
134 | String[] projection = {
135 | Channels._ID,
136 | Channels.COLUMN_DISPLAY_NUMBER
137 | };
138 |
139 | LongSparseArray channelMap = new LongSparseArray<>();
140 | Cursor cursor = null;
141 | try {
142 | cursor = resolver.query(uri, projection, null, null, null);
143 | if (cursor == null || cursor.getCount() == 0) {
144 | return null;
145 | }
146 |
147 | while (cursor.moveToNext()) {
148 | long channelId = cursor.getLong(0);
149 | String channelNumber = cursor.getString(1);
150 | channelMap.put(channelId, getChannelByNumber(channelNumber, channels));
151 | }
152 | } catch (Exception e) {
153 | Log.d(TAG, "Content provider query: " + e.getStackTrace());
154 | return null;
155 | } finally {
156 | if (cursor != null) {
157 | cursor.close();
158 | }
159 | }
160 | return channelMap;
161 | }
162 |
163 | public static Channel getChannel(ContentResolver resolver, Uri channelUri) {
164 | Cursor cursor = null;
165 | try {
166 | // TvProvider returns programs chronological order by default.
167 | cursor = resolver.query(channelUri, null, null, null, null);
168 | if (cursor == null || cursor.getCount() == 0) {
169 | return null;
170 | }
171 | if (cursor.moveToNext()) {
172 | return Channel.fromCursor(cursor);
173 | }
174 | } catch (Exception e) {
175 | Log.w(TAG, "Unable to get channel for " + channelUri, e);
176 | } finally {
177 | if (cursor != null) {
178 | cursor.close();
179 | }
180 | }
181 | return null;
182 | }
183 |
184 | public static List getPrograms(ContentResolver resolver, Uri channelUri) {
185 | Uri uri = TvContract.buildProgramsUriForChannel(channelUri);
186 | Cursor cursor = null;
187 | List programs = new ArrayList<>();
188 | try {
189 | // TvProvider returns programs chronological order by default.
190 | cursor = resolver.query(uri, null, null, null, null);
191 | if (cursor == null || cursor.getCount() == 0) {
192 | return programs;
193 | }
194 | while (cursor.moveToNext()) {
195 | programs.add(Program.fromCursor(cursor));
196 | }
197 | } catch (Exception e) {
198 | Log.w(TAG, "Unable to get programs for " + channelUri, e);
199 | } finally {
200 | if (cursor != null) {
201 | cursor.close();
202 | }
203 | }
204 | return programs;
205 | }
206 |
207 | public static List getProgramPlaybackInfo(
208 | ContentResolver resolver, Uri channelUri, long startTimeMs, long endTimeMs,
209 | int maxProgramInReturn) {
210 | Uri uri = TvContract.buildProgramsUriForChannel(channelUri, startTimeMs,
211 | endTimeMs);
212 | String[] projection = { Programs.COLUMN_START_TIME_UTC_MILLIS,
213 | Programs.COLUMN_END_TIME_UTC_MILLIS,
214 | Programs.COLUMN_CONTENT_RATING,
215 | Programs.COLUMN_INTERNAL_PROVIDER_DATA,
216 | Programs.COLUMN_CANONICAL_GENRE };
217 | Cursor cursor = null;
218 | List list = new ArrayList<>();
219 | try {
220 | cursor = resolver.query(uri, projection, null, null, null);
221 | while (cursor != null && cursor.moveToNext()) {
222 | long startMs = cursor.getLong(0);
223 | long endMs = cursor.getLong(1);
224 | TvContentRating[] ratings = stringToContentRatings(cursor.getString(2));
225 | Pair values = parseInternalProviderData(cursor.getString(3));
226 | int videoType = values.first;
227 | String videoUrl = values.second;
228 | list.add(new PlaybackInfo(startMs, endMs, videoUrl, videoType, ratings));
229 | if (list.size() > maxProgramInReturn) {
230 | break;
231 | }
232 | }
233 | } catch (Exception e) {
234 | Log.e(TAG, "Failed to get program playback info from TvProvider.", e);
235 | } finally {
236 | if (cursor != null) {
237 | cursor.close();
238 | }
239 | }
240 | return list;
241 | }
242 |
243 | public static String convertVideoInfoToInternalProviderData(int videotype, String videoUrl) {
244 | return videotype + "," + videoUrl;
245 | }
246 |
247 | public static Pair parseInternalProviderData(String internalData) {
248 | String[] values = internalData.split(",", 2);
249 | if (values.length != 2) {
250 | throw new IllegalArgumentException(internalData);
251 | }
252 | return new Pair<>(Integer.parseInt(values[0]), values[1]);
253 | }
254 |
255 | public static void insertUrl(Context context, Uri contentUri, URL sourceUrl) {
256 | if (DEBUG) {
257 | Log.d(TAG, "Inserting " + sourceUrl + " to " + contentUri);
258 | }
259 | InputStream is = null;
260 | OutputStream os = null;
261 | try {
262 | is = sourceUrl.openStream();
263 | os = context.getContentResolver().openOutputStream(contentUri);
264 | copy(is, os);
265 | } catch (IOException ioe) {
266 | Log.e(TAG, "Failed to write " + sourceUrl + " to " + contentUri, ioe);
267 | } finally {
268 | if (is != null) {
269 | try {
270 | is.close();
271 | } catch (IOException e) {
272 | // Ignore exception.
273 | }
274 | }
275 | if (os != null) {
276 | try {
277 | os.close();
278 | } catch (IOException e) {
279 | // Ignore exception.
280 | }
281 | }
282 | }
283 | }
284 |
285 | public static void copy(InputStream is, OutputStream os) throws IOException {
286 | byte[] buffer = new byte[1024];
287 | int len;
288 | while ((len = is.read(buffer)) != -1) {
289 | os.write(buffer, 0, len);
290 | }
291 | }
292 |
293 | public static TvContentRating[] stringToContentRatings(String commaSeparatedRatings) {
294 | if (TextUtils.isEmpty(commaSeparatedRatings)) {
295 | return null;
296 | }
297 | String[] ratings = commaSeparatedRatings.split("\\s*,\\s*");
298 | TvContentRating[] contentRatings = new TvContentRating[ratings.length];
299 | for (int i = 0; i < contentRatings.length; ++i) {
300 | contentRatings[i] = TvContentRating.unflattenFromString(ratings[i]);
301 | }
302 | return contentRatings;
303 | }
304 |
305 | public static String contentRatingsToString(TvContentRating[] contentRatings) {
306 | if (contentRatings == null || contentRatings.length == 0) {
307 | return null;
308 | }
309 | final String DELIMITER = ",";
310 | StringBuilder ratings = new StringBuilder(contentRatings[0].flattenToString());
311 | for (int i = 1; i < contentRatings.length; ++i) {
312 | ratings.append(DELIMITER);
313 | ratings.append(contentRatings[i].flattenToString());
314 | }
315 | return ratings.toString();
316 | }
317 |
318 | private static XmlTvParser.XmlTvChannel getChannelByNumber(String channelNumber,
319 | List channels) {
320 | for (XmlTvParser.XmlTvChannel channel : channels) {
321 | if (channelNumber.equals(channel.displayNumber)) {
322 | return channel;
323 | }
324 | }
325 | throw new IllegalArgumentException("Unknown channel: " + channelNumber);
326 | }
327 |
328 | public static class InsertLogosTask extends AsyncTask