• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.ui;
18 
19 import android.content.Context;
20 import android.content.res.Resources;
21 import android.graphics.Bitmap;
22 import android.graphics.drawable.BitmapDrawable;
23 import android.graphics.drawable.Drawable;
24 import android.os.Bundle;
25 import android.support.annotation.Nullable;
26 import android.support.v17.leanback.app.DetailsFragment;
27 import android.support.v17.leanback.widget.Action;
28 import android.support.v17.leanback.widget.ArrayObjectAdapter;
29 import android.support.v17.leanback.widget.ClassPresenterSelector;
30 import android.support.v17.leanback.widget.DetailsOverviewRow;
31 import android.support.v17.leanback.widget.DetailsOverviewRowPresenter;
32 import android.support.v17.leanback.widget.OnActionClickedListener;
33 import android.support.v17.leanback.widget.PresenterSelector;
34 import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
35 import android.support.v17.leanback.widget.VerticalGridView;
36 import android.text.TextUtils;
37 import com.android.tv.R;
38 import com.android.tv.TvSingletons;
39 import com.android.tv.common.feature.CommonFeatures;
40 import com.android.tv.data.Program;
41 import com.android.tv.data.api.Channel;
42 import com.android.tv.dvr.DvrDataManager;
43 import com.android.tv.dvr.DvrManager;
44 import com.android.tv.dvr.DvrScheduleManager;
45 import com.android.tv.dvr.data.ScheduledRecording;
46 import com.android.tv.dvr.ui.DvrUiHelper;
47 import com.android.tv.dvr.ui.browse.ActionPresenterSelector;
48 import com.android.tv.dvr.ui.browse.DetailsContent;
49 import com.android.tv.dvr.ui.browse.DetailsContentPresenter;
50 import com.android.tv.dvr.ui.browse.DetailsViewBackgroundHelper;
51 import com.android.tv.util.images.ImageLoader;
52 
53 /** A fragment shows the details of a Program */
54 public class ProgramDetailsFragment extends DetailsFragment
55         implements DvrDataManager.ScheduledRecordingListener,
56                 DvrScheduleManager.OnConflictStateChangeListener {
57     private static final int LOAD_LOGO_IMAGE = 1;
58     private static final int LOAD_BACKGROUND_IMAGE = 2;
59 
60     private static final int ACTION_VIEW_SCHEDULE = 1;
61     private static final int ACTION_CANCEL = 2;
62     private static final int ACTION_SCHEDULE_RECORDING = 3;
63 
64     protected DetailsViewBackgroundHelper mBackgroundHelper;
65     private ArrayObjectAdapter mRowsAdapter;
66     private DetailsOverviewRow mDetailsOverview;
67     private Program mProgram;
68     private String mInputId;
69     private ScheduledRecording mScheduledRecording;
70     private DvrManager mDvrManager;
71     private DvrDataManager mDvrDataManager;
72     private DvrScheduleManager mDvrScheduleManager;
73 
74     @Override
onCreate(Bundle savedInstanceState)75     public void onCreate(Bundle savedInstanceState) {
76         super.onCreate(savedInstanceState);
77         if (!onLoadDetails(getArguments())) {
78             getActivity().finish();
79         }
80     }
81 
82     @Override
onDestroy()83     public void onDestroy() {
84         mDvrDataManager.removeScheduledRecordingListener(this);
85         mDvrScheduleManager.removeOnConflictStateChangeListener(this);
86         super.onDestroy();
87     }
88 
89     @Override
onStart()90     public void onStart() {
91         super.onStart();
92         VerticalGridView container =
93                 (VerticalGridView) getActivity().findViewById(R.id.container_list);
94         // Need to manually modify offset. Please refer DetailsFragment.setVerticalGridViewLayout.
95         container.setItemAlignmentOffset(0);
96         container.setWindowAlignmentOffset(
97                 getResources().getDimensionPixelSize(R.dimen.lb_details_rows_align_top));
98     }
99 
setupAdapter()100     private void setupAdapter() {
101         DetailsOverviewRowPresenter rowPresenter =
102                 new DetailsOverviewRowPresenter(new DetailsContentPresenter(getActivity()));
103         rowPresenter.setBackgroundColor(
104                 getResources().getColor(R.color.common_tv_background, null));
105         rowPresenter.setSharedElementEnterTransition(
106                 getActivity(), DetailsActivity.SHARED_ELEMENT_NAME);
107         rowPresenter.setOnActionClickedListener(onCreateOnActionClickedListener());
108         mRowsAdapter = new ArrayObjectAdapter(onCreatePresenterSelector(rowPresenter));
109         setAdapter(mRowsAdapter);
110     }
111 
112     /** Sets details overview. */
setDetailsOverviewRow(DetailsContent detailsContent)113     protected void setDetailsOverviewRow(DetailsContent detailsContent) {
114         mDetailsOverview = new DetailsOverviewRow(detailsContent);
115         mDetailsOverview.setActionsAdapter(onCreateActionsAdapter());
116         mRowsAdapter.add(mDetailsOverview);
117         onLoadLogoAndBackgroundImages(detailsContent);
118     }
119 
120     /** Creates and returns presenter selector will be used by rows adaptor. */
onCreatePresenterSelector( DetailsOverviewRowPresenter rowPresenter)121     protected PresenterSelector onCreatePresenterSelector(
122             DetailsOverviewRowPresenter rowPresenter) {
123         ClassPresenterSelector presenterSelector = new ClassPresenterSelector();
124         presenterSelector.addClassPresenter(DetailsOverviewRow.class, rowPresenter);
125         return presenterSelector;
126     }
127 
128     /** Updates actions of details overview. */
updateActions()129     protected void updateActions() {
130         mDetailsOverview.setActionsAdapter(onCreateActionsAdapter());
131     }
132 
133     /**
134      * Loads program details according to the arguments the fragment got.
135      *
136      * @return false if cannot find valid programs, else return true. If the return value is false,
137      *     the detail activity and fragment will be ended.
138      */
onLoadDetails(Bundle args)139     private boolean onLoadDetails(Bundle args) {
140         Program program = args.getParcelable(DetailsActivity.PROGRAM);
141         long channelId = args.getLong(DetailsActivity.CHANNEL_ID);
142         String inputId = args.getString(DetailsActivity.INPUT_ID);
143         if (program != null && channelId != Channel.INVALID_ID && !TextUtils.isEmpty(inputId)) {
144             mProgram = program;
145             mInputId = inputId;
146             TvSingletons singletons = TvSingletons.getSingletons(getContext());
147             mDvrDataManager = singletons.getDvrDataManager();
148             mDvrManager = singletons.getDvrManager();
149             mDvrScheduleManager = singletons.getDvrScheduleManager();
150             mScheduledRecording =
151                     mDvrDataManager.getScheduledRecordingForProgramId(program.getId());
152             mBackgroundHelper = new DetailsViewBackgroundHelper(getActivity());
153             setupAdapter();
154             setDetailsOverviewRow(DetailsContent.createFromProgram(getContext(), mProgram));
155             mDvrDataManager.addScheduledRecordingListener(this);
156             mDvrScheduleManager.addOnConflictStateChangeListener(this);
157             return true;
158         }
159         return false;
160     }
161 
getScheduleIconId()162     private int getScheduleIconId() {
163         if (mDvrManager.isConflicting(mScheduledRecording)) {
164             return R.drawable.ic_warning_white_32dp;
165         } else {
166             return R.drawable.ic_schedule_32dp;
167         }
168     }
169 
170     /** Creates actions users can interact with and their adaptor for this fragment. */
onCreateActionsAdapter()171     private SparseArrayObjectAdapter onCreateActionsAdapter() {
172         SparseArrayObjectAdapter adapter =
173                 new SparseArrayObjectAdapter(new ActionPresenterSelector());
174         Resources res = getResources();
175         if (mScheduledRecording != null) {
176             adapter.set(
177                     ACTION_VIEW_SCHEDULE,
178                     new Action(
179                             ACTION_VIEW_SCHEDULE,
180                             res.getString(R.string.dvr_detail_view_schedule),
181                             null,
182                             res.getDrawable(getScheduleIconId())));
183             adapter.set(
184                     ACTION_CANCEL,
185                     new Action(
186                             ACTION_CANCEL,
187                             res.getString(R.string.dvr_detail_cancel_recording),
188                             null,
189                             res.getDrawable(R.drawable.ic_dvr_cancel_32dp)));
190         } else if (CommonFeatures.DVR.isEnabled(getActivity())
191                 && mDvrManager.isProgramRecordable(mProgram)) {
192             adapter.set(
193                     ACTION_SCHEDULE_RECORDING,
194                     new Action(
195                             ACTION_SCHEDULE_RECORDING,
196                             res.getString(R.string.dvr_detail_schedule_recording),
197                             null,
198                             res.getDrawable(R.drawable.ic_schedule_32dp)));
199         }
200         return adapter;
201     }
202 
203     /**
204      * Creates actions listeners to implement the behavior of the fragment after users click some
205      * action buttons.
206      */
onCreateOnActionClickedListener()207     private OnActionClickedListener onCreateOnActionClickedListener() {
208         return new OnActionClickedListener() {
209             @Override
210             public void onActionClicked(Action action) {
211                 long actionId = action.getId();
212                 if (actionId == ACTION_VIEW_SCHEDULE) {
213                     DvrUiHelper.startSchedulesActivity(getContext(), mScheduledRecording);
214                 } else if (actionId == ACTION_CANCEL) {
215                     mDvrManager.removeScheduledRecording(mScheduledRecording);
216                 } else if (actionId == ACTION_SCHEDULE_RECORDING) {
217                     DvrUiHelper.checkStorageStatusAndShowErrorMessage(
218                             getActivity(),
219                             mInputId,
220                             () ->
221                                     DvrUiHelper.requestRecordingFutureProgram(
222                                             getActivity(), mProgram, false));
223                 }
224             }
225         };
226     }
227 
228     /** Loads logo and background images for detail fragments. */
229     protected void onLoadLogoAndBackgroundImages(DetailsContent detailsContent) {
230         Drawable logoDrawable = null;
231         Drawable backgroundDrawable = null;
232         if (TextUtils.isEmpty(detailsContent.getLogoImageUri())) {
233             logoDrawable =
234                     getContext().getResources().getDrawable(R.drawable.dvr_default_poster, null);
235             mDetailsOverview.setImageDrawable(logoDrawable);
236         }
237         if (TextUtils.isEmpty(detailsContent.getBackgroundImageUri())) {
238             backgroundDrawable =
239                     getContext().getResources().getDrawable(R.drawable.dvr_default_poster, null);
240             mBackgroundHelper.setBackground(backgroundDrawable);
241         }
242         if (logoDrawable != null && backgroundDrawable != null) {
243             return;
244         }
245         if (logoDrawable == null
246                 && backgroundDrawable == null
247                 && detailsContent
248                         .getLogoImageUri()
249                         .equals(detailsContent.getBackgroundImageUri())) {
250             ImageLoader.loadBitmap(
251                     getContext(),
252                     detailsContent.getLogoImageUri(),
253                     new MyImageLoaderCallback(
254                             this, LOAD_LOGO_IMAGE | LOAD_BACKGROUND_IMAGE, getContext()));
255             return;
256         }
257         if (logoDrawable == null) {
258             int imageWidth = getResources().getDimensionPixelSize(R.dimen.dvr_details_poster_width);
259             int imageHeight =
260                     getResources().getDimensionPixelSize(R.dimen.dvr_details_poster_height);
261             ImageLoader.loadBitmap(
262                     getContext(),
263                     detailsContent.getLogoImageUri(),
264                     imageWidth,
265                     imageHeight,
266                     new MyImageLoaderCallback(this, LOAD_LOGO_IMAGE, getContext()));
267         }
268         if (backgroundDrawable == null) {
269             ImageLoader.loadBitmap(
270                     getContext(),
271                     detailsContent.getBackgroundImageUri(),
272                     new MyImageLoaderCallback(this, LOAD_BACKGROUND_IMAGE, getContext()));
273         }
274     }
275 
276     @Override
277     public void onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) {
278         for (ScheduledRecording recording : scheduledRecordings) {
279             if (recording.getProgramId() == mProgram.getId()) {
280                 mScheduledRecording = recording;
281                 updateActions();
282                 return;
283             }
284         }
285     }
286 
287     @Override
288     public void onScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings) {
289         if (mScheduledRecording == null) {
290             return;
291         }
292         for (ScheduledRecording recording : scheduledRecordings) {
293             if (recording.getId() == mScheduledRecording.getId()) {
294                 mScheduledRecording = null;
295                 updateActions();
296                 return;
297             }
298         }
299     }
300 
301     @Override
302     public void onScheduledRecordingStatusChanged(ScheduledRecording... scheduledRecordings) {
303         if (mScheduledRecording == null) {
304             return;
305         }
306         for (ScheduledRecording recording : scheduledRecordings) {
307             if (recording.getId() == mScheduledRecording.getId()) {
308                 mScheduledRecording = recording;
309                 updateActions();
310                 return;
311             }
312         }
313     }
314 
315     @Override
316     public void onConflictStateChange(boolean conflict, ScheduledRecording... scheduledRecordings) {
317         onScheduledRecordingStatusChanged(scheduledRecordings);
318     }
319 
320     private static class MyImageLoaderCallback
321             extends ImageLoader.ImageLoaderCallback<ProgramDetailsFragment> {
322         private final Context mContext;
323         private final int mLoadType;
324 
325         public MyImageLoaderCallback(
326                 ProgramDetailsFragment fragment, int loadType, Context context) {
327             super(fragment);
328             mLoadType = loadType;
329             mContext = context;
330         }
331 
332         @Override
333         public void onBitmapLoaded(ProgramDetailsFragment fragment, @Nullable Bitmap bitmap) {
334             Drawable drawable;
335             int loadType = mLoadType;
336             if (bitmap == null) {
337                 Resources res = mContext.getResources();
338                 drawable = res.getDrawable(R.drawable.dvr_default_poster, null);
339                 if ((loadType & LOAD_BACKGROUND_IMAGE) != 0 && !fragment.isDetached()) {
340                     loadType &= ~LOAD_BACKGROUND_IMAGE;
341                     fragment.mBackgroundHelper.setBackgroundColor(
342                             res.getColor(R.color.dvr_detail_default_background));
343                     fragment.mBackgroundHelper.setScrim(
344                             res.getColor(R.color.dvr_detail_default_background_scrim));
345                 }
346             } else {
347                 drawable = new BitmapDrawable(mContext.getResources(), bitmap);
348             }
349             if (!fragment.isDetached()) {
350                 if ((loadType & LOAD_LOGO_IMAGE) != 0) {
351                     fragment.mDetailsOverview.setImageDrawable(drawable);
352                 }
353                 if ((loadType & LOAD_BACKGROUND_IMAGE) != 0) {
354                     fragment.mBackgroundHelper.setBackground(drawable);
355                 }
356             }
357         }
358     }
359 }
360