diff options
author | nchalko <nchalko@google.com> | 2019-01-24 21:38:34 -0800 |
---|---|---|
committer | Nick Chalko <nchalko@google.com> | 2019-02-12 22:15:41 -0800 |
commit | 8968c2de57949e34a10d53cc5f2310687a58b3a4 (patch) | |
tree | eb98f026b93a1124c148ff204fe4b209bfe7b7b4 | |
parent | 6e15fb1aabe572ca629a00b0ac23b7a1450aa749 (diff) | |
download | android_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.java | 8 | ||||
-rw-r--r-- | src/com/android/tv/data/Program.java | 25 | ||||
-rw-r--r-- | src/com/android/tv/data/ProgramDataManager.java | 152 | ||||
-rw-r--r-- | src/com/android/tv/guide/ProgramGuide.java | 8 | ||||
-rw-r--r-- | src/com/android/tv/guide/ProgramManager.java | 31 |
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); } |