aboutsummaryrefslogtreecommitdiff
path: root/src/com/android/tv/data
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/tv/data')
-rw-r--r--src/com/android/tv/data/Channel.java9
-rw-r--r--src/com/android/tv/data/ChannelDataManager.java5
-rw-r--r--src/com/android/tv/data/ChannelLogoFetcher.java5
-rw-r--r--src/com/android/tv/data/ChannelNumber.java19
-rw-r--r--src/com/android/tv/data/InternalDataUtils.java1
-rw-r--r--src/com/android/tv/data/Lineup.java53
-rw-r--r--src/com/android/tv/data/PreviewDataManager.java15
-rw-r--r--src/com/android/tv/data/PreviewProgramContent.java49
-rw-r--r--src/com/android/tv/data/Program.java8
-rw-r--r--src/com/android/tv/data/ProgramDataManager.java3
-rw-r--r--src/com/android/tv/data/WatchedHistoryManager.java18
-rw-r--r--src/com/android/tv/data/epg/AutoValue_EpgReader_EpgChannel.java86
-rw-r--r--src/com/android/tv/data/epg/EpgFetchHelper.java9
-rw-r--r--src/com/android/tv/data/epg/EpgFetchService.java70
-rw-r--r--src/com/android/tv/data/epg/EpgFetcher.java705
-rw-r--r--src/com/android/tv/data/epg/EpgFetcherImpl.java814
-rw-r--r--src/com/android/tv/data/epg/EpgInputWhiteList.java92
-rw-r--r--src/com/android/tv/data/epg/EpgReader.java24
-rw-r--r--src/com/android/tv/data/epg/StubEpgReader.java11
19 files changed, 758 insertions, 1238 deletions
diff --git a/src/com/android/tv/data/Channel.java b/src/com/android/tv/data/Channel.java
index 1204a49f..eda188e4 100644
--- a/src/com/android/tv/data/Channel.java
+++ b/src/com/android/tv/data/Channel.java
@@ -28,8 +28,7 @@ import android.support.annotation.UiThread;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.Log;
-import com.android.tv.common.CommonConstants;
-import com.android.tv.common.util.CommonUtils;
+import com.android.tv.common.TvCommonConstants;
import com.android.tv.util.ImageLoader;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
@@ -125,7 +124,7 @@ public final class Channel {
channel.mAppLinkIconUri = cursor.getString(index++);
channel.mAppLinkPosterArtUri = cursor.getString(index++);
channel.mAppLinkIntentUri = cursor.getString(index++);
- if (CommonUtils.isBundledInput(channel.mInputId)) {
+ if (Utils.isBundledInput(channel.mInputId)) {
channel.mRecordingProhibited = cursor.getInt(index++) != 0;
}
return channel;
@@ -626,7 +625,7 @@ public final class Channel {
if (intent.resolveActivityInfo(pm, 0) != null) {
mAppLinkIntent = intent;
mAppLinkIntent.putExtra(
- CommonConstants.EXTRA_APP_LINK_CHANNEL_URI, getUri().toString());
+ TvCommonConstants.EXTRA_APP_LINK_CHANNEL_URI, getUri().toString());
mAppLinkType = APP_LINK_TYPE_CHANNEL;
return;
} else {
@@ -643,7 +642,7 @@ public final class Channel {
mAppLinkIntent = pm.getLeanbackLaunchIntentForPackage(mPackageName);
if (mAppLinkIntent != null) {
mAppLinkIntent.putExtra(
- CommonConstants.EXTRA_APP_LINK_CHANNEL_URI, getUri().toString());
+ TvCommonConstants.EXTRA_APP_LINK_CHANNEL_URI, getUri().toString());
mAppLinkType = APP_LINK_TYPE_APP;
}
}
diff --git a/src/com/android/tv/data/ChannelDataManager.java b/src/com/android/tv/data/ChannelDataManager.java
index 68fbdb6a..e4d1cd85 100644
--- a/src/com/android/tv/data/ChannelDataManager.java
+++ b/src/com/android/tv/data/ChannelDataManager.java
@@ -38,11 +38,11 @@ import android.support.annotation.VisibleForTesting;
import android.util.ArraySet;
import android.util.Log;
import android.util.MutableInt;
+import com.android.tv.common.SharedPreferencesUtils;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.WeakHandler;
-import com.android.tv.common.util.PermissionUtils;
-import com.android.tv.common.util.SharedPreferencesUtils;
import com.android.tv.util.AsyncDbTask;
+import com.android.tv.util.PermissionUtils;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
import java.io.IOException;
@@ -62,7 +62,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
* methods are called in only the main thread.
*/
@AnyThread
-@SuppressWarnings("TryWithResources") // TODO(b/62143348): remove when error prone check fixed
public class ChannelDataManager {
private static final String TAG = "ChannelDataManager";
private static final boolean DEBUG = false;
diff --git a/src/com/android/tv/data/ChannelLogoFetcher.java b/src/com/android/tv/data/ChannelLogoFetcher.java
index 8aaaf73a..2dc43102 100644
--- a/src/com/android/tv/data/ChannelLogoFetcher.java
+++ b/src/com/android/tv/data/ChannelLogoFetcher.java
@@ -28,10 +28,10 @@ import android.os.RemoteException;
import android.support.annotation.MainThread;
import android.text.TextUtils;
import android.util.Log;
-import com.android.tv.common.util.PermissionUtils;
-import com.android.tv.common.util.SharedPreferencesUtils;
+import com.android.tv.common.SharedPreferencesUtils;
import com.android.tv.util.BitmapUtils;
import com.android.tv.util.BitmapUtils.ScaledBitmapInfo;
+import com.android.tv.util.PermissionUtils;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
@@ -42,7 +42,6 @@ import java.util.Map;
* Fetches channel logos from the cloud into the database. It's for the channels which have no logos
* or need update logos. This class is thread safe.
*/
-@SuppressWarnings("TryWithResources") // TODO(b/62143348): remove when error prone check fixed
public class ChannelLogoFetcher {
private static final String TAG = "ChannelLogoFetcher";
private static final boolean DEBUG = false;
diff --git a/src/com/android/tv/data/ChannelNumber.java b/src/com/android/tv/data/ChannelNumber.java
index 1623b33d..63f8a972 100644
--- a/src/com/android/tv/data/ChannelNumber.java
+++ b/src/com/android/tv/data/ChannelNumber.java
@@ -19,7 +19,7 @@ package com.android.tv.data;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.view.KeyEvent;
-import com.android.tv.common.util.StringUtils;
+import com.android.tv.util.StringUtils;
import java.util.Objects;
/** A convenience class to handle channel number. */
@@ -43,23 +43,6 @@ public final class ChannelNumber implements Comparable<ChannelNumber> {
reset();
}
- /**
- * {@code lhs} and {@code rhs} are equivalent if {@link ChannelNumber#compare(String, String)}
- * is 0 or if only one has a delimiter and both {@link ChannelNumber#majorNumber} equals.
- */
- public static boolean equivalent(String lhs, String rhs) {
- if (compare(lhs, rhs) == 0) {
- return true;
- }
- // Match if only one has delimiter
- ChannelNumber lhsNumber = parseChannelNumber(lhs);
- ChannelNumber rhsNumber = parseChannelNumber(rhs);
- return lhsNumber != null
- && rhsNumber != null
- && lhsNumber.hasDelimiter != rhsNumber.hasDelimiter
- && lhsNumber.majorNumber.equals(rhsNumber.majorNumber);
- }
-
public void reset() {
setChannelNumber("", false, "");
}
diff --git a/src/com/android/tv/data/InternalDataUtils.java b/src/com/android/tv/data/InternalDataUtils.java
index 99a3d4e8..4c30d395 100644
--- a/src/com/android/tv/data/InternalDataUtils.java
+++ b/src/com/android/tv/data/InternalDataUtils.java
@@ -33,7 +33,6 @@ import java.util.List;
* android.media.tv.TvContract.Programs#COLUMN_INTERNAL_PROVIDER_DATA} field in the {@link
* android.media.tv.TvContract.Programs}.
*/
-@SuppressWarnings("TryWithResources") // TODO(b/62143348): remove when error prone check fixed
public final class InternalDataUtils {
private static final boolean DEBUG = false;
private static final String TAG = "InternalDataUtils";
diff --git a/src/com/android/tv/data/Lineup.java b/src/com/android/tv/data/Lineup.java
index 4393cd3d..0f11c1cc 100644
--- a/src/com/android/tv/data/Lineup.java
+++ b/src/com/android/tv/data/Lineup.java
@@ -19,45 +19,23 @@ package com.android.tv.data;
import android.support.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Collections;
-import java.util.List;
/** A class that represents a lineup. */
public class Lineup {
/** The ID of this lineup. */
- public String getId() {
- return id;
- }
+ public final String id;
/** The type associated with this lineup. */
- public int getType() {
- return type;
- }
-
- /** The human readable name associated with this lineup. */
- public String getName() {
- return name;
- }
+ public final int type;
/** The human readable name associated with this lineup. */
- public String getLocation() {
- return location;
- }
-
- /** An unmodifiable list of channel numbers that this lineup has. */
- public List<String> getChannels() {
- return channels;
- }
+ public final String name;
- private final String id;
-
- private final int type;
-
- private final String name;
-
- private final String location;
-
- private final List<String> channels;
+ /**
+ * Location this lineup can be found. This is a human readable description of a geographic
+ * location.
+ */
+ public final String location;
@Retention(RetentionPolicy.SOURCE)
@IntDef({
@@ -66,9 +44,7 @@ public class Lineup {
LINEUP_BROADCAST_DIGITAL,
LINEUP_BROADCAST_ANALOG,
LINEUP_IPTV,
- LINEUP_MVPD,
- LINEUP_INTERNET,
- LINEUP_OTHER
+ LINEUP_MVPD
})
public @interface LineupType {}
@@ -88,23 +64,16 @@ public class Lineup {
public static final int LINEUP_IPTV = 4;
/**
- * Indicates the lineup is either satellite, cable or IPTV but we are not sure which specific
+ * Indicates the lineup is either satelite, cable or IPTV but we are not sure which specific
* type.
*/
public static final int LINEUP_MVPD = 5;
- /** Lineup type for Internet. */
- public static final int LINEUP_INTERNET = 6;
-
- /** Lineup type for other. */
- public static final int LINEUP_OTHER = 7;
-
/** Creates a lineup. */
- public Lineup(String id, int type, String name, String location, List<String> channels) {
+ public Lineup(String id, int type, String name, String location) {
this.id = id;
this.type = type;
this.name = name;
this.location = location;
- this.channels = Collections.unmodifiableList(channels);
}
}
diff --git a/src/com/android/tv/data/PreviewDataManager.java b/src/com/android/tv/data/PreviewDataManager.java
index ac78147b..b103a5d7 100644
--- a/src/com/android/tv/data/PreviewDataManager.java
+++ b/src/com/android/tv/data/PreviewDataManager.java
@@ -36,7 +36,7 @@ import android.support.media.tv.PreviewProgram;
import android.util.Log;
import android.util.Pair;
import com.android.tv.R;
-import com.android.tv.common.util.PermissionUtils;
+import com.android.tv.util.PermissionUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.HashMap;
@@ -47,22 +47,22 @@ import java.util.concurrent.CopyOnWriteArraySet;
/** Class to manage the preview data. */
@TargetApi(Build.VERSION_CODES.O)
@MainThread
-@SuppressWarnings("TryWithResources") // TODO(b/62143348): remove when error prone check fixed
public class PreviewDataManager {
private static final String TAG = "PreviewDataManager";
- private static final boolean DEBUG = false;
+ // STOPSHIP: set it to false.
+ private static final boolean DEBUG = true;
/** Invalid preview channel ID. */
public static final long INVALID_PREVIEW_CHANNEL_ID = -1;
- @IntDef({TYPE_DEFAULT_PREVIEW_CHANNEL, TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL})
+ @IntDef({(int) TYPE_DEFAULT_PREVIEW_CHANNEL, (int) TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL})
@Retention(RetentionPolicy.SOURCE)
public @interface PreviewChannelType {}
/** Type of default preview channel */
- public static final int TYPE_DEFAULT_PREVIEW_CHANNEL = 1;
+ public static final long TYPE_DEFAULT_PREVIEW_CHANNEL = 1;
/** Type of recorded program channel */
- public static final int TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL = 2;
+ public static final long TYPE_RECORDED_PROGRAM_PREVIEW_CHANNEL = 2;
private final Context mContext;
private final ContentResolver mContentResolver;
@@ -604,8 +604,7 @@ public class PreviewDataManager {
.setPosterArtUri(program.getPosterArtUri())
.setIntentUri(program.getIntentUri())
.setPreviewVideoUri(program.getPreviewVideoUri())
- .setInternalProviderId(Long.toString(program.getId()))
- .setContentId(program.getIntentUri().toString());
+ .setInternalProviderId(Long.toString(program.getId()));
return builder.build();
}
diff --git a/src/com/android/tv/data/PreviewProgramContent.java b/src/com/android/tv/data/PreviewProgramContent.java
index 252092b0..845ca9d4 100644
--- a/src/com/android/tv/data/PreviewProgramContent.java
+++ b/src/com/android/tv/data/PreviewProgramContent.java
@@ -17,18 +17,17 @@
package com.android.tv.data;
import android.content.Context;
+import android.media.tv.TvContract;
import android.net.Uri;
-import android.support.annotation.VisibleForTesting;
-import android.support.media.tv.TvContractCompat;
import android.text.TextUtils;
import android.util.Pair;
-import com.android.tv.TvSingletons;
+import com.android.tv.TvApplication;
import com.android.tv.dvr.data.RecordedProgram;
import java.util.Objects;
/** A class to store the content of preview programs. */
public class PreviewProgramContent {
- @VisibleForTesting static final String PARAM_INPUT = "input";
+ private static final String PARAM_INPUT = "input";
private long mId;
private long mPreviewChannelId;
@@ -44,30 +43,17 @@ public class PreviewProgramContent {
public static PreviewProgramContent createFromProgram(
Context context, long previewChannelId, Program program) {
Channel channel =
- TvSingletons.getSingletons(context)
+ TvApplication.getSingletons(context)
.getChannelDataManager()
.getChannel(program.getChannelId());
- return channel == null ? null : createFromProgram(previewChannelId, program, channel);
- }
-
- /** Create preview program content from {@link RecordedProgram} */
- public static PreviewProgramContent createFromRecordedProgram(
- Context context, long previewChannelId, RecordedProgram recordedProgram) {
- Channel channel =
- TvSingletons.getSingletons(context)
- .getChannelDataManager()
- .getChannel(recordedProgram.getChannelId());
- return createFromRecordedProgram(previewChannelId, recordedProgram, channel);
- }
-
- @VisibleForTesting
- static PreviewProgramContent createFromProgram(
- long previewChannelId, Program program, Channel channel) {
+ if (channel == null) {
+ return null;
+ }
String channelDisplayName = channel.getDisplayName();
return new PreviewProgramContent.Builder()
.setId(program.getId())
.setPreviewChannelId(previewChannelId)
- .setType(TvContractCompat.PreviewPrograms.TYPE_CHANNEL)
+ .setType(TvContract.PreviewPrograms.TYPE_CHANNEL)
.setLive(true)
.setTitle(program.getTitle())
.setDescription(
@@ -82,15 +68,22 @@ public class PreviewProgramContent {
.build();
}
- @VisibleForTesting
- static PreviewProgramContent createFromRecordedProgram(
- long previewChannelId, RecordedProgram recordedProgram, Channel channel) {
- String channelDisplayName = channel == null ? null : channel.getDisplayName();
- Uri recordedProgramUri = TvContractCompat.buildRecordedProgramUri(recordedProgram.getId());
+ /** Create preview program content from {@link RecordedProgram} */
+ public static PreviewProgramContent createFromRecordedProgram(
+ Context context, long previewChannelId, RecordedProgram recordedProgram) {
+ Channel channel =
+ TvApplication.getSingletons(context)
+ .getChannelDataManager()
+ .getChannel(recordedProgram.getChannelId());
+ String channelDisplayName = null;
+ if (channel != null) {
+ channelDisplayName = channel.getDisplayName();
+ }
+ Uri recordedProgramUri = TvContract.buildRecordedProgramUri(recordedProgram.getId());
return new PreviewProgramContent.Builder()
.setId(recordedProgram.getId())
.setPreviewChannelId(previewChannelId)
- .setType(TvContractCompat.PreviewPrograms.TYPE_CLIP)
+ .setType(TvContract.PreviewPrograms.TYPE_CLIP)
.setTitle(recordedProgram.getTitle())
.setDescription(channelDisplayName != null ? channelDisplayName : "")
.setPosterArtUri(Uri.parse(recordedProgram.getPosterArtUri()))
diff --git a/src/com/android/tv/data/Program.java b/src/com/android/tv/data/Program.java
index 30a3033e..f47a3a06 100644
--- a/src/com/android/tv/data/Program.java
+++ b/src/com/android/tv/data/Program.java
@@ -33,9 +33,8 @@ import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.Log;
import com.android.tv.common.BuildConfig;
+import com.android.tv.common.CollectionUtils;
import com.android.tv.common.TvContentRatingCache;
-import com.android.tv.common.util.CollectionUtils;
-import com.android.tv.common.util.CommonUtils;
import com.android.tv.util.ImageLoader;
import com.android.tv.util.Utils;
import java.io.Serializable;
@@ -129,7 +128,7 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
builder.setEndTimeUtcMillis(cursor.getLong(index++));
builder.setVideoWidth((int) cursor.getLong(index++));
builder.setVideoHeight((int) cursor.getLong(index++));
- if (CommonUtils.isInBundledPackageSet(packageName)) {
+ if (Utils.isInBundledPackageSet(packageName)) {
InternalDataUtils.deserializeInternalProviderData(cursor.getBlob(index), builder);
}
index++;
@@ -476,9 +475,6 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
public static ContentValues toContentValues(Program program) {
ContentValues values = new ContentValues();
values.put(TvContract.Programs.COLUMN_CHANNEL_ID, program.getChannelId());
- if (!TextUtils.isEmpty(program.getPackageName())) {
- values.put(Programs.COLUMN_PACKAGE_NAME, program.getPackageName());
- }
putValue(values, TvContract.Programs.COLUMN_TITLE, program.getTitle());
putValue(values, TvContract.Programs.COLUMN_EPISODE_TITLE, program.getEpisodeTitle());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
diff --git a/src/com/android/tv/data/ProgramDataManager.java b/src/com/android/tv/data/ProgramDataManager.java
index 3a7693a4..639ac99a 100644
--- a/src/com/android/tv/data/ProgramDataManager.java
+++ b/src/com/android/tv/data/ProgramDataManager.java
@@ -35,8 +35,8 @@ import android.util.LongSparseArray;
import android.util.LruCache;
import com.android.tv.common.MemoryManageable;
import com.android.tv.common.SoftPreconditions;
-import com.android.tv.common.util.Clock;
import com.android.tv.util.AsyncDbTask;
+import com.android.tv.util.Clock;
import com.android.tv.util.MultiLongSparseArray;
import com.android.tv.util.Utils;
import java.util.ArrayList;
@@ -52,7 +52,6 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@MainThread
-@SuppressWarnings("TryWithResources") // TODO(b/62143348): remove when error prone check fixed
public class ProgramDataManager implements MemoryManageable {
private static final String TAG = "ProgramDataManager";
private static final boolean DEBUG = false;
diff --git a/src/com/android/tv/data/WatchedHistoryManager.java b/src/com/android/tv/data/WatchedHistoryManager.java
index 25ba7716..8c9756b0 100644
--- a/src/com/android/tv/data/WatchedHistoryManager.java
+++ b/src/com/android/tv/data/WatchedHistoryManager.java
@@ -1,18 +1,3 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
package com.android.tv.data;
import android.content.Context;
@@ -27,7 +12,7 @@ import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.support.annotation.WorkerThread;
import android.util.Log;
-import com.android.tv.common.util.SharedPreferencesUtils;
+import com.android.tv.common.SharedPreferencesUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -43,7 +28,6 @@ import java.util.concurrent.TimeUnit;
*
* <p>Note that this class is not thread safe. Please use this on one thread.
*/
-@SuppressWarnings("TryWithResources") // TODO(b/62143348): remove when error prone check fixed
public class WatchedHistoryManager {
private static final String TAG = "WatchedHistoryManager";
private static final boolean DEBUG = false;
diff --git a/src/com/android/tv/data/epg/AutoValue_EpgReader_EpgChannel.java b/src/com/android/tv/data/epg/AutoValue_EpgReader_EpgChannel.java
deleted file mode 100644
index 90d109d7..00000000
--- a/src/com/android/tv/data/epg/AutoValue_EpgReader_EpgChannel.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.tv.data.epg;
-
-import com.android.tv.data.Channel;
-
-/**
- * Hand copy of generated Autovalue class.
- *
- * TODO get autovalue working
- */
-final class AutoValue_EpgReader_EpgChannel extends EpgReader.EpgChannel {
-
- private final Channel channel;
- private final String epgChannelId;
-
- AutoValue_EpgReader_EpgChannel(
- Channel channel,
- String epgChannelId) {
- if (channel == null) {
- throw new NullPointerException("Null channel");
- }
- this.channel = channel;
- if (epgChannelId == null) {
- throw new NullPointerException("Null epgChannelId");
- }
- this.epgChannelId = epgChannelId;
- }
-
- @Override
- public Channel getChannel() {
- return channel;
- }
-
- @Override
- public String getEpgChannelId() {
- return epgChannelId;
- }
-
- @Override
- public String toString() {
- return "EpgChannel{"
- + "channel=" + channel + ", "
- + "epgChannelId=" + epgChannelId
- + "}";
- }
-
- @Override
- public boolean equals(Object o) {
- if (o == this) {
- return true;
- }
- if (o instanceof EpgReader.EpgChannel) {
- EpgReader.EpgChannel that = (EpgReader.EpgChannel) o;
- return (this.channel.equals(that.getChannel()))
- && (this.epgChannelId.equals(that.getEpgChannelId()));
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- int h = 1;
- h *= 1000003;
- h ^= this.channel.hashCode();
- h *= 1000003;
- h ^= this.epgChannelId.hashCode();
- return h;
- }
-
-}
-
diff --git a/src/com/android/tv/data/epg/EpgFetchHelper.java b/src/com/android/tv/data/epg/EpgFetchHelper.java
index 30123ee5..89d5f494 100644
--- a/src/com/android/tv/data/epg/EpgFetchHelper.java
+++ b/src/com/android/tv/data/epg/EpgFetchHelper.java
@@ -27,15 +27,13 @@ import android.preference.PreferenceManager;
import android.support.annotation.WorkerThread;
import android.text.TextUtils;
import android.util.Log;
-import com.android.tv.common.util.Clock;
import com.android.tv.data.Program;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
-/** The helper class for {@link EpgFetcher} */
-@SuppressWarnings("TryWithResources") // TODO(b/62143348): remove when error prone check fixed
+/** The helper class for {@link com.android.tv.data.epg.EpgFetcher} */
class EpgFetchHelper {
private static final String TAG = "EpgFetchHelper";
private static final boolean DEBUG = false;
@@ -66,14 +64,13 @@ class EpgFetchHelper {
* @param fetchedPrograms the newly fetched program data.
* @return {@code true} if new program data are successfully updated. Otherwise {@code false}.
*/
- static boolean updateEpgData(
- Context context, Clock clock, long channelId, List<Program> fetchedPrograms) {
+ static boolean updateEpgData(Context context, long channelId, List<Program> fetchedPrograms) {
final int fetchedProgramsCount = fetchedPrograms.size();
if (fetchedProgramsCount == 0) {
return false;
}
boolean updated = false;
- long startTimeMs = clock.currentTimeMillis();
+ long startTimeMs = System.currentTimeMillis();
long endTimeMs = startTimeMs + PROGRAM_QUERY_DURATION_MS;
List<Program> oldPrograms = queryPrograms(context, channelId, startTimeMs, endTimeMs);
int oldProgramsIndex = 0;
diff --git a/src/com/android/tv/data/epg/EpgFetchService.java b/src/com/android/tv/data/epg/EpgFetchService.java
deleted file mode 100644
index aa4f3588..00000000
--- a/src/com/android/tv/data/epg/EpgFetchService.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.tv.data.epg;
-
-import android.app.job.JobParameters;
-import android.app.job.JobService;
-import com.android.tv.Starter;
-import com.android.tv.TvSingletons;
-import com.android.tv.data.ChannelDataManager;
-
-/** JobService to Fetch EPG data. */
-public class EpgFetchService extends JobService {
- private EpgFetcher mEpgFetcher;
- private ChannelDataManager mChannelDataManager;
-
- @Override
- public void onCreate() {
- super.onCreate();
- Starter.start(this);
- TvSingletons tvSingletons = TvSingletons.getSingletons(getApplicationContext());
- mEpgFetcher = tvSingletons.getEpgFetcher();
- mChannelDataManager = tvSingletons.getChannelDataManager();
- }
-
- @Override
- public boolean onStartJob(JobParameters params) {
- if (!mChannelDataManager.isDbLoadFinished()) {
- mChannelDataManager.addListener(
- new ChannelDataManager.Listener() {
- @Override
- public void onLoadFinished() {
- mChannelDataManager.removeListener(this);
- if (!mEpgFetcher.executeFetchTaskIfPossible(
- EpgFetchService.this, params)) {
- jobFinished(params, false);
- }
- }
-
- @Override
- public void onChannelListUpdated() {}
-
- @Override
- public void onChannelBrowsableChanged() {}
- });
- return true;
- } else {
- return mEpgFetcher.executeFetchTaskIfPossible(this, params);
- }
- }
-
- @Override
- public boolean onStopJob(JobParameters params) {
- mEpgFetcher.stopFetchingJob();
- return false;
- }
-}
diff --git a/src/com/android/tv/data/epg/EpgFetcher.java b/src/com/android/tv/data/epg/EpgFetcher.java
index 9c24613d..b10bdc1b 100644
--- a/src/com/android/tv/data/epg/EpgFetcher.java
+++ b/src/com/android/tv/data/epg/EpgFetcher.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,13 +16,138 @@
package com.android.tv.data.epg;
+import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.media.tv.TvInputInfo;
+import android.net.TrafficStats;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.support.annotation.AnyThread;
import android.support.annotation.MainThread;
+import android.support.annotation.Nullable;
+import android.support.annotation.WorkerThread;
+import android.text.TextUtils;
+import android.util.Log;
+import com.android.tv.ApplicationSingletons;
+import com.android.tv.Features;
+import com.android.tv.TvApplication;
+import com.android.tv.common.SoftPreconditions;
+import com.android.tv.common.TvCommonUtils;
+import com.android.tv.config.RemoteConfigUtils;
+import com.android.tv.data.Channel;
+import com.android.tv.data.ChannelDataManager;
+import com.android.tv.data.ChannelLogoFetcher;
+import com.android.tv.data.Lineup;
+import com.android.tv.data.Program;
+import com.android.tv.perf.EventNames;
+import com.android.tv.perf.PerformanceMonitor;
+import com.android.tv.perf.TimerEvent;
+import com.android.tv.tuner.util.PostalCodeUtils;
+import com.android.tv.util.LocationUtils;
+import com.android.tv.util.NetworkTrafficTags;
+import com.android.tv.util.Utils;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
-/** Fetch EPG routinely or on-demand during channel scanning */
-public interface EpgFetcher {
+/**
+ * The service class to fetch EPG routinely or on-demand during channel scanning
+ *
+ * <p>Since the default executor of {@link AsyncTask} is {@link AsyncTask#SERIAL_EXECUTOR}, only one
+ * task can run at a time. Because fetching EPG takes long time, the fetching task shouldn't run on
+ * the serial executor. Instead, it should run on the {@link AsyncTask#THREAD_POOL_EXECUTOR}.
+ */
+public class EpgFetcher {
+ private static final String TAG = "EpgFetcher";
+ private static final boolean DEBUG = false;
+
+ private static final int EPG_ROUTINELY_FETCHING_JOB_ID = 101;
+
+ private static final long INITIAL_BACKOFF_MS = TimeUnit.SECONDS.toMillis(10);
+
+ private static final int REASON_EPG_READER_NOT_READY = 1;
+ private static final int REASON_LOCATION_INFO_UNAVAILABLE = 2;
+ private static final int REASON_LOCATION_PERMISSION_NOT_GRANTED = 3;
+ private static final int REASON_NO_EPG_DATA_RETURNED = 4;
+ private static final int REASON_NO_NEW_EPG = 5;
+
+ private static final long FETCH_DURING_SCAN_WAIT_TIME_MS = TimeUnit.SECONDS.toMillis(10);
+
+ private static final long FETCH_DURING_SCAN_DURATION_SEC = TimeUnit.HOURS.toSeconds(3);
+ private static final long FAST_FETCH_DURATION_SEC = TimeUnit.DAYS.toSeconds(2);
+
+ private static final int DEFAULT_ROUTINE_INTERVAL_HOUR = 4;
+ private static final String KEY_ROUTINE_INTERVAL = "live_channels_epg_fetcher_interval_hour";
+
+ private static final int MSG_PREPARE_FETCH_DURING_SCAN = 1;
+ private static final int MSG_CHANNEL_UPDATED_DURING_SCAN = 2;
+ private static final int MSG_FINISH_FETCH_DURING_SCAN = 3;
+ private static final int MSG_RETRY_PREPARE_FETCH_DURING_SCAN = 4;
+
+ private static final int QUERY_CHANNEL_COUNT = 50;
+ private static final int MINIMUM_CHANNELS_TO_DECIDE_LINEUP = 3;
+
+ private static EpgFetcher sInstance;
+
+ private final Context mContext;
+ private final ChannelDataManager mChannelDataManager;
+ private final EpgReader mEpgReader;
+ private final PerformanceMonitor mPerformanceMonitor;
+ private FetchAsyncTask mFetchTask;
+ private FetchDuringScanHandler mFetchDuringScanHandler;
+ private long mEpgTimeStamp;
+ private List<Lineup> mPossibleLineups;
+ private final Object mPossibleLineupsLock = new Object();
+ private final Object mFetchDuringScanHandlerLock = new Object();
+ // A flag to block the re-entrance of onChannelScanStarted and onChannelScanFinished.
+ private boolean mScanStarted;
+
+ private final long mRoutineIntervalMs;
+ private final long mEpgDataExpiredTimeLimitMs;
+ private final long mFastFetchDurationSec;
+
+ public static EpgFetcher getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new EpgFetcher(context);
+ }
+ return sInstance;
+ }
+
+ /** Creates and returns {@link EpgReader}. */
+ public static EpgReader createEpgReader(Context context, String region) {
+ return new StubEpgReader(context);
+ }
+
+ private EpgFetcher(Context context) {
+ mContext = context.getApplicationContext();
+ ApplicationSingletons applicationSingletons = TvApplication.getSingletons(mContext);
+ mChannelDataManager = applicationSingletons.getChannelDataManager();
+ mPerformanceMonitor = applicationSingletons.getPerformanceMonitor();
+ mEpgReader = createEpgReader(mContext, LocationUtils.getCurrentCountry(mContext));
+
+ int remoteInteval =
+ (int)
+ RemoteConfigUtils.getRemoteConfig(
+ context, KEY_ROUTINE_INTERVAL, DEFAULT_ROUTINE_INTERVAL_HOUR);
+ mRoutineIntervalMs =
+ remoteInteval < 0
+ ? TimeUnit.HOURS.toMillis(DEFAULT_ROUTINE_INTERVAL_HOUR)
+ : TimeUnit.HOURS.toMillis(remoteInteval);
+ mEpgDataExpiredTimeLimitMs = mRoutineIntervalMs * 2;
+ mFastFetchDurationSec = FAST_FETCH_DURATION_SEC + mRoutineIntervalMs / 1000;
+ }
/**
* Starts the routine service of EPG fetching. It use {@link JobScheduler} to schedule the EPG
@@ -30,30 +155,590 @@ public interface EpgFetcher {
* channel scanning of tuner input is started.
*/
@MainThread
- void startRoutineService();
+ public void startRoutineService() {
+ JobScheduler jobScheduler =
+ (JobScheduler) mContext.getSystemService(Context.JOB_SCHEDULER_SERVICE);
+ for (JobInfo job : jobScheduler.getAllPendingJobs()) {
+ if (job.getId() == EPG_ROUTINELY_FETCHING_JOB_ID) {
+ return;
+ }
+ }
+ JobInfo job =
+ new JobInfo.Builder(
+ EPG_ROUTINELY_FETCHING_JOB_ID,
+ new ComponentName(mContext, EpgFetchService.class))
+ .setPeriodic(mRoutineIntervalMs)
+ .setBackoffCriteria(INITIAL_BACKOFF_MS, JobInfo.BACKOFF_POLICY_EXPONENTIAL)
+ .setPersisted(true)
+ .build();
+ jobScheduler.schedule(job);
+ Log.i(TAG, "EPG fetching routine service started.");
+ }
/**
* Fetches EPG immediately if current EPG data are out-dated, i.e., not successfully updated by
* routine fetching service due to various reasons.
*/
@MainThread
- void fetchImmediatelyIfNeeded();
+ public void fetchImmediatelyIfNeeded() {
+ if (TvCommonUtils.isRunningInTest()) {
+ // Do not run EpgFetcher in test.
+ return;
+ }
+ new AsyncTask<Void, Void, Long>() {
+ @Override
+ protected Long doInBackground(Void... args) {
+ return EpgFetchHelper.getLastEpgUpdatedTimestamp(mContext);
+ }
+
+ @Override
+ protected void onPostExecute(Long result) {
+ if (System.currentTimeMillis() - EpgFetchHelper.getLastEpgUpdatedTimestamp(mContext)
+ > mEpgDataExpiredTimeLimitMs) {
+ Log.i(TAG, "EPG data expired. Start fetching immediately.");
+ fetchImmediately();
+ }
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
/** Fetches EPG immediately. */
@MainThread
- void fetchImmediately();
+ public void fetchImmediately() {
+ if (!mChannelDataManager.isDbLoadFinished()) {
+ mChannelDataManager.addListener(
+ new ChannelDataManager.Listener() {
+ @Override
+ public void onLoadFinished() {
+ mChannelDataManager.removeListener(this);
+ executeFetchTaskIfPossible(null, null);
+ }
+
+ @Override
+ public void onChannelListUpdated() {}
+
+ @Override
+ public void onChannelBrowsableChanged() {}
+ });
+ } else {
+ executeFetchTaskIfPossible(null, null);
+ }
+ }
/** Notifies EPG fetch service that channel scanning is started. */
@MainThread
- void onChannelScanStarted();
+ public void onChannelScanStarted() {
+ if (mScanStarted || !Features.ENABLE_CLOUD_EPG_REGION.isEnabled(mContext)) {
+ return;
+ }
+ mScanStarted = true;
+ stopFetchingJob();
+ synchronized (mFetchDuringScanHandlerLock) {
+ if (mFetchDuringScanHandler == null) {
+ HandlerThread thread = new HandlerThread("EpgFetchDuringScan");
+ thread.start();
+ mFetchDuringScanHandler = new FetchDuringScanHandler(thread.getLooper());
+ }
+ mFetchDuringScanHandler.sendEmptyMessage(MSG_PREPARE_FETCH_DURING_SCAN);
+ }
+ Log.i(TAG, "EPG fetching on channel scanning started.");
+ }
/** Notifies EPG fetch service that channel scanning is finished. */
@MainThread
- void onChannelScanFinished();
+ public void onChannelScanFinished() {
+ if (!mScanStarted) {
+ return;
+ }
+ mScanStarted = false;
+ mFetchDuringScanHandler.sendEmptyMessage(MSG_FINISH_FETCH_DURING_SCAN);
+ }
@MainThread
- boolean executeFetchTaskIfPossible(JobService jobService, JobParameters params);
+ private void stopFetchingJob() {
+ if (DEBUG) Log.d(TAG, "Try to stop routinely fetching job...");
+ if (mFetchTask != null) {
+ mFetchTask.cancel(true);
+ mFetchTask = null;
+ Log.i(TAG, "EPG routinely fetching job stopped.");
+ }
+ }
@MainThread
- void stopFetchingJob();
+ private boolean executeFetchTaskIfPossible(JobService service, JobParameters params) {
+ SoftPreconditions.checkState(mChannelDataManager.isDbLoadFinished());
+ if (!TvCommonUtils.isRunningInTest() && checkFetchPrerequisite()) {
+ mFetchTask = new FetchAsyncTask(service, params);
+ mFetchTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ return true;
+ }
+ return false;
+ }
+
+ @MainThread
+ private boolean checkFetchPrerequisite() {
+ if (DEBUG) Log.d(TAG, "Check prerequisite of routinely fetching job.");
+ if (!Features.ENABLE_CLOUD_EPG_REGION.isEnabled(mContext)) {
+ Log.i(
+ TAG,
+ "Cannot start routine service: country not supported: "
+ + LocationUtils.getCurrentCountry(mContext));
+ return false;
+ }
+ if (mFetchTask != null) {
+ // Fetching job is already running or ready to run, no need to start again.
+ return false;
+ }
+ if (mFetchDuringScanHandler != null) {
+ if (DEBUG) Log.d(TAG, "Cannot start routine service: scanning channels.");
+ return false;
+ }
+ if (getTunerChannelCount() == 0) {
+ if (DEBUG) Log.d(TAG, "Cannot start routine service: no internal tuner channels.");
+ return false;
+ }
+ if (!TextUtils.isEmpty(EpgFetchHelper.getLastLineupId(mContext))) {
+ return true;
+ }
+ if (!TextUtils.isEmpty(PostalCodeUtils.getLastPostalCode(mContext))) {
+ return true;
+ }
+ return true;
+ }
+
+ @MainThread
+ private int getTunerChannelCount() {
+ for (TvInputInfo input :
+ TvApplication.getSingletons(mContext)
+ .getTvInputManagerHelper()
+ .getTvInputInfos(true, true)) {
+ String inputId = input.getId();
+ if (Utils.isInternalTvInput(mContext, inputId)) {
+ return mChannelDataManager.getChannelCountForInput(inputId);
+ }
+ }
+ return 0;
+ }
+
+ @AnyThread
+ private void clearUnusedLineups(@Nullable String lineupId) {
+ synchronized (mPossibleLineupsLock) {
+ if (mPossibleLineups == null) {
+ return;
+ }
+ for (Lineup lineup : mPossibleLineups) {
+ if (!TextUtils.equals(lineupId, lineup.id)) {
+ mEpgReader.clearCachedChannels(lineup.id);
+ }
+ }
+ mPossibleLineups = null;
+ }
+ }
+
+ @WorkerThread
+ private Integer prepareFetchEpg(boolean forceUpdatePossibleLineups) {
+ if (!mEpgReader.isAvailable()) {
+ Log.i(TAG, "EPG reader is temporarily unavailable.");
+ return REASON_EPG_READER_NOT_READY;
+ }
+ // Checks the EPG Timestamp.
+ mEpgTimeStamp = mEpgReader.getEpgTimestamp();
+ if (mEpgTimeStamp <= EpgFetchHelper.getLastEpgUpdatedTimestamp(mContext)) {
+ if (DEBUG) Log.d(TAG, "No new EPG.");
+ return REASON_NO_NEW_EPG;
+ }
+ // Updates postal code.
+ boolean postalCodeChanged = false;
+ try {
+ postalCodeChanged = PostalCodeUtils.updatePostalCode(mContext);
+ } catch (IOException e) {
+ if (DEBUG) Log.d(TAG, "Couldn't get the current location.", e);
+ if (TextUtils.isEmpty(PostalCodeUtils.getLastPostalCode(mContext))) {
+ return REASON_LOCATION_INFO_UNAVAILABLE;
+ }
+ } catch (SecurityException e) {
+ Log.w(TAG, "No permission to get the current location.");
+ if (TextUtils.isEmpty(PostalCodeUtils.getLastPostalCode(mContext))) {
+ return REASON_LOCATION_PERMISSION_NOT_GRANTED;
+ }
+ } catch (PostalCodeUtils.NoPostalCodeException e) {
+ Log.i(TAG, "Cannot get address or postal code.");
+ return REASON_LOCATION_INFO_UNAVAILABLE;
+ }
+ // Updates possible lineups if necessary.
+ SoftPreconditions.checkState(mPossibleLineups == null, TAG, "Possible lineups not reset.");
+ if (postalCodeChanged
+ || forceUpdatePossibleLineups
+ || EpgFetchHelper.getLastLineupId(mContext) == null) {
+ // To prevent main thread being blocked, though theoretically it should not happen.
+ List<Lineup> possibleLineups =
+ mEpgReader.getLineups(PostalCodeUtils.getLastPostalCode(mContext));
+ if (possibleLineups.isEmpty()) {
+ return REASON_NO_EPG_DATA_RETURNED;
+ }
+ for (Lineup lineup : possibleLineups) {
+ mEpgReader.preloadChannels(lineup.id);
+ }
+ synchronized (mPossibleLineupsLock) {
+ mPossibleLineups = possibleLineups;
+ }
+ EpgFetchHelper.setLastLineupId(mContext, null);
+ }
+ return null;
+ }
+
+ @WorkerThread
+ private void batchFetchEpg(List<Channel> channels, long durationSec) {
+ Log.i(TAG, "Start batch fetching (" + durationSec + ")...." + channels.size());
+ if (channels.size() == 0) {
+ return;
+ }
+ List<Long> queryChannelIds = new ArrayList<>(QUERY_CHANNEL_COUNT);
+ for (Channel channel : channels) {
+ queryChannelIds.add(channel.getId());
+ if (queryChannelIds.size() >= QUERY_CHANNEL_COUNT) {
+ batchUpdateEpg(mEpgReader.getPrograms(queryChannelIds, durationSec));
+ queryChannelIds.clear();
+ }
+ }
+ if (!queryChannelIds.isEmpty()) {
+ batchUpdateEpg(mEpgReader.getPrograms(queryChannelIds, durationSec));
+ }
+ }
+
+ @WorkerThread
+ private void batchUpdateEpg(Map<Long, List<Program>> allPrograms) {
+ for (Map.Entry<Long, List<Program>> entry : allPrograms.entrySet()) {
+ List<Program> programs = entry.getValue();
+ if (programs == null) {
+ continue;
+ }
+ Collections.sort(programs);
+ Log.i(
+ TAG,
+ "Batch fetched " + programs.size() + " programs for channel " + entry.getKey());
+ EpgFetchHelper.updateEpgData(mContext, entry.getKey(), programs);
+ }
+ }
+
+ @Nullable
+ @WorkerThread
+ private String pickBestLineupId(List<Channel> currentChannelList) {
+ String maxLineupId = null;
+ synchronized (mPossibleLineupsLock) {
+ if (mPossibleLineups == null) {
+ return null;
+ }
+ int maxCount = 0;
+ for (Lineup lineup : mPossibleLineups) {
+ int count = getMatchedChannelCount(lineup.id, currentChannelList);
+ Log.i(TAG, lineup.name + " (" + lineup.id + ") - " + count + " matches");
+ if (count > maxCount) {
+ maxCount = count;
+ maxLineupId = lineup.id;
+ }
+ }
+ }
+ return maxLineupId;
+ }
+
+ @WorkerThread
+ private int getMatchedChannelCount(String lineupId, List<Channel> currentChannelList) {
+ // Construct a list of display numbers for existing channels.
+ if (currentChannelList.isEmpty()) {
+ if (DEBUG) Log.d(TAG, "No existing channel to compare");
+ return 0;
+ }
+ List<String> numbers = new ArrayList<>(currentChannelList.size());
+ for (Channel channel : currentChannelList) {
+ // We only support channels from internal tuner inputs.
+ if (Utils.isInternalTvInput(mContext, channel.getInputId())) {
+ numbers.add(channel.getDisplayNumber());
+ }
+ }
+ numbers.retainAll(mEpgReader.getChannelNumbers(lineupId));
+ return numbers.size();
+ }
+
+ public static class EpgFetchService extends JobService {
+ private EpgFetcher mEpgFetcher;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ TvApplication.setCurrentRunningProcess(this, true);
+ mEpgFetcher = EpgFetcher.getInstance(this);
+ }
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ if (!mEpgFetcher.mChannelDataManager.isDbLoadFinished()) {
+ mEpgFetcher.mChannelDataManager.addListener(
+ new ChannelDataManager.Listener() {
+ @Override
+ public void onLoadFinished() {
+ mEpgFetcher.mChannelDataManager.removeListener(this);
+ if (!mEpgFetcher.executeFetchTaskIfPossible(
+ EpgFetchService.this, params)) {
+ jobFinished(params, false);
+ }
+ }
+
+ @Override
+ public void onChannelListUpdated() {}
+
+ @Override
+ public void onChannelBrowsableChanged() {}
+ });
+ return true;
+ } else {
+ return mEpgFetcher.executeFetchTaskIfPossible(this, params);
+ }
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ mEpgFetcher.stopFetchingJob();
+ return false;
+ }
+ }
+
+ private class FetchAsyncTask extends AsyncTask<Void, Void, Integer> {
+ private final JobService mService;
+ private final JobParameters mParams;
+ private List<Channel> mCurrentChannelList;
+ private TimerEvent mTimerEvent;
+
+ private FetchAsyncTask(JobService service, JobParameters params) {
+ mService = service;
+ mParams = params;
+ }
+
+ @Override
+ protected void onPreExecute() {
+ mTimerEvent = mPerformanceMonitor.startTimer();
+ mCurrentChannelList = mChannelDataManager.getChannelList();
+ }
+
+ @Override
+ protected Integer doInBackground(Void... args) {
+ final int oldTag = TrafficStats.getThreadStatsTag();
+ TrafficStats.setThreadStatsTag(NetworkTrafficTags.EPG_FETCH);
+ try {
+ if (DEBUG) Log.d(TAG, "Start EPG routinely fetching.");
+ Integer failureReason = prepareFetchEpg(false);
+ // InterruptedException might be caught by RPC, we should check it here.
+ if (failureReason != null || this.isCancelled()) {
+ return failureReason;
+ }
+ String lineupId = EpgFetchHelper.getLastLineupId(mContext);
+ lineupId = lineupId == null ? pickBestLineupId(mCurrentChannelList) : lineupId;
+ if (lineupId != null) {
+ Log.i(TAG, "Selecting the lineup " + lineupId);
+ // During normal fetching process, the lineup ID should be confirmed since all
+ // channels are known, clear up possible lineups to save resources.
+ EpgFetchHelper.setLastLineupId(mContext, lineupId);
+ clearUnusedLineups(lineupId);
+ } else {
+ Log.i(TAG, "Failed to get lineup id");
+ return REASON_NO_EPG_DATA_RETURNED;
+ }
+ final List<Channel> channels = mEpgReader.getChannels(lineupId);
+ // InterruptedException might be caught by RPC, we should check it here.
+ if (this.isCancelled()) {
+ return null;
+ }
+ if (channels.isEmpty()) {
+ Log.i(TAG, "Failed to get EPG channels.");
+ return REASON_NO_EPG_DATA_RETURNED;
+ }
+ if (System.currentTimeMillis() - EpgFetchHelper.getLastEpgUpdatedTimestamp(mContext)
+ > mEpgDataExpiredTimeLimitMs) {
+ batchFetchEpg(channels, mFastFetchDurationSec);
+ }
+ new Handler(mContext.getMainLooper())
+ .post(
+ new Runnable() {
+ @Override
+ public void run() {
+ ChannelLogoFetcher.startFetchingChannelLogos(
+ mContext, channels);
+ }
+ });
+ for (Channel channel : channels) {
+ if (this.isCancelled()) {
+ return null;
+ }
+ long channelId = channel.getId();
+ List<Program> programs = new ArrayList<>(mEpgReader.getPrograms(channelId));
+ // InterruptedException might be caught by RPC, we should check it here.
+ Collections.sort(programs);
+ Log.i(TAG, "Fetched " + programs.size() + " programs for channel " + channelId);
+ EpgFetchHelper.updateEpgData(mContext, channelId, programs);
+ }
+ EpgFetchHelper.setLastEpgUpdatedTimestamp(mContext, mEpgTimeStamp);
+ if (DEBUG) Log.d(TAG, "Fetching EPG is finished.");
+ return null;
+ } finally {
+ TrafficStats.setThreadStatsTag(oldTag);
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Integer failureReason) {
+ mFetchTask = null;
+ if (failureReason == null
+ || failureReason == REASON_LOCATION_PERMISSION_NOT_GRANTED
+ || failureReason == REASON_NO_NEW_EPG) {
+ jobFinished(false);
+ } else {
+ // Applies back-off policy
+ jobFinished(true);
+ }
+ mPerformanceMonitor.stopTimer(mTimerEvent, EventNames.FETCH_EPG_TASK);
+ mPerformanceMonitor.recordMemory(EventNames.FETCH_EPG_TASK);
+ }
+
+ @Override
+ protected void onCancelled(Integer failureReason) {
+ clearUnusedLineups(null);
+ jobFinished(false);
+ }
+
+ private void jobFinished(boolean reschedule) {
+ if (mService != null && mParams != null) {
+ // Task is executed from JobService, need to report jobFinished.
+ mService.jobFinished(mParams, reschedule);
+ }
+ }
+ }
+
+ @WorkerThread
+ private class FetchDuringScanHandler extends Handler {
+ private final Set<Long> mFetchedChannelIdsDuringScan = new HashSet<>();
+ private String mPossibleLineupId;
+
+ private final ChannelDataManager.Listener mDuringScanChannelListener =
+ new ChannelDataManager.Listener() {
+ @Override
+ public void onLoadFinished() {
+ if (DEBUG) Log.d(TAG, "ChannelDataManager.onLoadFinished()");
+ if (getTunerChannelCount() >= MINIMUM_CHANNELS_TO_DECIDE_LINEUP
+ && !hasMessages(MSG_CHANNEL_UPDATED_DURING_SCAN)) {
+ Message.obtain(
+ FetchDuringScanHandler.this,
+ MSG_CHANNEL_UPDATED_DURING_SCAN,
+ new ArrayList<>(mChannelDataManager.getChannelList()))
+ .sendToTarget();
+ }
+ }
+
+ @Override
+ public void onChannelListUpdated() {
+ if (DEBUG) Log.d(TAG, "ChannelDataManager.onChannelListUpdated()");
+ if (getTunerChannelCount() >= MINIMUM_CHANNELS_TO_DECIDE_LINEUP
+ && !hasMessages(MSG_CHANNEL_UPDATED_DURING_SCAN)) {
+ Message.obtain(
+ FetchDuringScanHandler.this,
+ MSG_CHANNEL_UPDATED_DURING_SCAN,
+ mChannelDataManager.getChannelList())
+ .sendToTarget();
+ }
+ }
+
+ @Override
+ public void onChannelBrowsableChanged() {
+ // Do nothing
+ }
+ };
+
+ @AnyThread
+ private FetchDuringScanHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_PREPARE_FETCH_DURING_SCAN:
+ case MSG_RETRY_PREPARE_FETCH_DURING_SCAN:
+ onPrepareFetchDuringScan();
+ break;
+ case MSG_CHANNEL_UPDATED_DURING_SCAN:
+ if (!hasMessages(MSG_CHANNEL_UPDATED_DURING_SCAN)) {
+ onChannelUpdatedDuringScan((List<Channel>) msg.obj);
+ }
+ break;
+ case MSG_FINISH_FETCH_DURING_SCAN:
+ removeMessages(MSG_RETRY_PREPARE_FETCH_DURING_SCAN);
+ if (hasMessages(MSG_CHANNEL_UPDATED_DURING_SCAN)) {
+ sendEmptyMessage(MSG_FINISH_FETCH_DURING_SCAN);
+ } else {
+ onFinishFetchDuringScan();
+ }
+ break;
+ }
+ }
+
+ private void onPrepareFetchDuringScan() {
+ Integer failureReason = prepareFetchEpg(true);
+ if (failureReason != null) {
+ sendEmptyMessageDelayed(
+ MSG_RETRY_PREPARE_FETCH_DURING_SCAN, FETCH_DURING_SCAN_WAIT_TIME_MS);
+ return;
+ }
+ mChannelDataManager.addListener(mDuringScanChannelListener);
+ }
+
+ private void onChannelUpdatedDuringScan(List<Channel> currentChannelList) {
+ String lineupId = pickBestLineupId(currentChannelList);
+ Log.i(TAG, "Fast fetch channels for lineup ID: " + lineupId);
+ if (TextUtils.isEmpty(lineupId)) {
+ if (TextUtils.isEmpty(mPossibleLineupId)) {
+ return;
+ }
+ } else if (!TextUtils.equals(lineupId, mPossibleLineupId)) {
+ mFetchedChannelIdsDuringScan.clear();
+ mPossibleLineupId = lineupId;
+ }
+ List<Long> currentChannelIds = new ArrayList<>();
+ for (Channel channel : currentChannelList) {
+ currentChannelIds.add(channel.getId());
+ }
+ mFetchedChannelIdsDuringScan.retainAll(currentChannelIds);
+ List<Channel> newChannels = new ArrayList<>();
+ for (Channel channel : mEpgReader.getChannels(mPossibleLineupId)) {
+ if (!mFetchedChannelIdsDuringScan.contains(channel.getId())) {
+ newChannels.add(channel);
+ mFetchedChannelIdsDuringScan.add(channel.getId());
+ }
+ }
+ batchFetchEpg(newChannels, FETCH_DURING_SCAN_DURATION_SEC);
+ }
+
+ private void onFinishFetchDuringScan() {
+ mChannelDataManager.removeListener(mDuringScanChannelListener);
+ EpgFetchHelper.setLastLineupId(mContext, mPossibleLineupId);
+ clearUnusedLineups(null);
+ mFetchedChannelIdsDuringScan.clear();
+ synchronized (mFetchDuringScanHandlerLock) {
+ if (!hasMessages(MSG_PREPARE_FETCH_DURING_SCAN)) {
+ removeCallbacksAndMessages(null);
+ getLooper().quit();
+ mFetchDuringScanHandler = null;
+ }
+ }
+ // Clear timestamp to make routine service start right away.
+ EpgFetchHelper.setLastEpgUpdatedTimestamp(mContext, 0);
+ Log.i(TAG, "EPG Fetching during channel scanning finished.");
+ new Handler(Looper.getMainLooper())
+ .post(
+ new Runnable() {
+ @Override
+ public void run() {
+ fetchImmediately();
+ }
+ });
+ }
+ }
}
diff --git a/src/com/android/tv/data/epg/EpgFetcherImpl.java b/src/com/android/tv/data/epg/EpgFetcherImpl.java
deleted file mode 100644
index 523fc50c..00000000
--- a/src/com/android/tv/data/epg/EpgFetcherImpl.java
+++ /dev/null
@@ -1,814 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.data.epg;
-
-import android.app.job.JobInfo;
-import android.app.job.JobParameters;
-import android.app.job.JobScheduler;
-import android.app.job.JobService;
-import android.content.ComponentName;
-import android.content.Context;
-import android.database.Cursor;
-import android.media.tv.TvContract;
-import android.media.tv.TvInputInfo;
-import android.net.TrafficStats;
-import android.os.AsyncTask;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.support.annotation.AnyThread;
-import android.support.annotation.MainThread;
-import android.support.annotation.Nullable;
-import android.support.annotation.VisibleForTesting;
-import android.support.annotation.WorkerThread;
-import android.text.TextUtils;
-import android.util.Log;
-import com.android.tv.TvFeatures;
-import com.android.tv.TvSingletons;
-import com.android.tv.common.BuildConfig;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.common.config.RemoteConfigUtils;
-import com.android.tv.common.util.Clock;
-import com.android.tv.common.util.CommonUtils;
-import com.android.tv.common.util.LocationUtils;
-import com.android.tv.common.util.NetworkTrafficTags;
-import com.android.tv.common.util.PermissionUtils;
-import com.android.tv.common.util.PostalCodeUtils;
-import com.android.tv.data.Channel;
-import com.android.tv.data.ChannelDataManager;
-import com.android.tv.data.ChannelLogoFetcher;
-import com.android.tv.data.Lineup;
-import com.android.tv.data.Program;
-
-
-import com.android.tv.perf.EventNames;
-import com.android.tv.perf.PerformanceMonitor;
-import com.android.tv.perf.TimerEvent;
-import com.android.tv.util.Utils;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-/**
- * The service class to fetch EPG routinely or on-demand during channel scanning
- *
- * <p>Since the default executor of {@link AsyncTask} is {@link AsyncTask#SERIAL_EXECUTOR}, only one
- * task can run at a time. Because fetching EPG takes long time, the fetching task shouldn't run on
- * the serial executor. Instead, it should run on the {@link AsyncTask#THREAD_POOL_EXECUTOR}.
- */
-@SuppressWarnings("TryWithResources") // TODO(b/62143348): remove when error prone check fixed
-public class EpgFetcherImpl implements EpgFetcher {
- private static final String TAG = "EpgFetcherImpl";
- private static final boolean DEBUG = false;
-
- private static final int EPG_ROUTINELY_FETCHING_JOB_ID = 101;
-
- private static final long INITIAL_BACKOFF_MS = TimeUnit.SECONDS.toMillis(10);
-
- @VisibleForTesting static final int REASON_EPG_READER_NOT_READY = 1;
- @VisibleForTesting static final int REASON_LOCATION_INFO_UNAVAILABLE = 2;
- @VisibleForTesting static final int REASON_LOCATION_PERMISSION_NOT_GRANTED = 3;
- @VisibleForTesting static final int REASON_NO_EPG_DATA_RETURNED = 4;
- @VisibleForTesting static final int REASON_NO_NEW_EPG = 5;
- @VisibleForTesting static final int REASON_ERROR = 6;
- @VisibleForTesting static final int REASON_CLOUD_EPG_FAILURE = 7;
- @VisibleForTesting static final int REASON_NO_BUILT_IN_CHANNELS = 8;
-
- private static final long FETCH_DURING_SCAN_WAIT_TIME_MS = TimeUnit.SECONDS.toMillis(10);
-
- private static final long FETCH_DURING_SCAN_DURATION_SEC = TimeUnit.HOURS.toSeconds(3);
- private static final long FAST_FETCH_DURATION_SEC = TimeUnit.DAYS.toSeconds(2);
-
- private static final int DEFAULT_ROUTINE_INTERVAL_HOUR = 4;
- private static final String KEY_ROUTINE_INTERVAL = "live_channels_epg_fetcher_interval_hour";
-
- private static final int MSG_PREPARE_FETCH_DURING_SCAN = 1;
- private static final int MSG_CHANNEL_UPDATED_DURING_SCAN = 2;
- private static final int MSG_FINISH_FETCH_DURING_SCAN = 3;
- private static final int MSG_RETRY_PREPARE_FETCH_DURING_SCAN = 4;
-
- private static final int QUERY_CHANNEL_COUNT = 50;
- private static final int MINIMUM_CHANNELS_TO_DECIDE_LINEUP = 3;
-
- private final Context mContext;
- private final ChannelDataManager mChannelDataManager;
- private final EpgReader mEpgReader;
- private final PerformanceMonitor mPerformanceMonitor;
- private FetchAsyncTask mFetchTask;
- private FetchDuringScanHandler mFetchDuringScanHandler;
- private long mEpgTimeStamp;
- private List<Lineup> mPossibleLineups;
- private final Object mPossibleLineupsLock = new Object();
- private final Object mFetchDuringScanHandlerLock = new Object();
- // A flag to block the re-entrance of onChannelScanStarted and onChannelScanFinished.
- private boolean mScanStarted;
-
- private final long mRoutineIntervalMs;
- private final long mEpgDataExpiredTimeLimitMs;
- private final long mFastFetchDurationSec;
- private Clock mClock;
-
- public static EpgFetcher create(Context context) {
- context = context.getApplicationContext();
- TvSingletons tvSingletons = TvSingletons.getSingletons(context);
- ChannelDataManager channelDataManager = tvSingletons.getChannelDataManager();
- PerformanceMonitor performanceMonitor = tvSingletons.getPerformanceMonitor();
- EpgReader epgReader = tvSingletons.providesEpgReader().get();
- Clock clock = tvSingletons.getClock();
- int routineIntervalMs =
- (int)
- RemoteConfigUtils.getRemoteConfig(
- context, KEY_ROUTINE_INTERVAL, DEFAULT_ROUTINE_INTERVAL_HOUR);
-
- return new EpgFetcherImpl(
- context,
- channelDataManager,
- epgReader,
- performanceMonitor,
- clock,
- routineIntervalMs);
- }
-
- @VisibleForTesting
- EpgFetcherImpl(
- Context context,
- ChannelDataManager channelDataManager,
- EpgReader epgReader,
- PerformanceMonitor performanceMonitor,
- Clock clock,
- long routineIntervalMs) {
- mContext = context;
- mChannelDataManager = channelDataManager;
- mEpgReader = epgReader;
- mPerformanceMonitor = performanceMonitor;
- mClock = clock;
- mRoutineIntervalMs =
- routineIntervalMs <= 0
- ? TimeUnit.HOURS.toMillis(DEFAULT_ROUTINE_INTERVAL_HOUR)
- : TimeUnit.HOURS.toMillis(routineIntervalMs);
- mEpgDataExpiredTimeLimitMs = routineIntervalMs * 2;
- mFastFetchDurationSec = FAST_FETCH_DURATION_SEC + routineIntervalMs / 1000;
- }
-
- private static Set<Channel> getExistingChannelsForMyPackage(Context context) {
- HashSet<Channel> channels = new HashSet<>();
- String selection = null;
- String[] selectionArgs = null;
- String myPackageName = context.getPackageName();
- if (PermissionUtils.hasAccessAllEpg(context)) {
- selection = "package_name=?";
- selectionArgs = new String[] {myPackageName};
- }
- try (Cursor c =
- context.getContentResolver()
- .query(
- TvContract.Channels.CONTENT_URI,
- Channel.PROJECTION,
- selection,
- selectionArgs,
- null)) {
- if (c != null) {
- while (c.moveToNext()) {
- Channel channel = Channel.fromCursor(c);
- if (DEBUG) Log.d(TAG, "Found " + channel);
- if (myPackageName.equals(channel.getPackageName())) {
- channels.add(channel);
- }
- }
- }
- }
- if (DEBUG)
- Log.d(TAG, "Found " + channels.size() + " channels for package " + myPackageName);
- return channels;
- }
-
- @Override
- @MainThread
- public void startRoutineService() {
- JobScheduler jobScheduler =
- (JobScheduler) mContext.getSystemService(Context.JOB_SCHEDULER_SERVICE);
- for (JobInfo job : jobScheduler.getAllPendingJobs()) {
- if (job.getId() == EPG_ROUTINELY_FETCHING_JOB_ID) {
- return;
- }
- }
- JobInfo job =
- new JobInfo.Builder(
- EPG_ROUTINELY_FETCHING_JOB_ID,
- new ComponentName(mContext, EpgFetchService.class))
- .setPeriodic(mRoutineIntervalMs)
- .setBackoffCriteria(INITIAL_BACKOFF_MS, JobInfo.BACKOFF_POLICY_EXPONENTIAL)
- .setPersisted(true)
- .build();
- jobScheduler.schedule(job);
- Log.i(TAG, "EPG fetching routine service started.");
- }
-
- @Override
- @MainThread
- public void fetchImmediatelyIfNeeded() {
- if (CommonUtils.isRunningInTest()) {
- // Do not run EpgFetcher in test.
- return;
- }
- new AsyncTask<Void, Void, Long>() {
- @Override
- protected Long doInBackground(Void... args) {
- return EpgFetchHelper.getLastEpgUpdatedTimestamp(mContext);
- }
-
- @Override
- protected void onPostExecute(Long result) {
- if (mClock.currentTimeMillis() - EpgFetchHelper.getLastEpgUpdatedTimestamp(mContext)
- > mEpgDataExpiredTimeLimitMs) {
- Log.i(TAG, "EPG data expired. Start fetching immediately.");
- fetchImmediately();
- }
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- }
-
- @Override
- @MainThread
- public void fetchImmediately() {
- if (DEBUG) Log.d(TAG, "fetchImmediately");
- if (!mChannelDataManager.isDbLoadFinished()) {
- mChannelDataManager.addListener(
- new ChannelDataManager.Listener() {
- @Override
- public void onLoadFinished() {
- mChannelDataManager.removeListener(this);
- executeFetchTaskIfPossible(null, null);
- }
-
- @Override
- public void onChannelListUpdated() {}
-
- @Override
- public void onChannelBrowsableChanged() {}
- });
- } else {
- executeFetchTaskIfPossible(null, null);
- }
- }
-
- @Override
- @MainThread
- public void onChannelScanStarted() {
- if (mScanStarted || !TvFeatures.ENABLE_CLOUD_EPG_REGION.isEnabled(mContext)) {
- return;
- }
- mScanStarted = true;
- stopFetchingJob();
- synchronized (mFetchDuringScanHandlerLock) {
- if (mFetchDuringScanHandler == null) {
- HandlerThread thread = new HandlerThread("EpgFetchDuringScan");
- thread.start();
- mFetchDuringScanHandler = new FetchDuringScanHandler(thread.getLooper());
- }
- mFetchDuringScanHandler.sendEmptyMessage(MSG_PREPARE_FETCH_DURING_SCAN);
- }
- Log.i(TAG, "EPG fetching on channel scanning started.");
- }
-
- @Override
- @MainThread
- public void onChannelScanFinished() {
- if (!mScanStarted) {
- return;
- }
- mScanStarted = false;
- mFetchDuringScanHandler.sendEmptyMessage(MSG_FINISH_FETCH_DURING_SCAN);
- }
-
- @MainThread
- @Override
- public void stopFetchingJob() {
- if (DEBUG) Log.d(TAG, "Try to stop routinely fetching job...");
- if (mFetchTask != null) {
- mFetchTask.cancel(true);
- mFetchTask = null;
- Log.i(TAG, "EPG routinely fetching job stopped.");
- }
- }
-
- @MainThread
- @Override
- public boolean executeFetchTaskIfPossible(JobService service, JobParameters params) {
- if (DEBUG) Log.d(TAG, "executeFetchTaskIfPossible");
- SoftPreconditions.checkState(mChannelDataManager.isDbLoadFinished());
- if (!CommonUtils.isRunningInTest() && checkFetchPrerequisite()) {
- mFetchTask = createFetchTask(service, params);
- mFetchTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- return true;
- }
- return false;
- }
-
- @VisibleForTesting
- FetchAsyncTask createFetchTask(JobService service, JobParameters params) {
- return new FetchAsyncTask(service, params);
- }
-
- @MainThread
- private boolean checkFetchPrerequisite() {
- if (DEBUG) Log.d(TAG, "Check prerequisite of routinely fetching job.");
- if (!TvFeatures.ENABLE_CLOUD_EPG_REGION.isEnabled(mContext)) {
- Log.i(
- TAG,
- "Cannot start routine service: country not supported: "
- + LocationUtils.getCurrentCountry(mContext));
- return false;
- }
- if (mFetchTask != null) {
- // Fetching job is already running or ready to run, no need to start again.
- return false;
- }
- if (mFetchDuringScanHandler != null) {
- if (DEBUG) Log.d(TAG, "Cannot start routine service: scanning channels.");
- return false;
- }
- return true;
- }
-
- @MainThread
- private int getTunerChannelCount() {
- for (TvInputInfo input :
- TvSingletons.getSingletons(mContext)
- .getTvInputManagerHelper()
- .getTvInputInfos(true, true)) {
- String inputId = input.getId();
- if (Utils.isInternalTvInput(mContext, inputId)) {
- return mChannelDataManager.getChannelCountForInput(inputId);
- }
- }
- return 0;
- }
-
- @AnyThread
- private void clearUnusedLineups(@Nullable String lineupId) {
- synchronized (mPossibleLineupsLock) {
- if (mPossibleLineups == null) {
- return;
- }
- for (Lineup lineup : mPossibleLineups) {
- if (!TextUtils.equals(lineupId, lineup.getId())) {
- mEpgReader.clearCachedChannels(lineup.getId());
- }
- }
- mPossibleLineups = null;
- }
- }
-
- @WorkerThread
- private Integer prepareFetchEpg(boolean forceUpdatePossibleLineups) {
- if (!mEpgReader.isAvailable()) {
- Log.i(TAG, "EPG reader is temporarily unavailable.");
- return REASON_EPG_READER_NOT_READY;
- }
- // Checks the EPG Timestamp.
- mEpgTimeStamp = mEpgReader.getEpgTimestamp();
- if (mEpgTimeStamp <= EpgFetchHelper.getLastEpgUpdatedTimestamp(mContext)) {
- if (DEBUG) Log.d(TAG, "No new EPG.");
- return REASON_NO_NEW_EPG;
- }
- // Updates postal code.
- boolean postalCodeChanged = false;
- try {
- postalCodeChanged = PostalCodeUtils.updatePostalCode(mContext);
- } catch (IOException e) {
- if (DEBUG) Log.d(TAG, "Couldn't get the current location.", e);
- if (TextUtils.isEmpty(PostalCodeUtils.getLastPostalCode(mContext))) {
- return REASON_LOCATION_INFO_UNAVAILABLE;
- }
- } catch (SecurityException e) {
- Log.w(TAG, "No permission to get the current location.");
- if (TextUtils.isEmpty(PostalCodeUtils.getLastPostalCode(mContext))) {
- return REASON_LOCATION_PERMISSION_NOT_GRANTED;
- }
- } catch (PostalCodeUtils.NoPostalCodeException e) {
- Log.i(TAG, "Cannot get address or postal code.");
- return REASON_LOCATION_INFO_UNAVAILABLE;
- }
- // Updates possible lineups if necessary.
- SoftPreconditions.checkState(mPossibleLineups == null, TAG, "Possible lineups not reset.");
- if (postalCodeChanged
- || forceUpdatePossibleLineups
- || EpgFetchHelper.getLastLineupId(mContext) == null) {
- // To prevent main thread being blocked, though theoretically it should not happen.
- String lastPostalCode = PostalCodeUtils.getLastPostalCode(mContext);
- List<Lineup> possibleLineups = mEpgReader.getLineups(lastPostalCode);
- if (possibleLineups.isEmpty()) {
- Log.i(TAG, "No lineups found for " + lastPostalCode);
- return REASON_NO_EPG_DATA_RETURNED;
- }
- for (Lineup lineup : possibleLineups) {
- mEpgReader.preloadChannels(lineup.getId());
- }
- synchronized (mPossibleLineupsLock) {
- mPossibleLineups = possibleLineups;
- }
- EpgFetchHelper.setLastLineupId(mContext, null);
- }
- return null;
- }
-
- @WorkerThread
- private void batchFetchEpg(Set<EpgReader.EpgChannel> epgChannels, long durationSec) {
- Log.i(TAG, "Start batch fetching (" + durationSec + ")...." + epgChannels.size());
- if (epgChannels.size() == 0) {
- return;
- }
- Set<EpgReader.EpgChannel> batch = new HashSet<>(QUERY_CHANNEL_COUNT);
- for (EpgReader.EpgChannel epgChannel : epgChannels) {
- batch.add(epgChannel);
- if (batch.size() >= QUERY_CHANNEL_COUNT) {
- batchUpdateEpg(mEpgReader.getPrograms(batch, durationSec));
- batch.clear();
- }
- }
- if (!batch.isEmpty()) {
- batchUpdateEpg(mEpgReader.getPrograms(batch, durationSec));
- }
- }
-
- @WorkerThread
- private void batchUpdateEpg(Map<EpgReader.EpgChannel, Collection<Program>> allPrograms) {
- for (Map.Entry<EpgReader.EpgChannel, Collection<Program>> entry : allPrograms.entrySet()) {
- List<Program> programs = new ArrayList(entry.getValue());
- if (programs == null) {
- continue;
- }
- Collections.sort(programs);
- Log.i(
- TAG,
- "Batch fetched " + programs.size() + " programs for channel " + entry.getKey());
- EpgFetchHelper.updateEpgData(
- mContext, mClock, entry.getKey().getChannel().getId(), programs);
- }
- }
-
- @Nullable
- @WorkerThread
- private String pickBestLineupId(Set<Channel> currentChannels) {
- String maxLineupId = null;
- synchronized (mPossibleLineupsLock) {
- if (mPossibleLineups == null) {
- return null;
- }
- int maxCount = 0;
- for (Lineup lineup : mPossibleLineups) {
- int count = getMatchedChannelCount(lineup.getId(), currentChannels);
- Log.i(TAG, lineup.getName() + " (" + lineup.getId() + ") - " + count + " matches");
- if (count > maxCount) {
- maxCount = count;
- maxLineupId = lineup.getId();
- }
- }
- }
- return maxLineupId;
- }
-
- @WorkerThread
- private int getMatchedChannelCount(String lineupId, Set<Channel> currentChannels) {
- // Construct a list of display numbers for existing channels.
- if (currentChannels.isEmpty()) {
- if (DEBUG) Log.d(TAG, "No existing channel to compare");
- return 0;
- }
- List<String> numbers = new ArrayList<>(currentChannels.size());
- for (Channel channel : currentChannels) {
- // We only support channels from internal tuner inputs.
- if (Utils.isInternalTvInput(mContext, channel.getInputId())) {
- numbers.add(channel.getDisplayNumber());
- }
- }
- numbers.retainAll(mEpgReader.getChannelNumbers(lineupId));
- return numbers.size();
- }
-
- @VisibleForTesting
- class FetchAsyncTask extends AsyncTask<Void, Void, Integer> {
- private final JobService mService;
- private final JobParameters mParams;
- private Set<Channel> mCurrentChannels;
- private TimerEvent mTimerEvent;
-
- private FetchAsyncTask(JobService service, JobParameters params) {
- mService = service;
- mParams = params;
- }
-
- @Override
- protected void onPreExecute() {
- mTimerEvent = mPerformanceMonitor.startTimer();
- mCurrentChannels = new HashSet<>(mChannelDataManager.getChannelList());
- }
-
- @Override
- protected Integer doInBackground(Void... args) {
- final int oldTag = TrafficStats.getThreadStatsTag();
- TrafficStats.setThreadStatsTag(NetworkTrafficTags.EPG_FETCH);
- try {
- if (DEBUG) Log.d(TAG, "Start EPG routinely fetching.");
- Integer builtInResult = fetchEpgForBuiltInTuner();
- boolean anyCloudEpgFailure = false;
- boolean anyCloudEpgSuccess = false;
- return builtInResult;
- } finally {
- TrafficStats.setThreadStatsTag(oldTag);
- }
- }
-
- private Set<Channel> getExistingChannelsFor(String inputId) {
- Set<Channel> result = new HashSet<>();
- try (Cursor cursor =
- mContext.getContentResolver()
- .query(
- TvContract.buildChannelsUriForInput(inputId),
- Channel.PROJECTION,
- null,
- null,
- null)) {
- while (cursor.moveToNext()) {
- result.add(Channel.fromCursor(cursor));
- }
- return result;
- }
- }
-
- private Integer fetchEpgForBuiltInTuner() {
- try {
- Integer failureReason = prepareFetchEpg(false);
- // InterruptedException might be caught by RPC, we should check it here.
- if (failureReason != null || this.isCancelled()) {
- return failureReason;
- }
- String lineupId = EpgFetchHelper.getLastLineupId(mContext);
- lineupId = lineupId == null ? pickBestLineupId(mCurrentChannels) : lineupId;
- if (lineupId != null) {
- Log.i(TAG, "Selecting the lineup " + lineupId);
- // During normal fetching process, the lineup ID should be confirmed since all
- // channels are known, clear up possible lineups to save resources.
- EpgFetchHelper.setLastLineupId(mContext, lineupId);
- clearUnusedLineups(lineupId);
- } else {
- Log.i(TAG, "Failed to get lineup id");
- return REASON_NO_EPG_DATA_RETURNED;
- }
- Set<Channel> existingChannelsForMyPackage =
- getExistingChannelsForMyPackage(mContext);
- if (existingChannelsForMyPackage.isEmpty()) {
- return REASON_NO_BUILT_IN_CHANNELS;
- }
- return fetchEpgFor(lineupId, existingChannelsForMyPackage);
- } catch (Exception e) {
- Log.w(TAG, "Failed to update EPG for builtin tuner", e);
- return REASON_ERROR;
- }
- }
-
- @Nullable
- private Integer fetchEpgFor(String lineupId, Set<Channel> existingChannels) {
- if (DEBUG) {
- Log.d(
- TAG,
- "Starting Fetching EPG is for "
- + lineupId
- + " with channelCount "
- + existingChannels.size());
- }
- final Set<EpgReader.EpgChannel> channels =
- mEpgReader.getChannels(existingChannels, lineupId);
- // InterruptedException might be caught by RPC, we should check it here.
- if (this.isCancelled()) {
- return null;
- }
- if (channels.isEmpty()) {
- Log.i(TAG, "Failed to get EPG channels for " + lineupId);
- return REASON_NO_EPG_DATA_RETURNED;
- }
- if (mClock.currentTimeMillis() - EpgFetchHelper.getLastEpgUpdatedTimestamp(mContext)
- > mEpgDataExpiredTimeLimitMs) {
- batchFetchEpg(channels, mFastFetchDurationSec);
- }
- new Handler(mContext.getMainLooper())
- .post(
- new Runnable() {
- @Override
- public void run() {
- ChannelLogoFetcher.startFetchingChannelLogos(
- mContext, asChannelList(channels));
- }
- });
- for (EpgReader.EpgChannel epgChannel : channels) {
- if (this.isCancelled()) {
- return null;
- }
- List<Program> programs = new ArrayList<>(mEpgReader.getPrograms(epgChannel));
- // InterruptedException might be caught by RPC, we should check it here.
- Collections.sort(programs);
- Log.i(
- TAG,
- "Fetched "
- + programs.size()
- + " programs for channel "
- + epgChannel.getChannel());
- EpgFetchHelper.updateEpgData(
- mContext, mClock, epgChannel.getChannel().getId(), programs);
- }
- EpgFetchHelper.setLastEpgUpdatedTimestamp(mContext, mEpgTimeStamp);
- if (DEBUG) Log.d(TAG, "Fetching EPG is for " + lineupId);
- return null;
- }
-
- @Override
- protected void onPostExecute(Integer failureReason) {
- mFetchTask = null;
- if (failureReason == null
- || failureReason == REASON_LOCATION_PERMISSION_NOT_GRANTED
- || failureReason == REASON_NO_NEW_EPG) {
- jobFinished(false);
- } else {
- // Applies back-off policy
- jobFinished(true);
- }
- mPerformanceMonitor.stopTimer(mTimerEvent, EventNames.FETCH_EPG_TASK);
- mPerformanceMonitor.recordMemory(EventNames.FETCH_EPG_TASK);
- }
-
- @Override
- protected void onCancelled(Integer failureReason) {
- clearUnusedLineups(null);
- jobFinished(false);
- }
-
- private void jobFinished(boolean reschedule) {
- if (mService != null && mParams != null) {
- // Task is executed from JobService, need to report jobFinished.
- mService.jobFinished(mParams, reschedule);
- }
- }
- }
-
- private List<Channel> asChannelList(Set<EpgReader.EpgChannel> epgChannels) {
- List<Channel> result = new ArrayList<>(epgChannels.size());
- for (EpgReader.EpgChannel epgChannel : epgChannels) {
- result.add(epgChannel.getChannel());
- }
- return result;
- }
-
- @WorkerThread
- private class FetchDuringScanHandler extends Handler {
- private final Set<Long> mFetchedChannelIdsDuringScan = new HashSet<>();
- private String mPossibleLineupId;
-
- private final ChannelDataManager.Listener mDuringScanChannelListener =
- new ChannelDataManager.Listener() {
- @Override
- public void onLoadFinished() {
- if (DEBUG) Log.d(TAG, "ChannelDataManager.onLoadFinished()");
- if (getTunerChannelCount() >= MINIMUM_CHANNELS_TO_DECIDE_LINEUP
- && !hasMessages(MSG_CHANNEL_UPDATED_DURING_SCAN)) {
- Message.obtain(
- FetchDuringScanHandler.this,
- MSG_CHANNEL_UPDATED_DURING_SCAN,
- getExistingChannelsForMyPackage(mContext))
- .sendToTarget();
- }
- }
-
- @Override
- public void onChannelListUpdated() {
- if (DEBUG) Log.d(TAG, "ChannelDataManager.onChannelListUpdated()");
- if (getTunerChannelCount() >= MINIMUM_CHANNELS_TO_DECIDE_LINEUP
- && !hasMessages(MSG_CHANNEL_UPDATED_DURING_SCAN)) {
- Message.obtain(
- FetchDuringScanHandler.this,
- MSG_CHANNEL_UPDATED_DURING_SCAN,
- getExistingChannelsForMyPackage(mContext))
- .sendToTarget();
- }
- }
-
- @Override
- public void onChannelBrowsableChanged() {
- // Do nothing
- }
- };
-
- @AnyThread
- private FetchDuringScanHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_PREPARE_FETCH_DURING_SCAN:
- case MSG_RETRY_PREPARE_FETCH_DURING_SCAN:
- onPrepareFetchDuringScan();
- break;
- case MSG_CHANNEL_UPDATED_DURING_SCAN:
- if (!hasMessages(MSG_CHANNEL_UPDATED_DURING_SCAN)) {
- onChannelUpdatedDuringScan((Set<Channel>) msg.obj);
- }
- break;
- case MSG_FINISH_FETCH_DURING_SCAN:
- removeMessages(MSG_RETRY_PREPARE_FETCH_DURING_SCAN);
- if (hasMessages(MSG_CHANNEL_UPDATED_DURING_SCAN)) {
- sendEmptyMessage(MSG_FINISH_FETCH_DURING_SCAN);
- } else {
- onFinishFetchDuringScan();
- }
- break;
- default:
- // do nothing
- }
- }
-
- private void onPrepareFetchDuringScan() {
- Integer failureReason = prepareFetchEpg(true);
- if (failureReason != null) {
- sendEmptyMessageDelayed(
- MSG_RETRY_PREPARE_FETCH_DURING_SCAN, FETCH_DURING_SCAN_WAIT_TIME_MS);
- return;
- }
- mChannelDataManager.addListener(mDuringScanChannelListener);
- }
-
- private void onChannelUpdatedDuringScan(Set<Channel> currentChannels) {
- String lineupId = pickBestLineupId(currentChannels);
- Log.i(TAG, "Fast fetch channels for lineup ID: " + lineupId);
- if (TextUtils.isEmpty(lineupId)) {
- if (TextUtils.isEmpty(mPossibleLineupId)) {
- return;
- }
- } else if (!TextUtils.equals(lineupId, mPossibleLineupId)) {
- mFetchedChannelIdsDuringScan.clear();
- mPossibleLineupId = lineupId;
- }
- List<Long> currentChannelIds = new ArrayList<>();
- for (Channel channel : currentChannels) {
- currentChannelIds.add(channel.getId());
- }
- mFetchedChannelIdsDuringScan.retainAll(currentChannelIds);
- Set<EpgReader.EpgChannel> newChannels = new HashSet<>();
- for (EpgReader.EpgChannel epgChannel :
- mEpgReader.getChannels(currentChannels, mPossibleLineupId)) {
- if (!mFetchedChannelIdsDuringScan.contains(epgChannel.getChannel().getId())) {
- newChannels.add(epgChannel);
- mFetchedChannelIdsDuringScan.add(epgChannel.getChannel().getId());
- }
- }
- batchFetchEpg(newChannels, FETCH_DURING_SCAN_DURATION_SEC);
- }
-
- private void onFinishFetchDuringScan() {
- mChannelDataManager.removeListener(mDuringScanChannelListener);
- EpgFetchHelper.setLastLineupId(mContext, mPossibleLineupId);
- clearUnusedLineups(null);
- mFetchedChannelIdsDuringScan.clear();
- synchronized (mFetchDuringScanHandlerLock) {
- if (!hasMessages(MSG_PREPARE_FETCH_DURING_SCAN)) {
- removeCallbacksAndMessages(null);
- getLooper().quit();
- mFetchDuringScanHandler = null;
- }
- }
- // Clear timestamp to make routine service start right away.
- EpgFetchHelper.setLastEpgUpdatedTimestamp(mContext, 0);
- Log.i(TAG, "EPG Fetching during channel scanning finished.");
- new Handler(Looper.getMainLooper())
- .post(
- new Runnable() {
- @Override
- public void run() {
- fetchImmediately();
- }
- });
- }
- }
-}
diff --git a/src/com/android/tv/data/epg/EpgInputWhiteList.java b/src/com/android/tv/data/epg/EpgInputWhiteList.java
deleted file mode 100644
index de0478fc..00000000
--- a/src/com/android/tv/data/epg/EpgInputWhiteList.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.tv.data.epg;
-
-import android.support.annotation.Nullable;
-import android.support.annotation.VisibleForTesting;
-import android.text.TextUtils;
-import android.util.Log;
-import com.android.tv.common.BuildConfig;
-import com.android.tv.common.config.api.RemoteConfig;
-import com.android.tv.common.experiments.Experiments;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-/** Checks if a package or a input is white listed. */
-public final class EpgInputWhiteList {
- private static final boolean DEBUG = false;
- private static final String TAG = "EpgInputWhiteList";
- @VisibleForTesting public static final String KEY = "live_channels_3rd_party_epg_inputs";
- private static final String QA_DEV_INPUTS =
- "com.example.partnersupportsampletvinput/.SampleTvInputService";
-
- /** Returns the package portion of a inputId */
- @Nullable
- public static String getPackageFromInput(@Nullable String inputId) {
- return inputId == null ? null : inputId.substring(0, inputId.indexOf("/"));
- }
-
- private final RemoteConfig remoteConfig;
-
- public EpgInputWhiteList(RemoteConfig remoteConfig) {
- this.remoteConfig = remoteConfig;
- }
-
- public boolean isInputWhiteListed(String inputId) {
- return getWhiteListedInputs().contains(inputId);
- }
-
- public boolean isPackageWhiteListed(String packageName) {
- if (DEBUG) Log.d(TAG, "isPackageWhiteListed " + packageName);
- Set<String> whiteList = getWhiteListedInputs();
- for (String good : whiteList) {
- try {
- String goodPackage = getPackageFromInput(good);
- if (goodPackage.equals(packageName)) {
- return true;
- }
- } catch (Exception e) {
- if (DEBUG) Log.d(TAG, "Error parsing package name of " + good, e);
- continue;
- }
- }
- return false;
- }
-
- private Set<String> getWhiteListedInputs() {
- Set<String> result = toInputSet(remoteConfig.getString(KEY));
- if (BuildConfig.ENG || Experiments.ENABLE_QA_FEATURES.get()) {
- HashSet<String> moreInputs = new HashSet<>(toInputSet(QA_DEV_INPUTS));
- if (result.isEmpty()) {
- result = moreInputs;
- } else {
- result.addAll(moreInputs);
- }
- }
- if (DEBUG) Log.d(TAG, "getWhiteListedInputs " + result);
- return result;
- }
-
- private Set<String> toInputSet(String value) {
- if (TextUtils.isEmpty(value)) {
- return Collections.EMPTY_SET;
- }
- return new HashSet(Arrays.asList(value.split(",")));
- }
-}
diff --git a/src/com/android/tv/data/epg/EpgReader.java b/src/com/android/tv/data/epg/EpgReader.java
index 9c881439..d10a852c 100644
--- a/src/com/android/tv/data/epg/EpgReader.java
+++ b/src/com/android/tv/data/epg/EpgReader.java
@@ -23,27 +23,12 @@ import com.android.tv.data.Channel;
import com.android.tv.data.Lineup;
import com.android.tv.data.Program;
import com.android.tv.dvr.data.SeriesInfo;
-import java.util.Collection;
import java.util.List;
import java.util.Map;
-import java.util.Set;
/** An interface used to retrieve the EPG data. This class should be used in worker thread. */
@WorkerThread
public interface EpgReader {
-
- /** Value class that holds a EpgChannelId and its corresponding Channel */
- // TODO(b/72052568): Get autovalue to work in aosp
- abstract class EpgChannel {
- public static EpgChannel createEpgChannel(Channel channel, String epgChannelId) {
- return new AutoValue_EpgReader_EpgChannel(channel, epgChannelId);
- }
-
- public abstract Channel getChannel();
-
- public abstract String getEpgChannelId();
- }
-
/** Checks if the reader is available. */
boolean isAvailable();
@@ -70,7 +55,7 @@ public interface EpgReader {
* Returns the list of channels for the given lineup. The returned channels should map into the
* existing channels on the device. This method is usually called after selecting the lineup.
*/
- Set<EpgChannel> getChannels(Set<Channel> inputChannels, @NonNull String lineupId);
+ List<Channel> getChannels(@NonNull String lineupId);
/** Pre-loads and caches channels for a given lineup. */
void preloadChannels(@NonNull String lineupId);
@@ -80,19 +65,18 @@ public interface EpgReader {
void clearCachedChannels(@NonNull String lineupId);
/**
- * Returns the programs for the given channel. Must call {@link #getChannels(Set, String)}
+ * Returns the programs for the given channel. Must call {@link #getChannels(String)}
* beforehand. Note that the {@code Program} doesn't have valid program ID because it's not
* retrieved from TvProvider.
*/
- List<Program> getPrograms(EpgChannel epgChannel);
+ List<Program> getPrograms(long channelId);
/**
* Returns the programs for the given channels. Note that the {@code Program} doesn't have valid
* program ID because it's not retrieved from TvProvider. This method is only used to get
* programs for a short duration typically.
*/
- Map<EpgChannel, Collection<Program>> getPrograms(
- @NonNull Set<EpgChannel> epgChannels, long duration);
+ Map<Long, List<Program>> getPrograms(@NonNull List<Long> channelIds, long duration);
/** Returns the series information for the given series ID. */
SeriesInfo getSeriesInfo(@NonNull String seriesId);
diff --git a/src/com/android/tv/data/epg/StubEpgReader.java b/src/com/android/tv/data/epg/StubEpgReader.java
index 9a87619d..49409a1d 100644
--- a/src/com/android/tv/data/epg/StubEpgReader.java
+++ b/src/com/android/tv/data/epg/StubEpgReader.java
@@ -22,11 +22,9 @@ import com.android.tv.data.Channel;
import com.android.tv.data.Lineup;
import com.android.tv.data.Program;
import com.android.tv.dvr.data.SeriesInfo;
-import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
-import java.util.Set;
/** A stub class to read EPG. */
public class StubEpgReader implements EpgReader {
@@ -58,8 +56,8 @@ public class StubEpgReader implements EpgReader {
}
@Override
- public Set<EpgChannel> getChannels(Set<Channel> inputChannels, @NonNull String lineupId) {
- return Collections.emptySet();
+ public List<Channel> getChannels(@NonNull String lineupId) {
+ return Collections.emptyList();
}
@Override
@@ -73,13 +71,12 @@ public class StubEpgReader implements EpgReader {
}
@Override
- public List<Program> getPrograms(EpgChannel epgChannel) {
+ public List<Program> getPrograms(long channelId) {
return Collections.emptyList();
}
@Override
- public Map<EpgChannel, Collection<Program>> getPrograms(
- @NonNull Set<EpgChannel> channels, long duration) {
+ public Map<Long, List<Program>> getPrograms(@NonNull List<Long> channelIds, long duration) {
return Collections.emptyMap();
}