1816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko/*
2816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Copyright (C) 2015 The Android Open Source Project
3816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko *
4816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Licensed under the Apache License, Version 2.0 (the "License");
5816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * you may not use this file except in compliance with the License.
6816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * You may obtain a copy of the License at
7816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko *
8816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko *      http://www.apache.org/licenses/LICENSE-2.0
9816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko *
10816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Unless required by applicable law or agreed to in writing, software
11816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * distributed under the License is distributed on an "AS IS" BASIS,
12816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * See the License for the specific language governing permissions and
14816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * limitations under the License.
15816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */
16816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
17816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkopackage com.android.tv.data;
18816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
19816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.content.ContentResolver;
20816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.content.ContentValues;
21816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.content.Context;
227d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalkoimport android.content.SharedPreferences;
237d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalkoimport android.content.SharedPreferences.Editor;
246ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport android.content.res.AssetFileDescriptor;
25816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.database.ContentObserver;
266ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport android.database.sqlite.SQLiteException;
27816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.media.tv.TvContract;
28816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.media.tv.TvContract.Channels;
29816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.media.tv.TvInputManager.TvInputCallback;
306ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport android.os.AsyncTask;
31816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.os.Handler;
32816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.os.Looper;
33816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.os.Message;
346ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport android.support.annotation.AnyThread;
35ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkoimport android.support.annotation.MainThread;
3607b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalkoimport android.support.annotation.NonNull;
37816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.support.annotation.VisibleForTesting;
382e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalkoimport android.util.ArraySet;
39816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.util.Log;
40816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.util.MutableInt;
41816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
42ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkoimport com.android.tv.common.SharedPreferencesUtils;
432e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalkoimport com.android.tv.common.SoftPreconditions;
4407b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalkoimport com.android.tv.common.WeakHandler;
45816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.util.AsyncDbTask;
467d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalkoimport com.android.tv.util.PermissionUtils;
47816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.util.TvInputManagerHelper;
48816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.util.Utils;
49816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
506ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport java.io.IOException;
51816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.ArrayList;
52816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Collections;
53816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.HashMap;
54816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.HashSet;
55816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.List;
56816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Map;
57816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Set;
5865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.concurrent.CopyOnWriteArraySet;
59816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
60816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko/**
61816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * The class to manage channel data.
62816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Basic features: reading channel list and each channel's current program, and updating
63816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * the values of {@link Channels#COLUMN_BROWSABLE}, {@link Channels#COLUMN_LOCKED}.
64816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * This class is not thread-safe and under an assumption that its public methods are called in
65816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * only the main thread.
66816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */
676ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko@AnyThread
68816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkopublic class ChannelDataManager {
69816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final String TAG = "ChannelDataManager";
70816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final boolean DEBUG = false;
71816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
72816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private static final int MSG_UPDATE_CHANNELS = 1000;
73816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
74816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private final Context mContext;
75816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private final TvInputManagerHelper mInputManager;
76816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private boolean mStarted;
77816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private boolean mDbLoadFinished;
78816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private QueryAllChannelsTask mChannelsUpdateTask;
79816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private final List<Runnable> mPostRunnablesAfterChannelUpdate = new ArrayList<>();
80816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
8165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final Set<Listener> mListeners = new CopyOnWriteArraySet<>();
826ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    // Use container class to support multi-thread safety. This value can be set only on the main
836ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    // thread.
846ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    volatile private UnmodifiableChannelData mData = new UnmodifiableChannelData();
85816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private final Channel.DefaultComparator mChannelComparator;
86816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
87816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private final Handler mHandler;
88816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private final Set<Long> mBrowsableUpdateChannelIds = new HashSet<>();
89816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private final Set<Long> mLockedUpdateChannelIds = new HashSet<>();
90816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
91816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private final ContentResolver mContentResolver;
92816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private final ContentObserver mChannelObserver;
937d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko    private final boolean mStoreBrowsableInSharedPreferences;
947d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko    private final SharedPreferences mBrowsableSharedPreferences;
95816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
96816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private final TvInputCallback mTvInputCallback = new TvInputCallback() {
97816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        @Override
98816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        public void onInputAdded(String inputId) {
99816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            boolean channelAdded = false;
1006ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            ChannelData data = new ChannelData(mData);
1016ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            for (ChannelWrapper channel : mData.channelWrapperMap.values()) {
102816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (channel.mChannel.getInputId().equals(inputId)) {
103816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    channel.mInputRemoved = false;
1046ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    addChannel(data, channel.mChannel);
105816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    channelAdded = true;
106816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
107816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
108816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (channelAdded) {
1096ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                Collections.sort(data.channels, mChannelComparator);
1106ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                mData = new UnmodifiableChannelData(data);
111ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                notifyChannelListUpdated();
112816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
113816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
114816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
115816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        @Override
116816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        public void onInputRemoved(String inputId) {
117816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            boolean channelRemoved = false;
118816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            ArrayList<ChannelWrapper> removedChannels = new ArrayList<>();
1196ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            for (ChannelWrapper channel : mData.channelWrapperMap.values()) {
120816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (channel.mChannel.getInputId().equals(inputId)) {
121816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    channel.mInputRemoved = true;
122816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    channelRemoved = true;
123816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    removedChannels.add(channel);
124816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
125816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
126816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (channelRemoved) {
1276ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                ChannelData data = new ChannelData();
1286ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                data.channelWrapperMap.putAll(mData.channelWrapperMap);
1296ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                for (ChannelWrapper channelWrapper : data.channelWrapperMap.values()) {
130816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (!channelWrapper.mInputRemoved) {
1316ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        addChannel(data, channelWrapper.mChannel);
132816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
133816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
1346ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                Collections.sort(data.channels, mChannelComparator);
1356ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                mData = new UnmodifiableChannelData(data);
136ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                notifyChannelListUpdated();
137816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                for (ChannelWrapper channel : removedChannels) {
138816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    channel.notifyChannelRemoved();
139816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
140816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
141816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
142816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    };
143816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1446ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    @MainThread
145ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    public ChannelDataManager(Context context, TvInputManagerHelper inputManager) {
146ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        this(context, inputManager, context.getContentResolver());
147816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
148816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
1496ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    @MainThread
150816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @VisibleForTesting
151ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    ChannelDataManager(Context context, TvInputManagerHelper inputManager,
1527d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            ContentResolver contentResolver) {
153816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mContext = context;
154816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mInputManager = inputManager;
155816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mContentResolver = contentResolver;
156816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mChannelComparator = new Channel.DefaultComparator(context, inputManager);
157816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        // Detect duplicate channels while sorting.
158816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mChannelComparator.setDetectDuplicatesEnabled(true);
1597d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko        mHandler = new ChannelDataManagerHandler(this);
160816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mChannelObserver = new ContentObserver(mHandler) {
161816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            @Override
162816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            public void onChange(boolean selfChange) {
163816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (!mHandler.hasMessages(MSG_UPDATE_CHANNELS)) {
164816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    mHandler.sendEmptyMessage(MSG_UPDATE_CHANNELS);
165816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
166816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
167816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        };
1687d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko        mStoreBrowsableInSharedPreferences = !PermissionUtils.hasAccessAllEpg(mContext);
169ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        mBrowsableSharedPreferences = context.getSharedPreferences(
170ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                SharedPreferencesUtils.SHARED_PREF_BROWSABLE, Context.MODE_PRIVATE);
171816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
172816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
173816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    @VisibleForTesting
174816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    ContentObserver getContentObserver() {
175816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return mChannelObserver;
176816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
177816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
178816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
179816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Starts the manager. If data is ready, {@link Listener#onLoadFinished()} will be called.
180816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
1816ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    @MainThread
182816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void start() {
183816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (mStarted) {
184816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return;
185816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
186816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mStarted = true;
187816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        // Should be called directly instead of posting MSG_UPDATE_CHANNELS message to the handler.
188816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        // If not, other DB tasks can be executed before channel loading.
189816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        handleUpdateChannels();
190ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        mContentResolver.registerContentObserver(TvContract.Channels.CONTENT_URI, true,
191ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                mChannelObserver);
192816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mInputManager.addCallback(mTvInputCallback);
193816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
194816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
195816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
196816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Stops the manager. It clears manager states and runs pending DB operations. Added listeners
197816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * aren't automatically removed by this method.
198816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
1996ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    @MainThread
2007d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko    @VisibleForTesting
201816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void stop() {
202816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (!mStarted) {
203816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return;
204816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
205816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mStarted = false;
206816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mDbLoadFinished = false;
207816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
208816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mInputManager.removeCallback(mTvInputCallback);
209816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mContentResolver.unregisterContentObserver(mChannelObserver);
210816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mHandler.removeCallbacksAndMessages(null);
211816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
212816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        clearChannels();
213816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mPostRunnablesAfterChannelUpdate.clear();
214816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (mChannelsUpdateTask != null) {
215816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mChannelsUpdateTask.cancel(true);
216816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mChannelsUpdateTask = null;
217816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
218816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        applyUpdatedValuesToDb();
219816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
220816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
221816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
222816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Adds a {@link Listener}.
223816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
224816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void addListener(Listener listener) {
225ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        if (DEBUG) Log.d(TAG, "addListener " + listener);
226ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        SoftPreconditions.checkNotNull(listener);
227ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        if (listener != null) {
228ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            mListeners.add(listener);
229ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        }
230816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
231816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
232816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
233816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Removes a {@link Listener}.
234816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
235816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void removeListener(Listener listener) {
236ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        if (DEBUG) Log.d(TAG, "removeListener " + listener);
237ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        SoftPreconditions.checkNotNull(listener);
238ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        if (listener != null) {
239ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            mListeners.remove(listener);
240ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        }
241816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
242816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
243816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
244816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Adds a {@link ChannelListener} for a specific channel with the channel ID {@code channelId}.
245816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
246816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void addChannelListener(Long channelId, ChannelListener listener) {
2476ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        ChannelWrapper channelWrapper = mData.channelWrapperMap.get(channelId);
248816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (channelWrapper == null) {
249816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return;
250816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
251816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        channelWrapper.addListener(listener);
252816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
253816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
254816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
255816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Removes a {@link ChannelListener} for a specific channel with the channel ID
256816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * {@code channelId}.
257816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
258816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void removeChannelListener(Long channelId, ChannelListener listener) {
2596ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        ChannelWrapper channelWrapper = mData.channelWrapperMap.get(channelId);
260816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (channelWrapper == null) {
261816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return;
262816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
263816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        channelWrapper.removeListener(listener);
264816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
265816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
266816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
267816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Checks whether data is ready.
268816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
269816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public boolean isDbLoadFinished() {
270816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return mDbLoadFinished;
271816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
272816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
273816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
274816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Returns the number of channels.
275816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
276816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public int getChannelCount() {
2776ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        return mData.channels.size();
278816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
279816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
280816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
281816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Returns a list of channels.
282816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
283816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public List<Channel> getChannelList() {
2846ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        return new ArrayList<>(mData.channels);
285816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
286816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
287816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
288816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Returns a list of browsable channels.
289816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
290816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public List<Channel> getBrowsableChannelList() {
291816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        List<Channel> channels = new ArrayList<>();
2926ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        for (Channel channel : mData.channels) {
293816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (channel.isBrowsable()) {
294816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                channels.add(channel);
295816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
296816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
2972e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        return channels;
298816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
299816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
300816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
301816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Returns the total channel count for a given input.
302816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     *
303816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * @param inputId The ID of the input.
304816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
305816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public int getChannelCountForInput(String inputId) {
3066ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        MutableInt count = mData.channelCountMap.get(inputId);
307816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return count == null ? 0 : count.value;
308816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
309816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
310816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
311d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko     * Checks if the channel exists in DB.
312d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko     *
313d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko     * <p>Note that the channels of the removed inputs can not be obtained from {@link #getChannel}.
314d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko     * In that case this method is used to check if the channel exists in the DB.
315d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko     */
316d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    public boolean doesChannelExistInDb(long channelId) {
3176ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        return mData.channelWrapperMap.get(channelId) != null;
318d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    }
319d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
320d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    /**
321816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Returns true if and only if there exists at least one channel and all channels are hidden.
322816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
323816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public boolean areAllChannelsHidden() {
3246ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        for (Channel channel : mData.channels) {
325816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (channel.isBrowsable()) {
326816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return false;
327816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
328816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
329816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return true;
330816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
331816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
332816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
333816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Gets the channel with the channel ID {@code channelId}.
334816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
335816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public Channel getChannel(Long channelId) {
3366ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        ChannelWrapper channelWrapper = mData.channelWrapperMap.get(channelId);
337816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (channelWrapper == null || channelWrapper.mInputRemoved) {
338816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return null;
339816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
340816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        return channelWrapper.mChannel;
341816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
342816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
343816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
344816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * The value change will be applied to DB when applyPendingDbOperation is called.
345816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
346816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void updateBrowsable(Long channelId, boolean browsable) {
347816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        updateBrowsable(channelId, browsable, false);
348816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
349816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
350816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
351816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * The value change will be applied to DB when applyPendingDbOperation is called.
352816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     *
353816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * @param skipNotifyChannelBrowsableChanged If it's true, {@link Listener
354816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     *        #onChannelBrowsableChanged()} is not called, when this method is called.
355816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     *        {@link #notifyChannelBrowsableChanged} should be directly called, once browsable
356816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     *        update is completed.
357816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
358816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void updateBrowsable(Long channelId, boolean browsable,
359816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            boolean skipNotifyChannelBrowsableChanged) {
3606ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        ChannelWrapper channelWrapper = mData.channelWrapperMap.get(channelId);
361816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (channelWrapper == null) {
362816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return;
363816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
364816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (channelWrapper.mChannel.isBrowsable() != browsable) {
365816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            channelWrapper.mChannel.setBrowsable(browsable);
366816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (browsable == channelWrapper.mBrowsableInDb) {
367816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mBrowsableUpdateChannelIds.remove(channelWrapper.mChannel.getId());
368816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            } else {
369816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mBrowsableUpdateChannelIds.add(channelWrapper.mChannel.getId());
370816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
371816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            channelWrapper.notifyChannelUpdated();
372816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            // When updateBrowsable is called multiple times in a method, we don't need to
373816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            // notify Listener.onChannelBrowsableChanged multiple times but only once. So
374816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            // we send a message instead of directly calling onChannelBrowsableChanged.
375816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (!skipNotifyChannelBrowsableChanged) {
376816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                notifyChannelBrowsableChanged();
377816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
378816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
379816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
380816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
381816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void notifyChannelBrowsableChanged() {
38265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        for (Listener l : mListeners) {
383816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            l.onChannelBrowsableChanged();
384816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
385816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
386816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
387ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    private void notifyChannelListUpdated() {
38865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        for (Listener l : mListeners) {
389ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            l.onChannelListUpdated();
390ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        }
391ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    }
392ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko
393ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    private void notifyLoadFinished() {
39465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        for (Listener l : mListeners) {
395ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko            l.onLoadFinished();
396ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko        }
397ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko    }
398ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko
399816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
400816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Updates channels from DB. Once the update is done, {@code postRunnable} will
401816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * be called.
402816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
403816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void updateChannels(Runnable postRunnable) {
404816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (mChannelsUpdateTask != null) {
405816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mChannelsUpdateTask.cancel(true);
406816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mChannelsUpdateTask = null;
407816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
408816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mPostRunnablesAfterChannelUpdate.add(postRunnable);
409816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (!mHandler.hasMessages(MSG_UPDATE_CHANNELS)) {
410816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mHandler.sendEmptyMessage(MSG_UPDATE_CHANNELS);
411816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
412816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
413816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
414816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
415816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * The value change will be applied to DB when applyPendingDbOperation is called.
416816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
417816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void updateLocked(Long channelId, boolean locked) {
4186ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        ChannelWrapper channelWrapper = mData.channelWrapperMap.get(channelId);
419816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (channelWrapper == null) {
420816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            return;
421816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
422816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (channelWrapper.mChannel.isLocked() != locked) {
423816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            channelWrapper.mChannel.setLocked(locked);
424816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (locked == channelWrapper.mLockedInDb) {
425816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mLockedUpdateChannelIds.remove(channelWrapper.mChannel.getId());
426816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            } else {
427816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mLockedUpdateChannelIds.add(channelWrapper.mChannel.getId());
428816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
429816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            channelWrapper.notifyChannelUpdated();
430816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
431816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
432816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
433816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
434816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Applies the changed values by {@link #updateBrowsable} and {@link #updateLocked}
435816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * to DB.
436816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
437816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public void applyUpdatedValuesToDb() {
4386ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        ChannelData data = mData;
439816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        ArrayList<Long> browsableIds = new ArrayList<>();
440816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        ArrayList<Long> unbrowsableIds = new ArrayList<>();
441816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        for (Long id : mBrowsableUpdateChannelIds) {
4426ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            ChannelWrapper channelWrapper = data.channelWrapperMap.get(id);
443816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (channelWrapper == null) {
444816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                continue;
445816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
446816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (channelWrapper.mChannel.isBrowsable()) {
447816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                browsableIds.add(id);
448816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            } else {
449816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                unbrowsableIds.add(id);
450816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
451816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            channelWrapper.mBrowsableInDb = channelWrapper.mChannel.isBrowsable();
452816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
453816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        String column = TvContract.Channels.COLUMN_BROWSABLE;
4547d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko        if (mStoreBrowsableInSharedPreferences) {
4557d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            Editor editor = mBrowsableSharedPreferences.edit();
4567d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            for (Long id : browsableIds) {
4577d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                editor.putBoolean(getBrowsableKey(getChannel(id)), true);
4587d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            }
4597d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            for (Long id : unbrowsableIds) {
4607d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                editor.putBoolean(getBrowsableKey(getChannel(id)), false);
4617d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            }
4627d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            editor.apply();
4637d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko        } else {
4646ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            if (!browsableIds.isEmpty()) {
4657d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                updateOneColumnValue(column, 1, browsableIds);
4667d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            }
4676ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            if (!unbrowsableIds.isEmpty()) {
4687d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                updateOneColumnValue(column, 0, unbrowsableIds);
4697d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            }
470816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
471816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mBrowsableUpdateChannelIds.clear();
472816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
473816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        ArrayList<Long> lockedIds = new ArrayList<>();
474816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        ArrayList<Long> unlockedIds = new ArrayList<>();
475816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        for (Long id : mLockedUpdateChannelIds) {
4766ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            ChannelWrapper channelWrapper = data.channelWrapperMap.get(id);
477816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (channelWrapper == null) {
478816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                continue;
479816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
480816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (channelWrapper.mChannel.isLocked()) {
481816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                lockedIds.add(id);
482816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            } else {
483816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                unlockedIds.add(id);
484816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
485816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            channelWrapper.mLockedInDb = channelWrapper.mChannel.isLocked();
486816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
487816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        column = TvContract.Channels.COLUMN_LOCKED;
4886ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        if (!lockedIds.isEmpty()) {
489816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            updateOneColumnValue(column, 1, lockedIds);
490816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
4916ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        if (!unlockedIds.isEmpty()) {
492816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            updateOneColumnValue(column, 0, unlockedIds);
493816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
494816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mLockedUpdateChannelIds.clear();
495816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (DEBUG) {
496816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            Log.d(TAG, "applyUpdatedValuesToDb"
497816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    + "\n browsableIds size:" + browsableIds.size()
498816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    + "\n unbrowsableIds size:" + unbrowsableIds.size()
499816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    + "\n lockedIds size:" + lockedIds.size()
500816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    + "\n unlockedIds size:" + unlockedIds.size());
501816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
502816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
503816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
5046ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    @MainThread
5056ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private void addChannel(ChannelData data, Channel channel) {
5066ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        data.channels.add(channel);
507816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        String inputId = channel.getInputId();
5086ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        MutableInt count = data.channelCountMap.get(inputId);
509816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (count == null) {
5106ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            data.channelCountMap.put(inputId, new MutableInt(1));
511816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        } else {
512816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            count.value++;
513816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
514816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
515816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
5166ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    @MainThread
517816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private void clearChannels() {
5186ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        mData = new UnmodifiableChannelData();
519816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
520816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
5216ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    @MainThread
522816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private void handleUpdateChannels() {
523816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        if (mChannelsUpdateTask != null) {
524816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mChannelsUpdateTask.cancel(true);
525816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
526816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mChannelsUpdateTask = new QueryAllChannelsTask(mContentResolver);
527816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        mChannelsUpdateTask.executeOnDbThread();
528816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
529816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
5302e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    /**
5312e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko     * Reloads channel data.
5322e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko     */
5332e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    public void reload() {
5342e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        if (mDbLoadFinished && !mHandler.hasMessages(MSG_UPDATE_CHANNELS)) {
5352e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko            mHandler.sendEmptyMessage(MSG_UPDATE_CHANNELS);
5362e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        }
5372e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko    }
5382e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko
5396ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    /**
5406ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * A listener for ChannelDataManager. The callbacks are called on the main thread.
5416ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     */
542816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public interface Listener {
543816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        /**
544816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * Called when data load is finished.
545816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         */
546816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void onLoadFinished();
547816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
548816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        /**
549816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * Called when channels are added, deleted, or updated. But, when browsable is changed,
550816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * it won't be called. Instead, {@link #onChannelBrowsableChanged} will be called.
551816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         */
552816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void onChannelListUpdated();
553816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
554816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        /**
555816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * Called when browsable of channels are changed.
556816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         */
557816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void onChannelBrowsableChanged();
558816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
559816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
5606ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    /**
5616ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * A listener for individual channel change. The callbacks are called on the main thread.
5626ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     */
563816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    public interface ChannelListener {
564816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        /**
565816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * Called when the channel has been removed in DB.
566816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         */
567816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void onChannelRemoved(Channel channel);
568816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
569816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        /**
570816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         * Called when values of the channel has been changed.
571816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko         */
572816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void onChannelUpdated(Channel channel);
573816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
574816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
575816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private class ChannelWrapper {
5762e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko        final Set<ChannelListener> mChannelListeners = new ArraySet<>();
577816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        final Channel mChannel;
578816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        boolean mBrowsableInDb;
579816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        boolean mLockedInDb;
580816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        boolean mInputRemoved;
581816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
582816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        ChannelWrapper(Channel channel) {
583816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mChannel = channel;
584816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mBrowsableInDb = channel.isBrowsable();
585816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mLockedInDb = channel.isLocked();
586816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mInputRemoved = !mInputManager.hasTvInputInfo(channel.getInputId());
587816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
588816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
589816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void addListener(ChannelListener listener) {
590816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mChannelListeners.add(listener);
591816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
592816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
593816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void removeListener(ChannelListener listener) {
594816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mChannelListeners.remove(listener);
595816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
596816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
597816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void notifyChannelUpdated() {
598816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            for (ChannelListener l : mChannelListeners) {
599816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                l.onChannelUpdated(mChannel);
600816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
601816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
602816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
603816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        void notifyChannelRemoved() {
604816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            for (ChannelListener l : mChannelListeners) {
605816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                l.onChannelRemoved(mChannel);
606816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
607816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
608816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
609816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
6106ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private class CheckChannelLogoExistTask extends AsyncTask<Void, Void, Boolean> {
6116ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        private final Channel mChannel;
6126ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
6136ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        CheckChannelLogoExistTask(Channel channel) {
6146ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            mChannel = channel;
6156ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
6166ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
6176ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        @Override
6186ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        protected Boolean doInBackground(Void... params) {
6196ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            try (AssetFileDescriptor f = mContext.getContentResolver().openAssetFileDescriptor(
6206ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        TvContract.buildChannelLogoUri(mChannel.getId()), "r")) {
6216ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                return true;
6226ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            } catch (SQLiteException | IOException | NullPointerException e) {
6236ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                // File not found or asset file not found.
6246ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            }
6256ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            return false;
6266ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
6276ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
6286ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        @Override
6296ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        protected void onPostExecute(Boolean result) {
6306ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            ChannelWrapper wrapper = mData.channelWrapperMap.get(mChannel.getId());
6316ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            if (wrapper != null) {
6326ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                wrapper.mChannel.setChannelLogoExist(result);
6336ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            }
6346ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
6356ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    }
6366ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
637816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private final class QueryAllChannelsTask extends AsyncDbTask.AsyncChannelQueryTask {
638816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
6396ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        QueryAllChannelsTask(ContentResolver contentResolver) {
640816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            super(contentResolver);
641816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
642816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
643816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        @Override
644816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        protected void onPostExecute(List<Channel> channels) {
645816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mChannelsUpdateTask = null;
646816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (channels == null) {
647816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (DEBUG) Log.e(TAG, "onPostExecute with null channels");
648816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                return;
649816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
6506ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            ChannelData data = new ChannelData();
6516ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            data.channelWrapperMap.putAll(mData.channelWrapperMap);
6526ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            Set<Long> removedChannelIds = new HashSet<>(data.channelWrapperMap.keySet());
653816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            List<ChannelWrapper> removedChannelWrappers = new ArrayList<>();
654816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            List<ChannelWrapper> updatedChannelWrappers = new ArrayList<>();
655816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
656816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            boolean channelAdded = false;
657816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            boolean channelUpdated = false;
658816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            boolean channelRemoved = false;
6597d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            Map<String, ?> deletedBrowsableMap = null;
6607d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            if (mStoreBrowsableInSharedPreferences) {
6617d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                deletedBrowsableMap = new HashMap<>(mBrowsableSharedPreferences.getAll());
6627d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            }
663816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            for (Channel channel : channels) {
6647d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                if (mStoreBrowsableInSharedPreferences) {
6657d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                    String browsableKey = getBrowsableKey(channel);
6667d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                    channel.setBrowsable(mBrowsableSharedPreferences.getBoolean(browsableKey,
6677d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                            false));
6687d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                    deletedBrowsableMap.remove(browsableKey);
6697d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                }
670816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                long channelId = channel.getId();
671816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                boolean newlyAdded = !removedChannelIds.remove(channelId);
672816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                ChannelWrapper channelWrapper;
673816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (newlyAdded) {
6746ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    new CheckChannelLogoExistTask(channel)
6756ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                            .executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
676816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    channelWrapper = new ChannelWrapper(channel);
6776ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    data.channelWrapperMap.put(channel.getId(), channelWrapper);
678816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (!channelWrapper.mInputRemoved) {
679816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        channelAdded = true;
680816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
681816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                } else {
6826ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    channelWrapper = data.channelWrapperMap.get(channelId);
683816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    if (!channelWrapper.mChannel.hasSameReadOnlyInfo(channel)) {
684816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        // Channel data updated
685816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        Channel oldChannel = channelWrapper.mChannel;
686816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        // We assume that mBrowsable and mLocked are controlled by only TV app.
687816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        // The values for mBrowsable and mLocked are updated when
688816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        // {@link #applyUpdatedValuesToDb} is called. Therefore, the value
689816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        // between DB and ChannelDataManager could be different for a while.
690816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        // Therefore, we'll keep the values in ChannelDataManager.
691816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        channel.setBrowsable(oldChannel.isBrowsable());
692816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        channel.setLocked(oldChannel.isLocked());
6936ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        channelWrapper.mChannel.copyFrom(channel);
694816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        if (!channelWrapper.mInputRemoved) {
695816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            channelUpdated = true;
696816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                            updatedChannelWrappers.add(channelWrapper);
697816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                        }
698816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    }
699816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
700816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
7017d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            if (mStoreBrowsableInSharedPreferences && !deletedBrowsableMap.isEmpty()
7027d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                    && PermissionUtils.hasReadTvListings(mContext)) {
7037d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                // If hasReadTvListings(mContext) is false, the given channel list would
7047d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                // empty. In this case, we skip the browsable data clean up process.
7057d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                Editor editor = mBrowsableSharedPreferences.edit();
7067d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                for (String key : deletedBrowsableMap.keySet()) {
7077d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                    if (DEBUG) Log.d(TAG, "remove key: " + key);
7087d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                    editor.remove(key);
7097d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                }
7107d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko                editor.apply();
7117d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            }
712816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
713816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            for (long id : removedChannelIds) {
7146ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                ChannelWrapper channelWrapper = data.channelWrapperMap.remove(id);
715816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (!channelWrapper.mInputRemoved) {
716816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    channelRemoved = true;
717816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                    removedChannelWrappers.add(channelWrapper);
718816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
719816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
7206ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            for (ChannelWrapper channelWrapper : data.channelWrapperMap.values()) {
721816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                if (!channelWrapper.mInputRemoved) {
7226ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    addChannel(data, channelWrapper.mChannel);
723816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                }
724816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
7256ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            Collections.sort(data.channels, mChannelComparator);
7266ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            mData = new UnmodifiableChannelData(data);
727816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
728816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            if (!mDbLoadFinished) {
729816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mDbLoadFinished = true;
730ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                notifyLoadFinished();
731816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            } else if (channelAdded || channelUpdated || channelRemoved) {
732ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko                notifyChannelListUpdated();
733816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
734816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            for (ChannelWrapper channelWrapper : removedChannelWrappers) {
735816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                channelWrapper.notifyChannelRemoved();
736816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
737816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            for (ChannelWrapper channelWrapper : updatedChannelWrappers) {
738816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                channelWrapper.notifyChannelUpdated();
739816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
740816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            for (Runnable r : mPostRunnablesAfterChannelUpdate) {
741816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                r.run();
742816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
743816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            mPostRunnablesAfterChannelUpdate.clear();
744816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        }
745816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
746816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko
747816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    /**
748816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * Updates a column {@code columnName} of DB table {@code uri} with the value
749816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * {@code columnValue}. The selective rows in the ID list {@code ids} will be updated.
750816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     * The DB operations will run on {@link AsyncDbTask#getExecutor()}.
751816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko     */
752816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    private void updateOneColumnValue(
753816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            final String columnName, final int columnValue, final List<Long> ids) {
7547d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko        if (!PermissionUtils.hasAccessAllEpg(mContext)) {
7557d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            return;
7567d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko        }
7576ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        AsyncDbTask.executeOnDbThread(new Runnable() {
758816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            @Override
759816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            public void run() {
760816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                String selection = Utils.buildSelectionForIds(Channels._ID, ids);
761816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                ContentValues values = new ContentValues();
762816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                values.put(columnName, columnValue);
763816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko                mContentResolver.update(TvContract.Channels.CONTENT_URI, values, selection, null);
764816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko            }
765816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko        });
766816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko    }
76707b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko
7687d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko    private String getBrowsableKey(Channel channel) {
7697d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko        return channel.getInputId() + "|" + channel.getId();
7707d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko    }
7717d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko
7726ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    @MainThread
77307b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko    private static class ChannelDataManagerHandler extends WeakHandler<ChannelDataManager> {
7747d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko        public ChannelDataManagerHandler(ChannelDataManager channelDataManager) {
7757d67089aa1e9aa2123c3cd2f386d7019a1544db1Nick Chalko            super(Looper.getMainLooper(), channelDataManager);
77607b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko        }
77707b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko
77807b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko        @Override
77907b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko        public void handleMessage(Message msg, @NonNull ChannelDataManager channelDataManager) {
78007b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko            if (msg.what == MSG_UPDATE_CHANNELS) {
78107b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko                channelDataManager.handleUpdateChannels();
78207b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko            }
78307b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko        }
78407b043dc3db83d6d20f0e8513b946830ab00e37bNick Chalko    }
7856ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
7866ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    /**
7876ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * Container class which includes channel data that needs to be synced. This class is
7886ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * modifiable and used for changing channel data.
7896ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * e.g. TvInputCallback, or AsyncDbTask.onPostExecute.
7906ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     */
7916ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    @MainThread
7926ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static class ChannelData {
7936ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        final Map<Long, ChannelWrapper> channelWrapperMap;
7946ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        final Map<String, MutableInt> channelCountMap;
7956ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        final List<Channel> channels;
7966ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
7976ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        ChannelData() {
7986ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            channelWrapperMap = new HashMap<>();
7996ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            channelCountMap = new HashMap<>();
8006ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            channels = new ArrayList<>();
8016ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
8026ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
8036ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        ChannelData(ChannelData data) {
8046ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            channelWrapperMap = new HashMap<>(data.channelWrapperMap);
8056ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            channelCountMap = new HashMap<>(data.channelCountMap);
8066ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            channels = new ArrayList<>(data.channels);
8076ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
8086ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
8096ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        ChannelData(Map<Long, ChannelWrapper> channelWrapperMap,
8106ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                Map<String, MutableInt> channelCountMap, List<Channel> channels) {
8116ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            this.channelWrapperMap = channelWrapperMap;
8126ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            this.channelCountMap = channelCountMap;
8136ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            this.channels = channels;
8146ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
8156ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    }
8166ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
8176ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    /** Unmodifiable channel data. */
8186ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    @MainThread
8196ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static class UnmodifiableChannelData extends ChannelData {
8206ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        UnmodifiableChannelData() {
8216ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            super(Collections.unmodifiableMap(new HashMap<>()),
8226ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    Collections.unmodifiableMap(new HashMap<>()),
8236ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    Collections.unmodifiableList(new ArrayList<>()));
8246ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
8256ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
8266ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        UnmodifiableChannelData(ChannelData data) {
8276ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            super(Collections.unmodifiableMap(data.channelWrapperMap),
8286ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    Collections.unmodifiableMap(data.channelCountMap),
8296ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    Collections.unmodifiableList(data.channels));
8306ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
8316ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    }
832816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko}
833