Home | History | Annotate | Download | only in data
      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 
     17 package com.android.tv.data;
     18 
     19 import android.annotation.TargetApi;
     20 import android.content.ContentResolver;
     21 import android.content.ContentUris;
     22 import android.content.Context;
     23 import android.database.Cursor;
     24 import android.database.SQLException;
     25 import android.graphics.Bitmap;
     26 import android.graphics.drawable.BitmapDrawable;
     27 import android.graphics.drawable.Drawable;
     28 import android.media.tv.TvContract;
     29 import android.net.Uri;
     30 import android.os.AsyncTask;
     31 import android.os.Build;
     32 import android.support.annotation.IntDef;
     33 import android.support.annotation.MainThread;
     34 import android.support.media.tv.ChannelLogoUtils;
     35 import android.support.media.tv.PreviewProgram;
     36 import android.util.Log;
     37 import android.util.Pair;
     38 
     39 import com.android.tv.R;
     40 import com.android.tv.util.PermissionUtils;
     41 
     42 import java.lang.annotation.Retention;
     43 import java.lang.annotation.RetentionPolicy;
     44 import java.util.HashMap;
     45 import java.util.Map;
     46 import java.util.Set;
     47 import java.util.concurrent.CopyOnWriteArraySet;
     48 
     49 /**
     50  * Class to manage the preview data.
     51  */
     52 @TargetApi(Build.VERSION_CODES.O)
     53 @MainThread
     54 public class PreviewDataManager {
     55     private static final String TAG = "PreviewDataManager";
     56     // STOPSHIP: set it to false.
     57     private static final boolean DEBUG = true;
     58 
     59     /**
     60      * Invalid preview channel ID.
     61      */
     62     public static final long INVALID_PREVIEW_CHANNEL_ID = -1;
     63     @IntDef({TYPE_DEFAULT_PREVIEW_CHANNEL, TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL})
     64     @Retention(RetentionPolicy.SOURCE)
     65     public @interface PreviewChannelType{}
     66 
     67     /**
     68      * Type of default preview channel
     69      */
     70     public static final long TYPE_DEFAULT_PREVIEW_CHANNEL = 1;
     71     /**
     72      * Type of recorded program channel
     73      */
     74     public static final long TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL = 2;
     75 
     76     private final Context mContext;
     77     private final ContentResolver mContentResolver;
     78     private boolean mLoadFinished;
     79     private PreviewData mPreviewData = new PreviewData();
     80     private final Set<PreviewDataListener> mPreviewDataListeners = new CopyOnWriteArraySet<>();
     81 
     82     private QueryPreviewDataTask mQueryPreviewTask;
     83     private final Map<Long, CreatePreviewChannelTask> mCreatePreviewChannelTasks =
     84             new HashMap<>();
     85     private final Map<Long, UpdatePreviewProgramTask> mUpdatePreviewProgramTasks = new HashMap<>();
     86 
     87     private final int mPreviewChannelLogoWidth;
     88     private final int mPreviewChannelLogoHeight;
     89 
     90     public PreviewDataManager(Context context) {
     91         mContext = context.getApplicationContext();
     92         mContentResolver = context.getContentResolver();
     93         mPreviewChannelLogoWidth = mContext.getResources().getDimensionPixelSize(
     94                 R.dimen.preview_channel_logo_width);
     95         mPreviewChannelLogoHeight = mContext.getResources().getDimensionPixelSize(
     96                 R.dimen.preview_channel_logo_height);
     97     }
     98 
     99     /**
    100      * Starts the preview data manager.
    101      */
    102     public void start() {
    103         if (mQueryPreviewTask == null) {
    104             mQueryPreviewTask = new QueryPreviewDataTask();
    105             mQueryPreviewTask.execute();
    106         }
    107     }
    108 
    109     /**
    110      * Stops the preview data manager.
    111      */
    112     public void stop() {
    113         if (mQueryPreviewTask != null) {
    114             mQueryPreviewTask.cancel(true);
    115         }
    116         for (CreatePreviewChannelTask createPreviewChannelTask
    117                 : mCreatePreviewChannelTasks.values()) {
    118             createPreviewChannelTask.cancel(true);
    119         }
    120         for (UpdatePreviewProgramTask updatePreviewProgramTask
    121                 : mUpdatePreviewProgramTasks.values()) {
    122             updatePreviewProgramTask.cancel(true);
    123         }
    124 
    125         mQueryPreviewTask = null;
    126         mCreatePreviewChannelTasks.clear();
    127         mUpdatePreviewProgramTasks.clear();
    128     }
    129 
    130     /**
    131      * Gets preview channel ID from the preview channel type.
    132      */
    133     public @PreviewChannelType long getPreviewChannelId(long previewChannelType) {
    134         return mPreviewData.getPreviewChannelId(previewChannelType);
    135     }
    136 
    137     /**
    138      * Creates default preview channel.
    139      */
    140     public void createDefaultPreviewChannel(
    141             OnPreviewChannelCreationResultListener onPreviewChannelCreationResultListener) {
    142         createPreviewChannel(TYPE_DEFAULT_PREVIEW_CHANNEL, onPreviewChannelCreationResultListener);
    143     }
    144 
    145     /**
    146      * Creates a preview channel for specific channel type.
    147      */
    148     public void createPreviewChannel(@PreviewChannelType long previewChannelType,
    149             OnPreviewChannelCreationResultListener onPreviewChannelCreationResultListener) {
    150         CreatePreviewChannelTask currentRunningCreateTask =
    151                 mCreatePreviewChannelTasks.get(previewChannelType);
    152         if (currentRunningCreateTask == null) {
    153             CreatePreviewChannelTask createPreviewChannelTask = new CreatePreviewChannelTask(
    154                     previewChannelType);
    155             createPreviewChannelTask.addOnPreviewChannelCreationResultListener(
    156                     onPreviewChannelCreationResultListener);
    157             createPreviewChannelTask.execute();
    158             mCreatePreviewChannelTasks.put(previewChannelType, createPreviewChannelTask);
    159         } else {
    160             currentRunningCreateTask.addOnPreviewChannelCreationResultListener(
    161                     onPreviewChannelCreationResultListener);
    162         }
    163     }
    164 
    165     /**
    166      * Returns {@code true} if the preview data is loaded.
    167      */
    168     public boolean isLoadFinished() {
    169         return mLoadFinished;
    170     }
    171 
    172     /**
    173      * Adds listener.
    174      */
    175     public void addListener(PreviewDataListener previewDataListener) {
    176         mPreviewDataListeners.add(previewDataListener);
    177     }
    178 
    179     /**
    180      * Removes listener.
    181      */
    182     public void removeListener(PreviewDataListener previewDataListener) {
    183         mPreviewDataListeners.remove(previewDataListener);
    184     }
    185 
    186     /**
    187      * Updates the preview programs table for a specific preview channel.
    188      */
    189     public void updatePreviewProgramsForChannel(long previewChannelId,
    190             Set<PreviewProgramContent> programs, PreviewDataListener previewDataListener) {
    191         UpdatePreviewProgramTask currentRunningUpdateTask =
    192                 mUpdatePreviewProgramTasks.get(previewChannelId);
    193         if (currentRunningUpdateTask != null
    194                 && currentRunningUpdateTask.getPrograms().equals(programs)) {
    195             currentRunningUpdateTask.addPreviewDataListener(previewDataListener);
    196             return;
    197         }
    198         UpdatePreviewProgramTask updatePreviewProgramTask =
    199                 new UpdatePreviewProgramTask(previewChannelId, programs);
    200         updatePreviewProgramTask.addPreviewDataListener(previewDataListener);
    201         if (currentRunningUpdateTask != null) {
    202             currentRunningUpdateTask.cancel(true);
    203             currentRunningUpdateTask.saveStatus();
    204             updatePreviewProgramTask.addPreviewDataListeners(
    205                     currentRunningUpdateTask.getPreviewDataListeners());
    206         }
    207         updatePreviewProgramTask.execute();
    208         mUpdatePreviewProgramTasks.put(previewChannelId, updatePreviewProgramTask);
    209     }
    210 
    211     private void notifyPreviewDataLoadFinished() {
    212         for (PreviewDataListener l : mPreviewDataListeners) {
    213             l.onPreviewDataLoadFinished();
    214         }
    215     }
    216 
    217     public interface PreviewDataListener {
    218         /**
    219          * Called when the preview data is loaded.
    220          */
    221         void onPreviewDataLoadFinished();
    222 
    223         /**
    224          * Called when the preview data is updated.
    225          */
    226         void onPreviewDataUpdateFinished();
    227     }
    228 
    229     public interface OnPreviewChannelCreationResultListener {
    230         /**
    231          * Called when the creation of preview channel is finished.
    232          * @param createdPreviewChannelId The preview channel ID if created successfully,
    233          *        otherwise it's {@value #INVALID_PREVIEW_CHANNEL_ID}.
    234          */
    235         void onPreviewChannelCreationResult(long createdPreviewChannelId);
    236     }
    237 
    238     private final class QueryPreviewDataTask extends AsyncTask<Void, Void, PreviewData> {
    239         private final String PARAM_PREVIEW = "preview";
    240         private final String mChannelSelection = TvContract.Channels.COLUMN_PACKAGE_NAME + "=?";
    241 
    242         @Override
    243         protected PreviewData doInBackground(Void... voids) {
    244             // Query preview channels and programs.
    245             if (DEBUG) Log.d(TAG, "QueryPreviewDataTask.doInBackground");
    246             PreviewData previewData = new PreviewData();
    247             try {
    248                 Uri previewChannelsUri =
    249                         PreviewDataUtils.addQueryParamToUri(
    250                                 TvContract.Channels.CONTENT_URI,
    251                                 new Pair<>(PARAM_PREVIEW, String.valueOf(true)));
    252                 String packageName = mContext.getPackageName();
    253                 if (PermissionUtils.hasAccessAllEpg(mContext)) {
    254                     try (Cursor cursor =
    255                             mContentResolver.query(
    256                                     previewChannelsUri,
    257                                     android.support.media.tv.Channel.PROJECTION,
    258                                     mChannelSelection,
    259                                     new String[] {packageName},
    260                                     null)) {
    261                         if (cursor != null) {
    262                             while (cursor.moveToNext()) {
    263                                 android.support.media.tv.Channel previewChannel =
    264                                         android.support.media.tv.Channel.fromCursor(cursor);
    265                                 Long previewChannelType = previewChannel.getInternalProviderFlag1();
    266                                 if (previewChannelType != null) {
    267                                     previewData.addPreviewChannelId(
    268                                             previewChannelType, previewChannel.getId());
    269                                 }
    270                             }
    271                         }
    272                     }
    273                 } else {
    274                     try (Cursor cursor =
    275                             mContentResolver.query(
    276                                     previewChannelsUri,
    277                                     android.support.media.tv.Channel.PROJECTION,
    278                                     null,
    279                                     null,
    280                                     null)) {
    281                         if (cursor != null) {
    282                             while (cursor.moveToNext()) {
    283                                 android.support.media.tv.Channel previewChannel =
    284                                         android.support.media.tv.Channel.fromCursor(cursor);
    285                                 Long previewChannelType = previewChannel.getInternalProviderFlag1();
    286                                 if (previewChannel.getPackageName() == packageName
    287                                         && previewChannelType != null) {
    288                                     previewData.addPreviewChannelId(
    289                                             previewChannelType, previewChannel.getId());
    290                                 }
    291                             }
    292                         }
    293                     }
    294                 }
    295 
    296                 for (long previewChannelId : previewData.getAllPreviewChannelIds().values()) {
    297                     Uri previewProgramsUriForPreviewChannel =
    298                             TvContract.buildPreviewProgramsUriForChannel(previewChannelId);
    299                     try (Cursor previewProgramCursor =
    300                             mContentResolver.query(
    301                                     previewProgramsUriForPreviewChannel,
    302                                     PreviewProgram.PROJECTION,
    303                                     null,
    304                                     null,
    305                                     null)) {
    306                         if (previewProgramCursor != null) {
    307                             while (previewProgramCursor.moveToNext()) {
    308                                 PreviewProgram previewProgram =
    309                                         PreviewProgram.fromCursor(previewProgramCursor);
    310                                 previewData.addPreviewProgram(previewProgram);
    311                             }
    312                         }
    313                     }
    314                 }
    315             } catch (SQLException e) {
    316                 Log.w(TAG, "Unable to get preview data", e);
    317             }
    318             return previewData;
    319         }
    320 
    321         @Override
    322         protected void onPostExecute(PreviewData result) {
    323             super.onPostExecute(result);
    324             if (mQueryPreviewTask == this) {
    325                 mQueryPreviewTask = null;
    326                 mPreviewData = new PreviewData(result);
    327                 mLoadFinished = true;
    328                 notifyPreviewDataLoadFinished();
    329             }
    330         }
    331     }
    332 
    333     private final class CreatePreviewChannelTask extends AsyncTask<Void, Void, Long> {
    334         private final long mPreviewChannelType;
    335         private Set<OnPreviewChannelCreationResultListener>
    336                 mOnPreviewChannelCreationResultListeners = new CopyOnWriteArraySet<>();
    337 
    338         public CreatePreviewChannelTask(long previewChannelType) {
    339             mPreviewChannelType = previewChannelType;
    340         }
    341 
    342         public void addOnPreviewChannelCreationResultListener(
    343                 OnPreviewChannelCreationResultListener onPreviewChannelCreationResultListener) {
    344             if (onPreviewChannelCreationResultListener != null) {
    345                 mOnPreviewChannelCreationResultListeners.add(
    346                         onPreviewChannelCreationResultListener);
    347             }
    348         }
    349 
    350         @Override
    351         protected Long doInBackground(Void... params) {
    352             if (DEBUG) Log.d(TAG, "CreatePreviewChannelTask.doInBackground");
    353             long previewChannelId;
    354             try {
    355                 Uri channelUri = mContentResolver.insert(TvContract.Channels.CONTENT_URI,
    356                         PreviewDataUtils.createPreviewChannel(mContext, mPreviewChannelType)
    357                                 .toContentValues());
    358                 if (channelUri != null) {
    359                     previewChannelId = ContentUris.parseId(channelUri);
    360                 } else {
    361                     Log.e(TAG, "Fail to insert preview channel");
    362                     return INVALID_PREVIEW_CHANNEL_ID;
    363                 }
    364             } catch (UnsupportedOperationException | NumberFormatException e) {
    365                 Log.e(TAG, "Fail to get channel ID");
    366                 return INVALID_PREVIEW_CHANNEL_ID;
    367             }
    368             Drawable appIcon = mContext.getApplicationInfo().loadIcon(mContext.getPackageManager());
    369             if (appIcon != null && appIcon instanceof BitmapDrawable) {
    370                 ChannelLogoUtils.storeChannelLogo(mContext, previewChannelId,
    371                         Bitmap.createScaledBitmap(((BitmapDrawable) appIcon).getBitmap(),
    372                                 mPreviewChannelLogoWidth, mPreviewChannelLogoHeight, false));
    373             }
    374             return previewChannelId;
    375         }
    376 
    377         @Override
    378         protected void onPostExecute(Long result) {
    379             super.onPostExecute(result);
    380             if (result != INVALID_PREVIEW_CHANNEL_ID) {
    381                 mPreviewData.addPreviewChannelId(mPreviewChannelType, result);
    382             }
    383             for (OnPreviewChannelCreationResultListener onPreviewChannelCreationResultListener
    384                     : mOnPreviewChannelCreationResultListeners) {
    385                 onPreviewChannelCreationResultListener.onPreviewChannelCreationResult(result);
    386             }
    387             mCreatePreviewChannelTasks.remove(mPreviewChannelType);
    388         }
    389     }
    390 
    391     /**
    392      * Updates the whole data which belongs to the package in preview programs table for a
    393      * specific preview channel with a set of {@link PreviewProgramContent}.
    394      */
    395     private final class UpdatePreviewProgramTask extends AsyncTask<Void, Void, Void> {
    396         private long mPreviewChannelId;
    397         private Set<PreviewProgramContent> mPrograms;
    398         private Map<Long, Long> mCurrentProgramId2PreviewProgramId;
    399         private Set<PreviewDataListener> mPreviewDataListeners = new CopyOnWriteArraySet<>();
    400 
    401         public UpdatePreviewProgramTask(long previewChannelId,
    402                 Set<PreviewProgramContent> programs) {
    403             mPreviewChannelId = previewChannelId;
    404             mPrograms = programs;
    405             if (mPreviewData.getPreviewProgramIds(previewChannelId) == null) {
    406                 mCurrentProgramId2PreviewProgramId = new HashMap<>();
    407             } else {
    408                 mCurrentProgramId2PreviewProgramId = new HashMap<>(
    409                         mPreviewData.getPreviewProgramIds(previewChannelId));
    410             }
    411         }
    412 
    413         public void addPreviewDataListener(PreviewDataListener previewDataListener) {
    414             if (previewDataListener != null) {
    415                 mPreviewDataListeners.add(previewDataListener);
    416             }
    417         }
    418 
    419         public void addPreviewDataListeners(Set<PreviewDataListener> previewDataListeners) {
    420             if (previewDataListeners != null) {
    421                 mPreviewDataListeners.addAll(previewDataListeners);
    422             }
    423         }
    424 
    425         public Set<PreviewProgramContent> getPrograms() {
    426             return mPrograms;
    427         }
    428 
    429         public Set<PreviewDataListener> getPreviewDataListeners() {
    430             return mPreviewDataListeners;
    431         }
    432 
    433         @Override
    434         protected Void doInBackground(Void... params) {
    435             if (DEBUG) Log.d(TAG, "UpdatePreviewProgamTask.doInBackground");
    436             Map<Long, Long> uncheckedPrograms = new HashMap<>(mCurrentProgramId2PreviewProgramId);
    437             for (PreviewProgramContent program : mPrograms) {
    438                 if (isCancelled()) {
    439                     return null;
    440                 }
    441                 Long existingPreviewProgramId = uncheckedPrograms.remove(program.getId());
    442                 if (existingPreviewProgramId != null) {
    443                     if (DEBUG) Log.d(TAG, "Preview program " + existingPreviewProgramId + " " +
    444                             "already exists for program " + program.getId());
    445                     continue;
    446                 }
    447                 try {
    448                     Uri programUri = mContentResolver.insert(TvContract.PreviewPrograms.CONTENT_URI,
    449                             PreviewDataUtils.createPreviewProgramFromContent(program)
    450                                     .toContentValues());
    451                     if (programUri != null) {
    452                         long previewProgramId = ContentUris.parseId(programUri);
    453                         mCurrentProgramId2PreviewProgramId.put(program.getId(), previewProgramId);
    454                         if (DEBUG) Log.d(TAG, "Add new preview program " + previewProgramId);
    455                     } else {
    456                         Log.e(TAG, "Fail to insert preview program");
    457                     }
    458                 } catch (Exception e) {
    459                     Log.e(TAG, "Fail to get preview program ID");
    460                 }
    461             }
    462 
    463             for (Long key : uncheckedPrograms.keySet()) {
    464                 if (isCancelled()) {
    465                     return null;
    466                 }
    467                 try {
    468                     if (DEBUG) Log.d(TAG, "Remove preview program " + uncheckedPrograms.get(key));
    469                     mContentResolver.delete(TvContract.buildPreviewProgramUri(
    470                             uncheckedPrograms.get(key)), null, null);
    471                     mCurrentProgramId2PreviewProgramId.remove(key);
    472                 } catch (Exception e) {
    473                     Log.e(TAG, "Fail to remove preview program " + uncheckedPrograms.get(key));
    474                 }
    475             }
    476             return null;
    477         }
    478 
    479         @Override
    480         protected void onPostExecute(Void result) {
    481             super.onPostExecute(result);
    482             mPreviewData.setPreviewProgramIds(
    483                     mPreviewChannelId, mCurrentProgramId2PreviewProgramId);
    484             mUpdatePreviewProgramTasks.remove(mPreviewChannelId);
    485             for (PreviewDataListener previewDataListener : mPreviewDataListeners) {
    486                 previewDataListener.onPreviewDataUpdateFinished();
    487             }
    488         }
    489 
    490         public void saveStatus() {
    491             mPreviewData.setPreviewProgramIds(
    492                     mPreviewChannelId, mCurrentProgramId2PreviewProgramId);
    493         }
    494     }
    495 
    496     /**
    497      * Class to store the query result of preview data.
    498      */
    499     private static final class PreviewData {
    500         private Map<Long, Long> mPreviewChannelType2Id = new HashMap<>();
    501         private Map<Long, Map<Long, Long>> mProgramId2PreviewProgramId = new HashMap<>();
    502 
    503         PreviewData() {
    504             mPreviewChannelType2Id = new HashMap<>();
    505             mProgramId2PreviewProgramId = new HashMap<>();
    506         }
    507 
    508         PreviewData(PreviewData previewData) {
    509             mPreviewChannelType2Id = new HashMap<>(previewData.mPreviewChannelType2Id);
    510             mProgramId2PreviewProgramId = new HashMap<>(previewData.mProgramId2PreviewProgramId);
    511         }
    512 
    513         public void addPreviewProgram(PreviewProgram previewProgram) {
    514             long previewChannelId = previewProgram.getChannelId();
    515             Map<Long, Long> programId2PreviewProgram =
    516                     mProgramId2PreviewProgramId.get(previewChannelId);
    517             if (programId2PreviewProgram == null) {
    518                 programId2PreviewProgram = new HashMap<>();
    519             }
    520             mProgramId2PreviewProgramId.put(previewChannelId, programId2PreviewProgram);
    521             if (previewProgram.getInternalProviderId() != null) {
    522                 programId2PreviewProgram.put(
    523                         Long.parseLong(previewProgram.getInternalProviderId()),
    524                         previewProgram.getId());
    525             }
    526         }
    527 
    528         public @PreviewChannelType long getPreviewChannelId(long previewChannelType) {
    529             Long result = mPreviewChannelType2Id.get(previewChannelType);
    530             return result == null ? INVALID_PREVIEW_CHANNEL_ID : result;
    531         }
    532 
    533         public Map<Long, Long> getAllPreviewChannelIds() {
    534             return mPreviewChannelType2Id;
    535         }
    536 
    537         public void addPreviewChannelId(long previewChannelType, long previewChannelId) {
    538             mPreviewChannelType2Id.put(previewChannelType, previewChannelId);
    539         }
    540 
    541         public void removePreviewChannelId(long previewChannelType) {
    542             mPreviewChannelType2Id.remove(previewChannelType);
    543         }
    544 
    545         public void removePreviewChannel(long previewChannelId) {
    546             removePreviewChannelId(previewChannelId);
    547             removePreviewProgramIds(previewChannelId);
    548         }
    549 
    550         public Map<Long, Long> getPreviewProgramIds(long previewChannelId) {
    551             return mProgramId2PreviewProgramId.get(previewChannelId);
    552         }
    553 
    554         public Map<Long, Map<Long, Long>> getAllPreviewProgramIds() {
    555             return mProgramId2PreviewProgramId;
    556         }
    557 
    558         public void setPreviewProgramIds(
    559                 long previewChannelId, Map<Long, Long> programId2PreviewProgramId) {
    560             mProgramId2PreviewProgramId.put(previewChannelId, programId2PreviewProgramId);
    561         }
    562 
    563         public void removePreviewProgramIds(long previewChannelId) {
    564             mProgramId2PreviewProgramId.remove(previewChannelId);
    565         }
    566     }
    567 
    568     /**
    569      * A utils class for preview data.
    570      */
    571     public final static class PreviewDataUtils {
    572         /**
    573          * Creates a preview channel.
    574          */
    575         public static android.support.media.tv.Channel createPreviewChannel(
    576                 Context context, @PreviewChannelType long previewChannelType) {
    577             if (previewChannelType == TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL) {
    578                 return createRecordedProgramPreviewChannel(context, previewChannelType);
    579             }
    580             return createDefaultPreviewChannel(context, previewChannelType);
    581         }
    582 
    583         private static android.support.media.tv.Channel createDefaultPreviewChannel(
    584                 Context context, @PreviewChannelType long previewChannelType) {
    585             android.support.media.tv.Channel.Builder builder =
    586                     new android.support.media.tv.Channel.Builder();
    587             CharSequence appLabel =
    588                     context.getApplicationInfo().loadLabel(context.getPackageManager());
    589             CharSequence appDescription =
    590                     context.getApplicationInfo().loadDescription(context.getPackageManager());
    591             builder.setType(TvContract.Channels.TYPE_PREVIEW)
    592                     .setDisplayName(appLabel == null ? null : appLabel.toString())
    593                     .setDescription(appDescription == null ?  null : appDescription.toString())
    594                     .setAppLinkIntentUri(TvContract.Channels.CONTENT_URI)
    595                     .setInternalProviderFlag1(previewChannelType);
    596             return builder.build();
    597         }
    598 
    599         private static android.support.media.tv.Channel createRecordedProgramPreviewChannel(
    600                 Context context, @PreviewChannelType long previewChannelType) {
    601             android.support.media.tv.Channel.Builder builder =
    602                     new android.support.media.tv.Channel.Builder();
    603             builder.setType(TvContract.Channels.TYPE_PREVIEW)
    604                     .setDisplayName(context.getResources().getString(
    605                             R.string.recorded_programs_preview_channel))
    606                     .setAppLinkIntentUri(TvContract.Channels.CONTENT_URI)
    607                     .setInternalProviderFlag1(previewChannelType);
    608             return builder.build();
    609         }
    610 
    611         /**
    612          * Creates a preview program.
    613          */
    614         public static PreviewProgram createPreviewProgramFromContent(
    615                 PreviewProgramContent program) {
    616             PreviewProgram.Builder builder = new PreviewProgram.Builder();
    617             builder.setChannelId(program.getPreviewChannelId())
    618                     .setType(program.getType())
    619                     .setLive(program.getLive())
    620                     .setTitle(program.getTitle())
    621                     .setDescription(program.getDescription())
    622                     .setPosterArtUri(program.getPosterArtUri())
    623                     .setIntentUri(program.getIntentUri())
    624                     .setPreviewVideoUri(program.getPreviewVideoUri())
    625                     .setInternalProviderId(Long.toString(program.getId()));
    626             return builder.build();
    627         }
    628 
    629         /**
    630          * Appends query parameters to a Uri.
    631          */
    632         public static Uri addQueryParamToUri(Uri uri, Pair<String, String> param) {
    633             return uri.buildUpon().appendQueryParameter(param.first, param.second).build();
    634         }
    635     }
    636 }
    637