diff options
author | Live Channels Team <no-reply@google.com> | 2018-02-16 10:34:47 -0800 |
---|---|---|
committer | Nick Chalko <nchalko@google.com> | 2018-02-16 11:50:03 -0800 |
commit | 0cc0713c1bf8027642987b750b80217569d2932a (patch) | |
tree | afafc2e82a5626b383e495635c843c51ceb985c5 /src/com/android/tv/guide | |
parent | ee959c2539b768dd3fbcbdf70acb03e536a9d76d (diff) | |
download | TV-0cc0713c1bf8027642987b750b80217569d2932a.tar.gz |
Changes imported from Live Channels
- 186014614 Sync more make files with master by nchalko <nchalko@google.com>
- 186013553 FIX: safely parse URI from intents. by nchalko <nchalko@google.com>
- 186010488 Add missing license header by nchalko <nchalko@google.com>
- 186009400 Sync all make files with master by nchalko <nchalko@google.com>
- 185891159 Extract a Channel interface by nchalko <nchalko@google.com>
- 185885678 Make recording history available by shubang <shubang@google.com>
- 185725900 Extract an interface for TunableTvView by nchalko <nchalko@google.com>
- 185724604 CLEANUP: Move ImageLoader to a separate package by nchalko <nchalko@google.com>
- 185722979 CLEANUP: Fix amiguous method reference by renaming static... by nchalko <nchalko@google.com>
- 185721563 CLEANUP: create a seperate top level target for resources by nchalko <nchalko@google.com>
- 185720102 CLEANUP: fix comparison using reference equality instead ... by nchalko <nchalko@google.com>
- 185717674 CLEANUP: Move MemoryManageable to a separate package by nchalko <nchalko@google.com>
- 185609615 FIX: Improve Program Guide description for talkback by nchalko <nchalko@google.com>
- 185607602 PARTIAL: Speak the channel number for each item in the pr... by nchalko <nchalko@google.com>
- 185552957 CLEANUP: Move TestUtils to tests/common by nchalko <nchalko@google.com>
- 185549529 PARTIAL: Add program description to the content discripti... by nchalko <nchalko@google.com>
- 185521822 Use robolectric 3.6.1 in android.mk by nchalko <nchalko@google.com>
- 185521733 Add recording info to ProgramItemView content description by nchalko <nchalko@google.com>
- 185459218 Add time to ProgramItemView content description by nchalko <nchalko@google.com>
- 185449505 Add clock to the Utils.getDurationString by nchalko <nchalko@google.com>
- 185435931 Inject the clock in ProgramItemView for testing by nchalko <nchalko@google.com>
- 185435648 Fix link-type warnings by nchalko <nchalko@google.com>
- 185435488 Migrate to AAPT2 by nchalko <nchalko@google.com>
- 185434148 Add uptimeMillis to the Clock interface by nchalko <nchalko@google.com>
PiperOrigin-RevId: 186014614
Change-Id: I583af9ac3e56161736504024b62d1fd62e31c15a
Test: tested in google3
Diffstat (limited to 'src/com/android/tv/guide')
-rw-r--r-- | src/com/android/tv/guide/ProgramItemView.java | 224 | ||||
-rw-r--r-- | src/com/android/tv/guide/ProgramListAdapter.java | 2 | ||||
-rw-r--r-- | src/com/android/tv/guide/ProgramManager.java | 22 | ||||
-rw-r--r-- | src/com/android/tv/guide/ProgramRow.java | 2 | ||||
-rw-r--r-- | src/com/android/tv/guide/ProgramTableAdapter.java | 16 |
5 files changed, 183 insertions, 83 deletions
diff --git a/src/com/android/tv/guide/ProgramItemView.java b/src/com/android/tv/guide/ProgramItemView.java index 9daa9f2f..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; @@ -40,7 +39,10 @@ import com.android.tv.R; 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; @@ -70,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; @@ -86,6 +90,7 @@ public class ProgramItemView extends TextView { @Override public void onClick(final View view) { TableEntry entry = ((ProgramItemView) view).mTableEntry; + Clock clock = ((ProgramItemView) view).mClock; if (entry == null) { // do nothing return; @@ -114,7 +119,7 @@ public class ProgramItemView extends TextView { } else if (entry.program != null && CommonFeatures.DVR.isEnabled(view.getContext())) { DvrManager dvrManager = singletons.getDvrManager(); - if (entry.entryStartUtcMillis > System.currentTimeMillis() + if (entry.entryStartUtcMillis > clock.currentTimeMillis() && dvrManager.isProgramRecordable(entry.program)) { if (entry.scheduledRecording == null) { DvrUiHelper.checkStorageStatusAndShowErrorMessage( @@ -180,7 +185,8 @@ public class ProgramItemView extends TextView { background.jumpToCurrentState(); } int progress = - getProgress(entry.entryStartUtcMillis, entry.entryEndUtcMillis); + getProgress( + mClock, entry.entryStartUtcMillis, entry.entryEndUtcMillis); setProgress(background, R.id.reverse_progress, MAX_PROGRESS - progress); } if (getHandler() != null) { @@ -188,8 +194,7 @@ public class ProgramItemView extends TextView { .postAtTime( this, Utils.ceilTime( - SystemClock.uptimeMillis(), - FOCUS_UPDATE_FREQUENCY)); + mClock.uptimeMillis(), FOCUS_UPDATE_FREQUENCY)); } } }; @@ -206,7 +211,10 @@ public class ProgramItemView extends TextView { super(context, attrs, defStyle); setOnClickListener(ON_CLICKED); setOnFocusChangeListener(ON_FOCUS_CHANGED); - mDvrManager = TvSingletons.getSingletons(getContext()).getDvrManager(); + TvSingletons singletons = TvSingletons.getSingletons(getContext()); + mDvrManager = singletons.getDvrManager(); + mChannelDataManager = singletons.getChannelDataManager(); + mClock = singletons.getClock(); } private void initIfNeeded() { @@ -266,7 +274,7 @@ public class ProgramItemView extends TextView { @Override protected int[] onCreateDrawableState(int extraSpace) { if (mTableEntry != null) { - int states[] = + int[] states = super.onCreateDrawableState( extraSpace + STATE_CURRENT_PROGRAM.length + STATE_TOO_WIDE.length); if (mTableEntry.isCurrentProgram()) { @@ -296,78 +304,154 @@ public class ProgramItemView extends TextView { 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); + } + + private boolean isEntryWideEnough() { + return mTableEntry != null && mTableEntry.getWidth() >= sVisibleThreshold; + } + + private void updateText(int selectedGenreId, String title) { + if (!isEntryWideEnough()) { + setText(null); + return; + } - String title = entry.program != null ? entry.program.getTitle() : null; String episode = - entry.program != null ? entry.program.getEpisodeDisplayTitle(getContext()) : null; + 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. */ @@ -423,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) { diff --git a/src/com/android/tv/guide/ProgramListAdapter.java b/src/com/android/tv/guide/ProgramListAdapter.java index d997db1e..397bacfb 100644 --- a/src/com/android/tv/guide/ProgramListAdapter.java +++ b/src/com/android/tv/guide/ProgramListAdapter.java @@ -23,7 +23,7 @@ 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; diff --git a/src/com/android/tv/guide/ProgramManager.java b/src/com/android/tv/guide/ProgramManager.java index 6d355c7a..3f20a837 100644 --- a/src/com/android/tv/guide/ProgramManager.java +++ b/src/com/android/tv/guide/ProgramManager.java @@ -18,13 +18,14 @@ 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; @@ -731,7 +732,7 @@ public class ProgramManager { /** Returns true if this is a gap. */ boolean isGap() { - return !Program.isValid(program); + return !Program.isProgramValid(program); } /** Returns true if this channel is blocked. */ @@ -772,6 +773,23 @@ public class ProgramManager { } } + @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(); diff --git a/src/com/android/tv/guide/ProgramRow.java b/src/com/android/tv/guide/ProgramRow.java index 3765aa43..83175bb6 100644 --- a/src/com/android/tv/guide/ProgramRow.java +++ b/src/com/android/tv/guide/ProgramRow.java @@ -24,7 +24,7 @@ import android.util.Log; import android.util.Range; import android.view.View; import android.view.ViewTreeObserver.OnGlobalLayoutListener; -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; diff --git a/src/com/android/tv/guide/ProgramTableAdapter.java b/src/com/android/tv/guide/ProgramTableAdapter.java index 935f8b3a..6e7485ac 100644 --- a/src/com/android/tv/guide/ProgramTableAdapter.java +++ b/src/com/android/tv/guide/ProgramTableAdapter.java @@ -51,21 +51,21 @@ import com.android.tv.R; import com.android.tv.TvSingletons; import com.android.tv.common.feature.CommonFeatures; import com.android.tv.common.util.CommonUtils; -import com.android.tv.data.Channel; 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.ImageLoaderCallback; -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; @@ -316,7 +316,6 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr @Override public void onAccessibilityStateChanged(boolean enable) { enable &= !CommonUtils.isRunningInTest(); - mDetailView.setFocusable(enable); mChannelHeaderView.setFocusable(enable); } }; @@ -370,7 +369,6 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr boolean accessibilityEnabled = mAccessibilityManager.isEnabled() && !CommonUtils.isRunningInTest(); - mDetailView.setFocusable(accessibilityEnabled); mChannelHeaderView.setFocusable(accessibilityEnabled); } @@ -471,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); @@ -543,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; |