/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.tv.ui; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.support.annotation.Nullable; import android.text.TextUtils; import androidx.leanback.app.DetailsFragment; import androidx.leanback.widget.Action; import androidx.leanback.widget.ArrayObjectAdapter; import androidx.leanback.widget.ClassPresenterSelector; import androidx.leanback.widget.DetailsOverviewRow; import androidx.leanback.widget.DetailsOverviewRowPresenter; import androidx.leanback.widget.OnActionClickedListener; import androidx.leanback.widget.PresenterSelector; import androidx.leanback.widget.SparseArrayObjectAdapter; import androidx.leanback.widget.VerticalGridView; import com.android.tv.R; import com.android.tv.TvSingletons; import com.android.tv.common.feature.CommonFeatures; import com.android.tv.common.flags.DvrFlags; import com.android.tv.data.ProgramImpl; import com.android.tv.data.api.Channel; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.DvrScheduleManager; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.ui.DvrUiHelper; import com.android.tv.dvr.ui.browse.ActionPresenterSelector; import com.android.tv.dvr.ui.browse.DetailsContent; import com.android.tv.dvr.ui.browse.DetailsContentPresenter; import com.android.tv.dvr.ui.browse.DetailsViewBackgroundHelper; import com.android.tv.util.images.ImageLoader; import javax.inject.Inject; import dagger.android.AndroidInjection; /** A fragment shows the details of a Program */ public class ProgramDetailsFragment extends DetailsFragment implements DvrDataManager.ScheduledRecordingListener, DvrScheduleManager.OnConflictStateChangeListener { private static final int LOAD_LOGO_IMAGE = 1; private static final int LOAD_BACKGROUND_IMAGE = 2; private static final int ACTION_VIEW_SCHEDULE = 1; private static final int ACTION_CANCEL = 2; private static final int ACTION_SCHEDULE_RECORDING = 3; protected DetailsViewBackgroundHelper mBackgroundHelper; private ArrayObjectAdapter mRowsAdapter; private DetailsOverviewRow mDetailsOverview; private ProgramImpl mProgram; private String mInputId; private ScheduledRecording mScheduledRecording; private DvrManager mDvrManager; private DvrDataManager mDvrDataManager; private DvrScheduleManager mDvrScheduleManager; @Inject DvrFlags mDvrFlags; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (!onLoadDetails(getArguments())) { getActivity().finish(); } } @Override public void onAttach(Context context) { AndroidInjection.inject(this); super.onAttach(context); } @Override public void onDestroy() { mDvrDataManager.removeScheduledRecordingListener(this); mDvrScheduleManager.removeOnConflictStateChangeListener(this); super.onDestroy(); } @Override public void onStart() { super.onStart(); VerticalGridView container = (VerticalGridView) getActivity().findViewById(R.id.container_list); // Need to manually modify offset. Please refer DetailsFragment.setVerticalGridViewLayout. container.setItemAlignmentOffset(0); container.setWindowAlignmentOffset( getResources().getDimensionPixelSize(R.dimen.lb_details_rows_align_top)); } private void setupAdapter() { DetailsOverviewRowPresenter rowPresenter = new DetailsOverviewRowPresenter(new DetailsContentPresenter(getActivity())); rowPresenter.setBackgroundColor( getResources().getColor(R.color.common_tv_background, null)); rowPresenter.setSharedElementEnterTransition( getActivity(), DetailsActivity.SHARED_ELEMENT_NAME); rowPresenter.setOnActionClickedListener(onCreateOnActionClickedListener()); mRowsAdapter = new ArrayObjectAdapter(onCreatePresenterSelector(rowPresenter)); setAdapter(mRowsAdapter); } /** Sets details overview. */ protected void setDetailsOverviewRow(DetailsContent detailsContent) { mDetailsOverview = new DetailsOverviewRow(detailsContent); mDetailsOverview.setActionsAdapter(onCreateActionsAdapter()); mRowsAdapter.add(mDetailsOverview); onLoadLogoAndBackgroundImages(detailsContent); } /** Creates and returns presenter selector will be used by rows adaptor. */ protected PresenterSelector onCreatePresenterSelector( DetailsOverviewRowPresenter rowPresenter) { ClassPresenterSelector presenterSelector = new ClassPresenterSelector(); presenterSelector.addClassPresenter(DetailsOverviewRow.class, rowPresenter); return presenterSelector; } /** Updates actions of details overview. */ protected void updateActions() { mDetailsOverview.setActionsAdapter(onCreateActionsAdapter()); } /** * Loads program details according to the arguments the fragment got. * * @return false if cannot find valid programs, else return true. If the return value is false, * the detail activity and fragment will be ended. */ private boolean onLoadDetails(Bundle args) { ProgramImpl program = args.getParcelable(DetailsActivity.PROGRAM); long channelId = args.getLong(DetailsActivity.CHANNEL_ID); String inputId = args.getString(DetailsActivity.INPUT_ID); if (program != null && channelId != Channel.INVALID_ID && !TextUtils.isEmpty(inputId)) { mProgram = program; mInputId = inputId; TvSingletons singletons = TvSingletons.getSingletons(getContext()); mDvrDataManager = singletons.getDvrDataManager(); mDvrManager = singletons.getDvrManager(); mDvrScheduleManager = singletons.getDvrScheduleManager(); mScheduledRecording = mDvrDataManager.getScheduledRecordingForProgramId(program.getId()); mBackgroundHelper = new DetailsViewBackgroundHelper(getActivity()); setupAdapter(); setDetailsOverviewRow(DetailsContent.createFromProgram(getContext(), mProgram)); mDvrDataManager.addScheduledRecordingListener(this); mDvrScheduleManager.addOnConflictStateChangeListener(this); return true; } return false; } private int getScheduleIconId() { if (mDvrManager.isConflicting(mScheduledRecording)) { return R.drawable.ic_warning_white_32dp; } else { return R.drawable.ic_schedule_32dp; } } /** Creates actions users can interact with and their adaptor for this fragment. */ private SparseArrayObjectAdapter onCreateActionsAdapter() { SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter(new ActionPresenterSelector()); Resources res = getResources(); if (mScheduledRecording != null) { adapter.set( ACTION_VIEW_SCHEDULE, new Action( ACTION_VIEW_SCHEDULE, res.getString(R.string.dvr_detail_view_schedule), null, res.getDrawable(getScheduleIconId()))); adapter.set( ACTION_CANCEL, new Action( ACTION_CANCEL, res.getString(R.string.dvr_detail_cancel_recording), null, res.getDrawable(R.drawable.ic_dvr_cancel_32dp))); } else if (CommonFeatures.DVR.isEnabled(getActivity()) && mDvrManager.isProgramRecordable(mProgram)) { adapter.set( ACTION_SCHEDULE_RECORDING, new Action( ACTION_SCHEDULE_RECORDING, res.getString(R.string.dvr_detail_schedule_recording), null, res.getDrawable(R.drawable.ic_schedule_32dp))); } return adapter; } /** * Creates actions listeners to implement the behavior of the fragment after users click some * action buttons. */ private OnActionClickedListener onCreateOnActionClickedListener() { return new OnActionClickedListener() { @Override public void onActionClicked(Action action) { long actionId = action.getId(); if (actionId == ACTION_VIEW_SCHEDULE) { DvrUiHelper.startSchedulesActivity(getContext(), mScheduledRecording); } else if (actionId == ACTION_CANCEL) { mDvrManager.removeScheduledRecording(mScheduledRecording); } else if (actionId == ACTION_SCHEDULE_RECORDING) { if (!mProgram.isEpisodic() && mDvrFlags.startEarlyEndLateEnabled()) { DvrUiHelper.startRecordingSettingsActivity(getContext(), mProgram); } else { DvrUiHelper.checkStorageStatusAndShowErrorMessage( getActivity(), mInputId, () -> DvrUiHelper.requestRecordingFutureProgram( getActivity(), mProgram, false)); } } } }; } /** Loads logo and background images for detail fragments. */ protected void onLoadLogoAndBackgroundImages(DetailsContent detailsContent) { Drawable logoDrawable = null; Drawable backgroundDrawable = null; if (TextUtils.isEmpty(detailsContent.getLogoImageUri())) { logoDrawable = getContext().getResources().getDrawable(R.drawable.dvr_default_poster, null); mDetailsOverview.setImageDrawable(logoDrawable); } if (TextUtils.isEmpty(detailsContent.getBackgroundImageUri())) { backgroundDrawable = getContext().getResources().getDrawable(R.drawable.dvr_default_poster, null); mBackgroundHelper.setBackground(backgroundDrawable); } if (logoDrawable != null && backgroundDrawable != null) { return; } if (logoDrawable == null && backgroundDrawable == null && detailsContent .getLogoImageUri() .equals(detailsContent.getBackgroundImageUri())) { ImageLoader.loadBitmap( getContext(), detailsContent.getLogoImageUri(), new MyImageLoaderCallback( this, LOAD_LOGO_IMAGE | LOAD_BACKGROUND_IMAGE, getContext())); return; } if (logoDrawable == null) { int imageWidth = getResources().getDimensionPixelSize(R.dimen.dvr_details_poster_width); int imageHeight = getResources().getDimensionPixelSize(R.dimen.dvr_details_poster_height); ImageLoader.loadBitmap( getContext(), detailsContent.getLogoImageUri(), imageWidth, imageHeight, new MyImageLoaderCallback(this, LOAD_LOGO_IMAGE, getContext())); } if (backgroundDrawable == null) { ImageLoader.loadBitmap( getContext(), detailsContent.getBackgroundImageUri(), new MyImageLoaderCallback(this, LOAD_BACKGROUND_IMAGE, getContext())); } } @Override public void onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) { for (ScheduledRecording recording : scheduledRecordings) { if (recording.getProgramId() == mProgram.getId()) { mScheduledRecording = recording; updateActions(); return; } } } @Override public void onScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings) { if (mScheduledRecording == null) { return; } for (ScheduledRecording recording : scheduledRecordings) { if (recording.getId() == mScheduledRecording.getId()) { mScheduledRecording = null; updateActions(); return; } } } @Override public void onScheduledRecordingStatusChanged(ScheduledRecording... scheduledRecordings) { if (mScheduledRecording == null) { return; } for (ScheduledRecording recording : scheduledRecordings) { if (recording.getId() == mScheduledRecording.getId()) { mScheduledRecording = recording; updateActions(); return; } } } @Override public void onConflictStateChange(boolean conflict, ScheduledRecording... scheduledRecordings) { onScheduledRecordingStatusChanged(scheduledRecordings); } private static class MyImageLoaderCallback extends ImageLoader.ImageLoaderCallback { private final Context mContext; private final int mLoadType; public MyImageLoaderCallback( ProgramDetailsFragment fragment, int loadType, Context context) { super(fragment); mLoadType = loadType; mContext = context; } @Override public void onBitmapLoaded(ProgramDetailsFragment fragment, @Nullable Bitmap bitmap) { Drawable drawable; int loadType = mLoadType; if (bitmap == null) { Resources res = mContext.getResources(); drawable = res.getDrawable(R.drawable.dvr_default_poster, null); if ((loadType & LOAD_BACKGROUND_IMAGE) != 0 && !fragment.isDetached()) { loadType &= ~LOAD_BACKGROUND_IMAGE; fragment.mBackgroundHelper.setBackgroundColor( res.getColor(R.color.dvr_detail_default_background)); fragment.mBackgroundHelper.setScrim( res.getColor(R.color.dvr_detail_default_background_scrim)); } } else { drawable = new BitmapDrawable(mContext.getResources(), bitmap); } if (!fragment.isDetached()) { if ((loadType & LOAD_LOGO_IMAGE) != 0) { fragment.mDetailsOverview.setImageDrawable(drawable); } if ((loadType & LOAD_BACKGROUND_IMAGE) != 0) { fragment.mBackgroundHelper.setBackground(drawable); } } } } }