Download File
Download Project
Settings
Line Wrap
Themes
default
ambiance
bespin
dracula
eclipse
material
mbo
mdn-like
neat
solarized dark
ttcn
zenburn
TvContractUtils.java
/* * Copyright 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.media.tv.companionlibrary.utils; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.SharedPreferences; import android.database.Cursor; import android.media.tv.TvContentRating; import android.media.tv.TvContract; import android.media.tv.TvContract.Channels; import android.net.Uri; import android.os.AsyncTask; import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.Log; import android.util.LongSparseArray; import android.util.SparseArray; import com.google.android.media.tv.companionlibrary.BaseTvInputService; import com.google.android.media.tv.companionlibrary.model.Channel; import com.google.android.media.tv.companionlibrary.model.Program; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Static helper methods for working with {@link android.media.tv.TvContract}. */ public class TvContractUtils { /** Indicates that no source type has been defined for this video yet */ public static final int SOURCE_TYPE_INVALID = -1; /** Indicates that the video will use MPEG-DASH (Dynamic Adaptive Streaming over HTTP) for * playback. */ public static final int SOURCE_TYPE_MPEG_DASH = 0; /** Indicates that the video will use SS (Smooth Streaming) for playback. */ public static final int SOURCE_TYPE_SS = 1; /** Indicates that the video will use HLS (HTTP Live Streaming) for playback. */ public static final int SOURCE_TYPE_HLS = 2; /** Indicates that the video will use HTTP Progressive for playback. */ public static final int SOURCE_TYPE_HTTP_PROGRESSIVE = 3; private static final String TAG = "TvContractUtils"; private static final boolean DEBUG = false; private static final SparseArray
VIDEO_HEIGHT_TO_FORMAT_MAP = new SparseArray<>(); static { VIDEO_HEIGHT_TO_FORMAT_MAP.put(480, TvContract.Channels.VIDEO_FORMAT_480P); VIDEO_HEIGHT_TO_FORMAT_MAP.put(576, TvContract.Channels.VIDEO_FORMAT_576P); VIDEO_HEIGHT_TO_FORMAT_MAP.put(720, TvContract.Channels.VIDEO_FORMAT_720P); VIDEO_HEIGHT_TO_FORMAT_MAP.put(1080, TvContract.Channels.VIDEO_FORMAT_1080P); VIDEO_HEIGHT_TO_FORMAT_MAP.put(2160, TvContract.Channels.VIDEO_FORMAT_2160P); VIDEO_HEIGHT_TO_FORMAT_MAP.put(4320, TvContract.Channels.VIDEO_FORMAT_4320P); } /** * Updates the list of available channels. * * @param context The application's context. * @param inputId The ID of the TV input service that provides this TV channel. * @param channels The updated list of channels. * @hide */ public static void updateChannels(Context context, String inputId, List
channels) { // Create a map from original network ID to channel row ID for existing channels. SparseArray
channelMap = new SparseArray<>(); Uri channelsUri = TvContract.buildChannelsUriForInput(inputId); String[] projection = {Channels._ID, Channels.COLUMN_ORIGINAL_NETWORK_ID}; ContentResolver resolver = context.getContentResolver(); Cursor cursor = null; try { cursor = resolver.query(channelsUri, projection, null, null, null); cursor = resolver.query(channelsUri, projection, null, null, null); while (cursor != null && cursor.moveToNext()) { long rowId = cursor.getLong(0); int originalNetworkId = cursor.getInt(1); channelMap.put(originalNetworkId, rowId); } } finally { if (cursor != null) { cursor.close(); } } // If a channel exists, update it. If not, insert a new one. Map
logos = new HashMap<>(); for (Channel channel : channels) { ContentValues values = new ContentValues(); values.put(Channels.COLUMN_INPUT_ID, inputId); values.putAll(channel.toContentValues()); // If some required fields are not populated, the app may crash, so defaults are used if (channel.getPackageName() == null) { // If channel does not include package name, it will be added values.put(Channels.COLUMN_PACKAGE_NAME, context.getPackageName()); } if (channel.getInputId() == null) { // If channel does not include input id, it will be added values.put(Channels.COLUMN_INPUT_ID, inputId); } if (channel.getType() == null) { // If channel does not include type it will be added values.put(Channels.COLUMN_TYPE, Channels.TYPE_OTHER); } Long rowId = channelMap.get(channel.getOriginalNetworkId()); Uri uri; if (rowId == null) { uri = resolver.insert(TvContract.Channels.CONTENT_URI, values); if (DEBUG) { Log.d(TAG, "Adding channel " + channel.getDisplayName() + " at " + uri); } } else { values.put(Channels._ID, rowId); uri = TvContract.buildChannelUri(rowId); if (DEBUG) { Log.d(TAG, "Updating channel " + channel.getDisplayName() + " at " + uri); } resolver.update(uri, values, null, null); channelMap.remove(channel.getOriginalNetworkId()); } if (channel.getChannelLogo() != null && !TextUtils.isEmpty(channel.getChannelLogo())) { logos.put(TvContract.buildChannelLogoUri(uri), channel.getChannelLogo()); } } if (!logos.isEmpty()) { new InsertLogosTask(context).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, logos); } // Deletes channels which don't exist in the new feed. int size = channelMap.size(); for (int i = 0; i < size; ++i) { Long rowId = channelMap.valueAt(i); if (DEBUG) { Log.d(TAG, "Deleting channel " + rowId); } resolver.delete(TvContract.buildChannelUri(rowId), null, null); SharedPreferences.Editor editor = context.getSharedPreferences( BaseTvInputService.PREFERENCES_FILE_KEY, Context.MODE_PRIVATE).edit(); editor.remove(BaseTvInputService.SHARED_PREFERENCES_KEY_LAST_CHANNEL_AD_PLAY + rowId); editor.apply(); } } /** * Builds a map of available channels. * * @param resolver Application's ContentResolver. * @param inputId The ID of the TV input service that provides this TV channel. * @return LongSparseArray mapping each channel's {@link TvContract.Channels#_ID} to the * Channel object. * @hide */ public static LongSparseArray
buildChannelMap(@NonNull ContentResolver resolver, @NonNull String inputId) { Uri uri = TvContract.buildChannelsUriForInput(inputId); LongSparseArray
channelMap = new LongSparseArray<>(); Cursor cursor = null; try { cursor = resolver.query(uri, Channel.PROJECTION, null, null, null); if (cursor == null || cursor.getCount() == 0) { if (DEBUG) { Log.d(TAG, "Cursor is null or found no results"); } return null; } while (cursor.moveToNext()) { Channel nextChannel = Channel.fromCursor(cursor); channelMap.put(nextChannel.getId(), nextChannel); } } catch (Exception e) { Log.d(TAG, "Content provider query: " + Arrays.toString(e.getStackTrace())); return null; } finally { if (cursor != null) { cursor.close(); } } return channelMap; } /** * Returns the current list of channels your app provides. * * @param resolver Application's ContentResolver. * @return List of channels. */ public static List
getChannels(ContentResolver resolver) { List
channels = new ArrayList<>(); // TvProvider returns programs in chronological order by default. Cursor cursor = null; try { cursor = resolver.query(Channels.CONTENT_URI, Channel.PROJECTION, null, null, null); if (cursor == null || cursor.getCount() == 0) { return channels; } while (cursor.moveToNext()) { channels.add(Channel.fromCursor(cursor)); } } catch (Exception e) { Log.w(TAG, "Unable to get channels", e); } finally { if (cursor != null) { cursor.close(); } } return channels; } /** * Returns the {@link Channel} with specified channel URI. * @param resolver {@link ContentResolver} used to query database. * @param channelUri URI of channel. * @return An channel object with specified channel URI. * @hide */ public static Channel getChannel(ContentResolver resolver, Uri channelUri) { Cursor cursor = null; try { cursor = resolver.query(channelUri, Channel.PROJECTION, null, null, null); if (cursor == null || cursor.getCount() == 0) { Log.w(TAG, "No channel matches " + channelUri); return null; } cursor.moveToNext(); return Channel.fromCursor(cursor); } catch (Exception e) { Log.w(TAG, "Unable to get the channel with URI " + channelUri, e); return null; } finally { if (cursor != null) { cursor.close(); } } } /** * Returns the current list of programs on a given channel. * * @param resolver Application's ContentResolver. * @param channelUri Channel's Uri. * @return List of programs. * @hide */ public static List
getPrograms(ContentResolver resolver, Uri channelUri) { if (channelUri == null) { return null; } Uri uri = TvContract.buildProgramsUriForChannel(channelUri); List
programs = new ArrayList<>(); // TvProvider returns programs in chronological order by default. Cursor cursor = null; try { cursor = resolver.query(uri, Program.PROJECTION, null, null, null); if (cursor == null || cursor.getCount() == 0) { return programs; } while (cursor.moveToNext()) { programs.add(Program.fromCursor(cursor)); } } catch (Exception e) { Log.w(TAG, "Unable to get programs for " + channelUri, e); } finally { if (cursor != null) { cursor.close(); } } return programs; } /** * Returns the program that is scheduled to be playing now on a given channel. * * @param resolver Application's ContentResolver. * @param channelUri Channel's Uri. * @return The program that is scheduled for now in the EPG. */ public static Program getCurrentProgram(ContentResolver resolver, Uri channelUri) { List
programs = getPrograms(resolver, channelUri); if (programs == null) { return null; } long nowMs = System.currentTimeMillis(); for (Program program : programs) { if (program.getStartTimeUtcMillis() <= nowMs && program.getEndTimeUtcMillis() > nowMs) { return program; } } return null; } /** * Returns the program that is scheduled to be playing after a given program on a given channel. * * @param resolver Application's ContentResolver. * @param channelUri Channel's Uri. * @param currentProgram Program which plays before the desired program.If null, returns current * program * @return The program that is scheduled after given program in the EPG. */ public static Program getNextProgram(ContentResolver resolver, Uri channelUri, Program currentProgram) { if (currentProgram == null) { return getCurrentProgram(resolver, channelUri); } List
programs = getPrograms(resolver, channelUri); if (programs == null) { return null; } int currentProgramIndex = programs.indexOf(currentProgram); if (currentProgramIndex + 1 < programs.size()) { return programs.get(currentProgramIndex + 1); } return null; } private static void insertUrl(Context context, Uri contentUri, URL sourceUrl) { if (DEBUG) { Log.d(TAG, "Inserting " + sourceUrl + " to " + contentUri); } InputStream is = null; OutputStream os = null; try { is = sourceUrl.openStream(); os = context.getContentResolver().openOutputStream(contentUri); copy(is, os); } catch (IOException ioe) { Log.e(TAG, "Failed to write " + sourceUrl + " to " + contentUri, ioe); } finally { if (is != null) { try { is.close(); } catch (IOException e) { // Ignore exception. } } if (os != null) { try { os.close(); } catch (IOException e) { // Ignore exception. } } } } private static void copy(InputStream is, OutputStream os) throws IOException { byte[] buffer = new byte[1024]; int len; while ((len = is.read(buffer)) != -1) { os.write(buffer, 0, len); } } /** * Parses a string of comma-separated ratings into an array of {@link TvContentRating}. * * @param commaSeparatedRatings String containing various ratings, separated by commas. * @return An array of TvContentRatings. * @hide */ public static TvContentRating[] stringToContentRatings(String commaSeparatedRatings) { if (TextUtils.isEmpty(commaSeparatedRatings)) { return null; } String[] ratings = commaSeparatedRatings.split("\\s*,\\s*"); TvContentRating[] contentRatings = new TvContentRating[ratings.length]; for (int i = 0; i < contentRatings.length; ++i) { contentRatings[i] = TvContentRating.unflattenFromString(ratings[i]); } return contentRatings; } /** * Flattens an array of {@link TvContentRating} into a String to be inserted into a database. * * @param contentRatings An array of TvContentRatings. * @return A comma-separated String of ratings. * @hide */ public static String contentRatingsToString(TvContentRating[] contentRatings) { if (contentRatings == null || contentRatings.length == 0) { return null; } final String DELIMITER = ","; StringBuilder ratings = new StringBuilder(contentRatings[0].flattenToString()); for (int i = 1; i < contentRatings.length; ++i) { ratings.append(DELIMITER); ratings.append(contentRatings[i].flattenToString()); } return ratings.toString(); } private TvContractUtils() { } private static class InsertLogosTask extends AsyncTask
, Void, Void> { private final Context mContext; InsertLogosTask(Context context) { mContext = context; } @Override public Void doInBackground(Map
... logosList) { for (Map
logos : logosList) { for (Uri uri : logos.keySet()) { try { insertUrl(mContext, uri, new URL(logos.get(uri))); } catch (MalformedURLException e) { Log.e(TAG, "Can't load " + logos.get(uri), e); } } } return null; } } }