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