Home | History | Annotate | Download | only in browse
      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
     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
     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 
     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      */
    103     protected ArrayObjectAdapter getRowsAdapter() {
    104         return  mRowsAdapter;
    105     }
    106 
    107     /**
    108      * Sets details overview.
    109      */
    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      */
    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      */
    133     protected void onCreateInternal() { }
    134 
    135     /**
    136      * Updates actions of details overview.
    137      */
    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      */
    148     abstract boolean onLoadRecordingDetails(Bundle args);
    149 
    150     /**
    151      * Creates actions users can interact with and their adaptor for this fragment.
    152      */
    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      */
    159     abstract OnActionClickedListener onCreateOnActionClickedListener();
    160 
    161     /**
    162      * Loads logo and background images for detail fragments.
    163      */
    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 
    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 
    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 
    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 
    272         public MyImageLoaderCallback(DvrDetailsFragment fragment,
    273                 int loadType, Context context) {
    274             super(fragment);
    275             mLoadType = loadType;
    276             mContext = context;
    277         }
    278 
    279         @Override
    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