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