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;
26
27import com.android.tv.common.SoftPreconditions;
28import com.android.tv.common.feature.CommonFeatures;
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 com.android.tv.util.Clock;
34
35import java.util.ArrayList;
36import java.util.Arrays;
37import java.util.Collection;
38import java.util.Collections;
39import java.util.HashMap;
40import java.util.List;
41import java.util.Map;
42import java.util.Set;
43import java.util.concurrent.CopyOnWriteArraySet;
44
45/**
46 * Base implementation of @{link DataManagerInternal}.
47 */
48@MainThread
49@TargetApi(Build.VERSION_CODES.N)
50public abstract class BaseDvrDataManager implements WritableDvrDataManager {
51    private final static String TAG = "BaseDvrDataManager";
52    private final static boolean DEBUG = false;
53    protected final Clock mClock;
54
55    private final Set<OnDvrScheduleLoadFinishedListener> mOnDvrScheduleLoadFinishedListeners =
56            new CopyOnWriteArraySet<>();
57    private final Set<OnRecordedProgramLoadFinishedListener>
58            mOnRecordedProgramLoadFinishedListeners = new CopyOnWriteArraySet<>();
59    private final Set<ScheduledRecordingListener> mScheduledRecordingListeners = new ArraySet<>();
60    private final Set<SeriesRecordingListener> mSeriesRecordingListeners = new ArraySet<>();
61    private final Set<RecordedProgramListener> mRecordedProgramListeners = new ArraySet<>();
62    private final HashMap<Long, ScheduledRecording> mDeletedScheduleMap = new HashMap<>();
63
64    BaseDvrDataManager(Context context, Clock clock) {
65        SoftPreconditions.checkFeatureEnabled(context, CommonFeatures.DVR, TAG);
66        mClock = clock;
67    }
68
69    @Override
70    public void addDvrScheduleLoadFinishedListener(OnDvrScheduleLoadFinishedListener listener) {
71        mOnDvrScheduleLoadFinishedListeners.add(listener);
72    }
73
74    @Override
75    public void removeDvrScheduleLoadFinishedListener(OnDvrScheduleLoadFinishedListener listener) {
76        mOnDvrScheduleLoadFinishedListeners.remove(listener);
77    }
78
79    @Override
80    public void addRecordedProgramLoadFinishedListener(
81            OnRecordedProgramLoadFinishedListener listener) {
82        mOnRecordedProgramLoadFinishedListeners.add(listener);
83    }
84
85    @Override
86    public void removeRecordedProgramLoadFinishedListener(
87            OnRecordedProgramLoadFinishedListener listener) {
88        mOnRecordedProgramLoadFinishedListeners.remove(listener);
89    }
90
91    @Override
92    public final void addScheduledRecordingListener(ScheduledRecordingListener listener) {
93        mScheduledRecordingListeners.add(listener);
94    }
95
96    @Override
97    public final void removeScheduledRecordingListener(ScheduledRecordingListener listener) {
98        mScheduledRecordingListeners.remove(listener);
99    }
100
101    @Override
102    public final void addSeriesRecordingListener(SeriesRecordingListener listener) {
103        mSeriesRecordingListeners.add(listener);
104    }
105
106    @Override
107    public final void removeSeriesRecordingListener(SeriesRecordingListener listener) {
108        mSeriesRecordingListeners.remove(listener);
109    }
110
111    @Override
112    public final void addRecordedProgramListener(RecordedProgramListener listener) {
113        mRecordedProgramListeners.add(listener);
114    }
115
116    @Override
117    public final void removeRecordedProgramListener(RecordedProgramListener listener) {
118        mRecordedProgramListeners.remove(listener);
119    }
120
121    /**
122     * Calls {@link OnDvrScheduleLoadFinishedListener#onDvrScheduleLoadFinished} for each listener.
123     */
124    protected final void notifyDvrScheduleLoadFinished() {
125        for (OnDvrScheduleLoadFinishedListener l : mOnDvrScheduleLoadFinishedListeners) {
126            if (DEBUG) Log.d(TAG, "notify DVR schedule load finished");
127            l.onDvrScheduleLoadFinished();
128        }
129    }
130
131    /**
132     * Calls {@link OnRecordedProgramLoadFinishedListener#onRecordedProgramLoadFinished()}
133     * for each listener.
134     */
135    protected final void notifyRecordedProgramLoadFinished() {
136        for (OnRecordedProgramLoadFinishedListener l : mOnRecordedProgramLoadFinishedListeners) {
137            if (DEBUG) Log.d(TAG, "notify recorded programs load finished");
138            l.onRecordedProgramLoadFinished();
139        }
140    }
141
142    /**
143     * Calls {@link RecordedProgramListener#onRecordedProgramsAdded}
144     * for each listener.
145     */
146    protected final void notifyRecordedProgramsAdded(RecordedProgram... recordedPrograms) {
147        for (RecordedProgramListener l : mRecordedProgramListeners) {
148            if (DEBUG) Log.d(TAG, "notify " + l + " added " + Arrays.asList(recordedPrograms));
149            l.onRecordedProgramsAdded(recordedPrograms);
150        }
151    }
152
153    /**
154     * Calls {@link RecordedProgramListener#onRecordedProgramsChanged}
155     * for each listener.
156     */
157    protected final void notifyRecordedProgramsChanged(RecordedProgram... recordedPrograms) {
158        for (RecordedProgramListener l : mRecordedProgramListeners) {
159            if (DEBUG) Log.d(TAG, "notify " + l + " changed " + Arrays.asList(recordedPrograms));
160            l.onRecordedProgramsChanged(recordedPrograms);
161        }
162    }
163
164    /**
165     * Calls {@link RecordedProgramListener#onRecordedProgramsRemoved}
166     * for each  listener.
167     */
168    protected final void notifyRecordedProgramsRemoved(RecordedProgram... recordedPrograms) {
169        for (RecordedProgramListener l : mRecordedProgramListeners) {
170            if (DEBUG) Log.d(TAG, "notify " + l + " removed " + Arrays.asList(recordedPrograms));
171            l.onRecordedProgramsRemoved(recordedPrograms);
172        }
173    }
174
175    /**
176     * Calls {@link SeriesRecordingListener#onSeriesRecordingAdded}
177     * for each listener.
178     */
179    protected final void notifySeriesRecordingAdded(SeriesRecording... seriesRecordings) {
180        for (SeriesRecordingListener l : mSeriesRecordingListeners) {
181            if (DEBUG) Log.d(TAG, "notify " + l + " added  " + Arrays.asList(seriesRecordings));
182            l.onSeriesRecordingAdded(seriesRecordings);
183        }
184    }
185
186    /**
187     * Calls {@link SeriesRecordingListener#onSeriesRecordingRemoved}
188     * for each listener.
189     */
190    protected final void notifySeriesRecordingRemoved(SeriesRecording... seriesRecordings) {
191        for (SeriesRecordingListener l : mSeriesRecordingListeners) {
192            if (DEBUG) Log.d(TAG, "notify " + l + " removed " + Arrays.asList(seriesRecordings));
193            l.onSeriesRecordingRemoved(seriesRecordings);
194        }
195    }
196
197    /**
198     * Calls
199     * {@link SeriesRecordingListener#onSeriesRecordingChanged}
200     * for each listener.
201     */
202    protected final void notifySeriesRecordingChanged(SeriesRecording... seriesRecordings) {
203        for (SeriesRecordingListener l : mSeriesRecordingListeners) {
204            if (DEBUG) Log.d(TAG, "notify " + l + " changed " + Arrays.asList(seriesRecordings));
205            l.onSeriesRecordingChanged(seriesRecordings);
206        }
207    }
208
209    /**
210     * Calls {@link ScheduledRecordingListener#onScheduledRecordingAdded}
211     * for each listener.
212     */
213    protected final void notifyScheduledRecordingAdded(ScheduledRecording... scheduledRecording) {
214        for (ScheduledRecordingListener l : mScheduledRecordingListeners) {
215            if (DEBUG) Log.d(TAG, "notify " + l + " added  " + Arrays.asList(scheduledRecording));
216            l.onScheduledRecordingAdded(scheduledRecording);
217        }
218    }
219
220    /**
221     * Calls {@link ScheduledRecordingListener#onScheduledRecordingRemoved}
222     * for each listener.
223     */
224    protected final void notifyScheduledRecordingRemoved(ScheduledRecording... scheduledRecording) {
225        for (ScheduledRecordingListener l : mScheduledRecordingListeners) {
226            if (DEBUG) Log.d(TAG, "notify " + l + " removed " + Arrays.asList(scheduledRecording));
227            l.onScheduledRecordingRemoved(scheduledRecording);
228        }
229    }
230
231    /**
232     * Calls
233     * {@link ScheduledRecordingListener#onScheduledRecordingStatusChanged}
234     * for each listener.
235     */
236    protected final void notifyScheduledRecordingStatusChanged(
237            ScheduledRecording... scheduledRecording) {
238        for (ScheduledRecordingListener l : mScheduledRecordingListeners) {
239            if (DEBUG) Log.d(TAG, "notify " + l + " changed " + Arrays.asList(scheduledRecording));
240            l.onScheduledRecordingStatusChanged(scheduledRecording);
241        }
242    }
243
244    /**
245     * Returns a new list with only {@link ScheduledRecording} with a {@link
246     * ScheduledRecording#getEndTimeMs() endTime} after now.
247     */
248    private List<ScheduledRecording> filterEndTimeIsPast(List<ScheduledRecording> originals) {
249        List<ScheduledRecording> results = new ArrayList<>(originals.size());
250        for (ScheduledRecording r : originals) {
251            if (r.getEndTimeMs() > mClock.currentTimeMillis()) {
252                results.add(r);
253            }
254        }
255        return results;
256    }
257
258    @Override
259    public List<ScheduledRecording> getAvailableScheduledRecordings() {
260        return filterEndTimeIsPast(getRecordingsWithState(
261                ScheduledRecording.STATE_RECORDING_IN_PROGRESS,
262                ScheduledRecording.STATE_RECORDING_NOT_STARTED));
263    }
264
265    @Override
266    public List<ScheduledRecording> getStartedRecordings() {
267        return filterEndTimeIsPast(getRecordingsWithState(
268                ScheduledRecording.STATE_RECORDING_IN_PROGRESS));
269    }
270
271    @Override
272    public List<ScheduledRecording> getNonStartedScheduledRecordings() {
273        return filterEndTimeIsPast(getRecordingsWithState(
274                ScheduledRecording.STATE_RECORDING_NOT_STARTED));
275    }
276
277    @Override
278    public void changeState(ScheduledRecording scheduledRecording, @RecordingState int newState) {
279        if (scheduledRecording.getState() != newState) {
280            updateScheduledRecording(ScheduledRecording.buildFrom(scheduledRecording)
281                    .setState(newState).build());
282        }
283    }
284
285    @Override
286    public Collection<ScheduledRecording> getDeletedSchedules() {
287        return mDeletedScheduleMap.values();
288    }
289
290    @NonNull
291    @Override
292    public Collection<Long> getDisallowedProgramIds() {
293        return mDeletedScheduleMap.keySet();
294    }
295
296    /**
297     * Returns the map which contains the deleted schedules which are mapped from the program ID.
298     */
299    protected Map<Long, ScheduledRecording> getDeletedScheduleMap() {
300        return mDeletedScheduleMap;
301    }
302
303    /**
304     * Returns the schedules whose state is contained by states.
305     */
306    protected abstract List<ScheduledRecording> getRecordingsWithState(int... states);
307
308    @Override
309    public List<RecordedProgram> getRecordedPrograms(long seriesRecordingId) {
310        SeriesRecording seriesRecording = getSeriesRecording(seriesRecordingId);
311        if (seriesRecording == null) {
312            return Collections.emptyList();
313        }
314        List<RecordedProgram> result = new ArrayList<>();
315        for (RecordedProgram r : getRecordedPrograms()) {
316            if (seriesRecording.getSeriesId().equals(r.getSeriesId())) {
317                result.add(r);
318            }
319        }
320        return result;
321    }
322
323    @Override
324    public void checkAndRemoveEmptySeriesRecording(long... seriesRecordingIds) {
325        List<SeriesRecording> toRemove = new ArrayList<>();
326        for (long rId : seriesRecordingIds) {
327            SeriesRecording seriesRecording = getSeriesRecording(rId);
328            if (seriesRecording != null && isEmptySeriesRecording(seriesRecording)) {
329                toRemove.add(seriesRecording);
330            }
331        }
332        removeSeriesRecording(SeriesRecording.toArray(toRemove));
333    }
334
335    /**
336     * Returns {@code true}, if the series recording is empty and can be removed. If a series
337     * recording is in NORMAL state or has recordings or schedules, it is not empty and cannot be
338     * removed.
339     */
340    protected final boolean isEmptySeriesRecording(@NonNull SeriesRecording seriesRecording) {
341        if (!seriesRecording.isStopped()) {
342            return false;
343        }
344        long seriesRecordingId = seriesRecording.getId();
345        for (ScheduledRecording r : getAvailableScheduledRecordings()) {
346            if (r.getSeriesRecordingId() == seriesRecordingId) {
347                return false;
348            }
349        }
350        String seriesId = seriesRecording.getSeriesId();
351        for (RecordedProgram r : getRecordedPrograms()) {
352            if (seriesId.equals(r.getSeriesId())) {
353                return false;
354            }
355        }
356        return true;
357    }
358
359    @Override
360    public void forgetStorage(String inputId) { }
361}
362