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