diff options
Diffstat (limited to 'src/com/android/tv/dvr/ui/list/DvrHistoryRowAdapter.java')
-rw-r--r-- | src/com/android/tv/dvr/ui/list/DvrHistoryRowAdapter.java | 353 |
1 files changed, 353 insertions, 0 deletions
diff --git a/src/com/android/tv/dvr/ui/list/DvrHistoryRowAdapter.java b/src/com/android/tv/dvr/ui/list/DvrHistoryRowAdapter.java new file mode 100644 index 00000000..156d1a7e --- /dev/null +++ b/src/com/android/tv/dvr/ui/list/DvrHistoryRowAdapter.java @@ -0,0 +1,353 @@ +/* + * 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.dvr.ui.list; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build.VERSION_CODES; +import android.support.annotation.Nullable; +import android.support.v17.leanback.widget.ArrayObjectAdapter; +import android.support.v17.leanback.widget.ClassPresenterSelector; +import android.text.format.DateUtils; +import android.util.Log; +import com.android.tv.R; +import com.android.tv.TvSingletons; +import com.android.tv.common.SoftPreconditions; +import com.android.tv.common.util.Clock; +import com.android.tv.dvr.DvrDataManager; +import com.android.tv.dvr.data.RecordedProgram; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.recorder.ScheduledProgramReaper; +import com.android.tv.dvr.ui.list.SchedulesHeaderRow.DateHeaderRow; +import com.android.tv.util.Utils; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** An adapter for DVR history. */ +@TargetApi(VERSION_CODES.N) +@SuppressWarnings("AndroidApiChecker") // TODO(b/32513850) remove when error prone is updated +class DvrHistoryRowAdapter extends ArrayObjectAdapter { + private static final String TAG = "DvrHistoryRowAdapter"; + private static final boolean DEBUG = false; + + private static final long ONE_DAY_MS = TimeUnit.DAYS.toMillis(1); + private static final int MAX_HISTORY_DAYS = ScheduledProgramReaper.DAYS; + + private final Context mContext; + private final Clock mClock; + private final DvrDataManager mDvrDataManager; + private final List<String> mTitles = new ArrayList<>(); + private final Map<Long, ScheduledRecording> mRecordedProgramScheduleMap = new HashMap<>(); + + public DvrHistoryRowAdapter( + Context context, ClassPresenterSelector classPresenterSelector, Clock clock) { + super(classPresenterSelector); + mContext = context; + mClock = clock; + mDvrDataManager = TvSingletons.getSingletons(mContext).getDvrDataManager(); + mTitles.add(mContext.getString(R.string.dvr_date_today)); + mTitles.add(mContext.getString(R.string.dvr_date_yesterday)); + } + + /** Returns context. */ + protected Context getContext() { + return mContext; + } + + /** Starts row adapter. */ + public void start() { + clear(); + List<ScheduledRecording> recordingList = mDvrDataManager.getFailedScheduledRecordings(); + List<RecordedProgram> recordedProgramList = mDvrDataManager.getRecordedPrograms(); + + recordingList.addAll( + recordedProgramsToScheduledRecordings(recordedProgramList, MAX_HISTORY_DAYS)); + recordingList + .sort(ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR.reversed()); + long deadLine = Utils.getFirstMillisecondOfDay(mClock.currentTimeMillis()); + for (int i = 0; i < recordingList.size(); ) { + ArrayList<ScheduledRecording> section = new ArrayList<>(); + while (i < recordingList.size() && recordingList.get(i).getStartTimeMs() >= deadLine) { + section.add(recordingList.get(i++)); + } + if (!section.isEmpty()) { + SchedulesHeaderRow headerRow = + new DateHeaderRow( + calculateHeaderDate(deadLine), + mContext.getResources() + .getQuantityString( + R.plurals.dvr_schedules_section_subtitle, + section.size(), + section.size()), + section.size(), + deadLine); + add(headerRow); + for (ScheduledRecording recording : section) { + add(new ScheduleRow(recording, headerRow)); + } + } + deadLine -= ONE_DAY_MS; + } + } + + private String calculateHeaderDate(long timeMs) { + int titleIndex = + (int) + ((Utils.getFirstMillisecondOfDay(mClock.currentTimeMillis()) - timeMs) + / ONE_DAY_MS); + String headerDate; + if (titleIndex < mTitles.size()) { + headerDate = mTitles.get(titleIndex); + } else { + headerDate = + DateUtils.formatDateTime( + getContext(), + timeMs, + DateUtils.FORMAT_SHOW_WEEKDAY + | DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_ABBREV_MONTH); + } + return headerDate; + } + + private List<ScheduledRecording> recordedProgramsToScheduledRecordings( + List<RecordedProgram> programs, int maxDays) { + List<ScheduledRecording> result = new ArrayList<>(); + for (RecordedProgram recordedProgram : programs) { + ScheduledRecording scheduledRecording = + recordedProgramsToScheduledRecordings(recordedProgram, maxDays); + if (scheduledRecording != null) { + result.add(scheduledRecording); + } + } + return result; + } + + @Nullable + private ScheduledRecording recordedProgramsToScheduledRecordings( + RecordedProgram program, int maxDays) { + long firstMillisecondToday = Utils.getFirstMillisecondOfDay(mClock.currentTimeMillis()); + if (maxDays + < Utils.computeDateDifference( + program.getStartTimeUtcMillis(), + firstMillisecondToday)) { + return null; + } + ScheduledRecording scheduledRecording = ScheduledRecording.builder(program).build(); + mRecordedProgramScheduleMap.put(program.getId(), scheduledRecording); + return scheduledRecording; + } + + public void onScheduledRecordingAdded(ScheduledRecording schedule) { + if (DEBUG) { + Log.d(TAG, "onScheduledRecordingAdded: " + schedule); + } + if (findRowByScheduledRecording(schedule) == null + && (schedule.getState() == ScheduledRecording.STATE_RECORDING_FINISHED + || schedule.getState() == ScheduledRecording.STATE_RECORDING_CLIPPED + || schedule.getState() == ScheduledRecording.STATE_RECORDING_FAILED)) { + addScheduleRow(schedule); + } + } + + public void onScheduledRecordingAdded(RecordedProgram program) { + if (DEBUG) { + Log.d(TAG, "onScheduledRecordingAdded: " + program); + } + if (mRecordedProgramScheduleMap.get(program.getId()) != null) { + return; + } + ScheduledRecording schedule = + recordedProgramsToScheduledRecordings(program, MAX_HISTORY_DAYS); + if (schedule == null) { + return; + } + addScheduleRow(schedule); + } + + public void onScheduledRecordingRemoved(ScheduledRecording schedule) { + if (DEBUG) { + Log.d(TAG, "onScheduledRecordingRemoved: " + schedule); + } + ScheduleRow row = findRowByScheduledRecording(schedule); + if (row != null) { + removeScheduleRow(row); + notifyArrayItemRangeChanged(indexOf(row), 1); + } + } + + public void onScheduledRecordingRemoved(RecordedProgram program) { + if (DEBUG) { + Log.d(TAG, "onScheduledRecordingRemoved: " + program); + } + ScheduledRecording scheduledRecording = mRecordedProgramScheduleMap.get(program.getId()); + if (scheduledRecording != null) { + mRecordedProgramScheduleMap.remove(program.getId()); + ScheduleRow row = findRowByRecordedProgram(program); + if (row != null) { + removeScheduleRow(row); + notifyArrayItemRangeChanged(indexOf(row), 1); + } + } + } + + public void onScheduledRecordingUpdated(ScheduledRecording schedule) { + if (DEBUG) { + Log.d(TAG, "onScheduledRecordingUpdated: " + schedule); + } + ScheduleRow row = findRowByScheduledRecording(schedule); + if (row != null) { + row.setSchedule(schedule); + if (schedule.getState() != ScheduledRecording.STATE_RECORDING_FAILED) { + // Only handle failed schedules. Finished schedules are handled as recorded programs + removeScheduleRow(row); + } + notifyArrayItemRangeChanged(indexOf(row), 1); + } + } + + public void onScheduledRecordingUpdated(RecordedProgram program) { + if (DEBUG) { + Log.d(TAG, "onScheduledRecordingUpdated: " + program); + } + ScheduleRow row = findRowByRecordedProgram(program); + if (row != null) { + removeScheduleRow(row); + notifyArrayItemRangeChanged(indexOf(row), 1); + ScheduledRecording schedule = mRecordedProgramScheduleMap.get(program.getId()); + if (schedule != null) { + mRecordedProgramScheduleMap.remove(program.getId()); + } + } + onScheduledRecordingAdded(program); + } + + private void addScheduleRow(ScheduledRecording recording) { + // This method must not be called from inherited class. + SoftPreconditions.checkState(getClass().equals(DvrHistoryRowAdapter.class)); + if (recording != null) { + int pre = -1; + int index = 0; + for (; index < size(); index++) { + if (get(index) instanceof ScheduleRow) { + ScheduleRow scheduleRow = (ScheduleRow) get(index); + if (ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR.reversed() + .compare(scheduleRow.getSchedule(), recording) > 0) { + break; + } + pre = index; + } + } + long deadLine = Utils.getFirstMillisecondOfDay(recording.getStartTimeMs()); + if (pre >= 0 && getHeaderRow(pre).getDeadLineMs() == deadLine) { + SchedulesHeaderRow headerRow = ((ScheduleRow) get(pre)).getHeaderRow(); + headerRow.setItemCount(headerRow.getItemCount() + 1); + ScheduleRow addedRow = new ScheduleRow(recording, headerRow); + add(++pre, addedRow); + updateHeaderDescription(headerRow); + } else if (index < size() && getHeaderRow(index).getDeadLineMs() == deadLine) { + SchedulesHeaderRow headerRow = ((ScheduleRow) get(index)).getHeaderRow(); + headerRow.setItemCount(headerRow.getItemCount() + 1); + ScheduleRow addedRow = new ScheduleRow(recording, headerRow); + add(index, addedRow); + updateHeaderDescription(headerRow); + } else { + SchedulesHeaderRow headerRow = + new DateHeaderRow( + calculateHeaderDate(deadLine), + mContext.getResources() + .getQuantityString( + R.plurals.dvr_schedules_section_subtitle, 1, 1), + 1, + deadLine); + add(++pre, headerRow); + ScheduleRow addedRow = new ScheduleRow(recording, headerRow); + add(pre, addedRow); + } + } + } + + private DateHeaderRow getHeaderRow(int index) { + return ((DateHeaderRow) ((ScheduleRow) get(index)).getHeaderRow()); + } + + /** Gets which {@link ScheduleRow} the {@link ScheduledRecording} belongs to. */ + private ScheduleRow findRowByScheduledRecording(ScheduledRecording recording) { + if (recording == null) { + return null; + } + for (int i = 0; i < size(); i++) { + Object item = get(i); + if (item instanceof ScheduleRow && ((ScheduleRow) item).getSchedule() != null) { + if (((ScheduleRow) item).getSchedule().getId() == recording.getId()) { + return (ScheduleRow) item; + } + } + } + return null; + } + + private ScheduleRow findRowByRecordedProgram(RecordedProgram program) { + if (program == null) { + return null; + } + for (int i = 0; i < size(); i++) { + Object item = get(i); + if (item instanceof ScheduleRow) { + ScheduleRow row = (ScheduleRow) item; + if (row.hasRecordedProgram() + && row.getSchedule().getRecordedProgramId() == program.getId()) { + return (ScheduleRow) item; + } + } + } + return null; + } + + private void removeScheduleRow(ScheduleRow scheduleRow) { + // This method must not be called from inherited class. + SoftPreconditions.checkState(getClass().equals(DvrHistoryRowAdapter.class)); + if (scheduleRow != null) { + scheduleRow.setSchedule(null); + SchedulesHeaderRow headerRow = scheduleRow.getHeaderRow(); + remove(scheduleRow); + // Changes the count information of header which the removed row belongs to. + if (headerRow != null) { + int currentCount = headerRow.getItemCount(); + headerRow.setItemCount(--currentCount); + if (headerRow.getItemCount() == 0) { + remove(headerRow); + } else { + replace(indexOf(headerRow), headerRow); + updateHeaderDescription(headerRow); + } + } + } + } + + private void updateHeaderDescription(SchedulesHeaderRow headerRow) { + headerRow.setDescription( + mContext.getResources() + .getQuantityString( + R.plurals.dvr_schedules_section_subtitle, + headerRow.getItemCount(), + headerRow.getItemCount())); + } +} |