aboutsummaryrefslogtreecommitdiff
path: root/src/com/android/tv/guide
diff options
context:
space:
mode:
authorNick Chalko <nchalko@google.com>2016-10-26 14:03:09 -0700
committerNick Chalko <nchalko@google.com>2016-10-31 10:36:49 -0700
commitd41f0075a7d2ea826204e81fcec57d0aa57171a9 (patch)
treecb30cfbafd80e01d314868cdc36e783d39981119 /src/com/android/tv/guide
parent5e0ec06a797e3497da94390c63c7072de442695b (diff)
downloadTV-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.java61
-rw-r--r--src/com/android/tv/guide/ProgramItemView.java42
-rw-r--r--src/com/android/tv/guide/ProgramManager.java24
-rw-r--r--src/com/android/tv/guide/ProgramRow.java100
-rw-r--r--src/com/android/tv/guide/ProgramTableAdapter.java92
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;