11abddd9f6225298066094e20a6c29061b6af4590Nick Chalko/*
21abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * Copyright (C) 2015 The Android Open Source Project
31abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *
41abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * Licensed under the Apache License, Version 2.0 (the "License");
51abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * you may not use this file except in compliance with the License.
61abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * You may obtain a copy of the License at
71abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *
81abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *      http://www.apache.org/licenses/LICENSE-2.0
91abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *
101abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * Unless required by applicable law or agreed to in writing, software
111abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * distributed under the License is distributed on an "AS IS" BASIS,
121abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
131abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * See the License for the specific language governing permissions and
141abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * limitations under the License
151abddd9f6225298066094e20a6c29061b6af4590Nick Chalko */
161abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
171abddd9f6225298066094e20a6c29061b6af4590Nick Chalkopackage com.android.tv.dvr;
181abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
192e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalkoimport android.annotation.TargetApi;
201abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport android.content.Context;
212e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalkoimport android.os.Build;
22ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkoimport android.support.annotation.MainThread;
2365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.support.annotation.NonNull;
242e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalkoimport android.util.ArraySet;
251abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport android.util.Log;
261abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
272e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalkoimport com.android.tv.common.SoftPreconditions;
28ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkoimport com.android.tv.common.feature.CommonFeatures;
296ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport com.android.tv.dvr.data.RecordedProgram;
306ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport com.android.tv.dvr.data.ScheduledRecording;
316ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport com.android.tv.dvr.data.ScheduledRecording.RecordingState;
326ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport com.android.tv.dvr.data.SeriesRecording;
332e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalkoimport com.android.tv.util.Clock;
341abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
352e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalkoimport java.util.ArrayList;
36d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport java.util.Arrays;
3765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.Collection;
38d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport java.util.Collections;
3965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.HashMap;
402e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalkoimport java.util.List;
4165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.Map;
421abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport java.util.Set;
4365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.concurrent.CopyOnWriteArraySet;
441abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
451abddd9f6225298066094e20a6c29061b6af4590Nick Chalko/**
461abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * Base implementation of @{link DataManagerInternal}.
471abddd9f6225298066094e20a6c29061b6af4590Nick Chalko */
48ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko@MainThread
492e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko@TargetApi(Build.VERSION_CODES.N)
501abddd9f6225298066094e20a6c29061b6af4590Nick Chalkopublic abstract class BaseDvrDataManager implements WritableDvrDataManager {
511abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private final static String TAG = "BaseDvrDataManager";
521abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private final static boolean DEBUG = false;
532e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    protected final Clock mClock;
541abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
5565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final Set<OnDvrScheduleLoadFinishedListener> mOnDvrScheduleLoadFinishedListeners =
5665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            new CopyOnWriteArraySet<>();
5765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final Set<OnRecordedProgramLoadFinishedListener>
5865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mOnRecordedProgramLoadFinishedListeners = new CopyOnWriteArraySet<>();
592e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    private final Set<ScheduledRecordingListener> mScheduledRecordingListeners = new ArraySet<>();
6065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final Set<SeriesRecordingListener> mSeriesRecordingListeners = new ArraySet<>();
612e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    private final Set<RecordedProgramListener> mRecordedProgramListeners = new ArraySet<>();
6265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final HashMap<Long, ScheduledRecording> mDeletedScheduleMap = new HashMap<>();
631abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
642e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    BaseDvrDataManager(Context context, Clock clock) {
65ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        SoftPreconditions.checkFeatureEnabled(context, CommonFeatures.DVR, TAG);
662e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        mClock = clock;
671abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
681abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
691abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    @Override
7065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void addDvrScheduleLoadFinishedListener(OnDvrScheduleLoadFinishedListener listener) {
7165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mOnDvrScheduleLoadFinishedListeners.add(listener);
7265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
7365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
7465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
7565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void removeDvrScheduleLoadFinishedListener(OnDvrScheduleLoadFinishedListener listener) {
7665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mOnDvrScheduleLoadFinishedListeners.remove(listener);
7765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
7865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
7965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
8065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void addRecordedProgramLoadFinishedListener(
8165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            OnRecordedProgramLoadFinishedListener listener) {
8265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mOnRecordedProgramLoadFinishedListeners.add(listener);
8365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
8465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
8565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
8665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void removeRecordedProgramLoadFinishedListener(
8765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            OnRecordedProgramLoadFinishedListener listener) {
8865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mOnRecordedProgramLoadFinishedListeners.remove(listener);
8965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
9065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
9165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
922e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    public final void addScheduledRecordingListener(ScheduledRecordingListener listener) {
932e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        mScheduledRecordingListeners.add(listener);
941abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
951abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
961abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    @Override
972e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    public final void removeScheduledRecordingListener(ScheduledRecordingListener listener) {
982e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        mScheduledRecordingListeners.remove(listener);
992e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    }
1002e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko
1012e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    @Override
10265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public final void addSeriesRecordingListener(SeriesRecordingListener listener) {
10365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mSeriesRecordingListeners.add(listener);
10465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
10565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
10665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
10765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public final void removeSeriesRecordingListener(SeriesRecordingListener listener) {
10865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mSeriesRecordingListeners.remove(listener);
10965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
11065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
11165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
1122e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    public final void addRecordedProgramListener(RecordedProgramListener listener) {
1132e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        mRecordedProgramListeners.add(listener);
1142e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    }
1152e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko
1162e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    @Override
1172e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    public final void removeRecordedProgramListener(RecordedProgramListener listener) {
1182e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        mRecordedProgramListeners.remove(listener);
1191abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
1201abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
1211abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    /**
12265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Calls {@link OnDvrScheduleLoadFinishedListener#onDvrScheduleLoadFinished} for each listener.
12365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
12465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    protected final void notifyDvrScheduleLoadFinished() {
12565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        for (OnDvrScheduleLoadFinishedListener l : mOnDvrScheduleLoadFinishedListeners) {
12665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (DEBUG) Log.d(TAG, "notify DVR schedule load finished");
12765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            l.onDvrScheduleLoadFinished();
12865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
12965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
13065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
13165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
13265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Calls {@link OnRecordedProgramLoadFinishedListener#onRecordedProgramLoadFinished()}
13365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * for each listener.
13465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
13565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    protected final void notifyRecordedProgramLoadFinished() {
13665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        for (OnRecordedProgramLoadFinishedListener l : mOnRecordedProgramLoadFinishedListeners) {
13765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (DEBUG) Log.d(TAG, "notify recorded programs load finished");
13865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            l.onRecordedProgramLoadFinished();
13965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
14065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
14165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
14265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
143d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko     * Calls {@link RecordedProgramListener#onRecordedProgramsAdded}
1442e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko     * for each listener.
1451abddd9f6225298066094e20a6c29061b6af4590Nick Chalko     */
146d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    protected final void notifyRecordedProgramsAdded(RecordedProgram... recordedPrograms) {
1472e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        for (RecordedProgramListener l : mRecordedProgramListeners) {
148d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            if (DEBUG) Log.d(TAG, "notify " + l + " added " + Arrays.asList(recordedPrograms));
149d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            l.onRecordedProgramsAdded(recordedPrograms);
1501abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
1511abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
1521abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
1531abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    /**
154d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko     * Calls {@link RecordedProgramListener#onRecordedProgramsChanged}
1552e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko     * for each listener.
1561abddd9f6225298066094e20a6c29061b6af4590Nick Chalko     */
157d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    protected final void notifyRecordedProgramsChanged(RecordedProgram... recordedPrograms) {
1582e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        for (RecordedProgramListener l : mRecordedProgramListeners) {
159d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            if (DEBUG) Log.d(TAG, "notify " + l + " changed " + Arrays.asList(recordedPrograms));
160d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            l.onRecordedProgramsChanged(recordedPrograms);
1611abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
1621abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
1631abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
1641abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    /**
165d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko     * Calls {@link RecordedProgramListener#onRecordedProgramsRemoved}
1662e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko     * for each  listener.
1671abddd9f6225298066094e20a6c29061b6af4590Nick Chalko     */
168d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    protected final void notifyRecordedProgramsRemoved(RecordedProgram... recordedPrograms) {
1692e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        for (RecordedProgramListener l : mRecordedProgramListeners) {
170d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            if (DEBUG) Log.d(TAG, "notify " + l + " removed " + Arrays.asList(recordedPrograms));
171d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            l.onRecordedProgramsRemoved(recordedPrograms);
1721abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
1731abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
1742e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko
1752e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    /**
17665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Calls {@link SeriesRecordingListener#onSeriesRecordingAdded}
17765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * for each listener.
17865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
17965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    protected final void notifySeriesRecordingAdded(SeriesRecording... seriesRecordings) {
18065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        for (SeriesRecordingListener l : mSeriesRecordingListeners) {
181d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            if (DEBUG) Log.d(TAG, "notify " + l + " added  " + Arrays.asList(seriesRecordings));
18265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            l.onSeriesRecordingAdded(seriesRecordings);
18365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
18465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
18565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
18665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
18765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Calls {@link SeriesRecordingListener#onSeriesRecordingRemoved}
18865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * for each listener.
18965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
19065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    protected final void notifySeriesRecordingRemoved(SeriesRecording... seriesRecordings) {
19165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        for (SeriesRecordingListener l : mSeriesRecordingListeners) {
192d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            if (DEBUG) Log.d(TAG, "notify " + l + " removed " + Arrays.asList(seriesRecordings));
19365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            l.onSeriesRecordingRemoved(seriesRecordings);
19465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
19565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
19665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
19765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
19865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Calls
19965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * {@link SeriesRecordingListener#onSeriesRecordingChanged}
20065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * for each listener.
20165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
20265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    protected final void notifySeriesRecordingChanged(SeriesRecording... seriesRecordings) {
20365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        for (SeriesRecordingListener l : mSeriesRecordingListeners) {
204d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            if (DEBUG) Log.d(TAG, "notify " + l + " changed " + Arrays.asList(seriesRecordings));
20565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            l.onSeriesRecordingChanged(seriesRecordings);
20665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
20765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
20865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
20965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
21065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Calls {@link ScheduledRecordingListener#onScheduledRecordingAdded}
2112e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko     * for each listener.
2122e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko     */
21365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    protected final void notifyScheduledRecordingAdded(ScheduledRecording... scheduledRecording) {
2142e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        for (ScheduledRecordingListener l : mScheduledRecordingListeners) {
215d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            if (DEBUG) Log.d(TAG, "notify " + l + " added  " + Arrays.asList(scheduledRecording));
2162e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko            l.onScheduledRecordingAdded(scheduledRecording);
2172e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        }
2182e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    }
2192e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko
2202e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    /**
22165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Calls {@link ScheduledRecordingListener#onScheduledRecordingRemoved}
2222e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko     * for each listener.
2232e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko     */
22465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    protected final void notifyScheduledRecordingRemoved(ScheduledRecording... scheduledRecording) {
2252e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        for (ScheduledRecordingListener l : mScheduledRecordingListeners) {
226d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            if (DEBUG) Log.d(TAG, "notify " + l + " removed " + Arrays.asList(scheduledRecording));
2272e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko            l.onScheduledRecordingRemoved(scheduledRecording);
2282e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        }
2292e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    }
2302e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko
2312e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    /**
2322e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko     * Calls
23365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * {@link ScheduledRecordingListener#onScheduledRecordingStatusChanged}
2342e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko     * for each listener.
2352e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko     */
2362e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    protected final void notifyScheduledRecordingStatusChanged(
23765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            ScheduledRecording... scheduledRecording) {
2382e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        for (ScheduledRecordingListener l : mScheduledRecordingListeners) {
239d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            if (DEBUG) Log.d(TAG, "notify " + l + " changed " + Arrays.asList(scheduledRecording));
2402e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko            l.onScheduledRecordingStatusChanged(scheduledRecording);
2412e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        }
2422e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    }
2432e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko
2442e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    /**
2452e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko     * Returns a new list with only {@link ScheduledRecording} with a {@link
2462e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko     * ScheduledRecording#getEndTimeMs() endTime} after now.
2472e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko     */
2482e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    private List<ScheduledRecording> filterEndTimeIsPast(List<ScheduledRecording> originals) {
2492e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        List<ScheduledRecording> results = new ArrayList<>(originals.size());
2502e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        for (ScheduledRecording r : originals) {
2512e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko            if (r.getEndTimeMs() > mClock.currentTimeMillis()) {
2522e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko                results.add(r);
2532e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko            }
2542e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        }
2552e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        return results;
2562e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    }
2572e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko
2582e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    @Override
25965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public List<ScheduledRecording> getAvailableScheduledRecordings() {
26065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        return filterEndTimeIsPast(getRecordingsWithState(
26165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                ScheduledRecording.STATE_RECORDING_IN_PROGRESS,
26265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                ScheduledRecording.STATE_RECORDING_NOT_STARTED));
26365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
26465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
26565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
2662e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    public List<ScheduledRecording> getStartedRecordings() {
26765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        return filterEndTimeIsPast(getRecordingsWithState(
26865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                ScheduledRecording.STATE_RECORDING_IN_PROGRESS));
2692e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    }
2702e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko
2712e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    @Override
2722e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    public List<ScheduledRecording> getNonStartedScheduledRecordings() {
27365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        return filterEndTimeIsPast(getRecordingsWithState(
27465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                ScheduledRecording.STATE_RECORDING_NOT_STARTED));
27565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
27665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
27765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
27865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void changeState(ScheduledRecording scheduledRecording, @RecordingState int newState) {
27965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (scheduledRecording.getState() != newState) {
28065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            updateScheduledRecording(ScheduledRecording.buildFrom(scheduledRecording)
28165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    .setState(newState).build());
28265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
28365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
28465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
28565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
28665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public Collection<ScheduledRecording> getDeletedSchedules() {
28765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        return mDeletedScheduleMap.values();
2882e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    }
2892e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko
29065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @NonNull
29165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
29265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public Collection<Long> getDisallowedProgramIds() {
29365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        return mDeletedScheduleMap.keySet();
29465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
29565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
29665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
29765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Returns the map which contains the deleted schedules which are mapped from the program ID.
29865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
29965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    protected Map<Long, ScheduledRecording> getDeletedScheduleMap() {
30065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        return mDeletedScheduleMap;
30165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
30265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
30365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
30465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Returns the schedules whose state is contained by states.
30565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
30665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    protected abstract List<ScheduledRecording> getRecordingsWithState(int... states);
30765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
30865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
30965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public List<RecordedProgram> getRecordedPrograms(long seriesRecordingId) {
31065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        SeriesRecording seriesRecording = getSeriesRecording(seriesRecordingId);
311d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        if (seriesRecording == null) {
312d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            return Collections.emptyList();
313d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        }
31465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        List<RecordedProgram> result = new ArrayList<>();
31565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        for (RecordedProgram r : getRecordedPrograms()) {
31665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (seriesRecording.getSeriesId().equals(r.getSeriesId())) {
31765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                result.add(r);
31865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
31965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
32065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        return result;
32165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
322d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
323d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    @Override
3246ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    public void checkAndRemoveEmptySeriesRecording(long... seriesRecordingIds) {
3256ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        List<SeriesRecording> toRemove = new ArrayList<>();
3266ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        for (long rId : seriesRecordingIds) {
3276ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            SeriesRecording seriesRecording = getSeriesRecording(rId);
3286ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            if (seriesRecording != null && isEmptySeriesRecording(seriesRecording)) {
3296ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                toRemove.add(seriesRecording);
3306ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            }
3316ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
3326ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        removeSeriesRecording(SeriesRecording.toArray(toRemove));
3336ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    }
3346ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
3356ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    /**
3366ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * Returns {@code true}, if the series recording is empty and can be removed. If a series
3376ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * recording is in NORMAL state or has recordings or schedules, it is not empty and cannot be
3386ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * removed.
3396ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     */
3406ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    protected final boolean isEmptySeriesRecording(@NonNull SeriesRecording seriesRecording) {
3416ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        if (!seriesRecording.isStopped()) {
3426ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            return false;
3436ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
3446ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        long seriesRecordingId = seriesRecording.getId();
3456ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        for (ScheduledRecording r : getAvailableScheduledRecordings()) {
3466ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            if (r.getSeriesRecordingId() == seriesRecordingId) {
3476ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                return false;
3486ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            }
3496ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
3506ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        String seriesId = seriesRecording.getSeriesId();
3516ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        for (RecordedProgram r : getRecordedPrograms()) {
3526ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            if (seriesId.equals(r.getSeriesId())) {
3536ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                return false;
3546ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            }
3556ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
3566ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        return true;
3576ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    }
3586ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
3596ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    @Override
360d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    public void forgetStorage(String inputId) { }
3611abddd9f6225298066094e20a6c29061b6af4590Nick Chalko}
362