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