Home | History | Annotate | Download | only in dvr
      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 
     27 import com.android.tv.common.SoftPreconditions;
     28 import com.android.tv.common.feature.CommonFeatures;
     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 com.android.tv.util.Clock;
     34 
     35 import java.util.ArrayList;
     36 import java.util.Arrays;
     37 import java.util.Collection;
     38 import java.util.Collections;
     39 import java.util.HashMap;
     40 import java.util.List;
     41 import java.util.Map;
     42 import java.util.Set;
     43 import java.util.concurrent.CopyOnWriteArraySet;
     44 
     45 /**
     46  * Base implementation of @{link DataManagerInternal}.
     47  */
     48 @MainThread
     49 @TargetApi(Build.VERSION_CODES.N)
     50 public 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