165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko/* 265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Copyright (C) 2016 The Android Open Source Project 365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * 465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Licensed under the Apache License, Version 2.0 (the "License"); 565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * you may not use this file except in compliance with the License. 665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * You may obtain a copy of the License at 765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * 865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * http://www.apache.org/licenses/LICENSE-2.0 965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * 1065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Unless required by applicable law or agreed to in writing, software 1165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * distributed under the License is distributed on an "AS IS" BASIS, 1265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * See the License for the specific language governing permissions and 1465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * limitations under the License. 1565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 1665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 1765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkopackage com.android.tv.dvr; 1865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 1965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.annotation.SuppressLint; 2065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.annotation.TargetApi; 2165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.content.ContentUris; 2265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.content.Context; 2365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.database.ContentObserver; 2465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.media.tv.TvContract.Programs; 2565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.net.Uri; 2665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.os.Build; 2765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.os.Handler; 2865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.os.Looper; 2965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.support.annotation.MainThread; 3065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.support.annotation.VisibleForTesting; 31d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport android.util.Log; 3265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 33d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport com.android.tv.TvApplication; 34d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport com.android.tv.data.ChannelDataManager; 3565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.data.Program; 3665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; 3765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.util.AsyncDbTask.AsyncQueryProgramTask; 3865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.util.TvProviderUriMatcher; 3965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 40d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport java.util.ArrayList; 41d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport java.util.Collections; 42d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport java.util.HashSet; 4365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.LinkedList; 44d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport java.util.List; 4565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.Objects; 4665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.Queue; 47d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport java.util.Set; 4865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 4965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko/** 5065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * A class to synchronizes DVR DB with TvProvider. 51d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * 52d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * <p>The current implementation of AsyncDbTask allows only one task to run at a time, and all the 53d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * other tasks are blocked until the current one finishes. As this class performs the low priority 54d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * jobs which take long time, it should not block others if possible. For this reason, only one 55d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * program is queried at a time and others are queued and will be executed on the other 56d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * AsyncDbTask's after the current one finishes to minimize the execution time of one AsyncDbTask. 5765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 5865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko@MainThread 5965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko@TargetApi(Build.VERSION_CODES.N) 6065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoclass DvrDbSync { 61d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private static final String TAG = "DvrDbSync"; 62d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private static final boolean DEBUG = false; 63d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 6465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final Context mContext; 6565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final DvrDataManagerImpl mDataManager; 66d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private final ChannelDataManager mChannelDataManager; 6765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final Queue<Long> mProgramIdQueue = new LinkedList<>(); 68d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private QueryProgramTask mQueryProgramTask; 69d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private final SeriesRecordingScheduler mSeriesRecordingScheduler; 70d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private final ContentObserver mContentObserver = new ContentObserver(new Handler( 7165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko Looper.getMainLooper())) { 7265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @SuppressLint("SwitchIntDef") 7365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Override 7465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void onChange(boolean selfChange, Uri uri) { 7565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko switch (TvProviderUriMatcher.match(uri)) { 7665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko case TvProviderUriMatcher.MATCH_PROGRAM: 77d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (DEBUG) Log.d(TAG, "onProgramsUpdated"); 7865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko onProgramsUpdated(); 7965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko break; 8065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko case TvProviderUriMatcher.MATCH_PROGRAM_ID: 81d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (DEBUG) { 82d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko Log.d(TAG, "onProgramUpdated: programId=" + ContentUris.parseId(uri)); 83d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 84d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko onProgramUpdated(ContentUris.parseId(uri)); 8565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko break; 8665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 8765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 8865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko }; 89d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 90d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private final ChannelDataManager.Listener mChannelDataManagerListener = 91d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko new ChannelDataManager.Listener() { 92d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko @Override 93d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko public void onLoadFinished() { 94d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko start(); 95d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 96d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 97d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko @Override 98d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko public void onChannelListUpdated() { 99d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko onChannelsUpdated(); 100d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 101d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 102d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko @Override 103d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko public void onChannelBrowsableChanged() { } 104d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko }; 105d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 10665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final ScheduledRecordingListener mScheduleListener = new ScheduledRecordingListener() { 10765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Override 10865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void onScheduledRecordingAdded(ScheduledRecording... schedules) { 10965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (ScheduledRecording schedule : schedules) { 11065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko addProgramIdToCheckIfNeeded(schedule); 11165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 112d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko startNextUpdateIfNeeded(); 11365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 11465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 11565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Override 116d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko public void onScheduledRecordingRemoved(ScheduledRecording... schedules) { 117d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko for (ScheduledRecording schedule : schedules) { 118d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mProgramIdQueue.remove(schedule.getProgramId()); 119d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 120d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 12165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 12265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Override 12365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void onScheduledRecordingStatusChanged(ScheduledRecording... schedules) { 12465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko for (ScheduledRecording schedule : schedules) { 12565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mProgramIdQueue.remove(schedule.getProgramId()); 126d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko addProgramIdToCheckIfNeeded(schedule); 12765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 128d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko startNextUpdateIfNeeded(); 12965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 13065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko }; 13165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 132d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko DvrDbSync(Context context, DvrDataManagerImpl dataManager) { 133d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko this(context, dataManager, TvApplication.getSingletons(context).getChannelDataManager()); 134d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 135d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 136d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko @VisibleForTesting 137d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko DvrDbSync(Context context, DvrDataManagerImpl dataManager, 138d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko ChannelDataManager channelDataManager) { 13965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mContext = context; 14065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mDataManager = dataManager; 141d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mChannelDataManager = channelDataManager; 142d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mSeriesRecordingScheduler = SeriesRecordingScheduler.getInstance(context); 14365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 14465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 14565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 14665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Starts the DB sync. 14765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 14865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void start() { 149d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (!mChannelDataManager.isDbLoadFinished()) { 150d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mChannelDataManager.addListener(mChannelDataManagerListener); 151d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko return; 152d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 15365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mContext.getContentResolver().registerContentObserver(Programs.CONTENT_URI, true, 154d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mContentObserver); 15565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mDataManager.addScheduledRecordingListener(mScheduleListener); 156d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko onChannelsUpdated(); 15765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko onProgramsUpdated(); 15865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 15965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 16065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 16165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Stops the DB sync. 16265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 16365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public void stop() { 16465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mProgramIdQueue.clear(); 165d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (mQueryProgramTask != null) { 166d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mQueryProgramTask.cancel(true); 16765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 168d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mChannelDataManager.removeListener(mChannelDataManagerListener); 16965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mDataManager.removeScheduledRecordingListener(mScheduleListener); 170d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mContext.getContentResolver().unregisterContentObserver(mContentObserver); 171d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 172d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 173d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private void onChannelsUpdated() { 174d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko List<SeriesRecording> seriesRecordingsToUpdate = new ArrayList<>(); 175d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko for (SeriesRecording r : mDataManager.getSeriesRecordings()) { 176d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (r.getChannelOption() == SeriesRecording.OPTION_CHANNEL_ONE 177d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko && !mChannelDataManager.doesChannelExistInDb(r.getChannelId())) { 178d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko seriesRecordingsToUpdate.add(SeriesRecording.buildFrom(r) 179d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko .setChannelOption(SeriesRecording.OPTION_CHANNEL_ALL) 180d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko .setState(SeriesRecording.STATE_SERIES_STOPPED).build()); 181d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 182d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 183d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (!seriesRecordingsToUpdate.isEmpty()) { 184d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mDataManager.updateSeriesRecording( 185d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko SeriesRecording.toArray(seriesRecordingsToUpdate)); 186d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 187d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko List<ScheduledRecording> schedulesToRemove = new ArrayList<>(); 188d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko for (ScheduledRecording r : mDataManager.getAvailableScheduledRecordings()) { 189d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (!mChannelDataManager.doesChannelExistInDb(r.getChannelId())) { 190d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko schedulesToRemove.add(r); 191d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mProgramIdQueue.remove(r.getProgramId()); 192d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 193d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 194d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (!schedulesToRemove.isEmpty()) { 195d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mDataManager.removeScheduledRecording( 196d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko ScheduledRecording.toArray(schedulesToRemove)); 197d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 19865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 19965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 20065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private void onProgramsUpdated() { 201d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko for (ScheduledRecording schedule : mDataManager.getAvailableScheduledRecordings()) { 20265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko addProgramIdToCheckIfNeeded(schedule); 20365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 204d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko startNextUpdateIfNeeded(); 205d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 206d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko 207d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private void onProgramUpdated(long programId) { 208d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko addProgramIdToCheckIfNeeded(mDataManager.getScheduledRecordingForProgramId(programId)); 209d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko startNextUpdateIfNeeded(); 21065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 21165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 21265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private void addProgramIdToCheckIfNeeded(ScheduledRecording schedule) { 21365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (schedule == null) { 21465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return; 21565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 21665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko long programId = schedule.getProgramId(); 21765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (programId != ScheduledRecording.ID_NOT_SET 21865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko && !mProgramIdQueue.contains(programId) 21965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko && (schedule.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED 22065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko || schedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) { 221d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (DEBUG) Log.d(TAG, "Program ID enqueued: " + programId); 22265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mProgramIdQueue.offer(programId); 223d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // There are schedules to be updated. Pause the SeriesRecordingScheduler until all the 224d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // schedule updates finish. 225d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // Note that the SeriesRecordingScheduler should be paused even though the program to 226d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // check is not episodic because it can be changed to the episodic program after the 227d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // update, which affect the SeriesRecordingScheduler. 228d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mSeriesRecordingScheduler.pauseUpdate(); 22965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 23065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 23165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 23265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private void startNextUpdateIfNeeded() { 233d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (mQueryProgramTask != null && !mQueryProgramTask.isCancelled()) { 23465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return; 23565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 236d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (!mProgramIdQueue.isEmpty()) { 237d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (DEBUG) Log.d(TAG, "Program ID dequeued: " + mProgramIdQueue.peek()); 238d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mQueryProgramTask = new QueryProgramTask(mProgramIdQueue.poll()); 239d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mQueryProgramTask.executeOnDbThread(); 240d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } else { 241d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mSeriesRecordingScheduler.resumeUpdate(); 24265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 24365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 24465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 24565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @VisibleForTesting 24665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko void handleUpdateProgram(Program program, long programId) { 247d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko Set<SeriesRecording> seriesRecordingsToUpdate = new HashSet<>(); 24865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko ScheduledRecording schedule = mDataManager.getScheduledRecordingForProgramId(programId); 24965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (schedule != null 25065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko && (schedule.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED 25165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko || schedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) { 25265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (program == null) { 25365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mDataManager.removeScheduledRecording(schedule); 254d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (schedule.getSeriesRecordingId() != SeriesRecording.ID_NOT_SET) { 255d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko SeriesRecording seriesRecording = 256d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mDataManager.getSeriesRecording(schedule.getSeriesRecordingId()); 257d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (seriesRecording != null) { 258d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko seriesRecordingsToUpdate.add(seriesRecording); 259d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 260d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 26165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } else { 26265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko long currentTimeMs = System.currentTimeMillis(); 26365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko ScheduledRecording.Builder builder = ScheduledRecording.buildFrom(schedule) 26465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko .setEndTimeMs(program.getEndTimeUtcMillis()) 26565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko .setSeasonNumber(program.getSeasonNumber()) 26665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko .setEpisodeNumber(program.getEpisodeNumber()) 26765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko .setEpisodeTitle(program.getEpisodeTitle()) 26865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko .setProgramDescription(program.getDescription()) 26965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko .setProgramLongDescription(program.getLongDescription()) 27065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko .setProgramPosterArtUri(program.getPosterArtUri()) 27165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko .setProgramThumbnailUri(program.getThumbnailUri()); 272d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko boolean needUpdate = false; 273d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // Check the series recording. 274d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko SeriesRecording seriesRecordingForOldSchedule = 275d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mDataManager.getSeriesRecording(schedule.getSeriesRecordingId()); 276d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (program.getSeriesId() != null) { 277d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // New program belongs to a series. 278d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko SeriesRecording seriesRecording = 279d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mDataManager.getSeriesRecording(program.getSeriesId()); 280d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (seriesRecording == null) { 281d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // The new program is episodic while the previous one isn't. 282d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko SeriesRecording newSeriesRecording = TvApplication.getSingletons(mContext) 283d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko .getDvrManager().addSeriesRecording(program, 284d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko Collections.singletonList(program), 285d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko SeriesRecording.STATE_SERIES_STOPPED); 286d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko builder.setSeriesRecordingId(newSeriesRecording.getId()); 287d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko needUpdate = true; 288d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } else if (seriesRecording.getId() != schedule.getSeriesRecordingId()) { 289d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // The new program belongs to the other series. 290d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko builder.setSeriesRecordingId(seriesRecording.getId()); 291d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko needUpdate = true; 292d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko seriesRecordingsToUpdate.add(seriesRecording); 293d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (seriesRecordingForOldSchedule != null) { 294d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko seriesRecordingsToUpdate.add(seriesRecordingForOldSchedule); 295d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 296d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } else if (!Objects.equals(schedule.getSeasonNumber(), 297d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko program.getSeasonNumber()) 298d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko || !Objects.equals(schedule.getEpisodeNumber(), 299d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko program.getEpisodeNumber())) { 300d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // The episode number has been changed. 301d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (seriesRecordingForOldSchedule != null) { 302d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko seriesRecordingsToUpdate.add(seriesRecordingForOldSchedule); 303d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 304d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 305d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } else if (seriesRecordingForOldSchedule != null) { 306d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // Old program belongs to a series but the new one doesn't. 307d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko seriesRecordingsToUpdate.add(seriesRecordingForOldSchedule); 308d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 309d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // Change start time only when the recording start time has not passed. 310d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko boolean needToChangeStartTime = schedule.getStartTimeMs() > currentTimeMs 311d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko && program.getStartTimeUtcMillis() != schedule.getStartTimeMs(); 31265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko if (needToChangeStartTime) { 313d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko builder.setStartTimeMs(program.getStartTimeUtcMillis()); 314d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko needUpdate = true; 315d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 316d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (needUpdate || schedule.getEndTimeMs() != program.getEndTimeUtcMillis() 31765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko || !Objects.equals(schedule.getSeasonNumber(), program.getSeasonNumber()) 31865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko || !Objects.equals(schedule.getEpisodeNumber(), program.getEpisodeNumber()) 31965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko || !Objects.equals(schedule.getEpisodeTitle(), program.getEpisodeTitle()) 32065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko || !Objects.equals(schedule.getProgramDescription(), 32165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko program.getDescription()) 32265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko || !Objects.equals(schedule.getProgramLongDescription(), 32365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko program.getLongDescription()) 32465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko || !Objects.equals(schedule.getProgramPosterArtUri(), 32565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko program.getPosterArtUri()) 32665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko || !Objects.equals(schedule.getProgramThumbnailUri(), 32765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko program.getThumbnailUri())) { 32865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mDataManager.updateScheduledRecording(builder.build()); 32965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 330d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (!seriesRecordingsToUpdate.isEmpty()) { 331d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko // The series recordings will be updated after it's resumed. 332d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mSeriesRecordingScheduler.updateSchedules(seriesRecordingsToUpdate); 333d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 33465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 33565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 33665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 33765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 338d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko private class QueryProgramTask extends AsyncQueryProgramTask { 33965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private final long mProgramId; 34065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 341d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko QueryProgramTask(long programId) { 34265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko super(mContext.getContentResolver(), programId); 34365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mProgramId = programId; 34465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 34565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 34665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Override 34765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko protected void onCancelled(Program program) { 348d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (mQueryProgramTask == this) { 349d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mQueryProgramTask = null; 350d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 35165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko startNextUpdateIfNeeded(); 35265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 35365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 35465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko @Override 35565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko protected void onPostExecute(Program program) { 356d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko if (mQueryProgramTask == this) { 357d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko mQueryProgramTask = null; 358d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko } 35965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko handleUpdateProgram(program, mProgramId); 36065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko startNextUpdateIfNeeded(); 36165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 36265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 36365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko} 364