aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authornchalko <nchalko@google.com>2019-01-24 21:38:34 -0800
committerNick Chalko <nchalko@google.com>2019-02-12 22:15:41 -0800
commit8968c2de57949e34a10d53cc5f2310687a58b3a4 (patch)
treeeb98f026b93a1124c148ff204fe4b209bfe7b7b4
parent6e15fb1aabe572ca629a00b0ac23b7a1450aa749 (diff)
downloadandroid_packages_apps_TV-8968c2de57949e34a10d53cc5f2310687a58b3a4.tar.gz
android_packages_apps_TV-8968c2de57949e34a10d53cc5f2310687a58b3a4.tar.bz2
android_packages_apps_TV-8968c2de57949e34a10d53cc5f2310687a58b3a4.zip
Partial load of EPG data.
Load complete projection of programs on request. It reduces the prefetch time by approx 60%. Cloned from CL 230621408 by 'hg patch'. Original change by nchalko@nchalko:copybara_E799ABBB57696F84DDDEC3F423867CF9_0:10732:citc on 2019/01/23 16:05:31. Live Channels: Import of http://pa/1231437 Change-Id: I83f5d0f0ef5819b9006fa1579f58cd2254444df4 PiperOrigin-RevId: 230845690
-rw-r--r--src/com/android/tv/MainActivity.java8
-rw-r--r--src/com/android/tv/data/Program.java25
-rw-r--r--src/com/android/tv/data/ProgramDataManager.java152
-rw-r--r--src/com/android/tv/guide/ProgramGuide.java8
-rw-r--r--src/com/android/tv/guide/ProgramManager.java31
5 files changed, 183 insertions, 41 deletions
diff --git a/src/com/android/tv/MainActivity.java b/src/com/android/tv/MainActivity.java
index 0384eb99..3da1523a 100644
--- a/src/com/android/tv/MainActivity.java
+++ b/src/com/android/tv/MainActivity.java
@@ -152,6 +152,7 @@ import com.android.tv.util.images.ImageCache;
import com.google.common.base.Optional;
import dagger.android.AndroidInjection;
import dagger.android.ContributesAndroidInjector;
+import com.android.tv.common.flags.BackendKnobsFlags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayDeque;
@@ -276,6 +277,7 @@ public class MainActivity extends Activity
private final DurationTimer mTuneDurationTimer = new DurationTimer();
private DvrManager mDvrManager;
private ConflictChecker mDvrConflictChecker;
+ private BackendKnobsFlags mBackendKnobs;
@Inject SetupUtils mSetupUtils;
@Inject Optional<BuiltInTunerManager> mOptionalBuiltInTunerManager;
@@ -494,6 +496,7 @@ public class MainActivity extends Activity
finishAndRemoveTask();
return;
}
+ mBackendKnobs = tvSingletons.getBackendKnobs();
TvSingletons tvApplication = (TvSingletons) getApplication();
// In API 23, TvContract.isChannelUriForPassthroughInput is hidden.
@@ -2821,6 +2824,11 @@ public class MainActivity extends Activity
Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity.MyOnTuneListener.onTune");
mChannel = channel;
mWasUnderShrunkenTvView = wasUnderShrunkenTvView;
+
+ if (mBackendKnobs.enablePartialProgramFetch()) {
+ // Fetch complete projection of tuned channel.
+ mProgramDataManager.prefetchChannel(channel.getId());
+ }
}
@Override
diff --git a/src/com/android/tv/data/Program.java b/src/com/android/tv/data/Program.java
index 0119db2f..b688927a 100644
--- a/src/com/android/tv/data/Program.java
+++ b/src/com/android/tv/data/Program.java
@@ -89,6 +89,16 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
public static final String[] PROJECTION = createProjection();
+ public static final String[] PARTIAL_PROJECTION = {
+ TvContract.Programs._ID,
+ TvContract.Programs.COLUMN_CHANNEL_ID,
+ TvContract.Programs.COLUMN_TITLE,
+ TvContract.Programs.COLUMN_EPISODE_TITLE,
+ TvContract.Programs.COLUMN_CANONICAL_GENRE,
+ TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
+ TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
+ };
+
private static String[] createProjection() {
return CollectionUtils.concatAll(
PROJECTION_BASE,
@@ -154,6 +164,21 @@ public final class Program extends BaseProgram implements Comparable<Program>, P
return builder.build();
}
+ /** Creates {@code Program} object from cursor. */
+ public static Program fromCursorPartialProjection(Cursor cursor) {
+ // Columns read must match the order of match {@link #PARTIAL_PROJECTION}
+ Builder builder = new Builder();
+ int index = 0;
+ builder.setId(cursor.getLong(index++));
+ builder.setChannelId(cursor.getLong(index++));
+ builder.setTitle(cursor.getString(index++));
+ builder.setEpisodeTitle(cursor.getString(index++));
+ builder.setCanonicalGenres(cursor.getString(index++));
+ builder.setStartTimeUtcMillis(cursor.getLong(index++));
+ builder.setEndTimeUtcMillis(cursor.getLong(index++));
+ return builder.build();
+ }
+
public static Program fromParcel(Parcel in) {
Program program = new Program();
program.mId = in.readLong();
diff --git a/src/com/android/tv/data/ProgramDataManager.java b/src/com/android/tv/data/ProgramDataManager.java
index aa4948e9..54b04f6e 100644
--- a/src/com/android/tv/data/ProgramDataManager.java
+++ b/src/com/android/tv/data/ProgramDataManager.java
@@ -108,14 +108,15 @@ public class ProgramDataManager implements MemoryManageable {
private final MultiLongSparseArray<OnCurrentProgramUpdatedListener>
mChannelId2ProgramUpdatedListeners = new MultiLongSparseArray<>();
private final Handler mHandler;
- private final Set<Listener> mListeners = new ArraySet<>();
+ private final Set<Callback> mCallbacks = new ArraySet<>();
+ private Map<Long, ArrayList<Program>> mChannelIdProgramCache = new ConcurrentHashMap<>();
+ private final Set<Long> mCompleteInfoChannelIds = new HashSet<>();
private final ContentObserver mProgramObserver;
private boolean mPrefetchEnabled;
private long mProgramPrefetchUpdateWaitMs;
private long mLastPrefetchTaskRunMs;
private ProgramsPrefetchTask mProgramsPrefetchTask;
- private Map<Long, ArrayList<Program>> mChannelIdProgramCache = new HashMap<>();
// Any program that ends prior to this time will be removed from the cache
// when a channel's current program is updated.
@@ -258,24 +259,43 @@ public class ProgramDataManager implements MemoryManageable {
}
}
- /** A listener interface to receive notification on program data retrieval from DB. */
- public interface Listener {
+ public void prefetchChannel(long channelId) {
+ if (mCompleteInfoChannelIds.add(channelId)) {
+ long startTimeMs =
+ Utils.floorTime(
+ mClock.currentTimeMillis() - PROGRAM_GUIDE_SNAP_TIME_MS,
+ PROGRAM_GUIDE_SNAP_TIME_MS);
+ long endTimeMs = startTimeMs + TimeUnit.HOURS.toMillis(getFetchDuration());
+ new SingleChannelPrefetchTask(channelId, startTimeMs, endTimeMs).executeOnDbThread();
+ }
+ }
+
+ /** A Callback interface to receive notification on program data retrieval from DB. */
+ public interface Callback {
/**
* Called when a Program data is now available through getProgram() after the DB operation
* is done which wasn't before. This would be called only if fetched data is around the
* selected program.
*/
void onProgramUpdated();
+
+ /**
+ * Called when we update complete program data of specific channel during scrolling. Data is
+ * loaded from DB on request basis.
+ *
+ * @param channelId
+ */
+ void onSingleChannelUpdated(long channelId);
}
- /** Adds the {@link Listener}. */
- public void addListener(Listener listener) {
- mListeners.add(listener);
+ /** Adds the {@link Callback}. */
+ public void addCallback(Callback callback) {
+ mCallbacks.add(callback);
}
- /** Removes the {@link Listener}. */
- public void removeListener(Listener listener) {
- mListeners.remove(listener);
+ /** Removes the {@link Callback}. */
+ public void removeCallback(Callback callback) {
+ mCallbacks.remove(callback);
}
/** Enables or Disables program prefetch. */
@@ -480,26 +500,7 @@ public class ProgramDataManager implements MemoryManageable {
long time = mClock.currentTimeMillis();
mStartTimeMs =
Utils.floorTime(time - PROGRAM_GUIDE_SNAP_TIME_MS, PROGRAM_GUIDE_SNAP_TIME_MS);
- long durationHours;
- if (mChannelIdProgramCache.isEmpty()) {
- durationHours = Math.max(1L, mBackendKnobsFlags.programGuideInitialFetchHours());
- } else {
- int channelCount = mChannelDataManager.getChannelCount();
- long knobsMaxHours = mBackendKnobsFlags.programGuideMaxHours();
- long targetChannelCount = mBackendKnobsFlags.epgTargetChannelCount();
- if (channelCount <= targetChannelCount) {
- durationHours = Math.max(48L, knobsMaxHours);
- } else {
- // 2 days <= duration <= 14 days (336 hours)
- durationHours = knobsMaxHours * targetChannelCount / channelCount;
- if (durationHours < 48L) {
- durationHours = 48L;
- } else if (durationHours > 336L) {
- durationHours = 336L;
- }
- }
- }
- mEndTimeMs = mStartTimeMs + TimeUnit.HOURS.toMillis(durationHours);
+ mEndTimeMs = mStartTimeMs + TimeUnit.HOURS.toMillis(getFetchDuration());
mSuccess = false;
}
@@ -538,7 +539,10 @@ public class ProgramDataManager implements MemoryManageable {
}
programMap.clear();
- String[] projection = Program.PROJECTION;
+ String[] projection =
+ mBackendKnobsFlags.enablePartialProgramFetch()
+ ? Program.PARTIAL_PROJECTION
+ : Program.PROJECTION;
if (TvProviderUtils.checkSeriesIdColumn(mContext, Programs.CONTENT_URI)) {
if (Utils.isProgramsUri(uri)) {
projection = TvProviderUtils.addExtraColumnsToProjection(projection);
@@ -556,7 +560,10 @@ public class ProgramDataManager implements MemoryManageable {
}
return null;
}
- Program program = Program.fromCursor(c);
+ Program program =
+ mBackendKnobsFlags.enablePartialProgramFetch()
+ ? Program.fromCursorPartialProjection(c)
+ : Program.fromCursor(c);
if (Program.isDuplicate(program, lastReadProgram)) {
duplicateCount++;
continue;
@@ -566,6 +573,15 @@ public class ProgramDataManager implements MemoryManageable {
ArrayList<Program> programs = programMap.get(program.getChannelId());
if (programs == null) {
programs = new ArrayList<>();
+ if (mBackendKnobsFlags.enablePartialProgramFetch()) {
+ // To skip already loaded complete data.
+ Program currentProgramInfo =
+ mChannelIdCurrentProgramMap.get(program.getChannelId());
+ if (currentProgramInfo != null
+ && Program.isDuplicate(program, currentProgramInfo)) {
+ program = currentProgramInfo;
+ }
+ }
programMap.put(program.getChannelId(), programs);
}
programs.add(program);
@@ -615,6 +631,10 @@ public class ProgramDataManager implements MemoryManageable {
nextMessageDelayedTime = 0;
}
mChannelIdProgramCache = programs;
+ if (mBackendKnobsFlags.enablePartialProgramFetch()) {
+ // Since cache has partial data we need to reset the map of complete data.
+ mCompleteInfoChannelIds.clear();
+ }
notifyProgramUpdated();
if (mFromEmptyCacheTimeEvent != null) {
mPerformanceMonitor.stopTimer(
@@ -632,9 +652,70 @@ public class ProgramDataManager implements MemoryManageable {
}
}
+ private long getFetchDuration() {
+ if (mChannelIdProgramCache.isEmpty()) {
+ return Math.max(1L, mBackendKnobsFlags.programGuideInitialFetchHours());
+ } else {
+ long durationHours;
+ int channelCount = mChannelDataManager.getChannelCount();
+ long knobsMaxHours = mBackendKnobsFlags.programGuideMaxHours();
+ long targetChannelCount = mBackendKnobsFlags.epgTargetChannelCount();
+ if (channelCount <= targetChannelCount) {
+ durationHours = Math.max(48L, knobsMaxHours);
+ } else {
+ // 2 days <= duration <= 14 days (336 hours)
+ durationHours = knobsMaxHours * targetChannelCount / channelCount;
+ if (durationHours < 48L) {
+ durationHours = 48L;
+ } else if (durationHours > 336L) {
+ durationHours = 336L;
+ }
+ }
+ return durationHours;
+ }
+ }
+
+ private class SingleChannelPrefetchTask extends AsyncDbTask.AsyncQueryTask<ArrayList<Program>> {
+ long mChannelId;
+
+ public SingleChannelPrefetchTask(long channelId, long startTimeMs, long endTimeMs) {
+ super(
+ mDbExecutor,
+ mContext,
+ TvContract.buildProgramsUriForChannel(channelId, startTimeMs, endTimeMs),
+ Program.PROJECTION,
+ null,
+ null,
+ SORT_BY_TIME);
+ mChannelId = channelId;
+ }
+
+ @Override
+ protected ArrayList<Program> onQuery(Cursor c) {
+ ArrayList<Program> programMap = new ArrayList<>();
+ while (c.moveToNext()) {
+ Program program = Program.fromCursor(c);
+ programMap.add(program);
+ }
+ return programMap;
+ }
+
+ @Override
+ protected void onPostExecute(ArrayList<Program> programs) {
+ mChannelIdProgramCache.put(mChannelId, programs);
+ notifySingleChannelUpdated(mChannelId);
+ }
+ }
+
private void notifyProgramUpdated() {
- for (Listener listener : mListeners) {
- listener.onProgramUpdated();
+ for (Callback callback : mCallbacks) {
+ callback.onProgramUpdated();
+ }
+ }
+
+ private void notifySingleChannelUpdated(long channelId) {
+ for (Callback callback : mCallbacks) {
+ callback.onSingleChannelUpdated(channelId);
}
}
@@ -694,6 +775,9 @@ public class ProgramDataManager implements MemoryManageable {
for (Long channelId : removedChannelIds) {
if (mPrefetchEnabled) {
mChannelIdProgramCache.remove(channelId);
+ if (mBackendKnobsFlags.enablePartialProgramFetch()) {
+ mCompleteInfoChannelIds.remove(channelId);
+ }
}
mChannelIdCurrentProgramMap.remove(channelId);
notifyCurrentProgramUpdate(channelId, null);
diff --git a/src/com/android/tv/guide/ProgramGuide.java b/src/com/android/tv/guide/ProgramGuide.java
index 69e2d68c..bc1b11b6 100644
--- a/src/com/android/tv/guide/ProgramGuide.java
+++ b/src/com/android/tv/guide/ProgramGuide.java
@@ -65,6 +65,7 @@ import com.android.tv.ui.ViewUtils;
import com.android.tv.ui.hideable.AutoHideScheduler;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
+import com.android.tv.common.flags.BackendKnobsFlags;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -182,14 +183,17 @@ public class ProgramGuide
Runnable preShowRunnable,
Runnable postHideRunnable) {
mActivity = activity;
- mPerformanceMonitor = TvSingletons.getSingletons(mActivity).getPerformanceMonitor();
+ TvSingletons singletons = TvSingletons.getSingletons(mActivity);
+ mPerformanceMonitor = singletons.getPerformanceMonitor();
+ BackendKnobsFlags backendKnobsFlags = singletons.getBackendKnobs();
mProgramManager =
new ProgramManager(
tvInputManagerHelper,
channelDataManager,
programDataManager,
dvrDataManager,
- dvrScheduleManager);
+ dvrScheduleManager,
+ backendKnobsFlags);
mChannelTuner = channelTuner;
mTracker = tracker;
mPreShowRunnable = preShowRunnable;
diff --git a/src/com/android/tv/guide/ProgramManager.java b/src/com/android/tv/guide/ProgramManager.java
index 9a70ddd3..3a5a4a02 100644
--- a/src/com/android/tv/guide/ProgramManager.java
+++ b/src/com/android/tv/guide/ProgramManager.java
@@ -32,6 +32,7 @@ import com.android.tv.dvr.DvrScheduleManager.OnConflictStateChangeListener;
import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
+import com.android.tv.common.flags.BackendKnobsFlags;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -59,6 +60,7 @@ public class ProgramManager {
private final ProgramDataManager mProgramDataManager;
private final DvrDataManager mDvrDataManager; // Only set if DVR is enabled
private final DvrScheduleManager mDvrScheduleManager;
+ private final BackendKnobsFlags mBackendKnobsFlags;
private long mStartUtcMillis;
private long mEndUtcMillis;
@@ -114,12 +116,26 @@ public class ProgramManager {
}
};
- private final ProgramDataManager.Listener mProgramDataManagerListener =
- new ProgramDataManager.Listener() {
+ private final ProgramDataManager.Callback mProgramDataManagerCallback =
+ new ProgramDataManager.Callback() {
@Override
public void onProgramUpdated() {
updateTableEntries(true);
}
+
+ @Override
+ public void onSingleChannelUpdated(long channelId) {
+ boolean parentalControlsEnabled =
+ mTvInputManagerHelper
+ .getParentalControlSettings()
+ .isParentalControlsEnabled();
+ // Inline the updating of the mChannelIdEntriesMap here so we can only call
+ // getParentalControlSettings once.
+ List<TableEntry> entries =
+ createProgramEntries(channelId, parentalControlsEnabled);
+ mChannelIdEntriesMap.put(channelId, entries);
+ notifyTableEntriesUpdated();
+ }
};
private final DvrDataManager.ScheduledRecordingListener mScheduledRecordingListener =
@@ -199,19 +215,21 @@ public class ProgramManager {
ChannelDataManager channelDataManager,
ProgramDataManager programDataManager,
@Nullable DvrDataManager dvrDataManager,
- @Nullable DvrScheduleManager dvrScheduleManager) {
+ @Nullable DvrScheduleManager dvrScheduleManager,
+ BackendKnobsFlags backendKnobsFlags) {
mTvInputManagerHelper = tvInputManagerHelper;
mChannelDataManager = channelDataManager;
mProgramDataManager = programDataManager;
mDvrDataManager = dvrDataManager;
mDvrScheduleManager = dvrScheduleManager;
+ mBackendKnobsFlags = backendKnobsFlags;
}
void programGuideVisibilityChanged(boolean visible) {
mProgramDataManager.setPauseProgramUpdate(visible);
if (visible) {
mChannelDataManager.addListener(mChannelDataManagerListener);
- mProgramDataManager.addListener(mProgramDataManagerListener);
+ mProgramDataManager.addCallback(mProgramDataManagerCallback);
if (mDvrDataManager != null) {
if (!mDvrDataManager.isDvrScheduleLoadFinished()) {
mDvrDataManager.addDvrScheduleLoadFinishedListener(mDvrLoadedListener);
@@ -224,7 +242,7 @@ public class ProgramManager {
}
} else {
mChannelDataManager.removeListener(mChannelDataManagerListener);
- mProgramDataManager.removeListener(mProgramDataManagerListener);
+ mProgramDataManager.removeCallback(mProgramDataManagerCallback);
if (mDvrDataManager != null) {
mDvrDataManager.removeDvrScheduleLoadFinishedListener(mDvrLoadedListener);
mDvrDataManager.removeScheduledRecordingListener(mScheduledRecordingListener);
@@ -413,6 +431,9 @@ public class ProgramManager {
* (e.g., whose channelId is INVALID_ID), when it corresponds to a gap between programs.
*/
TableEntry getTableEntry(long channelId, int index) {
+ if (mBackendKnobsFlags.enablePartialProgramFetch()) {
+ mProgramDataManager.prefetchChannel(channelId);
+ }
return mChannelIdEntriesMap.get(channelId).get(index);
}