BaseDvrDataManager.java revision 944779887775bd950cf1abf348d2df461593f6ab
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.tv.dvr;
18
19import android.annotation.TargetApi;
20import android.content.Context;
21import android.os.Build;
22import android.support.annotation.MainThread;
23import android.support.annotation.NonNull;
24import android.util.ArraySet;
25import android.util.Log;
26import com.android.tv.common.SoftPreconditions;
27import com.android.tv.common.feature.CommonFeatures;
28import com.android.tv.common.util.Clock;
29import com.android.tv.dvr.data.RecordedProgram;
30import com.android.tv.dvr.data.ScheduledRecording;
31import com.android.tv.dvr.data.ScheduledRecording.RecordingState;
32import com.android.tv.dvr.data.SeriesRecording;
33import java.util.ArrayList;
34import java.util.Arrays;
35import java.util.Collection;
36import java.util.Collections;
37import java.util.HashMap;
38import java.util.List;
39import java.util.Map;
40import java.util.Set;
41import java.util.concurrent.CopyOnWriteArraySet;
42
43/** Base implementation of @{link DataManagerInternal}. */
44@MainThread
45@TargetApi(Build.VERSION_CODES.N)
46public abstract class BaseDvrDataManager implements WritableDvrDataManager {
47    private static final String TAG = "BaseDvrDataManager";
48    private static final boolean DEBUG = false;
49    protected final Clock mClock;
50
51    private final Set<OnDvrScheduleLoadFinishedListener> mOnDvrScheduleLoadFinishedListeners =
52            new CopyOnWriteArraySet<>();
53    private final Set<OnRecordedProgramLoadFinishedListener>
54            mOnRecordedProgramLoadFinishedListeners = new CopyOnWriteArraySet<>();
55    private final Set<ScheduledRecordingListener> mScheduledRecordingListeners = new ArraySet<>();
56    private final Set<SeriesRecordingListener> mSeriesRecordingListeners = new ArraySet<>();
57    private final Set<RecordedProgramListener> mRecordedProgramListeners = new ArraySet<>();
58    private final HashMap<Long, ScheduledRecording> mDeletedScheduleMap = new HashMap<>();
59
60    BaseDvrDataManager(Context context, Clock clock) {
61        SoftPreconditions.checkFeatureEnabled(context, CommonFeatures.DVR, TAG);
62        mClock = clock;
63    }
64
65    @Override
66    public void addDvrScheduleLoadFinishedListener(OnDvrScheduleLoadFinishedListener listener) {
67        mOnDvrScheduleLoadFinishedListeners.add(listener);
68    }
69
70    @Override
71    public void removeDvrScheduleLoadFinishedListener(OnDvrScheduleLoadFinishedListener listener) {
72        mOnDvrScheduleLoadFinishedListeners.remove(listener);
73    }
74
75    @Override
76    public void addRecordedProgramLoadFinishedListener(
77            OnRecordedProgramLoadFinishedListener listener) {
78        mOnRecordedProgramLoadFinishedListeners.add(listener);
79    }
80
81    @Override
82    public void removeRecordedProgramLoadFinishedListener(
83            OnRecordedProgramLoadFinishedListener listener) {
84        mOnRecordedProgramLoadFinishedListeners.remove(listener);
85    }
86
87    @Override
88    public final void addScheduledRecordingListener(ScheduledRecordingListener listener) {
89        mScheduledRecordingListeners.add(listener);
90    }
91
92    @Override
93    public final void removeScheduledRecordingListener(ScheduledRecordingListener listener) {
94        mScheduledRecordingListeners.remove(listener);
95    }
96
97    @Override
98    public final void addSeriesRecordingListener(SeriesRecordingListener listener) {
99        mSeriesRecordingListeners.add(listener);
100    }
101
102    @Override
103    public final void removeSeriesRecordingListener(SeriesRecordingListener listener) {
104        mSeriesRecordingListeners.remove(listener);
105    }
106
107    @Override
108    public final void addRecordedProgramListener(RecordedProgramListener listener) {
109        mRecordedProgramListeners.add(listener);
110    }
111
112    @Override
113    public final void removeRecordedProgramListener(RecordedProgramListener listener) {
114        mRecordedProgramListeners.remove(listener);
115    }
116
117    /**
118     * Calls {@link OnDvrScheduleLoadFinishedListener#onDvrScheduleLoadFinished} for each listener.
119     */
120    protected final void notifyDvrScheduleLoadFinished() {
121        for (OnDvrScheduleLoadFinishedListener l : mOnDvrScheduleLoadFinishedListeners) {
122            if (DEBUG) Log.d(TAG, "notify DVR schedule load finished");
123            l.onDvrScheduleLoadFinished();
124        }
125    }
126
127    /**
128     * Calls {@link OnRecordedProgramLoadFinishedListener#onRecordedProgramLoadFinished()} for each
129     * listener.
130     */
131    protected final void notifyRecordedProgramLoadFinished() {
132        for (OnRecordedProgramLoadFinishedListener l : mOnRecordedProgramLoadFinishedListeners) {
133            if (DEBUG) Log.d(TAG, "notify recorded programs load finished");
134            l.onRecordedProgramLoadFinished();
135        }
136    }
137
138    /** Calls {@link RecordedProgramListener#onRecordedProgramsAdded} for each listener. */
139    protected final void notifyRecordedProgramsAdded(RecordedProgram... recordedPrograms) {
140        for (RecordedProgramListener l : mRecordedProgramListeners) {
141            if (DEBUG) Log.d(TAG, "notify " + l + " added " + Arrays.asList(recordedPrograms));
142            l.onRecordedProgramsAdded(recordedPrograms);
143        }
144    }
145
146    /** Calls {@link RecordedProgramListener#onRecordedProgramsChanged} for each listener. */
147    protected final void notifyRecordedProgramsChanged(RecordedProgram... recordedPrograms) {
148        for (RecordedProgramListener l : mRecordedProgramListeners) {
149            if (DEBUG) Log.d(TAG, "notify " + l + " changed " + Arrays.asList(recordedPrograms));
150            l.onRecordedProgramsChanged(recordedPrograms);
151        }
152    }
153
154    /** Calls {@link RecordedProgramListener#onRecordedProgramsRemoved} for each listener. */
155    protected final void notifyRecordedProgramsRemoved(RecordedProgram... recordedPrograms) {
156        for (RecordedProgramListener l : mRecordedProgramListeners) {
157            if (DEBUG) Log.d(TAG, "notify " + l + " removed " + Arrays.asList(recordedPrograms));
158            l.onRecordedProgramsRemoved(recordedPrograms);
159        }
160    }
161
162    /** Calls {@link SeriesRecordingListener#onSeriesRecordingAdded} for each listener. */
163    protected final void notifySeriesRecordingAdded(SeriesRecording... seriesRecordings) {
164        for (SeriesRecordingListener l : mSeriesRecordingListeners) {
165            if (DEBUG) Log.d(TAG, "notify " + l + " added  " + Arrays.asList(seriesRecordings));
166            l.onSeriesRecordingAdded(seriesRecordings);
167        }
168    }
169
170    /** Calls {@link SeriesRecordingListener#onSeriesRecordingRemoved} for each listener. */
171    protected final void notifySeriesRecordingRemoved(SeriesRecording... seriesRecordings) {
172        for (SeriesRecordingListener l : mSeriesRecordingListeners) {
173            if (DEBUG) Log.d(TAG, "notify " + l + " removed " + Arrays.asList(seriesRecordings));
174            l.onSeriesRecordingRemoved(seriesRecordings);
175        }
176    }
177
178    /** Calls {@link SeriesRecordingListener#onSeriesRecordingChanged} for each listener. */
179    protected final void notifySeriesRecordingChanged(SeriesRecording... seriesRecordings) {
180        for (SeriesRecordingListener l : mSeriesRecordingListeners) {
181            if (DEBUG) Log.d(TAG, "notify " + l + " changed " + Arrays.asList(seriesRecordings));
182            l.onSeriesRecordingChanged(seriesRecordings);
183        }
184    }
185
186    /** Calls {@link ScheduledRecordingListener#onScheduledRecordingAdded} for each listener. */
187    protected final void notifyScheduledRecordingAdded(ScheduledRecording... scheduledRecording) {
188        for (ScheduledRecordingListener l : mScheduledRecordingListeners) {
189            if (DEBUG) Log.d(TAG, "notify " + l + " added  " + Arrays.asList(scheduledRecording));
190            l.onScheduledRecordingAdded(scheduledRecording);
191        }
192    }
193
194    /** Calls {@link ScheduledRecordingListener#onScheduledRecordingRemoved} for each listener. */
195    protected final void notifyScheduledRecordingRemoved(ScheduledRecording... scheduledRecording) {
196        for (ScheduledRecordingListener l : mScheduledRecordingListeners) {
197            if (DEBUG) Log.d(TAG, "notify " + l + " removed " + Arrays.asList(scheduledRecording));
198            l.onScheduledRecordingRemoved(scheduledRecording);
199        }
200    }
201
202    /**
203     * Calls {@link ScheduledRecordingListener#onScheduledRecordingStatusChanged} for each listener.
204     */
205    protected final void notifyScheduledRecordingStatusChanged(
206            ScheduledRecording... scheduledRecording) {
207        for (ScheduledRecordingListener l : mScheduledRecordingListeners) {
208            if (DEBUG) Log.d(TAG, "notify " + l + " changed " + Arrays.asList(scheduledRecording));
209            l.onScheduledRecordingStatusChanged(scheduledRecording);
210        }
211    }
212
213    /**
214     * Returns a new list with only {@link ScheduledRecording} with a {@link
215     * ScheduledRecording#getEndTimeMs() endTime} after now.
216     */
217    private List<ScheduledRecording> filterEndTimeIsPast(List<ScheduledRecording> originals) {
218        List<ScheduledRecording> results = new ArrayList<>(originals.size());
219        for (ScheduledRecording r : originals) {
220            if (r.getEndTimeMs() > mClock.currentTimeMillis()) {
221                results.add(r);
222            }
223        }
224        return results;
225    }
226
227    @Override
228    public List<ScheduledRecording> getAvailableScheduledRecordings() {
229        return filterEndTimeIsPast(
230                getRecordingsWithState(
231                        ScheduledRecording.STATE_RECORDING_IN_PROGRESS,
232                        ScheduledRecording.STATE_RECORDING_NOT_STARTED));
233    }
234
235    @Override
236    public List<ScheduledRecording> getStartedRecordings() {
237        return filterEndTimeIsPast(
238                getRecordingsWithState(ScheduledRecording.STATE_RECORDING_IN_PROGRESS));
239    }
240
241    @Override
242    public List<ScheduledRecording> getNonStartedScheduledRecordings() {
243        return filterEndTimeIsPast(
244                getRecordingsWithState(ScheduledRecording.STATE_RECORDING_NOT_STARTED));
245    }
246
247    @Override
248    public void changeState(ScheduledRecording scheduledRecording, @RecordingState int newState) {
249        if (scheduledRecording.getState() != newState) {
250            updateScheduledRecording(
251                    ScheduledRecording.buildFrom(scheduledRecording).setState(newState).build());
252        }
253    }
254
255    @Override
256    public Collection<ScheduledRecording> getDeletedSchedules() {
257        return mDeletedScheduleMap.values();
258    }
259
260    @NonNull
261    @Override
262    public Collection<Long> getDisallowedProgramIds() {
263        return mDeletedScheduleMap.keySet();
264    }
265
266    /**
267     * Returns the map which contains the deleted schedules which are mapped from the program ID.
268     */
269    protected Map<Long, ScheduledRecording> getDeletedScheduleMap() {
270        return mDeletedScheduleMap;
271    }
272
273    /** Returns the schedules whose state is contained by states. */
274    protected abstract List<ScheduledRecording> getRecordingsWithState(int... states);
275
276    @Override
277    public List<RecordedProgram> getRecordedPrograms(long seriesRecordingId) {
278        SeriesRecording seriesRecording = getSeriesRecording(seriesRecordingId);
279        if (seriesRecording == null) {
280            return Collections.emptyList();
281        }
282        List<RecordedProgram> result = new ArrayList<>();
283        for (RecordedProgram r : getRecordedPrograms()) {
284            if (seriesRecording.getSeriesId().equals(r.getSeriesId())) {
285                result.add(r);
286            }
287        }
288        return result;
289    }
290
291    @Override
292    public void checkAndRemoveEmptySeriesRecording(long... seriesRecordingIds) {
293        List<SeriesRecording> toRemove = new ArrayList<>();
294        for (long rId : seriesRecordingIds) {
295            SeriesRecording seriesRecording = getSeriesRecording(rId);
296            if (seriesRecording != null && isEmptySeriesRecording(seriesRecording)) {
297                toRemove.add(seriesRecording);
298            }
299        }
300        removeSeriesRecording(SeriesRecording.toArray(toRemove));
301    }
302
303    /**
304     * Returns {@code true}, if the series recording is empty and can be removed. If a series
305     * recording is in NORMAL state or has recordings or schedules, it is not empty and cannot be
306     * removed.
307     */
308    protected final boolean isEmptySeriesRecording(@NonNull SeriesRecording seriesRecording) {
309        if (!seriesRecording.isStopped()) {
310            return false;
311        }
312        long seriesRecordingId = seriesRecording.getId();
313        for (ScheduledRecording r : getAvailableScheduledRecordings()) {
314            if (r.getSeriesRecordingId() == seriesRecordingId) {
315                return false;
316            }
317        }
318        String seriesId = seriesRecording.getSeriesId();
319        for (RecordedProgram r : getRecordedPrograms()) {
320            if (seriesId.equals(r.getSeriesId())) {
321                return false;
322            }
323        }
324        return true;
325    }
326
327    @Override
328    public void forgetStorage(String inputId) {}
329}
330