From 0550a7221be0581b0bd421a9d70400ff8699a6e7 Mon Sep 17 00:00:00 2001 From: Nick Chalko Date: Tue, 9 May 2017 14:07:44 -0700 Subject: Sync to ub-tv-dev at lost+ hash 550cbec17259717c5453f6be1eb05736ba10ef1d Bug: 37849928 Test: tested on vendor branch Change-Id: I82190481d2bcef2b89e78414b6b92ed97720749d Merged-In: I4199ec04cacb4a78be58b85302a39d917658dc28 --- .../ui/browse/SeriesRecordingDetailsFragment.java | 369 +++++++++++++++++++++ 1 file changed, 369 insertions(+) create mode 100644 src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java (limited to 'src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java') diff --git a/src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java new file mode 100644 index 00000000..f7b60b50 --- /dev/null +++ b/src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java @@ -0,0 +1,369 @@ +/* + * Copyright (C) 2016 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.dvr.ui.browse; + +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.media.tv.TvInputManager; +import android.os.Bundle; +import android.support.v17.leanback.app.DetailsFragment; +import android.support.v17.leanback.widget.Action; +import android.support.v17.leanback.widget.ArrayObjectAdapter; +import android.support.v17.leanback.widget.ClassPresenterSelector; +import android.support.v17.leanback.widget.DetailsOverviewRow; +import android.support.v17.leanback.widget.DetailsOverviewRowPresenter; +import android.support.v17.leanback.widget.HeaderItem; +import android.support.v17.leanback.widget.ListRow; +import android.support.v17.leanback.widget.OnActionClickedListener; +import android.support.v17.leanback.widget.PresenterSelector; +import android.support.v17.leanback.widget.SparseArrayObjectAdapter; +import android.text.TextUtils; + +import com.android.tv.R; +import com.android.tv.TvApplication; +import com.android.tv.data.BaseProgram; +import com.android.tv.data.Channel; +import com.android.tv.dvr.DvrDataManager; +import com.android.tv.dvr.DvrWatchedPositionManager; +import com.android.tv.dvr.data.RecordedProgram; +import com.android.tv.dvr.data.SeriesRecording; +import com.android.tv.dvr.ui.DvrUiHelper; +import com.android.tv.dvr.ui.SortedArrayAdapter; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * {@link DetailsFragment} for series recording in DVR. + */ +public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implements + DvrDataManager.SeriesRecordingListener, DvrDataManager.RecordedProgramListener { + private static final int ACTION_WATCH = 1; + private static final int ACTION_SERIES_SCHEDULES = 2; + private static final int ACTION_DELETE = 3; + + private DvrWatchedPositionManager mDvrWatchedPositionManager; + private DvrDataManager mDvrDataManager; + + private SeriesRecording mSeries; + // NOTICE: mRecordedPrograms should only be used in creating details fragments. + // After fragments are created, it should be cleared to save resources. + private List mRecordedPrograms; + private RecordedProgram mRecommendRecordedProgram; + private DetailsContent mDetailsContent; + private int mSeasonRowCount; + private SparseArrayObjectAdapter mActionsAdapter; + private Action mDeleteAction; + + private boolean mPaused; + private long mInitialPlaybackPositionMs; + private String mWatchLabel; + private String mResumeLabel; + private Drawable mWatchDrawable; + private RecordedProgramPresenter mRecordedProgramPresenter; + + @Override + public void onCreate(Bundle savedInstanceState) { + mDvrDataManager = TvApplication.getSingletons(getActivity()).getDvrDataManager(); + mWatchLabel = getString(R.string.dvr_detail_watch); + mResumeLabel = getString(R.string.dvr_detail_series_resume); + mWatchDrawable = getResources().getDrawable(R.drawable.lb_ic_play, null); + mRecordedProgramPresenter = new RecordedProgramPresenter(getContext(), true, true); + super.onCreate(savedInstanceState); + } + + @Override + protected void onCreateInternal() { + mDvrWatchedPositionManager = TvApplication.getSingletons(getActivity()) + .getDvrWatchedPositionManager(); + setDetailsOverviewRow(mDetailsContent); + setupRecordedProgramsRow(); + mDvrDataManager.addSeriesRecordingListener(this); + mDvrDataManager.addRecordedProgramListener(this); + mRecordedPrograms = null; + } + + @Override + public void onResume() { + super.onResume(); + if (mPaused) { + updateWatchAction(); + mPaused = false; + } + } + + @Override + public void onPause() { + super.onPause(); + mPaused = true; + } + + private void updateWatchAction() { + List programs = mDvrDataManager.getRecordedPrograms(mSeries.getId()); + Collections.sort(programs, RecordedProgram.EPISODE_COMPARATOR); + mRecommendRecordedProgram = getRecommendProgram(programs); + if (mRecommendRecordedProgram == null) { + mActionsAdapter.clear(ACTION_WATCH); + } else { + String episodeStatus; + if(mDvrWatchedPositionManager.getWatchedStatus(mRecommendRecordedProgram) + == DvrWatchedPositionManager.DVR_WATCHED_STATUS_WATCHING) { + episodeStatus = mResumeLabel; + mInitialPlaybackPositionMs = mDvrWatchedPositionManager + .getWatchedPosition(mRecommendRecordedProgram.getId()); + } else { + episodeStatus = mWatchLabel; + mInitialPlaybackPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME; + } + String episodeDisplayNumber = mRecommendRecordedProgram.getEpisodeDisplayNumber( + getContext()); + mActionsAdapter.set(ACTION_WATCH, new Action(ACTION_WATCH, + episodeStatus, episodeDisplayNumber, mWatchDrawable)); + } + } + + @Override + protected boolean onLoadRecordingDetails(Bundle args) { + long recordId = args.getLong(DvrDetailsActivity.RECORDING_ID); + mSeries = TvApplication.getSingletons(getActivity()).getDvrDataManager() + .getSeriesRecording(recordId); + if (mSeries == null) { + return false; + } + mRecordedPrograms = mDvrDataManager.getRecordedPrograms(mSeries.getId()); + Collections.sort(mRecordedPrograms, RecordedProgram.SEASON_REVERSED_EPISODE_COMPARATOR); + mDetailsContent = createDetailsContent(); + return true; + } + + @Override + protected PresenterSelector onCreatePresenterSelector( + DetailsOverviewRowPresenter rowPresenter) { + ClassPresenterSelector presenterSelector = new ClassPresenterSelector(); + presenterSelector.addClassPresenter(DetailsOverviewRow.class, rowPresenter); + presenterSelector.addClassPresenter(ListRow.class, new DvrListRowPresenter(getContext())); + return presenterSelector; + } + + private DetailsContent createDetailsContent() { + Channel channel = TvApplication.getSingletons(getContext()).getChannelDataManager() + .getChannel(mSeries.getChannelId()); + String description = TextUtils.isEmpty(mSeries.getLongDescription()) + ? mSeries.getDescription() : mSeries.getLongDescription(); + return new DetailsContent.Builder() + .setTitle(mSeries.getTitle()) + .setDescription(description) + .setImageUris(mSeries.getPosterUri(), mSeries.getPhotoUri(), channel) + .build(); + } + + @Override + protected SparseArrayObjectAdapter onCreateActionsAdapter() { + mActionsAdapter = new SparseArrayObjectAdapter(new ActionPresenterSelector()); + Resources res = getResources(); + updateWatchAction(); + mActionsAdapter.set(ACTION_SERIES_SCHEDULES, new Action(ACTION_SERIES_SCHEDULES, + getString(R.string.dvr_detail_view_schedule), null, + res.getDrawable(R.drawable.ic_schedule_32dp, null))); + mDeleteAction = new Action(ACTION_DELETE, + getString(R.string.dvr_detail_series_delete), null, + res.getDrawable(R.drawable.ic_delete_32dp, null)); + if (!mRecordedPrograms.isEmpty()) { + mActionsAdapter.set(ACTION_DELETE, mDeleteAction); + } + return mActionsAdapter; + } + + private void setupRecordedProgramsRow() { + for (RecordedProgram program : mRecordedPrograms) { + addProgram(program); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + mDvrDataManager.removeSeriesRecordingListener(this); + mDvrDataManager.removeRecordedProgramListener(this); + if (mSeries != null) { + mDvrDataManager.checkAndRemoveEmptySeriesRecording(mSeries.getId()); + } + mRecordedProgramPresenter.unbindAllViewHolders(); + } + + @Override + protected OnActionClickedListener onCreateOnActionClickedListener() { + return new OnActionClickedListener() { + @Override + public void onActionClicked(Action action) { + if (action.getId() == ACTION_WATCH) { + startPlayback(mRecommendRecordedProgram, mInitialPlaybackPositionMs); + } else if (action.getId() == ACTION_SERIES_SCHEDULES) { + DvrUiHelper.startSchedulesActivityForSeries(getContext(), mSeries); + } else if (action.getId() == ACTION_DELETE) { + DvrUiHelper.startSeriesDeletionActivity(getContext(), mSeries.getId()); + } + } + }; + } + + /** + * The programs are sorted by season number and episode number. + */ + private RecordedProgram getRecommendProgram(List programs) { + for (int i = programs.size() - 1 ; i >= 0 ; i--) { + RecordedProgram program = programs.get(i); + int watchedStatus = mDvrWatchedPositionManager.getWatchedStatus(program); + if (watchedStatus == DvrWatchedPositionManager.DVR_WATCHED_STATUS_NEW) { + continue; + } + if (watchedStatus == DvrWatchedPositionManager.DVR_WATCHED_STATUS_WATCHING) { + return program; + } + if (i == programs.size() - 1) { + return program; + } else { + return programs.get(i + 1); + } + } + return programs.isEmpty() ? null : programs.get(0); + } + + @Override + public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) { } + + @Override + public void onSeriesRecordingChanged(SeriesRecording... seriesRecordings) { + for (SeriesRecording series : seriesRecordings) { + if (mSeries.getId() == series.getId()) { + mSeries = series; + } + } + } + + @Override + public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) { + for (SeriesRecording series : seriesRecordings) { + if (series.getId() == mSeries.getId()) { + getActivity().finish(); + return; + } + } + } + + @Override + public void onRecordedProgramsAdded(RecordedProgram... recordedPrograms) { + for (RecordedProgram recordedProgram : recordedPrograms) { + if (TextUtils.equals(recordedProgram.getSeriesId(), mSeries.getSeriesId())) { + addProgram(recordedProgram); + if (mActionsAdapter.lookup(ACTION_DELETE) == null) { + mActionsAdapter.set(ACTION_DELETE, mDeleteAction); + } + } + } + } + + @Override + public void onRecordedProgramsChanged(RecordedProgram... recordedPrograms) { + // Do nothing + } + + @Override + public void onRecordedProgramsRemoved(RecordedProgram... recordedPrograms) { + for (RecordedProgram recordedProgram : recordedPrograms) { + if (TextUtils.equals(recordedProgram.getSeriesId(), mSeries.getSeriesId())) { + ListRow row = getSeasonRow(recordedProgram.getSeasonNumber(), false); + if (row != null) { + SeasonRowAdapter adapter = (SeasonRowAdapter) row.getAdapter(); + adapter.remove(recordedProgram); + if (adapter.isEmpty()) { + getRowsAdapter().remove(row); + if (getRowsAdapter().size() == 1) { + // No season rows left. Only DetailsOverviewRow + mActionsAdapter.clear(ACTION_DELETE); + } + } + } + if (recordedProgram.getId() == mRecommendRecordedProgram.getId()) { + updateWatchAction(); + } + } + } + } + + private void addProgram(RecordedProgram program) { + String programSeasonNumber = + TextUtils.isEmpty(program.getSeasonNumber()) ? "" : program.getSeasonNumber(); + getOrCreateSeasonRowAdapter(programSeasonNumber).add(program); + } + + private SeasonRowAdapter getOrCreateSeasonRowAdapter(String seasonNumber) { + ListRow row = getSeasonRow(seasonNumber, true); + return (SeasonRowAdapter) row.getAdapter(); + } + + private ListRow getSeasonRow(String seasonNumber, boolean createNewRow) { + seasonNumber = TextUtils.isEmpty(seasonNumber) ? "" : seasonNumber; + ArrayObjectAdapter rowsAdaptor = getRowsAdapter(); + for (int i = rowsAdaptor.size() - 1; i >= 0; i--) { + Object row = rowsAdaptor.get(i); + if (row instanceof ListRow) { + int compareResult = BaseProgram.numberCompare(seasonNumber, + ((SeasonRowAdapter) ((ListRow) row).getAdapter()).mSeasonNumber); + if (compareResult == 0) { + return (ListRow) row; + } else if (compareResult < 0) { + return createNewRow ? createNewSeasonRow(seasonNumber, i + 1) : null; + } + } + } + return createNewRow ? createNewSeasonRow(seasonNumber, rowsAdaptor.size()) : null; + } + + private ListRow createNewSeasonRow(String seasonNumber, int position) { + String seasonTitle = seasonNumber.isEmpty() ? mSeries.getTitle() + : getString(R.string.dvr_detail_series_season_title, seasonNumber); + HeaderItem header = new HeaderItem(mSeasonRowCount++, seasonTitle); + ClassPresenterSelector selector = new ClassPresenterSelector(); + selector.addClassPresenter(RecordedProgram.class, mRecordedProgramPresenter); + ListRow row = new ListRow(header, new SeasonRowAdapter(selector, + new Comparator() { + @Override + public int compare(RecordedProgram lhs, RecordedProgram rhs) { + return BaseProgram.EPISODE_COMPARATOR.compare(lhs, rhs); + } + }, seasonNumber)); + getRowsAdapter().add(position, row); + return row; + } + + private class SeasonRowAdapter extends SortedArrayAdapter { + private String mSeasonNumber; + + SeasonRowAdapter(PresenterSelector selector, Comparator comparator, + String seasonNumber) { + super(selector, comparator); + mSeasonNumber = seasonNumber; + } + + @Override + public long getId(RecordedProgram program) { + return program.getId(); + } + } +} \ No newline at end of file -- cgit v1.2.3