| /* |
| * Copyright (C) 2015 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; |
| |
| import android.annotation.TargetApi; |
| import android.content.Context; |
| import android.os.Build; |
| import android.support.annotation.MainThread; |
| import android.support.annotation.NonNull; |
| import android.util.ArraySet; |
| import android.util.Log; |
| import com.android.tv.common.SoftPreconditions; |
| import com.android.tv.common.feature.CommonFeatures; |
| import com.android.tv.common.util.Clock; |
| import com.android.tv.dvr.data.RecordedProgram; |
| import com.android.tv.dvr.data.ScheduledRecording; |
| import com.android.tv.dvr.data.ScheduledRecording.RecordingState; |
| import com.android.tv.dvr.data.SeriesRecording; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.CopyOnWriteArraySet; |
| |
| /** Base implementation of @{link DataManagerInternal}. */ |
| @MainThread |
| @TargetApi(Build.VERSION_CODES.N) |
| public abstract class BaseDvrDataManager implements WritableDvrDataManager { |
| private static final String TAG = "BaseDvrDataManager"; |
| private static final boolean DEBUG = false; |
| protected final Clock mClock; |
| |
| private final Set<OnDvrScheduleLoadFinishedListener> mOnDvrScheduleLoadFinishedListeners = |
| new CopyOnWriteArraySet<>(); |
| private final Set<OnRecordedProgramLoadFinishedListener> |
| mOnRecordedProgramLoadFinishedListeners = new CopyOnWriteArraySet<>(); |
| private final Set<ScheduledRecordingListener> mScheduledRecordingListeners = new ArraySet<>(); |
| private final Set<SeriesRecordingListener> mSeriesRecordingListeners = new ArraySet<>(); |
| private final Set<RecordedProgramListener> mRecordedProgramListeners = new ArraySet<>(); |
| private final HashMap<Long, ScheduledRecording> mDeletedScheduleMap = new HashMap<>(); |
| |
| public BaseDvrDataManager(Context context, Clock clock) { |
| SoftPreconditions.checkFeatureEnabled(context, CommonFeatures.DVR, TAG); |
| mClock = clock; |
| } |
| |
| @Override |
| public void addDvrScheduleLoadFinishedListener(OnDvrScheduleLoadFinishedListener listener) { |
| mOnDvrScheduleLoadFinishedListeners.add(listener); |
| } |
| |
| @Override |
| public void removeDvrScheduleLoadFinishedListener(OnDvrScheduleLoadFinishedListener listener) { |
| mOnDvrScheduleLoadFinishedListeners.remove(listener); |
| } |
| |
| @Override |
| public void addRecordedProgramLoadFinishedListener( |
| OnRecordedProgramLoadFinishedListener listener) { |
| mOnRecordedProgramLoadFinishedListeners.add(listener); |
| } |
| |
| @Override |
| public void removeRecordedProgramLoadFinishedListener( |
| OnRecordedProgramLoadFinishedListener listener) { |
| mOnRecordedProgramLoadFinishedListeners.remove(listener); |
| } |
| |
| @Override |
| public final void addScheduledRecordingListener(ScheduledRecordingListener listener) { |
| mScheduledRecordingListeners.add(listener); |
| } |
| |
| @Override |
| public final void removeScheduledRecordingListener(ScheduledRecordingListener listener) { |
| mScheduledRecordingListeners.remove(listener); |
| } |
| |
| @Override |
| public final void addSeriesRecordingListener(SeriesRecordingListener listener) { |
| mSeriesRecordingListeners.add(listener); |
| } |
| |
| @Override |
| public final void removeSeriesRecordingListener(SeriesRecordingListener listener) { |
| mSeriesRecordingListeners.remove(listener); |
| } |
| |
| @Override |
| public final void addRecordedProgramListener(RecordedProgramListener listener) { |
| mRecordedProgramListeners.add(listener); |
| } |
| |
| @Override |
| public final void removeRecordedProgramListener(RecordedProgramListener listener) { |
| mRecordedProgramListeners.remove(listener); |
| } |
| |
| /** |
| * Calls {@link OnDvrScheduleLoadFinishedListener#onDvrScheduleLoadFinished} for each listener. |
| */ |
| protected final void notifyDvrScheduleLoadFinished() { |
| for (OnDvrScheduleLoadFinishedListener l : mOnDvrScheduleLoadFinishedListeners) { |
| if (DEBUG) Log.d(TAG, "notify DVR schedule load finished"); |
| l.onDvrScheduleLoadFinished(); |
| } |
| } |
| |
| /** |
| * Calls {@link OnRecordedProgramLoadFinishedListener#onRecordedProgramLoadFinished()} for each |
| * listener. |
| */ |
| protected final void notifyRecordedProgramLoadFinished() { |
| for (OnRecordedProgramLoadFinishedListener l : mOnRecordedProgramLoadFinishedListeners) { |
| if (DEBUG) Log.d(TAG, "notify recorded programs load finished"); |
| l.onRecordedProgramLoadFinished(); |
| } |
| } |
| |
| /** Calls {@link RecordedProgramListener#onRecordedProgramsAdded} for each listener. */ |
| protected final void notifyRecordedProgramsAdded(RecordedProgram... recordedPrograms) { |
| for (RecordedProgramListener l : mRecordedProgramListeners) { |
| if (DEBUG) Log.d(TAG, "notify " + l + " added " + Arrays.asList(recordedPrograms)); |
| l.onRecordedProgramsAdded(recordedPrograms); |
| } |
| } |
| |
| /** Calls {@link RecordedProgramListener#onRecordedProgramsChanged} for each listener. */ |
| protected final void notifyRecordedProgramsChanged(RecordedProgram... recordedPrograms) { |
| for (RecordedProgramListener l : mRecordedProgramListeners) { |
| if (DEBUG) Log.d(TAG, "notify " + l + " changed " + Arrays.asList(recordedPrograms)); |
| l.onRecordedProgramsChanged(recordedPrograms); |
| } |
| } |
| |
| /** Calls {@link RecordedProgramListener#onRecordedProgramsRemoved} for each listener. */ |
| protected final void notifyRecordedProgramsRemoved(RecordedProgram... recordedPrograms) { |
| for (RecordedProgramListener l : mRecordedProgramListeners) { |
| if (DEBUG) Log.d(TAG, "notify " + l + " removed " + Arrays.asList(recordedPrograms)); |
| l.onRecordedProgramsRemoved(recordedPrograms); |
| } |
| } |
| |
| /** Calls {@link SeriesRecordingListener#onSeriesRecordingAdded} for each listener. */ |
| protected final void notifySeriesRecordingAdded(SeriesRecording... seriesRecordings) { |
| for (SeriesRecordingListener l : mSeriesRecordingListeners) { |
| if (DEBUG) Log.d(TAG, "notify " + l + " added " + Arrays.asList(seriesRecordings)); |
| l.onSeriesRecordingAdded(seriesRecordings); |
| } |
| } |
| |
| /** Calls {@link SeriesRecordingListener#onSeriesRecordingRemoved} for each listener. */ |
| protected final void notifySeriesRecordingRemoved(SeriesRecording... seriesRecordings) { |
| for (SeriesRecordingListener l : mSeriesRecordingListeners) { |
| if (DEBUG) Log.d(TAG, "notify " + l + " removed " + Arrays.asList(seriesRecordings)); |
| l.onSeriesRecordingRemoved(seriesRecordings); |
| } |
| } |
| |
| /** Calls {@link SeriesRecordingListener#onSeriesRecordingChanged} for each listener. */ |
| protected final void notifySeriesRecordingChanged(SeriesRecording... seriesRecordings) { |
| for (SeriesRecordingListener l : mSeriesRecordingListeners) { |
| if (DEBUG) Log.d(TAG, "notify " + l + " changed " + Arrays.asList(seriesRecordings)); |
| l.onSeriesRecordingChanged(seriesRecordings); |
| } |
| } |
| |
| /** Calls {@link ScheduledRecordingListener#onScheduledRecordingAdded} for each listener. */ |
| protected final void notifyScheduledRecordingAdded(ScheduledRecording... scheduledRecording) { |
| for (ScheduledRecordingListener l : mScheduledRecordingListeners) { |
| if (DEBUG) Log.d(TAG, "notify " + l + " added " + Arrays.asList(scheduledRecording)); |
| l.onScheduledRecordingAdded(scheduledRecording); |
| } |
| } |
| |
| /** Calls {@link ScheduledRecordingListener#onScheduledRecordingRemoved} for each listener. */ |
| protected final void notifyScheduledRecordingRemoved(ScheduledRecording... scheduledRecording) { |
| for (ScheduledRecordingListener l : mScheduledRecordingListeners) { |
| if (DEBUG) Log.d(TAG, "notify " + l + " removed " + Arrays.asList(scheduledRecording)); |
| l.onScheduledRecordingRemoved(scheduledRecording); |
| } |
| } |
| |
| /** |
| * Calls {@link ScheduledRecordingListener#onScheduledRecordingStatusChanged} for each listener. |
| */ |
| protected final void notifyScheduledRecordingStatusChanged( |
| ScheduledRecording... scheduledRecording) { |
| for (ScheduledRecordingListener l : mScheduledRecordingListeners) { |
| if (DEBUG) Log.d(TAG, "notify " + l + " changed " + Arrays.asList(scheduledRecording)); |
| l.onScheduledRecordingStatusChanged(scheduledRecording); |
| } |
| } |
| |
| /** |
| * Returns a new list with only {@link ScheduledRecording} with a {@link |
| * ScheduledRecording#getEndTimeMs() endTime} after now. |
| */ |
| private List<ScheduledRecording> filterEndTimeIsPast(List<ScheduledRecording> originals) { |
| List<ScheduledRecording> results = new ArrayList<>(originals.size()); |
| for (ScheduledRecording r : originals) { |
| if (r.getEndTimeMs() > mClock.currentTimeMillis()) { |
| results.add(r); |
| } |
| } |
| return results; |
| } |
| |
| @Override |
| public List<ScheduledRecording> getAvailableScheduledRecordings() { |
| return filterEndTimeIsPast( |
| getRecordingsWithState( |
| ScheduledRecording.STATE_RECORDING_IN_PROGRESS, |
| ScheduledRecording.STATE_RECORDING_NOT_STARTED)); |
| } |
| |
| @Override |
| public List<ScheduledRecording> getStartedRecordings() { |
| return filterEndTimeIsPast( |
| getRecordingsWithState(ScheduledRecording.STATE_RECORDING_IN_PROGRESS)); |
| } |
| |
| @Override |
| public List<ScheduledRecording> getNonStartedScheduledRecordings() { |
| return filterEndTimeIsPast( |
| getRecordingsWithState(ScheduledRecording.STATE_RECORDING_NOT_STARTED)); |
| } |
| |
| @Override |
| public List<ScheduledRecording> getFailedScheduledRecordings() { |
| return getRecordingsWithState(ScheduledRecording.STATE_RECORDING_FAILED); |
| } |
| |
| @Override |
| public void changeState(ScheduledRecording scheduledRecording, @RecordingState int newState) { |
| if (scheduledRecording.getState() != newState) { |
| updateScheduledRecording( |
| ScheduledRecording.buildFrom(scheduledRecording).setState(newState).build()); |
| } |
| } |
| |
| @Override |
| public void changeState( |
| ScheduledRecording scheduledRecording, @RecordingState int newState, int reason) { |
| if (scheduledRecording.getState() != newState) { |
| ScheduledRecording.Builder builder = |
| ScheduledRecording.buildFrom(scheduledRecording).setState(newState); |
| if (newState == ScheduledRecording.STATE_RECORDING_FAILED) { |
| builder.setFailedReason(reason); |
| } |
| updateScheduledRecording(builder.build()); |
| } |
| } |
| |
| @Override |
| public Collection<ScheduledRecording> getDeletedSchedules() { |
| return mDeletedScheduleMap.values(); |
| } |
| |
| @NonNull |
| @Override |
| public Collection<Long> getDisallowedProgramIds() { |
| return mDeletedScheduleMap.keySet(); |
| } |
| |
| /** |
| * Returns the map which contains the deleted schedules which are mapped from the program ID. |
| */ |
| protected Map<Long, ScheduledRecording> getDeletedScheduleMap() { |
| return mDeletedScheduleMap; |
| } |
| |
| /** Returns the schedules whose state is contained by states. */ |
| protected abstract List<ScheduledRecording> getRecordingsWithState(int... states); |
| |
| @Override |
| public List<RecordedProgram> getRecordedPrograms(long seriesRecordingId) { |
| SeriesRecording seriesRecording = getSeriesRecording(seriesRecordingId); |
| if (seriesRecording == null) { |
| return Collections.emptyList(); |
| } |
| List<RecordedProgram> result = new ArrayList<>(); |
| for (RecordedProgram r : getRecordedPrograms()) { |
| if (seriesRecording.getSeriesId().equals(r.getSeriesId())) { |
| result.add(r); |
| } |
| } |
| return result; |
| } |
| |
| @Override |
| public void checkAndRemoveEmptySeriesRecording(long... seriesRecordingIds) { |
| List<SeriesRecording> toRemove = new ArrayList<>(); |
| for (long rId : seriesRecordingIds) { |
| SeriesRecording seriesRecording = getSeriesRecording(rId); |
| if (seriesRecording != null && isEmptySeriesRecording(seriesRecording)) { |
| toRemove.add(seriesRecording); |
| } |
| } |
| removeSeriesRecording(SeriesRecording.toArray(toRemove)); |
| } |
| |
| /** |
| * Returns {@code true}, if the series recording is empty and can be removed. If a series |
| * recording is in NORMAL state or has recordings or schedules, it is not empty and cannot be |
| * removed. |
| */ |
| protected final boolean isEmptySeriesRecording(@NonNull SeriesRecording seriesRecording) { |
| if (!seriesRecording.isStopped()) { |
| return false; |
| } |
| long seriesRecordingId = seriesRecording.getId(); |
| for (ScheduledRecording r : getAvailableScheduledRecordings()) { |
| if (r.getSeriesRecordingId() == seriesRecordingId) { |
| return false; |
| } |
| } |
| String seriesId = seriesRecording.getSeriesId(); |
| for (RecordedProgram r : getRecordedPrograms()) { |
| if (seriesId.equals(r.getSeriesId())) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| @Override |
| public void forgetStorage(String inputId) {} |
| } |