aboutsummaryrefslogtreecommitdiff
path: root/src/com/android/tv/data/PreviewDataManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/tv/data/PreviewDataManager.java')
-rw-r--r--src/com/android/tv/data/PreviewDataManager.java636
1 files changed, 636 insertions, 0 deletions
diff --git a/src/com/android/tv/data/PreviewDataManager.java b/src/com/android/tv/data/PreviewDataManager.java
new file mode 100644
index 00000000..01a58520
--- /dev/null
+++ b/src/com/android/tv/data/PreviewDataManager.java
@@ -0,0 +1,636 @@
+/*
+ * Copyright (C) 2017 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.android.tv.data;
+
+import android.annotation.TargetApi;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.media.tv.TvContract;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.support.annotation.IntDef;
+import android.support.annotation.MainThread;
+import android.support.media.tv.ChannelLogoUtils;
+import android.support.media.tv.PreviewProgram;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.tv.R;
+import com.android.tv.util.PermissionUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+/**
+ * Class to manage the preview data.
+ */
+@TargetApi(Build.VERSION_CODES.O)
+@MainThread
+public class PreviewDataManager {
+ private static final String TAG = "PreviewDataManager";
+ // STOPSHIP: set it to false.
+ private static final boolean DEBUG = true;
+
+ /**
+ * Invalid preview channel ID.
+ */
+ public static final long INVALID_PREVIEW_CHANNEL_ID = -1;
+ @IntDef({TYPE_DEFAULT_PREVIEW_CHANNEL, TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PreviewChannelType{}
+
+ /**
+ * Type of default preview channel
+ */
+ public static final long TYPE_DEFAULT_PREVIEW_CHANNEL = 1;
+ /**
+ * Type of recorded program channel
+ */
+ public static final long TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL = 2;
+
+ private final Context mContext;
+ private final ContentResolver mContentResolver;
+ private boolean mLoadFinished;
+ private PreviewData mPreviewData = new PreviewData();
+ private final Set<PreviewDataListener> mPreviewDataListeners = new CopyOnWriteArraySet<>();
+
+ private QueryPreviewDataTask mQueryPreviewTask;
+ private final Map<Long, CreatePreviewChannelTask> mCreatePreviewChannelTasks =
+ new HashMap<>();
+ private final Map<Long, UpdatePreviewProgramTask> mUpdatePreviewProgramTasks = new HashMap<>();
+
+ private final int mPreviewChannelLogoWidth;
+ private final int mPreviewChannelLogoHeight;
+
+ public PreviewDataManager(Context context) {
+ mContext = context.getApplicationContext();
+ mContentResolver = context.getContentResolver();
+ mPreviewChannelLogoWidth = mContext.getResources().getDimensionPixelSize(
+ R.dimen.preview_channel_logo_width);
+ mPreviewChannelLogoHeight = mContext.getResources().getDimensionPixelSize(
+ R.dimen.preview_channel_logo_height);
+ }
+
+ /**
+ * Starts the preview data manager.
+ */
+ public void start() {
+ if (mQueryPreviewTask == null) {
+ mQueryPreviewTask = new QueryPreviewDataTask();
+ mQueryPreviewTask.execute();
+ }
+ }
+
+ /**
+ * Stops the preview data manager.
+ */
+ public void stop() {
+ if (mQueryPreviewTask != null) {
+ mQueryPreviewTask.cancel(true);
+ }
+ for (CreatePreviewChannelTask createPreviewChannelTask
+ : mCreatePreviewChannelTasks.values()) {
+ createPreviewChannelTask.cancel(true);
+ }
+ for (UpdatePreviewProgramTask updatePreviewProgramTask
+ : mUpdatePreviewProgramTasks.values()) {
+ updatePreviewProgramTask.cancel(true);
+ }
+
+ mQueryPreviewTask = null;
+ mCreatePreviewChannelTasks.clear();
+ mUpdatePreviewProgramTasks.clear();
+ }
+
+ /**
+ * Gets preview channel ID from the preview channel type.
+ */
+ public @PreviewChannelType long getPreviewChannelId(long previewChannelType) {
+ return mPreviewData.getPreviewChannelId(previewChannelType);
+ }
+
+ /**
+ * Creates default preview channel.
+ */
+ public void createDefaultPreviewChannel(
+ OnPreviewChannelCreationResultListener onPreviewChannelCreationResultListener) {
+ createPreviewChannel(TYPE_DEFAULT_PREVIEW_CHANNEL, onPreviewChannelCreationResultListener);
+ }
+
+ /**
+ * Creates a preview channel for specific channel type.
+ */
+ public void createPreviewChannel(@PreviewChannelType long previewChannelType,
+ OnPreviewChannelCreationResultListener onPreviewChannelCreationResultListener) {
+ CreatePreviewChannelTask currentRunningCreateTask =
+ mCreatePreviewChannelTasks.get(previewChannelType);
+ if (currentRunningCreateTask == null) {
+ CreatePreviewChannelTask createPreviewChannelTask = new CreatePreviewChannelTask(
+ previewChannelType);
+ createPreviewChannelTask.addOnPreviewChannelCreationResultListener(
+ onPreviewChannelCreationResultListener);
+ createPreviewChannelTask.execute();
+ mCreatePreviewChannelTasks.put(previewChannelType, createPreviewChannelTask);
+ } else {
+ currentRunningCreateTask.addOnPreviewChannelCreationResultListener(
+ onPreviewChannelCreationResultListener);
+ }
+ }
+
+ /**
+ * Returns {@code true} if the preview data is loaded.
+ */
+ public boolean isLoadFinished() {
+ return mLoadFinished;
+ }
+
+ /**
+ * Adds listener.
+ */
+ public void addListener(PreviewDataListener previewDataListener) {
+ mPreviewDataListeners.add(previewDataListener);
+ }
+
+ /**
+ * Removes listener.
+ */
+ public void removeListener(PreviewDataListener previewDataListener) {
+ mPreviewDataListeners.remove(previewDataListener);
+ }
+
+ /**
+ * Updates the preview programs table for a specific preview channel.
+ */
+ public void updatePreviewProgramsForChannel(long previewChannelId,
+ Set<PreviewProgramContent> programs, PreviewDataListener previewDataListener) {
+ UpdatePreviewProgramTask currentRunningUpdateTask =
+ mUpdatePreviewProgramTasks.get(previewChannelId);
+ if (currentRunningUpdateTask != null
+ && currentRunningUpdateTask.getPrograms().equals(programs)) {
+ currentRunningUpdateTask.addPreviewDataListener(previewDataListener);
+ return;
+ }
+ UpdatePreviewProgramTask updatePreviewProgramTask =
+ new UpdatePreviewProgramTask(previewChannelId, programs);
+ updatePreviewProgramTask.addPreviewDataListener(previewDataListener);
+ if (currentRunningUpdateTask != null) {
+ currentRunningUpdateTask.cancel(true);
+ currentRunningUpdateTask.saveStatus();
+ updatePreviewProgramTask.addPreviewDataListeners(
+ currentRunningUpdateTask.getPreviewDataListeners());
+ }
+ updatePreviewProgramTask.execute();
+ mUpdatePreviewProgramTasks.put(previewChannelId, updatePreviewProgramTask);
+ }
+
+ private void notifyPreviewDataLoadFinished() {
+ for (PreviewDataListener l : mPreviewDataListeners) {
+ l.onPreviewDataLoadFinished();
+ }
+ }
+
+ public interface PreviewDataListener {
+ /**
+ * Called when the preview data is loaded.
+ */
+ void onPreviewDataLoadFinished();
+
+ /**
+ * Called when the preview data is updated.
+ */
+ void onPreviewDataUpdateFinished();
+ }
+
+ public interface OnPreviewChannelCreationResultListener {
+ /**
+ * Called when the creation of preview channel is finished.
+ * @param createdPreviewChannelId The preview channel ID if created successfully,
+ * otherwise it's {@value #INVALID_PREVIEW_CHANNEL_ID}.
+ */
+ void onPreviewChannelCreationResult(long createdPreviewChannelId);
+ }
+
+ private final class QueryPreviewDataTask extends AsyncTask<Void, Void, PreviewData> {
+ private final String PARAM_PREVIEW = "preview";
+ private final String mChannelSelection = TvContract.Channels.COLUMN_PACKAGE_NAME + "=?";
+
+ @Override
+ protected PreviewData doInBackground(Void... voids) {
+ // Query preview channels and programs.
+ if (DEBUG) Log.d(TAG, "QueryPreviewDataTask.doInBackground");
+ PreviewData previewData = new PreviewData();
+ try {
+ Uri previewChannelsUri =
+ PreviewDataUtils.addQueryParamToUri(
+ TvContract.Channels.CONTENT_URI,
+ new Pair<>(PARAM_PREVIEW, String.valueOf(true)));
+ String packageName = mContext.getPackageName();
+ if (PermissionUtils.hasAccessAllEpg(mContext)) {
+ try (Cursor cursor =
+ mContentResolver.query(
+ previewChannelsUri,
+ android.support.media.tv.Channel.PROJECTION,
+ mChannelSelection,
+ new String[] {packageName},
+ null)) {
+ if (cursor != null) {
+ while (cursor.moveToNext()) {
+ android.support.media.tv.Channel previewChannel =
+ android.support.media.tv.Channel.fromCursor(cursor);
+ Long previewChannelType = previewChannel.getInternalProviderFlag1();
+ if (previewChannelType != null) {
+ previewData.addPreviewChannelId(
+ previewChannelType, previewChannel.getId());
+ }
+ }
+ }
+ }
+ } else {
+ try (Cursor cursor =
+ mContentResolver.query(
+ previewChannelsUri,
+ android.support.media.tv.Channel.PROJECTION,
+ null,
+ null,
+ null)) {
+ if (cursor != null) {
+ while (cursor.moveToNext()) {
+ android.support.media.tv.Channel previewChannel =
+ android.support.media.tv.Channel.fromCursor(cursor);
+ Long previewChannelType = previewChannel.getInternalProviderFlag1();
+ if (previewChannel.getPackageName() == packageName
+ && previewChannelType != null) {
+ previewData.addPreviewChannelId(
+ previewChannelType, previewChannel.getId());
+ }
+ }
+ }
+ }
+ }
+
+ for (long previewChannelId : previewData.getAllPreviewChannelIds().values()) {
+ Uri previewProgramsUriForPreviewChannel =
+ TvContract.buildPreviewProgramsUriForChannel(previewChannelId);
+ try (Cursor previewProgramCursor =
+ mContentResolver.query(
+ previewProgramsUriForPreviewChannel,
+ PreviewProgram.PROJECTION,
+ null,
+ null,
+ null)) {
+ if (previewProgramCursor != null) {
+ while (previewProgramCursor.moveToNext()) {
+ PreviewProgram previewProgram =
+ PreviewProgram.fromCursor(previewProgramCursor);
+ previewData.addPreviewProgram(previewProgram);
+ }
+ }
+ }
+ }
+ } catch (SQLException e) {
+ Log.w(TAG, "Unable to get preview data", e);
+ }
+ return previewData;
+ }
+
+ @Override
+ protected void onPostExecute(PreviewData result) {
+ super.onPostExecute(result);
+ if (mQueryPreviewTask == this) {
+ mQueryPreviewTask = null;
+ mPreviewData = new PreviewData(result);
+ mLoadFinished = true;
+ notifyPreviewDataLoadFinished();
+ }
+ }
+ }
+
+ private final class CreatePreviewChannelTask extends AsyncTask<Void, Void, Long> {
+ private final long mPreviewChannelType;
+ private Set<OnPreviewChannelCreationResultListener>
+ mOnPreviewChannelCreationResultListeners = new CopyOnWriteArraySet<>();
+
+ public CreatePreviewChannelTask(long previewChannelType) {
+ mPreviewChannelType = previewChannelType;
+ }
+
+ public void addOnPreviewChannelCreationResultListener(
+ OnPreviewChannelCreationResultListener onPreviewChannelCreationResultListener) {
+ if (onPreviewChannelCreationResultListener != null) {
+ mOnPreviewChannelCreationResultListeners.add(
+ onPreviewChannelCreationResultListener);
+ }
+ }
+
+ @Override
+ protected Long doInBackground(Void... params) {
+ if (DEBUG) Log.d(TAG, "CreatePreviewChannelTask.doInBackground");
+ long previewChannelId;
+ try {
+ Uri channelUri = mContentResolver.insert(TvContract.Channels.CONTENT_URI,
+ PreviewDataUtils.createPreviewChannel(mContext, mPreviewChannelType)
+ .toContentValues());
+ if (channelUri != null) {
+ previewChannelId = ContentUris.parseId(channelUri);
+ } else {
+ Log.e(TAG, "Fail to insert preview channel");
+ return INVALID_PREVIEW_CHANNEL_ID;
+ }
+ } catch (UnsupportedOperationException | NumberFormatException e) {
+ Log.e(TAG, "Fail to get channel ID");
+ return INVALID_PREVIEW_CHANNEL_ID;
+ }
+ Drawable appIcon = mContext.getApplicationInfo().loadIcon(mContext.getPackageManager());
+ if (appIcon != null && appIcon instanceof BitmapDrawable) {
+ ChannelLogoUtils.storeChannelLogo(mContext, previewChannelId,
+ Bitmap.createScaledBitmap(((BitmapDrawable) appIcon).getBitmap(),
+ mPreviewChannelLogoWidth, mPreviewChannelLogoHeight, false));
+ }
+ return previewChannelId;
+ }
+
+ @Override
+ protected void onPostExecute(Long result) {
+ super.onPostExecute(result);
+ if (result != INVALID_PREVIEW_CHANNEL_ID) {
+ mPreviewData.addPreviewChannelId(mPreviewChannelType, result);
+ }
+ for (OnPreviewChannelCreationResultListener onPreviewChannelCreationResultListener
+ : mOnPreviewChannelCreationResultListeners) {
+ onPreviewChannelCreationResultListener.onPreviewChannelCreationResult(result);
+ }
+ mCreatePreviewChannelTasks.remove(mPreviewChannelType);
+ }
+ }
+
+ /**
+ * Updates the whole data which belongs to the package in preview programs table for a
+ * specific preview channel with a set of {@link PreviewProgramContent}.
+ */
+ private final class UpdatePreviewProgramTask extends AsyncTask<Void, Void, Void> {
+ private long mPreviewChannelId;
+ private Set<PreviewProgramContent> mPrograms;
+ private Map<Long, Long> mCurrentProgramId2PreviewProgramId;
+ private Set<PreviewDataListener> mPreviewDataListeners = new CopyOnWriteArraySet<>();
+
+ public UpdatePreviewProgramTask(long previewChannelId,
+ Set<PreviewProgramContent> programs) {
+ mPreviewChannelId = previewChannelId;
+ mPrograms = programs;
+ if (mPreviewData.getPreviewProgramIds(previewChannelId) == null) {
+ mCurrentProgramId2PreviewProgramId = new HashMap<>();
+ } else {
+ mCurrentProgramId2PreviewProgramId = new HashMap<>(
+ mPreviewData.getPreviewProgramIds(previewChannelId));
+ }
+ }
+
+ public void addPreviewDataListener(PreviewDataListener previewDataListener) {
+ if (previewDataListener != null) {
+ mPreviewDataListeners.add(previewDataListener);
+ }
+ }
+
+ public void addPreviewDataListeners(Set<PreviewDataListener> previewDataListeners) {
+ if (previewDataListeners != null) {
+ mPreviewDataListeners.addAll(previewDataListeners);
+ }
+ }
+
+ public Set<PreviewProgramContent> getPrograms() {
+ return mPrograms;
+ }
+
+ public Set<PreviewDataListener> getPreviewDataListeners() {
+ return mPreviewDataListeners;
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ if (DEBUG) Log.d(TAG, "UpdatePreviewProgamTask.doInBackground");
+ Map<Long, Long> uncheckedPrograms = new HashMap<>(mCurrentProgramId2PreviewProgramId);
+ for (PreviewProgramContent program : mPrograms) {
+ if (isCancelled()) {
+ return null;
+ }
+ Long existingPreviewProgramId = uncheckedPrograms.remove(program.getId());
+ if (existingPreviewProgramId != null) {
+ if (DEBUG) Log.d(TAG, "Preview program " + existingPreviewProgramId + " " +
+ "already exists for program " + program.getId());
+ continue;
+ }
+ try {
+ Uri programUri = mContentResolver.insert(TvContract.PreviewPrograms.CONTENT_URI,
+ PreviewDataUtils.createPreviewProgramFromContent(program)
+ .toContentValues());
+ if (programUri != null) {
+ long previewProgramId = ContentUris.parseId(programUri);
+ mCurrentProgramId2PreviewProgramId.put(program.getId(), previewProgramId);
+ if (DEBUG) Log.d(TAG, "Add new preview program " + previewProgramId);
+ } else {
+ Log.e(TAG, "Fail to insert preview program");
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Fail to get preview program ID");
+ }
+ }
+
+ for (Long key : uncheckedPrograms.keySet()) {
+ if (isCancelled()) {
+ return null;
+ }
+ try {
+ if (DEBUG) Log.d(TAG, "Remove preview program " + uncheckedPrograms.get(key));
+ mContentResolver.delete(TvContract.buildPreviewProgramUri(
+ uncheckedPrograms.get(key)), null, null);
+ mCurrentProgramId2PreviewProgramId.remove(key);
+ } catch (Exception e) {
+ Log.e(TAG, "Fail to remove preview program " + uncheckedPrograms.get(key));
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ super.onPostExecute(result);
+ mPreviewData.setPreviewProgramIds(
+ mPreviewChannelId, mCurrentProgramId2PreviewProgramId);
+ mUpdatePreviewProgramTasks.remove(mPreviewChannelId);
+ for (PreviewDataListener previewDataListener : mPreviewDataListeners) {
+ previewDataListener.onPreviewDataUpdateFinished();
+ }
+ }
+
+ public void saveStatus() {
+ mPreviewData.setPreviewProgramIds(
+ mPreviewChannelId, mCurrentProgramId2PreviewProgramId);
+ }
+ }
+
+ /**
+ * Class to store the query result of preview data.
+ */
+ private static final class PreviewData {
+ private Map<Long, Long> mPreviewChannelType2Id = new HashMap<>();
+ private Map<Long, Map<Long, Long>> mProgramId2PreviewProgramId = new HashMap<>();
+
+ PreviewData() {
+ mPreviewChannelType2Id = new HashMap<>();
+ mProgramId2PreviewProgramId = new HashMap<>();
+ }
+
+ PreviewData(PreviewData previewData) {
+ mPreviewChannelType2Id = new HashMap<>(previewData.mPreviewChannelType2Id);
+ mProgramId2PreviewProgramId = new HashMap<>(previewData.mProgramId2PreviewProgramId);
+ }
+
+ public void addPreviewProgram(PreviewProgram previewProgram) {
+ long previewChannelId = previewProgram.getChannelId();
+ Map<Long, Long> programId2PreviewProgram =
+ mProgramId2PreviewProgramId.get(previewChannelId);
+ if (programId2PreviewProgram == null) {
+ programId2PreviewProgram = new HashMap<>();
+ }
+ mProgramId2PreviewProgramId.put(previewChannelId, programId2PreviewProgram);
+ if (previewProgram.getInternalProviderId() != null) {
+ programId2PreviewProgram.put(
+ Long.parseLong(previewProgram.getInternalProviderId()),
+ previewProgram.getId());
+ }
+ }
+
+ public @PreviewChannelType long getPreviewChannelId(long previewChannelType) {
+ Long result = mPreviewChannelType2Id.get(previewChannelType);
+ return result == null ? INVALID_PREVIEW_CHANNEL_ID : result;
+ }
+
+ public Map<Long, Long> getAllPreviewChannelIds() {
+ return mPreviewChannelType2Id;
+ }
+
+ public void addPreviewChannelId(long previewChannelType, long previewChannelId) {
+ mPreviewChannelType2Id.put(previewChannelType, previewChannelId);
+ }
+
+ public void removePreviewChannelId(long previewChannelType) {
+ mPreviewChannelType2Id.remove(previewChannelType);
+ }
+
+ public void removePreviewChannel(long previewChannelId) {
+ removePreviewChannelId(previewChannelId);
+ removePreviewProgramIds(previewChannelId);
+ }
+
+ public Map<Long, Long> getPreviewProgramIds(long previewChannelId) {
+ return mProgramId2PreviewProgramId.get(previewChannelId);
+ }
+
+ public Map<Long, Map<Long, Long>> getAllPreviewProgramIds() {
+ return mProgramId2PreviewProgramId;
+ }
+
+ public void setPreviewProgramIds(
+ long previewChannelId, Map<Long, Long> programId2PreviewProgramId) {
+ mProgramId2PreviewProgramId.put(previewChannelId, programId2PreviewProgramId);
+ }
+
+ public void removePreviewProgramIds(long previewChannelId) {
+ mProgramId2PreviewProgramId.remove(previewChannelId);
+ }
+ }
+
+ /**
+ * A utils class for preview data.
+ */
+ public final static class PreviewDataUtils {
+ /**
+ * Creates a preview channel.
+ */
+ public static android.support.media.tv.Channel createPreviewChannel(
+ Context context, @PreviewChannelType long previewChannelType) {
+ if (previewChannelType == TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL) {
+ return createRecordedProgramPreviewChannel(context, previewChannelType);
+ }
+ return createDefaultPreviewChannel(context, previewChannelType);
+ }
+
+ private static android.support.media.tv.Channel createDefaultPreviewChannel(
+ Context context, @PreviewChannelType long previewChannelType) {
+ android.support.media.tv.Channel.Builder builder =
+ new android.support.media.tv.Channel.Builder();
+ CharSequence appLabel =
+ context.getApplicationInfo().loadLabel(context.getPackageManager());
+ CharSequence appDescription =
+ context.getApplicationInfo().loadDescription(context.getPackageManager());
+ builder.setType(TvContract.Channels.TYPE_PREVIEW)
+ .setDisplayName(appLabel == null ? null : appLabel.toString())
+ .setDescription(appDescription == null ? null : appDescription.toString())
+ .setAppLinkIntentUri(TvContract.Channels.CONTENT_URI)
+ .setInternalProviderFlag1(previewChannelType);
+ return builder.build();
+ }
+
+ private static android.support.media.tv.Channel createRecordedProgramPreviewChannel(
+ Context context, @PreviewChannelType long previewChannelType) {
+ android.support.media.tv.Channel.Builder builder =
+ new android.support.media.tv.Channel.Builder();
+ builder.setType(TvContract.Channels.TYPE_PREVIEW)
+ .setDisplayName(context.getResources().getString(
+ R.string.recorded_programs_preview_channel))
+ .setAppLinkIntentUri(TvContract.Channels.CONTENT_URI)
+ .setInternalProviderFlag1(previewChannelType);
+ return builder.build();
+ }
+
+ /**
+ * Creates a preview program.
+ */
+ public static PreviewProgram createPreviewProgramFromContent(
+ PreviewProgramContent program) {
+ PreviewProgram.Builder builder = new PreviewProgram.Builder();
+ builder.setChannelId(program.getPreviewChannelId())
+ .setType(program.getType())
+ .setLive(program.getLive())
+ .setTitle(program.getTitle())
+ .setDescription(program.getDescription())
+ .setPosterArtUri(program.getPosterArtUri())
+ .setIntentUri(program.getIntentUri())
+ .setPreviewVideoUri(program.getPreviewVideoUri())
+ .setInternalProviderId(Long.toString(program.getId()));
+ return builder.build();
+ }
+
+ /**
+ * Appends query parameters to a Uri.
+ */
+ public static Uri addQueryParamToUri(Uri uri, Pair<String, String> param) {
+ return uri.buildUpon().appendQueryParameter(param.first, param.second).build();
+ }
+ }
+}