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 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
     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
     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 
     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. */
     99     protected ArrayObjectAdapter getRowsAdapter() {
    100         return mRowsAdapter;
    101     }
    102 
    103     /** Sets details overview. */
    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. */
    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      */
    125     protected void onCreateInternal() {}
    126 
    127     /** Updates actions of details overview. */
    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      */
    138     abstract boolean onLoadRecordingDetails(Bundle args);
    139 
    140     /** Creates actions users can interact with and their adaptor for this fragment. */
    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      */
    147     abstract OnActionClickedListener onCreateOnActionClickedListener();
    148 
    149     /** Loads logo and background images for detail fragments. */
    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 
    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 
    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 
    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 
    279         public MyImageLoaderCallback(DvrDetailsFragment fragment, int loadType, Context context) {
    280             super(fragment);
    281             mLoadType = loadType;
    282             mContext = context;
    283         }
    284 
    285         @Override
    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