diff options
Diffstat (limited to 'src/com/android/tv/guide')
-rw-r--r-- | src/com/android/tv/guide/GenreListAdapter.java | 59 | ||||
-rw-r--r-- | src/com/android/tv/guide/GuideUtils.java | 42 | ||||
-rw-r--r-- | src/com/android/tv/guide/ProgramGrid.java | 40 | ||||
-rw-r--r-- | src/com/android/tv/guide/ProgramGuide.java | 684 | ||||
-rw-r--r-- | src/com/android/tv/guide/ProgramItemView.java | 522 | ||||
-rw-r--r-- | src/com/android/tv/guide/ProgramListAdapter.java | 14 | ||||
-rw-r--r-- | src/com/android/tv/guide/ProgramManager.java | 379 | ||||
-rw-r--r-- | src/com/android/tv/guide/ProgramRow.java | 129 | ||||
-rw-r--r-- | src/com/android/tv/guide/ProgramTableAdapter.java | 303 | ||||
-rw-r--r-- | src/com/android/tv/guide/TimeListAdapter.java | 13 | ||||
-rw-r--r-- | src/com/android/tv/guide/TimelineGridView.java | 17 | ||||
-rw-r--r-- | src/com/android/tv/guide/TimelineRow.java | 13 |
12 files changed, 1236 insertions, 979 deletions
diff --git a/src/com/android/tv/guide/GenreListAdapter.java b/src/com/android/tv/guide/GenreListAdapter.java index ce19eb2d..b4baf421 100644 --- a/src/com/android/tv/guide/GenreListAdapter.java +++ b/src/com/android/tv/guide/GenreListAdapter.java @@ -24,15 +24,11 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; - import com.android.tv.R; import com.android.tv.data.GenreItems; - import java.util.List; -/** - * Adapts the genre items obtained from {@link GenreItems} to the program guide side panel. - */ +/** Adapts the genre items obtained from {@link GenreItems} to the program guide side panel. */ class GenreListAdapter extends RecyclerView.Adapter<GenreListAdapter.GenreRowHolder> { private static final String TAG = "GenreListAdapter"; private static final boolean DEBUG = false; @@ -45,13 +41,14 @@ class GenreListAdapter extends RecyclerView.Adapter<GenreListAdapter.GenreRowHol GenreListAdapter(Context context, ProgramManager programManager, ProgramGuide guide) { mContext = context; mProgramManager = programManager; - mProgramManager.addListener(new ProgramManager.ListenerAdapter() { - @Override - public void onGenresUpdated() { - mGenreLabels = GenreItems.getLabels(mContext); - notifyDataSetChanged(); - } - }); + mProgramManager.addListener( + new ProgramManager.ListenerAdapter() { + @Override + public void onGenresUpdated() { + mGenreLabels = GenreItems.getLabels(mContext); + notifyDataSetChanged(); + } + }); mProgramGuide = guide; } @@ -80,23 +77,24 @@ class GenreListAdapter extends RecyclerView.Adapter<GenreListAdapter.GenreRowHol @Override public GenreRowHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false); - itemView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { - @Override - public void onViewAttachedToWindow(View view) { - // Animation is not meaningful now, skip it. - view.getStateListAnimator().jumpToCurrentState(); - } - - @Override - public void onViewDetachedFromWindow(View view) { - // Do nothing - } - }); + itemView.addOnAttachStateChangeListener( + new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View view) { + // Animation is not meaningful now, skip it. + view.getStateListAnimator().jumpToCurrentState(); + } + + @Override + public void onViewDetachedFromWindow(View view) { + // Do nothing + } + }); return new GenreRowHolder(itemView, mProgramGuide); } - static class GenreRowHolder extends RecyclerView.ViewHolder implements - View.OnFocusChangeListener { + static class GenreRowHolder extends RecyclerView.ViewHolder + implements View.OnFocusChangeListener { private final ProgramGuide mProgramGuide; private int mGenreId; @@ -119,8 +117,13 @@ class GenreListAdapter extends RecyclerView.Adapter<GenreListAdapter.GenreRowHol public void onFocusChange(View view, boolean hasFocus) { if (hasFocus) { if (DEBUG) { - Log.d(TAG, "onFocusChanged " + ((TextView) view).getText() - + "(" + mGenreId + ") hasFocus"); + Log.d( + TAG, + "onFocusChanged " + + ((TextView) view).getText() + + "(" + + mGenreId + + ") hasFocus"); } mProgramGuide.requestGenreChange(mGenreId); } diff --git a/src/com/android/tv/guide/GuideUtils.java b/src/com/android/tv/guide/GuideUtils.java index 403d00b5..51c14fd4 100644 --- a/src/com/android/tv/guide/GuideUtils.java +++ b/src/com/android/tv/guide/GuideUtils.java @@ -17,11 +17,9 @@ package com.android.tv.guide; import android.graphics.Rect; -import android.support.annotation.NonNull; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; - import java.util.ArrayList; import java.util.concurrent.TimeUnit; @@ -30,8 +28,8 @@ class GuideUtils { private static int sWidthPerHour = 0; /** - * Sets the width in pixels that corresponds to an hour in program guide. - * Assume that this is called from main thread only, so, no synchronization. + * Sets the width in pixels that corresponds to an hour in program guide. Assume that this is + * called from main thread only, so, no synchronization. */ static void setWidthPerHour(int widthPerHour) { sWidthPerHour = widthPerHour; @@ -44,30 +42,29 @@ class GuideUtils { return (int) (millis * sWidthPerHour / TimeUnit.HOURS.toMillis(1)); } - /** - * Gets the number of pixels in program guide table that corresponds to the given range. - */ + /** Gets the number of pixels in program guide table that corresponds to the given range. */ static int convertMillisToPixel(long startMillis, long endMillis) { // Convert to pixels first to avoid accumulation of rounding errors. return GuideUtils.convertMillisToPixel(endMillis) - GuideUtils.convertMillisToPixel(startMillis); } - /** - * Gets the time in millis that corresponds to the given pixels in the program guide. - */ + /** Gets the time in millis that corresponds to the given pixels in the program guide. */ static long convertPixelToMillis(int pixel) { return pixel * TimeUnit.HOURS.toMillis(1) / sWidthPerHour; } /** * Return the view should be focused in the given program row according to the focus range. - + * * @param keepCurrentProgramFocused If {@code true}, focuses on the current program if possible, - * else falls back the general logic. + * else falls back the general logic. */ - static View findNextFocusedProgram(View programRow, int focusRangeLeft, - int focusRangeRight, boolean keepCurrentProgramFocused) { + static View findNextFocusedProgram( + View programRow, + int focusRangeLeft, + int focusRangeRight, + boolean keepCurrentProgramFocused) { ArrayList<View> focusables = new ArrayList<>(); findFocusables(programRow, focusables); @@ -102,9 +99,10 @@ class GuideUtils { maxFullyOverlappedWidth = width; } } else if (maxFullyOverlappedWidth == Integer.MIN_VALUE) { - int overlappedWidth = (focusRangeLeft <= focusableRect.left) ? - focusRangeRight - focusableRect.left - : focusableRect.right - focusRangeLeft; + int overlappedWidth = + (focusRangeLeft <= focusableRect.left) + ? focusRangeRight - focusableRect.left + : focusableRect.right - focusRangeLeft; if (overlappedWidth > maxPartiallyOverlappedWidth) { nextFocusIndex = i; maxPartiallyOverlappedWidth = overlappedWidth; @@ -118,16 +116,14 @@ class GuideUtils { } /** - * Returns {@code true} if the program displayed in the give - * {@link com.android.tv.guide.ProgramItemView} is a current program. + * Returns {@code true} if the program displayed in the give {@link + * com.android.tv.guide.ProgramItemView} is a current program. */ static boolean isCurrentProgram(ProgramItemView view) { return view.getTableEntry().isCurrentProgram(); } - /** - * Returns {@code true} if the given view is a descendant of the give container. - */ + /** Returns {@code true} if the given view is a descendant of the give container. */ static boolean isDescendant(ViewGroup container, View view) { if (view == null) { return false; @@ -152,5 +148,5 @@ class GuideUtils { } } - private GuideUtils() { } + private GuideUtils() {} } diff --git a/src/com/android/tv/guide/ProgramGrid.java b/src/com/android/tv/guide/ProgramGrid.java index 58436425..caafb045 100644 --- a/src/com/android/tv/guide/ProgramGrid.java +++ b/src/com/android/tv/guide/ProgramGrid.java @@ -25,15 +25,11 @@ import android.util.Log; import android.util.Range; import android.view.View; import android.view.ViewTreeObserver; - import com.android.tv.R; import com.android.tv.ui.OnRepeatedKeyInterceptListener; - import java.util.concurrent.TimeUnit; -/** - * A {@link VerticalGridView} for the program table view. - */ +/** A {@link VerticalGridView} for the program table view. */ public class ProgramGrid extends VerticalGridView { private static final String TAG = "ProgramGrid"; @@ -84,7 +80,7 @@ public class ProgramGrid extends VerticalGridView { private final int mRowHeight; private final int mDetailHeight; - private final int mSelectionRow; // Row that is focused + private final int mSelectionRow; // Row that is focused private View mLastFocusedView; private final Rect mTempRect = new Rect(); @@ -97,8 +93,8 @@ public class ProgramGrid extends VerticalGridView { interface ChildFocusListener { /** - * Is called before focus is moved. Only children to {@code ProgramGrid} will be passed. - * See {@code ProgramGrid#setChildFocusListener(ChildFocusListener)}. + * Is called before focus is moved. Only children to {@code ProgramGrid} will be passed. See + * {@code ProgramGrid#setChildFocusListener(ChildFocusListener)}. */ void onRequestChildFocus(View oldFocus, View newFocus); } @@ -207,16 +203,13 @@ public class ProgramGrid extends VerticalGridView { } /** - * Initializes ProgramGrid. It should be called before the view is actually attached to - * Window. + * Initializes ProgramGrid. It should be called before the view is actually attached to Window. */ void initialize(ProgramManager programManager) { mProgramManager = programManager; } - /** - * Registers a listener focus events occurring on children to the {@code ProgramGrid}. - */ + /** Registers a listener focus events occurring on children to the {@code ProgramGrid}. */ void setChildFocusListener(ChildFocusListener childFocusListener) { mChildFocusListener = childFocusListener; } @@ -226,8 +219,8 @@ public class ProgramGrid extends VerticalGridView { } /** - * Resets focus states. If the logic to keep the last focus needs to be cleared, it should - * be called. + * Resets focus states. If the logic to keep the last focus needs to be cleared, it should be + * called. */ void resetFocusState() { mLastFocusedView = null; @@ -255,8 +248,8 @@ public class ProgramGrid extends VerticalGridView { Log.w(TAG, "No child view has focus"); return null; } - int nextChildIndex = direction == View.FOCUS_UP ? focusedChildIndex - 1 - : focusedChildIndex + 1; + int nextChildIndex = + direction == View.FOCUS_UP ? focusedChildIndex - 1 : focusedChildIndex + 1; if (nextChildIndex < 0 || nextChildIndex >= getChildCount()) { // Wraparound if reached head or end if (getSelectedPosition() == 0) { @@ -268,8 +261,12 @@ public class ProgramGrid extends VerticalGridView { } return focused; } - View nextFocusedProgram = GuideUtils.findNextFocusedProgram(getChildAt(nextChildIndex), - mFocusRangeLeft, mFocusRangeRight, mKeepCurrentProgramFocused); + View nextFocusedProgram = + GuideUtils.findNextFocusedProgram( + getChildAt(nextChildIndex), + mFocusRangeLeft, + mFocusRangeRight, + mKeepCurrentProgramFocused); if (nextFocusedProgram != null) { nextFocusedProgram.getGlobalVisibleRect(mTempRect); mNextFocusByUpDown = nextFocusedProgram; @@ -320,8 +317,9 @@ public class ProgramGrid extends VerticalGridView { mFocusRangeRight = getRightMostFocusablePosition(); mNextFocusByUpDown = null; // If focus is not a program item, drop focus to the current program when back to the grid - mKeepCurrentProgramFocused = !(focus instanceof ProgramItemView) - || GuideUtils.isCurrentProgram((ProgramItemView) focus); + mKeepCurrentProgramFocused = + !(focus instanceof ProgramItemView) + || GuideUtils.isCurrentProgram((ProgramItemView) focus); } private int getRightMostFocusablePosition() { diff --git a/src/com/android/tv/guide/ProgramGuide.java b/src/com/android/tv/guide/ProgramGuide.java index dd5444e2..5b53f904 100644 --- a/src/com/android/tv/guide/ProgramGuide.java +++ b/src/com/android/tv/guide/ProgramGuide.java @@ -43,14 +43,14 @@ import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityManager; - +import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener; import com.android.tv.ChannelTuner; -import com.android.tv.Features; import com.android.tv.MainActivity; import com.android.tv.R; -import com.android.tv.util.DurationTimer; +import com.android.tv.TvFeatures; import com.android.tv.analytics.Tracker; import com.android.tv.common.WeakHandler; +import com.android.tv.common.util.DurationTimer; import com.android.tv.data.ChannelDataManager; import com.android.tv.data.GenreItems; import com.android.tv.data.ProgramDataManager; @@ -58,17 +58,16 @@ import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrScheduleManager; import com.android.tv.ui.HardwareLayerAnimatorListenerAdapter; 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 java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; -/** - * The program guide. - */ -public class ProgramGuide implements ProgramGrid.ChildFocusListener { +/** The program guide. */ +public class ProgramGuide + implements ProgramGrid.ChildFocusListener, AccessibilityStateChangeListener { private static final String TAG = "ProgramGuide"; private static final boolean DEBUG = false; @@ -83,8 +82,8 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { // We clip out the first program entry in ProgramManager, if it does not have enough width. // In order to prevent from clipping out the current program, this value need be larger than // or equal to ProgramManager.FIRST_ENTRY_MIN_DURATION. - private static final long MIN_DURATION_FROM_START_TIME_TO_CURRENT_TIME - = ProgramManager.FIRST_ENTRY_MIN_DURATION; + private static final long MIN_DURATION_FROM_START_TIME_TO_CURRENT_TIME = + ProgramManager.FIRST_ENTRY_MIN_DURATION; private static final int MSG_PROGRAM_TABLE_FADE_IN_ANIM = 1000; @@ -103,7 +102,7 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { private final long mViewPortMillis; private final int mRowHeight; private final int mDetailHeight; - private final int mSelectionRow; // Row that is focused + private final int mSelectionRow; // Row that is focused private final int mTableFadeAnimDuration; private final int mAnimationDuration; private final int mDetailPadding; @@ -145,34 +144,44 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { private final Handler mHandler = new ProgramGuideHandler(this); private boolean mActive; - private final Runnable mHideRunnable = new Runnable() { - @Override - public void run() { - hide(); - } - }; + private final AutoHideScheduler mAutoHideScheduler; private final long mShowDurationMillis; private ViewTreeObserver.OnGlobalLayoutListener mOnLayoutListenerForShow; private final ProgramManagerListener mProgramManagerListener = new ProgramManagerListener(); - private final Runnable mUpdateTimeIndicator = new Runnable() { - @Override - public void run() { - positionCurrentTimeIndicator(); - mHandler.postAtTime(this, - Utils.ceilTime(SystemClock.uptimeMillis(), TIME_INDICATOR_UPDATE_FREQUENCY)); - } - }; - - public ProgramGuide(MainActivity activity, ChannelTuner channelTuner, - TvInputManagerHelper tvInputManagerHelper, ChannelDataManager channelDataManager, - ProgramDataManager programDataManager, @Nullable DvrDataManager dvrDataManager, - @Nullable DvrScheduleManager dvrScheduleManager, Tracker tracker, - Runnable preShowRunnable, Runnable postHideRunnable) { + private final Runnable mUpdateTimeIndicator = + new Runnable() { + @Override + public void run() { + positionCurrentTimeIndicator(); + mHandler.postAtTime( + this, + Utils.ceilTime( + SystemClock.uptimeMillis(), TIME_INDICATOR_UPDATE_FREQUENCY)); + } + }; + + @SuppressWarnings("RestrictTo") + public ProgramGuide( + MainActivity activity, + ChannelTuner channelTuner, + TvInputManagerHelper tvInputManagerHelper, + ChannelDataManager channelDataManager, + ProgramDataManager programDataManager, + @Nullable DvrDataManager dvrDataManager, + @Nullable DvrScheduleManager dvrScheduleManager, + Tracker tracker, + Runnable preShowRunnable, + Runnable postHideRunnable) { mActivity = activity; - mProgramManager = new ProgramManager(tvInputManagerHelper, channelDataManager, - programDataManager, dvrDataManager, dvrScheduleManager); + mProgramManager = + new ProgramManager( + tvInputManagerHelper, + channelDataManager, + programDataManager, + dvrDataManager, + dvrScheduleManager); mChannelTuner = channelTuner; mTracker = tracker; mPreShowRunnable = preShowRunnable; @@ -185,9 +194,11 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { Point displaySize = new Point(); mActivity.getWindowManager().getDefaultDisplay().getSize(displaySize); - int gridWidth = displaySize.x - - res.getDimensionPixelOffset(R.dimen.program_guide_table_margin_start) - - res.getDimensionPixelSize(R.dimen.program_guide_table_header_column_width); + int gridWidth = + displaySize.x + - res.getDimensionPixelOffset(R.dimen.program_guide_table_margin_start) + - res.getDimensionPixelSize( + R.dimen.program_guide_table_header_column_width); mViewPortMillis = (gridWidth * HOUR_IN_MILLIS) / mWidthPerHour; mRowHeight = res.getDimensionPixelSize(R.dimen.program_guide_table_item_row_height); @@ -201,43 +212,49 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { mDetailPadding = res.getDimensionPixelOffset(R.dimen.program_guide_table_detail_padding); mContainer = mActivity.findViewById(R.id.program_guide); - ViewTreeObserver.OnGlobalFocusChangeListener globalFocusChangeListener - = new GlobalFocusChangeListener(); + ViewTreeObserver.OnGlobalFocusChangeListener globalFocusChangeListener = + new GlobalFocusChangeListener(); mContainer.getViewTreeObserver().addOnGlobalFocusChangeListener(globalFocusChangeListener); GenreListAdapter genreListAdapter = new GenreListAdapter(mActivity, mProgramManager, this); mSidePanel = mContainer.findViewById(R.id.program_guide_side_panel); - mSidePanelGridView = (VerticalGridView) mContainer.findViewById( - R.id.program_guide_side_panel_grid_view); - mSidePanelGridView.getRecycledViewPool().setMaxRecycledViews( - R.layout.program_guide_side_panel_row, - res.getInteger(R.integer.max_recycled_view_pool_epg_side_panel_row)); + mSidePanelGridView = + (VerticalGridView) mContainer.findViewById(R.id.program_guide_side_panel_grid_view); + mSidePanelGridView + .getRecycledViewPool() + .setMaxRecycledViews( + R.layout.program_guide_side_panel_row, + res.getInteger(R.integer.max_recycled_view_pool_epg_side_panel_row)); mSidePanelGridView.setAdapter(genreListAdapter); mSidePanelGridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE); - mSidePanelGridView.setWindowAlignmentOffset(mActivity.getResources() - .getDimensionPixelOffset(R.dimen.program_guide_side_panel_alignment_y)); + mSidePanelGridView.setWindowAlignmentOffset( + mActivity + .getResources() + .getDimensionPixelOffset(R.dimen.program_guide_side_panel_alignment_y)); mSidePanelGridView.setWindowAlignmentOffsetPercent( VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED); - if (Features.EPG_SEARCH.isEnabled(mActivity)) { - mSearchOrb = (SearchOrbView) mContainer.findViewById( - R.id.program_guide_side_panel_search_orb); + if (TvFeatures.EPG_SEARCH.isEnabled(mActivity)) { + mSearchOrb = + (SearchOrbView) + mContainer.findViewById(R.id.program_guide_side_panel_search_orb); mSearchOrb.setVisibility(View.VISIBLE); - mSearchOrb.setOnOrbClickedListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - hide(); - mActivity.showProgramGuideSearchFragment(); - } - }); + mSearchOrb.setOnOrbClickedListener( + new View.OnClickListener() { + @Override + public void onClick(View view) { + hide(); + mActivity.showProgramGuideSearchFragment(); + } + }); mSidePanelGridView.setOnChildSelectedListener( new android.support.v17.leanback.widget.OnChildSelectedListener() { - @Override - public void onChildSelected(ViewGroup viewGroup, View view, int i, long l) { - mSearchOrb.animate().alpha(i == 0 ? 1.0f : 0.0f); - } - }); + @Override + public void onChildSelected(ViewGroup viewGroup, View view, int i, long l) { + mSearchOrb.animate().alpha(i == 0 ? 1.0f : 0.0f); + } + }); } else { mSearchOrb = null; } @@ -246,134 +263,156 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { mTimelineRow = (TimelineRow) mTable.findViewById(R.id.time_row); mTimeListAdapter = new TimeListAdapter(res); - mTimelineRow.getRecycledViewPool().setMaxRecycledViews( - R.layout.program_guide_table_header_row_item, - res.getInteger(R.integer.max_recycled_view_pool_epg_header_row_item)); + mTimelineRow + .getRecycledViewPool() + .setMaxRecycledViews( + R.layout.program_guide_table_header_row_item, + res.getInteger(R.integer.max_recycled_view_pool_epg_header_row_item)); mTimelineRow.setAdapter(mTimeListAdapter); ProgramTableAdapter programTableAdapter = new ProgramTableAdapter(mActivity, this); - programTableAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { - @Override - public void onChanged() { - // It is usually called when Genre is changed. - // Reset selection of ProgramGrid - resetRowSelection(); - updateGuidePosition(); - } - }); + programTableAdapter.registerAdapterDataObserver( + new RecyclerView.AdapterDataObserver() { + @Override + public void onChanged() { + // It is usually called when Genre is changed. + // Reset selection of ProgramGrid + resetRowSelection(); + updateGuidePosition(); + } + }); mGrid = (ProgramGrid) mTable.findViewById(R.id.grid); mGrid.initialize(mProgramManager); - mGrid.getRecycledViewPool().setMaxRecycledViews( - R.layout.program_guide_table_row, - res.getInteger(R.integer.max_recycled_view_pool_epg_table_row)); + mGrid.getRecycledViewPool() + .setMaxRecycledViews( + R.layout.program_guide_table_row, + res.getInteger(R.integer.max_recycled_view_pool_epg_table_row)); mGrid.setAdapter(programTableAdapter); mGrid.setChildFocusListener(this); - mGrid.setOnChildSelectedListener(new OnChildSelectedListener() { - @Override - public void onChildSelected(ViewGroup parent, View view, int position, long id) { - if (mIsDuringResetRowSelection) { - // Ignore if it's during the first resetRowSelection, because onChildSelected - // will be called again when rows are bound to the program table. if selectRow - // is called here, mSelectedRow is set and the second selectRow call doesn't - // work as intended. - mIsDuringResetRowSelection = false; - return; - } - selectRow(view); - } - }); + mGrid.setOnChildSelectedListener( + new OnChildSelectedListener() { + @Override + public void onChildSelected( + ViewGroup parent, View view, int position, long id) { + if (mIsDuringResetRowSelection) { + // Ignore if it's during the first resetRowSelection, because + // onChildSelected + // will be called again when rows are bound to the program table. if + // selectRow + // is called here, mSelectedRow is set and the second selectRow call + // doesn't + // work as intended. + mIsDuringResetRowSelection = false; + return; + } + selectRow(view); + } + }); mGrid.setFocusScrollStrategy(ProgramGrid.FOCUS_SCROLL_ALIGNED); mGrid.setWindowAlignmentOffset(mSelectionRow * mRowHeight); mGrid.setWindowAlignmentOffsetPercent(ProgramGrid.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED); mGrid.setItemAlignmentOffset(0); mGrid.setItemAlignmentOffsetPercent(ProgramGrid.ITEM_ALIGN_OFFSET_PERCENT_DISABLED); - RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() { - @Override - public void onScrolled(RecyclerView recyclerView, int dx, int dy) { - onHorizontalScrolled(dx); - } - }; + RecyclerView.OnScrollListener onScrollListener = + new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + onHorizontalScrolled(dx); + } + }; mTimelineRow.addOnScrollListener(onScrollListener); mCurrentTimeIndicator = mTable.findViewById(R.id.current_time_indicator); - mShowAnimatorFull = createAnimator( - R.animator.program_guide_side_panel_enter_full, - 0, - R.animator.program_guide_table_enter_full); - - mShowAnimatorPartial = createAnimator( - R.animator.program_guide_side_panel_enter_partial, - 0, - R.animator.program_guide_table_enter_partial); - mShowAnimatorPartial.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - mSidePanelGridView.setVisibility(View.VISIBLE); - mSidePanelGridView.setAlpha(1.0f); - } - }); - - mHideAnimatorFull = createAnimator( - R.animator.program_guide_side_panel_exit, - 0, - R.animator.program_guide_table_exit); - mHideAnimatorFull.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mContainer.setVisibility(View.GONE); - } - }); - mHideAnimatorPartial = createAnimator( - R.animator.program_guide_side_panel_exit, - 0, - R.animator.program_guide_table_exit); - mHideAnimatorPartial.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mContainer.setVisibility(View.GONE); - } - }); - - mPartialToFullAnimator = createAnimator( - R.animator.program_guide_side_panel_hide, - R.animator.program_guide_side_panel_grid_fade_out, - R.animator.program_guide_table_partial_to_full); - mFullToPartialAnimator = createAnimator( - R.animator.program_guide_side_panel_reveal, - R.animator.program_guide_side_panel_grid_fade_in, - R.animator.program_guide_table_full_to_partial); - - mProgramTableFadeOutAnimator = AnimatorInflater.loadAnimator(mActivity, - R.animator.program_guide_table_fade_out); + mShowAnimatorFull = + createAnimator( + R.animator.program_guide_side_panel_enter_full, + 0, + R.animator.program_guide_table_enter_full); + + mShowAnimatorPartial = + createAnimator( + R.animator.program_guide_side_panel_enter_partial, + 0, + R.animator.program_guide_table_enter_partial); + mShowAnimatorPartial.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mSidePanelGridView.setVisibility(View.VISIBLE); + mSidePanelGridView.setAlpha(1.0f); + } + }); + + mHideAnimatorFull = + createAnimator( + R.animator.program_guide_side_panel_exit, + 0, + R.animator.program_guide_table_exit); + mHideAnimatorFull.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mContainer.setVisibility(View.GONE); + } + }); + mHideAnimatorPartial = + createAnimator( + R.animator.program_guide_side_panel_exit, + 0, + R.animator.program_guide_table_exit); + mHideAnimatorPartial.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mContainer.setVisibility(View.GONE); + } + }); + + mPartialToFullAnimator = + createAnimator( + R.animator.program_guide_side_panel_hide, + R.animator.program_guide_side_panel_grid_fade_out, + R.animator.program_guide_table_partial_to_full); + mFullToPartialAnimator = + createAnimator( + R.animator.program_guide_side_panel_reveal, + R.animator.program_guide_side_panel_grid_fade_in, + R.animator.program_guide_table_full_to_partial); + + mProgramTableFadeOutAnimator = + AnimatorInflater.loadAnimator(mActivity, R.animator.program_guide_table_fade_out); mProgramTableFadeOutAnimator.setTarget(mTable); - mProgramTableFadeOutAnimator.addListener(new HardwareLayerAnimatorListenerAdapter(mTable) { - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - - if (!isActive()) { - return; - } - mProgramManager.resetChannelListWithGenre(mLastRequestedGenreId); - resetTimelineScroll(); - if (!mHandler.hasMessages(MSG_PROGRAM_TABLE_FADE_IN_ANIM)) { - mHandler.sendEmptyMessage(MSG_PROGRAM_TABLE_FADE_IN_ANIM); - } - } - }); - mProgramTableFadeInAnimator = AnimatorInflater.loadAnimator(mActivity, - R.animator.program_guide_table_fade_in); + mProgramTableFadeOutAnimator.addListener( + new HardwareLayerAnimatorListenerAdapter(mTable) { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + + if (!isActive()) { + return; + } + mProgramManager.resetChannelListWithGenre(mLastRequestedGenreId); + resetTimelineScroll(); + if (!mHandler.hasMessages(MSG_PROGRAM_TABLE_FADE_IN_ANIM)) { + mHandler.sendEmptyMessage(MSG_PROGRAM_TABLE_FADE_IN_ANIM); + } + } + }); + mProgramTableFadeInAnimator = + AnimatorInflater.loadAnimator(mActivity, R.animator.program_guide_table_fade_in); mProgramTableFadeInAnimator.setTarget(mTable); mProgramTableFadeInAnimator.addListener(new HardwareLayerAnimatorListenerAdapter(mTable)); mSharedPreference = PreferenceManager.getDefaultSharedPreferences(mActivity); mAccessibilityManager = (AccessibilityManager) mActivity.getSystemService(Context.ACCESSIBILITY_SERVICE); - mShowGuidePartial = mAccessibilityManager.isEnabled() - || mSharedPreference.getBoolean(KEY_SHOW_GUIDE_PARTIAL, true); + mShowGuidePartial = + mAccessibilityManager.isEnabled() + || mSharedPreference.getBoolean(KEY_SHOW_GUIDE_PARTIAL, true); + mAutoHideScheduler = new AutoHideScheduler(activity, this::hide); } @Override @@ -397,12 +436,12 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { } /** - * Show the program guide. This reveals the side panel, and the program guide table is shown + * Show the program guide. This reveals the side panel, and the program guide table is shown * partially. * - * <p>Note: the animation which starts together with ProgramGuide showing animation needs to - * be initiated in {@code runnableAfterAnimatorReady}. If the animation starts together - * with show(), the animation may drop some frames. + * <p>Note: the animation which starts together with ProgramGuide showing animation needs to be + * initiated in {@code runnableAfterAnimatorReady}. If the animation starts together with + * show(), the animation may drop some frames. */ public void show(final Runnable runnableAfterAnimatorReady) { if (mContainer.getVisibility() == View.VISIBLE) { @@ -416,9 +455,10 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { mVisibleDuration.start(); mProgramManager.programGuideVisibilityChanged(true); - mStartUtcTime = Utils.floorTime( - System.currentTimeMillis() - MIN_DURATION_FROM_START_TIME_TO_CURRENT_TIME, - HALF_HOUR_IN_MILLIS); + mStartUtcTime = + Utils.floorTime( + System.currentTimeMillis() - MIN_DURATION_FROM_START_TIME_TO_CURRENT_TIME, + HALF_HOUR_IN_MILLIS); mProgramManager.updateInitialTimeRange(mStartUtcTime, mStartUtcTime + mViewPortMillis); mProgramManager.addListener(mProgramManagerListener); mLastRequestedGenreId = GenreItems.ID_ALL_CHANNELS; @@ -435,51 +475,60 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { if (DEBUG) { Log.d(TAG, "show()"); } - mOnLayoutListenerForShow = new ViewTreeObserver.OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - mContainer.getViewTreeObserver().removeOnGlobalLayoutListener(this); - mTable.setLayerType(View.LAYER_TYPE_HARDWARE, null); - mSidePanelGridView.setLayerType(View.LAYER_TYPE_HARDWARE, null); - mTable.buildLayer(); - mSidePanelGridView.buildLayer(); - mOnLayoutListenerForShow = null; - mTimelineAnimation = true; - // Make sure that time indicator update starts after animation is finished. - startCurrentTimeIndicator(TIME_INDICATOR_UPDATE_FREQUENCY); - if (DEBUG) { - mContainer.getViewTreeObserver().addOnDrawListener( - new ViewTreeObserver.OnDrawListener() { - long time = System.currentTimeMillis(); - int count = 0; - - @Override - public void onDraw() { - long curtime = System.currentTimeMillis(); - Log.d(TAG, "onDraw " + count++ + " " + (curtime - time) + "ms"); - time = curtime; - if (count > 10) { - mContainer.getViewTreeObserver().removeOnDrawListener(this); - } - } - }); - } - updateGuidePosition(); - runnableAfterAnimatorReady.run(); - if (mShowGuidePartial) { - mShowAnimatorPartial.start(); - } else { - mShowAnimatorFull.start(); - } - } - }; + mOnLayoutListenerForShow = + new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + mContainer.getViewTreeObserver().removeOnGlobalLayoutListener(this); + mTable.setLayerType(View.LAYER_TYPE_HARDWARE, null); + mSidePanelGridView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + mTable.buildLayer(); + mSidePanelGridView.buildLayer(); + mOnLayoutListenerForShow = null; + mTimelineAnimation = true; + // Make sure that time indicator update starts after animation is finished. + startCurrentTimeIndicator(TIME_INDICATOR_UPDATE_FREQUENCY); + if (DEBUG) { + mContainer + .getViewTreeObserver() + .addOnDrawListener( + new ViewTreeObserver.OnDrawListener() { + long time = System.currentTimeMillis(); + int count = 0; + + @Override + public void onDraw() { + long curtime = System.currentTimeMillis(); + Log.d( + TAG, + "onDraw " + + count++ + + " " + + (curtime - time) + + "ms"); + time = curtime; + if (count > 10) { + mContainer + .getViewTreeObserver() + .removeOnDrawListener(this); + } + } + }); + } + updateGuidePosition(); + runnableAfterAnimatorReady.run(); + if (mShowGuidePartial) { + mShowAnimatorPartial.start(); + } else { + mShowAnimatorFull.start(); + } + } + }; mContainer.getViewTreeObserver().addOnGlobalLayoutListener(mOnLayoutListenerForShow); scheduleHide(); } - /** - * Hide the program guide. - */ + /** Hide the program guide. */ public void hide() { if (!isActive()) { return; @@ -516,52 +565,43 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { } } - /** - * Schedules hiding the program guide. - */ + /** Schedules hiding the program guide. */ public void scheduleHide() { - cancelHide(); - mHandler.postDelayed(mHideRunnable, mShowDurationMillis); + mAutoHideScheduler.schedule(mShowDurationMillis); } - /** - * Cancels hiding the program guide. - */ + /** Cancels hiding the program guide. */ public void cancelHide() { - mHandler.removeCallbacks(mHideRunnable); + mAutoHideScheduler.cancel(); } - /** - * Process the {@code KEYCODE_BACK} key event. - */ + /** Process the {@code KEYCODE_BACK} key event. */ public void onBackPressed() { hide(); } - /** - * Returns {@code true} if the program guide should process the input events. - */ + /** Returns {@code true} if the program guide should process the input events. */ public boolean isActive() { return mActive; } /** - * Returns {@code true} if the program guide is shown, i.e. showing animation is done and - * hiding animation is not started yet. + * Returns {@code true} if the program guide is shown, i.e. showing animation is done and hiding + * animation is not started yet. */ public boolean isRunningAnimation() { - return mShowAnimatorPartial.isStarted() || mShowAnimatorFull.isStarted() - || mHideAnimatorPartial.isStarted() || mHideAnimatorFull.isStarted(); + return mShowAnimatorPartial.isStarted() + || mShowAnimatorFull.isStarted() + || mHideAnimatorPartial.isStarted() + || mHideAnimatorFull.isStarted(); } - /** Returns if program table is in full screen mode. **/ + /** Returns if program table is in full screen mode. * */ boolean isFull() { return !mShowGuidePartial; } - /** - * Requests change genre to {@code genreId}. - */ + /** Requests change genre to {@code genreId}. */ void requestGenreChange(int genreId) { if (mLastRequestedGenreId == genreId) { // When Recycler.onLayout() removes its children to recycle, @@ -575,15 +615,15 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { // When requestGenreChange is called repeatedly in short time, we keep the fade-out // state for mTableFadeAnimDuration from now. Without it, we'll see blinks. mHandler.removeMessages(MSG_PROGRAM_TABLE_FADE_IN_ANIM); - mHandler.sendEmptyMessageDelayed(MSG_PROGRAM_TABLE_FADE_IN_ANIM, - mTableFadeAnimDuration); + mHandler.sendEmptyMessageDelayed( + MSG_PROGRAM_TABLE_FADE_IN_ANIM, mTableFadeAnimDuration); return; } if (mHandler.hasMessages(MSG_PROGRAM_TABLE_FADE_IN_ANIM)) { mProgramManager.resetChannelListWithGenre(mLastRequestedGenreId); mHandler.removeMessages(MSG_PROGRAM_TABLE_FADE_IN_ANIM); - mHandler.sendEmptyMessageDelayed(MSG_PROGRAM_TABLE_FADE_IN_ANIM, - mTableFadeAnimDuration); + mHandler.sendEmptyMessageDelayed( + MSG_PROGRAM_TABLE_FADE_IN_ANIM, mTableFadeAnimDuration); return; } if (mProgramTableFadeInAnimator.isStarted()) { @@ -593,9 +633,7 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { mProgramTableFadeOutAnimator.start(); } - /** - * Returns the scroll offset of the time line row in pixels. - */ + /** Returns the scroll offset of the time line row in pixels. */ int getTimelineRowScrollOffset() { return mTimelineRow.getScrollOffset(); } @@ -605,9 +643,7 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { return mGrid; } - /** - * Gets {@link VerticalGridView} for "genre select" side panel. - */ + /** Gets {@link VerticalGridView} for "genre select" side panel. */ VerticalGridView getSidePanel() { return mSidePanelGridView; } @@ -628,9 +664,12 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { int startPadding = res.getDimensionPixelOffset(R.dimen.program_guide_table_margin_start); int topPadding = res.getDimensionPixelOffset(R.dimen.program_guide_table_margin_top); int bottomPadding = res.getDimensionPixelOffset(R.dimen.program_guide_table_margin_bottom); - int tableHeight = res.getDimensionPixelOffset(R.dimen.program_guide_table_header_row_height) - + mDetailHeight + mRowHeight * mGrid.getAdapter().getItemCount() + topPadding - + bottomPadding; + int tableHeight = + res.getDimensionPixelOffset(R.dimen.program_guide_table_header_row_height) + + mDetailHeight + + mRowHeight * mGrid.getAdapter().getItemCount() + + topPadding + + bottomPadding; if (tableHeight > screenHeight) { // EPG height is longer that the screen height. mTable.setPaddingRelative(startPadding, topPadding, 0, 0); @@ -645,8 +684,8 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { } } - private Animator createAnimator(int sidePanelAnimResId, int sidePanelGridAnimResId, - int tableAnimResId) { + private Animator createAnimator( + int sidePanelAnimResId, int sidePanelGridAnimResId, int tableAnimResId) { List<Animator> animatorList = new ArrayList<>(); Animator sidePanelAnimator = AnimatorInflater.loadAnimator(mActivity, sidePanelAnimResId); @@ -654,8 +693,8 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { animatorList.add(sidePanelAnimator); if (sidePanelGridAnimResId != 0) { - Animator sidePanelGridAnimator = AnimatorInflater.loadAnimator(mActivity, - sidePanelGridAnimResId); + Animator sidePanelGridAnimator = + AnimatorInflater.loadAnimator(mActivity, sidePanelGridAnimResId); sidePanelGridAnimator.setTarget(mSidePanelGridView); sidePanelGridAnimator.addListener( new HardwareLayerAnimatorListenerAdapter(mSidePanelGridView)); @@ -700,8 +739,9 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { } private void positionCurrentTimeIndicator() { - int offset = GuideUtils.convertMillisToPixel(mStartUtcTime, System.currentTimeMillis()) - - mTimelineRow.getScrollOffset(); + int offset = + GuideUtils.convertMillisToPixel(mStartUtcTime, System.currentTimeMillis()) + - mTimelineRow.getScrollOffset(); if (offset < 0) { mCurrentTimeIndicator.setVisibility(View.GONE); } else { @@ -743,8 +783,7 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { mSelectedRow = null; mIsDuringResetRowSelection = true; mGrid.setSelectedPosition( - Math.max(mProgramManager.getChannelIndex(mChannelTuner.getCurrentChannel()), - 0)); + Math.max(mProgramManager.getChannelIndex(mChannelTuner.getCurrentChannel()), 0)); mGrid.resetFocusState(); mGrid.onItemSelectionReset(); mIsDuringResetRowSelection = false; @@ -767,12 +806,13 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { detailView.setVisibility(View.VISIBLE); final ProgramRow programRow = (ProgramRow) row.findViewById(R.id.row); - programRow.post(new Runnable() { - @Override - public void run() { - programRow.focusCurrentProgram(); - } - }); + programRow.post( + new Runnable() { + @Override + public void run() { + programRow.focusCurrentProgram(); + } + }); } else { animateRowChange(mSelectedRow, row); } @@ -799,38 +839,45 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { if (outDetail != null && outDetail.isShown()) { final View outDetailContent = outDetail.findViewById(R.id.detail_content_full); - Animator fadeOutAnimator = ObjectAnimator.ofPropertyValuesHolder(outDetailContent, - PropertyValuesHolder.ofFloat(View.ALPHA, outDetail.getAlpha(), 0f), - PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, - outDetailContent.getTranslationY(), animationPadding)); + Animator fadeOutAnimator = + ObjectAnimator.ofPropertyValuesHolder( + outDetailContent, + PropertyValuesHolder.ofFloat(View.ALPHA, outDetail.getAlpha(), 0f), + PropertyValuesHolder.ofFloat( + View.TRANSLATION_Y, + outDetailContent.getTranslationY(), + animationPadding)); fadeOutAnimator.setStartDelay(0); fadeOutAnimator.setDuration(mAnimationDuration); fadeOutAnimator.addListener(new HardwareLayerAnimatorListenerAdapter(outDetailContent)); - Animator collapseAnimator = ViewUtils - .createHeightAnimator(outDetail, ViewUtils.getLayoutHeight(outDetail), 0); + Animator collapseAnimator = + ViewUtils.createHeightAnimator( + outDetail, ViewUtils.getLayoutHeight(outDetail), 0); collapseAnimator.setStartDelay(mAnimationDuration); collapseAnimator.setDuration(mTableFadeAnimDuration); - collapseAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animator) { - outDetailContent.setVisibility(View.GONE); - } - - @Override - public void onAnimationEnd(Animator animator) { - outDetailContent.setVisibility(View.VISIBLE); - } - }); + collapseAnimator.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animator) { + outDetailContent.setVisibility(View.GONE); + } + + @Override + public void onAnimationEnd(Animator animator) { + outDetailContent.setVisibility(View.VISIBLE); + } + }); AnimatorSet outAnimator = new AnimatorSet(); outAnimator.playTogether(fadeOutAnimator, collapseAnimator); - outAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animator) { - mDetailOutAnimator = null; - } - }); + outAnimator.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + mDetailOutAnimator = null; + } + }); mDetailOutAnimator = outAnimator; outAnimator.start(); } @@ -842,39 +889,49 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { Animator expandAnimator = ViewUtils.createHeightAnimator(inDetail, 0, mDetailHeight); expandAnimator.setStartDelay(mAnimationDuration); expandAnimator.setDuration(mTableFadeAnimDuration); - expandAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animator) { - inDetailContent.setVisibility(View.GONE); - } - - @Override - public void onAnimationEnd(Animator animator) { - inDetailContent.setVisibility(View.VISIBLE); - inDetailContent.setAlpha(0); - } - }); - Animator fadeInAnimator = ObjectAnimator.ofPropertyValuesHolder(inDetailContent, - PropertyValuesHolder.ofFloat(View.ALPHA, 0f, 1f), - PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, -animationPadding, 0f)); + expandAnimator.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animator) { + inDetailContent.setVisibility(View.GONE); + } + + @Override + public void onAnimationEnd(Animator animator) { + inDetailContent.setVisibility(View.VISIBLE); + inDetailContent.setAlpha(0); + } + }); + Animator fadeInAnimator = + ObjectAnimator.ofPropertyValuesHolder( + inDetailContent, + PropertyValuesHolder.ofFloat(View.ALPHA, 0f, 1f), + PropertyValuesHolder.ofFloat( + View.TRANSLATION_Y, -animationPadding, 0f)); fadeInAnimator.setDuration(mAnimationDuration); fadeInAnimator.addListener(new HardwareLayerAnimatorListenerAdapter(inDetailContent)); AnimatorSet inAnimator = new AnimatorSet(); inAnimator.playSequentially(expandAnimator, fadeInAnimator); - inAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animator) { - mDetailInAnimator = null; - } - }); + inAnimator.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + mDetailInAnimator = null; + } + }); mDetailInAnimator = inAnimator; inAnimator.start(); } } - private class GlobalFocusChangeListener implements - ViewTreeObserver.OnGlobalFocusChangeListener { + @Override + public void onAccessibilityStateChanged(boolean enabled) { + mAutoHideScheduler.onAccessibilityStateChanged(enabled); + } + + private class GlobalFocusChangeListener + implements ViewTreeObserver.OnGlobalFocusChangeListener { private static final int UNKNOWN = 0; private static final int SIDE_PANEL = 1; private static final int PROGRAM_TABLE = 2; @@ -912,11 +969,16 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { private class ProgramManagerListener extends ProgramManager.ListenerAdapter { @Override public void onTimeRangeUpdated() { - int scrollOffset = (int) (mWidthPerHour * mProgramManager.getShiftedTime() - / HOUR_IN_MILLIS); + int scrollOffset = + (int) (mWidthPerHour * mProgramManager.getShiftedTime() / HOUR_IN_MILLIS); if (DEBUG) { - Log.d(TAG, "Horizontal scroll to " + scrollOffset + " pixels (" - + mProgramManager.getShiftedTime() + " millis)"); + Log.d( + TAG, + "Horizontal scroll to " + + scrollOffset + + " pixels (" + + mProgramManager.getShiftedTime() + + " millis)"); } mTimelineRow.scrollTo(scrollOffset, mTimelineAnimation); } diff --git a/src/com/android/tv/guide/ProgramItemView.java b/src/com/android/tv/guide/ProgramItemView.java index b23d578c..9f379e43 100644 --- a/src/com/android/tv/guide/ProgramItemView.java +++ b/src/com/android/tv/guide/ProgramItemView.java @@ -24,7 +24,6 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.graphics.drawable.StateListDrawable; import android.os.Handler; -import android.os.SystemClock; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextUtils; @@ -35,21 +34,21 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import android.widget.Toast; - -import com.android.tv.ApplicationSingletons; import com.android.tv.MainActivity; import com.android.tv.R; -import com.android.tv.TvApplication; +import com.android.tv.TvSingletons; import com.android.tv.analytics.Tracker; import com.android.tv.common.feature.CommonFeatures; -import com.android.tv.data.Channel; +import com.android.tv.common.util.Clock; +import com.android.tv.data.ChannelDataManager; +import com.android.tv.data.Program; +import com.android.tv.data.api.Channel; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.ui.DvrUiHelper; import com.android.tv.guide.ProgramManager.TableEntry; import com.android.tv.util.ToastUtils; import com.android.tv.util.Utils; - import java.lang.reflect.InvocationTargetException; import java.util.concurrent.TimeUnit; @@ -60,10 +59,10 @@ public class ProgramItemView extends TextView { private static final int MAX_PROGRESS = 10000; // From android.widget.ProgressBar.MAX_VALUE // State indicating the focused program is the current program - private static final int[] STATE_CURRENT_PROGRAM = { R.attr.state_current_program }; + private static final int[] STATE_CURRENT_PROGRAM = {R.attr.state_current_program}; // Workaround state in order to not use too much texture memory for RippleDrawable - private static final int[] STATE_TOO_WIDE = { R.attr.state_program_too_wide }; + private static final int[] STATE_TOO_WIDE = {R.attr.state_program_too_wide}; private static int sVisibleThreshold; private static int sItemPadding; @@ -73,8 +72,10 @@ public class ProgramItemView extends TextView { private static TextAppearanceSpan sEpisodeTitleStyle; private static TextAppearanceSpan sGrayedOutEpisodeTitleStyle; + private final DvrManager mDvrManager; + private final Clock mClock; + private final ChannelDataManager mChannelDataManager; private ProgramGuide mProgramGuide; - private DvrManager mDvrManager; private TableEntry mTableEntry; private int mMaxWidthForRipple; private int mTextWidth; @@ -84,96 +85,119 @@ public class ProgramItemView extends TextView { // as a result of the re-layout (see b/21378855). private boolean mPreventParentRelayout; - private static final View.OnClickListener ON_CLICKED = new View.OnClickListener() { - @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(); - final MainActivity tvActivity = (MainActivity) view.getContext(); - final Channel channel = tvActivity.getChannelDataManager().getChannel(entry.channelId); - if (entry.isCurrentProgram()) { - view.postDelayed(new Runnable() { - @Override - public void run() { - tvActivity.tuneToChannel(channel); - tvActivity.hideOverlaysForTune(); + private static final View.OnClickListener ON_CLICKED = + new View.OnClickListener() { + @Override + public void onClick(final View view) { + TableEntry entry = ((ProgramItemView) view).mTableEntry; + Clock clock = ((ProgramItemView) view).mClock; + if (entry == null) { + // do nothing + return; } - }, entry.getWidth() > ((ProgramItemView) view).mMaxWidthForRipple ? 0 - : view.getResources() - .getInteger(R.integer.program_guide_ripple_anim_duration)); - } else if (entry.program != null && CommonFeatures.DVR.isEnabled(view.getContext())) { - DvrManager dvrManager = singletons.getDvrManager(); - if (entry.entryStartUtcMillis > System.currentTimeMillis() - && dvrManager.isProgramRecordable(entry.program)) { - if (entry.scheduledRecording == null) { - DvrUiHelper.checkStorageStatusAndShowErrorMessage(tvActivity, - channel.getInputId(), new Runnable() { + TvSingletons singletons = TvSingletons.getSingletons(view.getContext()); + Tracker tracker = singletons.getTracker(); + tracker.sendEpgItemClicked(); + final MainActivity tvActivity = (MainActivity) view.getContext(); + final Channel channel = + tvActivity.getChannelDataManager().getChannel(entry.channelId); + if (entry.isCurrentProgram()) { + view.postDelayed( + new Runnable() { @Override public void run() { - DvrUiHelper.requestRecordingFutureProgram(tvActivity, - entry.program, false); + tvActivity.tuneToChannel(channel); + tvActivity.hideOverlaysForTune(); } - }); - } else { - dvrManager.removeScheduledRecording(entry.scheduledRecording); - String msg = view.getResources().getString( - R.string.dvr_schedules_deletion_info, entry.program.getTitle()); - ToastUtils.show(view.getContext(), msg, Toast.LENGTH_SHORT); + }, + entry.getWidth() > ((ProgramItemView) view).mMaxWidthForRipple + ? 0 + : view.getResources() + .getInteger( + R.integer + .program_guide_ripple_anim_duration)); + } else if (entry.program != null + && CommonFeatures.DVR.isEnabled(view.getContext())) { + DvrManager dvrManager = singletons.getDvrManager(); + if (entry.entryStartUtcMillis > clock.currentTimeMillis() + && dvrManager.isProgramRecordable(entry.program)) { + if (entry.scheduledRecording == null) { + DvrUiHelper.checkStorageStatusAndShowErrorMessage( + tvActivity, + channel.getInputId(), + new Runnable() { + @Override + public void run() { + DvrUiHelper.requestRecordingFutureProgram( + tvActivity, entry.program, false); + } + }); + } else { + dvrManager.removeScheduledRecording(entry.scheduledRecording); + String msg = + view.getResources() + .getString( + R.string.dvr_schedules_deletion_info, + entry.program.getTitle()); + ToastUtils.show(view.getContext(), msg, Toast.LENGTH_SHORT); + } + } else { + ToastUtils.show( + view.getContext(), + view.getResources() + .getString(R.string.dvr_msg_cannot_record_program), + Toast.LENGTH_SHORT); + } } - } else { - ToastUtils.show(view.getContext(), view.getResources() - .getString(R.string.dvr_msg_cannot_record_program), Toast.LENGTH_SHORT); } - } - } - }; + }; private static final View.OnFocusChangeListener ON_FOCUS_CHANGED = new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View view, boolean hasFocus) { - if (hasFocus) { - ((ProgramItemView) view).mUpdateFocus.run(); - } else { - Handler handler = view.getHandler(); - if (handler != null) { - handler.removeCallbacks(((ProgramItemView) view).mUpdateFocus); + @Override + public void onFocusChange(View view, boolean hasFocus) { + if (hasFocus) { + ((ProgramItemView) view).mUpdateFocus.run(); + } else { + Handler handler = view.getHandler(); + if (handler != null) { + handler.removeCallbacks(((ProgramItemView) view).mUpdateFocus); + } + } } - } - } - }; - - private final Runnable mUpdateFocus = new Runnable() { - @Override - public void run() { - refreshDrawableState(); - TableEntry entry = mTableEntry; - if (entry == null) { - //do nothing - return; - } - if (entry.isCurrentProgram()) { - Drawable background = getBackground(); - if (!mProgramGuide.isActive() || mProgramGuide.isRunningAnimation()) { - // If program guide is not active or is during showing/hiding, - // the animation is unnecessary, skip it. - background.jumpToCurrentState(); + }; + + private final Runnable mUpdateFocus = + new Runnable() { + @Override + public void run() { + refreshDrawableState(); + TableEntry entry = mTableEntry; + if (entry == null) { + // do nothing + return; + } + if (entry.isCurrentProgram()) { + Drawable background = getBackground(); + if (!mProgramGuide.isActive() || mProgramGuide.isRunningAnimation()) { + // If program guide is not active or is during showing/hiding, + // the animation is unnecessary, skip it. + background.jumpToCurrentState(); + } + int progress = + getProgress( + mClock, entry.entryStartUtcMillis, entry.entryEndUtcMillis); + setProgress(background, R.id.reverse_progress, MAX_PROGRESS - progress); + } + if (getHandler() != null) { + getHandler() + .postAtTime( + this, + Utils.ceilTime( + mClock.uptimeMillis(), FOCUS_UPDATE_FREQUENCY)); + } } - int progress = getProgress(entry.entryStartUtcMillis, entry.entryEndUtcMillis); - setProgress(background, R.id.reverse_progress, MAX_PROGRESS - progress); - } - if (getHandler() != null) { - getHandler().postAtTime(this, - Utils.ceilTime(SystemClock.uptimeMillis(), FOCUS_UPDATE_FREQUENCY)); - } - } - }; + }; public ProgramItemView(Context context) { this(context, null); @@ -187,7 +211,10 @@ public class ProgramItemView extends TextView { super(context, attrs, defStyle); setOnClickListener(ON_CLICKED); setOnFocusChangeListener(ON_FOCUS_CHANGED); - mDvrManager = TvApplication.getSingletons(getContext()).getDvrManager(); + TvSingletons singletons = TvSingletons.getSingletons(getContext()); + mDvrManager = singletons.getDvrManager(); + mChannelDataManager = singletons.getChannelDataManager(); + mClock = singletons.getClock(); } private void initIfNeeded() { @@ -196,35 +223,46 @@ public class ProgramItemView extends TextView { } Resources res = getContext().getResources(); - sVisibleThreshold = res.getDimensionPixelOffset( - R.dimen.program_guide_table_item_visible_threshold); + sVisibleThreshold = + res.getDimensionPixelOffset(R.dimen.program_guide_table_item_visible_threshold); sItemPadding = res.getDimensionPixelOffset(R.dimen.program_guide_table_item_padding); - sCompoundDrawablePadding = res.getDimensionPixelOffset( - R.dimen.program_guide_table_item_compound_drawable_padding); - - ColorStateList programTitleColor = ColorStateList.valueOf(res.getColor( - R.color.program_guide_table_item_program_title_text_color, null)); - ColorStateList grayedOutProgramTitleColor = res.getColorStateList( - R.color.program_guide_table_item_grayed_out_program_text_color, null); - ColorStateList episodeTitleColor = ColorStateList.valueOf(res.getColor( - R.color.program_guide_table_item_program_episode_title_text_color, null)); - ColorStateList grayedOutEpisodeTitleColor = ColorStateList.valueOf(res.getColor( - R.color.program_guide_table_item_grayed_out_program_episode_title_text_color, - null)); - int programTitleSize = res.getDimensionPixelSize( - R.dimen.program_guide_table_item_program_title_font_size); - int episodeTitleSize = res.getDimensionPixelSize( - R.dimen.program_guide_table_item_program_episode_title_font_size); - - sProgramTitleStyle = new TextAppearanceSpan(null, 0, programTitleSize, programTitleColor, - null); - sGrayedOutProgramTitleStyle = new TextAppearanceSpan(null, 0, programTitleSize, - grayedOutProgramTitleColor, null); - sEpisodeTitleStyle = new TextAppearanceSpan(null, 0, episodeTitleSize, episodeTitleColor, - null); - sGrayedOutEpisodeTitleStyle = new TextAppearanceSpan(null, 0, episodeTitleSize, - grayedOutEpisodeTitleColor, null); + sCompoundDrawablePadding = + res.getDimensionPixelOffset( + R.dimen.program_guide_table_item_compound_drawable_padding); + + ColorStateList programTitleColor = + ColorStateList.valueOf( + res.getColor( + R.color.program_guide_table_item_program_title_text_color, null)); + ColorStateList grayedOutProgramTitleColor = + res.getColorStateList( + R.color.program_guide_table_item_grayed_out_program_text_color, null); + ColorStateList episodeTitleColor = + ColorStateList.valueOf( + res.getColor( + R.color.program_guide_table_item_program_episode_title_text_color, + null)); + ColorStateList grayedOutEpisodeTitleColor = + ColorStateList.valueOf( + res.getColor( + R.color + .program_guide_table_item_grayed_out_program_episode_title_text_color, + null)); + int programTitleSize = + res.getDimensionPixelSize(R.dimen.program_guide_table_item_program_title_font_size); + int episodeTitleSize = + res.getDimensionPixelSize( + R.dimen.program_guide_table_item_program_episode_title_font_size); + + sProgramTitleStyle = + new TextAppearanceSpan(null, 0, programTitleSize, programTitleColor, null); + sGrayedOutProgramTitleStyle = + new TextAppearanceSpan(null, 0, programTitleSize, grayedOutProgramTitleColor, null); + sEpisodeTitleStyle = + new TextAppearanceSpan(null, 0, episodeTitleSize, episodeTitleColor, null); + sGrayedOutEpisodeTitleStyle = + new TextAppearanceSpan(null, 0, episodeTitleSize, grayedOutEpisodeTitleColor, null); } @Override @@ -236,8 +274,9 @@ public class ProgramItemView extends TextView { @Override protected int[] onCreateDrawableState(int extraSpace) { if (mTableEntry != null) { - int states[] = super.onCreateDrawableState(extraSpace - + STATE_CURRENT_PROGRAM.length + STATE_TOO_WIDE.length); + int[] states = + super.onCreateDrawableState( + extraSpace + STATE_CURRENT_PROGRAM.length + STATE_TOO_WIDE.length); if (mTableEntry.isCurrentProgram()) { mergeDrawableStates(states, STATE_CURRENT_PROGRAM); } @@ -254,86 +293,168 @@ public class ProgramItemView extends TextView { } @SuppressLint("SwitchIntDef") - public void setValues(ProgramGuide programGuide, TableEntry entry, int selectedGenreId, - long fromUtcMillis, long toUtcMillis, String gapTitle) { + public void setValues( + ProgramGuide programGuide, + TableEntry entry, + int selectedGenreId, + long fromUtcMillis, + long toUtcMillis, + String gapTitle) { mProgramGuide = programGuide; mTableEntry = entry; ViewGroup.LayoutParams layoutParams = getLayoutParams(); - layoutParams.width = entry.getWidth(); - setLayoutParams(layoutParams); + if (layoutParams != null) { + // There is no layoutParams in the tests so we skip this + layoutParams.width = entry.getWidth(); + setLayoutParams(layoutParams); + } + String title = mTableEntry.program != null ? mTableEntry.program.getTitle() : null; + if (mTableEntry.isGap()) { + title = gapTitle; + } + if (TextUtils.isEmpty(title)) { + title = getResources().getString(R.string.program_title_for_no_information); + } + updateText(selectedGenreId, title); + updateIcons(); + updateContentDescription(title); + measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); + mTextWidth = getMeasuredWidth() - getPaddingStart() - getPaddingEnd(); + // Maximum width for us to use a ripple + mMaxWidthForRipple = GuideUtils.convertMillisToPixel(fromUtcMillis, toUtcMillis); + } - String title = entry.program != null ? entry.program.getTitle() : null; - String episode = entry.program != null ? - entry.program.getEpisodeDisplayTitle(getContext()) : null; + private boolean isEntryWideEnough() { + return mTableEntry != null && mTableEntry.getWidth() >= sVisibleThreshold; + } + + private void updateText(int selectedGenreId, String title) { + if (!isEntryWideEnough()) { + setText(null); + return; + } + + String episode = + mTableEntry.program != null + ? mTableEntry.program.getEpisodeDisplayTitle(getContext()) + : null; TextAppearanceSpan titleStyle = sGrayedOutProgramTitleStyle; TextAppearanceSpan episodeStyle = sGrayedOutEpisodeTitleStyle; + if (mTableEntry.isGap()) { - if (entry.getWidth() < sVisibleThreshold) { - setText(null); + episode = null; + } else if (mTableEntry.hasGenre(selectedGenreId)) { + titleStyle = sProgramTitleStyle; + episodeStyle = sEpisodeTitleStyle; + } + SpannableStringBuilder description = new SpannableStringBuilder(); + description.append(title); + if (!TextUtils.isEmpty(episode)) { + description.append('\n'); + + // Add a 'zero-width joiner'/ZWJ in order to ensure we have the same line height for + // all lines. This is a non-printing character so it will not change the horizontal + // spacing however it will affect the line height. As we ensure the ZWJ has the same + // text style as the title it will make sure the line height is consistent. + description.append('\u200D'); + + int middle = description.length(); + description.append(episode); + + description.setSpan(titleStyle, 0, middle, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + description.setSpan( + episodeStyle, middle, description.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else { - if (entry.isGap()) { - title = gapTitle; - episode = null; - } else if (entry.hasGenre(selectedGenreId)) { - titleStyle = sProgramTitleStyle; - episodeStyle = sEpisodeTitleStyle; + description.setSpan( + titleStyle, 0, description.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + setText(description); + } + + private void updateIcons() { + // Sets recording icons if needed. + int iconResId = 0; + if (isEntryWideEnough() && mTableEntry.scheduledRecording != null) { + if (mDvrManager.isConflicting(mTableEntry.scheduledRecording)) { + iconResId = R.drawable.ic_warning_white_18dp; + } else { + switch (mTableEntry.scheduledRecording.getState()) { + case ScheduledRecording.STATE_RECORDING_NOT_STARTED: + iconResId = R.drawable.ic_scheduled_recording; + break; + case ScheduledRecording.STATE_RECORDING_IN_PROGRESS: + iconResId = R.drawable.ic_recording_program; + break; + default: + // leave the iconResId=0 + } } - if (TextUtils.isEmpty(title)) { - title = getResources().getString(R.string.program_title_for_no_information); + } + setCompoundDrawablePadding(iconResId != 0 ? sCompoundDrawablePadding : 0); + setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, iconResId, 0); + } + + private void updateContentDescription(String title) { + // The content description includes extra information that is displayed on the detail view + Resources resources = getResources(); + String description = title; + // TODO(b/73282818): only say channel name when the row changes + Channel channel = mChannelDataManager.getChannel(mTableEntry.channelId); + if (channel != null) { + description = channel.getDisplayNumber() + " " + description; + } + description += + " " + + Utils.getDurationString( + getContext(), + mClock, + mTableEntry.entryStartUtcMillis, + mTableEntry.entryEndUtcMillis, + true); + Program program = mTableEntry.program; + if (program != null) { + String episodeDescription = program.getEpisodeContentDescription(getContext()); + if (!TextUtils.isEmpty(episodeDescription)) { + description += " " + episodeDescription; } - SpannableStringBuilder description = new SpannableStringBuilder(); - description.append(title); - if (!TextUtils.isEmpty(episode)) { - description.append('\n'); - - // Add a 'zero-width joiner'/ZWJ in order to ensure we have the same line height for - // all lines. This is a non-printing character so it will not change the horizontal - // spacing however it will affect the line height. As we ensure the ZWJ has the same - // text style as the title it will make sure the line height is consistent. - description.append('\u200D'); - - int middle = description.length(); - description.append(episode); - - description.setSpan(titleStyle, 0, middle, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - description.setSpan(episodeStyle, middle, description.length(), - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (mTableEntry.scheduledRecording != null) { + if (mDvrManager.isConflicting(mTableEntry.scheduledRecording)) { + description += + " " + resources.getString(R.string.dvr_epg_program_recording_conflict); } else { - description.setSpan(titleStyle, 0, description.length(), - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - setText(description); - - // Sets recording icons if needed. - int iconResId = 0; - if (mTableEntry.scheduledRecording != null) { - if (mDvrManager.isConflicting(mTableEntry.scheduledRecording)) { - iconResId = R.drawable.ic_warning_white_18dp; - } else { - switch (mTableEntry.scheduledRecording.getState()) { - case ScheduledRecording.STATE_RECORDING_NOT_STARTED: - iconResId = R.drawable.ic_scheduled_recording; - break; - case ScheduledRecording.STATE_RECORDING_IN_PROGRESS: - iconResId = R.drawable.ic_recording_program; - break; - } + switch (mTableEntry.scheduledRecording.getState()) { + case ScheduledRecording.STATE_RECORDING_NOT_STARTED: + description += + " " + + resources.getString( + R.string.dvr_epg_program_recording_scheduled); + break; + case ScheduledRecording.STATE_RECORDING_IN_PROGRESS: + description += + " " + + resources.getString( + R.string.dvr_epg_program_recording_in_progress); + break; + default: + // do nothing } } - setCompoundDrawablePadding(iconResId != 0 ? sCompoundDrawablePadding : 0); - setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, iconResId, 0); } - measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); - mTextWidth = getMeasuredWidth() - getPaddingStart() - getPaddingEnd(); - // Maximum width for us to use a ripple - mMaxWidthForRipple = GuideUtils.convertMillisToPixel(fromUtcMillis, toUtcMillis); + if (mTableEntry.isBlocked()) { + description += " " + resources.getString(R.string.program_guide_content_locked); + } else if (program != null) { + String programDescription = program.getDescription(); + if (!TextUtils.isEmpty(programDescription)) { + description += " " + programDescription; + } + } + setContentDescription(description); } - /** - * Update programItemView to handle alignments of text. - */ + /** Update programItemView to handle alignments of text. */ public void updateVisibleArea() { View parentView = ((View) getParent()); if (parentView == null) { @@ -341,7 +462,7 @@ public class ProgramItemView extends TextView { } if (getLayoutDirection() == LAYOUT_DIRECTION_LTR) { layoutVisibleArea(parentView.getLeft() - getLeft(), getRight() - parentView.getRight()); - } else { + } else { layoutVisibleArea(getRight() - parentView.getRight(), parentView.getLeft() - getLeft()); } } @@ -349,16 +470,14 @@ public class ProgramItemView extends TextView { /** * Layout title and episode according to visible area. * - * Here's the spec. - * 1. Don't show text if it's shorter than 48dp. - * 2. Try showing whole text in visible area by placing and wrapping text, - * but do not wrap text less than 30min. - * 3. Episode title is visible only if title isn't multi-line. + * <p>Here's the spec. 1. Don't show text if it's shorter than 48dp. 2. Try showing whole text + * in visible area by placing and wrapping text, but do not wrap text less than 30min. 3. + * Episode title is visible only if title isn't multi-line. * * @param startOffset Offset of the start position from the enclosing view's start position. * @param endOffset Offset of the end position from the enclosing view's end position. */ - private void layoutVisibleArea(int startOffset, int endOffset) { + private void layoutVisibleArea(int startOffset, int endOffset) { int width = mTableEntry.getWidth(); int startPadding = Math.max(0, startOffset); int endPadding = Math.max(0, endOffset); @@ -388,8 +507,8 @@ public class ProgramItemView extends TextView { mTableEntry = null; } - private static int getProgress(long start, long end) { - long currentTime = System.currentTimeMillis(); + private static int getProgress(Clock clock, long start, long end) { + long currentTime = clock.currentTimeMillis(); if (currentTime <= start) { return 0; } else if (currentTime >= end) { @@ -417,11 +536,15 @@ public class ProgramItemView extends TextView { private static int getStateCount(StateListDrawable stateListDrawable) { try { - Object stateCount = StateListDrawable.class.getDeclaredMethod("getStateCount") - .invoke(stateListDrawable); + Object stateCount = + StateListDrawable.class + .getDeclaredMethod("getStateCount") + .invoke(stateListDrawable); return (int) stateCount; - } catch (NoSuchMethodException|IllegalAccessException|IllegalArgumentException - |InvocationTargetException e) { + } catch (NoSuchMethodException + | IllegalAccessException + | IllegalArgumentException + | InvocationTargetException e) { Log.e(TAG, "Failed to call StateListDrawable.getStateCount()", e); return 0; } @@ -429,12 +552,15 @@ public class ProgramItemView extends TextView { private static Drawable getStateDrawable(StateListDrawable stateListDrawable, int index) { try { - Object drawable = StateListDrawable.class - .getDeclaredMethod("getStateDrawable", Integer.TYPE) - .invoke(stateListDrawable, index); + Object drawable = + StateListDrawable.class + .getDeclaredMethod("getStateDrawable", Integer.TYPE) + .invoke(stateListDrawable, index); return (Drawable) drawable; - } catch (NoSuchMethodException|IllegalAccessException|IllegalArgumentException - |InvocationTargetException e) { + } catch (NoSuchMethodException + | IllegalAccessException + | IllegalArgumentException + | InvocationTargetException e) { Log.e(TAG, "Failed to call StateListDrawable.getStateDrawable(" + index + ")", e); return null; } diff --git a/src/com/android/tv/guide/ProgramListAdapter.java b/src/com/android/tv/guide/ProgramListAdapter.java index c1fcdd40..397bacfb 100644 --- a/src/com/android/tv/guide/ProgramListAdapter.java +++ b/src/com/android/tv/guide/ProgramListAdapter.java @@ -22,9 +22,8 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import com.android.tv.R; -import com.android.tv.data.Channel; +import com.android.tv.data.api.Channel; import com.android.tv.guide.ProgramManager.TableEntriesUpdatedListener; import com.android.tv.guide.ProgramManager.TableEntry; @@ -111,9 +110,14 @@ class ProgramListAdapter extends RecyclerView.Adapter<ProgramListAdapter.Program Log.d(TAG, "onBind. View = " + itemView + ", Entry = " + entry); } ProgramManager programManager = programGuide.getProgramManager(); - ((ProgramItemView) itemView).setValues(programGuide, entry, - programManager.getSelectedGenreId(), programManager.getFromUtcMillis(), - programManager.getToUtcMillis(), gapTitle); + ((ProgramItemView) itemView) + .setValues( + programGuide, + entry, + programManager.getSelectedGenreId(), + programManager.getFromUtcMillis(), + programManager.getToUtcMillis(), + gapTitle); } void onUnbind() { diff --git a/src/com/android/tv/guide/ProgramManager.java b/src/com/android/tv/guide/ProgramManager.java index 4ec3f77e..3f20a837 100644 --- a/src/com/android/tv/guide/ProgramManager.java +++ b/src/com/android/tv/guide/ProgramManager.java @@ -18,21 +18,20 @@ package com.android.tv.guide; import android.support.annotation.MainThread; import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; import android.util.ArraySet; import android.util.Log; - -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.data.api.Channel; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrScheduleManager; 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 java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -40,9 +39,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; -/** - * Manages the channels and programs for the program guide. - */ +/** Manages the channels and programs for the program guide. */ @MainThread public class ProgramManager { private static final String TAG = "ProgramManager"; @@ -60,7 +57,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 final DvrDataManager mDvrDataManager; // Only set if DVR is enabled private final DvrScheduleManager mDvrScheduleManager; private long mStartUtcMillis; @@ -127,51 +124,67 @@ public class ProgramManager { private final DvrDataManager.ScheduledRecordingListener mScheduledRecordingListener = new DvrDataManager.ScheduledRecordingListener() { - @Override - public void onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) { - for (ScheduledRecording schedule : scheduledRecordings) { - TableEntry oldEntry = getTableEntry(schedule); - if (oldEntry != null) { - TableEntry newEntry = new TableEntry(oldEntry.channelId, oldEntry.program, - schedule, oldEntry.entryStartUtcMillis, - oldEntry.entryEndUtcMillis, oldEntry.isBlocked()); - updateEntry(oldEntry, newEntry); + @Override + public void onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) { + for (ScheduledRecording schedule : scheduledRecordings) { + TableEntry oldEntry = getTableEntry(schedule); + if (oldEntry != null) { + TableEntry newEntry = + new TableEntry( + oldEntry.channelId, + oldEntry.program, + schedule, + oldEntry.entryStartUtcMillis, + oldEntry.entryEndUtcMillis, + oldEntry.isBlocked()); + updateEntry(oldEntry, newEntry); + } + } } - } - } - @Override - public void onScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings) { - for (ScheduledRecording schedule : scheduledRecordings) { - TableEntry oldEntry = getTableEntry(schedule); - if (oldEntry != null) { - TableEntry newEntry = new TableEntry(oldEntry.channelId, oldEntry.program, null, - oldEntry.entryStartUtcMillis, oldEntry.entryEndUtcMillis, - oldEntry.isBlocked()); - updateEntry(oldEntry, newEntry); + @Override + public void onScheduledRecordingRemoved(ScheduledRecording... scheduledRecordings) { + for (ScheduledRecording schedule : scheduledRecordings) { + TableEntry oldEntry = getTableEntry(schedule); + 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... scheduledRecordings) { - for (ScheduledRecording schedule : scheduledRecordings) { - TableEntry oldEntry = getTableEntry(schedule); - if (oldEntry != null) { - TableEntry newEntry = new TableEntry(oldEntry.channelId, oldEntry.program, - schedule, oldEntry.entryStartUtcMillis, - oldEntry.entryEndUtcMillis, oldEntry.isBlocked()); - updateEntry(oldEntry, newEntry); + @Override + public void onScheduledRecordingStatusChanged( + ScheduledRecording... scheduledRecordings) { + for (ScheduledRecording schedule : scheduledRecordings) { + TableEntry oldEntry = getTableEntry(schedule); + if (oldEntry != null) { + TableEntry newEntry = + new TableEntry( + oldEntry.channelId, + oldEntry.program, + schedule, + oldEntry.entryStartUtcMillis, + oldEntry.entryEndUtcMillis, + oldEntry.isBlocked()); + updateEntry(oldEntry, newEntry); + } + } } - } - } - }; + }; private final OnConflictStateChangeListener mOnConflictStateChangeListener = new OnConflictStateChangeListener() { @Override - public void onConflictStateChange(boolean conflict, - ScheduledRecording... schedules) { + public void onConflictStateChange( + boolean conflict, ScheduledRecording... schedules) { for (ScheduledRecording schedule : schedules) { TableEntry entry = getTableEntry(schedule); if (entry != null) { @@ -181,8 +194,10 @@ public class ProgramManager { } }; - public ProgramManager(TvInputManagerHelper tvInputManagerHelper, - ChannelDataManager channelDataManager, ProgramDataManager programDataManager, + public ProgramManager( + TvInputManagerHelper tvInputManagerHelper, + ChannelDataManager channelDataManager, + ProgramDataManager programDataManager, @Nullable DvrDataManager dvrDataManager, @Nullable DvrScheduleManager dvrScheduleManager) { mTvInputManagerHelper = tvInputManagerHelper; @@ -221,52 +236,39 @@ public class ProgramManager { } } - /** - * Adds a {@link Listener}. - */ + /** Adds a {@link Listener}. */ void addListener(Listener listener) { mListeners.add(listener); } - /** - * Registers a listener to be invoked when table entries are updated. - */ + /** Registers a listener to be invoked when table entries are updated. */ void addTableEntriesUpdatedListener(TableEntriesUpdatedListener listener) { mTableEntriesUpdatedListeners.add(listener); } - /** - * Registers a listener to be invoked when a table entry is changed. - */ + /** Registers a listener to be invoked when a table entry is changed. */ void addTableEntryChangedListener(TableEntryChangedListener listener) { mTableEntryChangedListeners.add(listener); } - /** - * Removes a {@link Listener}. - */ + /** Removes a {@link Listener}. */ void removeListener(Listener listener) { mListeners.remove(listener); } - /** - * Removes a previously installed table entries update listener. - */ + /** Removes a previously installed table entries update listener. */ void removeTableEntriesUpdatedListener(TableEntriesUpdatedListener listener) { mTableEntriesUpdatedListeners.remove(listener); } - /** - * Removes a previously installed table entry changed listener. - */ + /** Removes a previously installed table entry changed listener. */ void removeTableEntryChangedListener(TableEntryChangedListener listener) { mTableEntryChangedListeners.remove(listener); } /** - * Resets channel list with given genre. - * Caller should call {@link #buildGenreFilters()} prior to call this API to make - * This notifies channel updates to listeners. + * Resets channel list with given genre. Caller should call {@link #buildGenreFilters()} prior + * to call this API to make This notifies channel updates to listeners. */ void resetChannelListWithGenre(int genreId) { if (genreId == mSelectedGenreId) { @@ -275,8 +277,14 @@ public class ProgramManager { mFilteredChannels = mGenreChannelList.get(genreId); mSelectedGenreId = genreId; if (DEBUG) { - Log.d(TAG, "resetChannelListWithGenre: " + GenreItems.getCanonicalGenre(genreId) - + " has " + mFilteredChannels.size() + " channels out of " + mChannels.size()); + Log.d( + TAG, + "resetChannelListWithGenre: " + + GenreItems.getCanonicalGenre(genreId) + + " has " + + mFilteredChannels.size() + + " channels out of " + + mChannels.size()); } if (mGenreChannelList.get(mSelectedGenreId) == null) { throw new IllegalStateException("Genre filter isn't ready."); @@ -284,9 +292,7 @@ public class ProgramManager { notifyChannelsUpdated(); } - /** - * Update the initial time range to manage. It updates program entries and genre as well. - */ + /** Update the initial time range to manage. It updates program entries and genre as well. */ void updateInitialTimeRange(long startUtcMillis, long endUtcMillis) { mStartUtcMillis = startUtcMillis; if (endUtcMillis > mEndUtcMillis) { @@ -298,10 +304,7 @@ public class ProgramManager { setTimeRange(startUtcMillis, endUtcMillis); } - - /** - * Shifts the time range by the given time. Also makes ProgramGuide scroll the views. - */ + /** Shifts the time range by the given time. Also makes ProgramGuide scroll the views. */ void shiftTime(long timeMillisToScroll) { long fromUtcMillis = mFromUtcMillis + timeMillisToScroll; long toUtcMillis = mToUtcMillis + timeMillisToScroll; @@ -316,23 +319,17 @@ public class ProgramManager { setTimeRange(fromUtcMillis, toUtcMillis); } - /** - * Returned the scrolled(shifted) time in milliseconds. - */ + /** Returned the scrolled(shifted) time in milliseconds. */ long getShiftedTime() { return mFromUtcMillis - mStartUtcMillis; } - /** - * Returns the start time set by {@link #updateInitialTimeRange}. - */ + /** Returns the start time set by {@link #updateInitialTimeRange}. */ long getStartTime() { return mStartUtcMillis; } - /** - * Returns the program index of the program with {@code entryId} or -1 if not found. - */ + /** Returns the program index of the program with {@code entryId} or -1 if not found. */ int getProgramIdIndex(long channelId, long entryId) { List<TableEntry> entries = mChannelIdEntriesMap.get(channelId); if (entries != null) { @@ -345,38 +342,29 @@ public class ProgramManager { return -1; } - /** - * Returns the program index of the program at {@code time} or -1 if not found. - */ + /** Returns the program index of the program at {@code time} or -1 if not found. */ 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); - if (entry.entryStartUtcMillis <= time - && time < entry.entryEndUtcMillis) { + if (entry.entryStartUtcMillis <= time && time < entry.entryEndUtcMillis) { return i; } } return -1; } - /** - * Returns the start time of currently managed time range, in UTC millisecond. - */ + /** Returns the start time of currently managed time range, in UTC millisecond. */ long getFromUtcMillis() { return mFromUtcMillis; } - /** - * Returns the end time of currently managed time range, in UTC millisecond. - */ + /** Returns the end time of currently managed time range, in UTC millisecond. */ long getToUtcMillis() { return mToUtcMillis; } - /** - * Returns the number of the currently managed channels. - */ + /** Returns the number of the currently managed channels. */ int getChannelCount() { return mFilteredChannels.size(); } @@ -393,15 +381,15 @@ public class ProgramManager { } /** - * Returns the index of provided {@link Channel} within the currently managed channels. - * Returns -1 if such a channel is not found. + * Returns the index of provided {@link Channel} within the currently managed channels. Returns + * -1 if such a channel is not found. */ int getChannelIndex(Channel channel) { return mFilteredChannels.indexOf(channel); } /** - * Returns the index of channel with {@code channelId} within the currently managed channels. + * Returns the index of channel with {@code channelId} within the currently managed channels. * Returns -1 if such a channel is not found. */ int getChannelIndex(long channelId) { @@ -425,9 +413,7 @@ public class ProgramManager { return mChannelIdEntriesMap.get(channelId).get(index); } - /** - * Returns list genre ID's which has a channel. - */ + /** Returns list genre ID's which has a channel. */ List<Integer> getFilteredGenreIds() { return mFilteredGenreIds; } @@ -457,15 +443,13 @@ public class ProgramManager { buildGenreFilters(); } - /** - * Updates the table entries without notifying the change. - */ + /** Updates the table entries without notifying the change. */ private void updateTableEntriesWithoutNotification(boolean clear) { if (clear) { mChannelIdEntriesMap.clear(); } - boolean parentalControlsEnabled = mTvInputManagerHelper.getParentalControlSettings() - .isParentalControlsEnabled(); + boolean parentalControlsEnabled = + mTvInputManagerHelper.getParentalControlSettings().isParentalControlsEnabled(); for (Channel channel : mChannels) { long channelId = channel.getId(); // Inline the updating of the mChannelIdEntriesMap here so we can only call @@ -475,8 +459,12 @@ public class ProgramManager { int size = entries.size(); if (DEBUG) { - Log.d(TAG, "Programs are loaded for channel " + channel.getId() - + ", loaded size = " + size); + Log.d( + TAG, + "Programs are loaded for channel " + + channel.getId() + + ", loaded size = " + + size); } if (size == 0) { continue; @@ -496,14 +484,19 @@ public class ProgramManager { } else { TableEntry lastEntry = entries.get(entries.size() - 1); if (mEndUtcMillis > lastEntry.entryEndUtcMillis) { - entries.add(new TableEntry(channelId, lastEntry.entryEndUtcMillis, - mEndUtcMillis)); + entries.add( + new TableEntry( + channelId, lastEntry.entryEndUtcMillis, mEndUtcMillis)); } 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)); + entries.add( + new TableEntry( + lastEntry.channelId, + lastEntry.program, + lastEntry.scheduledRecording, + lastEntry.entryStartUtcMillis, + mEndUtcMillis, + lastEntry.mIsBlocked)); } } } @@ -511,11 +504,10 @@ public class ProgramManager { } /** - * 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 - * with built channel list. - * This is expected to be called whenever program guide is shown. + * 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 with built channel list. This is expected to be called whenever program + * guide is shown. */ private void buildGenreFilters() { if (DEBUG) Log.d(TAG, "buildGenreFilters"); @@ -572,9 +564,13 @@ public class ProgramManager { private void setTimeRange(long fromUtcMillis, long toUtcMillis) { if (DEBUG) { - Log.d(TAG, "setTimeRange. {FromTime=" - + Utils.toTimeString(fromUtcMillis) + ", ToTime=" - + Utils.toTimeString(toUtcMillis) + "}"); + Log.d( + TAG, + "setTimeRange. {FromTime=" + + Utils.toTimeString(fromUtcMillis) + + ", ToTime=" + + Utils.toTimeString(toUtcMillis) + + "}"); } if (mFromUtcMillis != fromUtcMillis || mToUtcMillis != toUtcMillis) { mFromUtcMillis = fromUtcMillis; @@ -585,8 +581,8 @@ public class ProgramManager { private List<TableEntry> createProgramEntries(long channelId, boolean parentalControlsEnabled) { List<TableEntry> entries = new ArrayList<>(); - boolean channelLocked = parentalControlsEnabled - && mChannelDataManager.getChannel(channelId).isLocked(); + boolean channelLocked = + parentalControlsEnabled && mChannelDataManager.getChannel(channelId).isLocked(); if (channelLocked) { entries.add(new TableEntry(channelId, mStartUtcMillis, Long.MAX_VALUE, true)); } else { @@ -597,20 +593,27 @@ public class ProgramManager { // Dummy program. continue; } - long programStartTime = Math.max(program.getStartTimeUtcMillis(), - mStartUtcMillis); + long programStartTime = Math.max(program.getStartTimeUtcMillis(), mStartUtcMillis); long programEndTime = program.getEndTimeUtcMillis(); if (programStartTime > lastProgramEndTime) { // Gap since the last program. - entries.add(new TableEntry(channelId, lastProgramEndTime, - programStartTime)); + entries.add(new TableEntry(channelId, lastProgramEndTime, programStartTime)); lastProgramEndTime = programStartTime; } if (programEndTime > lastProgramEndTime) { - ScheduledRecording scheduledRecording = mDvrDataManager == null ? null - : mDvrDataManager.getScheduledRecordingForProgramId(program.getId()); - entries.add(new TableEntry(channelId, program, scheduledRecording, - lastProgramEndTime, programEndTime, false)); + ScheduledRecording scheduledRecording = + mDvrDataManager == null + ? null + : mDvrDataManager.getScheduledRecordingForProgramId( + program.getId()); + entries.add( + new TableEntry( + channelId, + program, + scheduledRecording, + lastProgramEndTime, + programEndTime, + false)); lastProgramEndTime = programEndTime; } } @@ -622,9 +625,15 @@ public class ProgramManager { // If the first entry's width doesn't have enough width, it is not good to show // the first entry from UI perspective. So we clip it out. entries.remove(0); - entries.set(0, new TableEntry(secondEntry.channelId, secondEntry.program, - secondEntry.scheduledRecording, mStartUtcMillis, - secondEntry.entryEndUtcMillis, secondEntry.mIsBlocked)); + entries.set( + 0, + new TableEntry( + secondEntry.channelId, + secondEntry.program, + secondEntry.scheduledRecording, + mStartUtcMillis, + secondEntry.entryEndUtcMillis, + secondEntry.mIsBlocked)); } } return entries; @@ -662,8 +671,8 @@ public class ProgramManager { /** * Entry for program guide table. An "entry" can be either an actual program or a gap between - * programs. This is needed for {@link ProgramListAdapter} because - * {@link android.support.v17.leanback.widget.HorizontalGridView} ignores margins between items. + * programs. This is needed for {@link ProgramListAdapter} because {@link + * android.support.v17.leanback.widget.HorizontalGridView} ignores margins between items. */ static class TableEntry { /** Channel ID which this entry is included. */ @@ -686,18 +695,27 @@ public class ProgramManager { this(channelId, null, startUtcMillis, endUtcMillis, false); } - private TableEntry(long channelId, long startUtcMillis, long endUtcMillis, - boolean blocked) { + private TableEntry( + long channelId, long startUtcMillis, long endUtcMillis, boolean blocked) { this(channelId, null, null, startUtcMillis, endUtcMillis, blocked); } - private TableEntry(long channelId, Program program, long entryStartUtcMillis, - long entryEndUtcMillis, boolean isBlocked) { + 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, ScheduledRecording scheduledRecording, - long entryStartUtcMillis, long entryEndUtcMillis, boolean isBlocked) { + private TableEntry( + long channelId, + Program program, + ScheduledRecording scheduledRecording, + long entryStartUtcMillis, + long entryEndUtcMillis, + boolean isBlocked) { this.channelId = channelId; this.program = program; this.scheduledRecording = scheduledRecording; @@ -706,46 +724,34 @@ public class ProgramManager { mIsBlocked = isBlocked; } - /** - * A stable id useful for {@link android.support.v7.widget.RecyclerView.Adapter}. - */ + /** A stable id useful for {@link android.support.v7.widget.RecyclerView.Adapter}. */ 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. - */ + /** Returns true if this is a gap. */ boolean isGap() { - return !Program.isValid(program); + return !Program.isProgramValid(program); } - /** - * Returns true if this channel is blocked. - */ + /** Returns true if this channel is blocked. */ boolean isBlocked() { return mIsBlocked; } - /** - * Returns true if this program is on the air. - */ + /** Returns true if this program is on the air. */ boolean isCurrentProgram() { long current = System.currentTimeMillis(); return entryStartUtcMillis <= current && entryEndUtcMillis > current; } - /** - * Returns if this program has the genre. - */ + /** Returns if this program has the genre. */ boolean hasGenre(int genreId) { return !isGap() && program.hasGenre(genreId); } - /** - * Returns the width of table entry, in pixels. - */ + /** Returns the width of table entry, in pixels. */ int getWidth() { return GuideUtils.convertMillisToPixel(entryStartUtcMillis, entryEndUtcMillis); } @@ -753,17 +759,42 @@ public class ProgramManager { @Override public String toString() { return "TableEntry{" - + "hashCode=" + hashCode() - + ", channelId=" + channelId - + ", program=" + program - + ", startTime=" + Utils.toTimeString(entryStartUtcMillis) - + ", endTimeTime=" + Utils.toTimeString(entryEndUtcMillis) + "}"; - } + + "hashCode=" + + hashCode() + + ", channelId=" + + channelId + + ", program=" + + program + + ", startTime=" + + Utils.toTimeString(entryStartUtcMillis) + + ", endTimeTime=" + + Utils.toTimeString(entryEndUtcMillis) + + "}"; + } + } + + @VisibleForTesting + public static TableEntry createTableEntryForTest( + long channelId, + Program program, + ScheduledRecording scheduledRecording, + long entryStartUtcMillis, + long entryEndUtcMillis, + boolean isBlocked) { + return new TableEntry( + channelId, + program, + scheduledRecording, + entryStartUtcMillis, + entryEndUtcMillis, + isBlocked); } interface Listener { void onGenresUpdated(); + void onChannelsUpdated(); + void onTimeRangeUpdated(); } @@ -777,12 +808,12 @@ public class ProgramManager { static class ListenerAdapter implements Listener { @Override - public void onGenresUpdated() { } + public void onGenresUpdated() {} @Override - public void onChannelsUpdated() { } + public void onChannelsUpdated() {} @Override - public void onTimeRangeUpdated() { } + public void onTimeRangeUpdated() {} } } diff --git a/src/com/android/tv/guide/ProgramRow.java b/src/com/android/tv/guide/ProgramRow.java index fefc724c..83175bb6 100644 --- a/src/com/android/tv/guide/ProgramRow.java +++ b/src/com/android/tv/guide/ProgramRow.java @@ -24,12 +24,9 @@ import android.util.Log; import android.util.Range; import android.view.View; import android.view.ViewTreeObserver.OnGlobalLayoutListener; - -import com.android.tv.MainActivity; -import com.android.tv.data.Channel; +import com.android.tv.data.api.Channel; import com.android.tv.guide.ProgramManager.TableEntry; import com.android.tv.util.Utils; - import java.util.concurrent.TimeUnit; public class ProgramRow extends TimelineGridView { @@ -47,25 +44,23 @@ public class ProgramRow extends TimelineGridView { interface ChildFocusListener { /** - * Is called after focus is moved. Caller should check if old and new focuses are - * listener's children. - * See {@code ProgramRow#setChildFocusListener(ChildFocusListener)}. + * Is called after focus is moved. Caller should check if old and new focuses are listener's + * children. See {@code ProgramRow#setChildFocusListener(ChildFocusListener)}. */ void onChildFocus(View oldFocus, View newFocus); } - /** - * Used only for debugging. - */ + /** Used only for debugging. */ private Channel mChannel; - private final OnGlobalLayoutListener mLayoutListener = new OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - getViewTreeObserver().removeOnGlobalLayoutListener(this); - updateChildVisibleArea(); - } - }; + private final OnGlobalLayoutListener mLayoutListener = + new OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + getViewTreeObserver().removeOnGlobalLayoutListener(this); + updateChildVisibleArea(); + } + }; public ProgramRow(Context context) { this(context, null); @@ -79,9 +74,7 @@ public class ProgramRow extends TimelineGridView { super(context, attrs, defStyle); } - /** - * Registers a listener focus events occurring on children to the {@code ProgramRow}. - */ + /** Registers a listener focus events occurring on children to the {@code ProgramRow}. */ public void setChildFocusListener(ChildFocusListener childFocusListener) { mChildFocusListener = childFocusListener; } @@ -108,9 +101,7 @@ public class ProgramRow extends TimelineGridView { updateChildVisibleArea(); } - /** - * Moves focus to the current program. - */ + /** Moves focus to the current program. */ public void focusCurrentProgram() { View currentProgram = getCurrentProgramView(); if (currentProgram == null) { @@ -124,13 +115,15 @@ public class ProgramRow extends TimelineGridView { // Call this API after RTL is resolved. (i.e. View is measured.) private boolean isDirectionStart(int direction) { return getLayoutDirection() == LAYOUT_DIRECTION_LTR - ? direction == View.FOCUS_LEFT : direction == View.FOCUS_RIGHT; + ? direction == View.FOCUS_LEFT + : direction == View.FOCUS_RIGHT; } // Call this API after RTL is resolved. (i.e. View is measured.) private boolean isDirectionEnd(int direction) { return getLayoutDirection() == LAYOUT_DIRECTION_LTR - ? direction == View.FOCUS_RIGHT : direction == View.FOCUS_LEFT; + ? direction == View.FOCUS_RIGHT + : direction == View.FOCUS_LEFT; } @Override @@ -142,8 +135,8 @@ public class ProgramRow extends TimelineGridView { if (isDirectionStart(direction) || direction == View.FOCUS_BACKWARD) { if (focusedEntry.entryStartUtcMillis < fromMillis) { // The current entry starts outside of the view; Align or scroll to the left. - scrollByTime(Math.max(-ONE_HOUR_MILLIS, - focusedEntry.entryStartUtcMillis - fromMillis)); + scrollByTime( + Math.max(-ONE_HOUR_MILLIS, focusedEntry.entryStartUtcMillis - fromMillis)); return focused; } } else if (isDirectionEnd(direction) || direction == View.FOCUS_FORWARD) { @@ -169,17 +162,19 @@ public class ProgramRow extends TimelineGridView { TableEntry targetEntry = ((ProgramItemView) target).getTableEntry(); if (isDirectionStart(direction) || direction == View.FOCUS_BACKWARD) { - if (targetEntry.entryStartUtcMillis < fromMillis && - targetEntry.entryEndUtcMillis < fromMillis + HALF_HOUR_MILLIS) { + if (targetEntry.entryStartUtcMillis < fromMillis + && targetEntry.entryEndUtcMillis < fromMillis + HALF_HOUR_MILLIS) { // The target entry starts outside the view; Align or scroll to the left. - scrollByTime(Math.max(-ONE_HOUR_MILLIS, - targetEntry.entryStartUtcMillis - fromMillis)); + scrollByTime( + Math.max(-ONE_HOUR_MILLIS, targetEntry.entryStartUtcMillis - fromMillis)); } } else if (isDirectionEnd(direction) || direction == View.FOCUS_FORWARD) { if (targetEntry.entryStartUtcMillis > fromMillis + ONE_HOUR_MILLIS + HALF_HOUR_MILLIS) { // The target entry starts outside the view; Align or scroll to the right. - scrollByTime(Math.min(ONE_HOUR_MILLIS, - targetEntry.entryStartUtcMillis - fromMillis - ONE_HOUR_MILLIS)); + scrollByTime( + Math.min( + ONE_HOUR_MILLIS, + targetEntry.entryStartUtcMillis - fromMillis - ONE_HOUR_MILLIS)); } } @@ -188,8 +183,11 @@ public class ProgramRow extends TimelineGridView { private void scrollByTime(long timeToScroll) { if (DEBUG) { - Log.d(TAG, "scrollByTime(timeToScroll=" - + TimeUnit.MILLISECONDS.toMinutes(timeToScroll) + "min)"); + Log.d( + TAG, + "scrollByTime(timeToScroll=" + + TimeUnit.MILLISECONDS.toMinutes(timeToScroll) + + "min)"); } mProgramManager.shiftTime(timeToScroll); } @@ -203,12 +201,13 @@ public class ProgramRow extends TimelineGridView { // The focus is lost due to information loaded. Requests focus immediately. // (Because this entry is detached after real entries attached, we can't take // the below approach to resume focus on entry being attached.) - post(new Runnable() { - @Override - public void run() { - requestFocus(); - } - }); + post( + new Runnable() { + @Override + public void run() { + requestFocus(); + } + }); } else if (entry.isCurrentProgram()) { if (DEBUG) Log.d(TAG, "Keep focus to the current program"); // Current program is visible in the guide. @@ -227,12 +226,13 @@ public class ProgramRow extends TimelineGridView { TableEntry entry = ((ProgramItemView) child).getTableEntry(); if (entry.isCurrentProgram()) { mKeepFocusToCurrentProgram = false; - post(new Runnable() { - @Override - public void run() { - requestFocus(); - } - }); + post( + new Runnable() { + @Override + public void run() { + requestFocus(); + } + }); } } } @@ -243,8 +243,12 @@ public class ProgramRow extends TimelineGridView { // Give focus according to the previous focused range Range<Integer> focusRange = programGrid.getFocusRange(); - View nextFocus = GuideUtils.findNextFocusedProgram(this, focusRange.getLower(), - focusRange.getUpper(), programGrid.isKeepCurrentProgramFocused()); + View nextFocus = + GuideUtils.findNextFocusedProgram( + this, + focusRange.getLower(), + focusRange.getUpper(), + programGrid.isKeepCurrentProgramFocused()); if (nextFocus != null) { return nextFocus.requestFocus(); @@ -279,30 +283,29 @@ public class ProgramRow extends TimelineGridView { mChannel = channel; } - /** - * Sets the instance of {@link ProgramGuide} - */ + /** Sets the instance of {@link ProgramGuide} */ public void setProgramGuide(ProgramGuide programGuide) { mProgramGuide = programGuide; mProgramManager = programGuide.getProgramManager(); } - /** - * Resets the scroll with the initial offset {@code scrollOffset}. - */ + /** Resets the scroll with the initial offset {@code scrollOffset}. */ public void resetScroll(int scrollOffset) { - long startTime = GuideUtils.convertPixelToMillis(scrollOffset) - + mProgramManager.getStartTime(); - int position = mChannel == null ? -1 : mProgramManager.getProgramIndexAtTime( - mChannel.getId(), startTime); + long startTime = + GuideUtils.convertPixelToMillis(scrollOffset) + mProgramManager.getStartTime(); + int position = + mChannel == null + ? -1 + : mProgramManager.getProgramIndexAtTime(mChannel.getId(), startTime); if (position < 0) { getLayoutManager().scrollToPosition(0); } else { TableEntry entry = mProgramManager.getTableEntry(mChannel.getId(), position); - int offset = GuideUtils.convertMillisToPixel( - mProgramManager.getStartTime(), entry.entryStartUtcMillis) - scrollOffset; - ((LinearLayoutManager) getLayoutManager()) - .scrollToPositionWithOffset(position, offset); + int offset = + GuideUtils.convertMillisToPixel( + mProgramManager.getStartTime(), entry.entryStartUtcMillis) + - scrollOffset; + ((LinearLayoutManager) getLayoutManager()).scrollToPositionWithOffset(position, offset); // Workaround to b/31598505. When a program's duration is too long, // RecyclerView.onScrolled() will not be called after scrollToPositionWithOffset(). // Therefore we have to update children's visible areas by ourselves in this case. diff --git a/src/com/android/tv/guide/ProgramTableAdapter.java b/src/com/android/tv/guide/ProgramTableAdapter.java index 99f853b1..6e7485ac 100644 --- a/src/com/android/tv/guide/ProgramTableAdapter.java +++ b/src/com/android/tv/guide/ProgramTableAdapter.java @@ -16,8 +16,6 @@ package com.android.tv.guide; -import static com.android.tv.util.ImageLoader.ImageLoaderCallback; - import android.animation.Animator; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; @@ -49,32 +47,30 @@ import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeL import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; - import com.android.tv.R; -import com.android.tv.TvApplication; -import com.android.tv.common.TvCommonUtils; +import com.android.tv.TvSingletons; import com.android.tv.common.feature.CommonFeatures; -import com.android.tv.data.Channel; +import com.android.tv.common.util.CommonUtils; import com.android.tv.data.Program; import com.android.tv.data.Program.CriticScore; +import com.android.tv.data.api.Channel; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.data.ScheduledRecording; 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; +import com.android.tv.util.images.ImageCache; +import com.android.tv.util.images.ImageLoader; +import com.android.tv.util.images.ImageLoader.ImageLoaderCallback; +import com.android.tv.util.images.ImageLoader.LoadTvInputLogoTask; import java.util.ArrayList; import java.util.List; -/** - * Adapts the {@link ProgramListAdapter} list to the body of the program guide table. - */ +/** Adapts the {@link ProgramListAdapter} list to the body of the program guide table. */ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.ProgramRowViewHolder> implements ProgramManager.TableEntryChangedListener { private static final String TAG = "ProgramTableAdapter"; @@ -118,10 +114,10 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr mContext = context; mAccessibilityManager = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); - mTvInputManagerHelper = TvApplication.getSingletons(context).getTvInputManagerHelper(); + mTvInputManagerHelper = TvSingletons.getSingletons(context).getTvInputManagerHelper(); if (CommonFeatures.DVR.isEnabled(context)) { - mDvrManager = TvApplication.getSingletons(context).getDvrManager(); - mDvrDataManager = TvApplication.getSingletons(context).getDvrDataManager(); + mDvrManager = TvSingletons.getSingletons(context).getDvrManager(); + mDvrDataManager = TvSingletons.getSingletons(context).getDvrDataManager(); } else { mDvrManager = null; mDvrDataManager = null; @@ -130,58 +126,62 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr mProgramManager = programGuide.getProgramManager(); Resources res = context.getResources(); - mChannelLogoWidth = res.getDimensionPixelSize( - R.dimen.program_guide_table_header_column_channel_logo_width); - mChannelLogoHeight = res.getDimensionPixelSize( - R.dimen.program_guide_table_header_column_channel_logo_height); - mImageWidth = res.getDimensionPixelSize( - R.dimen.program_guide_table_detail_image_width); - mImageHeight = res.getDimensionPixelSize( - R.dimen.program_guide_table_detail_image_height); - mProgramTitleForNoInformation = res.getString( - R.string.program_title_for_no_information); - mProgramTitleForBlockedChannel = res.getString( - R.string.program_title_for_blocked_channel); - mChannelTextColor = res.getColor( - R.color.program_guide_table_header_column_channel_number_text_color, null); - mChannelBlockedTextColor = res.getColor( - R.color.program_guide_table_header_column_channel_number_blocked_text_color, null); - mDetailTextColor = res.getColor( - R.color.program_guide_table_detail_title_text_color, null); - mDetailGrayedTextColor = res.getColor( - R.color.program_guide_table_detail_title_grayed_text_color, null); + mChannelLogoWidth = + res.getDimensionPixelSize( + R.dimen.program_guide_table_header_column_channel_logo_width); + mChannelLogoHeight = + res.getDimensionPixelSize( + R.dimen.program_guide_table_header_column_channel_logo_height); + mImageWidth = res.getDimensionPixelSize(R.dimen.program_guide_table_detail_image_width); + mImageHeight = res.getDimensionPixelSize(R.dimen.program_guide_table_detail_image_height); + mProgramTitleForNoInformation = res.getString(R.string.program_title_for_no_information); + mProgramTitleForBlockedChannel = res.getString(R.string.program_title_for_blocked_channel); + mChannelTextColor = + res.getColor( + R.color.program_guide_table_header_column_channel_number_text_color, null); + mChannelBlockedTextColor = + res.getColor( + R.color.program_guide_table_header_column_channel_number_blocked_text_color, + null); + mDetailTextColor = res.getColor(R.color.program_guide_table_detail_title_text_color, null); + mDetailGrayedTextColor = + res.getColor(R.color.program_guide_table_detail_title_grayed_text_color, null); mAnimationDuration = res.getInteger(R.integer.program_guide_table_detail_fade_anim_duration); - mDetailPadding = res.getDimensionPixelOffset( - R.dimen.program_guide_table_detail_padding); + mDetailPadding = res.getDimensionPixelOffset(R.dimen.program_guide_table_detail_padding); mProgramRecordableText = res.getString(R.string.dvr_epg_program_recordable); mRecordingScheduledText = res.getString(R.string.dvr_epg_program_recording_scheduled); mRecordingConflictText = res.getString(R.string.dvr_epg_program_recording_conflict); mRecordingFailedText = res.getString(R.string.dvr_epg_program_recording_failed); mRecordingInProgressText = res.getString(R.string.dvr_epg_program_recording_in_progress); - mDvrPaddingStartWithTrack = res.getDimensionPixelOffset( - R.dimen.program_guide_table_detail_dvr_margin_start); - mDvrPaddingStartWithOutTrack = res.getDimensionPixelOffset( - R.dimen.program_guide_table_detail_dvr_margin_start_without_track); - - int episodeTitleSize = res.getDimensionPixelSize( - R.dimen.program_guide_table_detail_episode_title_text_size); - ColorStateList episodeTitleColor = ColorStateList.valueOf( - res.getColor(R.color.program_guide_table_detail_episode_title_text_color, null)); - mEpisodeTitleStyle = new TextAppearanceSpan(null, 0, episodeTitleSize, - episodeTitleColor, null); + mDvrPaddingStartWithTrack = + res.getDimensionPixelOffset(R.dimen.program_guide_table_detail_dvr_margin_start); + mDvrPaddingStartWithOutTrack = + res.getDimensionPixelOffset( + R.dimen.program_guide_table_detail_dvr_margin_start_without_track); + + int episodeTitleSize = + res.getDimensionPixelSize( + R.dimen.program_guide_table_detail_episode_title_text_size); + ColorStateList episodeTitleColor = + ColorStateList.valueOf( + res.getColor( + R.color.program_guide_table_detail_episode_title_text_color, null)); + mEpisodeTitleStyle = + new TextAppearanceSpan(null, 0, episodeTitleSize, episodeTitleColor, null); mCriticScoreViews = new ArrayList<>(); mRecycledViewPool = new RecycledViewPool(); - mRecycledViewPool.setMaxRecycledViews(R.layout.program_guide_table_item, - context.getResources().getInteger( - R.integer.max_recycled_view_pool_epg_table_item)); - mProgramManager.addListener(new ProgramManager.ListenerAdapter() { - @Override - public void onChannelsUpdated() { - update(); - } - }); + mRecycledViewPool.setMaxRecycledViews( + R.layout.program_guide_table_item, + context.getResources().getInteger(R.integer.max_recycled_view_pool_epg_table_item)); + mProgramManager.addListener( + new ProgramManager.ListenerAdapter() { + @Override + public void onChannelsUpdated() { + update(); + } + }); update(); mProgramManager.addTableEntryChangedListener(this); } @@ -193,8 +193,8 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr } mProgramListAdapters.clear(); for (int i = 0; i < mProgramManager.getChannelCount(); i++) { - ProgramListAdapter listAdapter = new ProgramListAdapter(mContext.getResources(), - mProgramGuide, i); + ProgramListAdapter listAdapter = + new ProgramListAdapter(mContext.getResources(), mProgramGuide, i); mProgramManager.addTableEntriesUpdatedListener(listAdapter); mProgramListAdapters.add(listAdapter); } @@ -250,29 +250,31 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr private ProgramManager.TableEntry mSelectedEntry; private Animator mDetailOutAnimator; private Animator mDetailInAnimator; - private final Runnable mDetailInStarter = new Runnable() { - @Override - public void run() { - mProgramRow.removeOnScrollListener(mOnScrollListener); - if (mDetailInAnimator != null) { - mDetailInAnimator.start(); - } - } - }; - private final Runnable mUpdateDetailViewRunnable = new Runnable() { - @Override - public void run() { - updateDetailView(); - } - }; + private final Runnable mDetailInStarter = + new Runnable() { + @Override + public void run() { + mProgramRow.removeOnScrollListener(mOnScrollListener); + if (mDetailInAnimator != null) { + mDetailInAnimator.start(); + } + } + }; + private final Runnable mUpdateDetailViewRunnable = + new Runnable() { + @Override + public void run() { + updateDetailView(); + } + }; private final RecyclerView.OnScrollListener mOnScrollListener = new RecyclerView.OnScrollListener() { - @Override - public void onScrolled(RecyclerView recyclerView, int dx, int dy) { - onHorizontalScrolled(); - } - }; + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + onHorizontalScrolled(); + } + }; private final ViewTreeObserver.OnGlobalFocusChangeListener mGlobalFocusChangeListener = new ViewTreeObserver.OnGlobalFocusChangeListener() { @@ -313,8 +315,7 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr new AccessibilityManager.AccessibilityStateChangeListener() { @Override public void onAccessibilityStateChanged(boolean enable) { - enable &= !TvCommonUtils.isRunningInTest(); - mDetailView.setFocusable(enable); + enable &= !CommonUtils.isRunningInTest(); mChannelHeaderView.setFocusable(enable); } }; @@ -327,7 +328,8 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { - mContainer.getViewTreeObserver() + mContainer + .getViewTreeObserver() .addOnGlobalFocusChangeListener(mGlobalFocusChangeListener); mAccessibilityManager.addAccessibilityStateChangeListener( mAccessibilityStateChangeListener); @@ -335,7 +337,8 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr @Override public void onViewDetachedFromWindow(View v) { - mContainer.getViewTreeObserver() + mContainer + .getViewTreeObserver() .removeOnGlobalFocusChangeListener(mGlobalFocusChangeListener); mAccessibilityManager.removeAccessibilityStateChangeListener( mAccessibilityStateChangeListener); @@ -364,9 +367,8 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr mChannelBlockView = (ImageView) mContainer.findViewById(R.id.channel_block); mInputLogoView = (ImageView) mContainer.findViewById(R.id.input_logo); - boolean accessibilityEnabled = mAccessibilityManager.isEnabled() - && !TvCommonUtils.isRunningInTest(); - mDetailView.setFocusable(accessibilityEnabled); + boolean accessibilityEnabled = + mAccessibilityManager.isEnabled() && !CommonUtils.isRunningInTest(); mChannelHeaderView.setFocusable(accessibilityEnabled); } @@ -382,9 +384,10 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr mDetailView.setVisibility(View.GONE); // The bottom-left of the last channel header view will have a rounded corner. - mChannelHeaderView.setBackgroundResource((position < mProgramListAdapters.size() - 1) - ? R.drawable.program_guide_table_header_column_item_background - : R.drawable.program_guide_table_header_column_last_item_background); + mChannelHeaderView.setBackgroundResource( + (position < mProgramListAdapters.size() - 1) + ? R.drawable.program_guide_table_header_column_item_background + : R.drawable.program_guide_table_header_column_last_item_background); } private void onBindChannel(Channel channel) { @@ -411,7 +414,8 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr } else { size = R.dimen.program_guide_table_header_column_channel_number_small_font_size; } - mChannelNumberView.setTextSize(TypedValue.COMPLEX_UNIT_PX, + mChannelNumberView.setTextSize( + TypedValue.COMPLEX_UNIT_PX, mChannelNumberView.getContext().getResources().getDimension(size)); mChannelNumberView.setText(displayNumber); mChannelNumberView.setVisibility(View.VISIBLE); @@ -429,8 +433,11 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr mChannelNameView.setVisibility(View.VISIBLE); mChannelBlockView.setVisibility(View.GONE); - mChannel.loadBitmap(itemView.getContext(), Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO, - mChannelLogoWidth, mChannelLogoHeight, + mChannel.loadBitmap( + itemView.getContext(), + Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO, + mChannelLogoWidth, + mChannelLogoHeight, createChannelLogoLoadedCallback(this, channel.getId())); } } @@ -439,7 +446,8 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr public void onChildFocus(View oldFocus, View newFocus) { if (newFocus == null) { return; - } // When the accessibility service is enabled, focus might be put on channel's header or + } // When the accessibility service is enabled, focus might be put on channel's header + // or // detail view, besides program items. if (newFocus == mChannelHeaderView) { mSelectedEntry = ((ProgramItemView) mProgramRow.getChildAt(0)).getTableEntry(); @@ -461,7 +469,7 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr return; } - if (Program.isValid(mSelectedEntry.program)) { + if (Program.isProgramValid(mSelectedEntry.program)) { Program program = mSelectedEntry.program; if (getProgramBlock(program) == null) { program.prefetchPosterArt(itemView.getContext(), mImageWidth, mImageHeight); @@ -473,10 +481,12 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr View detailContentView = mDetailView.findViewById(R.id.detail_content); if (mDetailInAnimator == null) { - mDetailOutAnimator = ObjectAnimator.ofPropertyValuesHolder(detailContentView, - PropertyValuesHolder.ofFloat(View.ALPHA, 1f, 0f), - PropertyValuesHolder.ofFloat(View.TRANSLATION_X, - 0f, direction * mDetailPadding)); + mDetailOutAnimator = + ObjectAnimator.ofPropertyValuesHolder( + detailContentView, + PropertyValuesHolder.ofFloat(View.ALPHA, 1f, 0f), + PropertyValuesHolder.ofFloat( + View.TRANSLATION_X, 0f, direction * mDetailPadding)); mDetailOutAnimator.setDuration(mAnimationDuration); mDetailOutAnimator.addListener( new HardwareLayerAnimatorListenerAdapter(detailContentView) { @@ -501,10 +511,12 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr mHandler.postDelayed(mDetailInStarter, mAnimationDuration); } - mDetailInAnimator = ObjectAnimator.ofPropertyValuesHolder(detailContentView, - PropertyValuesHolder.ofFloat(View.ALPHA, 0f, 1f), - PropertyValuesHolder.ofFloat(View.TRANSLATION_X, - direction * -mDetailPadding, 0f)); + mDetailInAnimator = + ObjectAnimator.ofPropertyValuesHolder( + detailContentView, + PropertyValuesHolder.ofFloat(View.ALPHA, 0f, 1f), + PropertyValuesHolder.ofFloat( + View.TRANSLATION_X, direction * -mDetailPadding, 0f)); mDetailInAnimator.setDuration(mAnimationDuration); mDetailInAnimator.addListener( new HardwareLayerAnimatorListenerAdapter(detailContentView) { @@ -529,7 +541,7 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr } if (DEBUG) Log.d(TAG, "updateDetailView"); mCriticScoresLayout.removeAllViews(); - if (Program.isValid(mSelectedEntry.program)) { + if (Program.isProgramValid(mSelectedEntry.program)) { mTitleView.setTextColor(mDetailTextColor); Context context = itemView.getContext(); Program program = mSelectedEntry.program; @@ -538,7 +550,10 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr updatePosterArt(null); if (blockedRating == null) { - program.loadPosterArt(context, mImageWidth, mImageHeight, + program.loadPosterArt( + context, + mImageWidth, + mImageHeight, createProgramPosterArtCallback(this, program)); } @@ -550,24 +565,35 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr String fullTitle = title + " " + episodeTitle; SpannableString text = new SpannableString(fullTitle); - text.setSpan(mEpisodeTitleStyle, - fullTitle.length() - episodeTitle.length(), fullTitle.length(), + text.setSpan( + mEpisodeTitleStyle, + fullTitle.length() - episodeTitle.length(), + fullTitle.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); mTitleView.setText(text); } - updateTextView(mTimeView, Utils.getDurationString(context, - program.getStartTimeUtcMillis(), - program.getEndTimeUtcMillis(), false)); - - boolean trackMetaDataVisible = updateTextView(mAspectRatioView, Utils - .getAspectRatioString(program.getVideoWidth(), program.getVideoHeight())); - - int videoDefinitionLevel = Utils.getVideoDefinitionLevelFromSize( - program.getVideoWidth(), program.getVideoHeight()); + updateTextView( + mTimeView, + Utils.getDurationString( + context, + program.getStartTimeUtcMillis(), + program.getEndTimeUtcMillis(), + false)); + + boolean trackMetaDataVisible = + updateTextView( + mAspectRatioView, + Utils.getAspectRatioString( + program.getVideoWidth(), program.getVideoHeight())); + + int videoDefinitionLevel = + Utils.getVideoDefinitionLevelFromSize( + program.getVideoWidth(), program.getVideoHeight()); trackMetaDataVisible |= - updateTextView(mResolutionView, Utils.getVideoDefinitionLevelString( - context, videoDefinitionLevel)); + updateTextView( + mResolutionView, + Utils.getVideoDefinitionLevelString(context, videoDefinitionLevel)); if (mDvrManager != null && mDvrManager.isProgramRecordable(program)) { ScheduledRecording scheduledRecording = @@ -616,7 +642,6 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr mDvrIndicator.setVisibility(View.GONE); } - if (blockedRating == null) { mBlockView.setVisibility(View.GONE); updateTextView(mDescriptionView, program.getDescription()); @@ -655,8 +680,10 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr } private String getBlockedDescription(TvContentRating blockedRating) { - String name = mTvInputManagerHelper.getContentRatingsManager() - .getDisplayNameForRating(blockedRating); + String name = + mTvInputManagerHelper + .getContentRatingsManager() + .getDisplayNameForRating(blockedRating); if (TextUtils.isEmpty(name)) { return mContext.getString(R.string.program_guide_content_locked); } else { @@ -691,8 +718,9 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr mIsInputLogoVisible = true; TvInputInfo info = mTvInputManagerHelper.getTvInputInfo(mChannel.getInputId()); if (info != null) { - LoadTvInputLogoTask task = new LoadTvInputLogoTask( - itemView.getContext(), ImageCache.getInstance(), info); + LoadTvInputLogoTask task = + new LoadTvInputLogoTask( + itemView.getContext(), ImageCache.getInstance(), info); ImageLoader.loadBitmap(createTvInputLogoLoadedCallback(info, this), task); } } @@ -735,23 +763,28 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr mInputLogoView.setVisibility(View.VISIBLE); } - private void updateCriticScoreView(ProgramRowViewHolder holder, final long programId, - CriticScore criticScore, View view) { + private void updateCriticScoreView( + ProgramRowViewHolder holder, + final long programId, + CriticScore criticScore, + View view) { TextView criticScoreSource = (TextView) view.findViewById(R.id.critic_score_source); TextView criticScoreText = (TextView) view.findViewById(R.id.critic_score_score); ImageView criticScoreLogo = (ImageView) view.findViewById(R.id.critic_score_logo); - //set the appropriate information in the views + // set the appropriate information in the views if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { - criticScoreSource.setText(Html.fromHtml(criticScore.source, - Html.FROM_HTML_MODE_LEGACY)); + criticScoreSource.setText( + Html.fromHtml(criticScore.source, Html.FROM_HTML_MODE_LEGACY)); } else { criticScoreSource.setText(Html.fromHtml(criticScore.source)); } criticScoreText.setText(criticScore.score); criticScoreSource.setVisibility(View.VISIBLE); criticScoreText.setVisibility(View.VISIBLE); - ImageLoader.loadBitmap(mContext, criticScore.logoUrl, + ImageLoader.loadBitmap( + mContext, + criticScore.logoUrl, createCriticScoreLogoCallback(holder, programId, criticScoreLogo)); } @@ -768,7 +801,8 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr return new ImageLoaderCallback<ProgramRowViewHolder>(holder) { @Override public void onBitmapLoaded(ProgramRowViewHolder holder, @Nullable Bitmap logoImage) { - if (logoImage == null || holder.mSelectedEntry == null + if (logoImage == null + || holder.mSelectedEntry == null || holder.mSelectedEntry.program == null || holder.mSelectedEntry.program.getId() != programId) { logoView.setVisibility(View.GONE); @@ -785,7 +819,8 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr return new ImageLoaderCallback<ProgramRowViewHolder>(holder) { @Override public void onBitmapLoaded(ProgramRowViewHolder holder, @Nullable Bitmap posterArt) { - if (posterArt == null || holder.mSelectedEntry == null + if (posterArt == null + || holder.mSelectedEntry == null || holder.mSelectedEntry.program == null) { return; } @@ -803,7 +838,8 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr return new ImageLoaderCallback<ProgramRowViewHolder>(holder) { @Override public void onBitmapLoaded(ProgramRowViewHolder holder, @Nullable Bitmap logo) { - if (logo == null || holder.mChannel == null + if (logo == null + || holder.mChannel == null || holder.mChannel.getId() != channelId) { return; } @@ -817,8 +853,9 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr return new ImageLoaderCallback<ProgramRowViewHolder>(holder) { @Override public void onBitmapLoaded(ProgramRowViewHolder holder, @Nullable Bitmap logo) { - if (logo != null && holder.mChannel != null && info.getId() - .equals(holder.mChannel.getInputId())) { + if (logo != null + && holder.mChannel != null + && info.getId().equals(holder.mChannel.getInputId())) { holder.updateInputLogoInternal(logo); } } diff --git a/src/com/android/tv/guide/TimeListAdapter.java b/src/com/android/tv/guide/TimeListAdapter.java index d9e96a40..9c10c952 100644 --- a/src/com/android/tv/guide/TimeListAdapter.java +++ b/src/com/android/tv/guide/TimeListAdapter.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.text.format.DateFormat; @@ -24,17 +23,15 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; - import com.android.tv.R; import com.android.tv.util.Utils; - import java.util.Date; import java.util.Locale; import java.util.concurrent.TimeUnit; /** - * Adapts the time range from {@link ProgramManager} to the timeline header row of the program - * guide table. + * Adapts the time range from {@link ProgramManager} to the timeline header row of the program guide + * table. */ class TimeListAdapter extends RecyclerView.Adapter<TimeListAdapter.TimeViewHolder> { private static final long TIME_UNIT_MS = TimeUnit.MINUTES.toMillis(30); @@ -53,8 +50,10 @@ class TimeListAdapter extends RecyclerView.Adapter<TimeListAdapter.TimeViewHolde TimeListAdapter(Resources res) { if (sRowHeaderOverlapping == 0) { - sRowHeaderOverlapping = Math.abs(res.getDimensionPixelOffset( - R.dimen.program_guide_table_header_row_overlap)); + sRowHeaderOverlapping = + Math.abs( + res.getDimensionPixelOffset( + R.dimen.program_guide_table_header_row_overlap)); } Locale locale = res.getConfiguration().locale; mTimePatternSameDay = DateFormat.getBestDateTimePattern(locale, TIME_PATTERN_SAME_DAY); diff --git a/src/com/android/tv/guide/TimelineGridView.java b/src/com/android/tv/guide/TimelineGridView.java index 8af1c8a3..c4922b75 100644 --- a/src/com/android/tv/guide/TimelineGridView.java +++ b/src/com/android/tv/guide/TimelineGridView.java @@ -34,14 +34,15 @@ public class TimelineGridView extends RecyclerView { public TimelineGridView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) { - @Override - public boolean onRequestChildFocus(RecyclerView parent, State state, View child, - View focused) { - // This disables the default scroll behavior for focus movement. - return true; - } - }); + setLayoutManager( + new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) { + @Override + public boolean onRequestChildFocus( + RecyclerView parent, State state, View child, View focused) { + // This disables the default scroll behavior for focus movement. + return true; + } + }); // RecyclerView is always focusable, however this is not desirable for us, so disable. // See b/18863217 (ag/634046) for reasons to why RecyclerView is focusable. diff --git a/src/com/android/tv/guide/TimelineRow.java b/src/com/android/tv/guide/TimelineRow.java index 3f0c8678..b6a10ab1 100644 --- a/src/com/android/tv/guide/TimelineRow.java +++ b/src/com/android/tv/guide/TimelineRow.java @@ -40,19 +40,16 @@ public class TimelineRow extends TimelineGridView { getLayoutManager().scrollToPosition(0); } - /** - * Returns the current scroll position - */ + /** Returns the current scroll position */ public int getScrollOffset() { return Math.abs(mScrollPosition); } - /** - * Scrolls horizontally to the given position. - */ + /** Scrolls horizontally to the given position. */ public void scrollTo(int scrollOffset, boolean smoothScroll) { - int dx = (scrollOffset - getScrollOffset()) - * (getLayoutDirection() == LAYOUT_DIRECTION_LTR ? 1 : -1); + int dx = + (scrollOffset - getScrollOffset()) + * (getLayoutDirection() == LAYOUT_DIRECTION_LTR ? 1 : -1); if (smoothScroll) { smoothScrollBy(dx, 0); } else { |