11abddd9f6225298066094e20a6c29061b6af4590Nick Chalko/*
21abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * Copyright (C) 2015 The Android Open Source Project
31abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *
41abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * Licensed under the Apache License, Version 2.0 (the "License");
51abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * you may not use this file except in compliance with the License.
61abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * You may obtain a copy of the License at
71abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *
81abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *      http://www.apache.org/licenses/LICENSE-2.0
91abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *
101abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * Unless required by applicable law or agreed to in writing, software
111abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * distributed under the License is distributed on an "AS IS" BASIS,
121abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
131abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * See the License for the specific language governing permissions and
141abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * limitations under the License.
151abddd9f6225298066094e20a6c29061b6af4590Nick Chalko */
161abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
171abddd9f6225298066094e20a6c29061b6af4590Nick Chalkopackage com.android.tv.dvr;
181abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
1948dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.annotation.TargetApi;
2048dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.content.ContentResolver;
2148dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.content.ContentUris;
221abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport android.content.Context;
2348dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.database.ContentObserver;
2448dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.database.Cursor;
2548dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.media.tv.TvContract;
2648dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.net.Uri;
2748dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.os.AsyncTask;
2848dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.os.Build;
2948dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.os.Handler;
3048dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.os.Looper;
31ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkoimport android.support.annotation.MainThread;
32ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkoimport android.support.annotation.Nullable;
331abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport android.support.annotation.VisibleForTesting;
3448dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.util.ArraySet;
35ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkoimport android.util.Log;
361abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport android.util.Range;
371abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
3848dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport com.android.tv.common.SoftPreconditions;
3948dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport com.android.tv.common.recording.RecordedProgram;
4048dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport com.android.tv.dvr.ScheduledRecording.RecordingState;
41ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkoimport com.android.tv.dvr.provider.AsyncDvrDbTask;
421abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport com.android.tv.dvr.provider.AsyncDvrDbTask.AsyncDvrQueryTask;
4348dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport com.android.tv.util.AsyncDbTask;
4448dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport com.android.tv.util.Clock;
451abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
461abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport java.util.ArrayList;
471abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport java.util.Collections;
48ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkoimport java.util.HashMap;
4948dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport java.util.Iterator;
501abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport java.util.List;
5148dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport java.util.Set;
521abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
531abddd9f6225298066094e20a6c29061b6af4590Nick Chalko/**
541abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * DVR Data manager to handle recordings and schedules.
551abddd9f6225298066094e20a6c29061b6af4590Nick Chalko */
56ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko@MainThread
5748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho@TargetApi(Build.VERSION_CODES.N)
581abddd9f6225298066094e20a6c29061b6af4590Nick Chalkopublic class DvrDataManagerImpl extends BaseDvrDataManager {
59ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    private static final String TAG = "DvrDataManagerImpl";
6048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private static final boolean DEBUG = false;
61ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko
6248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private final HashMap<Long, ScheduledRecording> mScheduledRecordings = new HashMap<>();
6348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private final HashMap<Long, ScheduledRecording> mProgramId2ScheduledRecordings =
6448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            new HashMap<>();
6548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private final HashMap<Long, RecordedProgram> mRecordedPrograms = new HashMap<>();
661abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
6748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private final Context mContext;
6848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
6948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private final ContentObserver mContentObserver = new ContentObserver(mMainThreadHandler) {
7048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
7148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        @Override
7248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        public void onChange(boolean selfChange) {
7348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            onChange(selfChange, null);
7448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
7548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
7648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        @Override
7748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        public void onChange(boolean selfChange, @Nullable final Uri uri) {
7848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            if (uri == null) {
7948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                // TODO reload everything.
8048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            }
8148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            AsyncRecordedProgramQueryTask task = new AsyncRecordedProgramQueryTask(
8248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                    mContext.getContentResolver(), uri);
8348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            task.executeOnDbThread();
8448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mPendingTasks.add(task);
8548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
8648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    };
8748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
8848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private void onObservedChange(Uri uri, RecordedProgram recordedProgram) {
8948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        long id = ContentUris.parseId(uri);
9048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (DEBUG) {
9148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            Log.d(TAG, "changed recorded program #" + id + " to " + recordedProgram);
9248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
9348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (recordedProgram == null) {
9448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            RecordedProgram old = mRecordedPrograms.remove(id);
9548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            if (old != null) {
9648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                notifyRecordedProgramRemoved(old);
9748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            } else {
9848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                Log.w(TAG, "Could not find old version of deleted program #" + id);
9948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            }
10048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        } else {
10148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            RecordedProgram old = mRecordedPrograms.put(id, recordedProgram);
10248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            if (old == null) {
10348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                notifyRecordedProgramAdded(recordedProgram);
10448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            } else {
10548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                notifyRecordedProgramChanged(recordedProgram);
10648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            }
10748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
10848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
10948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
11048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private boolean mDvrLoadFinished;
11148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private boolean mRecordedProgramLoadFinished;
11248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private final Set<AsyncTask> mPendingTasks = new ArraySet<>();
11348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
11448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    public DvrDataManagerImpl(Context context, Clock clock) {
11548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        super(context, clock);
1161abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        mContext = context;
1171abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
1181abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
1191abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public void start() {
12048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        AsyncDvrQueryTask mDvrQueryTask = new AsyncDvrQueryTask(mContext) {
12148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
1221abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            @Override
12348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            protected void onCancelled(List<ScheduledRecording> scheduledRecordings) {
12448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                mPendingTasks.remove(this);
12548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            }
12648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
12748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            @Override
12848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            protected void onPostExecute(List<ScheduledRecording> result) {
12948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                mPendingTasks.remove(this);
13048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                mDvrLoadFinished = true;
13148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                for (ScheduledRecording r : result) {
13248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                    mScheduledRecordings.put(r.getId(), r);
133ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                }
1341abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
1351abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        };
13648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mDvrQueryTask.executeOnDbThread();
13748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mPendingTasks.add(mDvrQueryTask);
13848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        AsyncRecordedProgramsQueryTask mRecordedProgramQueryTask =
13948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                new AsyncRecordedProgramsQueryTask(mContext.getContentResolver());
14048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mRecordedProgramQueryTask.executeOnDbThread();
14148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        ContentResolver cr = mContext.getContentResolver();
14248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        cr.registerContentObserver(TvContract.RecordedPrograms.CONTENT_URI, true, mContentObserver);
1431abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
1441abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
1451abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public void stop() {
14648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        ContentResolver cr = mContext.getContentResolver();
14748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        cr.unregisterContentObserver(mContentObserver);
14848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        Iterator<AsyncTask> i = mPendingTasks.iterator();
14948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        while (i.hasNext()) {
15048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            AsyncTask task = i.next();
15148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            i.remove();
15248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            task.cancel(true);
1531abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
1541abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
1551abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
1561abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    @Override
1571abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public boolean isInitialized() {
15848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        return mDvrLoadFinished && mRecordedProgramLoadFinished;
1591abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
1601abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
16148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private List<ScheduledRecording> getScheduledRecordingsPrograms() {
16248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (!mDvrLoadFinished) {
1631abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            return Collections.emptyList();
1641abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
16548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        ArrayList<ScheduledRecording> list = new ArrayList<>(mScheduledRecordings.size());
16648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        list.addAll(mScheduledRecordings.values());
16748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        Collections.sort(list, ScheduledRecording.START_TIME_COMPARATOR);
16848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        return list;
1693a72b93e554bd22a5c64e71a6956d9604ce05108Youngsang Cho    }
1703a72b93e554bd22a5c64e71a6956d9604ce05108Youngsang Cho
1713a72b93e554bd22a5c64e71a6956d9604ce05108Youngsang Cho    @Override
17248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    public List<RecordedProgram> getRecordedPrograms() {
17348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (!mRecordedProgramLoadFinished) {
17448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            return Collections.emptyList();
17548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
17648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        return new ArrayList<>(mRecordedPrograms.values());
1771abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
1781abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
1791abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    @Override
18048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    public List<ScheduledRecording> getAllScheduledRecordings() {
18148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        return new ArrayList<>(mScheduledRecordings.values());
1821abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
1831abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
18448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    protected List<ScheduledRecording> getRecordingsWithState(@RecordingState int state) {
18548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        List<ScheduledRecording> result = new ArrayList<>();
18648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        for (ScheduledRecording r : mScheduledRecordings.values()) {
1871abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (r.getState() == state) {
1881abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                result.add(r);
1891abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
1901abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
1911abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return result;
1921abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
1931abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
1941abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    @Override
1951abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public List<SeasonRecording> getSeasonRecordings() {
1961abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // If we return dummy data here, we can implement UI part independently.
1971abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return Collections.emptyList();
1981abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
1991abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
2001abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    @Override
2011abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public long getNextScheduledStartTimeAfter(long startTime) {
20248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        return getNextStartTimeAfter(getScheduledRecordingsPrograms(), startTime);
2031abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
2041abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
2051abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    @VisibleForTesting
20648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    static long getNextStartTimeAfter(List<ScheduledRecording> scheduledRecordings, long startTime) {
2071abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int start = 0;
20848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        int end = scheduledRecordings.size() - 1;
2091abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        while (start <= end) {
2101abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int mid = (start + end) / 2;
21148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            if (scheduledRecordings.get(mid).getStartTimeMs() <= startTime) {
2121abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                start = mid + 1;
2131abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            } else {
2141abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                end = mid - 1;
2151abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
2161abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
21748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        return start < scheduledRecordings.size() ? scheduledRecordings.get(start).getStartTimeMs()
2181abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                : NEXT_START_TIME_NOT_FOUND;
2191abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
2201abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
2211abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    @Override
22248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    public List<ScheduledRecording> getRecordingsThatOverlapWith(Range<Long> period) {
22348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        List<ScheduledRecording> result = new ArrayList<>();
22448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        for (ScheduledRecording r : mScheduledRecordings.values()) {
2251abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (r.isOverLapping(period)) {
2261abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                result.add(r);
2271abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
2281abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
2291abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return result;
2301abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
2311abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
232ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    @Nullable
233ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    @Override
23448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    public ScheduledRecording getScheduledRecording(long recordingId) {
23548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (mDvrLoadFinished) {
23648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            return mScheduledRecordings.get(recordingId);
23748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
23848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        return null;
23948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
24048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
24148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    @Nullable
24248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    @Override
24348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    public ScheduledRecording getScheduledRecordingForProgramId(long programId) {
24448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (mDvrLoadFinished) {
24548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            return mProgramId2ScheduledRecordings.get(programId);
246ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        }
247ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        return null;
248ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    }
249ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko
25048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    @Nullable
25148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    @Override
25248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    public RecordedProgram getRecordedProgram(long recordingId) {
25348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        return mRecordedPrograms.get(recordingId);
25448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
25548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
2561abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    @Override
25748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    public void addScheduledRecording(final ScheduledRecording scheduledRecording) {
258ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        new AsyncDvrDbTask.AsyncAddRecordingTask(mContext) {
259ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            @Override
26048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            protected void onPostExecute(List<ScheduledRecording> scheduledRecordings) {
26148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                super.onPostExecute(scheduledRecordings);
26248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                SoftPreconditions.checkArgument(scheduledRecordings.size() == 1);
26348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                for (ScheduledRecording r : scheduledRecordings) {
264ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                    if (r.getId() != -1) {
26548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                        mScheduledRecordings.put(r.getId(), r);
26648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                        if (r.getProgramId() != ScheduledRecording.ID_NOT_SET) {
26748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                            mProgramId2ScheduledRecordings.put(r.getProgramId(), r);
26848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                        }
26948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                        notifyScheduledRecordingAdded(r);
270ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                    } else {
271ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                        Log.w(TAG, "Error adding " + r);
272ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                    }
273ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                }
274ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko
275ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            }
27648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }.executeOnDbThread(scheduledRecording);
277ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    }
2781abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
2791abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    @Override
2801abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public void addSeasonRecording(SeasonRecording seasonRecording) { }
2811abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
2821abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    @Override
28348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    public void removeScheduledRecording(final ScheduledRecording scheduledRecording) {
284ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        new AsyncDvrDbTask.AsyncDeleteRecordingTask(mContext) {
285ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            @Override
286ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            protected void onPostExecute(List<Integer> counts) {
287ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                super.onPostExecute(counts);
288ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                SoftPreconditions.checkArgument(counts.size() == 1);
289ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                for (Integer c : counts) {
290ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                    if (c == 1) {
29148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                        mScheduledRecordings.remove(scheduledRecording.getId());
29248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                        if (scheduledRecording.getProgramId() != ScheduledRecording.ID_NOT_SET) {
29348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                            mProgramId2ScheduledRecordings
29448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                                    .remove(scheduledRecording.getProgramId());
29548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                        }
296ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                        //TODO change to notifyRecordingUpdated
29748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                        notifyScheduledRecordingRemoved(scheduledRecording);
298ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                    } else {
29948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                        Log.w(TAG, "Error removing " + scheduledRecording);
300ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                    }
301ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                }
302ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko
303ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            }
30448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }.executeOnDbThread(scheduledRecording);
305ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    }
3061abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
3071abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    @Override
3081abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public void removeSeasonSchedule(SeasonRecording seasonSchedule) { }
3091abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
3101abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    @Override
31148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    public void updateScheduledRecording(final ScheduledRecording scheduledRecording) {
312ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        new AsyncDvrDbTask.AsyncUpdateRecordingTask(mContext) {
313ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            @Override
314ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            protected void onPostExecute(List<Integer> counts) {
315ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                super.onPostExecute(counts);
316ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                SoftPreconditions.checkArgument(counts.size() == 1);
317ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                for (Integer c : counts) {
318ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                    if (c == 1) {
31948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                        ScheduledRecording oldScheduledRecording = mScheduledRecordings
32048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                                .put(scheduledRecording.getId(), scheduledRecording);
32148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                        long programId = scheduledRecording.getProgramId();
32248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                        if (oldScheduledRecording != null
32348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                                && oldScheduledRecording.getProgramId() != programId
32448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                                && oldScheduledRecording.getProgramId()
32548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                                != ScheduledRecording.ID_NOT_SET) {
32648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                            ScheduledRecording oldValueForProgramId = mProgramId2ScheduledRecordings
32748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                                    .get(oldScheduledRecording.getProgramId());
32848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                            if (oldValueForProgramId.getId() == scheduledRecording.getId()) {
32948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                                //Only remove the old ScheduledRecording if it has the same ID as
33048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                                // the new one.
33148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                                mProgramId2ScheduledRecordings
33248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                                        .remove(oldScheduledRecording.getProgramId());
33348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                            }
33448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                        }
33548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                        if (programId != ScheduledRecording.ID_NOT_SET) {
33648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                            mProgramId2ScheduledRecordings.put(programId, scheduledRecording);
33748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                        }
338ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                        //TODO change to notifyRecordingUpdated
33948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                        notifyScheduledRecordingStatusChanged(scheduledRecording);
340ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                    } else {
34148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                        Log.w(TAG, "Error updating " + scheduledRecording);
342ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                    }
343ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                }
34448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            }
34548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }.executeOnDbThread(scheduledRecording);
34648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
34748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
34848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private final class AsyncRecordedProgramsQueryTask
34948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            extends AsyncDbTask.AsyncQueryListTask<RecordedProgram> {
35048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        public AsyncRecordedProgramsQueryTask(ContentResolver contentResolver) {
35148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            super(contentResolver, TvContract.RecordedPrograms.CONTENT_URI,
35248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                    RecordedProgram.PROJECTION, null, null, null);
35348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
35448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
35548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        @Override
35648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        protected RecordedProgram fromCursor(Cursor c) {
35748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            return RecordedProgram.fromCursor(c);
35848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
35948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
36048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        @Override
36148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        protected void onCancelled(List<RecordedProgram> scheduledRecordings) {
36248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mPendingTasks.remove(this);
36348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
364ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko
36548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        @Override
36648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        protected void onPostExecute(List<RecordedProgram> result) {
36748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mPendingTasks.remove(this);
36848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mRecordedProgramLoadFinished = true;
36948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            if (result != null) {
37048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                for (RecordedProgram r : result) {
37148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                    mRecordedPrograms.put(r.getId(), r);
37248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                }
373ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            }
37448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
37548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
37648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
37748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private final class AsyncRecordedProgramQueryTask
37848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            extends AsyncDbTask.AsyncQueryItemTask<RecordedProgram> {
37948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
38048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        private final Uri mUri;
38148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
38248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        public AsyncRecordedProgramQueryTask(ContentResolver contentResolver, Uri uri) {
38348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            super(contentResolver, uri, RecordedProgram.PROJECTION, null, null, null);
38448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mUri = uri;
38548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
38648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
38748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        @Override
38848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        protected RecordedProgram fromCursor(Cursor c) {
38948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            return RecordedProgram.fromCursor(c);
39048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
39148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
39248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        @Override
39348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        protected void onCancelled(RecordedProgram recordedProgram) {
39448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mPendingTasks.remove(this);
39548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
39648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
39748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        @Override
39848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        protected void onPostExecute(RecordedProgram recordedProgram) {
39948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mPendingTasks.remove(this);
40048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            onObservedChange(mUri, recordedProgram);
40148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
402ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    }
4031abddd9f6225298066094e20a6c29061b6af4590Nick Chalko}
404