aboutsummaryrefslogtreecommitdiff
path: root/src/com/android/tv/data/ChannelDataManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/tv/data/ChannelDataManager.java')
-rw-r--r--src/com/android/tv/data/ChannelDataManager.java200
1 files changed, 147 insertions, 53 deletions
diff --git a/src/com/android/tv/data/ChannelDataManager.java b/src/com/android/tv/data/ChannelDataManager.java
index 6f9ea6d7..6f93fbd1 100644
--- a/src/com/android/tv/data/ChannelDataManager.java
+++ b/src/com/android/tv/data/ChannelDataManager.java
@@ -21,13 +21,17 @@ import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
+import android.content.res.AssetFileDescriptor;
import android.database.ContentObserver;
+import android.database.sqlite.SQLiteException;
import android.media.tv.TvContract;
import android.media.tv.TvContract.Channels;
import android.media.tv.TvInputManager.TvInputCallback;
+import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.support.annotation.AnyThread;
import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
@@ -43,6 +47,7 @@ import com.android.tv.util.PermissionUtils;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -59,7 +64,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
* This class is not thread-safe and under an assumption that its public methods are called in
* only the main thread.
*/
-@MainThread
+@AnyThread
public class ChannelDataManager {
private static final String TAG = "ChannelDataManager";
private static final boolean DEBUG = false;
@@ -74,10 +79,10 @@ public class ChannelDataManager {
private final List<Runnable> mPostRunnablesAfterChannelUpdate = new ArrayList<>();
private final Set<Listener> mListeners = new CopyOnWriteArraySet<>();
- private final Map<Long, ChannelWrapper> mChannelWrapperMap = new HashMap<>();
- private final Map<String, MutableInt> mChannelCountMap = new HashMap<>();
+ // Use container class to support multi-thread safety. This value can be set only on the main
+ // thread.
+ volatile private UnmodifiableChannelData mData = new UnmodifiableChannelData();
private final Channel.DefaultComparator mChannelComparator;
- private final List<Channel> mChannels = new ArrayList<>();
private final Handler mHandler;
private final Set<Long> mBrowsableUpdateChannelIds = new HashSet<>();
@@ -92,15 +97,17 @@ public class ChannelDataManager {
@Override
public void onInputAdded(String inputId) {
boolean channelAdded = false;
- for (ChannelWrapper channel : mChannelWrapperMap.values()) {
+ ChannelData data = new ChannelData(mData);
+ for (ChannelWrapper channel : mData.channelWrapperMap.values()) {
if (channel.mChannel.getInputId().equals(inputId)) {
channel.mInputRemoved = false;
- addChannel(channel.mChannel);
+ addChannel(data, channel.mChannel);
channelAdded = true;
}
}
if (channelAdded) {
- Collections.sort(mChannels, mChannelComparator);
+ Collections.sort(data.channels, mChannelComparator);
+ mData = new UnmodifiableChannelData(data);
notifyChannelListUpdated();
}
}
@@ -109,7 +116,7 @@ public class ChannelDataManager {
public void onInputRemoved(String inputId) {
boolean channelRemoved = false;
ArrayList<ChannelWrapper> removedChannels = new ArrayList<>();
- for (ChannelWrapper channel : mChannelWrapperMap.values()) {
+ for (ChannelWrapper channel : mData.channelWrapperMap.values()) {
if (channel.mChannel.getInputId().equals(inputId)) {
channel.mInputRemoved = true;
channelRemoved = true;
@@ -117,13 +124,15 @@ public class ChannelDataManager {
}
}
if (channelRemoved) {
- clearChannels();
- for (ChannelWrapper channelWrapper : mChannelWrapperMap.values()) {
+ ChannelData data = new ChannelData();
+ data.channelWrapperMap.putAll(mData.channelWrapperMap);
+ for (ChannelWrapper channelWrapper : data.channelWrapperMap.values()) {
if (!channelWrapper.mInputRemoved) {
- addChannel(channelWrapper.mChannel);
+ addChannel(data, channelWrapper.mChannel);
}
}
- Collections.sort(mChannels, mChannelComparator);
+ Collections.sort(data.channels, mChannelComparator);
+ mData = new UnmodifiableChannelData(data);
notifyChannelListUpdated();
for (ChannelWrapper channel : removedChannels) {
channel.notifyChannelRemoved();
@@ -132,10 +141,12 @@ public class ChannelDataManager {
}
};
+ @MainThread
public ChannelDataManager(Context context, TvInputManagerHelper inputManager) {
this(context, inputManager, context.getContentResolver());
}
+ @MainThread
@VisibleForTesting
ChannelDataManager(Context context, TvInputManagerHelper inputManager,
ContentResolver contentResolver) {
@@ -167,6 +178,7 @@ public class ChannelDataManager {
/**
* Starts the manager. If data is ready, {@link Listener#onLoadFinished()} will be called.
*/
+ @MainThread
public void start() {
if (mStarted) {
return;
@@ -184,6 +196,7 @@ public class ChannelDataManager {
* Stops the manager. It clears manager states and runs pending DB operations. Added listeners
* aren't automatically removed by this method.
*/
+ @MainThread
@VisibleForTesting
public void stop() {
if (!mStarted) {
@@ -192,12 +205,10 @@ public class ChannelDataManager {
mStarted = false;
mDbLoadFinished = false;
- ChannelLogoFetcher.stopFetchingChannelLogos();
mInputManager.removeCallback(mTvInputCallback);
mContentResolver.unregisterContentObserver(mChannelObserver);
mHandler.removeCallbacksAndMessages(null);
- mChannelWrapperMap.clear();
clearChannels();
mPostRunnablesAfterChannelUpdate.clear();
if (mChannelsUpdateTask != null) {
@@ -233,7 +244,7 @@ public class ChannelDataManager {
* Adds a {@link ChannelListener} for a specific channel with the channel ID {@code channelId}.
*/
public void addChannelListener(Long channelId, ChannelListener listener) {
- ChannelWrapper channelWrapper = mChannelWrapperMap.get(channelId);
+ ChannelWrapper channelWrapper = mData.channelWrapperMap.get(channelId);
if (channelWrapper == null) {
return;
}
@@ -245,7 +256,7 @@ public class ChannelDataManager {
* {@code channelId}.
*/
public void removeChannelListener(Long channelId, ChannelListener listener) {
- ChannelWrapper channelWrapper = mChannelWrapperMap.get(channelId);
+ ChannelWrapper channelWrapper = mData.channelWrapperMap.get(channelId);
if (channelWrapper == null) {
return;
}
@@ -263,14 +274,14 @@ public class ChannelDataManager {
* Returns the number of channels.
*/
public int getChannelCount() {
- return mChannels.size();
+ return mData.channels.size();
}
/**
* Returns a list of channels.
*/
public List<Channel> getChannelList() {
- return Collections.unmodifiableList(mChannels);
+ return new ArrayList<>(mData.channels);
}
/**
@@ -278,7 +289,7 @@ public class ChannelDataManager {
*/
public List<Channel> getBrowsableChannelList() {
List<Channel> channels = new ArrayList<>();
- for (Channel channel : mChannels) {
+ for (Channel channel : mData.channels) {
if (channel.isBrowsable()) {
channels.add(channel);
}
@@ -292,7 +303,7 @@ public class ChannelDataManager {
* @param inputId The ID of the input.
*/
public int getChannelCountForInput(String inputId) {
- MutableInt count = mChannelCountMap.get(inputId);
+ MutableInt count = mData.channelCountMap.get(inputId);
return count == null ? 0 : count.value;
}
@@ -303,17 +314,14 @@ public class ChannelDataManager {
* In that case this method is used to check if the channel exists in the DB.
*/
public boolean doesChannelExistInDb(long channelId) {
- return mChannelWrapperMap.get(channelId) != null;
+ return mData.channelWrapperMap.get(channelId) != null;
}
/**
* Returns true if and only if there exists at least one channel and all channels are hidden.
*/
public boolean areAllChannelsHidden() {
- if (mChannels.isEmpty()) {
- return false;
- }
- for (Channel channel : mChannels) {
+ for (Channel channel : mData.channels) {
if (channel.isBrowsable()) {
return false;
}
@@ -325,7 +333,7 @@ public class ChannelDataManager {
* Gets the channel with the channel ID {@code channelId}.
*/
public Channel getChannel(Long channelId) {
- ChannelWrapper channelWrapper = mChannelWrapperMap.get(channelId);
+ ChannelWrapper channelWrapper = mData.channelWrapperMap.get(channelId);
if (channelWrapper == null || channelWrapper.mInputRemoved) {
return null;
}
@@ -349,7 +357,7 @@ public class ChannelDataManager {
*/
public void updateBrowsable(Long channelId, boolean browsable,
boolean skipNotifyChannelBrowsableChanged) {
- ChannelWrapper channelWrapper = mChannelWrapperMap.get(channelId);
+ ChannelWrapper channelWrapper = mData.channelWrapperMap.get(channelId);
if (channelWrapper == null) {
return;
}
@@ -407,7 +415,7 @@ public class ChannelDataManager {
* The value change will be applied to DB when applyPendingDbOperation is called.
*/
public void updateLocked(Long channelId, boolean locked) {
- ChannelWrapper channelWrapper = mChannelWrapperMap.get(channelId);
+ ChannelWrapper channelWrapper = mData.channelWrapperMap.get(channelId);
if (channelWrapper == null) {
return;
}
@@ -427,10 +435,11 @@ public class ChannelDataManager {
* to DB.
*/
public void applyUpdatedValuesToDb() {
+ ChannelData data = mData;
ArrayList<Long> browsableIds = new ArrayList<>();
ArrayList<Long> unbrowsableIds = new ArrayList<>();
for (Long id : mBrowsableUpdateChannelIds) {
- ChannelWrapper channelWrapper = mChannelWrapperMap.get(id);
+ ChannelWrapper channelWrapper = data.channelWrapperMap.get(id);
if (channelWrapper == null) {
continue;
}
@@ -452,10 +461,10 @@ public class ChannelDataManager {
}
editor.apply();
} else {
- if (browsableIds.size() != 0) {
+ if (!browsableIds.isEmpty()) {
updateOneColumnValue(column, 1, browsableIds);
}
- if (unbrowsableIds.size() != 0) {
+ if (!unbrowsableIds.isEmpty()) {
updateOneColumnValue(column, 0, unbrowsableIds);
}
}
@@ -464,7 +473,7 @@ public class ChannelDataManager {
ArrayList<Long> lockedIds = new ArrayList<>();
ArrayList<Long> unlockedIds = new ArrayList<>();
for (Long id : mLockedUpdateChannelIds) {
- ChannelWrapper channelWrapper = mChannelWrapperMap.get(id);
+ ChannelWrapper channelWrapper = data.channelWrapperMap.get(id);
if (channelWrapper == null) {
continue;
}
@@ -476,10 +485,10 @@ public class ChannelDataManager {
channelWrapper.mLockedInDb = channelWrapper.mChannel.isLocked();
}
column = TvContract.Channels.COLUMN_LOCKED;
- if (lockedIds.size() != 0) {
+ if (!lockedIds.isEmpty()) {
updateOneColumnValue(column, 1, lockedIds);
}
- if (unlockedIds.size() != 0) {
+ if (!unlockedIds.isEmpty()) {
updateOneColumnValue(column, 0, unlockedIds);
}
mLockedUpdateChannelIds.clear();
@@ -492,22 +501,24 @@ public class ChannelDataManager {
}
}
- private void addChannel(Channel channel) {
- mChannels.add(channel);
+ @MainThread
+ private void addChannel(ChannelData data, Channel channel) {
+ data.channels.add(channel);
String inputId = channel.getInputId();
- MutableInt count = mChannelCountMap.get(inputId);
+ MutableInt count = data.channelCountMap.get(inputId);
if (count == null) {
- mChannelCountMap.put(inputId, new MutableInt(1));
+ data.channelCountMap.put(inputId, new MutableInt(1));
} else {
count.value++;
}
}
+ @MainThread
private void clearChannels() {
- mChannels.clear();
- mChannelCountMap.clear();
+ mData = new UnmodifiableChannelData();
}
+ @MainThread
private void handleUpdateChannels() {
if (mChannelsUpdateTask != null) {
mChannelsUpdateTask.cancel(true);
@@ -525,6 +536,9 @@ public class ChannelDataManager {
}
}
+ /**
+ * A listener for ChannelDataManager. The callbacks are called on the main thread.
+ */
public interface Listener {
/**
* Called when data load is finished.
@@ -543,6 +557,9 @@ public class ChannelDataManager {
void onChannelBrowsableChanged();
}
+ /**
+ * A listener for individual channel change. The callbacks are called on the main thread.
+ */
public interface ChannelListener {
/**
* Called when the channel has been removed in DB.
@@ -590,9 +607,36 @@ public class ChannelDataManager {
}
}
+ private class CheckChannelLogoExistTask extends AsyncTask<Void, Void, Boolean> {
+ private final Channel mChannel;
+
+ CheckChannelLogoExistTask(Channel channel) {
+ mChannel = channel;
+ }
+
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ try (AssetFileDescriptor f = mContext.getContentResolver().openAssetFileDescriptor(
+ TvContract.buildChannelLogoUri(mChannel.getId()), "r")) {
+ return true;
+ } catch (SQLiteException | IOException | NullPointerException e) {
+ // File not found or asset file not found.
+ }
+ return false;
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ ChannelWrapper wrapper = mData.channelWrapperMap.get(mChannel.getId());
+ if (wrapper != null) {
+ wrapper.mChannel.setChannelLogoExist(result);
+ }
+ }
+ }
+
private final class QueryAllChannelsTask extends AsyncDbTask.AsyncChannelQueryTask {
- public QueryAllChannelsTask(ContentResolver contentResolver) {
+ QueryAllChannelsTask(ContentResolver contentResolver) {
super(contentResolver);
}
@@ -603,7 +647,9 @@ public class ChannelDataManager {
if (DEBUG) Log.e(TAG, "onPostExecute with null channels");
return;
}
- Set<Long> removedChannelIds = new HashSet<>(mChannelWrapperMap.keySet());
+ ChannelData data = new ChannelData();
+ data.channelWrapperMap.putAll(mData.channelWrapperMap);
+ Set<Long> removedChannelIds = new HashSet<>(data.channelWrapperMap.keySet());
List<ChannelWrapper> removedChannelWrappers = new ArrayList<>();
List<ChannelWrapper> updatedChannelWrappers = new ArrayList<>();
@@ -625,13 +671,15 @@ public class ChannelDataManager {
boolean newlyAdded = !removedChannelIds.remove(channelId);
ChannelWrapper channelWrapper;
if (newlyAdded) {
+ new CheckChannelLogoExistTask(channel)
+ .executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
channelWrapper = new ChannelWrapper(channel);
- mChannelWrapperMap.put(channel.getId(), channelWrapper);
+ data.channelWrapperMap.put(channel.getId(), channelWrapper);
if (!channelWrapper.mInputRemoved) {
channelAdded = true;
}
} else {
- channelWrapper = mChannelWrapperMap.get(channelId);
+ channelWrapper = data.channelWrapperMap.get(channelId);
if (!channelWrapper.mChannel.hasSameReadOnlyInfo(channel)) {
// Channel data updated
Channel oldChannel = channelWrapper.mChannel;
@@ -640,9 +688,9 @@ public class ChannelDataManager {
// {@link #applyUpdatedValuesToDb} is called. Therefore, the value
// between DB and ChannelDataManager could be different for a while.
// Therefore, we'll keep the values in ChannelDataManager.
- channelWrapper.mChannel.copyFrom(channel);
channel.setBrowsable(oldChannel.isBrowsable());
channel.setLocked(oldChannel.isLocked());
+ channelWrapper.mChannel.copyFrom(channel);
if (!channelWrapper.mInputRemoved) {
channelUpdated = true;
updatedChannelWrappers.add(channelWrapper);
@@ -663,19 +711,19 @@ public class ChannelDataManager {
}
for (long id : removedChannelIds) {
- ChannelWrapper channelWrapper = mChannelWrapperMap.remove(id);
+ ChannelWrapper channelWrapper = data.channelWrapperMap.remove(id);
if (!channelWrapper.mInputRemoved) {
channelRemoved = true;
removedChannelWrappers.add(channelWrapper);
}
}
- clearChannels();
- for (ChannelWrapper channelWrapper : mChannelWrapperMap.values()) {
+ for (ChannelWrapper channelWrapper : data.channelWrapperMap.values()) {
if (!channelWrapper.mInputRemoved) {
- addChannel(channelWrapper.mChannel);
+ addChannel(data, channelWrapper.mChannel);
}
}
- Collections.sort(mChannels, mChannelComparator);
+ Collections.sort(data.channels, mChannelComparator);
+ mData = new UnmodifiableChannelData(data);
if (!mDbLoadFinished) {
mDbLoadFinished = true;
@@ -693,7 +741,6 @@ public class ChannelDataManager {
r.run();
}
mPostRunnablesAfterChannelUpdate.clear();
- ChannelLogoFetcher.startFetchingChannelLogos(mContext);
}
}
@@ -705,10 +752,9 @@ public class ChannelDataManager {
private void updateOneColumnValue(
final String columnName, final int columnValue, final List<Long> ids) {
if (!PermissionUtils.hasAccessAllEpg(mContext)) {
- // TODO: support this feature for non-system LC app. b/23939816
return;
}
- AsyncDbTask.execute(new Runnable() {
+ AsyncDbTask.executeOnDbThread(new Runnable() {
@Override
public void run() {
String selection = Utils.buildSelectionForIds(Channels._ID, ids);
@@ -723,6 +769,7 @@ public class ChannelDataManager {
return channel.getInputId() + "|" + channel.getId();
}
+ @MainThread
private static class ChannelDataManagerHandler extends WeakHandler<ChannelDataManager> {
public ChannelDataManagerHandler(ChannelDataManager channelDataManager) {
super(Looper.getMainLooper(), channelDataManager);
@@ -735,4 +782,51 @@ public class ChannelDataManager {
}
}
}
+
+ /**
+ * Container class which includes channel data that needs to be synced. This class is
+ * modifiable and used for changing channel data.
+ * e.g. TvInputCallback, or AsyncDbTask.onPostExecute.
+ */
+ @MainThread
+ private static class ChannelData {
+ final Map<Long, ChannelWrapper> channelWrapperMap;
+ final Map<String, MutableInt> channelCountMap;
+ final List<Channel> channels;
+
+ ChannelData() {
+ channelWrapperMap = new HashMap<>();
+ channelCountMap = new HashMap<>();
+ channels = new ArrayList<>();
+ }
+
+ ChannelData(ChannelData data) {
+ channelWrapperMap = new HashMap<>(data.channelWrapperMap);
+ channelCountMap = new HashMap<>(data.channelCountMap);
+ channels = new ArrayList<>(data.channels);
+ }
+
+ ChannelData(Map<Long, ChannelWrapper> channelWrapperMap,
+ Map<String, MutableInt> channelCountMap, List<Channel> channels) {
+ this.channelWrapperMap = channelWrapperMap;
+ this.channelCountMap = channelCountMap;
+ this.channels = channels;
+ }
+ }
+
+ /** Unmodifiable channel data. */
+ @MainThread
+ private static class UnmodifiableChannelData extends ChannelData {
+ UnmodifiableChannelData() {
+ super(Collections.unmodifiableMap(new HashMap<>()),
+ Collections.unmodifiableMap(new HashMap<>()),
+ Collections.unmodifiableList(new ArrayList<>()));
+ }
+
+ UnmodifiableChannelData(ChannelData data) {
+ super(Collections.unmodifiableMap(data.channelWrapperMap),
+ Collections.unmodifiableMap(data.channelCountMap),
+ Collections.unmodifiableList(data.channels));
+ }
+ }
}