• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.dvr.ui.browse;
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.media.tv.TvContentRating;
25 import android.net.Uri;
26 import android.os.Bundle;
27 import android.support.annotation.Nullable;
28 import android.support.v17.leanback.app.DetailsFragment;
29 import android.support.v17.leanback.widget.ArrayObjectAdapter;
30 import android.support.v17.leanback.widget.ClassPresenterSelector;
31 import android.support.v17.leanback.widget.DetailsOverviewRow;
32 import android.support.v17.leanback.widget.DetailsOverviewRowPresenter;
33 import android.support.v17.leanback.widget.OnActionClickedListener;
34 import android.support.v17.leanback.widget.PresenterSelector;
35 import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
36 import android.support.v17.leanback.widget.VerticalGridView;
37 import android.text.TextUtils;
38 import android.widget.Toast;
39 
40 import com.android.tv.R;
41 import com.android.tv.TvApplication;
42 import com.android.tv.common.SoftPreconditions;
43 import com.android.tv.data.Channel;
44 import com.android.tv.data.ChannelDataManager;
45 import com.android.tv.dialog.PinDialogFragment;
46 import com.android.tv.dialog.PinDialogFragment.OnPinCheckedListener;
47 import com.android.tv.dvr.data.RecordedProgram;
48 import com.android.tv.dvr.ui.DvrUiHelper;
49 import com.android.tv.parental.ParentalControlSettings;
50 import com.android.tv.util.ImageLoader;
51 import com.android.tv.util.ToastUtils;
52 import com.android.tv.util.Utils;
53 
54 import java.io.File;
55 
56 abstract class DvrDetailsFragment extends DetailsFragment {
57     private static final int LOAD_LOGO_IMAGE = 1;
58     private static final int LOAD_BACKGROUND_IMAGE = 2;
59 
60     protected DetailsViewBackgroundHelper mBackgroundHelper;
61     private ArrayObjectAdapter mRowsAdapter;
62     private DetailsOverviewRow mDetailsOverview;
63 
64     @Override
onCreate(Bundle savedInstanceState)65     public void onCreate(Bundle savedInstanceState) {
66         super.onCreate(savedInstanceState);
67         if (!onLoadRecordingDetails(getArguments())) {
68             getActivity().finish();
69             return;
70         }
71         mBackgroundHelper = new DetailsViewBackgroundHelper(getActivity());
72         setupAdapter();
73         onCreateInternal();
74     }
75 
76     @Override
onStart()77     public void onStart() {
78         super.onStart();
79         // TODO: remove the workaround of b/30401180.
80         VerticalGridView container = (VerticalGridView) getActivity()
81                 .findViewById(R.id.container_list);
82         // Need to manually modify offset. Please refer DetailsFragment.setVerticalGridViewLayout.
83         container.setItemAlignmentOffset(0);
84         container.setWindowAlignmentOffset(
85                 getResources().getDimensionPixelSize(R.dimen.lb_details_rows_align_top));
86     }
87 
setupAdapter()88     private void setupAdapter() {
89         DetailsOverviewRowPresenter rowPresenter = new DetailsOverviewRowPresenter(
90                 new DetailsContentPresenter(getActivity()));
91         rowPresenter.setBackgroundColor(getResources().getColor(R.color.common_tv_background,
92                 null));
93         rowPresenter.setSharedElementEnterTransition(getActivity(),
94                 DvrDetailsActivity.SHARED_ELEMENT_NAME);
95         rowPresenter.setOnActionClickedListener(onCreateOnActionClickedListener());
96         mRowsAdapter = new ArrayObjectAdapter(onCreatePresenterSelector(rowPresenter));
97         setAdapter(mRowsAdapter);
98     }
99 
100     /**
101      * Returns details views' rows adapter.
102      */
getRowsAdapter()103     protected ArrayObjectAdapter getRowsAdapter() {
104         return  mRowsAdapter;
105     }
106 
107     /**
108      * Sets details overview.
109      */
setDetailsOverviewRow(DetailsContent detailsContent)110     protected void setDetailsOverviewRow(DetailsContent detailsContent) {
111         mDetailsOverview = new DetailsOverviewRow(detailsContent);
112         mDetailsOverview.setActionsAdapter(onCreateActionsAdapter());
113         mRowsAdapter.add(mDetailsOverview);
114         onLoadLogoAndBackgroundImages(detailsContent);
115     }
116 
117     /**
118      * Creates and returns presenter selector will be used by rows adaptor.
119      */
onCreatePresenterSelector( DetailsOverviewRowPresenter rowPresenter)120     protected PresenterSelector onCreatePresenterSelector(
121             DetailsOverviewRowPresenter rowPresenter) {
122         ClassPresenterSelector presenterSelector = new ClassPresenterSelector();
123         presenterSelector.addClassPresenter(DetailsOverviewRow.class, rowPresenter);
124         return presenterSelector;
125     }
126 
127     /**
128      * Does customized initialization of subclasses. Since {@link #onCreate(Bundle)} might finish
129      * activity early when it cannot fetch valid recordings, subclasses' onCreate method should not
130      * do anything after calling {@link #onCreate(Bundle)}. If there's something subclasses have to
131      * do after the super class did onCreate, it should override this method and put the codes here.
132      */
onCreateInternal()133     protected void onCreateInternal() { }
134 
135     /**
136      * Updates actions of details overview.
137      */
updateActions()138     protected void updateActions() {
139         mDetailsOverview.setActionsAdapter(onCreateActionsAdapter());
140     }
141 
142     /**
143      * Loads recording details according to the arguments the fragment got.
144      *
145      * @return false if cannot find valid recordings, else return true. If the return value
146      *         is false, the detail activity and fragment will be ended.
147      */
onLoadRecordingDetails(Bundle args)148     abstract boolean onLoadRecordingDetails(Bundle args);
149 
150     /**
151      * Creates actions users can interact with and their adaptor for this fragment.
152      */
onCreateActionsAdapter()153     abstract SparseArrayObjectAdapter onCreateActionsAdapter();
154 
155     /**
156      * Creates actions listeners to implement the behavior of the fragment after users click some
157      * action buttons.
158      */
onCreateOnActionClickedListener()159     abstract OnActionClickedListener onCreateOnActionClickedListener();
160 
161     /**
162      * Loads logo and background images for detail fragments.
163      */
onLoadLogoAndBackgroundImages(DetailsContent detailsContent)164     protected void onLoadLogoAndBackgroundImages(DetailsContent detailsContent) {
165         Drawable logoDrawable = null;
166         Drawable backgroundDrawable = null;
167         if (TextUtils.isEmpty(detailsContent.getLogoImageUri())) {
168             logoDrawable = getContext().getResources()
169                     .getDrawable(R.drawable.dvr_default_poster, null);
170             mDetailsOverview.setImageDrawable(logoDrawable);
171         }
172         if (TextUtils.isEmpty(detailsContent.getBackgroundImageUri())) {
173             backgroundDrawable = getContext().getResources()
174                     .getDrawable(R.drawable.dvr_default_poster, null);
175             mBackgroundHelper.setBackground(backgroundDrawable);
176         }
177         if (logoDrawable != null && backgroundDrawable != null) {
178             return;
179         }
180         if (logoDrawable == null && backgroundDrawable == null
181                 && detailsContent.getLogoImageUri().equals(
182                 detailsContent.getBackgroundImageUri())) {
183             ImageLoader.loadBitmap(getContext(), detailsContent.getLogoImageUri(),
184                     new MyImageLoaderCallback(this, LOAD_LOGO_IMAGE | LOAD_BACKGROUND_IMAGE,
185                             getContext()));
186             return;
187         }
188         if (logoDrawable == null) {
189             int imageWidth = getResources().getDimensionPixelSize(R.dimen.dvr_details_poster_width);
190             int imageHeight = getResources()
191                     .getDimensionPixelSize(R.dimen.dvr_details_poster_height);
192             ImageLoader.loadBitmap(getContext(), detailsContent.getLogoImageUri(),
193                     imageWidth, imageHeight,
194                     new MyImageLoaderCallback(this, LOAD_LOGO_IMAGE, getContext()));
195         }
196         if (backgroundDrawable == null) {
197             ImageLoader.loadBitmap(getContext(), detailsContent.getBackgroundImageUri(),
198                     new MyImageLoaderCallback(this, LOAD_BACKGROUND_IMAGE, getContext()));
199         }
200     }
201 
startPlayback(RecordedProgram recordedProgram, long seekTimeMs)202     protected void startPlayback(RecordedProgram recordedProgram, long seekTimeMs) {
203         if (Utils.isInBundledPackageSet(recordedProgram.getPackageName()) &&
204                 !isDataUriAccessible(recordedProgram.getDataUri())) {
205             // Since cleaning RecordedProgram from forgotten storage will take some time,
206             // ignore playback until cleaning is finished.
207             ToastUtils.show(getContext(),
208                     getContext().getResources().getString(R.string.dvr_toast_recording_deleted),
209                     Toast.LENGTH_SHORT);
210             return;
211         }
212         long programId = recordedProgram.getId();
213         ParentalControlSettings parental = TvApplication.getSingletons(getActivity())
214                 .getTvInputManagerHelper().getParentalControlSettings();
215         if (!parental.isParentalControlsEnabled()) {
216             DvrUiHelper.startPlaybackActivity(getContext(), programId, seekTimeMs, false);
217             return;
218         }
219         ChannelDataManager channelDataManager =
220                 TvApplication.getSingletons(getActivity()).getChannelDataManager();
221         Channel channel = channelDataManager.getChannel(recordedProgram.getChannelId());
222         if (channel != null && channel.isLocked()) {
223             checkPinToPlay(recordedProgram, seekTimeMs);
224             return;
225         }
226         TvContentRating[] ratings = recordedProgram.getContentRatings();
227         TvContentRating blockRatings = parental.getBlockedRating(ratings);
228         if (blockRatings != null) {
229             checkPinToPlay(recordedProgram, seekTimeMs);
230         } else {
231             DvrUiHelper.startPlaybackActivity(getContext(), programId, seekTimeMs, false);
232         }
233     }
234 
isDataUriAccessible(Uri dataUri)235     private boolean isDataUriAccessible(Uri dataUri) {
236         if (dataUri == null || dataUri.getPath() == null) {
237             return false;
238         }
239         try {
240             File recordedProgramPath = new File(dataUri.getPath());
241             if (recordedProgramPath.exists()) {
242                 return true;
243             }
244         } catch (SecurityException e) {
245         }
246         return false;
247     }
248 
checkPinToPlay(RecordedProgram recordedProgram, long seekTimeMs)249     private void checkPinToPlay(RecordedProgram recordedProgram, long seekTimeMs) {
250         SoftPreconditions.checkState(getActivity() instanceof DvrDetailsActivity);
251         if (getActivity() instanceof DvrDetailsActivity) {
252             ((DvrDetailsActivity) getActivity()).setOnPinCheckListener(new OnPinCheckedListener() {
253                 @Override
254                 public void onPinChecked(boolean checked, int type, String rating) {
255                     ((DvrDetailsActivity) getActivity()).setOnPinCheckListener(null);
256                     if (checked && type == PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM) {
257                         DvrUiHelper.startPlaybackActivity(getContext(), recordedProgram.getId(),
258                                 seekTimeMs, true);
259                     }
260                 }
261             });
262             PinDialogFragment.create(PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM)
263                     .show(getActivity().getFragmentManager(), PinDialogFragment.DIALOG_TAG);
264         }
265     }
266 
267     private static class MyImageLoaderCallback extends
268             ImageLoader.ImageLoaderCallback<DvrDetailsFragment> {
269         private final Context mContext;
270         private final int mLoadType;
271 
MyImageLoaderCallback(DvrDetailsFragment fragment, int loadType, Context context)272         public MyImageLoaderCallback(DvrDetailsFragment fragment,
273                 int loadType, Context context) {
274             super(fragment);
275             mLoadType = loadType;
276             mContext = context;
277         }
278 
279         @Override
onBitmapLoaded(DvrDetailsFragment fragment, @Nullable Bitmap bitmap)280         public void onBitmapLoaded(DvrDetailsFragment fragment,
281                 @Nullable Bitmap bitmap) {
282             Drawable drawable;
283             int loadType = mLoadType;
284             if (bitmap == null) {
285                 Resources res = mContext.getResources();
286                 drawable = res.getDrawable(R.drawable.dvr_default_poster, null);
287                 if ((loadType & LOAD_BACKGROUND_IMAGE) != 0 && !fragment.isDetached()) {
288                     loadType &= ~LOAD_BACKGROUND_IMAGE;
289                     fragment.mBackgroundHelper.setBackgroundColor(
290                             res.getColor(R.color.dvr_detail_default_background));
291                     fragment.mBackgroundHelper.setScrim(
292                             res.getColor(R.color.dvr_detail_default_background_scrim));
293                 }
294             } else {
295                 drawable = new BitmapDrawable(mContext.getResources(), bitmap);
296             }
297             if (!fragment.isDetached()) {
298                 if ((loadType & LOAD_LOGO_IMAGE) != 0) {
299                     fragment.mDetailsOverview.setImageDrawable(drawable);
300                 }
301                 if ((loadType & LOAD_BACKGROUND_IMAGE) != 0) {
302                     fragment.mBackgroundHelper.setBackground(drawable);
303                 }
304             }
305         }
306     }
307 }
308