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 
17 package com.android.tv.dvr;
18 
19 import android.annotation.TargetApi;
20 import android.content.Context;
21 import android.os.Build;
22 import android.support.annotation.MainThread;
23 import android.support.annotation.NonNull;
24 import android.util.ArraySet;
25 import android.util.Log;
26 import com.android.tv.common.SoftPreconditions;
27 import com.android.tv.common.feature.CommonFeatures;
28 import com.android.tv.common.util.Clock;
29 import com.android.tv.dvr.data.RecordedProgram;
30 import com.android.tv.dvr.data.ScheduledRecording;
31 import com.android.tv.dvr.data.ScheduledRecording.RecordingState;
32 import com.android.tv.dvr.data.SeriesRecording;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.Collection;
36 import java.util.Collections;
37 import java.util.HashMap;
38 import java.util.List;
39 import java.util.Map;
40 import java.util.Set;
41 import java.util.concurrent.CopyOnWriteArraySet;
42 
43 /** Base implementation of @{link DataManagerInternal}. */
44 @MainThread
45 @TargetApi(Build.VERSION_CODES.N)
46 public 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 
BaseDvrDataManager(Context context, Clock clock)60     public BaseDvrDataManager(Context context, Clock clock) {
61         SoftPreconditions.checkFeatureEnabled(context, CommonFeatures.DVR, TAG);
62         mClock = clock;
63     }
64 
65     @Override
addDvrScheduleLoadFinishedListener(OnDvrScheduleLoadFinishedListener listener)66     public void addDvrScheduleLoadFinishedListener(OnDvrScheduleLoadFinishedListener listener) {
67         mOnDvrScheduleLoadFinishedListeners.add(listener);
68     }
69 
70     @Override
removeDvrScheduleLoadFinishedListener(OnDvrScheduleLoadFinishedListener listener)71     public void removeDvrScheduleLoadFinishedListener(OnDvrScheduleLoadFinishedListener listener) {
72         mOnDvrScheduleLoadFinishedListeners.remove(listener);
73     }
74 
75     @Override
addRecordedProgramLoadFinishedListener( OnRecordedProgramLoadFinishedListener listener)76     public void addRecordedProgramLoadFinishedListener(
77             OnRecordedProgramLoadFinishedListener listener) {
78         mOnRecordedProgramLoadFinishedListeners.add(listener);
79     }
80 
81     @Override
removeRecordedProgramLoadFinishedListener( OnRecordedProgramLoadFinishedListener listener)82     public void removeRecordedProgramLoadFinishedListener(
83             OnRecordedProgramLoadFinishedListener listener) {
84         mOnRecordedProgramLoadFinishedListeners.remove(listener);
85     }
86 
87     @Override
addScheduledRecordingListener(ScheduledRecordingListener listener)88     public final void addScheduledRecordingListener(ScheduledRecordingListener listener) {
89         mScheduledRecordingListeners.add(listener);
90     }
91 
92     @Override
removeScheduledRecordingListener(ScheduledRecordingListener listener)93     public final void removeScheduledRecordingListener(ScheduledRecordingListener listener) {
94         mScheduledRecordingListeners.remove(listener);
95     }
96 
97     @Override
addSeriesRecordingListener(SeriesRecordingListener listener)98     public final void addSeriesRecordingListener(SeriesRecordingListener listener) {
99         mSeriesRecordingListeners.add(listener);
100     }
101 
102     @Override
removeSeriesRecordingListener(SeriesRecordingListener listener)103     public final void removeSeriesRecordingListener(SeriesRecordingListener listener) {
104         mSeriesRecordingListeners.remove(listener);
105     }
106 
107     @Override
addRecordedProgramListener(RecordedProgramListener listener)108     public final void addRecordedProgramListener(RecordedProgramListener listener) {
109         mRecordedProgramListeners.add(listener);
110     }
111 
112     @Override
removeRecordedProgramListener(RecordedProgramListener listener)113     public final void removeRecordedProgramListener(RecordedProgramListener listener) {
114         mRecordedProgramListeners.remove(listener);
115     }
116 
117     /**
118      * Calls {@link OnDvrScheduleLoadFinishedListener#onDvrScheduleLoadFinished} for each listener.
119      */
notifyDvrScheduleLoadFinished()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      */
notifyRecordedProgramLoadFinished()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. */
notifyRecordedProgramsAdded(RecordedProgram... recordedPrograms)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. */
notifyRecordedProgramsChanged(RecordedProgram... recordedPrograms)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. */
notifyRecordedProgramsRemoved(RecordedProgram... recordedPrograms)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. */
notifySeriesRecordingAdded(SeriesRecording... seriesRecordings)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. */
notifySeriesRecordingRemoved(SeriesRecording... seriesRecordings)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. */
notifySeriesRecordingChanged(SeriesRecording... seriesRecordings)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. */
notifyScheduledRecordingAdded(ScheduledRecording... scheduledRecording)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. */
notifyScheduledRecordingRemoved(ScheduledRecording... scheduledRecording)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      */
notifyScheduledRecordingStatusChanged( ScheduledRecording... scheduledRecording)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      */
filterEndTimeIsPast(List<ScheduledRecording> originals)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
getAvailableScheduledRecordings()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
getStartedRecordings()236     public List<ScheduledRecording> getStartedRecordings() {
237         return filterEndTimeIsPast(
238                 getRecordingsWithState(ScheduledRecording.STATE_RECORDING_IN_PROGRESS));
239     }
240 
241     @Override
getNonStartedScheduledRecordings()242     public List<ScheduledRecording> getNonStartedScheduledRecordings() {
243         return filterEndTimeIsPast(
244                 getRecordingsWithState(ScheduledRecording.STATE_RECORDING_NOT_STARTED));
245     }
246 
247     @Override
getFailedScheduledRecordings()248     public List<ScheduledRecording> getFailedScheduledRecordings() {
249         return getRecordingsWithState(ScheduledRecording.STATE_RECORDING_FAILED);
250     }
251 
252     @Override
changeState(ScheduledRecording scheduledRecording, @RecordingState int newState)253     public void changeState(ScheduledRecording scheduledRecording, @RecordingState int newState) {
254         if (scheduledRecording.getState() != newState) {
255             updateScheduledRecording(
256                     ScheduledRecording.buildFrom(scheduledRecording).setState(newState).build());
257         }
258     }
259 
260     @Override
changeState( ScheduledRecording scheduledRecording, @RecordingState int newState, int reason)261     public void changeState(
262             ScheduledRecording scheduledRecording, @RecordingState int newState, int reason) {
263         if (scheduledRecording.getState() != newState) {
264             ScheduledRecording.Builder builder =
265                     ScheduledRecording.buildFrom(scheduledRecording).setState(newState);
266             if (newState == ScheduledRecording.STATE_RECORDING_FAILED) {
267                 builder.setFailedReason(reason);
268             }
269             updateScheduledRecording(builder.build());
270         }
271     }
272 
273     @Override
getDeletedSchedules()274     public Collection<ScheduledRecording> getDeletedSchedules() {
275         return mDeletedScheduleMap.values();
276     }
277 
278     @NonNull
279     @Override
getDisallowedProgramIds()280     public Collection<Long> getDisallowedProgramIds() {
281         return mDeletedScheduleMap.keySet();
282     }
283 
284     /**
285      * Returns the map which contains the deleted schedules which are mapped from the program ID.
286      */
getDeletedScheduleMap()287     protected Map<Long, ScheduledRecording> getDeletedScheduleMap() {
288         return mDeletedScheduleMap;
289     }
290 
291     /** Returns the schedules whose state is contained by states. */
getRecordingsWithState(int... states)292     protected abstract List<ScheduledRecording> getRecordingsWithState(int... states);
293 
294     @Override
getRecordedPrograms(long seriesRecordingId)295     public List<RecordedProgram> getRecordedPrograms(long seriesRecordingId) {
296         SeriesRecording seriesRecording = getSeriesRecording(seriesRecordingId);
297         if (seriesRecording == null) {
298             return Collections.emptyList();
299         }
300         List<RecordedProgram> result = new ArrayList<>();
301         for (RecordedProgram r : getRecordedPrograms()) {
302             if (seriesRecording.getSeriesId().equals(r.getSeriesId())) {
303                 result.add(r);
304             }
305         }
306         return result;
307     }
308 
309     @Override
checkAndRemoveEmptySeriesRecording(long... seriesRecordingIds)310     public void checkAndRemoveEmptySeriesRecording(long... seriesRecordingIds) {
311         List<SeriesRecording> toRemove = new ArrayList<>();
312         for (long rId : seriesRecordingIds) {
313             SeriesRecording seriesRecording = getSeriesRecording(rId);
314             if (seriesRecording != null && isEmptySeriesRecording(seriesRecording)) {
315                 toRemove.add(seriesRecording);
316             }
317         }
318         removeSeriesRecording(SeriesRecording.toArray(toRemove));
319     }
320 
321     /**
322      * Returns {@code true}, if the series recording is empty and can be removed. If a series
323      * recording is in NORMAL state or has recordings or schedules, it is not empty and cannot be
324      * removed.
325      */
isEmptySeriesRecording(@onNull SeriesRecording seriesRecording)326     protected final boolean isEmptySeriesRecording(@NonNull SeriesRecording seriesRecording) {
327         if (!seriesRecording.isStopped()) {
328             return false;
329         }
330         long seriesRecordingId = seriesRecording.getId();
331         for (ScheduledRecording r : getAvailableScheduledRecordings()) {
332             if (r.getSeriesRecordingId() == seriesRecordingId) {
333                 return false;
334             }
335         }
336         String seriesId = seriesRecording.getSeriesId();
337         for (RecordedProgram r : getRecordedPrograms()) {
338             if (seriesId.equals(r.getSeriesId())) {
339                 return false;
340             }
341         }
342         return true;
343     }
344 
345     @Override
forgetStorage(String inputId)346     public void forgetStorage(String inputId) {}
347 }
348