diff options
author | Nick Chalko <nchalko@google.com> | 2016-10-26 14:03:09 -0700 |
---|---|---|
committer | Nick Chalko <nchalko@google.com> | 2016-10-31 10:36:49 -0700 |
commit | d41f0075a7d2ea826204e81fcec57d0aa57171a9 (patch) | |
tree | cb30cfbafd80e01d314868cdc36e783d39981119 /src/com/android/tv/guide | |
parent | 5e0ec06a797e3497da94390c63c7072de442695b (diff) | |
download | TV-d41f0075a7d2ea826204e81fcec57d0aa57171a9.tar.gz |
Sync to ub-tv-killing at 6f6e46557accb62c9548e4177d6005aa944dbf33
Change-Id: I873644d6d9d0110c981ef6075cb4019c16bbb94b
Diffstat (limited to 'src/com/android/tv/guide')
-rw-r--r-- | src/com/android/tv/guide/ProgramGuide.java | 61 | ||||
-rw-r--r-- | src/com/android/tv/guide/ProgramItemView.java | 42 | ||||
-rw-r--r-- | src/com/android/tv/guide/ProgramManager.java | 24 | ||||
-rw-r--r-- | src/com/android/tv/guide/ProgramRow.java | 100 | ||||
-rw-r--r-- | src/com/android/tv/guide/ProgramTableAdapter.java | 92 |
5 files changed, 161 insertions, 158 deletions
diff --git a/src/com/android/tv/guide/ProgramGuide.java b/src/com/android/tv/guide/ProgramGuide.java index a6ac4375..120b3dba 100644 --- a/src/com/android/tv/guide/ProgramGuide.java +++ b/src/com/android/tv/guide/ProgramGuide.java @@ -22,7 +22,7 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; -import android.animation.ValueAnimator; +import android.content.Context; import android.content.SharedPreferences; import android.content.res.Resources; import android.graphics.Point; @@ -42,6 +42,7 @@ import android.view.View.MeasureSpec; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewTreeObserver; +import android.view.accessibility.AccessibilityManager; import com.android.tv.ChannelTuner; import com.android.tv.Features; @@ -56,6 +57,7 @@ import com.android.tv.data.ProgramDataManager; 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.util.TvInputManagerHelper; import com.android.tv.util.Utils; @@ -90,6 +92,7 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { private final MainActivity mActivity; private final ProgramManager mProgramManager; + private final AccessibilityManager mAccessibilityManager; private final ChannelTuner mChannelTuner; private final Tracker mTracker; private final DurationTimer mVisibleDuration = new DurationTimer(); @@ -374,7 +377,10 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { mProgramTableFadeInAnimator.setTarget(mTable); mProgramTableFadeInAnimator.addListener(new HardwareLayerAnimatorListenerAdapter(mTable)); mSharedPreference = PreferenceManager.getDefaultSharedPreferences(mActivity); - mShowGuidePartial = mSharedPreference.getBoolean(KEY_SHOW_GUIDE_PARTIAL, true); + mAccessibilityManager = + (AccessibilityManager) mActivity.getSystemService(Context.ACCESSIBILITY_SERVICE); + mShowGuidePartial = mAccessibilityManager.isEnabled() + || mSharedPreference.getBoolean(KEY_SHOW_GUIDE_PARTIAL, true); } private void updateGuidePosition() { @@ -606,7 +612,9 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { } private void startFull() { - if (isFull()) { + if (isFull() || mAccessibilityManager.isEnabled()) { + // If accessibility service is enabled, focus cannot be moved to side panel due to it's + // hidden. Therefore, we don't hide side panel when accessibility service is enabled. return; } mShowGuidePartial = false; @@ -743,7 +751,7 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { View detailView = row.findViewById(R.id.detail); detailView.findViewById(R.id.detail_content_full).setAlpha(1); detailView.findViewById(R.id.detail_content_full).setTranslationY(0); - setLayoutHeight(detailView, mDetailHeight); + ViewUtils.setLayoutHeight(detailView, mDetailHeight); detailView.setVisibility(View.VISIBLE); final ProgramRow programRow = (ProgramRow) row.findViewById(R.id.row); @@ -785,8 +793,8 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { fadeOutAnimator.setDuration(mAnimationDuration); fadeOutAnimator.addListener(new HardwareLayerAnimatorListenerAdapter(outDetailContent)); - Animator collapseAnimator = - createHeightAnimator(outDetail, getLayoutHeight(outDetail), 0); + Animator collapseAnimator = ViewUtils + .createHeightAnimator(outDetail, ViewUtils.getLayoutHeight(outDetail), 0); collapseAnimator.setStartDelay(mAnimationDuration); collapseAnimator.setDuration(mTableFadeAnimDuration); collapseAnimator.addListener(new AnimatorListenerAdapter() { @@ -817,7 +825,7 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { if (inDetail != null) { final View inDetailContent = inDetail.findViewById(R.id.detail_content_full); - Animator expandAnimator = createHeightAnimator(inDetail, 0, mDetailHeight); + Animator expandAnimator = ViewUtils.createHeightAnimator(inDetail, 0, mDetailHeight); expandAnimator.setStartDelay(mAnimationDuration); expandAnimator.setDuration(mTableFadeAnimDuration); expandAnimator.addListener(new AnimatorListenerAdapter() { @@ -832,17 +840,15 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { inDetailContent.setAlpha(0); } }); - Animator fadeInAnimator = ObjectAnimator.ofPropertyValuesHolder(inDetailContent, PropertyValuesHolder.ofFloat(View.ALPHA, 0f, 1f), PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, direction * -mDetailPadding, 0f)); - fadeInAnimator.setStartDelay(mAnimationDuration + mTableFadeAnimDuration); fadeInAnimator.setDuration(mAnimationDuration); fadeInAnimator.addListener(new HardwareLayerAnimatorListenerAdapter(inDetailContent)); AnimatorSet inAnimator = new AnimatorSet(); - inAnimator.playTogether(expandAnimator, fadeInAnimator); + inAnimator.playSequentially(expandAnimator, fadeInAnimator); inAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animator) { @@ -854,41 +860,6 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener { } } - private Animator createHeightAnimator( - final View target, int initialHeight, int targetHeight) { - ValueAnimator animator = ValueAnimator.ofInt(initialHeight, targetHeight); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - int value = (Integer) animation.getAnimatedValue(); - if (value == 0) { - if (target.getVisibility() != View.GONE) { - target.setVisibility(View.GONE); - } - } else { - if (target.getVisibility() != View.VISIBLE) { - target.setVisibility(View.VISIBLE); - } - setLayoutHeight(target, value); - } - } - }); - return animator; - } - - private int getLayoutHeight(View view) { - LayoutParams layoutParams = view.getLayoutParams(); - return layoutParams.height; - } - - private void setLayoutHeight(View view, int height) { - LayoutParams layoutParams = view.getLayoutParams(); - if (height != layoutParams.height) { - layoutParams.height = height; - view.setLayoutParams(layoutParams); - } - } - private class GlobalFocusChangeListener implements ViewTreeObserver.OnGlobalFocusChangeListener { private static final int UNKNOWN = 0; diff --git a/src/com/android/tv/guide/ProgramItemView.java b/src/com/android/tv/guide/ProgramItemView.java index f638e830..4c7a4404 100644 --- a/src/com/android/tv/guide/ProgramItemView.java +++ b/src/com/android/tv/guide/ProgramItemView.java @@ -47,9 +47,9 @@ import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.DvrUiHelper; import com.android.tv.dvr.ScheduledRecording; import com.android.tv.guide.ProgramManager.TableEntry; +import com.android.tv.util.ToastUtils; import com.android.tv.util.Utils; -import java.lang.ref.WeakReference; import java.lang.reflect.InvocationTargetException; import java.util.concurrent.TimeUnit; @@ -73,8 +73,6 @@ public class ProgramItemView extends TextView { private static TextAppearanceSpan sEpisodeTitleStyle; private static TextAppearanceSpan sGrayedOutEpisodeTitleStyle; - private static WeakReference<Toast> sToast; - private DvrManager mDvrManager; private TableEntry mTableEntry; private int mMaxWidthForRipple; @@ -96,10 +94,9 @@ public class ProgramItemView extends TextView { 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()) { - final MainActivity tvActivity = (MainActivity) view.getContext(); - final Channel channel = tvActivity.getChannelDataManager() - .getChannel(entry.channelId); view.postDelayed(new Runnable() { @Override public void run() { @@ -114,42 +111,25 @@ public class ProgramItemView extends TextView { if (entry.entryStartUtcMillis > System.currentTimeMillis() && dvrManager.isProgramRecordable(entry.program)) { if (entry.scheduledRecording == null) { - if (DvrUiHelper.handleCreateSchedule((MainActivity) view.getContext(), - entry.program)) { + if (DvrUiHelper.checkStorageStatusAndShowErrorMessage(tvActivity, + channel.getInputId()) + && DvrUiHelper.handleCreateSchedule(tvActivity, entry.program)) { String msg = view.getContext().getString( R.string.dvr_msg_program_scheduled, entry.program.getTitle()); - showToast(view.getContext(), msg); + ToastUtils.show(view.getContext(), msg, Toast.LENGTH_SHORT); } - } else if (entry.scheduledRecording.getState() - == ScheduledRecording.STATE_RECORDING_CANCELED) { - // TODO: replace the toast with a dialog. - String msg = view.getResources().getString( - R.string.dvr_msg_program_scheduled, entry.program.getTitle()); - showToast(view.getContext(), msg); - dvrManager.updateScheduledRecording(ScheduledRecording.buildFrom(entry - .scheduledRecording).setState(ScheduledRecording - .STATE_RECORDING_NOT_STARTED).build()); } else { dvrManager.removeScheduledRecording(entry.scheduledRecording); String msg = view.getResources().getString( R.string.dvr_schedules_deletion_info, entry.program.getTitle()); - showToast(view.getContext(), msg); + ToastUtils.show(view.getContext(), msg, Toast.LENGTH_SHORT); } } else { - showToast(view.getContext(), view.getResources() - .getString(R.string.dvr_msg_cannot_record_program)); + ToastUtils.show(view.getContext(), view.getResources() + .getString(R.string.dvr_msg_cannot_record_program), Toast.LENGTH_SHORT); } } } - - private void showToast(Context context, String msg) { - if (sToast != null && sToast.get() != null) { - sToast.get().cancel(); - } - Toast toast = Toast.makeText(context, msg, Toast.LENGTH_SHORT); - toast.show(); - sToast = new WeakReference<>(toast); - } }; private static final View.OnFocusChangeListener ON_FOCUS_CHANGED = @@ -322,7 +302,7 @@ public class ProgramItemView extends TextView { int iconResId = 0; if (mTableEntry.scheduledRecording != null) { if (mDvrManager.isConflicting(mTableEntry.scheduledRecording)) { - iconResId = R.drawable.ic_warning_white_36dp; + iconResId = R.drawable.ic_warning_white_18dp; } else { switch (mTableEntry.scheduledRecording.getState()) { case ScheduledRecording.STATE_RECORDING_NOT_STARTED: diff --git a/src/com/android/tv/guide/ProgramManager.java b/src/com/android/tv/guide/ProgramManager.java index 489cc5ef..e3d919df 100644 --- a/src/com/android/tv/guide/ProgramManager.java +++ b/src/com/android/tv/guide/ProgramManager.java @@ -204,17 +204,17 @@ public class ProgramManager { @Override public void onLoadFinished() { mChannelDataLoaded = true; - updateChannels(true, false); + updateChannels(false); } @Override public void onChannelListUpdated() { - updateChannels(true, false); + updateChannels(false); } @Override public void onChannelBrowsableChanged() { - updateChannels(true, false); + updateChannels(false); } }; @@ -222,7 +222,7 @@ public class ProgramManager { new ProgramDataManager.Listener() { @Override public void onProgramUpdated() { - updateTableEntries(true, true); + updateTableEntries(true); } }; @@ -434,18 +434,16 @@ public class ProgramManager { // Note that This can be happens only if program guide isn't shown // because an user has to select channels as browsable through UI. - private void updateChannels(boolean notify, boolean clearPreviousTableEntries) { + private void updateChannels(boolean clearPreviousTableEntries) { if (DEBUG) Log.d(TAG, "updateChannels"); mChannels = mChannelDataManager.getBrowsableChannelList(); mSelectedGenreId = GenreItems.ID_ALL_CHANNELS; mFilteredChannels = mChannels; - if (notify) { - notifyChannelsUpdated(); - } - updateTableEntries(notify, clearPreviousTableEntries); + notifyChannelsUpdated(); + updateTableEntries(clearPreviousTableEntries); } - private void updateTableEntries(boolean notify, boolean clear) { + private void updateTableEntries(boolean clear) { if (clear) { mChannelIdEntriesMap.clear(); } @@ -494,9 +492,7 @@ public class ProgramManager { } } - if (notify) { - notifyTableEntriesUpdated(); - } + notifyTableEntriesUpdated(); buildGenreFilters(); } @@ -579,7 +575,7 @@ public class ProgramManager { } mProgramDataManager.setPrefetchTimeRange(mStartUtcMillis); - updateChannels(true, true); + updateChannels(true); setTimeRange(startUtcMillis, endUtcMillis); } diff --git a/src/com/android/tv/guide/ProgramRow.java b/src/com/android/tv/guide/ProgramRow.java index 5c4236a6..2c98ab2d 100644 --- a/src/com/android/tv/guide/ProgramRow.java +++ b/src/com/android/tv/guide/ProgramRow.java @@ -22,8 +22,7 @@ import android.support.v7.widget.LinearLayoutManager; import android.util.AttributeSet; import android.util.Log; import android.view.View; -import android.view.ViewParent; -import android.view.ViewTreeObserver; +import android.view.ViewTreeObserver.OnGlobalLayoutListener; import com.android.tv.data.Channel; import com.android.tv.guide.ProgramManager.TableEntry; @@ -45,25 +44,26 @@ public class ProgramRow extends TimelineGridView { interface ChildFocusListener { /** - * Is called after focus is moved. Only children to {@code ProgramRow} will be passed. + * Is called after focus is moved. It used {@link ChildFocusListener#isChild} to decide if + * old and new focuses are listener's children. * See {@code ProgramRow#setChildFocusListener(ChildFocusListener)}. */ void onChildFocus(View oldFocus, View newFocus); } - private final ViewTreeObserver.OnGlobalFocusChangeListener mGlobalFocusChangeListener = - new ViewTreeObserver.OnGlobalFocusChangeListener() { - @Override - public void onGlobalFocusChanged(View oldFocus, View newFocus) { - updateCurrentFocus(oldFocus, newFocus); - } - }; - /** * Used only for debugging. */ private Channel mChannel; + private final OnGlobalLayoutListener mLayoutListener = new OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + getViewTreeObserver().removeOnGlobalLayoutListener(this); + updateChildVisibleArea(); + } + }; + public ProgramRow(Context context) { this(context, null); } @@ -94,19 +94,15 @@ public class ProgramRow extends TimelineGridView { @Override public void onScrolled(int dx, int dy) { + // Remove callback to prevent updateChildVisibleArea being called twice. + getViewTreeObserver().removeOnGlobalLayoutListener(mLayoutListener); super.onScrolled(dx, dy); - int childCount = getChildCount(); if (DEBUG) { Log.d(TAG, "onScrolled by " + dx); - Log.d(TAG, "channelId=" + mChannel.getId() + ", childCount=" + childCount); + Log.d(TAG, "channelId=" + mChannel.getId() + ", childCount=" + getChildCount()); Log.d(TAG, "ProgramRow {" + Utils.toRectString(this) + "}"); } - for (int i = 0; i < childCount; ++i) { - ProgramItemView child = (ProgramItemView) getChildAt(i); - if (getLeft() <= child.getRight() && child.getLeft() <= getRight()) { - child.updateVisibleArea(); - } - } + updateChildVisibleArea(); } /** @@ -117,29 +113,9 @@ public class ProgramRow extends TimelineGridView { if (currentProgram == null) { currentProgram = getChildAt(0); } - updateCurrentFocus(null, currentProgram); - } - - private void updateCurrentFocus(View oldFocus, View newFocus) { - if (mChildFocusListener == null) { - return; - } - - mChildFocusListener.onChildFocus(isChild(oldFocus) ? oldFocus : null, - isChild(newFocus) ? newFocus : null); - } - - private boolean isChild(View view) { - if (view == null) { - return false; - } - - for (ViewParent p = view.getParent(); p != null; p = p.getParent()) { - if (p == this) { - return true; - } + if (mChildFocusListener != null) { + mChildFocusListener.onChildFocus(null, currentProgram); } - return false; } // Call this API after RTL is resolved. (i.e. View is measured.) @@ -216,23 +192,21 @@ public class ProgramRow extends TimelineGridView { } @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - getViewTreeObserver().addOnGlobalFocusChangeListener(mGlobalFocusChangeListener); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - getViewTreeObserver().removeOnGlobalFocusChangeListener(mGlobalFocusChangeListener); - } - - @Override public void onChildDetachedFromWindow(View child) { if (child.hasFocus()) { // Focused view can be detached only if it's updated. TableEntry entry = ((ProgramItemView) child).getTableEntry(); - if (entry.isCurrentProgram()) { + if (entry.program == null) { + // 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(); + } + }); + } else if (entry.isCurrentProgram()) { if (DEBUG) Log.d(TAG, "Keep focus to the current program"); // Current program is visible in the guide. // Updated entries including current program's will be attached again soon @@ -250,13 +224,13 @@ public class ProgramRow extends TimelineGridView { if (mKeepFocusToCurrentProgram) { TableEntry entry = ((ProgramItemView) child).getTableEntry(); if (entry.isCurrentProgram()) { + mKeepFocusToCurrentProgram = false; post(new Runnable() { @Override public void run() { requestFocus(); } }); - mKeepFocusToCurrentProgram = false; } } } @@ -324,6 +298,22 @@ public class ProgramRow extends TimelineGridView { 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 theis case. + // Since scrollToPositionWithOffset() will call requestLayout(), we can listen to this + // behavior to ensure program items' visible areas are correctly updated after layouts + // are adjusted, i.e., scrolling is over. + getViewTreeObserver().addOnGlobalLayoutListener(mLayoutListener); + } + } + + private void updateChildVisibleArea() { + for (int i = 0; i < getChildCount(); ++i) { + ProgramItemView child = (ProgramItemView) getChildAt(i); + if (getLeft() < child.getRight() && child.getLeft() < getRight()) { + child.updateVisibleArea(); + } } } } diff --git a/src/com/android/tv/guide/ProgramTableAdapter.java b/src/com/android/tv/guide/ProgramTableAdapter.java index fd561992..e4a67972 100644 --- a/src/com/android/tv/guide/ProgramTableAdapter.java +++ b/src/com/android/tv/guide/ProgramTableAdapter.java @@ -17,6 +17,7 @@ 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; @@ -42,6 +43,8 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; +import android.view.ViewTreeObserver; +import android.view.accessibility.AccessibilityManager; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; @@ -50,10 +53,10 @@ import com.android.tv.R; import com.android.tv.TvApplication; import com.android.tv.common.feature.CommonFeatures; import com.android.tv.data.Channel; -import com.android.tv.data.Program.CriticScore; import com.android.tv.data.Program; -import com.android.tv.dvr.DvrManager; +import com.android.tv.data.Program.CriticScore; import com.android.tv.dvr.DvrDataManager; +import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.ScheduledRecording; import com.android.tv.guide.ProgramManager.TableEntriesUpdatedListener; import com.android.tv.parental.ParentalControlSettings; @@ -80,6 +83,7 @@ public class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapte private final DvrManager mDvrManager; private final DvrDataManager mDvrDataManager; private final ProgramManager mProgramManager; + private final AccessibilityManager mAccessibilityManager; private final ProgramGuide mProgramGuide; private final Handler mHandler = new Handler(); private final List<ProgramListAdapter> mProgramListAdapters = new ArrayList<>(); @@ -103,6 +107,7 @@ public class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapte private final String mProgramRecordableText; private final String mRecordingScheduledText; private final String mRecordingConflictText; + private final String mRecordingFailedText; private final String mRecordingInProgressText; private final int mDvrPaddingStartWithTrack; private final int mDvrPaddingStartWithOutTrack; @@ -110,6 +115,8 @@ public class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapte public ProgramTableAdapter(Context context, ProgramManager programManager, ProgramGuide programGuide) { mContext = context; + mAccessibilityManager = + (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); mTvInputManagerHelper = TvApplication.getSingletons(context).getTvInputManagerHelper(); if (CommonFeatures.DVR.isEnabled(context)) { mDvrManager = TvApplication.getSingletons(context).getDvrManager(); @@ -149,6 +156,7 @@ public class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapte 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); @@ -162,7 +170,7 @@ public class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapte mEpisodeTitleStyle = new TextAppearanceSpan(null, 0, episodeTitleSize, episodeTitleColor, null); - mCriticScoreViews = new ArrayList<LinearLayout>(); + mCriticScoreViews = new ArrayList<>(); mRecycledViewPool = new RecycledViewPool(); mRecycledViewPool.setMaxRecycledViews(R.layout.program_guide_table_item, context.getResources().getInteger( @@ -170,12 +178,7 @@ public class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapte mProgramManager.addListener(new ProgramManager.ListenerAdapter() { @Override public void onChannelsUpdated() { - mHandler.post(new Runnable() { - @Override - public void run() { - update(); - } - }); + update(); } }); update(); @@ -238,6 +241,16 @@ public class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapte notifyItemChanged(channelIndex, true); } + @Override + public void onViewAttachedToWindow(ProgramRowHolder holder) { + holder.onAttachedToWindow(); + } + + @Override + public void onViewDetachedFromWindow(ProgramRowHolder holder) { + holder.onDetachedFromWindow(); + } + // TODO: make it static public class ProgramRowHolder extends RecyclerView.ViewHolder implements ProgramRow.ChildFocusListener { @@ -265,6 +278,15 @@ public class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapte } }; + private final ViewTreeObserver.OnGlobalFocusChangeListener mGlobalFocusChangeListener = + new ViewTreeObserver.OnGlobalFocusChangeListener() { + @Override + public void onGlobalFocusChanged(View oldFocus, View newFocus) { + onChildFocus(isChild(oldFocus) ? oldFocus : null, + isChild(newFocus) ? newFocus : null); + } + }; + // Members of Program Details private final ViewGroup mDetailView; private final ImageView mImageView; @@ -317,6 +339,16 @@ public class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapte mChannelLogoView = (ImageView) mContainer.findViewById(R.id.channel_logo); mChannelBlockView = (ImageView) mContainer.findViewById(R.id.channel_block); mInputLogoView = (ImageView) mContainer.findViewById(R.id.input_logo); + mDetailView.setFocusable(mAccessibilityManager.isEnabled()); + mChannelHeaderView.setFocusable(mAccessibilityManager.isEnabled()); + mAccessibilityManager.addAccessibilityStateChangeListener( + new AccessibilityManager.AccessibilityStateChangeListener() { + @Override + public void onAccessibilityStateChanged(boolean enable) { + mDetailView.setFocusable(enable); + mChannelHeaderView.setFocusable(enable); + } + }); } public void onBind(int position) { @@ -384,12 +416,32 @@ public class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapte } } + public boolean isChild(View view) { + if (view == null) { + return false; + } + for (ViewParent p = view.getParent(); p != null; p = p.getParent()) { + if (p == mContainer) { + return true; + } + } + return false; + } + @Override public void onChildFocus(View oldFocus, View newFocus) { if (newFocus == null) { return; } - mSelectedEntry = ((ProgramItemView) newFocus).getTableEntry(); + // 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(); + } else if (newFocus == mDetailView) { + return; + } else { + mSelectedEntry = ((ProgramItemView) newFocus).getTableEntry(); + } if (oldFocus == null) { updateDetailView(); return; @@ -456,7 +508,21 @@ public class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapte }); } + private void onAttachedToWindow() { + mContainer.getViewTreeObserver() + .addOnGlobalFocusChangeListener(mGlobalFocusChangeListener); + } + + private void onDetachedFromWindow() { + mContainer.getViewTreeObserver() + .removeOnGlobalFocusChangeListener(mGlobalFocusChangeListener); + } + private void updateDetailView() { + if (mSelectedEntry == null) { + // The view holder is never on focus before. + return; + } if (DEBUG) Log.d(TAG, "updateDetailView"); mCriticScoresLayout.removeAllViews(); if (Program.isValid(mSelectedEntry.program)) { @@ -508,7 +574,7 @@ public class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapte int iconResId = 0; if (scheduledRecording != null) { if (mDvrManager.isConflicting(scheduledRecording)) { - iconResId = R.drawable.ic_warning_white_24dp; + iconResId = R.drawable.ic_warning_white_12dp; statusText = mRecordingConflictText; } else { switch (scheduledRecording.getState()) { @@ -521,8 +587,8 @@ public class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapte statusText = mRecordingScheduledText; break; case ScheduledRecording.STATE_RECORDING_FAILED: - iconResId = R.drawable.ic_warning_white_24dp; - statusText = mRecordingConflictText; + iconResId = R.drawable.ic_warning_white_12dp; + statusText = mRecordingFailedText; break; default: iconResId = 0; |