diff options
Diffstat (limited to 'src/com/android/tv/dvr/SeriesRecordingScheduler.java')
-rw-r--r-- | src/com/android/tv/dvr/SeriesRecordingScheduler.java | 579 |
1 files changed, 0 insertions, 579 deletions
diff --git a/src/com/android/tv/dvr/SeriesRecordingScheduler.java b/src/com/android/tv/dvr/SeriesRecordingScheduler.java deleted file mode 100644 index 5ed12ce8..00000000 --- a/src/com/android/tv/dvr/SeriesRecordingScheduler.java +++ /dev/null @@ -1,579 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tv.dvr; - -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.content.Context; -import android.content.SharedPreferences; -import android.os.AsyncTask; -import android.os.Build; -import android.support.annotation.MainThread; -import android.support.annotation.VisibleForTesting; -import android.text.TextUtils; -import android.util.ArraySet; -import android.util.Log; -import android.util.LongSparseArray; - -import com.android.tv.ApplicationSingletons; -import com.android.tv.TvApplication; -import com.android.tv.common.CollectionUtils; -import com.android.tv.common.SharedPreferencesUtils; -import com.android.tv.common.SoftPreconditions; -import com.android.tv.data.Program; -import com.android.tv.data.epg.EpgFetcher; -import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; -import com.android.tv.dvr.DvrDataManager.SeriesRecordingListener; -import com.android.tv.dvr.EpisodicProgramLoadTask.ScheduledEpisode; -import com.android.tv.experiments.Experiments; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.concurrent.CopyOnWriteArraySet; -import java.util.Set; - -/** - * Creates the {@link ScheduledRecording}s for the {@link SeriesRecording}. - * <p> - * The current implementation assumes that the series recordings are scheduled only for one channel. - */ -@TargetApi(Build.VERSION_CODES.N) -public class SeriesRecordingScheduler { - private static final String TAG = "SeriesRecordingSchd"; - private static final boolean DEBUG = false; - - private static final String KEY_FETCHED_SERIES_IDS = - "SeriesRecordingScheduler.fetched_series_ids"; - - @SuppressLint("StaticFieldLeak") - private static SeriesRecordingScheduler sInstance; - - /** - * Creates and returns the {@link SeriesRecordingScheduler}. - */ - public static synchronized SeriesRecordingScheduler getInstance(Context context) { - if (sInstance == null) { - sInstance = new SeriesRecordingScheduler(context); - } - return sInstance; - } - - private final Context mContext; - private final DvrManager mDvrManager; - private final WritableDvrDataManager mDataManager; - private final List<SeriesRecordingUpdateTask> mScheduleTasks = new ArrayList<>(); - private final List<FetchSeriesInfoTask> mFetchSeriesInfoTasks = new ArrayList<>(); - private final Set<String> mFetchedSeriesIds = new ArraySet<>(); - private final SharedPreferences mSharedPreferences; - private boolean mStarted; - private boolean mPaused; - private final Set<Long> mPendingSeriesRecordings = new ArraySet<>(); - private final Set<OnSeriesRecordingUpdatedListener> mOnSeriesRecordingUpdatedListeners = - new CopyOnWriteArraySet<>(); - - - private final SeriesRecordingListener mSeriesRecordingListener = new SeriesRecordingListener() { - @Override - public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) { - for (SeriesRecording seriesRecording : seriesRecordings) { - executeFetchSeriesInfoTask(seriesRecording); - } - } - - @Override - public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) { - // Cancel the update. - for (Iterator<SeriesRecordingUpdateTask> iter = mScheduleTasks.iterator(); - iter.hasNext(); ) { - SeriesRecordingUpdateTask task = iter.next(); - if (CollectionUtils.subtract(task.getSeriesRecordings(), seriesRecordings, - SeriesRecording.ID_COMPARATOR).isEmpty()) { - task.cancel(true); - iter.remove(); - } - } - } - - @Override - public void onSeriesRecordingChanged(SeriesRecording... seriesRecordings) { - List<SeriesRecording> stopped = new ArrayList<>(); - List<SeriesRecording> normal = new ArrayList<>(); - for (SeriesRecording r : seriesRecordings) { - if (r.isStopped()) { - stopped.add(r); - } else { - normal.add(r); - } - } - if (!stopped.isEmpty()) { - onSeriesRecordingRemoved(SeriesRecording.toArray(stopped)); - } - if (!normal.isEmpty()) { - updateSchedules(normal); - } - } - }; - - private final ScheduledRecordingListener mScheduledRecordingListener = - new ScheduledRecordingListener() { - @Override - public void onScheduledRecordingAdded(ScheduledRecording... schedules) { - // No need to update series recordings when the new schedule is added. - } - - @Override - public void onScheduledRecordingRemoved(ScheduledRecording... schedules) { - handleScheduledRecordingChange(Arrays.asList(schedules)); - } - - @Override - public void onScheduledRecordingStatusChanged(ScheduledRecording... schedules) { - List<ScheduledRecording> schedulesForUpdate = new ArrayList<>(); - for (ScheduledRecording r : schedules) { - if ((r.getState() == ScheduledRecording.STATE_RECORDING_FAILED - || r.getState() == ScheduledRecording.STATE_RECORDING_CLIPPED) - && r.getSeriesRecordingId() != SeriesRecording.ID_NOT_SET - && !TextUtils.isEmpty(r.getSeasonNumber()) - && !TextUtils.isEmpty(r.getEpisodeNumber())) { - schedulesForUpdate.add(r); - } - } - if (!schedulesForUpdate.isEmpty()) { - handleScheduledRecordingChange(schedulesForUpdate); - } - } - - private void handleScheduledRecordingChange(List<ScheduledRecording> schedules) { - if (schedules.isEmpty()) { - return; - } - Set<Long> seriesRecordingIds = new HashSet<>(); - for (ScheduledRecording r : schedules) { - if (r.getSeriesRecordingId() != SeriesRecording.ID_NOT_SET) { - seriesRecordingIds.add(r.getSeriesRecordingId()); - } - } - if (!seriesRecordingIds.isEmpty()) { - List<SeriesRecording> seriesRecordings = new ArrayList<>(); - for (Long id : seriesRecordingIds) { - SeriesRecording seriesRecording = mDataManager.getSeriesRecording(id); - if (seriesRecording != null) { - seriesRecordings.add(seriesRecording); - } - } - if (!seriesRecordings.isEmpty()) { - updateSchedules(seriesRecordings); - } - } - } - }; - - private SeriesRecordingScheduler(Context context) { - mContext = context.getApplicationContext(); - ApplicationSingletons appSingletons = TvApplication.getSingletons(context); - mDvrManager = appSingletons.getDvrManager(); - mDataManager = (WritableDvrDataManager) appSingletons.getDvrDataManager(); - mSharedPreferences = context.getSharedPreferences( - SharedPreferencesUtils.SHARED_PREF_SERIES_RECORDINGS, Context.MODE_PRIVATE); - mFetchedSeriesIds.addAll(mSharedPreferences.getStringSet(KEY_FETCHED_SERIES_IDS, - Collections.emptySet())); - } - - /** - * Starts the scheduler. - */ - @MainThread - public void start() { - SoftPreconditions.checkState(mDataManager.isInitialized()); - if (mStarted) { - return; - } - if (DEBUG) Log.d(TAG, "start"); - mStarted = true; - mDataManager.addSeriesRecordingListener(mSeriesRecordingListener); - mDataManager.addScheduledRecordingListener(mScheduledRecordingListener); - startFetchingSeriesInfo(); - updateSchedules(mDataManager.getSeriesRecordings()); - } - - @MainThread - public void stop() { - if (!mStarted) { - return; - } - if (DEBUG) Log.d(TAG, "stop"); - mStarted = false; - for (FetchSeriesInfoTask task : mFetchSeriesInfoTasks) { - task.cancel(true); - } - mFetchSeriesInfoTasks.clear(); - for (SeriesRecordingUpdateTask task : mScheduleTasks) { - task.cancel(true); - } - mScheduleTasks.clear(); - mDataManager.removeScheduledRecordingListener(mScheduledRecordingListener); - mDataManager.removeSeriesRecordingListener(mSeriesRecordingListener); - } - - private void startFetchingSeriesInfo() { - for (SeriesRecording seriesRecording : mDataManager.getSeriesRecordings()) { - if (!mFetchedSeriesIds.contains(seriesRecording.getSeriesId())) { - executeFetchSeriesInfoTask(seriesRecording); - } - } - } - - private void executeFetchSeriesInfoTask(SeriesRecording seriesRecording) { - if (Experiments.CLOUD_EPG.get()) { - FetchSeriesInfoTask task = new FetchSeriesInfoTask(seriesRecording); - task.execute(); - mFetchSeriesInfoTasks.add(task); - } - } - - /** - * Pauses the updates of the series recordings. - */ - public void pauseUpdate() { - if (DEBUG) Log.d(TAG, "Schedule paused"); - if (mPaused) { - return; - } - mPaused = true; - if (!mStarted) { - return; - } - for (SeriesRecordingUpdateTask task : mScheduleTasks) { - for (SeriesRecording r : task.getSeriesRecordings()) { - mPendingSeriesRecordings.add(r.getId()); - } - task.cancel(true); - } - } - - /** - * Resumes the updates of the series recordings. - */ - public void resumeUpdate() { - if (DEBUG) Log.d(TAG, "Schedule resumed"); - if (!mPaused) { - return; - } - mPaused = false; - if (!mStarted) { - return; - } - if (!mPendingSeriesRecordings.isEmpty()) { - List<SeriesRecording> seriesRecordings = new ArrayList<>(); - for (long seriesRecordingId : mPendingSeriesRecordings) { - SeriesRecording seriesRecording = - mDataManager.getSeriesRecording(seriesRecordingId); - if (seriesRecording != null) { - seriesRecordings.add(seriesRecording); - } - } - if (!seriesRecordings.isEmpty()) { - updateSchedules(seriesRecordings); - } - } - } - - /** - * Update schedules for the given series recordings. If it's paused, the update will be done - * after it's resumed. - */ - public void updateSchedules(Collection<SeriesRecording> seriesRecordings) { - if (DEBUG) Log.d(TAG, "updateSchedules:" + seriesRecordings); - if (!mStarted) { - if (DEBUG) Log.d(TAG, "Not started yet."); - return; - } - if (mPaused) { - for (SeriesRecording r : seriesRecordings) { - mPendingSeriesRecordings.add(r.getId()); - } - if (DEBUG) { - Log.d(TAG, "The scheduler has been paused. Adding to the pending list. size=" - + mPendingSeriesRecordings.size()); - } - return; - } - Set<SeriesRecording> previousSeriesRecordings = new HashSet<>(); - for (Iterator<SeriesRecordingUpdateTask> iter = mScheduleTasks.iterator(); - iter.hasNext(); ) { - SeriesRecordingUpdateTask task = iter.next(); - if (CollectionUtils.containsAny(task.getSeriesRecordings(), seriesRecordings, - SeriesRecording.ID_COMPARATOR)) { - // The task is affected by the seriesRecordings - task.cancel(true); - previousSeriesRecordings.addAll(task.getSeriesRecordings()); - iter.remove(); - } - } - List<SeriesRecording> seriesRecordingsToUpdate = CollectionUtils.union(seriesRecordings, - previousSeriesRecordings, SeriesRecording.ID_COMPARATOR); - for (Iterator<SeriesRecording> iter = seriesRecordingsToUpdate.iterator(); - iter.hasNext(); ) { - SeriesRecording seriesRecording = mDataManager.getSeriesRecording(iter.next().getId()); - if (seriesRecording == null || seriesRecording.isStopped()) { - // Series recording has been removed or stopped. - iter.remove(); - } - } - if (seriesRecordingsToUpdate.isEmpty()) { - return; - } - if (needToReadAllChannels(seriesRecordingsToUpdate)) { - SeriesRecordingUpdateTask task = - new SeriesRecordingUpdateTask(seriesRecordingsToUpdate); - mScheduleTasks.add(task); - if (DEBUG) Log.d(TAG, "Added schedule task: " + task); - task.execute(); - } else { - for (SeriesRecording seriesRecording : seriesRecordingsToUpdate) { - SeriesRecordingUpdateTask task = new SeriesRecordingUpdateTask( - Collections.singletonList(seriesRecording)); - mScheduleTasks.add(task); - if (DEBUG) Log.d(TAG, "Added schedule task: " + task); - task.execute(); - } - } - } - - /** - * Adds {@link OnSeriesRecordingUpdatedListener}. - */ - public void addOnSeriesRecordingUpdatedListener(OnSeriesRecordingUpdatedListener listener) { - mOnSeriesRecordingUpdatedListeners.add(listener); - } - - /** - * Removes {@link OnSeriesRecordingUpdatedListener}. - */ - public void removeOnSeriesRecordingUpdatedListener(OnSeriesRecordingUpdatedListener listener) { - mOnSeriesRecordingUpdatedListeners.remove(listener); - } - - private boolean needToReadAllChannels(List<SeriesRecording> seriesRecordingsToUpdate) { - for (SeriesRecording seriesRecording : seriesRecordingsToUpdate) { - if (seriesRecording.getChannelOption() == SeriesRecording.OPTION_CHANNEL_ALL) { - return true; - } - } - return false; - } - - /** - * Pick one program per an episode. - * - * <p>Note that the programs which has been already scheduled have the highest priority, and all - * of them are added even though they are the same episodes. That's because the schedules - * should be added to the series recording. - * <p>If there are no existing schedules for an episode, one program which starts earlier is - * picked. - */ - private LongSparseArray<List<Program>> pickOneProgramPerEpisode( - List<SeriesRecording> seriesRecordings, List<Program> programs) { - return pickOneProgramPerEpisode(mDataManager, seriesRecordings, programs); - } - - /** - * @see #pickOneProgramPerEpisode(List, List) - */ - @VisibleForTesting - static LongSparseArray<List<Program>> pickOneProgramPerEpisode( - DvrDataManager dataManager, List<SeriesRecording> seriesRecordings, - List<Program> programs) { - // Initialize. - LongSparseArray<List<Program>> result = new LongSparseArray<>(); - Map<String, Long> seriesRecordingIds = new HashMap<>(); - for (SeriesRecording seriesRecording : seriesRecordings) { - result.put(seriesRecording.getId(), new ArrayList<>()); - seriesRecordingIds.put(seriesRecording.getSeriesId(), seriesRecording.getId()); - } - // Group programs by the episode. - Map<ScheduledEpisode, List<Program>> programsForEpisodeMap = new HashMap<>(); - for (Program program : programs) { - long seriesRecordingId = seriesRecordingIds.get(program.getSeriesId()); - if (TextUtils.isEmpty(program.getSeasonNumber()) - || TextUtils.isEmpty(program.getEpisodeNumber())) { - // Add all the programs if it doesn't have season number or episode number. - result.get(seriesRecordingId).add(program); - continue; - } - ScheduledEpisode episode = new ScheduledEpisode(seriesRecordingId, - program.getSeasonNumber(), program.getEpisodeNumber()); - List<Program> programsForEpisode = programsForEpisodeMap.get(episode); - if (programsForEpisode == null) { - programsForEpisode = new ArrayList<>(); - programsForEpisodeMap.put(episode, programsForEpisode); - } - programsForEpisode.add(program); - } - // Pick one program. - for (Entry<ScheduledEpisode, List<Program>> entry : programsForEpisodeMap.entrySet()) { - List<Program> programsForEpisode = entry.getValue(); - Collections.sort(programsForEpisode, new Comparator<Program>() { - @Override - public int compare(Program lhs, Program rhs) { - // Place the existing schedule first. - boolean lhsScheduled = isProgramScheduled(dataManager, lhs); - boolean rhsScheduled = isProgramScheduled(dataManager, rhs); - if (lhsScheduled && !rhsScheduled) { - return -1; - } - if (!lhsScheduled && rhsScheduled) { - return 1; - } - // Sort by the start time in ascending order. - return lhs.compareTo(rhs); - } - }); - boolean added = false; - // Add all the scheduled programs - List<Program> programsForSeries = result.get(entry.getKey().seriesRecordingId); - for (Program program : programsForEpisode) { - if (isProgramScheduled(dataManager, program)) { - programsForSeries.add(program); - added = true; - } else if (!added) { - programsForSeries.add(program); - break; - } - } - } - return result; - } - - private static boolean isProgramScheduled(DvrDataManager dataManager, Program program) { - ScheduledRecording schedule = - dataManager.getScheduledRecordingForProgramId(program.getId()); - return schedule != null && schedule.getState() - == ScheduledRecording.STATE_RECORDING_NOT_STARTED; - } - - private void updateFetchedSeries() { - mSharedPreferences.edit().putStringSet(KEY_FETCHED_SERIES_IDS, mFetchedSeriesIds).apply(); - } - - /** - * This works only for the existing series recordings. Do not use this task for the - * "adding series recording" UI. - */ - private class SeriesRecordingUpdateTask extends EpisodicProgramLoadTask { - SeriesRecordingUpdateTask(List<SeriesRecording> seriesRecordings) { - super(mContext, seriesRecordings); - } - - @Override - protected void onPostExecute(List<Program> programs) { - if (DEBUG) Log.d(TAG, "onPostExecute: updating schedules with programs:" + programs); - mScheduleTasks.remove(this); - if (programs == null) { - Log.e(TAG, "Creating schedules for series recording failed: " - + getSeriesRecordings()); - return; - } - LongSparseArray<List<Program>> seriesProgramMap = pickOneProgramPerEpisode( - getSeriesRecordings(), programs); - for (SeriesRecording seriesRecording : getSeriesRecordings()) { - // Check the series recording is still valid. - SeriesRecording actualSeriesRecording = mDataManager.getSeriesRecording( - seriesRecording.getId()); - if (actualSeriesRecording == null || actualSeriesRecording.isStopped()) { - continue; - } - List<Program> programsToSchedule = seriesProgramMap.get(seriesRecording.getId()); - if (mDataManager.getSeriesRecording(seriesRecording.getId()) != null - && !programsToSchedule.isEmpty()) { - mDvrManager.addScheduleToSeriesRecording(seriesRecording, programsToSchedule); - } - } - if (!mOnSeriesRecordingUpdatedListeners.isEmpty()) { - for (OnSeriesRecordingUpdatedListener listener - : mOnSeriesRecordingUpdatedListeners) { - listener.onSeriesRecordingUpdated( - SeriesRecording.toArray(getSeriesRecordings())); - } - } - } - - @Override - protected void onCancelled(List<Program> programs) { - mScheduleTasks.remove(this); - } - - @Override - public String toString() { - return "SeriesRecordingUpdateTask:{" - + "series_recordings=" + getSeriesRecordings() - + "}"; - } - } - - private class FetchSeriesInfoTask extends AsyncTask<Void, Void, SeriesInfo> { - private SeriesRecording mSeriesRecording; - - FetchSeriesInfoTask(SeriesRecording seriesRecording) { - mSeriesRecording = seriesRecording; - } - - @Override - protected SeriesInfo doInBackground(Void... voids) { - return EpgFetcher.createEpgReader(mContext) - .getSeriesInfo(mSeriesRecording.getSeriesId()); - } - - @Override - protected void onPostExecute(SeriesInfo seriesInfo) { - if (seriesInfo != null) { - mDataManager.updateSeriesRecording(SeriesRecording.buildFrom(mSeriesRecording) - .setTitle(seriesInfo.getTitle()) - .setDescription(seriesInfo.getDescription()) - .setLongDescription(seriesInfo.getLongDescription()) - .setCanonicalGenreIds(seriesInfo.getCanonicalGenreIds()) - .setPosterUri(seriesInfo.getPosterUri()) - .setPhotoUri(seriesInfo.getPhotoUri()) - .build()); - mFetchedSeriesIds.add(seriesInfo.getId()); - updateFetchedSeries(); - } - mFetchSeriesInfoTasks.remove(this); - } - - @Override - protected void onCancelled(SeriesInfo seriesInfo) { - mFetchSeriesInfoTasks.remove(this); - } - } - - /** - * A listener to notify when series recording are updated. - */ - public interface OnSeriesRecordingUpdatedListener { - void onSeriesRecordingUpdated(SeriesRecording... seriesRecordings); - } -} |