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