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