blob: 49f8b8d8189737861785b739ce708ec6cf1d51e5 [file] [log] [blame]
/*
* 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<ProgramDetailsFragment> {
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);
}
}
}
}
}