aboutsummaryrefslogtreecommitdiff
path: root/src/com/android/tv/guide
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/tv/guide')
-rw-r--r--src/com/android/tv/guide/ProgramGrid.java60
-rw-r--r--src/com/android/tv/guide/ProgramGuide.java19
-rw-r--r--src/com/android/tv/guide/ProgramItemView.java136
-rw-r--r--src/com/android/tv/guide/ProgramListAdapter.java48
-rw-r--r--src/com/android/tv/guide/ProgramManager.java179
-rw-r--r--src/com/android/tv/guide/ProgramRow.java2
-rw-r--r--src/com/android/tv/guide/ProgramTableAdapter.java92
-rw-r--r--src/com/android/tv/guide/TimelineRow.java4
8 files changed, 386 insertions, 154 deletions
diff --git a/src/com/android/tv/guide/ProgramGrid.java b/src/com/android/tv/guide/ProgramGrid.java
index 1339ddf8..77de5827 100644
--- a/src/com/android/tv/guide/ProgramGrid.java
+++ b/src/com/android/tv/guide/ProgramGrid.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v7.widget.RecyclerView.LayoutManager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
@@ -66,6 +67,16 @@ public class ProgramGrid extends VerticalGridView {
}
};
+ private final ViewTreeObserver.OnPreDrawListener mPreDrawListener =
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ getViewTreeObserver().removeOnPreDrawListener(this);
+ updateInputLogo();
+ return true;
+ }
+ };
+
private ProgramManager mProgramManager;
private View mNextFocusByUpDown;
@@ -83,7 +94,7 @@ public class ProgramGrid extends VerticalGridView {
private boolean mKeepCurrentProgram;
private ChildFocusListener mChildFocusListener;
- private OnRepeatedKeyInterceptListener mOnRepeatedKeyInterceptListener;
+ private final OnRepeatedKeyInterceptListener mOnRepeatedKeyInterceptListener;
interface ChildFocusListener {
/**
@@ -332,6 +343,10 @@ public class ProgramGrid extends VerticalGridView {
return contains((View) v.getParent());
}
+ public void onItemSelectionReset() {
+ getViewTreeObserver().addOnPreDrawListener(mPreDrawListener);
+ }
+
@Override
public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
if (mLastFocusedView != null && mLastFocusedView.isShown()) {
@@ -359,6 +374,49 @@ public class ProgramGrid extends VerticalGridView {
int maxY = (mSelectionRow + 1) * mRowHeight + mDetailHeight;
if (y > maxY) scrollBy(0, y - maxY);
}
+ updateInputLogo();
+ }
+
+ @Override
+ public void onViewRemoved(View view) {
+ // It is required to ensure input logo showing when the scroll is moved to most bottom.
+ updateInputLogo();
+ }
+
+ private int getFirstVisibleChildIndex() {
+ final LayoutManager mLayoutManager = getLayoutManager();
+ int top = mLayoutManager.getPaddingTop();
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View childView = getChildAt(i);
+ int childTop = mLayoutManager.getDecoratedTop(childView);
+ int childBottom = mLayoutManager.getDecoratedBottom(childView);
+ if ((childTop + childBottom) / 2 > top) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public void updateInputLogo() {
+ int childCount = getChildCount();
+ if (childCount == 0) {
+ return;
+ }
+ int firstVisibleChildIndex = getFirstVisibleChildIndex();
+ if (firstVisibleChildIndex == -1) {
+ return;
+ }
+ View childView = getChildAt(firstVisibleChildIndex);
+ int childAdapterPosition = getChildAdapterPosition(childView);
+ ((ProgramTableAdapter.ProgramRowHolder) getChildViewHolder(childView))
+ .updateInputLogo(childAdapterPosition, true);
+ for (int i = firstVisibleChildIndex + 1; i < childCount; i++) {
+ childView = getChildAt(i);
+ ((ProgramTableAdapter.ProgramRowHolder) getChildViewHolder(childView))
+ .updateInputLogo(childAdapterPosition, false);
+ childAdapterPosition = getChildAdapterPosition(childView);
+ }
}
private static void findFocusables(View v, ArrayList<View> outFocusable) {
diff --git a/src/com/android/tv/guide/ProgramGuide.java b/src/com/android/tv/guide/ProgramGuide.java
index 77a1146b..bfcb8b0d 100644
--- a/src/com/android/tv/guide/ProgramGuide.java
+++ b/src/com/android/tv/guide/ProgramGuide.java
@@ -31,6 +31,7 @@ import android.os.Message;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v17.leanback.widget.OnChildSelectedListener;
import android.support.v17.leanback.widget.SearchOrbView;
import android.support.v17.leanback.widget.VerticalGridView;
@@ -52,6 +53,7 @@ import com.android.tv.common.WeakHandler;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.GenreItems;
import com.android.tv.data.ProgramDataManager;
+import com.android.tv.dvr.DvrDataManager;
import com.android.tv.ui.HardwareLayerAnimatorListenerAdapter;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
@@ -160,12 +162,11 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener {
public ProgramGuide(MainActivity activity, ChannelTuner channelTuner,
TvInputManagerHelper tvInputManagerHelper, ChannelDataManager channelDataManager,
- ProgramDataManager programDataManager, Tracker tracker, Runnable preShowRunnable,
- Runnable postHideRunnable) {
+ ProgramDataManager programDataManager, @Nullable DvrDataManager dvrDataManager,
+ Tracker tracker, Runnable preShowRunnable, Runnable postHideRunnable) {
mActivity = activity;
- mProgramManager = new ProgramManager(tvInputManagerHelper,
- channelDataManager,
- programDataManager);
+ mProgramManager = new ProgramManager(tvInputManagerHelper, channelDataManager,
+ programDataManager, dvrDataManager);
mChannelTuner = channelTuner;
mTracker = tracker;
mPreShowRunnable = preShowRunnable;
@@ -245,7 +246,7 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener {
mTimelineRow.setAdapter(mTimeListAdapter);
ProgramTableAdapter programTableAdapter = new ProgramTableAdapter(mActivity,
- tvInputManagerHelper, mProgramManager, this);
+ mProgramManager, this);
programTableAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
@@ -590,7 +591,10 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener {
return mTimelineRow.getScrollOffset();
}
- private void cancelHide() {
+ /**
+ * Cancel hiding the program guide.
+ */
+ public void cancelHide() {
mHandler.removeCallbacks(mHideRunnable);
}
@@ -720,6 +724,7 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener {
Math.max(mProgramManager.getChannelIndex(mChannelTuner.getCurrentChannel()),
0));
mGrid.resetFocusState();
+ mGrid.onItemSelectionReset();
mIsDuringResetRowSelection = false;
}
diff --git a/src/com/android/tv/guide/ProgramItemView.java b/src/com/android/tv/guide/ProgramItemView.java
index 09a93037..172ee070 100644
--- a/src/com/android/tv/guide/ProgramItemView.java
+++ b/src/com/android/tv/guide/ProgramItemView.java
@@ -16,9 +16,7 @@
package com.android.tv.guide;
-import android.app.AlertDialog;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
@@ -26,6 +24,7 @@ import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.StateListDrawable;
import android.os.Handler;
import android.os.SystemClock;
+import android.support.v4.os.BuildCompat;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
@@ -43,15 +42,14 @@ import com.android.tv.TvApplication;
import com.android.tv.analytics.Tracker;
import com.android.tv.common.feature.CommonFeatures;
import com.android.tv.data.Channel;
-import com.android.tv.data.Program;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.Recording;
+import com.android.tv.dvr.ui.DvrDialogFragment;
+import com.android.tv.dvr.ui.DvrRecordDeleteFragment;
+import com.android.tv.dvr.ui.DvrRecordScheduleFragment;
import com.android.tv.guide.ProgramManager.TableEntry;
import com.android.tv.util.Utils;
import java.lang.reflect.InvocationTargetException;
-import java.util.ArrayList;
-import java.util.List;
import java.util.concurrent.TimeUnit;
public class ProgramItemView extends TextView {
@@ -60,9 +58,6 @@ public class ProgramItemView extends TextView {
private static final long FOCUS_UPDATE_FREQUENCY = TimeUnit.SECONDS.toMillis(1);
private static final int MAX_PROGRESS = 10000; // From android.widget.ProgressBar.MAX_VALUE
- private static final int ACTION_RECORD_PROGRAM = 100;
- private static final int ACTION_RECORD_SEASON = 101;
-
// State indicating the focused program is the current program
private static final int[] STATE_CURRENT_PROGRAM = { R.attr.state_current_program };
@@ -89,6 +84,10 @@ public class ProgramItemView extends TextView {
@Override
public void onClick(final View view) {
TableEntry entry = ((ProgramItemView) view).mTableEntry;
+ if (entry == null) {
+ //do nothing
+ return;
+ }
ApplicationSingletons singletons = TvApplication.getSingletons(view.getContext());
Tracker tracker = singletons.getTracker();
tracker.sendEpgItemClicked();
@@ -105,78 +104,38 @@ public class ProgramItemView extends TextView {
}, entry.getWidth() > ((ProgramItemView) view).mMaxWidthForRipple ? 0
: view.getResources()
.getInteger(R.integer.program_guide_ripple_anim_duration));
- } else if (CommonFeatures.DVR.isEnabled(view.getContext())) {
+ } else if (CommonFeatures.DVR.isEnabled(view.getContext()) && BuildCompat
+ .isAtLeastN()) {
final MainActivity tvActivity = (MainActivity) view.getContext();
final DvrManager dvrManager = singletons.getDvrManager();
final Channel channel = tvActivity.getChannelDataManager()
.getChannel(entry.channelId);
- if (dvrManager.canRecord(channel.getInputId())) {
- showDvrDialog(view, entry, dvrManager);
+ if (dvrManager.canRecord(channel.getInputId()) && entry.program != null) {
+ if (entry.scheduledRecording == null) {
+ showDvrDialog(view, entry);
+ } else {
+ showRecordDeleteDialog(view, entry);
+ }
}
}
}
- private void showDvrDialog(final View view, TableEntry entry, final DvrManager dvrManager) {
- List<CharSequence> items = new ArrayList<>();
- final List<Integer> actions = new ArrayList<>();
- // TODO: the items can be changed by the state of the program. For example,
- // if the program is already added in scheduler, we need to show an item to
- // delete the recording schedule.
- items.add(view.getResources().getString(R.string.epg_dvr_record_program));
- actions.add(ACTION_RECORD_PROGRAM);
- items.add(view.getResources().getString(R.string.epg_dvr_record_season));
- actions.add(ACTION_RECORD_SEASON);
-
- final Program program = entry.program;
- final List<Recording> conflicts = dvrManager
- .getScheduledRecordingsThatConflict(program);
- // TODO: it is a tentative UI. Don't publish the UI.
- DialogInterface.OnClickListener onClickListener
- = new DialogInterface.OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog, int which) {
- if (actions.get(which) == ACTION_RECORD_PROGRAM) {
- if (conflicts.isEmpty()) {
- dvrManager.addSchedule(program, conflicts);
- } else {
- showConflictDialog(view, dvrManager, program, conflicts);
- }
- } else if (actions.get(which) == ACTION_RECORD_SEASON) {
- dvrManager.addSeasonSchedule(program);
- }
- dialog.dismiss();
- }
- };
- new AlertDialog.Builder(view.getContext())
- .setItems(items.toArray(new CharSequence[items.size()]), onClickListener)
- .create()
- .show();
+ private void showDvrDialog(final View view, TableEntry entry) {
+ Utils.showToastMessageForDeveloperFeature(view.getContext());
+ DvrRecordScheduleFragment dvrRecordScheduleFragment =
+ new DvrRecordScheduleFragment(entry);
+ DvrDialogFragment dvrDialogFragment = new DvrDialogFragment(dvrRecordScheduleFragment);
+ ((MainActivity) view.getContext()).getOverlayManager().showDialogFragment(
+ DvrDialogFragment.DIALOG_TAG, dvrDialogFragment, true, true);
}
- };
- private static void showConflictDialog(final View view, final DvrManager dvrManager,
- final Program program, final List<Recording> conflicts) {
- DialogInterface.OnClickListener conflictClickListener
- = new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- if (which == AlertDialog.BUTTON_POSITIVE) {
- dvrManager.addSchedule(program, conflicts);
- dialog.dismiss();
- }
- }
- };
- StringBuilder sb = new StringBuilder();
- for (Recording r : conflicts) {
- sb.append(r.toString()).append('\n');
+ private void showRecordDeleteDialog(final View view, final TableEntry entry) {
+ DvrRecordDeleteFragment recordDeleteDialogFragment = new DvrRecordDeleteFragment(entry);
+ DvrDialogFragment dvrDialogFragment = new DvrDialogFragment(recordDeleteDialogFragment);
+ ((MainActivity) view.getContext()).getOverlayManager().showDialogFragment(
+ DvrDialogFragment.DIALOG_TAG, dvrDialogFragment, true, true);
}
- new AlertDialog.Builder(view.getContext()).setTitle(R.string.dvr_epg_conflict_dialog_title)
- .setMessage(sb.toString())
- .setPositiveButton(R.string.dvr_epg_record, conflictClickListener)
- .setNegativeButton(R.string.dvr_epg_do_not_record, conflictClickListener)
- .create()
- .show();
- }
+ };
private static final View.OnFocusChangeListener ON_FOCUS_CHANGED =
new View.OnFocusChangeListener() {
@@ -198,6 +157,10 @@ public class ProgramItemView extends TextView {
public void run() {
refreshDrawableState();
TableEntry entry = mTableEntry;
+ if (entry == null) {
+ //do nothing
+ return;
+ }
if (entry.isCurrentProgram()) {
Drawable background = getBackground();
int progress = getProgress(entry.entryStartUtcMillis, entry.entryEndUtcMillis);
@@ -220,6 +183,8 @@ public class ProgramItemView extends TextView {
public ProgramItemView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
+ setOnClickListener(ON_CLICKED);
+ setOnFocusChangeListener(ON_FOCUS_CHANGED);
}
private void initIfNeeded() {
@@ -282,11 +247,9 @@ public class ProgramItemView extends TextView {
return mTableEntry;
}
- public void onBind(TableEntry entry, ProgramListAdapter adapter) {
+ public void setValues(TableEntry entry, int selectedGenreId, long fromUtcMillis,
+ long toUtcMillis, String gapTitle) {
mTableEntry = entry;
- setOnClickListener(ON_CLICKED);
- setOnFocusChangeListener(ON_FOCUS_CHANGED);
- ProgramManager programManager = adapter.getProgramManager();
ViewGroup.LayoutParams layoutParams = getLayoutParams();
layoutParams.width = entry.getWidth();
@@ -303,16 +266,19 @@ public class ProgramItemView extends TextView {
setText(null);
} else {
if (entry.isGap()) {
- if (entry.isBlocked()) {
- title = adapter.getBlockedProgramTitle();
- } else {
- title = adapter.getNoInfoProgramTitle();
- }
+ title = gapTitle;
episode = null;
- } else if (entry.hasGenre(programManager.getSelectedGenreId())) {
+ } else if (entry.hasGenre(selectedGenreId)) {
titleStyle = sProgramTitleStyle;
episodeStyle = sEpisodeTitleStyle;
}
+ if (TextUtils.isEmpty(title)) {
+ title = getResources().getString(R.string.program_title_for_no_information);
+ }
+ if (mTableEntry.scheduledRecording != null) {
+ //TODO(dvr): use a proper icon for UI status.
+ title = "®" + title;
+ }
SpannableStringBuilder description = new SpannableStringBuilder();
description.append(title);
@@ -340,12 +306,11 @@ public class ProgramItemView extends TextView {
measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
mTextWidth = getMeasuredWidth() - getPaddingStart() - getPaddingEnd();
int start = GuideUtils.convertMillisToPixel(entry.entryStartUtcMillis);
- int guideStart = GuideUtils.convertMillisToPixel(programManager.getFromUtcMillis());
+ int guideStart = GuideUtils.convertMillisToPixel(fromUtcMillis);
layoutVisibleArea(guideStart - start);
// Maximum width for us to use a ripple
- mMaxWidthForRipple = GuideUtils.convertMillisToPixel(
- programManager.getFromUtcMillis(), programManager.getToUtcMillis());
+ mMaxWidthForRipple = GuideUtils.convertMillisToPixel(fromUtcMillis, toUtcMillis);
}
/**
@@ -374,14 +339,13 @@ public class ProgramItemView extends TextView {
}
}
- public void onUnbind() {
+ public void clearValues() {
if (getHandler() != null) {
getHandler().removeCallbacks(mUpdateFocus);
}
setTag(null);
- setOnFocusChangeListener(null);
- setOnClickListener(null);
+ mTableEntry = null;
}
private static int getProgress(long start, long end) {
diff --git a/src/com/android/tv/guide/ProgramListAdapter.java b/src/com/android/tv/guide/ProgramListAdapter.java
index 88ba435e..03aea5ad 100644
--- a/src/com/android/tv/guide/ProgramListAdapter.java
+++ b/src/com/android/tv/guide/ProgramListAdapter.java
@@ -16,7 +16,6 @@
package com.android.tv.guide;
-import android.content.Context;
import android.content.res.Resources;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
@@ -33,30 +32,24 @@ import com.android.tv.guide.ProgramManager.TableEntry;
* Adapts a program list for a specific channel from {@link ProgramManager} to a row of the program
* guide table.
*/
-public class ProgramListAdapter extends
- RecyclerView.Adapter<ProgramListAdapter.ProgramViewHolder> implements
- TableEntriesUpdatedListener {
+public class ProgramListAdapter extends RecyclerView.Adapter<ProgramListAdapter.ProgramViewHolder>
+ implements TableEntriesUpdatedListener {
private static final String TAG = "ProgramListAdapter";
private static final boolean DEBUG = false;
- private final String mNoInfoProgramTitle;
- private final String mBlockedProgramTitle;
-
private final ProgramManager mProgramManager;
private final int mChannelIndex;
+ private final String mNoInfoProgramTitle;
+ private final String mBlockedProgramTitle;
private long mChannelId;
- public ProgramListAdapter(Context context, ProgramManager programManager,
- int channelIndex) {
- Resources res = context.getResources();
- mNoInfoProgramTitle = res.getString(
- R.string.program_title_for_no_information);
- mBlockedProgramTitle = res.getString(
- R.string.program_title_for_blocked_channel);
-
+ public ProgramListAdapter(Resources res, ProgramManager programManager, int channelIndex) {
+ setHasStableIds(true);
mProgramManager = programManager;
mChannelIndex = channelIndex;
+ mNoInfoProgramTitle = res.getString(R.string.program_title_for_no_information);
+ mBlockedProgramTitle = res.getString(R.string.program_title_for_blocked_channel);
onTableEntriesUpdated();
}
@@ -76,14 +69,6 @@ public class ProgramListAdapter extends
return mProgramManager;
}
- public String getNoInfoProgramTitle() {
- return mNoInfoProgramTitle;
- }
-
- public String getBlockedProgramTitle() {
- return mBlockedProgramTitle;
- }
-
@Override
public int getItemCount() {
return mProgramManager.getTableEntryCount(mChannelId);
@@ -95,8 +80,15 @@ public class ProgramListAdapter extends
}
@Override
+ public long getItemId(int position) {
+ return mProgramManager.getTableEntry(mChannelId, position).getId();
+ }
+
+ @Override
public void onBindViewHolder(ProgramViewHolder holder, int position) {
- holder.onBind(mProgramManager.getTableEntry(mChannelId, position), this);
+ TableEntry tableEntry = mProgramManager.getTableEntry(mChannelId, position);
+ String gapTitle = tableEntry.isBlocked() ? mBlockedProgramTitle : mNoInfoProgramTitle;
+ holder.onBind(tableEntry, this.getProgramManager(), gapTitle);
}
@Override
@@ -116,16 +108,16 @@ public class ProgramListAdapter extends
super(itemView);
}
- public void onBind(TableEntry entry, ProgramListAdapter adapter) {
+ public void onBind(TableEntry entry, ProgramManager programManager, String gapTitle) {
if (DEBUG) {
Log.d(TAG, "onBind. View = " + itemView + ", Entry = " + entry);
}
-
- ((ProgramItemView) itemView).onBind(entry, adapter);
+ ((ProgramItemView) itemView).setValues(entry, programManager.getSelectedGenreId(),
+ programManager.getFromUtcMillis(), programManager.getToUtcMillis(), gapTitle);
}
public void onUnbind() {
- ((ProgramItemView) itemView).onUnbind();
+ ((ProgramItemView) itemView).clearValues();
}
}
}
diff --git a/src/com/android/tv/guide/ProgramManager.java b/src/com/android/tv/guide/ProgramManager.java
index df52abbe..fe1a981f 100644
--- a/src/com/android/tv/guide/ProgramManager.java
+++ b/src/com/android/tv/guide/ProgramManager.java
@@ -17,14 +17,17 @@
package com.android.tv.guide;
import android.support.annotation.MainThread;
+import android.support.annotation.Nullable;
+import android.util.ArraySet;
import android.util.Log;
-import com.android.tv.common.CollectionUtils;
import com.android.tv.data.Channel;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.GenreItems;
import com.android.tv.data.Program;
import com.android.tv.data.ProgramDataManager;
+import com.android.tv.dvr.DvrDataManager;
+import com.android.tv.dvr.ScheduledRecording;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
@@ -55,6 +58,7 @@ public class ProgramManager {
private final TvInputManagerHelper mTvInputManagerHelper;
private final ChannelDataManager mChannelDataManager;
private final ProgramDataManager mProgramDataManager;
+ private final DvrDataManager mDvrDataManager; // Only set if DVR is enabled
private long mStartUtcMillis;
private long mEndUtcMillis;
@@ -74,6 +78,8 @@ public class ProgramManager {
/** Program corresponding to the entry. {@code null} means that this entry is a gap. */
public final Program program;
+ public final ScheduledRecording scheduledRecording;
+
/** Start time of entry in UTC milliseconds. */
public final long entryStartUtcMillis;
@@ -82,34 +88,39 @@ public class ProgramManager {
private final boolean mIsBlocked;
- private TableEntry(long startUtcMillis, long endUtcMillis) {
- this(INVALID_ID, null, startUtcMillis, endUtcMillis, false);
- }
-
private TableEntry(long channelId, long startUtcMillis, long endUtcMillis) {
this(channelId, null, startUtcMillis, endUtcMillis, false);
}
private TableEntry(long channelId, long startUtcMillis, long endUtcMillis,
boolean blocked) {
- this(channelId, null, startUtcMillis, endUtcMillis, blocked);
+ this(channelId, null, null, startUtcMillis, endUtcMillis, blocked);
}
- private TableEntry(long channelId, Program program,
- long entryStartUtcMillis, long entryEndUtcMillis) {
- this(channelId, program, entryStartUtcMillis, entryEndUtcMillis, false);
+ private TableEntry(long channelId, Program program, long entryStartUtcMillis,
+ long entryEndUtcMillis, boolean isBlocked) {
+ this(channelId, program, null, entryStartUtcMillis, entryEndUtcMillis, isBlocked);
}
- private TableEntry(long channelId, Program program,
+ private TableEntry(long channelId, Program program, ScheduledRecording scheduledRecording,
long entryStartUtcMillis, long entryEndUtcMillis, boolean isBlocked) {
this.channelId = channelId;
this.program = program;
+ this.scheduledRecording = scheduledRecording;
this.entryStartUtcMillis = entryStartUtcMillis;
this.entryEndUtcMillis = entryEndUtcMillis;
mIsBlocked = isBlocked;
}
/**
+ * A stable id useful for {@link android.support.v7.widget.RecyclerView.Adapter}.
+ */
+ public long getId() {
+ // using a negative entryEndUtcMillis keeps it from conflicting with program Id
+ return program != null ? program.getId() : -entryEndUtcMillis;
+ }
+
+ /**
* Returns true if this is a gap.
*/
public boolean isGap() {
@@ -167,9 +178,10 @@ public class ProgramManager {
// Should be matched with mSelectedGenreId always.
private List<Channel> mFilteredChannels = mChannels;
- private final Set<Listener> mListeners = CollectionUtils.createSmallSet();
- private final Set<TableEntriesUpdatedListener> mTableEntriesUpdatedListeners = CollectionUtils
- .createSmallSet();
+ private final Set<Listener> mListeners = new ArraySet<>();
+ private final Set<TableEntriesUpdatedListener> mTableEntriesUpdatedListeners = new ArraySet<>();
+
+ private final Set<TableEntryChangedListener> mTableEntryChangedListeners = new ArraySet<>();
private final ChannelDataManager.Listener mChannelDataManagerListener =
new ChannelDataManager.Listener() {
@@ -197,12 +209,49 @@ public class ProgramManager {
}
};
+ private final DvrDataManager.ScheduledRecordingListener mScheduledRecordingListener =
+ new DvrDataManager.ScheduledRecordingListener() {
+ @Override
+ public void onScheduledRecordingAdded(ScheduledRecording scheduledRecording) {
+ TableEntry oldEntry = getTableEntry(scheduledRecording);
+ if (oldEntry != null) {
+ TableEntry newEntry = new TableEntry(oldEntry.channelId, oldEntry.program,
+ scheduledRecording, oldEntry.entryStartUtcMillis,
+ oldEntry.entryEndUtcMillis, oldEntry.isBlocked());
+ updateEntry(oldEntry, newEntry);
+ }
+ }
+
+ @Override
+ public void onScheduledRecordingRemoved(ScheduledRecording scheduledRecording) {
+ TableEntry oldEntry = getTableEntry(scheduledRecording);
+ if (oldEntry != null) {
+ TableEntry newEntry = new TableEntry(oldEntry.channelId, oldEntry.program, null,
+ oldEntry.entryStartUtcMillis, oldEntry.entryEndUtcMillis,
+ oldEntry.isBlocked());
+ updateEntry(oldEntry, newEntry);
+ }
+ }
+
+ @Override
+ public void onScheduledRecordingStatusChanged(ScheduledRecording scheduledRecording) {
+ TableEntry oldEntry = getTableEntry(scheduledRecording);
+ if (oldEntry != null) {
+ TableEntry newEntry = new TableEntry(oldEntry.channelId, oldEntry.program,
+ scheduledRecording, oldEntry.entryStartUtcMillis,
+ oldEntry.entryEndUtcMillis, oldEntry.isBlocked());
+ updateEntry(oldEntry, newEntry);
+ }
+ }
+ };
+
public ProgramManager(TvInputManagerHelper tvInputManagerHelper,
- ChannelDataManager channelDataManager,
- ProgramDataManager programDataManager) {
+ ChannelDataManager channelDataManager, ProgramDataManager programDataManager,
+ @Nullable DvrDataManager dvrDataManager) {
mTvInputManagerHelper = tvInputManagerHelper;
mChannelDataManager = channelDataManager;
mProgramDataManager = programDataManager;
+ mDvrDataManager = dvrDataManager;
}
public void programGuideVisibilityChanged(boolean visible) {
@@ -210,41 +259,61 @@ public class ProgramManager {
if (visible) {
mChannelDataManager.addListener(mChannelDataManagerListener);
mProgramDataManager.addListener(mProgramDataManagerListener);
+ if (mDvrDataManager != null) {
+ mDvrDataManager.addScheduledRecordingListener(mScheduledRecordingListener);
+ }
} else {
mChannelDataManager.removeListener(mChannelDataManagerListener);
mProgramDataManager.removeListener(mProgramDataManagerListener);
+ if (mDvrDataManager != null) {
+ mDvrDataManager.removeScheduledRecordingListener(mScheduledRecordingListener);
+ }
}
}
/**
- * Add a {@link Listener}.
+ * Adds a {@link Listener}.
*/
public void addListener(Listener listener) {
mListeners.add(listener);
}
/**
- * Register a listener to be invoked when table entries are updated.
+ * Registers a listener to be invoked when table entries are updated.
*/
public void addTableEntriesUpdatedListener(TableEntriesUpdatedListener listener) {
mTableEntriesUpdatedListeners.add(listener);
}
/**
- * Remove a {@link Listener}.
+ * Registers a listener to be invoked when a table entry is changed.
+ */
+ public void addTableEntryChangedListener(TableEntryChangedListener listener) {
+ mTableEntryChangedListeners.add(listener);
+ }
+
+ /**
+ * Removes a {@link Listener}.
*/
public void removeListener(Listener listener) {
mListeners.remove(listener);
}
/**
- * Remove a previously installed table entries update listener.
+ * Removes a previously installed table entries update listener.
*/
public void removeTableEntriesUpdatedListener(TableEntriesUpdatedListener listener) {
mTableEntriesUpdatedListeners.remove(listener);
}
/**
+ * Removes a previously installed table entry changed listener.
+ */
+ public void removeTableEntryChangedListener(TableEntryChangedListener listener) {
+ mTableEntryChangedListeners.remove(listener);
+ }
+
+ /**
* Build genre filters based on the current programs.
* This categories channels by its current program's canonical genres
* and subsequent @{link resetChannelListWithGenre(int)} calls will reset channel list
@@ -366,6 +435,7 @@ public class ProgramManager {
} else if (lastEntry.entryEndUtcMillis == Long.MAX_VALUE) {
entries.remove(entries.size() - 1);
entries.add(new TableEntry(lastEntry.channelId, lastEntry.program,
+ lastEntry.scheduledRecording,
lastEntry.entryStartUtcMillis, mEndUtcMillis,
lastEntry.mIsBlocked));
}
@@ -403,6 +473,37 @@ public class ProgramManager {
}
}
+ private void notifyTableEntryUpdated(TableEntry entry) {
+ for (TableEntryChangedListener listener : mTableEntryChangedListeners) {
+ listener.onTableEntryChanged(entry);
+ }
+ }
+
+ private void updateEntry(TableEntry old, TableEntry newEntry) {
+ List<TableEntry> entries = mChannelIdEntriesMap.get(old.channelId);
+ int index = entries.indexOf(old);
+ entries.set(index, newEntry);
+ notifyTableEntryUpdated(newEntry);
+ }
+
+ @Nullable
+ private TableEntry getTableEntry(ScheduledRecording scheduledRecording) {
+ return getTableEntry(scheduledRecording.getChannelId(), scheduledRecording.getProgramId());
+ }
+
+ @Nullable
+ private TableEntry getTableEntry(long channelId, long entryId) {
+ List<TableEntry> entries = mChannelIdEntriesMap.get(channelId);
+ if (entries != null) {
+ for (TableEntry entry : entries) {
+ if (entry.getId() == entryId) {
+ return entry;
+ }
+ }
+ }
+ return null;
+ }
+
/**
* Returns the start time of currently managed time range, in UTC millisecond.
*/
@@ -471,6 +572,14 @@ public class ProgramManager {
}
/**
+ * Returns the index of channel with {@code channelId} within the currently managed channels.
+ * Returns -1 if such a channel is not found.
+ */
+ public int getChannelIndex(long channelId) {
+ return getChannelIndex(mChannelDataManager.getChannel(channelId));
+ }
+
+ /**
* Returns the number of "entries", which lies within the currently managed time range, for a
* given {@code channelId}.
*/
@@ -511,8 +620,10 @@ public class ProgramManager {
lastProgramEndTime = programStartTime;
}
if (programEndTime > lastProgramEndTime) {
- entries.add(new TableEntry(channelId, program, lastProgramEndTime,
- programEndTime));
+ ScheduledRecording scheduledRecording = mDvrDataManager == null ? null
+ : mDvrDataManager.getScheduledRecordingForProgramId(program.getId());
+ entries.add(new TableEntry(channelId, program, scheduledRecording,
+ lastProgramEndTime, programEndTime, false));
lastProgramEndTime = programEndTime;
}
}
@@ -525,7 +636,8 @@ public class ProgramManager {
// the first entry from UI perspective. So we clip it out.
entries.remove(0);
entries.set(0, new TableEntry(secondEntry.channelId, secondEntry.program,
- mStartUtcMillis, secondEntry.entryEndUtcMillis));
+ secondEntry.scheduledRecording, mStartUtcMillis,
+ secondEntry.entryEndUtcMillis, secondEntry.mIsBlocked));
}
}
return entries;
@@ -555,6 +667,10 @@ public class ProgramManager {
void onTableEntriesUpdated();
}
+ public interface TableEntryChangedListener {
+ void onTableEntryChanged(TableEntry entry);
+ }
+
public static class ListenerAdapter implements Listener {
@Override
public void onGenresUpdated() { }
@@ -598,9 +714,24 @@ public class ProgramManager {
}
/**
- * Returns the program index of the program at {@code time}.
+ * Returns the program index of the program with {@code entryId} or -1 if not found.
+ */
+ public int getProgramIdIndex(long channelId, long entryId) {
+ List<TableEntry> entries = mChannelIdEntriesMap.get(channelId);
+ if (entries != null) {
+ for (int i = 0; i < entries.size(); i++) {
+ if (entries.get(i).getId() == entryId) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the program index of the program at {@code time} or -1 if not found.
*/
- public int getProgramIndex(long channelId, long time) {
+ public int getProgramIndexAtTime(long channelId, long time) {
List<TableEntry> entries = mChannelIdEntriesMap.get(channelId);
for (int i = 0; i < entries.size(); ++i) {
TableEntry entry = entries.get(i);
diff --git a/src/com/android/tv/guide/ProgramRow.java b/src/com/android/tv/guide/ProgramRow.java
index 4f38b879..54b864db 100644
--- a/src/com/android/tv/guide/ProgramRow.java
+++ b/src/com/android/tv/guide/ProgramRow.java
@@ -306,7 +306,7 @@ public class ProgramRow extends TimelineGridView {
public void resetScroll(int scrollOffset) {
long startTime = GuideUtils.convertPixelToMillis(scrollOffset)
+ mProgramManager.getStartTime();
- int position = mChannel == null ? -1 : mProgramManager.getProgramIndex(
+ int position = mChannel == null ? -1 : mProgramManager.getProgramIndexAtTime(
mChannel.getId(), startTime);
if (position < 0) {
getLayoutManager().scrollToPosition(0);
diff --git a/src/com/android/tv/guide/ProgramTableAdapter.java b/src/com/android/tv/guide/ProgramTableAdapter.java
index a86c1332..83755b5f 100644
--- a/src/com/android/tv/guide/ProgramTableAdapter.java
+++ b/src/com/android/tv/guide/ProgramTableAdapter.java
@@ -26,7 +26,9 @@ import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.media.tv.TvContentRating;
+import android.media.tv.TvInputInfo;
import android.os.Handler;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.RecycledViewPool;
@@ -43,11 +45,15 @@ import android.widget.ImageView;
import android.widget.TextView;
import com.android.tv.R;
+import com.android.tv.TvApplication;
import com.android.tv.data.Channel;
import com.android.tv.data.Program;
import com.android.tv.guide.ProgramManager.TableEntriesUpdatedListener;
import com.android.tv.parental.ParentalControlSettings;
import com.android.tv.ui.HardwareLayerAnimatorListenerAdapter;
+import com.android.tv.util.ImageCache;
+import com.android.tv.util.ImageLoader;
+import com.android.tv.util.ImageLoader.LoadTvInputLogoTask;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
@@ -57,8 +63,8 @@ import java.util.List;
/**
* Adapts the {@link ProgramListAdapter} list to the body of the program guide table.
*/
-public class ProgramTableAdapter extends
- RecyclerView.Adapter<ProgramTableAdapter.ProgramRowHolder> {
+public class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.ProgramRowHolder>
+ implements ProgramManager.TableEntryChangedListener {
private static final String TAG = "ProgramTableAdapter";
private static final boolean DEBUG = false;
@@ -84,10 +90,10 @@ public class ProgramTableAdapter extends
private final int mDetailPadding;
private final TextAppearanceSpan mEpisodeTitleStyle;
- public ProgramTableAdapter(Context context, TvInputManagerHelper tvInputManagerHelper,
- ProgramManager programManager, ProgramGuide programGuide) {
+ public ProgramTableAdapter(Context context, ProgramManager programManager,
+ ProgramGuide programGuide) {
mContext = context;
- mTvInputManagerHelper = tvInputManagerHelper;
+ mTvInputManagerHelper = TvApplication.getSingletons(context).getTvInputManagerHelper();
mProgramManager = programManager;
mProgramGuide = programGuide;
@@ -140,6 +146,7 @@ public class ProgramTableAdapter extends
}
});
update();
+ mProgramManager.addTableEntryChangedListener(this);
}
private void update() {
@@ -149,7 +156,8 @@ public class ProgramTableAdapter extends
}
mProgramListAdapters.clear();
for (int i = 0; i < mProgramManager.getChannelCount(); i++) {
- ProgramListAdapter listAdapter = new ProgramListAdapter(mContext, mProgramManager, i);
+ ProgramListAdapter listAdapter = new ProgramListAdapter(mContext.getResources(),
+ mProgramManager, i);
mProgramManager.addTableEntriesUpdatedListener(listAdapter);
mProgramListAdapters.add(listAdapter);
}
@@ -179,6 +187,14 @@ public class ProgramTableAdapter extends
return new ProgramRowHolder(itemView);
}
+ @Override
+ public void onTableEntryChanged(ProgramManager.TableEntry tableEntry) {
+ int channelIndex = mProgramManager.getChannelIndex(tableEntry.channelId);
+ int pos = mProgramManager.getProgramIdIndex(tableEntry.channelId, tableEntry.getId());
+ if (DEBUG) Log.d(TAG, "update(" + channelIndex + ", " + pos + ")");
+ mProgramListAdapters.get(channelIndex).notifyItemChanged(pos, tableEntry);
+ }
+
// TODO: make it static
public class ProgramRowHolder extends RecyclerView.ViewHolder
implements ProgramRow.ChildFocusListener {
@@ -223,6 +239,9 @@ public class ProgramTableAdapter extends
private final TextView mChannelNameView;
private final ImageView mChannelLogoView;
private final ImageView mChannelBlockView;
+ private final ImageView mInputLogoView;
+
+ private boolean mIsInputLogoVisible;
public ProgramRowHolder(View itemView) {
super(itemView);
@@ -244,6 +263,7 @@ public class ProgramTableAdapter extends
mChannelNameView = (TextView) mContainer.findViewById(R.id.channel_name);
mChannelLogoView = (ImageView) mContainer.findViewById(R.id.channel_logo);
mChannelBlockView = (ImageView) mContainer.findViewById(R.id.channel_block);
+ mInputLogoView = (ImageView) mContainer.findViewById(R.id.input_logo);
}
public void onBind(int position) {
@@ -267,6 +287,8 @@ public class ProgramTableAdapter extends
if (DEBUG) Log.d(TAG, "onBindChannel " + channel);
mChannel = channel;
+ mInputLogoView.setVisibility(View.GONE);
+ mIsInputLogoVisible = false;
if (channel == null) {
mChannelNumberView.setVisibility(View.GONE);
mChannelNameView.setVisibility(View.GONE);
@@ -467,6 +489,43 @@ public class ProgramTableAdapter extends
}
}
+ /**
+ * Update tv input logo. It should be called when the visible child item in ProgramGrid
+ * changed.
+ */
+ public void updateInputLogo(int lastPosition, boolean forceShow) {
+ if (mChannel == null) {
+ mInputLogoView.setVisibility(View.GONE);
+ mIsInputLogoVisible = false;
+ return;
+ }
+
+ boolean showLogo = forceShow;
+ if (!showLogo) {
+ Channel lastChannel = mProgramManager.getChannel(lastPosition);
+ if (lastChannel == null
+ || !mChannel.getInputId().equals(lastChannel.getInputId())) {
+ showLogo = true;
+ }
+ }
+
+ if (showLogo) {
+ if (!mIsInputLogoVisible) {
+ mIsInputLogoVisible = true;
+ TvInputInfo info = mTvInputManagerHelper.getTvInputInfo(mChannel.getInputId());
+ if (info != null) {
+ LoadTvInputLogoTask task = new LoadTvInputLogoTask(
+ itemView.getContext(), ImageCache.getInstance(), info);
+ ImageLoader.loadBitmap(createTvInputLogoLoadedCallback(info, this), task);
+ }
+ }
+ } else {
+ mInputLogoView.setVisibility(View.GONE);
+ mInputLogoView.setImageDrawable(null);
+ mIsInputLogoVisible = false;
+ }
+ }
+
private void updateTextView(TextView textView, String text) {
if (!TextUtils.isEmpty(text)) {
textView.setVisibility(View.VISIBLE);
@@ -487,6 +546,14 @@ public class ProgramTableAdapter extends
mChannelLogoView.setVisibility(View.VISIBLE);
}
+ private void updateInputLogoInternal(@NonNull Bitmap tvInputLogo) {
+ if (!mIsInputLogoVisible) {
+ return;
+ }
+ mInputLogoView.setImageBitmap(tvInputLogo);
+ mInputLogoView.setVisibility(View.VISIBLE);
+ }
+
private void onHorizontalScrolled() {
if (mDetailInAnimator != null) {
mHandler.removeCallbacks(mDetailInStarter);
@@ -526,4 +593,17 @@ public class ProgramTableAdapter extends
}
};
}
+
+ private static ImageLoaderCallback<ProgramRowHolder> createTvInputLogoLoadedCallback(
+ final TvInputInfo info, ProgramRowHolder holder) {
+ return new ImageLoaderCallback<ProgramRowHolder>(holder) {
+ @Override
+ public void onBitmapLoaded(ProgramRowHolder holder, @Nullable Bitmap logo) {
+ if (logo != null && info.getId()
+ .equals(holder.mChannel.getInputId())) {
+ holder.updateInputLogoInternal(logo);
+ }
+ }
+ };
+ }
}
diff --git a/src/com/android/tv/guide/TimelineRow.java b/src/com/android/tv/guide/TimelineRow.java
index 891b14cd..3f0c8678 100644
--- a/src/com/android/tv/guide/TimelineRow.java
+++ b/src/com/android/tv/guide/TimelineRow.java
@@ -64,7 +64,9 @@ public class TimelineRow extends TimelineGridView {
public void onRtlPropertiesChanged(int layoutDirection) {
super.onRtlPropertiesChanged(layoutDirection);
// Reset scroll
- scrollTo(getScrollOffset(), false);
+ if (isAttachedToWindow()) {
+ scrollTo(getScrollOffset(), false);
+ }
}
@Override