/* * 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 mOnDvrScheduleLoadFinishedListeners = new CopyOnWriteArraySet<>(); private final Set mOnRecordedProgramLoadFinishedListeners = new CopyOnWriteArraySet<>(); private final Set mScheduledRecordingListeners = new ArraySet<>(); private final Set mSeriesRecordingListeners = new ArraySet<>(); private final Set mRecordedProgramListeners = new ArraySet<>(); private final HashMap mDeletedScheduleMap = new HashMap<>(); 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 filterEndTimeIsPast(List originals) { List results = new ArrayList<>(originals.size()); for (ScheduledRecording r : originals) { if (r.getEndTimeMs() > mClock.currentTimeMillis()) { results.add(r); } } return results; } @Override public List getAvailableScheduledRecordings() { return filterEndTimeIsPast( getRecordingsWithState( ScheduledRecording.STATE_RECORDING_IN_PROGRESS, ScheduledRecording.STATE_RECORDING_NOT_STARTED)); } @Override public List getStartedRecordings() { return filterEndTimeIsPast( getRecordingsWithState(ScheduledRecording.STATE_RECORDING_IN_PROGRESS)); } @Override public List getNonStartedScheduledRecordings() { return filterEndTimeIsPast( getRecordingsWithState(ScheduledRecording.STATE_RECORDING_NOT_STARTED)); } @Override public void changeState(ScheduledRecording scheduledRecording, @RecordingState int newState) { if (scheduledRecording.getState() != newState) { updateScheduledRecording( ScheduledRecording.buildFrom(scheduledRecording).setState(newState).build()); } } @Override public Collection getDeletedSchedules() { return mDeletedScheduleMap.values(); } @NonNull @Override public Collection getDisallowedProgramIds() { return mDeletedScheduleMap.keySet(); } /** * Returns the map which contains the deleted schedules which are mapped from the program ID. */ protected Map getDeletedScheduleMap() { return mDeletedScheduleMap; } /** Returns the schedules whose state is contained by states. */ protected abstract List getRecordingsWithState(int... states); @Override public List getRecordedPrograms(long seriesRecordingId) { SeriesRecording seriesRecording = getSeriesRecording(seriesRecordingId); if (seriesRecording == null) { return Collections.emptyList(); } List 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 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) {} }