aboutsummaryrefslogtreecommitdiff
path: root/src/com/android/tv/dvr/ui
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/tv/dvr/ui')
-rw-r--r--src/com/android/tv/dvr/ui/BigArguments.java54
-rw-r--r--src/com/android/tv/dvr/ui/ChangeImageTransformWithScaledParent.java79
-rw-r--r--src/com/android/tv/dvr/ui/CurrentRecordingDetailsFragment.java59
-rw-r--r--src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java4
-rw-r--r--src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java5
-rw-r--r--src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java2
-rw-r--r--src/com/android/tv/dvr/ui/DvrConflictFragment.java7
-rw-r--r--src/com/android/tv/dvr/ui/DvrForgetStorageErrorFragment.java87
-rw-r--r--src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java50
-rw-r--r--src/com/android/tv/dvr/ui/DvrHalfSizedDialogFragment.java12
-rw-r--r--src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java84
-rw-r--r--src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java50
-rw-r--r--src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java (renamed from src/com/android/tv/dvr/ui/PrioritySettingsFragment.java)7
-rw-r--r--src/com/android/tv/dvr/ui/DvrScheduleFragment.java17
-rw-r--r--src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java5
-rw-r--r--src/com/android/tv/dvr/ui/DvrSeriesDeletionFragment.java (renamed from src/com/android/tv/dvr/ui/SeriesDeletionFragment.java)11
-rw-r--r--src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java45
-rw-r--r--src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java20
-rw-r--r--src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java (renamed from src/com/android/tv/dvr/ui/SeriesSettingsFragment.java)209
-rw-r--r--src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java11
-rw-r--r--src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java4
-rw-r--r--src/com/android/tv/dvr/ui/DvrUiHelper.java575
-rw-r--r--src/com/android/tv/dvr/ui/FadeBackground.java70
-rw-r--r--src/com/android/tv/dvr/ui/HalfSizedDialogFragment.java117
-rw-r--r--src/com/android/tv/dvr/ui/SortedArrayAdapter.java90
-rw-r--r--src/com/android/tv/dvr/ui/browse/ActionPresenterSelector.java (renamed from src/com/android/tv/dvr/ui/ActionPresenterSelector.java)10
-rw-r--r--src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java120
-rw-r--r--src/com/android/tv/dvr/ui/browse/DetailsContent.java (renamed from src/com/android/tv/dvr/ui/DetailsContent.java)4
-rw-r--r--src/com/android/tv/dvr/ui/browse/DetailsContentPresenter.java (renamed from src/com/android/tv/dvr/ui/DetailsContentPresenter.java)37
-rw-r--r--src/com/android/tv/dvr/ui/browse/DetailsViewBackgroundHelper.java (renamed from src/com/android/tv/dvr/ui/DetailsViewBackgroundHelper.java)4
-rw-r--r--src/com/android/tv/dvr/ui/browse/DvrBrowseActivity.java (renamed from src/com/android/tv/dvr/ui/DvrActivity.java)6
-rw-r--r--src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java (renamed from src/com/android/tv/dvr/ui/DvrBrowseFragment.java)159
-rw-r--r--src/com/android/tv/dvr/ui/browse/DvrDetailsActivity.java (renamed from src/com/android/tv/dvr/ui/DvrDetailsActivity.java)2
-rw-r--r--src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java (renamed from src/com/android/tv/dvr/ui/DvrDetailsFragment.java)6
-rw-r--r--src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java (renamed from src/com/android/tv/dvr/ui/DvrItemPresenter.java)13
-rw-r--r--src/com/android/tv/dvr/ui/browse/DvrListRowPresenter.java34
-rw-r--r--src/com/android/tv/dvr/ui/browse/FullScheduleCardHolder.java (renamed from src/com/android/tv/dvr/ui/FullScheduleCardHolder.java)2
-rw-r--r--src/com/android/tv/dvr/ui/browse/FullSchedulesCardPresenter.java (renamed from src/com/android/tv/dvr/ui/FullSchedulesCardPresenter.java)66
-rw-r--r--src/com/android/tv/dvr/ui/browse/RecordedProgramDetailsFragment.java (renamed from src/com/android/tv/dvr/ui/RecordedProgramDetailsFragment.java)6
-rw-r--r--src/com/android/tv/dvr/ui/browse/RecordedProgramPresenter.java (renamed from src/com/android/tv/dvr/ui/RecordedProgramPresenter.java)31
-rw-r--r--src/com/android/tv/dvr/ui/browse/RecordingCardView.java (renamed from src/com/android/tv/dvr/ui/RecordingCardView.java)113
-rw-r--r--src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java (renamed from src/com/android/tv/dvr/ui/RecordingDetailsFragment.java)4
-rw-r--r--src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java (renamed from src/com/android/tv/dvr/ui/ScheduledRecordingDetailsFragment.java)4
-rw-r--r--src/com/android/tv/dvr/ui/browse/ScheduledRecordingPresenter.java (renamed from src/com/android/tv/dvr/ui/ScheduledRecordingPresenter.java)21
-rw-r--r--src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java (renamed from src/com/android/tv/dvr/ui/SeriesRecordingDetailsFragment.java)24
-rw-r--r--src/com/android/tv/dvr/ui/browse/SeriesRecordingPresenter.java (renamed from src/com/android/tv/dvr/ui/SeriesRecordingPresenter.java)11
-rw-r--r--src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java2
-rw-r--r--src/com/android/tv/dvr/ui/list/DvrSchedulesActivity.java (renamed from src/com/android/tv/dvr/ui/DvrSchedulesActivity.java)76
-rw-r--r--src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java5
-rw-r--r--src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java72
-rw-r--r--src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java6
-rw-r--r--src/com/android/tv/dvr/ui/list/ScheduleRow.java4
-rw-r--r--src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java4
-rw-r--r--src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java50
-rw-r--r--src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java20
-rw-r--r--src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java26
-rw-r--r--src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java24
-rw-r--r--src/com/android/tv/dvr/ui/list/SeriesScheduleRowPresenter.java17
-rw-r--r--src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java67
-rw-r--r--src/com/android/tv/dvr/ui/playback/DvrPlaybackCardPresenter.java (renamed from src/com/android/tv/dvr/ui/DvrPlaybackCardPresenter.java)31
-rw-r--r--src/com/android/tv/dvr/ui/playback/DvrPlaybackControlHelper.java (renamed from src/com/android/tv/dvr/ui/DvrPlaybackControlHelper.java)106
-rw-r--r--src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java335
-rw-r--r--src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java (renamed from src/com/android/tv/dvr/ui/DvrPlaybackOverlayFragment.java)181
-rw-r--r--src/com/android/tv/dvr/ui/playback/DvrPlaybackSideFragment.java154
-rw-r--r--src/com/android/tv/dvr/ui/playback/DvrPlayer.java583
65 files changed, 3216 insertions, 898 deletions
diff --git a/src/com/android/tv/dvr/ui/BigArguments.java b/src/com/android/tv/dvr/ui/BigArguments.java
new file mode 100644
index 00000000..ec3b5065
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/BigArguments.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tv.dvr.ui;
+
+import android.support.annotation.NonNull;
+
+import com.android.tv.common.SoftPreconditions;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Stores the object to pass through activities/fragments.
+ */
+public class BigArguments {
+ private final static String TAG = "BigArguments";
+ private static Map<String, Object> sBigArgumentMap = new HashMap<>();
+
+ /**
+ * Sets the argument.
+ */
+ public static void setArgument(String name, @NonNull Object value) {
+ SoftPreconditions.checkState(value != null, TAG, "Set argument, but value is null");
+ sBigArgumentMap.put(name, value);
+ }
+
+ /**
+ * Returns the argument which is associated to the name.
+ */
+ public static Object getArgument(String name) {
+ return sBigArgumentMap.get(name);
+ }
+
+ /**
+ * Resets the arguments.
+ */
+ public static void reset() {
+ sBigArgumentMap.clear();
+ }
+}
diff --git a/src/com/android/tv/dvr/ui/ChangeImageTransformWithScaledParent.java b/src/com/android/tv/dvr/ui/ChangeImageTransformWithScaledParent.java
new file mode 100644
index 00000000..cddece73
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/ChangeImageTransformWithScaledParent.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tv.dvr.ui;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.graphics.drawable.BitmapDrawable;
+import android.transition.ChangeImageTransform;
+import android.transition.TransitionValues;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
+
+import com.android.tv.R;
+
+import java.util.Map;
+
+/**
+ * TODO: Remove this class once b/32405620 is fixed.
+ * This class is for the workaround of b/32405620 and only for the shared element transition between
+ * {@link com.android.tv.dvr.ui.browse.RecordingCardView} and
+ * {@link com.android.tv.dvr.ui.browse.DvrDetailsActivity}.
+ */
+public class ChangeImageTransformWithScaledParent extends ChangeImageTransform {
+ private static final String PROPNAME_MATRIX = "android:changeImageTransform:matrix";
+
+ public ChangeImageTransformWithScaledParent(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void captureStartValues(TransitionValues transitionValues) {
+ super.captureStartValues(transitionValues);
+ applyParentScale(transitionValues);
+ }
+
+ @Override
+ public void captureEndValues(TransitionValues transitionValues) {
+ super.captureEndValues(transitionValues);
+ applyParentScale(transitionValues);
+ }
+
+ private void applyParentScale(TransitionValues transitionValues) {
+ View view = transitionValues.view;
+ Map<String, Object> values = transitionValues.values;
+ Matrix matrix = (Matrix) values.get(PROPNAME_MATRIX);
+ if (matrix != null && view.getId() == R.id.details_overview_image
+ && view instanceof ImageView) {
+ ImageView imageView = (ImageView) view;
+ if (imageView.getScaleType() == ScaleType.CENTER_INSIDE
+ && imageView.getDrawable() instanceof BitmapDrawable) {
+ Bitmap bitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
+ if (bitmap.getWidth() < imageView.getWidth()
+ && bitmap.getHeight() < imageView.getHeight()) {
+ float scale = imageView.getContext().getResources().getFraction(
+ R.fraction.lb_focus_zoom_factor_medium, 1, 1);
+ matrix.postScale(scale, scale, imageView.getWidth() / 2,
+ imageView.getHeight() / 2);
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/android/tv/dvr/ui/CurrentRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/CurrentRecordingDetailsFragment.java
deleted file mode 100644
index 5d8e20ff..00000000
--- a/src/com/android/tv/dvr/ui/CurrentRecordingDetailsFragment.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.tv.dvr.ui;
-
-import android.content.res.Resources;
-import android.support.v17.leanback.widget.Action;
-import android.support.v17.leanback.widget.OnActionClickedListener;
-import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
-
-import com.android.tv.R;
-import com.android.tv.TvApplication;
-import com.android.tv.dvr.DvrManager;
-
-/**
- * {@link RecordingDetailsFragment} for current recording in DVR.
- */
-public class CurrentRecordingDetailsFragment extends RecordingDetailsFragment {
- private static final int ACTION_STOP_RECORDING = 1;
-
- @Override
- protected SparseArrayObjectAdapter onCreateActionsAdapter() {
- SparseArrayObjectAdapter adapter =
- new SparseArrayObjectAdapter(new ActionPresenterSelector());
- Resources res = getResources();
- adapter.set(ACTION_STOP_RECORDING, new Action(ACTION_STOP_RECORDING,
- res.getString(R.string.epg_dvr_dialog_message_stop_recording), null,
- res.getDrawable(R.drawable.lb_ic_stop)));
- return adapter;
- }
-
- @Override
- protected OnActionClickedListener onCreateOnActionClickedListener() {
- return new OnActionClickedListener() {
- @Override
- public void onActionClicked(Action action) {
- if (action.getId() == ACTION_STOP_RECORDING) {
- DvrManager dvrManager = TvApplication.getSingletons(getActivity())
- .getDvrManager();
- dvrManager.stopRecording(getRecording());
- }
- getActivity().finish();
- }
- };
- }
-}
diff --git a/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java b/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java
index 9df228d1..936e9c31 100644
--- a/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java
@@ -28,11 +28,9 @@ import android.widget.Toast;
import com.android.tv.R;
import com.android.tv.TvApplication;
-import com.android.tv.dvr.RecordedProgram;
import com.android.tv.data.Program;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.DvrUiHelper;
-import com.android.tv.util.Utils;
+import com.android.tv.dvr.data.RecordedProgram;
import java.util.List;
diff --git a/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java b/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java
index 78f21784..3c73cb47 100644
--- a/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java
@@ -25,15 +25,12 @@ import android.support.annotation.NonNull;
import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
import android.support.v17.leanback.widget.GuidedAction;
import android.text.format.DateUtils;
-import android.widget.Toast;
import com.android.tv.R;
import com.android.tv.TvApplication;
import com.android.tv.data.Program;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.DvrUiHelper;
-import com.android.tv.dvr.ScheduledRecording;
-import com.android.tv.util.Utils;
+import com.android.tv.dvr.data.ScheduledRecording;
import java.util.List;
diff --git a/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java b/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java
index 837d8ab2..880dc8ac 100644
--- a/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java
@@ -27,7 +27,7 @@ import com.android.tv.TvApplication;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.Channel;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.ScheduledRecording;
+import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.ui.DvrConflictFragment.DvrChannelRecordConflictFragment;
import java.util.ArrayList;
diff --git a/src/com/android/tv/dvr/ui/DvrConflictFragment.java b/src/com/android/tv/dvr/ui/DvrConflictFragment.java
index e7be4d0a..5985f56f 100644
--- a/src/com/android/tv/dvr/ui/DvrConflictFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrConflictFragment.java
@@ -34,10 +34,9 @@ import com.android.tv.TvApplication;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.Channel;
import com.android.tv.data.Program;
-import com.android.tv.dvr.ConflictChecker;
-import com.android.tv.dvr.ConflictChecker.OnUpcomingConflictChangeListener;
-import com.android.tv.dvr.DvrUiHelper;
-import com.android.tv.dvr.ScheduledRecording;
+import com.android.tv.dvr.recorder.ConflictChecker;
+import com.android.tv.dvr.recorder.ConflictChecker.OnUpcomingConflictChangeListener;
+import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.util.Utils;
import java.util.ArrayList;
diff --git a/src/com/android/tv/dvr/ui/DvrForgetStorageErrorFragment.java b/src/com/android/tv/dvr/ui/DvrForgetStorageErrorFragment.java
deleted file mode 100644
index 73ddcdd0..00000000
--- a/src/com/android/tv/dvr/ui/DvrForgetStorageErrorFragment.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.dvr.ui;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
-import android.text.TextUtils;
-
-import com.android.tv.R;
-import com.android.tv.TvApplication;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.dvr.DvrDataManager;
-import com.android.tv.dvr.DvrManager;
-
-import java.util.List;
-
-public class DvrForgetStorageErrorFragment extends DvrGuidedStepFragment {
- private static final int ACTION_CANCEL = 1;
- private static final int ACTION_FORGET_STORAGE = 2;
- private String mInputId;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- Bundle args = getArguments();
- if (args != null) {
- mInputId = args.getString(DvrHalfSizedDialogFragment.KEY_INPUT_ID);
- }
- SoftPreconditions.checkArgument(!TextUtils.isEmpty(mInputId));
- super.onCreate(savedInstanceState);
- }
-
- @NonNull
- @Override
- public Guidance onCreateGuidance(Bundle savedInstanceState) {
- String title = getResources().getString(R.string.dvr_error_forget_storage_title);
- String description = getResources().getString(
- R.string.dvr_error_forget_storage_description);
- return new Guidance(title, description, null, null);
- }
-
- @Override
- public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
- Activity activity = getActivity();
- actions.add(new GuidedAction.Builder(activity)
- .id(ACTION_CANCEL)
- .title(getResources().getString(R.string.dvr_action_error_cancel))
- .build());
- actions.add(new GuidedAction.Builder(activity)
- .id(ACTION_FORGET_STORAGE)
- .title(getResources().getString(R.string.dvr_action_error_forget_storage))
- .build());
- }
-
- @Override
- public void onGuidedActionClicked(GuidedAction action) {
- if (action.getId() != ACTION_FORGET_STORAGE) {
- dismissDialog();
- return;
- }
- DvrManager dvrManager = TvApplication.getSingletons(getContext()).getDvrManager();
- dvrManager.forgetStorage(mInputId);
- Activity activity = getActivity();
- if (activity instanceof DvrDetailsActivity) {
- // Since we removed everything, just finish the activity.
- activity.finish();
- } else {
- dismissDialog();
- }
- }
-}
diff --git a/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java b/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java
index d26e6836..433588da 100644
--- a/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java
@@ -16,10 +16,12 @@
package com.android.tv.dvr.ui;
+import android.app.Activity;
import android.app.DialogFragment;
import android.content.Context;
import android.os.Bundle;
import android.support.v17.leanback.app.GuidedStepFragment;
+import android.support.v17.leanback.widget.GuidanceStylist;
import android.support.v17.leanback.widget.GuidedAction;
import android.support.v17.leanback.widget.VerticalGridView;
import android.view.LayoutInflater;
@@ -29,11 +31,26 @@ import android.view.ViewGroup;
import com.android.tv.MainActivity;
import com.android.tv.R;
import com.android.tv.TvApplication;
+import com.android.tv.dialog.HalfSizedDialogFragment.OnActionClickListener;
import com.android.tv.dialog.SafeDismissDialogFragment;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.ui.HalfSizedDialogFragment.OnActionClickListener;
+
+import java.util.List;
public class DvrGuidedStepFragment extends GuidedStepFragment {
+ /**
+ * Action ID for "recording/scheduling the program anyway".
+ */
+ public static final int ACTION_RECORD_ANYWAY = 1;
+ /**
+ * Action ID for "deleting existed recordings".
+ */
+ public static final int ACTION_DELETE_RECORDINGS = 2;
+ /**
+ * Action ID for "cancelling current recording request".
+ */
+ public static final int ACTION_CANCEL_RECORDING = 3;
+
private DvrManager mDvrManager;
private OnActionClickListener mOnActionClickListener;
@@ -86,4 +103,35 @@ public class DvrGuidedStepFragment extends GuidedStepFragment {
protected void setOnActionClickListener(OnActionClickListener listener) {
mOnActionClickListener = listener;
}
+
+ /**
+ * The inner guided step fragment for
+ * {@link com.android.tv.dvr.ui.DvrHalfSizedDialogFragment
+ * .DvrNoFreeSpaceErrorDialogFragment}.
+ */
+ public static class DvrNoFreeSpaceErrorFragment
+ extends DvrGuidedStepFragment {
+ @Override
+ public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
+ return new GuidanceStylist.Guidance(getString(R.string.dvr_error_no_free_space_title),
+ getString(R.string.dvr_error_no_free_space_description), null, null);
+ }
+
+ @Override
+ public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
+ Activity activity = getActivity();
+ actions.add(new GuidedAction.Builder(activity)
+ .id(ACTION_RECORD_ANYWAY)
+ .title(R.string.dvr_action_record_anyway)
+ .build());
+ actions.add(new GuidedAction.Builder(activity)
+ .id(ACTION_DELETE_RECORDINGS)
+ .title(R.string.dvr_action_delete_recordings)
+ .build());
+ actions.add(new GuidedAction.Builder(activity)
+ .id(ACTION_CANCEL_RECORDING)
+ .title(R.string.dvr_action_record_cancel)
+ .build());
+ }
+ }
} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/DvrHalfSizedDialogFragment.java b/src/com/android/tv/dvr/ui/DvrHalfSizedDialogFragment.java
index 2b132db8..9054dd03 100644
--- a/src/com/android/tv/dvr/ui/DvrHalfSizedDialogFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrHalfSizedDialogFragment.java
@@ -29,6 +29,7 @@ import android.view.ViewGroup;
import com.android.tv.MainActivity;
import com.android.tv.R;
import com.android.tv.dvr.DvrStorageStatusManager;
+import com.android.tv.dialog.HalfSizedDialogFragment;
import com.android.tv.dvr.ui.DvrConflictFragment.DvrChannelWatchConflictFragment;
import com.android.tv.dvr.ui.DvrConflictFragment.DvrProgramConflictFragment;
import com.android.tv.guide.ProgramGuide;
@@ -166,6 +167,17 @@ public class DvrHalfSizedDialogFragment extends HalfSizedDialogFragment {
}
/**
+ * A dialog fragment to show error message when there is no enough free space to record.
+ */
+ public static class DvrNoFreeSpaceErrorDialogFragment
+ extends DvrGuidedStepDialogFragment {
+ @Override
+ protected DvrGuidedStepFragment onCreateGuidedStepFragment() {
+ return new DvrGuidedStepFragment.DvrNoFreeSpaceErrorFragment();
+ }
+ }
+
+ /**
* A dialog fragment to show error message when the current storage is too small to
* support DVR
*/
diff --git a/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java b/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java
index 3b1dbfa0..3c5df1a6 100644
--- a/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java
@@ -17,6 +17,7 @@
package com.android.tv.dvr.ui;
import android.app.Activity;
+import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
@@ -24,19 +25,67 @@ import android.support.v17.leanback.widget.GuidedAction;
import com.android.tv.R;
import com.android.tv.TvApplication;
-import com.android.tv.dvr.DvrDataManager;
+import com.android.tv.common.SoftPreconditions;
+import com.android.tv.dvr.ui.browse.DvrBrowseActivity;
+import java.util.ArrayList;
import java.util.List;
public class DvrInsufficientSpaceErrorFragment extends DvrGuidedStepFragment {
- private static final int ACTION_DONE = 1;
- private static final int ACTION_OPEN_DVR = 2;
+ /**
+ * Key for the failed scheduled recordings information.
+ */
+ public static final String FAILED_SCHEDULED_RECORDING_INFOS =
+ "failed_scheduled_recording_infos";
+
+ private static final String TAG = "DvrInsufficientSpaceErrorFragment";
+
+ private static final int ACTION_VIEW_RECENT_RECORDINGS = 1;
+
+ private ArrayList<String> mFailedScheduledRecordingInfos;
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ Bundle args = getArguments();
+ if (args != null) {
+ mFailedScheduledRecordingInfos =
+ args.getStringArrayList(FAILED_SCHEDULED_RECORDING_INFOS);
+ }
+ SoftPreconditions.checkState(
+ mFailedScheduledRecordingInfos != null && !mFailedScheduledRecordingInfos.isEmpty(),
+ TAG, "failed scheduled recording is null");
+ }
@Override
public Guidance onCreateGuidance(Bundle savedInstanceState) {
- String title = getResources().getString(R.string.dvr_error_insufficient_space_title);
- String description = getResources()
- .getString(R.string.dvr_error_insufficient_space_description);
+ String title;
+ String description;
+ int failedScheduledRecordingSize = mFailedScheduledRecordingInfos.size();
+ if (failedScheduledRecordingSize == 1) {
+ title = getString(
+ R.string.dvr_error_insufficient_space_title_one_recording,
+ mFailedScheduledRecordingInfos.get(0));
+ description = getString(
+ R.string.dvr_error_insufficient_space_description_one_recording,
+ mFailedScheduledRecordingInfos.get(0));
+ } else if (failedScheduledRecordingSize == 2) {
+ title = getString(
+ R.string.dvr_error_insufficient_space_title_two_recordings,
+ mFailedScheduledRecordingInfos.get(0), mFailedScheduledRecordingInfos.get(1));
+ description = getString(
+ R.string.dvr_error_insufficient_space_description_two_recordings,
+ mFailedScheduledRecordingInfos.get(0), mFailedScheduledRecordingInfos.get(1));
+ } else {
+ title = getString(
+ R.string.dvr_error_insufficient_space_title_three_or_more_recordings,
+ mFailedScheduledRecordingInfos.get(0), mFailedScheduledRecordingInfos.get(1),
+ mFailedScheduledRecordingInfos.get(2));
+ description = getString(
+ R.string.dvr_error_insufficient_space_description_three_or_more_recordings,
+ mFailedScheduledRecordingInfos.get(0), mFailedScheduledRecordingInfos.get(1),
+ mFailedScheduledRecordingInfos.get(2));
+ }
return new Guidance(title, description, null, null);
}
@@ -44,26 +93,21 @@ public class DvrInsufficientSpaceErrorFragment extends DvrGuidedStepFragment {
public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
Activity activity = getActivity();
actions.add(new GuidedAction.Builder(activity)
- .id(ACTION_DONE)
- .title(getResources().getString(R.string.dvr_action_error_done))
+ .clickAction(GuidedAction.ACTION_ID_OK)
.build());
- DvrDataManager dvrDataManager = TvApplication.getSingletons(getContext())
- .getDvrDataManager();
- if (!(dvrDataManager.getRecordedPrograms().isEmpty()
- && dvrDataManager.getStartedRecordings().isEmpty()
- && dvrDataManager.getNonStartedScheduledRecordings().isEmpty()
- && dvrDataManager.getSeriesRecordings().isEmpty())) {
- actions.add(new GuidedAction.Builder(activity)
- .id(ACTION_OPEN_DVR)
- .title(getResources().getString(R.string.dvr_action_error_open_dvr))
- .build());
+ if (TvApplication.getSingletons(getContext()).getDvrManager().hasValidItems()) {
+ actions.add(new GuidedAction.Builder(activity)
+ .id(ACTION_VIEW_RECENT_RECORDINGS)
+ .title(getResources().getString(
+ R.string.dvr_error_insufficient_space_action_view_recent_recordings))
+ .build());
}
}
@Override
public void onGuidedActionClicked(GuidedAction action) {
- if (action.getId() == ACTION_OPEN_DVR) {
- Intent intent = new Intent(getActivity(), DvrActivity.class);
+ if (action.getId() == ACTION_VIEW_RECENT_RECORDINGS) {
+ Intent intent = new Intent(getActivity(), DvrBrowseActivity.class);
getActivity().startActivity(intent);
}
dismissDialog();
diff --git a/src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java b/src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java
index 2e2c2849..8dc9eb4e 100644
--- a/src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java
@@ -17,29 +17,27 @@
package com.android.tv.dvr.ui;
import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
import android.os.Bundle;
-import android.support.v17.leanback.app.GuidedStepFragment;
+import android.provider.Settings;
import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
import android.support.v17.leanback.widget.GuidedAction;
-import android.text.TextUtils;
+import android.util.Log;
import com.android.tv.R;
-import com.android.tv.common.SoftPreconditions;
+import com.android.tv.dvr.ui.browse.DvrDetailsActivity;
import java.util.List;
public class DvrMissingStorageErrorFragment extends DvrGuidedStepFragment {
- private static final int ACTION_CANCEL = 1;
- private static final int ACTION_FORGET_STORAGE = 2;
- private String mInputId;
+ private static String TAG = "DvrMissingStorageErrorFragment";
+
+ private static final int ACTION_OK = 1;
+ private static final int ACTION_OPEN_STORAGE_SETTINGS = 2;
@Override
public void onCreate(Bundle savedInstanceState) {
- Bundle args = getArguments();
- if (args != null) {
- mInputId = args.getString(DvrHalfSizedDialogFragment.KEY_INPUT_ID);
- }
- SoftPreconditions.checkArgument(!TextUtils.isEmpty(mInputId));
super.onCreate(savedInstanceState);
}
@@ -55,25 +53,31 @@ public class DvrMissingStorageErrorFragment extends DvrGuidedStepFragment {
public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
Activity activity = getActivity();
actions.add(new GuidedAction.Builder(activity)
- .id(ACTION_CANCEL)
- .title(getResources().getString(R.string.dvr_action_error_cancel))
+ .id(ACTION_OK)
+ .title(android.R.string.ok)
.build());
actions.add(new GuidedAction.Builder(activity)
- .id(ACTION_FORGET_STORAGE)
- .title(getResources().getString(R.string.dvr_action_error_forget_storage))
+ .id(ACTION_OPEN_STORAGE_SETTINGS)
+ .title(getResources().getString(R.string.dvr_action_error_storage_settings))
.build());
}
@Override
public void onGuidedActionClicked(GuidedAction action) {
- if (action.getId() == ACTION_FORGET_STORAGE) {
- DvrForgetStorageErrorFragment fragment = new DvrForgetStorageErrorFragment();
- Bundle args = new Bundle();
- args.putString(DvrHalfSizedDialogFragment.KEY_INPUT_ID, mInputId);
- fragment.setArguments(args);
- GuidedStepFragment.add(getFragmentManager(), fragment, R.id.halfsized_dialog_host);
+ Activity activity = getActivity();
+ if (activity instanceof DvrDetailsActivity) {
+ activity.finish();
+ } else {
+ dismissDialog();
+ }
+ if (action.getId() != ACTION_OPEN_STORAGE_SETTINGS) {
return;
}
- dismissDialog();
+ final Intent intent = new Intent(Settings.ACTION_INTERNAL_STORAGE_SETTINGS);
+ try {
+ getContext().startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ Log.e(TAG, "Can't start internal storage settings activity", e);
+ }
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/PrioritySettingsFragment.java b/src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java
index 158bd824..562898a3 100644
--- a/src/com/android/tv/dvr/ui/PrioritySettingsFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java
@@ -33,7 +33,7 @@ import com.android.tv.TvApplication;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.DvrScheduleManager;
-import com.android.tv.dvr.SeriesRecording;
+import com.android.tv.dvr.data.SeriesRecording;
import java.util.ArrayList;
import java.util.List;
@@ -41,7 +41,7 @@ import java.util.List;
/**
* Fragment for DVR series recording settings.
*/
-public class PrioritySettingsFragment extends GuidedStepFragment {
+public class DvrPrioritySettingsFragment extends GuidedStepFragment {
/**
* Name of series recording id starting the fragment.
* Type: Long
@@ -162,7 +162,6 @@ public class PrioritySettingsFragment extends GuidedStepFragment {
return;
}
if (action.getId() < 0) {
- int selectedPosition = mSeriesRecordings.indexOf(mSelectedRecording);
mSelectedRecording = null;
for (int i = 0; i < mSeriesRecordings.size(); ++i) {
updateItem(i);
@@ -248,4 +247,4 @@ public class PrioritySettingsFragment extends GuidedStepFragment {
titleView.setTypeface(titleView.getTypeface(), Typeface.NORMAL);
}
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/DvrScheduleFragment.java b/src/com/android/tv/dvr/ui/DvrScheduleFragment.java
index da6d1637..d6008315 100644
--- a/src/com/android/tv/dvr/ui/DvrScheduleFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrScheduleFragment.java
@@ -32,9 +32,8 @@ import com.android.tv.TvApplication;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.Program;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.DvrUiHelper;
-import com.android.tv.dvr.ScheduledRecording;
-import com.android.tv.dvr.SeriesRecording;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.dvr.ui.DvrConflictFragment.DvrProgramConflictFragment;
import com.android.tv.util.Utils;
@@ -48,18 +47,26 @@ import java.util.List;
*/
@TargetApi(Build.VERSION_CODES.N)
public class DvrScheduleFragment extends DvrGuidedStepFragment {
+ /**
+ * Key for the whether to add the current program to series.
+ * Type: boolean
+ */
+ public static final String KEY_ADD_CURRENT_PROGRAM_TO_SERIES = "add_current_program_to_series";
+
private static final String TAG = "DvrScheduleFragment";
private static final int ACTION_RECORD_EPISODE = 1;
private static final int ACTION_RECORD_SERIES = 2;
private Program mProgram;
+ private boolean mAddCurrentProgramToSeries;
@Override
public void onCreate(Bundle savedInstanceState) {
Bundle args = getArguments();
if (args != null) {
mProgram = args.getParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM);
+ mAddCurrentProgramToSeries = args.getBoolean(KEY_ADD_CURRENT_PROGRAM_TO_SERIES, false);
}
DvrManager dvrManager = TvApplication.getSingletons(getContext()).getDvrManager();
SoftPreconditions.checkArgument(mProgram != null && mProgram.isEpisodic(), TAG,
@@ -139,8 +146,10 @@ public class DvrScheduleFragment extends DvrGuidedStepFragment {
.build();
getDvrManager().updateSeriesRecording(seriesRecording);
}
+
DvrUiHelper.startSeriesSettingsActivity(getContext(),
- seriesRecording.getId(), null, true, true, true);
+ seriesRecording.getId(), null, true, true, true,
+ mAddCurrentProgramToSeries ? mProgram : null);
dismissDialog();
}
}
diff --git a/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java b/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java
index f57e4b05..667af34a 100644
--- a/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java
+++ b/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java
@@ -22,9 +22,6 @@ import android.support.v17.leanback.app.GuidedStepFragment;
import com.android.tv.R;
import com.android.tv.TvApplication;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.dvr.ui.SeriesDeletionFragment;
-import com.android.tv.ui.sidepanel.SettingsFragment;
/**
* Activity to show details view in DVR.
@@ -42,7 +39,7 @@ public class DvrSeriesDeletionActivity extends Activity {
setContentView(R.layout.activity_dvr_series_settings);
// Check savedInstanceState to prevent that activity is being showed with animation.
if (savedInstanceState == null) {
- SeriesDeletionFragment deletionFragment = new SeriesDeletionFragment();
+ DvrSeriesDeletionFragment deletionFragment = new DvrSeriesDeletionFragment();
deletionFragment.setArguments(getIntent().getExtras());
GuidedStepFragment.addAsRoot(this, deletionFragment, R.id.dvr_settings_view_frame);
}
diff --git a/src/com/android/tv/dvr/ui/SeriesDeletionFragment.java b/src/com/android/tv/dvr/ui/DvrSeriesDeletionFragment.java
index 36e3cfc1..8bf8560f 100644
--- a/src/com/android/tv/dvr/ui/SeriesDeletionFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrSeriesDeletionFragment.java
@@ -33,9 +33,10 @@ import com.android.tv.common.SoftPreconditions;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.DvrWatchedPositionManager;
-import com.android.tv.dvr.RecordedProgram;
-import com.android.tv.dvr.SeriesRecording;
+import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.ui.GuidedActionsStylistWithDivider;
+import com.android.tv.util.Utils;
import java.util.ArrayList;
import java.util.Collections;
@@ -47,7 +48,7 @@ import java.util.concurrent.TimeUnit;
/**
* Fragment for DVR series recording settings.
*/
-public class SeriesDeletionFragment extends GuidedStepFragment {
+public class DvrSeriesDeletionFragment extends GuidedStepFragment {
private static final long WATCHED_TIME_UNIT_THRESHOLD = TimeUnit.MINUTES.toMillis(2);
// Since recordings' IDs are used as its check actions' IDs, which are random positive numbers,
@@ -218,8 +219,8 @@ public class SeriesDeletionFragment extends GuidedStepFragment {
private String getWatchedString(long watchedPositionMs, long durationMs) {
if (durationMs > WATCHED_TIME_UNIT_THRESHOLD) {
return getResources().getString(R.string.dvr_series_watched_info_minutes,
- Math.max(1, TimeUnit.MILLISECONDS.toMinutes(watchedPositionMs)),
- TimeUnit.MILLISECONDS.toMinutes(durationMs));
+ Math.max(1, Utils.getRoundOffMinsFromMs(watchedPositionMs)),
+ Utils.getRoundOffMinsFromMs(durationMs));
} else {
return getResources().getString(R.string.dvr_series_watched_info_seconds,
Math.max(1, TimeUnit.MILLISECONDS.toSeconds(watchedPositionMs)),
diff --git a/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java b/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java
index 1173df46..8f880f16 100644
--- a/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java
@@ -25,22 +25,29 @@ import android.support.v17.leanback.widget.GuidedAction;
import com.android.tv.R;
import com.android.tv.TvApplication;
-import com.android.tv.dvr.DvrDataManager;
+import com.android.tv.data.Program;
import com.android.tv.dvr.DvrScheduleManager;
-import com.android.tv.dvr.ScheduledRecording;
-import com.android.tv.dvr.SeriesRecording;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.data.SeriesRecording;
+import com.android.tv.dvr.ui.list.DvrSchedulesActivity;
import com.android.tv.dvr.ui.list.DvrSeriesSchedulesFragment;
import java.util.List;
public class DvrSeriesScheduledFragment extends DvrGuidedStepFragment {
+ /**
+ * The key for program list which will be passed to {@link DvrSeriesSchedulesFragment}.
+ * Type: List<{@link Program}>
+ */
+ public static final String SERIES_SCHEDULED_KEY_PROGRAMS = "series_scheduled_key_programs";
+
private final static long SERIES_RECORDING_ID_NOT_SET = -1;
private final static int ACTION_VIEW_SCHEDULES = 1;
- private DvrScheduleManager mDvrScheduleManager;
private SeriesRecording mSeriesRecording;
private boolean mShowViewScheduleOption;
+ private List<Program> mPrograms;
private int mSchedulesAddedCount = 0;
private boolean mHasConflict = false;
@@ -58,22 +65,25 @@ public class DvrSeriesScheduledFragment extends DvrGuidedStepFragment {
}
mShowViewScheduleOption = getArguments().getBoolean(
DvrSeriesScheduledDialogActivity.SHOW_VIEW_SCHEDULE_OPTION);
- mDvrScheduleManager = TvApplication.getSingletons(context).getDvrScheduleManager();
mSeriesRecording = TvApplication.getSingletons(context).getDvrDataManager()
.getSeriesRecording(seriesRecordingId);
if (mSeriesRecording == null) {
getActivity().finish();
return;
}
+ mPrograms = (List<Program>) BigArguments.getArgument(SERIES_SCHEDULED_KEY_PROGRAMS);
+ BigArguments.reset();
mSchedulesAddedCount = TvApplication.getSingletons(getContext()).getDvrManager()
.getAvailableScheduledRecording(mSeriesRecording.getId()).size();
+ DvrScheduleManager dvrScheduleManager =
+ TvApplication.getSingletons(context).getDvrScheduleManager();
List<ScheduledRecording> conflictingRecordings =
- mDvrScheduleManager.getConflictingSchedules(mSeriesRecording);
+ dvrScheduleManager.getConflictingSchedules(mSeriesRecording);
mHasConflict = !conflictingRecordings.isEmpty();
for (ScheduledRecording recording : conflictingRecordings) {
if (recording.getSeriesRecordingId() == mSeriesRecording.getId()) {
++mInThisSeriesConflictCount;
- } else {
+ } else if (recording.getPriority() < mSeriesRecording.getPriority()) {
++mOutThisSeriesConflictCount;
}
}
@@ -113,6 +123,9 @@ public class DvrSeriesScheduledFragment extends DvrGuidedStepFragment {
.TYPE_SERIES_SCHEDULE);
intent.putExtra(DvrSeriesSchedulesFragment.SERIES_SCHEDULES_KEY_SERIES_RECORDING,
mSeriesRecording);
+ BigArguments.reset();
+ BigArguments.setArgument(DvrSeriesSchedulesFragment
+ .SERIES_SCHEDULES_KEY_SERIES_PROGRAMS, mPrograms);
startActivity(intent);
}
getActivity().finish();
@@ -121,30 +134,30 @@ public class DvrSeriesScheduledFragment extends DvrGuidedStepFragment {
private String getDescription() {
if (!mHasConflict) {
return getResources().getQuantityString(
- R.plurals.dvr_series_recording_scheduled_no_conflict, mSchedulesAddedCount,
+ R.plurals.dvr_series_scheduled_no_conflict, mSchedulesAddedCount,
mSchedulesAddedCount, mSeriesRecording.getTitle());
} else {
// mInThisSeriesConflictCount equals 0 and mOutThisSeriesConflictCount equals 0 means
// mHasConflict is false. So we don't need to check that case.
if (mInThisSeriesConflictCount != 0 && mOutThisSeriesConflictCount != 0) {
- return getResources().getQuantityString(R.plurals
- .dvr_series_recording_scheduled_this_and_other_series_conflict,
+ return getResources().getQuantityString(
+ R.plurals.dvr_series_scheduled_this_and_other_series_conflict,
mSchedulesAddedCount, mSchedulesAddedCount, mSeriesRecording.getTitle(),
mInThisSeriesConflictCount + mOutThisSeriesConflictCount);
} else if (mInThisSeriesConflictCount != 0) {
- return getResources().getQuantityString(R.plurals
- .dvr_series_recording_scheduled_only_this_series_conflict,
+ return getResources().getQuantityString(
+ R.plurals.dvr_series_recording_scheduled_only_this_series_conflict,
mSchedulesAddedCount, mSchedulesAddedCount, mSeriesRecording.getTitle(),
mInThisSeriesConflictCount);
} else {
if (mOutThisSeriesConflictCount == 1) {
- return getResources().getQuantityString(R.plurals
- .dvr_series_recording_scheduled_only_other_series_one_conflict,
+ return getResources().getQuantityString(
+ R.plurals.dvr_series_scheduled_only_other_series_one_conflict,
mSchedulesAddedCount, mSchedulesAddedCount,
mSeriesRecording.getTitle());
} else {
- return getResources().getQuantityString(R.plurals
- .dvr_series_recording_scheduled_only_other_series_conflict,
+ return getResources().getQuantityString(
+ R.plurals.dvr_series_scheduled_only_other_series_many_conflicts,
mSchedulesAddedCount, mSchedulesAddedCount, mSeriesRecording.getTitle(),
mOutThisSeriesConflictCount);
}
diff --git a/src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java b/src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java
index 3f7671b3..6dd20b3a 100644
--- a/src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java
+++ b/src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java
@@ -17,7 +17,6 @@
package com.android.tv.dvr.ui;
import android.app.Activity;
-import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.support.v17.leanback.app.GuidedStepFragment;
@@ -38,25 +37,34 @@ public class DvrSeriesSettingsActivity extends Activity {
/**
* Name of the boolean flag to decide if the series recording with empty schedule and recording
* will be removed.
+ * Type: boolean
*/
public static final String REMOVE_EMPTY_SERIES_RECORDING = "remove_empty_series_recording";
/**
* Name of the boolean flag to decide if the setting fragment should be translucent.
+ * Type: boolean
*/
public static final String IS_WINDOW_TRANSLUCENT = "windows_translucent";
/**
- * Name of the channel id list. If the channel list is given, we show the channels
- * from the values in channel option.
- * Type: Long array
+ * Name of the program list. The list contains the programs which belong to the series.
+ * Type: List<{@link com.android.tv.data.Program}>
*/
- public static final String CHANNEL_ID_LIST = "channel_id_list";
+ public static final String PROGRAM_LIST = "program_list";
/**
* Name of the boolean flag to check if the confirm dialog should show view schedule option.
+ * Type: boolean
*/
public static final String SHOW_VIEW_SCHEDULE_OPTION_IN_DIALOG =
"show_view_schedule_option_in_dialog";
+ /**
+ * Name of the current program added to series. The current program will be recorded only when
+ * the series recording is initialized from media controller. But for other case, the current
+ * program won't be recorded.
+ */
+ public static final String CURRENT_PROGRAM = "current_program";
+
@Override
public void onCreate(Bundle savedInstanceState) {
TvApplication.setCurrentRunningProcess(this, true);
@@ -66,7 +74,7 @@ public class DvrSeriesSettingsActivity extends Activity {
SoftPreconditions.checkArgument(seriesRecordingId != -1);
if (savedInstanceState == null) {
- SeriesSettingsFragment settingFragment = new SeriesSettingsFragment();
+ DvrSeriesSettingsFragment settingFragment = new DvrSeriesSettingsFragment();
settingFragment.setArguments(getIntent().getExtras());
GuidedStepFragment.addAsRoot(this, settingFragment, R.id.dvr_settings_view_frame);
}
diff --git a/src/com/android/tv/dvr/ui/SeriesSettingsFragment.java b/src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java
index 6c05c9c6..f28382da 100644
--- a/src/com/android/tv/dvr/ui/SeriesSettingsFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java
@@ -17,19 +17,13 @@
package com.android.tv.dvr.ui;
import android.app.FragmentManager;
-import android.app.ProgressDialog;
import android.content.Context;
import android.os.Bundle;
import android.support.v17.leanback.app.GuidedStepFragment;
import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
import android.support.v17.leanback.widget.GuidedAction;
import android.support.v17.leanback.widget.GuidedActionsStylist;
-import android.util.Log;
import android.util.LongSparseArray;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import android.widget.ProgressBar;
import com.android.tv.R;
import com.android.tv.TvApplication;
@@ -38,14 +32,14 @@ import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.Program;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.DvrUiHelper;
-import com.android.tv.dvr.EpisodicProgramLoadTask;
-import com.android.tv.dvr.SeriesRecording;
-import com.android.tv.dvr.SeriesRecording.ChannelOption;
-import com.android.tv.dvr.SeriesRecordingScheduler;
-import com.android.tv.dvr.SeriesRecordingScheduler.OnSeriesRecordingUpdatedListener;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.data.SeasonEpisodeNumber;
+import com.android.tv.dvr.data.SeriesRecording;
+import com.android.tv.dvr.data.SeriesRecording.ChannelOption;
+import com.android.tv.dvr.recorder.SeriesRecordingScheduler;
+
import java.util.ArrayList;
-import java.util.Comparator;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -53,7 +47,7 @@ import java.util.Set;
/**
* Fragment for DVR series recording settings.
*/
-public class SeriesSettingsFragment extends GuidedStepFragment
+public class DvrSeriesSettingsFragment extends GuidedStepFragment
implements DvrDataManager.SeriesRecordingListener {
private static final String TAG = "SeriesSettingsFragment";
private static final boolean DEBUG = false;
@@ -66,15 +60,13 @@ public class SeriesSettingsFragment extends GuidedStepFragment
private static final long SUB_ACTION_ID_CHANNEL_ONE_BASE = 500;
private DvrDataManager mDvrDataManager;
- private ChannelDataManager mChannelDataManager;
- private DvrManager mDvrManager;
private SeriesRecording mSeriesRecording;
private long mSeriesRecordingId;
@ChannelOption int mChannelOption;
- private Comparator<Channel> mChannelComparator;
private long mSelectedChannelId;
private int mBackStackCount;
private boolean mShowViewScheduleOptionInDialog;
+ private Program mCurrentProgram;
private String mFragmentTitle;
private String mProrityActionTitle;
@@ -84,7 +76,7 @@ public class SeriesSettingsFragment extends GuidedStepFragment
private String mChannelsActionAllText;
private LongSparseArray<Channel> mId2Channel = new LongSparseArray<>();
private List<Channel> mChannels = new ArrayList<>();
- private EpisodicProgramLoadTask mEpisodicProgramLoadTask;
+ private List<Program> mPrograms;
private GuidedAction mPriorityGuidedAction;
private GuidedAction mChannelsGuidedAction;
@@ -100,22 +92,24 @@ public class SeriesSettingsFragment extends GuidedStepFragment
getActivity().finish();
return;
}
- mDvrManager = TvApplication.getSingletons(context).getDvrManager();
mShowViewScheduleOptionInDialog = getArguments().getBoolean(
DvrSeriesSettingsActivity.SHOW_VIEW_SCHEDULE_OPTION_IN_DIALOG);
+ mCurrentProgram = getArguments().getParcelable(DvrSeriesSettingsActivity.CURRENT_PROGRAM);
mDvrDataManager.addSeriesRecordingListener(this);
- long[] channelIds = getArguments().getLongArray(DvrSeriesSettingsActivity.CHANNEL_ID_LIST);
- mChannelDataManager = TvApplication.getSingletons(context).getChannelDataManager();
- if (channelIds == null) {
- Channel channel = mChannelDataManager.getChannel(mSeriesRecording.getChannelId());
- if (channel != null) {
- mId2Channel.put(channel.getId(), channel);
- mChannels.add(channel);
- }
- collectChannelsInBackground();
- } else {
- for (long channelId : channelIds) {
- Channel channel = mChannelDataManager.getChannel(channelId);
+ mPrograms = (List<Program>) BigArguments.getArgument(
+ DvrSeriesSettingsActivity.PROGRAM_LIST);
+ BigArguments.reset();
+ if (mPrograms == null) {
+ getActivity().finish();
+ return;
+ }
+ Set<Long> channelIds = new HashSet<>();
+ ChannelDataManager channelDataManager =
+ TvApplication.getSingletons(context).getChannelDataManager();
+ for (Program program : mPrograms) {
+ long channelId = program.getChannelId();
+ if (channelIds.add(channelId)) {
+ Channel channel = channelDataManager.getChannel(channelId);
if (channel != null) {
mId2Channel.put(channel.getId(), channel);
mChannels.add(channel);
@@ -125,16 +119,14 @@ public class SeriesSettingsFragment extends GuidedStepFragment
mChannelOption = mSeriesRecording.getChannelOption();
mSelectedChannelId = Channel.INVALID_ID;
if (mChannelOption == SeriesRecording.OPTION_CHANNEL_ONE) {
- Channel channel = mChannelDataManager.getChannel(mSeriesRecording.getChannelId());
+ Channel channel = channelDataManager.getChannel(mSeriesRecording.getChannelId());
if (channel != null) {
mSelectedChannelId = channel.getId();
} else {
mChannelOption = SeriesRecording.OPTION_CHANNEL_ALL;
}
}
- mChannelComparator = new Channel.DefaultComparator(context,
- TvApplication.getSingletons(context).getTvInputManagerHelper());
- mChannels.sort(mChannelComparator);
+ mChannels.sort(Channel.CHANNEL_NUMBER_COMPARATOR);
mFragmentTitle = getString(R.string.dvr_series_settings_title);
mProrityActionTitle = getString(R.string.dvr_series_settings_priority);
mProrityActionHighestText = getString(R.string.dvr_series_settings_priority_highest);
@@ -144,23 +136,23 @@ public class SeriesSettingsFragment extends GuidedStepFragment
}
@Override
+ public void onResume() {
+ super.onResume();
+ // To avoid the order of series's priority has changed, but series doesn't get update.
+ updatePriorityGuidedAction();
+ }
+
+ @Override
public void onDetach() {
super.onDetach();
mDvrDataManager.removeSeriesRecordingListener(this);
- if (mEpisodicProgramLoadTask != null) {
- mEpisodicProgramLoadTask.cancel(true);
- mEpisodicProgramLoadTask = null;
- }
}
@Override
public void onDestroy() {
- DvrManager dvrManager = TvApplication.getSingletons(getActivity()).getDvrManager();
- if (getFragmentManager().getBackStackEntryCount() == mBackStackCount
- && getArguments()
- .getBoolean(DvrSeriesSettingsActivity.REMOVE_EMPTY_SERIES_RECORDING)
- && dvrManager.canRemoveSeriesRecording(mSeriesRecordingId)) {
- dvrManager.removeSeriesRecording(mSeriesRecordingId);
+ if (getFragmentManager().getBackStackEntryCount() == mBackStackCount && getArguments()
+ .getBoolean(DvrSeriesSettingsActivity.REMOVE_EMPTY_SERIES_RECORDING)) {
+ mDvrDataManager.checkAndRemoveEmptySeriesRecording(mSeriesRecordingId);
}
super.onDestroy();
}
@@ -178,7 +170,6 @@ public class SeriesSettingsFragment extends GuidedStepFragment
.id(ACTION_ID_PRIORITY)
.title(mProrityActionTitle)
.build();
- updatePriorityGuidedAction(false);
actions.add(mPriorityGuidedAction);
mChannelsGuidedAction = new GuidedAction.Builder(getActivity())
@@ -204,10 +195,6 @@ public class SeriesSettingsFragment extends GuidedStepFragment
public void onGuidedActionClicked(GuidedAction action) {
long actionId = action.getId();
if (actionId == GuidedAction.ACTION_ID_OK) {
- if (mEpisodicProgramLoadTask != null) {
- mEpisodicProgramLoadTask.cancel(true);
- mEpisodicProgramLoadTask = null;
- }
if (mChannelOption != mSeriesRecording.getChannelOption()
|| mSeriesRecording.isStopped()
|| (mChannelOption == SeriesRecording.OPTION_CHANNEL_ONE
@@ -218,28 +205,14 @@ public class SeriesSettingsFragment extends GuidedStepFragment
if (mSelectedChannelId != Channel.INVALID_ID) {
builder.setChannelId(mSelectedChannelId);
}
- TvApplication.getSingletons(getContext()).getDvrManager()
- .updateSeriesRecording(builder.build());
- SeriesRecordingScheduler scheduler =
- SeriesRecordingScheduler.getInstance(getContext());
- // Since dialog is used even after the fragment is closed, we should
- // use application context.
- ProgressDialog dialog = ProgressDialog.show(getContext(), null, getString(
- R.string.dvr_series_schedules_progress_message_updating_programs));
- scheduler.addOnSeriesRecordingUpdatedListener(
- new OnSeriesRecordingUpdatedListener() {
- @Override
- public void onSeriesRecordingUpdated(SeriesRecording... seriesRecordings) {
- for (SeriesRecording seriesRecording : seriesRecordings) {
- if (seriesRecording.getId() == mSeriesRecordingId) {
- dialog.dismiss();
- scheduler.removeOnSeriesRecordingUpdatedListener(this);
- showConfirmDialog();
- return;
- }
- }
- }
- });
+ DvrManager dvrManager = TvApplication.getSingletons(getContext()).getDvrManager();
+ dvrManager.updateSeriesRecording(builder.build());
+ if (mCurrentProgram != null && (mChannelOption == SeriesRecording.OPTION_CHANNEL_ALL
+ || mSelectedChannelId == mCurrentProgram.getChannelId())) {
+ dvrManager.addSchedule(mCurrentProgram);
+ }
+ updateSchedulesToSeries();
+ showConfirmDialog();
} else {
showConfirmDialog();
}
@@ -247,9 +220,9 @@ public class SeriesSettingsFragment extends GuidedStepFragment
finishGuidedStepFragments();
} else if (actionId == ACTION_ID_PRIORITY) {
FragmentManager fragmentManager = getFragmentManager();
- PrioritySettingsFragment fragment = new PrioritySettingsFragment();
+ DvrPrioritySettingsFragment fragment = new DvrPrioritySettingsFragment();
Bundle args = new Bundle();
- args.putLong(PrioritySettingsFragment.COME_FROM_SERIES_RECORDING_ID,
+ args.putLong(DvrPrioritySettingsFragment.COME_FROM_SERIES_RECORDING_ID,
mSeriesRecording.getId());
fragment.setArguments(args);
GuidedStepFragment.add(fragmentManager, fragment, R.id.dvr_settings_view_frame);
@@ -281,7 +254,7 @@ public class SeriesSettingsFragment extends GuidedStepFragment
private void updateChannelsGuidedAction(boolean notifyActionChanged) {
if (mChannelOption == SeriesRecording.OPTION_CHANNEL_ALL) {
mChannelsGuidedAction.setDescription(mChannelsActionAllText);
- } else {
+ } else if (mId2Channel.get(mSelectedChannelId) != null){
mChannelsGuidedAction.setDescription(mId2Channel.get(mSelectedChannelId)
.getDisplayText());
}
@@ -290,7 +263,7 @@ public class SeriesSettingsFragment extends GuidedStepFragment
}
}
- private void updatePriorityGuidedAction(boolean notifyActionChanged) {
+ private void updatePriorityGuidedAction() {
int totalSeriesCount = 0;
int priorityOrder = 0;
for (SeriesRecording seriesRecording : mDvrDataManager.getSeriesRecordings()) {
@@ -312,49 +285,38 @@ public class SeriesSettingsFragment extends GuidedStepFragment
mPriorityGuidedAction.setDescription(getString(
R.string.dvr_series_settings_priority_rank, priorityOrder + 1));
}
- if (notifyActionChanged) {
- notifyActionChanged(findActionPositionById(ACTION_ID_PRIORITY));
- }
+ notifyActionChanged(findActionPositionById(ACTION_ID_PRIORITY));
}
- private void collectChannelsInBackground() {
- if (mEpisodicProgramLoadTask != null) {
- mEpisodicProgramLoadTask.cancel(true);
+ private void updateSchedulesToSeries() {
+ List<Program> recordingCandidates = new ArrayList<>();
+ Set<SeasonEpisodeNumber> scheduledEpisodes = new HashSet<>();
+ for (ScheduledRecording r : mDvrDataManager.getScheduledRecordings(mSeriesRecordingId)) {
+ if (r.getState() != ScheduledRecording.STATE_RECORDING_FAILED
+ && r.getState() != ScheduledRecording.STATE_RECORDING_CLIPPED) {
+ scheduledEpisodes.add(new SeasonEpisodeNumber(
+ r.getSeriesRecordingId(), r.getSeasonNumber(), r.getEpisodeNumber()));
+ }
}
- mEpisodicProgramLoadTask = new EpisodicProgramLoadTask(getContext(), mSeriesRecording) {
- @Override
- protected void onPostExecute(List<Program> programs) {
- mEpisodicProgramLoadTask = null;
- Set<Long> channelIds = new HashSet<>();
- for (Program program : programs) {
- channelIds.add(program.getChannelId());
- }
- boolean channelAdded = false;
- for (Long channelId : channelIds) {
- if (mId2Channel.get(channelId) != null) {
- continue;
- }
- Channel channel = mChannelDataManager.getChannel(channelId);
- if (channel != null) {
- channelAdded = true;
- mId2Channel.put(channelId, channel);
- mChannels.add(channel);
- if (DEBUG) Log.d(TAG, "Added channel: " + channel);
- }
- }
- if (!channelAdded) {
- return;
- }
- mChannels.sort(mChannelComparator);
- mChannelsGuidedAction.setSubActions(buildChannelSubAction());
- notifyActionChanged(findActionPositionById(ACTION_ID_CHANNEL));
- if (DEBUG) Log.d(TAG, "Complete EpisodicProgramLoadTask");
+ for (Program program : mPrograms) {
+ // Removes current programs and scheduled episodes out, matches the channel option.
+ if (program.getStartTimeUtcMillis() >= System.currentTimeMillis()
+ && mSeriesRecording.matchProgram(program)
+ && !scheduledEpisodes.contains(new SeasonEpisodeNumber(
+ mSeriesRecordingId, program.getSeasonNumber(), program.getEpisodeNumber()))) {
+ recordingCandidates.add(program);
}
- }.setLoadCurrentProgram(true)
- .setLoadDisallowedProgram(true)
- .setLoadScheduledEpisode(true)
- .setIgnoreChannelOption(true);
- mEpisodicProgramLoadTask.execute();
+ }
+ if (recordingCandidates.isEmpty()) {
+ return;
+ }
+ List<Program> programsToSchedule = SeriesRecordingScheduler.pickOneProgramPerEpisode(
+ mDvrDataManager, Collections.singletonList(mSeriesRecording), recordingCandidates)
+ .get(mSeriesRecordingId);
+ if (!programsToSchedule.isEmpty()) {
+ TvApplication.getSingletons(getContext()).getDvrManager()
+ .addScheduleToSeriesRecording(mSeriesRecording, programsToSchedule);
+ }
}
private List<GuidedAction> buildChannelSubAction() {
@@ -373,8 +335,8 @@ public class SeriesSettingsFragment extends GuidedStepFragment
}
private void showConfirmDialog() {
- DvrUiHelper.StartSeriesScheduledDialogActivity(
- getContext(), mSeriesRecording, mShowViewScheduleOptionInDialog);
+ DvrUiHelper.StartSeriesScheduledDialogActivity(getContext(), mSeriesRecording,
+ mShowViewScheduleOptionInDialog, mPrograms);
finishGuidedStepFragments();
}
@@ -382,16 +344,23 @@ public class SeriesSettingsFragment extends GuidedStepFragment
public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) { }
@Override
- public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) { }
+ public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) {
+ for (SeriesRecording series : seriesRecordings) {
+ if (series.getId() == mSeriesRecording.getId()) {
+ finishGuidedStepFragments();
+ return;
+ }
+ }
+ }
@Override
public void onSeriesRecordingChanged(SeriesRecording... seriesRecordings) {
for (SeriesRecording seriesRecording : seriesRecordings) {
if (seriesRecording.getId() == mSeriesRecordingId) {
mSeriesRecording = seriesRecording;
- updatePriorityGuidedAction(true);
+ updatePriorityGuidedAction();
return;
}
}
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java b/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java
index c3867886..b476fff7 100644
--- a/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java
@@ -33,7 +33,7 @@ import com.android.tv.data.Channel;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
-import com.android.tv.dvr.ScheduledRecording;
+import com.android.tv.dvr.data.ScheduledRecording;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -131,15 +131,8 @@ public class DvrStopRecordingFragment extends DvrGuidedStepFragment {
String title = getString(R.string.dvr_stop_recording_dialog_title);
String description;
if (mStopReason == REASON_ON_CONFLICT) {
- String programTitle = mSchedule.getProgramTitle();
- if (TextUtils.isEmpty(programTitle)) {
- ChannelDataManager channelDataManager =
- TvApplication.getSingletons(getActivity()).getChannelDataManager();
- Channel channel = channelDataManager.getChannel(mSchedule.getChannelId());
- programTitle = channel.getDisplayName();
- }
description = getString(R.string.dvr_stop_recording_dialog_description_on_conflict,
- mSchedule.getProgramTitle());
+ mSchedule.getProgramDisplayTitle(getContext()));
} else {
description = getString(R.string.dvr_stop_recording_dialog_description);
}
diff --git a/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java b/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java
index feaa2357..fe3a4a60 100644
--- a/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java
@@ -31,8 +31,8 @@ import com.android.tv.R;
import com.android.tv.TvApplication;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.ScheduledRecording;
-import com.android.tv.dvr.SeriesRecording;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.data.SeriesRecording;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/com/android/tv/dvr/ui/DvrUiHelper.java b/src/com/android/tv/dvr/ui/DvrUiHelper.java
new file mode 100644
index 00000000..507db6e7
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/DvrUiHelper.java
@@ -0,0 +1,575 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tv.dvr.ui;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.media.tv.TvInputManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.ActivityOptionsCompat;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import com.android.tv.MainActivity;
+import com.android.tv.R;
+import com.android.tv.TvApplication;
+import com.android.tv.common.SoftPreconditions;
+import com.android.tv.data.Channel;
+import com.android.tv.data.Program;
+import com.android.tv.dialog.HalfSizedDialogFragment;
+import com.android.tv.dvr.DvrManager;
+import com.android.tv.dvr.DvrStorageStatusManager;
+import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.data.SeriesRecording;
+import com.android.tv.dvr.provider.EpisodicProgramLoadTask;
+import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrAlreadyRecordedDialogFragment;
+import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrAlreadyScheduledDialogFragment;
+import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrChannelRecordDurationOptionDialogFragment;
+import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrChannelWatchConflictDialogFragment;
+import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrInsufficientSpaceErrorDialogFragment;
+import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrMissingStorageErrorDialogFragment;
+import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrNoFreeSpaceErrorDialogFragment;
+import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrProgramConflictDialogFragment;
+import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrScheduleDialogFragment;
+import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrSmallSizedStorageErrorDialogFragment;
+import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrStopRecordingDialogFragment;
+import com.android.tv.dvr.ui.browse.DvrBrowseActivity;
+import com.android.tv.dvr.ui.browse.DvrDetailsActivity;
+import com.android.tv.dvr.ui.list.DvrSchedulesActivity;
+import com.android.tv.dvr.ui.list.DvrSchedulesFragment;
+import com.android.tv.dvr.ui.list.DvrSeriesSchedulesFragment;
+import com.android.tv.util.ToastUtils;
+import com.android.tv.util.Utils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A helper class for DVR UI.
+ */
+@MainThread
+@TargetApi(Build.VERSION_CODES.N)
+public class DvrUiHelper {
+ private static String TAG = "DvrUiHelper";
+
+ private static ProgressDialog sProgressDialog = null;
+
+ /**
+ * Checks if the storage status is good for recording and shows error messages if needed.
+ *
+ * @param recordingRequestRunnable if the storage status is OK to record or users choose to
+ * perform the operation anyway, this Runnable will run.
+ */
+ public static void checkStorageStatusAndShowErrorMessage(Activity activity, String inputId,
+ Runnable recordingRequestRunnable) {
+ if (Utils.isBundledInput(inputId)) {
+ switch (TvApplication.getSingletons(activity).getDvrStorageStatusManager()
+ .getDvrStorageStatus()) {
+ case DvrStorageStatusManager.STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL:
+ showDvrSmallSizedStorageErrorDialog(activity);
+ return;
+ case DvrStorageStatusManager.STORAGE_STATUS_MISSING:
+ showDvrMissingStorageErrorDialog(activity);
+ return;
+ case DvrStorageStatusManager.STORAGE_STATUS_FREE_SPACE_INSUFFICIENT:
+ showDvrNoFreeSpaceErrorDialog(activity, recordingRequestRunnable);
+ return;
+ }
+ }
+ recordingRequestRunnable.run();
+ }
+
+ /**
+ * Shows the schedule dialog.
+ */
+ public static void showScheduleDialog(Activity activity, Program program,
+ boolean addCurrentProgramToSeries) {
+ if (SoftPreconditions.checkNotNull(program) == null) {
+ return;
+ }
+ Bundle args = new Bundle();
+ args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program);
+ args.putBoolean(DvrScheduleFragment.KEY_ADD_CURRENT_PROGRAM_TO_SERIES,
+ addCurrentProgramToSeries);
+ showDialogFragment(activity, new DvrScheduleDialogFragment(), args, true, true);
+ }
+
+ /**
+ * Shows the recording duration options dialog.
+ */
+ public static void showChannelRecordDurationOptions(Activity activity, Channel channel) {
+ if (SoftPreconditions.checkNotNull(channel) == null) {
+ return;
+ }
+ Bundle args = new Bundle();
+ args.putLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID, channel.getId());
+ showDialogFragment(activity, new DvrChannelRecordDurationOptionDialogFragment(), args);
+ }
+
+ /**
+ * Shows the dialog which says that the new schedule conflicts with others.
+ */
+ public static void showScheduleConflictDialog(Activity activity, Program program) {
+ if (program == null) {
+ return;
+ }
+ Bundle args = new Bundle();
+ args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program);
+ showDialogFragment(activity, new DvrProgramConflictDialogFragment(), args, false, true);
+ }
+
+ /**
+ * Shows the conflict dialog for the channel watching.
+ */
+ public static void showChannelWatchConflictDialog(MainActivity activity, Channel channel) {
+ if (channel == null) {
+ return;
+ }
+ Bundle args = new Bundle();
+ args.putLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID, channel.getId());
+ showDialogFragment(activity, new DvrChannelWatchConflictDialogFragment(), args);
+ }
+
+ /**
+ * Shows DVR insufficient space error dialog.
+ */
+ public static void showDvrInsufficientSpaceErrorDialog(MainActivity activity,
+ Set<String> failedScheduledRecordingInfoSet) {
+ Bundle args = new Bundle();
+ ArrayList<String> failedScheduledRecordingInfoArray =
+ new ArrayList<>(failedScheduledRecordingInfoSet);
+ args.putStringArrayList(DvrInsufficientSpaceErrorFragment.FAILED_SCHEDULED_RECORDING_INFOS,
+ failedScheduledRecordingInfoArray);
+ showDialogFragment(activity, new DvrInsufficientSpaceErrorDialogFragment(), args);
+ Utils.clearRecordingFailedReason(activity,
+ TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE);
+ Utils.clearFailedScheduledRecordingInfoSet(activity);
+ }
+
+ /**
+ * Shows DVR no free space error dialog.
+ *
+ * @param recordingRequestRunnable the recording request to be executed when users choose
+ * {@link DvrGuidedStepFragment#ACTION_RECORD_ANYWAY}.
+ */
+ public static void showDvrNoFreeSpaceErrorDialog(Activity activity,
+ Runnable recordingRequestRunnable) {
+ DvrHalfSizedDialogFragment fragment = new DvrNoFreeSpaceErrorDialogFragment();
+ fragment.setOnActionClickListener(new HalfSizedDialogFragment.OnActionClickListener() {
+ @Override
+ public void onActionClick(long actionId) {
+ if (actionId == DvrGuidedStepFragment.ACTION_RECORD_ANYWAY) {
+ recordingRequestRunnable.run();
+ } else if (actionId == DvrGuidedStepFragment.ACTION_DELETE_RECORDINGS) {
+ Intent intent = new Intent(activity, DvrBrowseActivity.class);
+ activity.startActivity(intent);
+ }
+ }
+ });
+ showDialogFragment(activity, fragment, null);
+ }
+
+ /**
+ * Shows DVR missing storage error dialog.
+ */
+ private static void showDvrMissingStorageErrorDialog(Activity activity) {
+ showDialogFragment(activity, new DvrMissingStorageErrorDialogFragment(), null);
+ }
+
+ /**
+ * Shows DVR small sized storage error dialog.
+ */
+ public static void showDvrSmallSizedStorageErrorDialog(Activity activity) {
+ showDialogFragment(activity, new DvrSmallSizedStorageErrorDialogFragment(), null);
+ }
+
+ /**
+ * Shows stop recording dialog.
+ */
+ public static void showStopRecordingDialog(Activity activity, long channelId, int reason,
+ HalfSizedDialogFragment.OnActionClickListener listener) {
+ Bundle args = new Bundle();
+ args.putLong(DvrHalfSizedDialogFragment.KEY_CHANNEL_ID, channelId);
+ args.putInt(DvrStopRecordingFragment.KEY_REASON, reason);
+ DvrHalfSizedDialogFragment fragment = new DvrStopRecordingDialogFragment();
+ fragment.setOnActionClickListener(listener);
+ showDialogFragment(activity, fragment, args);
+ }
+
+ /**
+ * Shows "already scheduled" dialog.
+ */
+ public static void showAlreadyScheduleDialog(Activity activity, Program program) {
+ if (program == null) {
+ return;
+ }
+ Bundle args = new Bundle();
+ args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program);
+ showDialogFragment(activity, new DvrAlreadyScheduledDialogFragment(), args, false, true);
+ }
+
+ /**
+ * Shows "already recorded" dialog.
+ */
+ public static void showAlreadyRecordedDialog(Activity activity, Program program) {
+ if (program == null) {
+ return;
+ }
+ Bundle args = new Bundle();
+ args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program);
+ showDialogFragment(activity, new DvrAlreadyRecordedDialogFragment(), args, false, true);
+ }
+
+ /**
+ * Handle the request of recording a current program. It will handle creating schedules and
+ * shows the proper dialog and toast message respectively for timed-recording and program
+ * recording cases.
+ *
+ * @param addProgramToSeries denotes whether the program to be recorded should be added into
+ * the series recording when users choose to record the entire series.
+ */
+ public static void requestRecordingCurrentProgram(Activity activity,
+ Channel channel, Program program, boolean addProgramToSeries) {
+ if (program == null) {
+ DvrUiHelper.showChannelRecordDurationOptions(activity, channel);
+ } else if (DvrUiHelper.handleCreateSchedule(activity, program, addProgramToSeries)) {
+ String msg = activity.getString(R.string.dvr_msg_current_program_scheduled,
+ program.getTitle(), Utils.toTimeString(program.getEndTimeUtcMillis(), false));
+ Toast.makeText(activity, msg, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ /**
+ * Handle the request of recording a future program. It will handle creating schedules and
+ * shows the proper toast message.
+ *
+ * @param addProgramToSeries denotes whether the program to be recorded should be added into
+ * the series recording when users choose to record the entire series.
+ */
+ public static void requestRecordingFutureProgram(Activity activity,
+ Program program, boolean addProgramToSeries) {
+ if (DvrUiHelper.handleCreateSchedule(activity, program, addProgramToSeries)) {
+ String msg = activity.getString(
+ R.string.dvr_msg_program_scheduled, program.getTitle());
+ ToastUtils.show(activity, msg, Toast.LENGTH_SHORT);
+ }
+ }
+
+ /**
+ * Handles the action to create the new schedule. It returns {@code true} if the schedule is
+ * added and there's no additional UI, otherwise {@code false}.
+ */
+ private static boolean handleCreateSchedule(Activity activity, Program program,
+ boolean addProgramToSeries) {
+ if (program == null) {
+ return false;
+ }
+ DvrManager dvrManager = TvApplication.getSingletons(activity).getDvrManager();
+ if (!program.isEpisodic()) {
+ // One time recording.
+ dvrManager.addSchedule(program);
+ if (!dvrManager.getConflictingSchedules(program).isEmpty()) {
+ DvrUiHelper.showScheduleConflictDialog(activity, program);
+ return false;
+ }
+ } else {
+ // Show recorded program rather than the schedule.
+ RecordedProgram recordedProgram = dvrManager.getRecordedProgram(program.getTitle(),
+ program.getSeasonNumber(), program.getEpisodeNumber());
+ if (recordedProgram != null) {
+ DvrUiHelper.showAlreadyRecordedDialog(activity, program);
+ return false;
+ }
+ ScheduledRecording duplicate = dvrManager.getScheduledRecording(program.getTitle(),
+ program.getSeasonNumber(), program.getEpisodeNumber());
+ if (duplicate != null
+ && (duplicate.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED
+ || duplicate.getState()
+ == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) {
+ DvrUiHelper.showAlreadyScheduleDialog(activity, program);
+ return false;
+ }
+ SeriesRecording seriesRecording = dvrManager.getSeriesRecording(program);
+ if (seriesRecording == null || seriesRecording.isStopped()) {
+ DvrUiHelper.showScheduleDialog(activity, program, addProgramToSeries);
+ return false;
+ } else {
+ // Just add the schedule.
+ dvrManager.addSchedule(program);
+ }
+ }
+ return true;
+ }
+
+ private static void showDialogFragment(Activity activity,
+ DvrHalfSizedDialogFragment dialogFragment, Bundle args) {
+ showDialogFragment(activity, dialogFragment, args, false, false);
+ }
+
+ private static void showDialogFragment(Activity activity,
+ DvrHalfSizedDialogFragment dialogFragment, Bundle args, boolean keepSidePanelHistory,
+ boolean keepProgramGuide) {
+ dialogFragment.setArguments(args);
+ if (activity instanceof MainActivity) {
+ ((MainActivity) activity).getOverlayManager()
+ .showDialogFragment(DvrHalfSizedDialogFragment.DIALOG_TAG, dialogFragment,
+ keepSidePanelHistory, keepProgramGuide);
+ } else {
+ dialogFragment.show(activity.getFragmentManager(),
+ DvrHalfSizedDialogFragment.DIALOG_TAG);
+ }
+ }
+
+ /**
+ * Checks whether channel watch conflict dialog is open or not.
+ */
+ public static boolean isChannelWatchConflictDialogShown(MainActivity activity) {
+ return activity.getOverlayManager().getCurrentDialog() instanceof
+ DvrChannelWatchConflictDialogFragment;
+ }
+
+ private static ScheduledRecording getEarliestScheduledRecording(List<ScheduledRecording>
+ recordings) {
+ ScheduledRecording earlistScheduledRecording = null;
+ if (!recordings.isEmpty()) {
+ Collections.sort(recordings,
+ ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR);
+ earlistScheduledRecording = recordings.get(0);
+ }
+ return earlistScheduledRecording;
+ }
+
+ /**
+ * Shows the schedules activity to resolve the tune conflict.
+ */
+ public static void startSchedulesActivityForTuneConflict(Context context, Channel channel) {
+ if (channel == null) {
+ return;
+ }
+ List<ScheduledRecording> conflicts = TvApplication.getSingletons(context).getDvrManager()
+ .getConflictingSchedulesForTune(channel.getId());
+ startSchedulesActivity(context, getEarliestScheduledRecording(conflicts));
+ }
+
+ /**
+ * Shows the schedules activity to resolve the one time recording conflict.
+ */
+ public static void startSchedulesActivityForOneTimeRecordingConflict(Context context,
+ List<ScheduledRecording> conflicts) {
+ startSchedulesActivity(context, getEarliestScheduledRecording(conflicts));
+ }
+
+ /**
+ * Shows the schedules activity with full schedule.
+ */
+ public static void startSchedulesActivity(Context context, ScheduledRecording
+ focusedScheduledRecording) {
+ Intent intent = new Intent(context, DvrSchedulesActivity.class);
+ intent.putExtra(DvrSchedulesActivity.KEY_SCHEDULES_TYPE,
+ DvrSchedulesActivity.TYPE_FULL_SCHEDULE);
+ if (focusedScheduledRecording != null) {
+ intent.putExtra(DvrSchedulesFragment.SCHEDULES_KEY_SCHEDULED_RECORDING,
+ focusedScheduledRecording);
+ }
+ context.startActivity(intent);
+ }
+
+ /**
+ * Shows the schedules activity for series recording.
+ */
+ public static void startSchedulesActivityForSeries(Context context,
+ SeriesRecording seriesRecording) {
+ Intent intent = new Intent(context, DvrSchedulesActivity.class);
+ intent.putExtra(DvrSchedulesActivity.KEY_SCHEDULES_TYPE,
+ DvrSchedulesActivity.TYPE_SERIES_SCHEDULE);
+ intent.putExtra(DvrSeriesSchedulesFragment.SERIES_SCHEDULES_KEY_SERIES_RECORDING,
+ seriesRecording);
+ context.startActivity(intent);
+ }
+
+ /**
+ * Shows the series settings activity.
+ *
+ * @param programs list of programs which belong to the series.
+ */
+ public static void startSeriesSettingsActivity(Context context, long seriesRecordingId,
+ @Nullable List<Program> programs, boolean removeEmptySeriesSchedule,
+ boolean isWindowTranslucent, boolean showViewScheduleOptionInDialog,
+ Program currentProgram) {
+ SeriesRecording series = TvApplication.getSingletons(context).getDvrDataManager()
+ .getSeriesRecording(seriesRecordingId);
+ if (series == null) {
+ return;
+ }
+ if (programs != null) {
+ startSeriesSettingsActivityInternal(context, seriesRecordingId, programs,
+ removeEmptySeriesSchedule, isWindowTranslucent,
+ showViewScheduleOptionInDialog, currentProgram);
+ } else {
+ EpisodicProgramLoadTask episodicProgramLoadTask =
+ new EpisodicProgramLoadTask(context, series) {
+ @Override
+ protected void onPostExecute(List<Program> loadedPrograms) {
+ sProgressDialog.dismiss();
+ sProgressDialog = null;
+ startSeriesSettingsActivityInternal(context, seriesRecordingId,
+ loadedPrograms == null ? Collections.EMPTY_LIST : loadedPrograms,
+ removeEmptySeriesSchedule, isWindowTranslucent,
+ showViewScheduleOptionInDialog, currentProgram);
+ }
+ }.setLoadCurrentProgram(true)
+ .setLoadDisallowedProgram(true)
+ .setLoadScheduledEpisode(true)
+ .setIgnoreChannelOption(true);
+ sProgressDialog = ProgressDialog.show(context, null, context.getString(
+ R.string.dvr_series_progress_message_reading_programs), true, true,
+ new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialogInterface) {
+ episodicProgramLoadTask.cancel(true);
+ sProgressDialog = null;
+ }
+ });
+ episodicProgramLoadTask.execute();
+ }
+ }
+
+ private static void startSeriesSettingsActivityInternal(Context context, long seriesRecordingId,
+ @NonNull List<Program> programs, boolean removeEmptySeriesSchedule,
+ boolean isWindowTranslucent, boolean showViewScheduleOptionInDialog,
+ Program currentProgram) {
+ SoftPreconditions.checkState(programs != null,
+ TAG, "Start series settings activity but programs is null");
+ Intent intent = new Intent(context, DvrSeriesSettingsActivity.class);
+ intent.putExtra(DvrSeriesSettingsActivity.SERIES_RECORDING_ID, seriesRecordingId);
+ BigArguments.reset();
+ BigArguments.setArgument(DvrSeriesSettingsActivity.PROGRAM_LIST, programs);
+ intent.putExtra(DvrSeriesSettingsActivity.REMOVE_EMPTY_SERIES_RECORDING,
+ removeEmptySeriesSchedule);
+ intent.putExtra(DvrSeriesSettingsActivity.IS_WINDOW_TRANSLUCENT, isWindowTranslucent);
+ intent.putExtra(DvrSeriesSettingsActivity.SHOW_VIEW_SCHEDULE_OPTION_IN_DIALOG,
+ showViewScheduleOptionInDialog);
+ intent.putExtra(DvrSeriesSettingsActivity.CURRENT_PROGRAM, currentProgram);
+ context.startActivity(intent);
+ }
+
+ /**
+ * Shows "series recording scheduled" dialog activity.
+ */
+ public static void StartSeriesScheduledDialogActivity(Context context,
+ SeriesRecording seriesRecording, boolean showViewScheduleOptionInDialog,
+ List<Program> programs) {
+ if (seriesRecording == null) {
+ return;
+ }
+ Intent intent = new Intent(context, DvrSeriesScheduledDialogActivity.class);
+ intent.putExtra(DvrSeriesScheduledDialogActivity.SERIES_RECORDING_ID,
+ seriesRecording.getId());
+ intent.putExtra(DvrSeriesScheduledDialogActivity.SHOW_VIEW_SCHEDULE_OPTION,
+ showViewScheduleOptionInDialog);
+ BigArguments.reset();
+ BigArguments.setArgument(DvrSeriesScheduledFragment.SERIES_SCHEDULED_KEY_PROGRAMS,
+ programs);
+ context.startActivity(intent);
+ }
+
+ /**
+ * Shows the details activity for the DVR items. The type of DVR items may be
+ * {@link ScheduledRecording}, {@link RecordedProgram}, or {@link SeriesRecording}.
+ */
+ public static void startDetailsActivity(Activity activity, Object dvrItem,
+ @Nullable ImageView imageView, boolean hideViewSchedule) {
+ if (dvrItem == null) {
+ return;
+ }
+ Intent intent = new Intent(activity, DvrDetailsActivity.class);
+ long recordingId;
+ int viewType;
+ if (dvrItem instanceof ScheduledRecording) {
+ ScheduledRecording schedule = (ScheduledRecording) dvrItem;
+ recordingId = schedule.getId();
+ if (schedule.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED) {
+ viewType = DvrDetailsActivity.SCHEDULED_RECORDING_VIEW;
+ } else if (schedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS) {
+ viewType = DvrDetailsActivity.CURRENT_RECORDING_VIEW;
+ } else {
+ return;
+ }
+ } else if (dvrItem instanceof RecordedProgram) {
+ recordingId = ((RecordedProgram) dvrItem).getId();
+ viewType = DvrDetailsActivity.RECORDED_PROGRAM_VIEW;
+ } else if (dvrItem instanceof SeriesRecording) {
+ recordingId = ((SeriesRecording) dvrItem).getId();
+ viewType = DvrDetailsActivity.SERIES_RECORDING_VIEW;
+ } else {
+ return;
+ }
+ intent.putExtra(DvrDetailsActivity.RECORDING_ID, recordingId);
+ intent.putExtra(DvrDetailsActivity.DETAILS_VIEW_TYPE, viewType);
+ intent.putExtra(DvrDetailsActivity.HIDE_VIEW_SCHEDULE, hideViewSchedule);
+ Bundle bundle = null;
+ if (imageView != null) {
+ bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, imageView,
+ DvrDetailsActivity.SHARED_ELEMENT_NAME).toBundle();
+ }
+ activity.startActivity(intent, bundle);
+ }
+
+ /**
+ * Shows the cancel all dialog for series schedules list.
+ */
+ public static void showCancelAllSeriesRecordingDialog(DvrSchedulesActivity activity,
+ SeriesRecording seriesRecording) {
+ DvrStopSeriesRecordingDialogFragment dvrStopSeriesRecordingDialogFragment =
+ new DvrStopSeriesRecordingDialogFragment();
+ Bundle arguments = new Bundle();
+ arguments.putParcelable(DvrStopSeriesRecordingFragment.KEY_SERIES_RECORDING,
+ seriesRecording);
+ dvrStopSeriesRecordingDialogFragment.setArguments(arguments);
+ dvrStopSeriesRecordingDialogFragment.show(activity.getFragmentManager(),
+ DvrStopSeriesRecordingDialogFragment.DIALOG_TAG);
+ }
+
+ /**
+ * Shows the series deletion activity.
+ */
+ public static void startSeriesDeletionActivity(Context context, long seriesRecordingId) {
+ Intent intent = new Intent(context, DvrSeriesDeletionActivity.class);
+ intent.putExtra(DvrSeriesDeletionActivity.SERIES_RECORDING_ID, seriesRecordingId);
+ context.startActivity(intent);
+ }
+
+ public static void showAddScheduleToast(Context context,
+ String title, long startTimeMs, long endTimeMs) {
+ String msg = (startTimeMs > System.currentTimeMillis()) ?
+ context.getString(R.string.dvr_msg_program_scheduled, title)
+ : context.getString(R.string.dvr_msg_current_program_scheduled, title,
+ Utils.toTimeString(endTimeMs, false));
+ Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
+ }
+} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/FadeBackground.java b/src/com/android/tv/dvr/ui/FadeBackground.java
new file mode 100644
index 00000000..4f06ebcf
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/FadeBackground.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tv.dvr.ui;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.transition.Transition;
+import android.transition.TransitionValues;
+import android.transition.Visibility;
+import android.util.AttributeSet;
+import android.view.ViewGroup;
+
+import com.android.tv.R;
+
+/**
+ * This transition fades in/out of the background of the view by changing the background color.
+ */
+public class FadeBackground extends Transition {
+ private final int mMode;
+
+ public FadeBackground(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FadeBackground);
+ mMode = a.getInt(R.styleable.FadeBackground_fadingMode, Visibility.MODE_IN);
+ a.recycle();
+ }
+
+ @Override
+ public void captureStartValues(TransitionValues transitionValues) { }
+
+ @Override
+ public void captureEndValues(TransitionValues transitionValues) { }
+
+ @Override
+ public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
+ TransitionValues endValues) {
+ if (startValues == null || endValues == null) {
+ return null;
+ }
+ Drawable background = endValues.view.getBackground();
+ if (background instanceof ColorDrawable) {
+ int color = ((ColorDrawable) background).getColor();
+ int transparentColor = Color.argb(0, Color.red(color), Color.green(color),
+ Color.blue(color));
+ return mMode == Visibility.MODE_OUT
+ ? ObjectAnimator.ofArgb(background, "color", transparentColor)
+ : ObjectAnimator.ofArgb(background, "color", transparentColor, color);
+ }
+ return null;
+ }
+}
diff --git a/src/com/android/tv/dvr/ui/HalfSizedDialogFragment.java b/src/com/android/tv/dvr/ui/HalfSizedDialogFragment.java
deleted file mode 100644
index d320816e..00000000
--- a/src/com/android/tv/dvr/ui/HalfSizedDialogFragment.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.dvr.ui;
-
-import android.content.DialogInterface;
-import android.os.Bundle;
-import android.os.Handler;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.tv.R;
-import com.android.tv.dialog.SafeDismissDialogFragment;
-
-import java.util.concurrent.TimeUnit;
-
-public class HalfSizedDialogFragment extends SafeDismissDialogFragment {
- public static final String DIALOG_TAG = HalfSizedDialogFragment.class.getSimpleName();
- public static final String TRACKER_LABEL = "Half sized dialog";
-
- private static final long AUTO_DISMISS_TIME_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
-
- private OnActionClickListener mOnActionClickListener;
-
- private Handler mHandler = new Handler();
- private Runnable mAutoDismisser = new Runnable() {
- @Override
- public void run() {
- dismiss();
- }
- };
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- return inflater.inflate(R.layout.halfsized_dialog, container, false);
- }
-
- @Override
- public void onStart() {
- super.onStart();
- getDialog().setOnKeyListener(new DialogInterface.OnKeyListener() {
- public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent keyEvent) {
- mHandler.removeCallbacks(mAutoDismisser);
- mHandler.postDelayed(mAutoDismisser, AUTO_DISMISS_TIME_THRESHOLD_MS);
- return false;
- }
- });
- mHandler.postDelayed(mAutoDismisser, AUTO_DISMISS_TIME_THRESHOLD_MS);
- }
-
- @Override
- public void onPause() {
- super.onPause();
- if (mOnActionClickListener != null) {
- // Dismisses the dialog to prevent the callback being forgotten during
- // fragment re-creating.
- dismiss();
- }
- }
-
- @Override
- public void onStop() {
- super.onStop();
- mHandler.removeCallbacks(mAutoDismisser);
- }
-
- @Override
- public int getTheme() {
- return R.style.Theme_TV_dialog_HalfSizedDialog;
- }
-
- @Override
- public String getTrackerLabel() {
- return TRACKER_LABEL;
- }
-
- /**
- * Sets {@link OnActionClickListener} for the dialog fragment. If listener is set, the dialog
- * will be automatically closed when it's paused to prevent the fragment being re-created by
- * the framework, which will result the listener being forgotten.
- */
- public void setOnActionClickListener(OnActionClickListener listener) {
- mOnActionClickListener = listener;
- }
-
- /**
- * Returns {@link OnActionClickListener} for sub-classes or any inner fragments.
- */
- protected OnActionClickListener getOnActionClickListener() {
- return mOnActionClickListener;
- }
-
- /**
- * An interface to provide callbacks for half-sized dialogs. Subclasses or inner fragments
- * should invoke {@link OnActionClickListener#onActionClick(long)} and provide the identifier
- * of the action user clicked.
- */
- public interface OnActionClickListener {
- void onActionClick(long actionId);
- }
-} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/SortedArrayAdapter.java b/src/com/android/tv/dvr/ui/SortedArrayAdapter.java
index 393a5ff3..8c0af9ed 100644
--- a/src/com/android/tv/dvr/ui/SortedArrayAdapter.java
+++ b/src/com/android/tv/dvr/ui/SortedArrayAdapter.java
@@ -20,11 +20,15 @@ import android.support.annotation.VisibleForTesting;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.PresenterSelector;
+import com.android.tv.common.SoftPreconditions;
+
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* Keeps a set of items sorted
@@ -35,16 +39,18 @@ public abstract class SortedArrayAdapter<T> extends ArrayObjectAdapter {
private final Comparator<T> mComparator;
private final int mMaxItemCount;
private int mExtraItemCount;
+ private final Set<Long> mIds = new HashSet<>();
- SortedArrayAdapter(PresenterSelector presenterSelector, Comparator<T> comparator) {
+ public SortedArrayAdapter(PresenterSelector presenterSelector, Comparator<T> comparator) {
this(presenterSelector, comparator, Integer.MAX_VALUE);
}
- SortedArrayAdapter(PresenterSelector presenterSelector, Comparator<T> comparator,
+ public SortedArrayAdapter(PresenterSelector presenterSelector, Comparator<T> comparator,
int maxItemCount) {
super(presenterSelector);
mComparator = comparator;
mMaxItemCount = maxItemCount;
+ setHasStableIds(true);
}
/**
@@ -56,7 +62,12 @@ public abstract class SortedArrayAdapter<T> extends ArrayObjectAdapter {
final void setInitialItems(List<T> items) {
List<T> itemsCopy = new ArrayList<>(items);
Collections.sort(itemsCopy, mComparator);
- addAll(0, itemsCopy.subList(0, Math.min(mMaxItemCount, itemsCopy.size())));
+ for (T item : itemsCopy) {
+ add(item, true);
+ if (size() == mMaxItemCount) {
+ break;
+ }
+ }
}
/**
@@ -82,6 +93,9 @@ public abstract class SortedArrayAdapter<T> extends ArrayObjectAdapter {
* the end to save search time.
*/
public final void add(T item, boolean insertToEnd) {
+ long newItemId = getId(item);
+ SoftPreconditions.checkState(!mIds.contains(newItemId));
+ mIds.add(newItemId);
int i;
if (insertToEnd) {
i = findInsertPosition(item);
@@ -89,8 +103,9 @@ public abstract class SortedArrayAdapter<T> extends ArrayObjectAdapter {
i = findInsertPositionBinary(item);
}
super.add(i, item);
- if (size() > mMaxItemCount + mExtraItemCount) {
- removeItems(mMaxItemCount, size() - mMaxItemCount - mExtraItemCount);
+ if (mMaxItemCount < Integer.MAX_VALUE && size() > mMaxItemCount + mExtraItemCount) {
+ Object removedItem = get(mMaxItemCount);
+ remove(removedItem);
}
}
@@ -100,48 +115,97 @@ public abstract class SortedArrayAdapter<T> extends ArrayObjectAdapter {
* They will be presented in their insertion order.
*/
public int addExtraItem(T item) {
+ long newItemId = getId(item);
+ SoftPreconditions.checkState(!mIds.contains(newItemId));
+ mIds.add(newItemId);
super.add(item);
return ++mExtraItemCount;
}
+ @Override
+ public boolean remove(Object item) {
+ return removeWithId((T) item);
+ }
+
/**
* Removes an item which has the same ID as {@code item}.
*/
public boolean removeWithId(T item) {
- int index = indexWithTypeAndId(item);
- return index >= 0 && index < size() && remove(get(index));
+ int index = indexWithId(item);
+ return index >= 0 && index < size() && removeItems(index, 1) == 1;
+ }
+
+ @Override
+ public int removeItems(int position, int count) {
+ int upperBound = Math.min(position + count, size());
+ for (int i = position; i < upperBound; i++) {
+ mIds.remove(getId((T) get(i)));
+ }
+ if (upperBound > size() - mExtraItemCount) {
+ mExtraItemCount -= upperBound - Math.max(size() - mExtraItemCount, position);
+ }
+ return super.removeItems(position, count);
+ }
+
+ @Override
+ public void replace(int position, Object item) {
+ boolean wasExtra = position >= size() - mExtraItemCount;
+ removeItems(position, 1);
+ if (!wasExtra) {
+ add(item);
+ } else {
+ addExtraItem((T) item);
+ }
+ }
+
+ @Override
+ public void clear() {
+ mIds.clear();
+ super.clear();
}
/**
- * Change an item in the list.
+ * Changes an item in the list.
* @param item The item to change.
*/
public final void change(T item) {
- int oldIndex = indexWithTypeAndId(item);
+ int oldIndex = indexWithId(item);
if (oldIndex != -1) {
T old = (T) get(oldIndex);
if (mComparator.compare(old, item) == 0) {
replace(oldIndex, item);
return;
}
- removeItems(oldIndex, 1);
+ remove(old);
}
add(item);
}
/**
+ * Checks whether the item is in the list.
+ */
+ public final boolean contains(T item) {
+ return indexWithId(item) != -1;
+ }
+
+ @Override
+ public long getId(int position) {
+ return getId((T) get(position));
+ }
+
+ /**
* Returns the id of the the given {@code item}, which will be used in {@link #change} to
* decide if the given item is already existed in the adapter.
*
* The id must be stable.
*/
- abstract long getId(T item);
+ protected abstract long getId(T item);
- private int indexWithTypeAndId(T item) {
+ private int indexWithId(T item) {
long id = getId(item);
for (int i = 0; i < size() - mExtraItemCount; i++) {
T r = (T) get(i);
- if (r.getClass() == item.getClass() && getId(r) == id) {
+ if (getId(r) == id) {
return i;
}
}
diff --git a/src/com/android/tv/dvr/ui/ActionPresenterSelector.java b/src/com/android/tv/dvr/ui/browse/ActionPresenterSelector.java
index 8b8cd5c5..38a78f5d 100644
--- a/src/com/android/tv/dvr/ui/ActionPresenterSelector.java
+++ b/src/com/android/tv/dvr/ui/browse/ActionPresenterSelector.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
import android.graphics.drawable.Drawable;
import android.support.v17.leanback.R;
@@ -110,11 +110,7 @@ class ActionPresenterSelector extends PresenterSelector {
.getDimensionPixelSize(R.dimen.lb_action_padding_horizontal);
vh.view.setPaddingRelative(padding, 0, padding, 0);
}
- if (vh.mLayoutDirection == View.LAYOUT_DIRECTION_RTL) {
- vh.mButton.setCompoundDrawablesWithIntrinsicBounds(null, null, icon, null);
- } else {
- vh.mButton.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
- }
+ vh.mButton.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null);
CharSequence line1 = action.getLabel1();
CharSequence line2 = action.getLabel2();
@@ -130,7 +126,7 @@ class ActionPresenterSelector extends PresenterSelector {
@Override
public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
ActionViewHolder vh = (ActionViewHolder) viewHolder;
- vh.mButton.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
+ vh.mButton.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, null, null);
vh.view.setPadding(0, 0, 0, 0);
vh.mAction = null;
}
diff --git a/src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java
new file mode 100644
index 00000000..c8f6a03f
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.tv.dvr.ui.browse;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.OnActionClickedListener;
+import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+
+import com.android.tv.R;
+import com.android.tv.TvApplication;
+import com.android.tv.dialog.HalfSizedDialogFragment;
+import com.android.tv.dvr.DvrDataManager;
+import com.android.tv.dvr.DvrManager;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.ui.DvrStopRecordingFragment;
+import com.android.tv.dvr.ui.DvrUiHelper;
+
+/**
+ * {@link RecordingDetailsFragment} for current recording in DVR.
+ */
+public class CurrentRecordingDetailsFragment extends RecordingDetailsFragment {
+ private static final int ACTION_STOP_RECORDING = 1;
+
+ private DvrDataManager mDvrDataManger;
+ private final DvrDataManager.ScheduledRecordingListener mScheduledRecordingListener =
+ new DvrDataManager.ScheduledRecordingListener() {
+ @Override
+ public void onScheduledRecordingAdded(ScheduledRecording... schedules) { }
+
+ @Override
+ public void onScheduledRecordingRemoved(ScheduledRecording... schedules) {
+ for (ScheduledRecording schedule : schedules) {
+ if (schedule.getId() == getRecording().getId()) {
+ getActivity().finish();
+ return;
+ }
+ }
+ }
+
+ @Override
+ public void onScheduledRecordingStatusChanged(ScheduledRecording... schedules) {
+ for (ScheduledRecording schedule : schedules) {
+ if (schedule.getId() == getRecording().getId()
+ && schedule.getState()
+ != ScheduledRecording.STATE_RECORDING_IN_PROGRESS) {
+ getActivity().finish();
+ return;
+ }
+ }
+ }
+ };
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ mDvrDataManger = TvApplication.getSingletons(context).getDvrDataManager();
+ mDvrDataManger.addScheduledRecordingListener(mScheduledRecordingListener);
+ }
+
+ @Override
+ protected SparseArrayObjectAdapter onCreateActionsAdapter() {
+ SparseArrayObjectAdapter adapter =
+ new SparseArrayObjectAdapter(new ActionPresenterSelector());
+ Resources res = getResources();
+ adapter.set(ACTION_STOP_RECORDING, new Action(ACTION_STOP_RECORDING,
+ res.getString(R.string.epg_dvr_dialog_message_stop_recording), null,
+ res.getDrawable(R.drawable.lb_ic_stop)));
+ return adapter;
+ }
+
+ @Override
+ protected OnActionClickedListener onCreateOnActionClickedListener() {
+ return new OnActionClickedListener() {
+ @Override
+ public void onActionClicked(Action action) {
+ if (action.getId() == ACTION_STOP_RECORDING) {
+ DvrUiHelper.showStopRecordingDialog(getActivity(),
+ getRecording().getChannelId(),
+ DvrStopRecordingFragment.REASON_USER_STOP,
+ new HalfSizedDialogFragment.OnActionClickListener() {
+ @Override
+ public void onActionClick(long actionId) {
+ if (actionId == DvrStopRecordingFragment.ACTION_STOP) {
+ DvrManager dvrManager =
+ TvApplication.getSingletons(getContext())
+ .getDvrManager();
+ dvrManager.stopRecording(getRecording());
+ getActivity().finish();
+ }
+ }
+ });
+ }
+ }
+ };
+ }
+
+ @Override
+ public void onDetach() {
+ if (mDvrDataManger != null) {
+ mDvrDataManger.removeScheduledRecordingListener(mScheduledRecordingListener);
+ }
+ super.onDetach();
+ }
+}
diff --git a/src/com/android/tv/dvr/ui/DetailsContent.java b/src/com/android/tv/dvr/ui/browse/DetailsContent.java
index 19521fca..b43d1f12 100644
--- a/src/com/android/tv/dvr/ui/DetailsContent.java
+++ b/src/com/android/tv/dvr/ui/browse/DetailsContent.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
import android.media.tv.TvContract;
import android.support.annotation.Nullable;
@@ -26,7 +26,7 @@ import com.android.tv.data.Channel;
/**
* A class for details content.
*/
-public class DetailsContent {
+class DetailsContent {
/** Constant for invalid time. */
public static final long INVALID_TIME = -1;
diff --git a/src/com/android/tv/dvr/ui/DetailsContentPresenter.java b/src/com/android/tv/dvr/ui/browse/DetailsContentPresenter.java
index 175f05bc..a2e3fe16 100644
--- a/src/com/android/tv/dvr/ui/DetailsContentPresenter.java
+++ b/src/com/android/tv/dvr/ui/browse/DetailsContentPresenter.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
import android.app.Activity;
import android.animation.Animator;
@@ -38,13 +38,14 @@ import com.android.tv.util.Utils;
/**
* An {@link Presenter} for rendering a detailed description of an DVR item.
- * Typically this Presenter will be used in a {@link DetailsOverviewRowPresenter}.
+ * Typically this Presenter will be used in a
+ * {@link android.support.v17.leanback.widget.DetailsOverviewRowPresenter}.
* Most codes of this class is originated from
* {@link android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter}.
* The latter class are re-used to provide a customized version of
* {@link android.support.v17.leanback.widget.DetailsOverviewRow}.
*/
-public class DetailsContentPresenter extends Presenter {
+class DetailsContentPresenter extends Presenter {
/**
* The ViewHolder for the {@link DetailsContentPresenter}.
*/
@@ -113,6 +114,20 @@ public class DetailsContentPresenter extends Presenter {
public ViewHolder(final View view) {
super(view);
+ view.addOnAttachStateChangeListener(
+ new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ // In case predraw listener was removed in detach, make sure
+ // we have the proper layout.
+ addPreDrawListener();
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ removePreDrawListener();
+ }
+ });
mTitle = (TextView) view.findViewById(R.id.dvr_details_description_title);
mSubtitle = (TextView) view.findViewById(R.id.dvr_details_description_subtitle);
mBody = (TextView) view.findViewById(R.id.dvr_details_description_body);
@@ -276,22 +291,6 @@ public class DetailsContentPresenter extends Presenter {
@Override
public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { }
- @Override
- public void onViewAttachedToWindow(Presenter.ViewHolder holder) {
- // In case predraw listener was removed in detach, make sure
- // we have the proper layout.
- ViewHolder vh = (ViewHolder) holder;
- vh.addPreDrawListener();
- super.onViewAttachedToWindow(holder);
- }
-
- @Override
- public void onViewDetachedFromWindow(Presenter.ViewHolder holder) {
- ViewHolder vh = (ViewHolder) holder;
- vh.removePreDrawListener();
- super.onViewDetachedFromWindow(holder);
- }
-
private void setTopMargin(View view, int topMargin) {
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
lp.topMargin = topMargin;
diff --git a/src/com/android/tv/dvr/ui/DetailsViewBackgroundHelper.java b/src/com/android/tv/dvr/ui/browse/DetailsViewBackgroundHelper.java
index 6714ecd3..82fe9ce3 100644
--- a/src/com/android/tv/dvr/ui/DetailsViewBackgroundHelper.java
+++ b/src/com/android/tv/dvr/ui/browse/DetailsViewBackgroundHelper.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
import android.app.Activity;
import android.graphics.drawable.BitmapDrawable;
@@ -26,7 +26,7 @@ import android.support.v17.leanback.app.BackgroundManager;
/**
* The Background Helper.
*/
-public class DetailsViewBackgroundHelper {
+class DetailsViewBackgroundHelper {
// Background delay serves to avoid kicking off expensive bitmap loading
// in case multiple backgrounds are set in quick succession.
private static final int SET_BACKGROUND_DELAY_MS = 100;
diff --git a/src/com/android/tv/dvr/ui/DvrActivity.java b/src/com/android/tv/dvr/ui/browse/DvrBrowseActivity.java
index 45fb1cf1..2b3dcb25 100644
--- a/src/com/android/tv/dvr/ui/DvrActivity.java
+++ b/src/com/android/tv/dvr/ui/browse/DvrBrowseActivity.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
import android.app.Activity;
import android.os.Bundle;
@@ -25,11 +25,11 @@ import com.android.tv.TvApplication;
/**
* {@link android.app.Activity} for DVR UI.
*/
-public class DvrActivity extends Activity {
+public class DvrBrowseActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
TvApplication.setCurrentRunningProcess(this, true);
super.onCreate(savedInstanceState);
setContentView(R.layout.dvr_main);
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/DvrBrowseFragment.java b/src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java
index a6dd31d1..803d1017 100644
--- a/src/com/android/tv/dvr/ui/DvrBrowseFragment.java
+++ b/src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java
@@ -14,10 +14,9 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
import android.content.Context;
-import android.media.tv.TvInputManager.TvInputCallback;
import android.os.Bundle;
import android.os.Handler;
import android.support.v17.leanback.app.BrowseFragment;
@@ -25,11 +24,11 @@ import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.ClassPresenterSelector;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.ListRowPresenter;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.TitleViewAdapter;
-import android.text.TextUtils;
import android.util.Log;
+import android.view.View;
+import android.view.ViewTreeObserver.OnGlobalFocusChangeListener;
import com.android.tv.ApplicationSingletons;
import com.android.tv.R;
@@ -42,9 +41,10 @@ import com.android.tv.dvr.DvrDataManager.RecordedProgramListener;
import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
import com.android.tv.dvr.DvrDataManager.SeriesRecordingListener;
import com.android.tv.dvr.DvrScheduleManager;
-import com.android.tv.dvr.RecordedProgram;
-import com.android.tv.dvr.ScheduledRecording;
-import com.android.tv.dvr.SeriesRecording;
+import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.data.SeriesRecording;
+import com.android.tv.dvr.ui.SortedArrayAdapter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -79,6 +79,20 @@ public class DvrBrowseFragment extends BrowseFragment implements
private ClassPresenterSelector mPresenterSelector;
private final HashMap<String, RecordedProgram> mSeriesId2LatestProgram = new HashMap<>();
private final Handler mHandler = new Handler();
+ private final OnGlobalFocusChangeListener mOnGlobalFocusChangeListener =
+ new OnGlobalFocusChangeListener() {
+ @Override
+ public void onGlobalFocusChanged(View oldFocus, View newFocus) {
+ if (oldFocus instanceof RecordingCardView) {
+ ((RecordingCardView) oldFocus).expandTitle(false, true);
+ }
+ if (newFocus instanceof RecordingCardView) {
+ // If the header transition is ongoing, expand cards immediately without
+ // animation to make a smooth transition.
+ ((RecordingCardView) newFocus).expandTitle(true, !isInHeadersTransition());
+ }
+ }
+ };
private final Comparator<Object> RECORDED_PROGRAM_COMPARATOR = new Comparator<Object>() {
@Override
@@ -128,7 +142,7 @@ public class DvrBrowseFragment extends BrowseFragment implements
public void onConflictStateChange(boolean conflict, ScheduledRecording... schedules) {
if (mScheduleAdapter != null) {
for (ScheduledRecording schedule : schedules) {
- onScheduledRecordingStatusChanged(schedule);
+ onScheduledRecordingConflictStatusChanged(schedule);
}
}
}
@@ -154,16 +168,12 @@ public class DvrBrowseFragment extends BrowseFragment implements
new ScheduledRecordingPresenter(context))
.addClassPresenter(RecordedProgram.class, new RecordedProgramPresenter(context))
.addClassPresenter(SeriesRecording.class, new SeriesRecordingPresenter(context))
- .addClassPresenter(FullScheduleCardHolder.class, new FullSchedulesCardPresenter());
+ .addClassPresenter(FullScheduleCardHolder.class,
+ new FullSchedulesCardPresenter(context));
mGenreLabels = new ArrayList<>(Arrays.asList(GenreItems.getLabels(context)));
mGenreLabels.add(getString(R.string.dvr_main_others));
- setupUiElements();
- setupAdapters();
- mDvrScheudleManager.addOnConflictStateChangeListener(mOnConflictStateChangeListener);
- prepareEntranceTransition();
- if (mDvrDataManager.isInitialized()) {
- startEntranceTransition();
- } else {
+ prepareUiElements();
+ if (!startBrowseIfDvrInitialized()) {
if (!mDvrDataManager.isDvrScheduleLoadFinished()) {
mDvrDataManager.addDvrScheduleLoadFinishedListener(this);
}
@@ -174,6 +184,19 @@ public class DvrBrowseFragment extends BrowseFragment implements
}
@Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ view.getViewTreeObserver().addOnGlobalFocusChangeListener(mOnGlobalFocusChangeListener);
+ }
+
+ @Override
+ public void onDestroyView() {
+ getView().getViewTreeObserver()
+ .removeOnGlobalFocusChangeListener(mOnGlobalFocusChangeListener);
+ super.onDestroyView();
+ }
+
+ @Override
public void onDestroy() {
if (DEBUG) Log.d(TAG, "onDestroy");
mHandler.removeCallbacks(mUpdateRowsRunnable);
@@ -195,25 +218,13 @@ public class DvrBrowseFragment extends BrowseFragment implements
@Override
public void onDvrScheduleLoadFinished() {
- List<ScheduledRecording> scheduledRecordings = mDvrDataManager.getAllScheduledRecordings();
- onScheduledRecordingAdded(ScheduledRecording.toArray(scheduledRecordings));
- List<SeriesRecording> seriesRecordings = mDvrDataManager.getSeriesRecordings();
- onSeriesRecordingAdded(SeriesRecording.toArray(seriesRecordings));
- if (mDvrDataManager.isInitialized()) {
- startEntranceTransition();
- }
+ startBrowseIfDvrInitialized();
mDvrDataManager.removeDvrScheduleLoadFinishedListener(this);
}
@Override
public void onRecordedProgramLoadFinished() {
- for (RecordedProgram recordedProgram : mDvrDataManager.getRecordedPrograms()) {
- handleRecordedProgramAdded(recordedProgram, true);
- }
- updateRows();
- if (mDvrDataManager.isInitialized()) {
- startEntranceTransition();
- }
+ startBrowseIfDvrInitialized();
mDvrDataManager.removeRecordedProgramLoadFinishedListener(this);
}
@@ -270,6 +281,18 @@ public class DvrBrowseFragment extends BrowseFragment implements
}
}
+ private void onScheduledRecordingConflictStatusChanged(ScheduledRecording... schedules) {
+ for (ScheduledRecording schedule : schedules) {
+ if (needToShowScheduledRecording(schedule)) {
+ if (mScheduleAdapter.contains(schedule)) {
+ mScheduleAdapter.change(schedule);
+ }
+ } else {
+ mScheduleAdapter.removeWithId(schedule);
+ }
+ }
+ }
+
@Override
public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) {
handleSeriesRecordingsAdded(Arrays.asList(seriesRecordings));
@@ -295,44 +318,53 @@ public class DvrBrowseFragment extends BrowseFragment implements
super.showTitle(flags);
}
- private void setupUiElements() {
+ private void prepareUiElements() {
setBadgeDrawable(getActivity().getDrawable(R.drawable.ic_dvr_badge));
setHeadersState(HEADERS_ENABLED);
setHeadersTransitionOnBackEnabled(false);
setBrandColor(getResources().getColor(R.color.program_guide_side_panel_background, null));
+ mRowsAdapter = new ArrayObjectAdapter(new DvrListRowPresenter(getContext()));
+ setAdapter(mRowsAdapter);
+ prepareEntranceTransition();
}
- private void setupAdapters() {
- mRecentAdapter = new RecordedProgramAdapter(MAX_RECENT_ITEM_COUNT);
- mScheduleAdapter = new ScheduleAdapter(MAX_SCHEDULED_ITEM_COUNT);
- mSeriesAdapter = new SeriesAdapter();
- for (int i = 0; i < mGenreAdapters.length; i++) {
- mGenreAdapters[i] = new RecordedProgramAdapter();
- }
- // Schedule Recordings.
- List<ScheduledRecording> schedules = mDvrDataManager.getAllScheduledRecordings();
- onScheduledRecordingAdded(ScheduledRecording.toArray(schedules));
- mScheduleAdapter.addExtraItem(FullScheduleCardHolder.FULL_SCHEDULE_CARD_HOLDER);
- // Recorded Programs.
- for (RecordedProgram recordedProgram : mDvrDataManager.getRecordedPrograms()) {
- handleRecordedProgramAdded(recordedProgram, false);
- }
- // Series Recordings. Series recordings should be added after recorded programs, because
- // we build series recordings' latest program information while adding recorded programs.
- List<SeriesRecording> recordings = mDvrDataManager.getSeriesRecordings();
- handleSeriesRecordingsAdded(recordings);
- mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
- mRecentRow = new ListRow(new HeaderItem(
- getString(R.string.dvr_main_recent)), mRecentAdapter);
- mRowsAdapter.add(new ListRow(new HeaderItem(
- getString(R.string.dvr_main_scheduled)), mScheduleAdapter));
- mSeriesRow = new ListRow(new HeaderItem(
- getString(R.string.dvr_main_series)), mSeriesAdapter);
- updateRows();
- mDvrDataManager.addRecordedProgramListener(this);
- mDvrDataManager.addScheduledRecordingListener(this);
- mDvrDataManager.addSeriesRecordingListener(this);
- setAdapter(mRowsAdapter);
+ private boolean startBrowseIfDvrInitialized() {
+ if (mDvrDataManager.isInitialized()) {
+ // Setup rows
+ mRecentAdapter = new RecordedProgramAdapter(MAX_RECENT_ITEM_COUNT);
+ mScheduleAdapter = new ScheduleAdapter(MAX_SCHEDULED_ITEM_COUNT);
+ mSeriesAdapter = new SeriesAdapter();
+ for (int i = 0; i < mGenreAdapters.length; i++) {
+ mGenreAdapters[i] = new RecordedProgramAdapter();
+ }
+ // Schedule Recordings.
+ List<ScheduledRecording> schedules = mDvrDataManager.getAllScheduledRecordings();
+ onScheduledRecordingAdded(ScheduledRecording.toArray(schedules));
+ mScheduleAdapter.addExtraItem(FullScheduleCardHolder.FULL_SCHEDULE_CARD_HOLDER);
+ // Recorded Programs.
+ for (RecordedProgram recordedProgram : mDvrDataManager.getRecordedPrograms()) {
+ handleRecordedProgramAdded(recordedProgram, false);
+ }
+ // Series Recordings. Series recordings should be added after recorded programs, because
+ // we build series recordings' latest program information while adding recorded programs.
+ List<SeriesRecording> recordings = mDvrDataManager.getSeriesRecordings();
+ handleSeriesRecordingsAdded(recordings);
+ mRecentRow = new ListRow(new HeaderItem(
+ getString(R.string.dvr_main_recent)), mRecentAdapter);
+ mRowsAdapter.add(new ListRow(new HeaderItem(
+ getString(R.string.dvr_main_scheduled)), mScheduleAdapter));
+ mSeriesRow = new ListRow(new HeaderItem(
+ getString(R.string.dvr_main_series)), mSeriesAdapter);
+ updateRows();
+ // Initialize listeners
+ mDvrDataManager.addRecordedProgramListener(this);
+ mDvrDataManager.addScheduledRecordingListener(this);
+ mDvrDataManager.addSeriesRecordingListener(this);
+ mDvrScheudleManager.addOnConflictStateChangeListener(mOnConflictStateChangeListener);
+ startEntranceTransition();
+ return true;
+ }
+ return false;
}
private void handleRecordedProgramAdded(RecordedProgram recordedProgram,
@@ -589,10 +621,11 @@ public class DvrBrowseFragment extends BrowseFragment implements
@Override
public long getId(Object item) {
+ // We takes the inverse number for the ID of recorded programs to make the ID stable.
if (item instanceof SeriesRecording) {
return ((SeriesRecording) item).getId();
} else if (item instanceof RecordedProgram) {
- return ((RecordedProgram) item).getId();
+ return -((RecordedProgram) item).getId() - 1;
} else {
return -1;
}
diff --git a/src/com/android/tv/dvr/ui/DvrDetailsActivity.java b/src/com/android/tv/dvr/ui/browse/DvrDetailsActivity.java
index 806c775c..30c81e83 100644
--- a/src/com/android/tv/dvr/ui/DvrDetailsActivity.java
+++ b/src/com/android/tv/dvr/ui/browse/DvrDetailsActivity.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
import android.app.Activity;
import android.os.Bundle;
diff --git a/src/com/android/tv/dvr/ui/DvrDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java
index 21f9c4b4..4d3698ef 100644
--- a/src/com/android/tv/dvr/ui/DvrDetailsFragment.java
+++ b/src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
import android.content.Context;
import android.content.Intent;
@@ -48,8 +48,8 @@ import com.android.tv.data.BaseProgram;
import com.android.tv.data.Channel;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.dialog.PinDialogFragment;
-import com.android.tv.dvr.DvrPlaybackActivity;
-import com.android.tv.dvr.RecordedProgram;
+import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.dvr.ui.playback.DvrPlaybackActivity;
import com.android.tv.parental.ParentalControlSettings;
import com.android.tv.util.ImageLoader;
import com.android.tv.util.ToastUtils;
diff --git a/src/com/android/tv/dvr/ui/DvrItemPresenter.java b/src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java
index 339e5d2f..317b6af3 100644
--- a/src/com/android/tv/dvr/ui/DvrItemPresenter.java
+++ b/src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
import android.app.Activity;
import android.support.annotation.CallSuper;
@@ -22,16 +22,17 @@ import android.support.v17.leanback.widget.Presenter;
import android.view.View;
import android.view.View.OnClickListener;
-import com.android.tv.dvr.DvrUiHelper;
+import com.android.tv.dvr.ui.DvrUiHelper;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.Set;
/**
* An abstract class to present DVR items in {@link RecordingCardView}, which is mainly used in
- * {@link DvrBrowseFragment}. DVR items might include: {@link ScheduledRecording},
- * {@link RecordedProgram}, and {@link SeriesRecording}.
+ * {@link DvrBrowseFragment}. DVR items might include:
+ * {@link com.android.tv.dvr.data.ScheduledRecording},
+ * {@link com.android.tv.dvr.data.RecordedProgram}, and
+ * {@link com.android.tv.dvr.data.SeriesRecording}.
*/
public abstract class DvrItemPresenter extends Presenter {
private final Set<ViewHolder> mBoundViewHolders = new HashSet<>();
@@ -49,6 +50,8 @@ public abstract class DvrItemPresenter extends Presenter {
@CallSuper
public void onUnbindViewHolder(ViewHolder viewHolder) {
mBoundViewHolders.remove(viewHolder);
+ viewHolder.view.setTag(null);
+ viewHolder.view.setOnClickListener(null);
}
/**
diff --git a/src/com/android/tv/dvr/ui/browse/DvrListRowPresenter.java b/src/com/android/tv/dvr/ui/browse/DvrListRowPresenter.java
new file mode 100644
index 00000000..37a72eaf
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/browse/DvrListRowPresenter.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tv.dvr.ui.browse;
+
+import android.content.Context;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.view.ViewGroup;
+
+import com.android.tv.R;
+
+/** A list row presenter to display expand/fold card views list. */
+public class DvrListRowPresenter extends ListRowPresenter {
+ public DvrListRowPresenter(Context context) {
+ super();
+ setRowHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
+ setExpandedRowHeight(
+ context.getResources()
+ .getDimensionPixelSize(R.dimen.dvr_library_expanded_row_height));
+ }
+}
diff --git a/src/com/android/tv/dvr/ui/FullScheduleCardHolder.java b/src/com/android/tv/dvr/ui/browse/FullScheduleCardHolder.java
index d4d4d8ab..311137a9 100644
--- a/src/com/android/tv/dvr/ui/FullScheduleCardHolder.java
+++ b/src/com/android/tv/dvr/ui/browse/FullScheduleCardHolder.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
/**
* Special object for schedule preview;
diff --git a/src/com/android/tv/dvr/ui/FullSchedulesCardPresenter.java b/src/com/android/tv/dvr/ui/browse/FullSchedulesCardPresenter.java
index 7dd85f45..6d4763d4 100644
--- a/src/com/android/tv/dvr/ui/FullSchedulesCardPresenter.java
+++ b/src/com/android/tv/dvr/ui/browse/FullSchedulesCardPresenter.java
@@ -14,17 +14,17 @@
* limitations under the License.
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
import android.content.Context;
-import android.support.v17.leanback.widget.Presenter;
+import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.ViewGroup;
import com.android.tv.R;
import com.android.tv.TvApplication;
-import com.android.tv.dvr.DvrUiHelper;
-import com.android.tv.dvr.ScheduledRecording;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.ui.DvrUiHelper;
import com.android.tv.util.Utils;
import java.util.Collections;
@@ -33,23 +33,31 @@ import java.util.List;
/**
* Presents a {@link ScheduledRecording} in the {@link DvrBrowseFragment}.
*/
-public class FullSchedulesCardPresenter extends Presenter {
+class FullSchedulesCardPresenter extends DvrItemPresenter {
+ private Context mContext;
+ private final Drawable mIconDrawable;
+ private final String mCardTitleText;
+
+ public FullSchedulesCardPresenter(Context context) {
+ mContext = context;
+ mIconDrawable = mContext.getDrawable(R.drawable.dvr_full_schedule);
+ mCardTitleText = mContext.getString(R.string.dvr_full_schedule_card_view_title);
+ }
+
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
Context context = parent.getContext();
RecordingCardView view = new RecordingCardView(context);
- return new ScheduledRecordingViewHolder(view);
+ return new ViewHolder(view);
}
@Override
- public void onBindViewHolder(ViewHolder baseHolder, Object o) {
- final ScheduledRecordingViewHolder viewHolder = (ScheduledRecordingViewHolder) baseHolder;
- final RecordingCardView cardView = (RecordingCardView) viewHolder.view;
- final Context context = viewHolder.view.getContext();
+ public void onBindViewHolder(ViewHolder vh, Object o) {
+ final RecordingCardView cardView = (RecordingCardView) vh.view;
- cardView.setImage(context.getDrawable(R.drawable.dvr_full_schedule));
- cardView.setTitle(context.getString(R.string.dvr_full_schedule_card_view_title));
- List<ScheduledRecording> scheduledRecordings = TvApplication.getSingletons(context)
+ cardView.setImage(mIconDrawable);
+ cardView.setTitle(mCardTitleText);
+ List<ScheduledRecording> scheduledRecordings = TvApplication.getSingletons(mContext)
.getDvrDataManager().getAvailableScheduledRecordings();
int fullDays = 0;
if (!scheduledRecordings.isEmpty()) {
@@ -57,28 +65,24 @@ public class FullSchedulesCardPresenter extends Presenter {
Collections.max(scheduledRecordings, ScheduledRecording.START_TIME_COMPARATOR)
.getStartTimeMs()) + 1;
}
- cardView.setContent(context.getResources().getQuantityString(
+ cardView.setContent(mContext.getResources().getQuantityString(
R.plurals.dvr_full_schedule_card_view_content, fullDays, fullDays), null);
-
- View.OnClickListener clickListener = new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- DvrUiHelper.startSchedulesActivity(context, null);
- }
- };
- baseHolder.view.setOnClickListener(clickListener);
+ super.onBindViewHolder(vh, o);
}
@Override
- public void onUnbindViewHolder(ViewHolder baseHolder) {
- ScheduledRecordingViewHolder viewHolder = (ScheduledRecordingViewHolder) baseHolder;
- final RecordingCardView cardView = (RecordingCardView) viewHolder.view;
- cardView.reset();
+ public void onUnbindViewHolder(ViewHolder vh) {
+ ((RecordingCardView) vh.view).reset();
+ super.onUnbindViewHolder(vh);
}
- private static final class ScheduledRecordingViewHolder extends ViewHolder {
- ScheduledRecordingViewHolder(RecordingCardView view) {
- super(view);
- }
+ @Override
+ protected View.OnClickListener onCreateOnClickListener() {
+ return new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ DvrUiHelper.startSchedulesActivity(mContext, null);
+ }
+ };
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/RecordedProgramDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/RecordedProgramDetailsFragment.java
index e698b8a2..fe9b9de5 100644
--- a/src/com/android/tv/dvr/ui/RecordedProgramDetailsFragment.java
+++ b/src/com/android/tv/dvr/ui/browse/RecordedProgramDetailsFragment.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
import android.content.res.Resources;
import android.media.tv.TvInputManager;
@@ -30,10 +30,10 @@ import com.android.tv.data.Channel;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.DvrWatchedPositionManager;
-import com.android.tv.dvr.RecordedProgram;
+import com.android.tv.dvr.data.RecordedProgram;
/**
- * {@link DetailsFragment} for recorded program in DVR.
+ * {@link android.support.v17.leanback.app.DetailsFragment} for recorded program in DVR.
*/
public class RecordedProgramDetailsFragment extends DvrDetailsFragment
implements DvrDataManager.RecordedProgramListener {
diff --git a/src/com/android/tv/dvr/ui/RecordedProgramPresenter.java b/src/com/android/tv/dvr/ui/browse/RecordedProgramPresenter.java
index 1bf34310..ee978797 100644
--- a/src/com/android/tv/dvr/ui/RecordedProgramPresenter.java
+++ b/src/com/android/tv/dvr/ui/browse/RecordedProgramPresenter.java
@@ -14,31 +14,26 @@
* limitations under the License.
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
-import android.app.Activity;
import android.content.Context;
import android.media.tv.TvContract;
import android.media.tv.TvInputManager;
-import android.net.Uri;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.TextAppearanceSpan;
-import android.view.View;
import android.view.ViewGroup;
import com.android.tv.R;
import com.android.tv.TvApplication;
-import com.android.tv.dvr.RecordedProgram;
import com.android.tv.data.Channel;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.dvr.DvrWatchedPositionManager;
import com.android.tv.dvr.DvrWatchedPositionManager.WatchedPositionChangedListener;
+import com.android.tv.dvr.data.RecordedProgram;
import com.android.tv.util.Utils;
-import java.util.concurrent.TimeUnit;
-
/**
* Presents a {@link RecordedProgram} in the {@link DvrBrowseFragment}.
*/
@@ -50,6 +45,7 @@ public class RecordedProgramPresenter extends DvrItemPresenter {
private String mYesterdayString;
private final int mProgressBarColor;
private final boolean mShowEpisodeTitle;
+ private final boolean mExpandTitleWhenFocused;
private static final class RecordedProgramViewHolder extends ViewHolder
implements WatchedPositionChangedListener {
@@ -79,25 +75,27 @@ public class RecordedProgramPresenter extends DvrItemPresenter {
}
}
- public RecordedProgramPresenter(Context context, boolean showEpisodeTitle) {
+ public RecordedProgramPresenter(Context context, boolean showEpisodeTitle,
+ boolean expandTitleWhenFocused) {
mContext = context;
- mChannelDataManager = TvApplication.getSingletons(context).getChannelDataManager();
- mTodayString = context.getString(R.string.dvr_date_today);
- mYesterdayString = context.getString(R.string.dvr_date_yesterday);
+ mChannelDataManager = TvApplication.getSingletons(mContext).getChannelDataManager();
+ mTodayString = mContext.getString(R.string.dvr_date_today);
+ mYesterdayString = mContext.getString(R.string.dvr_date_yesterday);
mDvrWatchedPositionManager =
- TvApplication.getSingletons(context).getDvrWatchedPositionManager();
- mProgressBarColor = context.getResources()
+ TvApplication.getSingletons(mContext).getDvrWatchedPositionManager();
+ mProgressBarColor = mContext.getResources()
.getColor(R.color.play_controls_progress_bar_watched);
mShowEpisodeTitle = showEpisodeTitle;
+ mExpandTitleWhenFocused = expandTitleWhenFocused;
}
public RecordedProgramPresenter(Context context) {
- this(context, false);
+ this(context, false, false);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
- RecordingCardView view = new RecordingCardView(mContext);
+ RecordingCardView view = new RecordingCardView(mContext, mExpandTitleWhenFocused);
return new RecordedProgramViewHolder(view, mProgressBarColor);
}
@@ -132,8 +130,7 @@ public class RecordedProgramPresenter extends DvrItemPresenter {
isChannelLogo = true;
}
cardView.setImageUri(imageUri, isChannelLogo);
- int durationMinutes =
- Math.max(1, (int) TimeUnit.MILLISECONDS.toMinutes(program.getDurationMillis()));
+ int durationMinutes = Math.max(1, Utils.getRoundOffMinsFromMs(program.getDurationMillis()));
String durationString = getContext().getResources().getQuantityString(
R.plurals.dvr_program_duration, durationMinutes, durationMinutes);
cardView.setContent(getDescription(program), durationString);
diff --git a/src/com/android/tv/dvr/ui/RecordingCardView.java b/src/com/android/tv/dvr/ui/browse/RecordingCardView.java
index 51c3b03b..7b0a8cb9 100644
--- a/src/com/android/tv/dvr/ui/RecordingCardView.java
+++ b/src/com/android/tv/dvr/ui/browse/RecordingCardView.java
@@ -14,51 +14,69 @@
* limitations under the License.
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
+import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
+import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
-import android.net.Uri;
import android.support.annotation.Nullable;
import android.support.v17.leanback.widget.BaseCardView;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
+import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.android.tv.R;
-import com.android.tv.dvr.RecordedProgram;
+import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.ui.ViewUtils;
import com.android.tv.util.ImageLoader;
/**
- * A CardView for displaying info about a {@link com.android.tv.dvr.ScheduledRecording} or
- * {@link RecordedProgram} or
- * {@link com.android.tv.dvr.SeriesRecording}.
+ * A CardView for displaying info about a {@link com.android.tv.dvr.data.ScheduledRecording}
+ * or {@link RecordedProgram} or {@link com.android.tv.dvr.data.SeriesRecording}.
*/
-class RecordingCardView extends BaseCardView {
+public class RecordingCardView extends BaseCardView {
+ // This value should be the same with
+ // android.support.v17.leanback.widget.FocusHighlightHelper.BrowseItemFocusHighlight.DURATION_MS
+ private final static int ANIMATION_DURATION = 150;
private final ImageView mImageView;
private final int mImageWidth;
private final int mImageHeight;
private String mImageUri;
- private final TextView mTitleView;
private final TextView mMajorContentView;
private final TextView mMinorContentView;
private final ProgressBar mProgressBar;
private final View mAffiliatedIconContainer;
private final ImageView mAffiliatedIcon;
private final Drawable mDefaultImage;
+ private final FrameLayout mTitleArea;
+ private final TextView mFoldedTitleView;
+ private final TextView mExpandedTitleView;
+ private final ValueAnimator mExpandTitleAnimator;
+ private final int mFoldedTitleHeight;
+ private final int mExpandedTitleHeight;
+ private final boolean mExpandTitleWhenFocused;
+ private boolean mExpanded;
- RecordingCardView(Context context) {
- this(context,
- context.getResources().getDimensionPixelSize(R.dimen.dvr_card_image_layout_width),
- context.getResources().getDimensionPixelSize(R.dimen.dvr_card_image_layout_height));
+ public RecordingCardView(Context context) {
+ this(context, false);
}
- RecordingCardView(Context context, int imageWidth, int imageHeight) {
+ public RecordingCardView(Context context, boolean expandTitleWhenFocused) {
+ this(context, context.getResources().getDimensionPixelSize(
+ R.dimen.dvr_library_card_image_layout_width), context.getResources()
+ .getDimensionPixelSize(R.dimen.dvr_library_card_image_layout_height),
+ expandTitleWhenFocused);
+ }
+
+ public RecordingCardView(Context context, int imageWidth, int imageHeight,
+ boolean expandTitleWhenFocused) {
super(context);
//TODO(dvr): move these to the layout XML.
setCardType(BaseCardView.CARD_TYPE_INFO_UNDER_WITH_EXTRA);
@@ -75,13 +93,73 @@ class RecordingCardView extends BaseCardView {
mProgressBar = (ProgressBar) findViewById(R.id.recording_progress);
mAffiliatedIconContainer = findViewById(R.id.affiliated_icon_container);
mAffiliatedIcon = (ImageView) findViewById(R.id.affiliated_icon);
- mTitleView = (TextView) findViewById(R.id.title);
mMajorContentView = (TextView) findViewById(R.id.content_major);
mMinorContentView = (TextView) findViewById(R.id.content_minor);
+ mTitleArea = (FrameLayout) findViewById(R.id.title_area);
+ mFoldedTitleView = (TextView) findViewById(R.id.title_one_line);
+ mExpandedTitleView = (TextView) findViewById(R.id.title_two_lines);
+ mFoldedTitleHeight = getResources()
+ .getDimensionPixelSize(R.dimen.dvr_library_card_folded_title_height);
+ mExpandedTitleHeight = getResources()
+ .getDimensionPixelSize(R.dimen.dvr_library_card_expanded_title_height);
+ mExpandTitleAnimator = ValueAnimator.ofFloat(0.0f, 1.0f).setDuration(ANIMATION_DURATION);
+ mExpandTitleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ float value = (Float) valueAnimator.getAnimatedValue();
+ mExpandedTitleView.setAlpha(value);
+ mFoldedTitleView.setAlpha(1.0f - value);
+ ViewUtils.setLayoutHeight(mTitleArea, (int) (mFoldedTitleHeight
+ + (mExpandedTitleHeight - mFoldedTitleHeight) * value));
+ }
+ });
+ mExpandTitleWhenFocused = expandTitleWhenFocused;
+ }
+
+ @Override
+ protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
+ super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+ if (mExpandTitleWhenFocused) {
+ if (gainFocus) {
+ expandTitle(true, true);
+ } else {
+ expandTitle(false, true);
+ }
+ }
+ }
+
+ /**
+ * Expands/folds the title area to show program title with two/one lines.
+ *
+ * @param expand {@code true} to expand the title area, or {@code false} to fold it.
+ * @param withAnimation {@code true} to expand/fold with animation.
+ */
+ public void expandTitle(boolean expand, boolean withAnimation) {
+ if (expand != mExpanded && mFoldedTitleView.getLayout().getEllipsisCount(0) > 0) {
+ if (withAnimation) {
+ if (expand) {
+ mExpandTitleAnimator.start();
+ } else {
+ mExpandTitleAnimator.reverse();
+ }
+ } else {
+ if (expand) {
+ mFoldedTitleView.setAlpha(0.0f);
+ mExpandedTitleView.setAlpha(1.0f);
+ ViewUtils.setLayoutHeight(mTitleArea, mExpandedTitleHeight);
+ } else {
+ mFoldedTitleView.setAlpha(1.0f);
+ mExpandedTitleView.setAlpha(0.0f);
+ ViewUtils.setLayoutHeight(mTitleArea, mFoldedTitleHeight);
+ }
+ }
+ mExpanded = expand;
+ }
}
void setTitle(CharSequence title) {
- mTitleView.setText(title);
+ mFoldedTitleView.setText(title);
+ mExpandedTitleView.setText(title);
}
void setContent(CharSequence majorContent, CharSequence minorContent) {
@@ -178,8 +256,9 @@ class RecordingCardView extends BaseCardView {
}
public void reset() {
- mTitleView.setText(null);
+ mFoldedTitleView.setText(null);
+ mExpandedTitleView.setText(null);
setContent(null, null);
mImageView.setImageDrawable(mDefaultImage);
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/RecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java
index 4e19ec3f..a877e05f 100644
--- a/src/com/android/tv/dvr/ui/RecordingDetailsFragment.java
+++ b/src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
import android.os.Bundle;
import android.support.v17.leanback.app.DetailsFragment;
@@ -26,7 +26,7 @@ import android.text.style.TextAppearanceSpan;
import com.android.tv.R;
import com.android.tv.TvApplication;
import com.android.tv.data.Channel;
-import com.android.tv.dvr.ScheduledRecording;
+import com.android.tv.dvr.data.ScheduledRecording;
/**
* {@link DetailsFragment} for recordings in DVR.
diff --git a/src/com/android/tv/dvr/ui/ScheduledRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java
index 60816bb5..eb0f4f0d 100644
--- a/src/com/android/tv/dvr/ui/ScheduledRecordingDetailsFragment.java
+++ b/src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
import android.content.res.Resources;
import android.os.Bundle;
@@ -26,7 +26,7 @@ import android.text.TextUtils;
import com.android.tv.R;
import com.android.tv.TvApplication;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.DvrUiHelper;
+import com.android.tv.dvr.ui.DvrUiHelper;
/**
* {@link RecordingDetailsFragment} for scheduled recording in DVR.
diff --git a/src/com/android/tv/dvr/ui/ScheduledRecordingPresenter.java b/src/com/android/tv/dvr/ui/browse/ScheduledRecordingPresenter.java
index 5f447f13..efc8785a 100644
--- a/src/com/android/tv/dvr/ui/ScheduledRecordingPresenter.java
+++ b/src/com/android/tv/dvr/ui/browse/ScheduledRecordingPresenter.java
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
-import android.app.Activity;
import android.content.Context;
import android.media.tv.TvContract;
import android.os.Handler;
@@ -32,7 +31,7 @@ import com.android.tv.TvApplication;
import com.android.tv.data.Channel;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.ScheduledRecording;
+import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.util.Utils;
import java.util.concurrent.TimeUnit;
@@ -40,7 +39,7 @@ import java.util.concurrent.TimeUnit;
/**
* Presents a {@link ScheduledRecording} in the {@link DvrBrowseFragment}.
*/
-public class ScheduledRecordingPresenter extends DvrItemPresenter {
+class ScheduledRecordingPresenter extends DvrItemPresenter {
private static final long PROGRESS_UPDATE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(5);
private final ChannelDataManager mChannelDataManager;
@@ -92,18 +91,17 @@ public class ScheduledRecordingPresenter extends DvrItemPresenter {
}
public ScheduledRecordingPresenter(Context context) {
- ApplicationSingletons singletons = TvApplication.getSingletons(context);
+ mContext = context;
+ ApplicationSingletons singletons = TvApplication.getSingletons(mContext);
mChannelDataManager = singletons.getChannelDataManager();
mDvrManager = singletons.getDvrManager();
- mContext = context;
- mProgressBarColor = context.getResources()
+ mProgressBarColor = mContext.getResources()
.getColor(R.color.play_controls_recording_icon_color_on_focus);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
- Context context = parent.getContext();
- RecordingCardView view = new RecordingCardView(context);
+ RecordingCardView view = new RecordingCardView(mContext);
return new ScheduledRecordingViewHolder(view, mProgressBarColor);
}
@@ -144,9 +142,8 @@ public class ScheduledRecordingPresenter extends DvrItemPresenter {
public void onUnbindViewHolder(ViewHolder baseHolder) {
ScheduledRecordingViewHolder viewHolder = (ScheduledRecordingViewHolder) baseHolder;
viewHolder.stopUpdateProgressBar();
- final RecordingCardView cardView = (RecordingCardView) viewHolder.view;
viewHolder.mScheduledRecording = null;
- cardView.reset();
+ ((RecordingCardView) viewHolder.view).reset();
super.onUnbindViewHolder(viewHolder);
}
@@ -174,4 +171,4 @@ public class ScheduledRecordingPresenter extends DvrItemPresenter {
cardView.setTitle(title);
cardView.setImageUri(imageUri, isChannelLogo);
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/SeriesRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java
index e9e391d4..f7b60b50 100644
--- a/src/com/android/tv/dvr/ui/SeriesRecordingDetailsFragment.java
+++ b/src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
@@ -28,7 +28,6 @@ import android.support.v17.leanback.widget.DetailsOverviewRow;
import android.support.v17.leanback.widget.DetailsOverviewRowPresenter;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.ListRowPresenter;
import android.support.v17.leanback.widget.OnActionClickedListener;
import android.support.v17.leanback.widget.PresenterSelector;
import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
@@ -39,12 +38,11 @@ import com.android.tv.TvApplication;
import com.android.tv.data.BaseProgram;
import com.android.tv.data.Channel;
import com.android.tv.dvr.DvrDataManager;
-import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.DvrUiHelper;
import com.android.tv.dvr.DvrWatchedPositionManager;
-import com.android.tv.dvr.RecordedProgram;
-import com.android.tv.dvr.ScheduledRecording;
-import com.android.tv.dvr.SeriesRecording;
+import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.dvr.data.SeriesRecording;
+import com.android.tv.dvr.ui.DvrUiHelper;
+import com.android.tv.dvr.ui.SortedArrayAdapter;
import java.util.Collections;
import java.util.Comparator;
@@ -85,7 +83,7 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement
mWatchLabel = getString(R.string.dvr_detail_watch);
mResumeLabel = getString(R.string.dvr_detail_series_resume);
mWatchDrawable = getResources().getDrawable(R.drawable.lb_ic_play, null);
- mRecordedProgramPresenter = new RecordedProgramPresenter(getContext(), true);
+ mRecordedProgramPresenter = new RecordedProgramPresenter(getContext(), true, true);
super.onCreate(savedInstanceState);
}
@@ -158,7 +156,7 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement
DetailsOverviewRowPresenter rowPresenter) {
ClassPresenterSelector presenterSelector = new ClassPresenterSelector();
presenterSelector.addClassPresenter(DetailsOverviewRow.class, rowPresenter);
- presenterSelector.addClassPresenter(ListRow.class, new ListRowPresenter());
+ presenterSelector.addClassPresenter(ListRow.class, new DvrListRowPresenter(getContext()));
return presenterSelector;
}
@@ -203,10 +201,7 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement
mDvrDataManager.removeSeriesRecordingListener(this);
mDvrDataManager.removeRecordedProgramListener(this);
if (mSeries != null) {
- DvrManager dvrManager = TvApplication.getSingletons(getActivity()).getDvrManager();
- if (dvrManager.canRemoveSeriesRecording(mSeries.getId())) {
- dvrManager.removeSeriesRecording(mSeries.getId());
- }
+ mDvrDataManager.checkAndRemoveEmptySeriesRecording(mSeries.getId());
}
mRecordedProgramPresenter.unbindAllViewHolders();
}
@@ -265,7 +260,6 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement
public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) {
for (SeriesRecording series : seriesRecordings) {
if (series.getId() == mSeries.getId()) {
- mSeries = null;
getActivity().finish();
return;
}
@@ -372,4 +366,4 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement
return program.getId();
}
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/SeriesRecordingPresenter.java b/src/com/android/tv/dvr/ui/browse/SeriesRecordingPresenter.java
index c2c0f596..af6ecc19 100644
--- a/src/com/android/tv/dvr/ui/SeriesRecordingPresenter.java
+++ b/src/com/android/tv/dvr/ui/browse/SeriesRecordingPresenter.java
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
-import android.app.Activity;
import android.content.Context;
import android.media.tv.TvContract;
import android.media.tv.TvInputManager;
@@ -34,16 +33,16 @@ import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.DvrWatchedPositionManager;
import com.android.tv.dvr.DvrWatchedPositionManager.WatchedPositionChangedListener;
-import com.android.tv.dvr.RecordedProgram;
-import com.android.tv.dvr.ScheduledRecording;
-import com.android.tv.dvr.SeriesRecording;
+import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.data.SeriesRecording;
import java.util.List;
/**
* Presents a {@link SeriesRecording} in {@link DvrBrowseFragment}.
*/
-public class SeriesRecordingPresenter extends DvrItemPresenter {
+class SeriesRecordingPresenter extends DvrItemPresenter {
private final ChannelDataManager mChannelDataManager;
private final DvrDataManager mDvrDataManager;
private final DvrManager mDvrManager;
diff --git a/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java b/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java
index d28f026c..5abd52a1 100644
--- a/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java
+++ b/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java
@@ -29,7 +29,7 @@ import com.android.tv.R;
import com.android.tv.TvApplication;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrScheduleManager;
-import com.android.tv.dvr.ScheduledRecording;
+import com.android.tv.dvr.data.ScheduledRecording;
/**
* A base fragment to show the list of schedule recordings.
diff --git a/src/com/android/tv/dvr/ui/DvrSchedulesActivity.java b/src/com/android/tv/dvr/ui/list/DvrSchedulesActivity.java
index f6e6ac26..a0410bb3 100644
--- a/src/com/android/tv/dvr/ui/DvrSchedulesActivity.java
+++ b/src/com/android/tv/dvr/ui/list/DvrSchedulesActivity.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.list;
import android.app.Activity;
import android.app.ProgressDialog;
@@ -24,15 +24,13 @@ import android.support.annotation.IntDef;
import com.android.tv.R;
import com.android.tv.TvApplication;
import com.android.tv.data.Program;
-import com.android.tv.dvr.EpisodicProgramLoadTask;
-import com.android.tv.dvr.SeriesRecording;
-import com.android.tv.dvr.SeriesRecordingScheduler;
-import com.android.tv.dvr.ui.list.DvrSchedulesFragment;
-import com.android.tv.dvr.ui.list.DvrSeriesSchedulesFragment;
+import com.android.tv.dvr.data.SeriesRecording;
+import com.android.tv.dvr.provider.EpisodicProgramLoadTask;
+import com.android.tv.dvr.recorder.SeriesRecordingScheduler;
+import com.android.tv.dvr.ui.BigArguments;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -72,33 +70,47 @@ public class DvrSchedulesActivity extends Activity {
getFragmentManager().beginTransaction().add(
R.id.fragment_container, schedulesFragment).commit();
} else if (scheduleType == TYPE_SERIES_SCHEDULE) {
- final ProgressDialog dialog = ProgressDialog.show(this, null, getString(
- R.string.dvr_series_schedules_progress_message_reading_programs));
- SeriesRecording seriesRecording = getIntent().getExtras()
- .getParcelable(DvrSeriesSchedulesFragment
- .SERIES_SCHEDULES_KEY_SERIES_RECORDING);
- // To get programs faster, hold the update of the series schedules.
- SeriesRecordingScheduler.getInstance(this).pauseUpdate();
- new EpisodicProgramLoadTask(this, Collections.singletonList(seriesRecording)) {
- @Override
- protected void onPostExecute(List<Program> programs) {
- SeriesRecordingScheduler.getInstance(DvrSchedulesActivity.this).resumeUpdate();
- dialog.dismiss();
- Bundle args = getIntent().getExtras();
- args.putParcelableArrayList(DvrSeriesSchedulesFragment
- .SERIES_SCHEDULES_KEY_SERIES_PROGRAMS, new ArrayList<>(programs));
- DvrSeriesSchedulesFragment schedulesFragment = new DvrSeriesSchedulesFragment();
- schedulesFragment.setArguments(args);
- getFragmentManager().beginTransaction().add(
- R.id.fragment_container, schedulesFragment).commit();
- }
- }.setLoadCurrentProgram(true)
- .setLoadDisallowedProgram(true)
- .setLoadScheduledEpisode(true)
- .setIgnoreChannelOption(true)
- .execute();
+ if (BigArguments.getArgument(DvrSeriesSchedulesFragment
+ .SERIES_SCHEDULES_KEY_SERIES_PROGRAMS) != null) {
+ // The programs will be passed to the DvrSeriesSchedulesFragment, so don't need
+ // to reset the BigArguments.
+ showDvrSeriesSchedulesFragment(getIntent().getExtras());
+ } else {
+ final ProgressDialog dialog = ProgressDialog.show(this, null, getString(
+ R.string.dvr_series_progress_message_reading_programs));
+ SeriesRecording seriesRecording = getIntent().getExtras()
+ .getParcelable(DvrSeriesSchedulesFragment
+ .SERIES_SCHEDULES_KEY_SERIES_RECORDING);
+ // To get programs faster, hold the update of the series schedules.
+ SeriesRecordingScheduler.getInstance(this).pauseUpdate();
+ new EpisodicProgramLoadTask(this, Collections.singletonList(seriesRecording)) {
+ @Override
+ protected void onPostExecute(List<Program> programs) {
+ SeriesRecordingScheduler.getInstance(DvrSchedulesActivity.this)
+ .resumeUpdate();
+ dialog.dismiss();
+ Bundle args = getIntent().getExtras();
+ BigArguments.reset();
+ BigArguments.setArgument(
+ DvrSeriesSchedulesFragment.SERIES_SCHEDULES_KEY_SERIES_PROGRAMS,
+ programs == null ? Collections.EMPTY_LIST : programs);
+ showDvrSeriesSchedulesFragment(args);
+ }
+ }.setLoadCurrentProgram(true)
+ .setLoadDisallowedProgram(true)
+ .setLoadScheduledEpisode(true)
+ .setIgnoreChannelOption(true)
+ .execute();
+ }
} else {
finish();
}
}
+
+ private void showDvrSeriesSchedulesFragment(Bundle args) {
+ DvrSeriesSchedulesFragment schedulesFragment = new DvrSeriesSchedulesFragment();
+ schedulesFragment.setArguments(args);
+ getFragmentManager().beginTransaction().add(
+ R.id.fragment_container, schedulesFragment).commit();
+ }
}
diff --git a/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java b/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java
index 722c9b6e..3cbb500a 100644
--- a/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java
+++ b/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java
@@ -18,12 +18,9 @@ package com.android.tv.dvr.ui.list;
import android.os.Bundle;
import android.support.v17.leanback.widget.ClassPresenterSelector;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
import com.android.tv.R;
-import com.android.tv.dvr.ScheduledRecording;
+import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.ui.list.SchedulesHeaderRowPresenter.DateHeaderRowPresenter;
/**
diff --git a/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java b/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java
index 42a1e72b..57e7a88f 100644
--- a/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java
+++ b/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java
@@ -17,6 +17,7 @@
package com.android.tv.dvr.ui.list;
import android.annotation.TargetApi;
+import android.content.Context;
import android.database.ContentObserver;
import android.media.tv.TvContract.Programs;
import android.net.Uri;
@@ -35,11 +36,13 @@ import com.android.tv.R;
import com.android.tv.TvApplication;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.Program;
+import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrDataManager.SeriesRecordingListener;
-import com.android.tv.dvr.EpisodicProgramLoadTask;
-import com.android.tv.dvr.SeriesRecording;
-import com.android.tv.dvr.ui.list.SchedulesHeaderRowPresenter.SeriesRecordingHeaderRowPresenter;
+import com.android.tv.dvr.data.SeriesRecording;
+import com.android.tv.dvr.provider.EpisodicProgramLoadTask;
+import com.android.tv.dvr.ui.BigArguments;
+import java.util.Collections;
import java.util.List;
/**
@@ -47,20 +50,22 @@ import java.util.List;
*/
@TargetApi(Build.VERSION_CODES.N)
public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment {
- private static final String TAG = "DvrSeriesSchedulesFragment";
/**
* The key for series recording whose scheduled recording list will be displayed.
+ * Type: {@link SeriesRecording}
*/
public static final String SERIES_SCHEDULES_KEY_SERIES_RECORDING =
"series_schedules_key_series_recording";
/**
- * The key for programs belong to the series recording whose scheduled recording
- * list will be displayed.
+ * The key for programs which belong to the series recording whose scheduled recording list
+ * will be displayed.
+ * Type: List<{@link Program}>
*/
public static final String SERIES_SCHEDULES_KEY_SERIES_PROGRAMS =
"series_schedules_key_series_programs";
private ChannelDataManager mChannelDataManager;
+ private DvrDataManager mDvrDataManager;
private SeriesRecording mSeriesRecording;
private List<Program> mPrograms;
private EpisodicProgramLoadTask mProgramLoadTask;
@@ -87,20 +92,22 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment {
&& getRowsAdapter() instanceof SeriesScheduleRowAdapter) {
((SeriesScheduleRowAdapter) getRowsAdapter())
.onSeriesRecordingUpdated(r);
+ mSeriesRecording = r;
+ updateEmptyMessage();
return;
}
}
}
};
- private final ContentObserver mContentObserver =
- new ContentObserver(new Handler(Looper.getMainLooper())) {
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- super.onChange(selfChange, uri);
- executeProgramLoadingTask();
- }
- };
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final ContentObserver mContentObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ super.onChange(selfChange, uri);
+ executeProgramLoadingTask();
+ }
+ };
private final ChannelDataManager.Listener mChannelListener = new ChannelDataManager.Listener() {
@Override
@@ -120,17 +127,28 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment {
}
@Override
- public void onCreate(Bundle savedInstanceState) {
+ public void onAttach(Context context) {
+ super.onAttach(context);
Bundle args = getArguments();
if (args != null) {
mSeriesRecording = args.getParcelable(SERIES_SCHEDULES_KEY_SERIES_RECORDING);
- mPrograms = args.getParcelableArrayList(SERIES_SCHEDULES_KEY_SERIES_PROGRAMS);
+ mPrograms = (List<Program>) BigArguments.getArgument(
+ SERIES_SCHEDULES_KEY_SERIES_PROGRAMS);
+ BigArguments.reset();
}
+ if (args == null || mPrograms == null) {
+ getActivity().finish();
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ApplicationSingletons singletons = TvApplication.getSingletons(getContext());
- singletons.getDvrDataManager().addSeriesRecordingListener(mSeriesRecordingListener);
mChannelDataManager = singletons.getChannelDataManager();
mChannelDataManager.addListener(mChannelListener);
+ mDvrDataManager = singletons.getDvrDataManager();
+ mDvrDataManager.addSeriesRecordingListener(mSeriesRecordingListener);
getContext().getContentResolver().registerContentObserver(Programs.CONTENT_URI, true,
mContentObserver);
}
@@ -144,8 +162,16 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment {
private void onProgramsUpdated() {
((SeriesScheduleRowAdapter) getRowsAdapter()).setPrograms(mPrograms);
+ updateEmptyMessage();
+ }
+
+ private void updateEmptyMessage() {
if (mPrograms == null || mPrograms.isEmpty()) {
- showEmptyMessage(R.string.dvr_series_schedules_empty_state);
+ if (mSeriesRecording.getState() == SeriesRecording.STATE_SERIES_STOPPED) {
+ showEmptyMessage(R.string.dvr_series_schedules_stopped_empty_state);
+ } else {
+ showEmptyMessage(R.string.dvr_series_schedules_empty_state);
+ }
} else {
hideEmptyMessage();
}
@@ -158,15 +184,15 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment {
mProgramLoadTask = null;
}
getContext().getContentResolver().unregisterContentObserver(mContentObserver);
+ mHandler.removeCallbacksAndMessages(null);
mChannelDataManager.removeListener(mChannelListener);
- TvApplication.getSingletons(getContext()).getDvrDataManager()
- .removeSeriesRecordingListener(mSeriesRecordingListener);
+ mDvrDataManager.removeSeriesRecordingListener(mSeriesRecordingListener);
super.onDestroy();
}
@Override
public SchedulesHeaderRowPresenter onCreateHeaderRowPresenter() {
- return new SeriesRecordingHeaderRowPresenter(getContext());
+ return new SchedulesHeaderRowPresenter.SeriesRecordingHeaderRowPresenter(getContext());
}
@Override
@@ -195,7 +221,7 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment {
mProgramLoadTask = new EpisodicProgramLoadTask(getContext(), mSeriesRecording) {
@Override
protected void onPostExecute(List<Program> programs) {
- mPrograms = programs;
+ mPrograms = programs == null ? Collections.EMPTY_LIST : programs;
onProgramsUpdated();
}
};
@@ -205,4 +231,4 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment {
.setIgnoreChannelOption(true)
.execute();
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java b/src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java
index 23aebf59..9b0ad105 100644
--- a/src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java
+++ b/src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java
@@ -19,13 +19,13 @@ package com.android.tv.dvr.ui.list;
import android.content.Context;
import com.android.tv.data.Program;
-import com.android.tv.dvr.ScheduledRecording;
-import com.android.tv.dvr.ScheduledRecording.Builder;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.data.ScheduledRecording.Builder;
/**
* A class for the episodic program.
*/
-public class EpisodicProgramRow extends ScheduleRow {
+class EpisodicProgramRow extends ScheduleRow {
private final String mInputId;
private final Program mProgram;
diff --git a/src/com/android/tv/dvr/ui/list/ScheduleRow.java b/src/com/android/tv/dvr/ui/list/ScheduleRow.java
index 3fc92e8a..66e96f94 100644
--- a/src/com/android/tv/dvr/ui/list/ScheduleRow.java
+++ b/src/com/android/tv/dvr/ui/list/ScheduleRow.java
@@ -20,12 +20,12 @@ import android.content.Context;
import android.support.annotation.Nullable;
import com.android.tv.common.SoftPreconditions;
-import com.android.tv.dvr.ScheduledRecording;
+import com.android.tv.dvr.data.ScheduledRecording;
/**
* A class for schedule recording row.
*/
-public class ScheduleRow {
+class ScheduleRow {
private final SchedulesHeaderRow mHeaderRow;
@Nullable private ScheduledRecording mSchedule;
private boolean mStopRecordingRequested;
diff --git a/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java b/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java
index 9cc82653..97d60473 100644
--- a/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java
+++ b/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java
@@ -30,8 +30,8 @@ import com.android.tv.R;
import com.android.tv.TvApplication;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.ScheduledRecording;
import com.android.tv.dvr.ui.list.SchedulesHeaderRow.DateHeaderRow;
+import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.util.Utils;
import java.util.ArrayList;
@@ -43,7 +43,7 @@ import java.util.concurrent.TimeUnit;
/**
* An adapter for {@link ScheduleRow}.
*/
-public class ScheduleRowAdapter extends ArrayObjectAdapter {
+class ScheduleRowAdapter extends ArrayObjectAdapter {
private static final String TAG = "ScheduleRowAdapter";
private static final boolean DEBUG = false;
diff --git a/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java b/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java
index 1257e725..dc4e3c41 100644
--- a/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java
+++ b/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java
@@ -42,25 +42,24 @@ import com.android.tv.R;
import com.android.tv.TvApplication;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.Channel;
+import com.android.tv.dialog.HalfSizedDialogFragment;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.DvrScheduleManager;
-import com.android.tv.dvr.DvrUiHelper;
-import com.android.tv.dvr.ScheduledRecording;
+import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.ui.DvrStopRecordingFragment;
-import com.android.tv.dvr.ui.HalfSizedDialogFragment;
+import com.android.tv.dvr.ui.DvrUiHelper;
import com.android.tv.util.ToastUtils;
import com.android.tv.util.Utils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
-import java.util.concurrent.TimeUnit;
/**
* A RowPresenter for {@link ScheduleRow}.
*/
@TargetApi(Build.VERSION_CODES.N)
-public class ScheduleRowPresenter extends RowPresenter {
+class ScheduleRowPresenter extends RowPresenter {
private static final String TAG = "ScheduleRowPresenter";
@Retention(RetentionPolicy.SOURCE)
@@ -345,7 +344,9 @@ public class ScheduleRowPresenter extends RowPresenter {
viewHolder.mInfoContainer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
- onInfoClicked(row);
+ if (isInfoClickable(row)) {
+ onInfoClicked(row);
+ }
}
});
@@ -366,8 +367,7 @@ public class ScheduleRowPresenter extends RowPresenter {
viewHolder.mTimeView.setText(onGetRecordingTimeText(row));
String programInfoText = onGetProgramInfoText(row);
if (TextUtils.isEmpty(programInfoText)) {
- int durationMins =
- Math.max((int) TimeUnit.MILLISECONDS.toMinutes(row.getDuration()), 1);
+ int durationMins = Math.max(1, Utils.getRoundOffMinsFromMs(row.getDuration()));
programInfoText = mContext.getResources().getQuantityString(
R.plurals.dvr_schedules_recording_duration, durationMins, durationMins);
}
@@ -403,6 +403,7 @@ public class ScheduleRowPresenter extends RowPresenter {
} else {
viewHolder.whiteBackInfo();
}
+ viewHolder.mInfoContainer.setFocusable(isInfoClickable(row));
updateActionContainer(viewHolder, viewHolder.isSelected());
}
@@ -454,11 +455,13 @@ public class ScheduleRowPresenter extends RowPresenter {
/**
* Called when user click Info in {@link ScheduleRow}.
*/
- protected void onInfoClicked(ScheduleRow scheduleRow) {
- ScheduledRecording schedule = scheduleRow.getSchedule();
- if (schedule != null) {
- DvrUiHelper.startDetailsActivity((Activity) mContext, schedule, null, true);
- }
+ protected void onInfoClicked(ScheduleRow row) {
+ DvrUiHelper.startDetailsActivity((Activity) mContext, row.getSchedule(), null, true);
+ }
+
+ private boolean isInfoClickable(ScheduleRow row) {
+ return row.getSchedule() != null
+ && (row.getSchedule().isNotStarted() || row.getSchedule().isInProgress());
}
/**
@@ -545,7 +548,7 @@ public class ScheduleRowPresenter extends RowPresenter {
// This row has been deleted.
return;
}
- if (row.isOnAir() && row.isRecordingInProgress() && !row.isStopRecordingRequested()) {
+ if (row.isRecordingInProgress() && !row.isStopRecordingRequested()) {
row.setStopRecordingRequested(true);
mDvrManager.stopRecording(row.getSchedule());
CharSequence deletedInfo = onGetProgramInfoText(row);
@@ -670,10 +673,9 @@ public class ScheduleRowPresenter extends RowPresenter {
hideActionView(viewHolder.mFirstActionContainer, View.GONE);
}
};
- if (mLastFocusedViewId == R.id.action_first_container
- || mLastFocusedViewId == R.id.action_second_container) {
- mLastFocusedViewId = R.id.info_container;
- }
+ mLastFocusedViewId = R.id.info_container;
+ SoftPreconditions.checkState(viewHolder.mInfoContainer.isFocusable(), TAG,
+ "No focusable view in this row: " + viewHolder);
break;
}
View view = viewHolder.view.findViewById(mLastFocusedViewId);
@@ -683,8 +685,10 @@ public class ScheduleRowPresenter extends RowPresenter {
// requestFocus() explicitly.
if (view.hasFocus()) {
viewHolder.mPendingAnimationRunnable.run();
- } else {
+ } else if (view.isFocusable()){
view.requestFocus();
+ } else {
+ viewHolder.view.requestFocus();
}
}
} else {
@@ -737,10 +741,10 @@ public class ScheduleRowPresenter extends RowPresenter {
@ScheduleRowAction
protected int[] getAvailableActions(ScheduleRow row) {
if (row.getSchedule() != null) {
- if (row.isOnAir()) {
- if (row.isRecordingInProgress()) {
- return new int[] {ACTION_STOP_RECORDING};
- } else if (row.isRecordingNotStarted()) {
+ if (row.isRecordingInProgress()) {
+ return new int[]{ACTION_STOP_RECORDING};
+ } else if (row.isOnAir()) {
+ if (row.isRecordingNotStarted()) {
if (canResolveConflict()) {
// The "START" action can change the conflict states.
return new int[] {ACTION_REMOVE_SCHEDULE, ACTION_START_RECORDING};
diff --git a/src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java b/src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java
index 0fb0924d..715ecb8c 100644
--- a/src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java
+++ b/src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java
@@ -16,12 +16,15 @@
package com.android.tv.dvr.ui.list;
-import com.android.tv.dvr.SeriesRecording;
+import com.android.tv.data.Program;
+import com.android.tv.dvr.data.SeriesRecording;
+
+import java.util.List;
/**
* A base class for the rows for schedules' header.
*/
-public abstract class SchedulesHeaderRow {
+abstract class SchedulesHeaderRow {
private String mTitle;
private String mDescription;
private int mItemCount;
@@ -98,11 +101,20 @@ public abstract class SchedulesHeaderRow {
*/
public static class SeriesRecordingHeaderRow extends SchedulesHeaderRow {
private SeriesRecording mSeriesRecording;
+ private List<Program> mPrograms;
public SeriesRecordingHeaderRow(String title, String description, int itemCount,
- SeriesRecording series) {
+ SeriesRecording series, List<Program> programs) {
super(title, description, itemCount);
mSeriesRecording = series;
+ mPrograms = programs;
+ }
+
+ /**
+ * Returns the list of programs which belong to the series.
+ */
+ public List<Program> getPrograms() {
+ return mPrograms;
}
/**
@@ -119,4 +131,4 @@ public abstract class SchedulesHeaderRow {
mSeriesRecording = seriesRecording;
}
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java b/src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java
index 69c33a96..fe2033ba 100644
--- a/src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java
+++ b/src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java
@@ -30,15 +30,14 @@ import android.widget.TextView;
import com.android.tv.R;
import com.android.tv.TvApplication;
-import com.android.tv.dvr.DvrUiHelper;
-import com.android.tv.dvr.SeriesRecording;
-import com.android.tv.dvr.ui.DvrSchedulesActivity;
+import com.android.tv.dvr.data.SeriesRecording;
+import com.android.tv.dvr.ui.DvrUiHelper;
import com.android.tv.dvr.ui.list.SchedulesHeaderRow.SeriesRecordingHeaderRow;
/**
* A base class for RowPresenter for {@link SchedulesHeaderRow}
*/
-public abstract class SchedulesHeaderRowPresenter extends RowPresenter {
+abstract class SchedulesHeaderRowPresenter extends RowPresenter {
private Context mContext;
public SchedulesHeaderRowPresenter(Context context) {
@@ -79,7 +78,7 @@ public abstract class SchedulesHeaderRowPresenter extends RowPresenter {
}
/**
- * A presenter for {@link com.android.tv.dvr.ui.list.SchedulesHeaderRow.DateHeaderRow}.
+ * A presenter for {@link SchedulesHeaderRow.DateHeaderRow}.
*/
public static class DateHeaderRowPresenter extends SchedulesHeaderRowPresenter {
public DateHeaderRowPresenter(Context context) {
@@ -93,7 +92,7 @@ public abstract class SchedulesHeaderRowPresenter extends RowPresenter {
/**
* A ViewHolder for
- * {@link com.android.tv.dvr.ui.list.SchedulesHeaderRow.DateHeaderRow}.
+ * {@link SchedulesHeaderRow.DateHeaderRow}.
*/
public static class DateHeaderRowViewHolder extends SchedulesHeaderRowViewHolder {
public DateHeaderRowViewHolder(Context context, ViewGroup parent) {
@@ -152,9 +151,9 @@ public abstract class SchedulesHeaderRowPresenter extends RowPresenter {
headerViewHolder.mSeriesSettingsButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
- // TODO: pass channel list for settings.
DvrUiHelper.startSeriesSettingsActivity(getContext(),
- header.getSeriesRecording().getId(), null, false, false, false);
+ header.getSeriesRecording().getId(),
+ header.getPrograms(), false, false, false, null);
}
});
headerViewHolder.mToggleStartStopButton.setOnClickListener(new OnClickListener() {
@@ -169,9 +168,9 @@ public abstract class SchedulesHeaderRowPresenter extends RowPresenter {
.build();
TvApplication.getSingletons(getContext()).getDvrManager()
.updateSeriesRecording(seriesRecording);
- // TODO: pass channel list for settings.
DvrUiHelper.startSeriesSettingsActivity(getContext(),
- header.getSeriesRecording().getId(), null, false, false, false);
+ header.getSeriesRecording().getId(),
+ header.getPrograms(), false, false, false, null);
} else {
DvrUiHelper.showCancelAllSeriesRecordingDialog(
(DvrSchedulesActivity) view.getContext(),
@@ -182,11 +181,8 @@ public abstract class SchedulesHeaderRowPresenter extends RowPresenter {
}
private void setTextDrawable(TextView textView, Drawable drawableStart) {
- if (mLtr) {
- textView.setCompoundDrawablesWithIntrinsicBounds(drawableStart, null, null, null);
- } else {
- textView.setCompoundDrawablesWithIntrinsicBounds(null, null, drawableStart, null);
- }
+ textView.setCompoundDrawablesRelativeWithIntrinsicBounds(drawableStart, null, null,
+ null);
}
/**
diff --git a/src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java b/src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java
index 3b493774..6b6de8b8 100644
--- a/src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java
+++ b/src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java
@@ -31,8 +31,8 @@ import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.Program;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.ScheduledRecording;
-import com.android.tv.dvr.SeriesRecording;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.dvr.ui.list.SchedulesHeaderRow.SeriesRecordingHeaderRow;
import com.android.tv.util.Utils;
@@ -46,7 +46,7 @@ import java.util.Map;
* An adapter for series schedule row.
*/
@TargetApi(Build.VERSION_CODES.N)
-public class SeriesScheduleRowAdapter extends ScheduleRowAdapter {
+class SeriesScheduleRowAdapter extends ScheduleRowAdapter {
private static final String TAG = "SeriesRowAdapter";
private static final boolean DEBUG = false;
@@ -96,7 +96,7 @@ public class SeriesScheduleRowAdapter extends ScheduleRowAdapter {
Collections.sort(sortedPrograms);
List<EpisodicProgramRow> rows = new ArrayList<>();
mHeaderRow = new SeriesRecordingHeaderRow(mSeriesRecording.getTitle(),
- null, sortedPrograms.size(), mSeriesRecording);
+ null, sortedPrograms.size(), mSeriesRecording, programs);
for (Program program : sortedPrograms) {
ScheduledRecording schedule =
mDataManager.getScheduledRecordingForProgramId(program.getId());
@@ -145,7 +145,7 @@ public class SeriesScheduleRowAdapter extends ScheduleRowAdapter {
if (index != -1) {
EpisodicProgramRow row = (EpisodicProgramRow) get(index);
if (!row.isStartRecordingRequested()) {
- row.setSchedule(schedule);
+ setScheduleToRow(row, schedule);
notifyArrayItemRangeChanged(index, 1);
}
}
@@ -195,12 +195,10 @@ public class SeriesScheduleRowAdapter extends ScheduleRowAdapter {
if (!isStartOrStopRequested()) {
executePendingUpdate();
}
- row.setSchedule(schedule);
+ setScheduleToRow(row, schedule);
}
- } else if (willBeKept(schedule)) {
- row.setSchedule(schedule);
} else {
- row.setSchedule(null);
+ setScheduleToRow(row, schedule);
}
notifyArrayItemRangeChanged(index, 1);
}
@@ -213,6 +211,14 @@ public class SeriesScheduleRowAdapter extends ScheduleRowAdapter {
}
}
+ private void setScheduleToRow(ScheduleRow row, ScheduledRecording schedule) {
+ if (schedule != null && willBeKept(schedule)) {
+ row.setSchedule(schedule);
+ } else {
+ row.setSchedule(null);
+ }
+ }
+
private int findRowIndexByProgramId(long programId) {
for (int i = 0; i < size(); i++) {
Object item = get(i);
diff --git a/src/com/android/tv/dvr/ui/list/SeriesScheduleRowPresenter.java b/src/com/android/tv/dvr/ui/list/SeriesScheduleRowPresenter.java
index 5d88579a..c8503e0d 100644
--- a/src/com/android/tv/dvr/ui/list/SeriesScheduleRowPresenter.java
+++ b/src/com/android/tv/dvr/ui/list/SeriesScheduleRowPresenter.java
@@ -22,13 +22,13 @@ import android.view.ViewGroup;
import com.android.tv.R;
import com.android.tv.common.SoftPreconditions;
-import com.android.tv.dvr.DvrUiHelper;
+import com.android.tv.dvr.ui.DvrUiHelper;
import com.android.tv.util.Utils;
/**
* A RowPresenter for series schedule row.
*/
-public class SeriesScheduleRowPresenter extends ScheduleRowPresenter {
+class SeriesScheduleRowPresenter extends ScheduleRowPresenter {
private static final String TAG = "SeriesRowPresenter";
private boolean mLtr;
@@ -74,13 +74,8 @@ public class SeriesScheduleRowPresenter extends ScheduleRowPresenter {
viewHolder.getProgramTitleView().setCompoundDrawablePadding(getContext()
.getResources().getDimensionPixelOffset(
R.dimen.dvr_schedules_warning_icon_padding));
- if (mLtr) {
- viewHolder.getProgramTitleView().setCompoundDrawablesWithIntrinsicBounds(
- R.drawable.ic_warning_gray600_36dp, 0, 0, 0);
- } else {
- viewHolder.getProgramTitleView().setCompoundDrawablesWithIntrinsicBounds(
- 0, 0, R.drawable.ic_warning_gray600_36dp, 0);
- }
+ viewHolder.getProgramTitleView().setCompoundDrawablesRelativeWithIntrinsicBounds(
+ R.drawable.ic_warning_gray600_36dp, 0, 0, 0);
} else {
viewHolder.getProgramTitleView().setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
}
@@ -88,9 +83,7 @@ public class SeriesScheduleRowPresenter extends ScheduleRowPresenter {
@Override
protected void onInfoClicked(ScheduleRow row) {
- if (row.getSchedule() != null) {
- DvrUiHelper.startSchedulesActivity(getContext(), row.getSchedule());
- }
+ DvrUiHelper.startSchedulesActivity(getContext(), row.getSchedule());
}
@Override
diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java
new file mode 100644
index 00000000..2437d1f5
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.tv.dvr.ui.playback;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.tv.R;
+import com.android.tv.TvApplication;
+import com.android.tv.dvr.data.RecordedProgram;
+
+/**
+ * Activity to play a {@link RecordedProgram}.
+ */
+public class DvrPlaybackActivity extends Activity {
+ private static final String TAG = "DvrPlaybackActivity";
+ private static final boolean DEBUG = false;
+
+ private DvrPlaybackOverlayFragment mOverlayFragment;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ TvApplication.setCurrentRunningProcess(this, true);
+ if (DEBUG) Log.d(TAG, "onCreate");
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_dvr_playback);
+ mOverlayFragment = (DvrPlaybackOverlayFragment) getFragmentManager()
+ .findFragmentById(R.id.dvr_playback_controls_fragment);
+ }
+
+ @Override
+ public void onVisibleBehindCanceled() {
+ if (DEBUG) Log.d(TAG, "onVisibleBehindCanceled");
+ super.onVisibleBehindCanceled();
+ finish();
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ mOverlayFragment.onNewIntent(intent);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ float density = getResources().getDisplayMetrics().density;
+ mOverlayFragment.onWindowSizeChanged((int) (newConfig.screenWidthDp * density),
+ (int) (newConfig.screenHeightDp * density));
+ }
+} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/DvrPlaybackCardPresenter.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackCardPresenter.java
index 8c4c856c..4bd121b1 100644
--- a/src/com/android/tv/dvr/ui/DvrPlaybackCardPresenter.java
+++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackCardPresenter.java
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.playback;
import android.content.Context;
import android.content.Intent;
-import android.content.res.Resources;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
@@ -26,22 +25,25 @@ import android.view.View.OnClickListener;
import android.view.ViewGroup;
import com.android.tv.R;
-import com.android.tv.dvr.RecordedProgram;
-import com.android.tv.dvr.DvrPlaybackActivity;
+import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.dvr.ui.browse.RecordedProgramPresenter;
+import com.android.tv.dvr.ui.browse.RecordingCardView;
import com.android.tv.util.Utils;
/**
* This class is used to generate Views and bind Objects for related recordings in DVR playback.
*/
-public class DvrPlaybackCardPresenter extends RecordedProgramPresenter {
+class DvrPlaybackCardPresenter extends RecordedProgramPresenter {
private static final String TAG = "DvrPlaybackCardPresenter";
private static final boolean DEBUG = false;
private final int mRelatedRecordingCardWidth;
private final int mRelatedRecordingCardHeight;
+ private final DvrPlaybackOverlayFragment mFragment;
- DvrPlaybackCardPresenter(Context context) {
+ DvrPlaybackCardPresenter(Context context, DvrPlaybackOverlayFragment fragment) {
super(context);
+ mFragment = fragment;
mRelatedRecordingCardWidth =
context.getResources().getDimensionPixelSize(R.dimen.dvr_related_recordings_width);
mRelatedRecordingCardHeight =
@@ -50,9 +52,8 @@ public class DvrPlaybackCardPresenter extends RecordedProgramPresenter {
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
- Resources res = parent.getResources();
RecordingCardView view = new RecordingCardView(
- getContext(), mRelatedRecordingCardWidth, mRelatedRecordingCardHeight);
+ getContext(), mRelatedRecordingCardWidth, mRelatedRecordingCardHeight, true);
return new ViewHolder(view);
}
@@ -61,6 +62,10 @@ public class DvrPlaybackCardPresenter extends RecordedProgramPresenter {
return new OnClickListener() {
@Override
public void onClick(View v) {
+ // Disable fading of overlay fragment to prevent the layout blinking while updating
+ // new playback states and info. The fading enabled status will be reset during
+ // playback state changing, in DvrPlaybackControlHelper.onStateChanged().
+ mFragment.setFadingEnabled(false);
long programId = ((RecordedProgram) v.getTag()).getId();
if (DEBUG) Log.d(TAG, "Play Related Recording:" + programId);
Intent intent = new Intent(getContext(), DvrPlaybackActivity.class);
@@ -69,14 +74,4 @@ public class DvrPlaybackCardPresenter extends RecordedProgramPresenter {
}
};
}
-
- @Override
- protected String getDescription(RecordedProgram program) {
- String description = program.getDescription();
- if (TextUtils.isEmpty(description)) {
- description =
- getContext().getResources().getString(R.string.dvr_msg_no_program_description);
- }
- return description;
- }
} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/DvrPlaybackControlHelper.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackControlHelper.java
index 0bc4ecb1..4658a328 100644
--- a/src/com/android/tv/dvr/ui/DvrPlaybackControlHelper.java
+++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackControlHelper.java
@@ -14,19 +14,26 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.playback;
import android.app.Activity;
+import android.content.Context;
import android.graphics.drawable.Drawable;
import android.media.MediaMetadata;
import android.media.session.MediaController;
import android.media.session.MediaController.TransportControls;
import android.media.session.PlaybackState;
+import android.media.tv.TvTrackInfo;
+import android.os.Bundle;
import android.support.v17.leanback.app.PlaybackControlGlue;
import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter;
import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
import android.support.v17.leanback.widget.OnActionClickedListener;
import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.PlaybackControlsRow.ClosedCaptioningAction;
+import android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction;
import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
import android.support.v17.leanback.widget.RowPresenter;
import android.text.TextUtils;
@@ -37,19 +44,18 @@ import android.view.View;
import com.android.tv.R;
import com.android.tv.util.TimeShiftUtils;
+import java.util.ArrayList;
+
/**
* A helper class to assist {@link DvrPlaybackOverlayFragment} to manage its controls row and
* send command to the media controller. It also helps to update playback states displayed in the
* fragment according to information the media session provides.
*/
-public class DvrPlaybackControlHelper extends PlaybackControlGlue {
+class DvrPlaybackControlHelper extends PlaybackControlGlue {
private static final String TAG = "DvrPlaybackControlHelper";
private static final boolean DEBUG = false;
- /**
- * Indicates the ID of the media under playback is unknown.
- */
- public static int UNKNOWN_MEDIA_ID = -1;
+ private static final int AUDIO_ACTION_ID = 1001;
private int mPlaybackState = PlaybackState.STATE_NONE;
private int mPlaybackSpeedLevel;
@@ -60,6 +66,9 @@ public class DvrPlaybackControlHelper extends PlaybackControlGlue {
private final MediaController.Callback mMediaControllerCallback = new MediaControllerCallback();
private final TransportControls mTransportControls;
private final int mExtraPaddingTopForNoDescription;
+ private final ArrayObjectAdapter mSecondaryActionsAdapter;
+ private final MultiAction mClosedCaptioningAction;
+ private final MultiAction mMultiAudioAction;
public DvrPlaybackControlHelper(Activity activity, DvrPlaybackOverlayFragment overlayFragment) {
super(activity, overlayFragment, new int[TimeShiftUtils.MAX_SPEED_LEVEL + 1]);
@@ -68,11 +77,15 @@ public class DvrPlaybackControlHelper extends PlaybackControlGlue {
mTransportControls = mMediaController.getTransportControls();
mExtraPaddingTopForNoDescription = activity.getResources()
.getDimensionPixelOffset(R.dimen.dvr_playback_controls_extra_padding_top);
+ mSecondaryActionsAdapter = new ArrayObjectAdapter(new ControlButtonPresenterSelector());
+ mClosedCaptioningAction = new ClosedCaptioningAction(activity);
+ mMultiAudioAction = new MultiAudioAction(activity);
}
@Override
public PlaybackControlsRowPresenter createControlsRowAndPresenter() {
PlaybackControlsRow controlsRow = new PlaybackControlsRow(this);
+ controlsRow.setSecondaryActionsAdapter(mSecondaryActionsAdapter);
setControlsRow(controlsRow);
AbstractDetailsDescriptionPresenter detailsPresenter =
new AbstractDetailsDescriptionPresenter() {
@@ -116,7 +129,21 @@ public class DvrPlaybackControlHelper extends PlaybackControlGlue {
@Override
public void onActionClicked(Action action) {
if (mReadyToControl) {
- DvrPlaybackControlHelper.super.onActionClicked(action);
+ int trackType;
+ if (action.getId() == mClosedCaptioningAction.getId()) {
+ trackType = TvTrackInfo.TYPE_SUBTITLE;
+ } else if (action.getId() == AUDIO_ACTION_ID) {
+ trackType = TvTrackInfo.TYPE_AUDIO;
+ } else {
+ DvrPlaybackControlHelper.super.onActionClicked(action);
+ return;
+ }
+ ArrayList<TvTrackInfo> trackInfos =
+ ((DvrPlaybackOverlayFragment) getFragment()).getTracks(trackType);
+ if (!trackInfos.isEmpty()) {
+ showSideFragment(trackInfos, ((DvrPlaybackOverlayFragment)
+ getFragment()).getSelectedTrackId(trackType));
+ }
}
}
});
@@ -158,10 +185,10 @@ public class DvrPlaybackControlHelper extends PlaybackControlGlue {
/**
* Returns the ID of the media under playback.
*/
- public long getMediaId() {
+ public String getMediaId() {
MediaMetadata mediaMetadata = mMediaController.getMetadata();
- return mediaMetadata == null ? UNKNOWN_MEDIA_ID
- : mediaMetadata.getLong(MediaMetadata.METADATA_KEY_MEDIA_ID);
+ return mediaMetadata == null ? null
+ : mediaMetadata.getString(MediaMetadata.METADATA_KEY_MEDIA_ID);
}
@Override
@@ -217,6 +244,37 @@ public class DvrPlaybackControlHelper extends PlaybackControlGlue {
mMediaController.unregisterCallback(mMediaControllerCallback);
}
+ /**
+ * Update the secondary controls row.
+ * @param hasClosedCaption {@code true} to show the closed caption selection button,
+ * {@code false} to hide it.
+ * @param hasMultiAudio {@code true} to show the audio track selection button,
+ * {@code false} to hide it.
+ */
+ public void updateSecondaryRow(boolean hasClosedCaption, boolean hasMultiAudio) {
+ if (hasClosedCaption) {
+ if (mSecondaryActionsAdapter.indexOf(mClosedCaptioningAction) < 0) {
+ mSecondaryActionsAdapter.add(0, mClosedCaptioningAction);
+ }
+ } else {
+ mSecondaryActionsAdapter.remove(mClosedCaptioningAction);
+ }
+ if (hasMultiAudio) {
+ if (mSecondaryActionsAdapter.indexOf(mMultiAudioAction) < 0) {
+ mSecondaryActionsAdapter.add(mMultiAudioAction);
+ }
+ } else {
+ mSecondaryActionsAdapter.remove(mMultiAudioAction);
+ }
+ }
+
+ /**
+ * Returns if the secondary controls row has any buttons and thus should be shown.
+ */
+ public boolean hasSecondaryRow() {
+ return mSecondaryActionsAdapter.size() != 0;
+ }
+
@Override
protected void startPlayback(int speedId) {
if (getCurrentSpeedId() == speedId) {
@@ -251,6 +309,14 @@ public class DvrPlaybackControlHelper extends PlaybackControlGlue {
// Do nothing.
}
+ /**
+ * Notifies closed caption being enabled/disabled to update related UI.
+ */
+ void onSubtitleTrackStateChanged(boolean enabled) {
+ mClosedCaptioningAction.setIndex(enabled ?
+ ClosedCaptioningAction.ON : ClosedCaptioningAction.OFF);
+ }
+
private void onStateChanged(int state, long positionMs, int speedLevel) {
if (DEBUG) Log.d(TAG, "onStateChanged");
getControlsRow().setCurrentTime((int) positionMs);
@@ -297,6 +363,19 @@ public class DvrPlaybackControlHelper extends PlaybackControlGlue {
onStateChanged();
}
+ private void showSideFragment(ArrayList<TvTrackInfo> trackInfos, String selectedTrackId) {
+ Bundle args = new Bundle();
+ args.putParcelableArrayList(DvrPlaybackSideFragment.TRACK_INFOS, trackInfos);
+ args.putString(DvrPlaybackSideFragment.SELECTED_TRACK_ID, selectedTrackId);
+ DvrPlaybackSideFragment sideFragment = new DvrPlaybackSideFragment();
+ sideFragment.setArguments(args);
+ getFragment().getFragmentManager().beginTransaction()
+ .hide(getFragment())
+ .replace(R.id.dvr_playback_side_fragment, sideFragment)
+ .addToBackStack(null)
+ .commit();
+ }
+
private class MediaControllerCallback extends MediaController.Callback {
@Override
public void onPlaybackStateChanged(PlaybackState state) {
@@ -310,4 +389,11 @@ public class DvrPlaybackControlHelper extends PlaybackControlGlue {
((DvrPlaybackOverlayFragment) getFragment()).onMediaControllerUpdated();
}
}
+
+ private static class MultiAudioAction extends MultiAction {
+ MultiAudioAction(Context context) {
+ super(AUDIO_ACTION_ID);
+ setDrawables(new Drawable[]{context.getDrawable(R.drawable.ic_tvoption_multi_track)});
+ }
+ }
} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java
new file mode 100644
index 00000000..843d2dbe
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.tv.dvr.ui.playback;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.media.MediaMetadata;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.media.tv.TvContract;
+import android.os.AsyncTask;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+
+import com.android.tv.R;
+import com.android.tv.TvApplication;
+import com.android.tv.common.SoftPreconditions;
+import com.android.tv.data.Channel;
+import com.android.tv.data.ChannelDataManager;
+import com.android.tv.dvr.DvrWatchedPositionManager;
+import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.util.ImageLoader;
+import com.android.tv.util.TimeShiftUtils;
+import com.android.tv.util.Utils;
+
+class DvrPlaybackMediaSessionHelper {
+ private static final String TAG = "DvrPlaybackMediaSessionHelper";
+ private static final boolean DEBUG = false;
+
+ private int mNowPlayingCardWidth;
+ private int mNowPlayingCardHeight;
+ private int mSpeedLevel;
+ private long mProgramDurationMs;
+
+ private Activity mActivity;
+ private DvrPlayer mDvrPlayer;
+ private MediaSession mMediaSession;
+ private final DvrWatchedPositionManager mDvrWatchedPositionManager;
+ private final ChannelDataManager mChannelDataManager;
+
+ public DvrPlaybackMediaSessionHelper(Activity activity, String mediaSessionTag,
+ DvrPlayer dvrPlayer, DvrPlaybackOverlayFragment overlayFragment) {
+ mActivity = activity;
+ mDvrPlayer = dvrPlayer;
+ mDvrWatchedPositionManager =
+ TvApplication.getSingletons(activity).getDvrWatchedPositionManager();
+ mChannelDataManager = TvApplication.getSingletons(activity).getChannelDataManager();
+ mDvrPlayer.setCallback(new DvrPlayer.DvrPlayerCallback() {
+ @Override
+ public void onPlaybackStateChanged(int playbackState, int playbackSpeed) {
+ updateMediaSessionPlaybackState();
+ }
+
+ @Override
+ public void onPlaybackPositionChanged(long positionMs) {
+ updateMediaSessionPlaybackState();
+ if (mDvrPlayer.isPlaybackPrepared()) {
+ mDvrWatchedPositionManager
+ .setWatchedPosition(mDvrPlayer.getProgram().getId(), positionMs);
+ }
+ }
+
+ @Override
+ public void onPlaybackEnded() {
+ // TODO: Deal with watched over recordings in DVR library
+ RecordedProgram nextEpisode =
+ overlayFragment.getNextEpisode(mDvrPlayer.getProgram());
+ if (nextEpisode == null) {
+ mDvrPlayer.reset();
+ mActivity.finish();
+ } else {
+ Intent intent = new Intent(activity, DvrPlaybackActivity.class);
+ intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, nextEpisode.getId());
+ mActivity.startActivity(intent);
+ }
+ }
+ });
+ initializeMediaSession(mediaSessionTag);
+ }
+
+ /**
+ * Stops DVR player and release media session.
+ */
+ public void release() {
+ if (mDvrPlayer != null) {
+ mDvrPlayer.reset();
+ }
+ if (mMediaSession != null) {
+ mMediaSession.release();
+ mMediaSession = null;
+ }
+ }
+
+ /**
+ * Updates media session's playback state and speed.
+ */
+ public void updateMediaSessionPlaybackState() {
+ mMediaSession.setPlaybackState(new PlaybackState.Builder()
+ .setState(mDvrPlayer.getPlaybackState(), mDvrPlayer.getPlaybackPosition(),
+ mSpeedLevel).build());
+ }
+
+ /**
+ * Sets the recorded program for playback.
+ *
+ * @param program The recorded program to play. {@code null} to reset the DVR player.
+ */
+ public void setupPlayback(RecordedProgram program, long seekPositionMs) {
+ if (program != null) {
+ mDvrPlayer.setProgram(program, seekPositionMs);
+ setupMediaSession(program);
+ } else {
+ mDvrPlayer.reset();
+ mMediaSession.setActive(false);
+ }
+ }
+
+ /**
+ * Returns the recorded program now playing.
+ */
+ public RecordedProgram getProgram() {
+ return mDvrPlayer.getProgram();
+ }
+
+ /**
+ * Checks if the recorded program is the same as now playing one.
+ */
+ public boolean isCurrentProgram(RecordedProgram program) {
+ return program != null && program.equals(getProgram());
+ }
+
+ /**
+ * Returns playback state.
+ */
+ public int getPlaybackState() {
+ return mDvrPlayer.getPlaybackState();
+ }
+
+ /**
+ * Returns the underlying DVR player.
+ */
+ public DvrPlayer getDvrPlayer() {
+ return mDvrPlayer;
+ }
+
+ private void initializeMediaSession(String mediaSessionTag) {
+ mMediaSession = new MediaSession(mActivity, mediaSessionTag);
+ mMediaSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
+ | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
+ mNowPlayingCardWidth = mActivity.getResources()
+ .getDimensionPixelSize(R.dimen.notif_card_img_max_width);
+ mNowPlayingCardHeight = mActivity.getResources()
+ .getDimensionPixelSize(R.dimen.notif_card_img_height);
+ mMediaSession.setCallback(new MediaSessionCallback());
+ mActivity.setMediaController(
+ new MediaController(mActivity, mMediaSession.getSessionToken()));
+ updateMediaSessionPlaybackState();
+ }
+
+ private void setupMediaSession(RecordedProgram program) {
+ mProgramDurationMs = program.getDurationMillis();
+ String cardTitleText = program.getTitle();
+ if (TextUtils.isEmpty(cardTitleText)) {
+ Channel channel = mChannelDataManager.getChannel(program.getChannelId());
+ cardTitleText = (channel != null) ? channel.getDisplayName()
+ : mActivity.getString(R.string.no_program_information);
+ }
+ final MediaMetadata currentMetadata = updateMetadataTextInfo(program.getId(), cardTitleText,
+ program.getDescription(), mProgramDurationMs);
+ String posterArtUri = program.getPosterArtUri();
+ if (posterArtUri == null) {
+ posterArtUri = TvContract.buildChannelLogoUri(program.getChannelId()).toString();
+ }
+ updatePosterArt(program, currentMetadata, null, posterArtUri);
+ mMediaSession.setActive(true);
+ }
+
+ private void updatePosterArt(RecordedProgram program, MediaMetadata currentMetadata,
+ @Nullable Bitmap posterArt, @Nullable String posterArtUri) {
+ if (posterArt != null) {
+ updateMetadataImageInfo(program, currentMetadata, posterArt, 0);
+ } else if (posterArtUri != null) {
+ ImageLoader.loadBitmap(mActivity, posterArtUri, mNowPlayingCardWidth,
+ mNowPlayingCardHeight,
+ new ProgramPosterArtCallback(mActivity, program, currentMetadata));
+ } else {
+ updateMetadataImageInfo(program, currentMetadata, null, R.drawable.default_now_card);
+ }
+ }
+
+ private class ProgramPosterArtCallback extends
+ ImageLoader.ImageLoaderCallback<Activity> {
+ private final RecordedProgram mRecordedProgram;
+ private final MediaMetadata mCurrentMetadata;
+
+ public ProgramPosterArtCallback(Activity activity, RecordedProgram program,
+ MediaMetadata metadata) {
+ super(activity);
+ mRecordedProgram = program;
+ mCurrentMetadata = metadata;
+ }
+
+ @Override
+ public void onBitmapLoaded(Activity activity, @Nullable Bitmap posterArt) {
+ if (isCurrentProgram(mRecordedProgram)) {
+ updatePosterArt(mRecordedProgram, mCurrentMetadata, posterArt, null);
+ }
+ }
+ }
+
+ private MediaMetadata updateMetadataTextInfo(final long programId, final String title,
+ final String subtitle, final long duration) {
+ MediaMetadata.Builder builder = new MediaMetadata.Builder();
+ builder.putString(MediaMetadata.METADATA_KEY_MEDIA_ID, Long.toString(programId))
+ .putString(MediaMetadata.METADATA_KEY_TITLE, title)
+ .putLong(MediaMetadata.METADATA_KEY_DURATION, duration);
+ if (subtitle != null) {
+ builder.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, subtitle);
+ }
+ MediaMetadata metadata = builder.build();
+ mMediaSession.setMetadata(metadata);
+ return metadata;
+ }
+
+ private void updateMetadataImageInfo(final RecordedProgram program,
+ final MediaMetadata currentMetadata, final Bitmap posterArt, final int imageResId) {
+ if (mMediaSession != null && (posterArt != null || imageResId != 0)) {
+ MediaMetadata.Builder builder = new MediaMetadata.Builder(currentMetadata);
+ if (posterArt != null) {
+ builder.putBitmap(MediaMetadata.METADATA_KEY_ART, posterArt);
+ mMediaSession.setMetadata(builder.build());
+ } else {
+ new AsyncTask<Void, Void, Bitmap>() {
+ @Override
+ protected Bitmap doInBackground(Void... arg0) {
+ return BitmapFactory.decodeResource(mActivity.getResources(), imageResId);
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap programPosterArt) {
+ if (mMediaSession != null && programPosterArt != null
+ && isCurrentProgram(program)) {
+ builder.putBitmap(MediaMetadata.METADATA_KEY_ART, programPosterArt);
+ mMediaSession.setMetadata(builder.build());
+ }
+ }
+ }.execute();
+ }
+ }
+ }
+
+ // An event was triggered by MediaController.TransportControls and must be handled here.
+ // Here we update the media itself to act on the event that was triggered.
+ private class MediaSessionCallback extends MediaSession.Callback {
+ @Override
+ public void onPrepare() {
+ if (!mDvrPlayer.isPlaybackPrepared()) {
+ mDvrPlayer.prepare(true);
+ }
+ }
+
+ @Override
+ public void onPlay() {
+ if (mDvrPlayer.isPlaybackPrepared()) {
+ mDvrPlayer.play();
+ }
+ }
+
+ @Override
+ public void onPause() {
+ if (mDvrPlayer.isPlaybackPrepared()) {
+ mDvrPlayer.pause();
+ }
+ }
+
+ @Override
+ public void onFastForward() {
+ if (!mDvrPlayer.isPlaybackPrepared()) {
+ return;
+ }
+ if (mDvrPlayer.getPlaybackState() == PlaybackState.STATE_FAST_FORWARDING) {
+ if (mSpeedLevel < TimeShiftUtils.MAX_SPEED_LEVEL) {
+ mSpeedLevel++;
+ } else {
+ return;
+ }
+ } else {
+ mSpeedLevel = 0;
+ }
+ mDvrPlayer.fastForward(
+ TimeShiftUtils.getPlaybackSpeed(mSpeedLevel, mProgramDurationMs));
+ }
+
+ @Override
+ public void onRewind() {
+ if (!mDvrPlayer.isPlaybackPrepared()) {
+ return;
+ }
+ if (mDvrPlayer.getPlaybackState() == PlaybackState.STATE_REWINDING) {
+ if (mSpeedLevel < TimeShiftUtils.MAX_SPEED_LEVEL) {
+ mSpeedLevel++;
+ } else {
+ return;
+ }
+ } else {
+ mSpeedLevel = 0;
+ }
+ mDvrPlayer.rewind(TimeShiftUtils.getPlaybackSpeed(mSpeedLevel, mProgramDurationMs));
+ }
+
+ @Override
+ public void onSeekTo(long positionMs) {
+ if (mDvrPlayer.isPlaybackPrepared()) {
+ mDvrPlayer.seekTo(positionMs);
+ }
+ }
+ }
+}
diff --git a/src/com/android/tv/dvr/ui/DvrPlaybackOverlayFragment.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java
index 51ec93b8..ff907182 100644
--- a/src/com/android/tv/dvr/ui/DvrPlaybackOverlayFragment.java
+++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java
@@ -14,13 +14,14 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.playback;
import android.content.Context;
import android.content.Intent;
import android.graphics.Point;
import android.hardware.display.DisplayManager;
import android.media.tv.TvContentRating;
+import android.media.tv.TvTrackInfo;
import android.os.Bundle;
import android.media.session.PlaybackState;
import android.media.tv.TvInputManager;
@@ -30,7 +31,6 @@ import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.ClassPresenterSelector;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.ListRowPresenter;
import android.support.v17.leanback.widget.PlaybackControlsRow;
import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
import android.support.v17.leanback.widget.SinglePresenterSelector;
@@ -38,20 +38,25 @@ import android.view.Display;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
-import android.text.TextUtils;
import android.util.Log;
import com.android.tv.R;
import com.android.tv.TvApplication;
import com.android.tv.data.BaseProgram;
-import com.android.tv.dvr.RecordedProgram;
import com.android.tv.dialog.PinDialogFragment;
import com.android.tv.dvr.DvrDataManager;
-import com.android.tv.dvr.DvrPlayer;
-import com.android.tv.dvr.DvrPlaybackMediaSessionHelper;
+import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.dvr.data.SeriesRecording;
+import com.android.tv.dvr.ui.SortedArrayAdapter;
+import com.android.tv.dvr.ui.browse.DvrListRowPresenter;
import com.android.tv.parental.ContentRatingsManager;
+import com.android.tv.util.TvSettings;
+import com.android.tv.util.TvTrackInfoUtils;
import com.android.tv.util.Utils;
+import java.util.List;
+import java.util.ArrayList;
+
public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
// TODO: Handles audio focus. Deals with block and ratings.
private static final String TAG = "DvrPlaybackOverlayFragment";
@@ -62,6 +67,7 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
// mProgram is only used to store program from intent. Don't use it elsewhere.
private RecordedProgram mProgram;
+ private DvrPlayer mDvrPlayer;
private DvrPlaybackMediaSessionHelper mMediaSessionHelper;
private DvrPlaybackControlHelper mPlaybackControlHelper;
private ArrayObjectAdapter mRowsAdapter;
@@ -72,19 +78,30 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
private TvView mTvView;
private View mBlockScreenView;
private ListRow mRelatedRecordingsRow;
- private int mExtraPaddingNoRelatedRow;
+ private int mPaddingWithoutRelatedRow;
+ private int mPaddingWithoutSecondaryRow;
private int mWindowWidth;
private int mWindowHeight;
private float mAppliedAspectRatio;
private float mWindowAspectRatio;
private boolean mPinChecked;
+ private DvrPlayer.OnTrackSelectedListener mOnSubtitleTrackSelectedListener =
+ new DvrPlayer.OnTrackSelectedListener() {
+ @Override
+ public void onTrackSelected(String selectedTrackId) {
+ mPlaybackControlHelper.onSubtitleTrackStateChanged(selectedTrackId != null);
+ mRowsAdapter.notifyArrayItemRangeChanged(0, 1);
+ }
+ };
@Override
public void onCreate(Bundle savedInstanceState) {
if (DEBUG) Log.d(TAG, "onCreate");
super.onCreate(savedInstanceState);
- mExtraPaddingNoRelatedRow = getActivity().getResources()
- .getDimensionPixelOffset(R.dimen.dvr_playback_fragment_extra_padding_top);
+ mPaddingWithoutRelatedRow = getActivity().getResources()
+ .getDimensionPixelOffset(R.dimen.dvr_playback_overlay_padding_top_no_related_row);
+ mPaddingWithoutSecondaryRow = getActivity().getResources()
+ .getDimensionPixelOffset(R.dimen.dvr_playback_overlay_padding_top_no_secondary_row);
mDvrDataManager = TvApplication.getSingletons(getActivity()).getDvrDataManager();
mContentRatingsManager = TvApplication.getSingletons(getContext())
.getTvInputManagerHelper().getContentRatingsManager();
@@ -110,13 +127,31 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
super.onActivityCreated(savedInstanceState);
mTvView = (TvView) getActivity().findViewById(R.id.dvr_tv_view);
mBlockScreenView = getActivity().findViewById(R.id.block_screen);
+ mDvrPlayer = new DvrPlayer(mTvView);
mMediaSessionHelper = new DvrPlaybackMediaSessionHelper(
- getActivity(), MEDIA_SESSION_TAG, new DvrPlayer(mTvView), this);
+ getActivity(), MEDIA_SESSION_TAG, mDvrPlayer, this);
mPlaybackControlHelper = new DvrPlaybackControlHelper(getActivity(), this);
setUpRows();
- preparePlayback(getActivity().getIntent());
- DvrPlayer dvrPlayer = mMediaSessionHelper.getDvrPlayer();
- dvrPlayer.setAspectRatioChangedListener(new DvrPlayer.AspectRatioChangedListener() {
+ mDvrPlayer.setOnTracksAvailabilityChangedListener(
+ new DvrPlayer.OnTracksAvailabilityChangedListener() {
+ @Override
+ public void onTracksAvailabilityChanged(boolean hasClosedCaption,
+ boolean hasMultiAudio) {
+ mPlaybackControlHelper.updateSecondaryRow(hasClosedCaption, hasMultiAudio);
+ if (hasClosedCaption) {
+ mDvrPlayer.setOnTrackSelectedListener(TvTrackInfo.TYPE_SUBTITLE,
+ mOnSubtitleTrackSelectedListener);
+ selectBestMatchedTrack(TvTrackInfo.TYPE_SUBTITLE);
+ } else {
+ mDvrPlayer.setOnTrackSelectedListener(TvTrackInfo.TYPE_SUBTITLE, null);
+ }
+ if (hasMultiAudio) {
+ selectBestMatchedTrack(TvTrackInfo.TYPE_AUDIO);
+ }
+ onMediaControllerUpdated();
+ }
+ });
+ mDvrPlayer.setOnAspectRatioChangedListener(new DvrPlayer.OnAspectRatioChangedListener() {
@Override
public void onAspectRatioChanged(float videoAspectRatio) {
updateAspectRatio(videoAspectRatio);
@@ -124,7 +159,7 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
});
mPinChecked = getActivity().getIntent()
.getBooleanExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_PIN_CHECKED, false);
- dvrPlayer.setContentBlockedListener(new DvrPlayer.ContentBlockedListener() {
+ mDvrPlayer.setOnContentBlockedListener(new DvrPlayer.OnContentBlockedListener() {
@Override
public void onContentBlocked(TvContentRating rating) {
if (mPinChecked) {
@@ -149,6 +184,7 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
.show(getActivity().getFragmentManager(), PinDialogFragment.DIALOG_TAG);
}
});
+ preparePlayback(getActivity().getIntent());
}
@Override
@@ -200,6 +236,9 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
updateAspectRatio(mAppliedAspectRatio);
}
+ /**
+ * Returns next recorded episode in the same series as now playing program.
+ */
public RecordedProgram getNextEpisode(RecordedProgram program) {
int position = mRelatedRecordingsRowAdapter.findInsertPosition(program);
if (position == mRelatedRecordingsRowAdapter.size()) {
@@ -209,16 +248,92 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
}
}
+ /**
+ * Returns the tracks of the give type of the current playback.
+
+ * @param trackType Should be {@link TvTrackInfo#TYPE_SUBTITLE}
+ * or {@link TvTrackInfo#TYPE_AUDIO}. Or returns {@code null}.
+ */
+ public ArrayList<TvTrackInfo> getTracks(int trackType) {
+ if (trackType == TvTrackInfo.TYPE_AUDIO) {
+ return mDvrPlayer.getAudioTracks();
+ } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
+ return mDvrPlayer.getSubtitleTracks();
+ }
+ return null;
+ }
+
+ /**
+ * Returns the ID of the selected track of the given type.
+ */
+ public String getSelectedTrackId(int trackType) {
+ return mDvrPlayer.getSelectedTrackId(trackType);
+ }
+
+ /**
+ * Returns the language setting of the given track type.
+
+ * @param trackType Should be {@link TvTrackInfo#TYPE_SUBTITLE}
+ * or {@link TvTrackInfo#TYPE_AUDIO}.
+ * @return {@code null} if no language has been set for the given track type.
+ */
+ TvTrackInfo getTrackSetting(int trackType) {
+ return TvSettings.getDvrPlaybackTrackSettings(getContext(), trackType);
+ }
+
+ /**
+ * Selects the given audio or subtitle track for DVR playback.
+ * @param trackType Should be {@link TvTrackInfo#TYPE_SUBTITLE}
+ * or {@link TvTrackInfo#TYPE_AUDIO}.
+ * @param selectedTrack {@code null} to disable the audio or subtitle track according to
+ * trackType.
+ */
+ void selectTrack(int trackType, TvTrackInfo selectedTrack) {
+ if (mDvrPlayer.isPlaybackPrepared()) {
+ mDvrPlayer.selectTrack(trackType, selectedTrack);
+ }
+ }
+
+ /**
+ * Notifies the content of controls row or related recordings row is changed and the UI should
+ * be updated according to the change.
+ */
void onMediaControllerUpdated() {
- mRowsAdapter.notifyArrayItemRangeChanged(0, 1);
+ updateVerticalPosition();
+ mRowsAdapter.notifyArrayItemRangeChanged(0, 2);
+ }
+
+ private void selectBestMatchedTrack(int trackType) {
+ TvTrackInfo selectedTrack = getTrackSetting(trackType);
+ if (selectedTrack != null) {
+ TvTrackInfo bestMatchedTrack = TvTrackInfoUtils.getBestTrackInfo(getTracks(trackType),
+ selectedTrack.getId(), selectedTrack.getLanguage(),
+ trackType == TvTrackInfo.TYPE_AUDIO ? selectedTrack.getAudioChannelCount() : 0);
+ if (bestMatchedTrack != null && (trackType == TvTrackInfo.TYPE_AUDIO || Utils
+ .isEqualLanguage(bestMatchedTrack.getLanguage(),
+ selectedTrack.getLanguage()))) {
+ selectTrack(trackType, bestMatchedTrack);
+ return;
+ }
+ }
+ if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
+ // Disables closed captioning if there's no matched language.
+ selectTrack(TvTrackInfo.TYPE_SUBTITLE, null);
+ }
}
private void updateAspectRatio(float videoAspectRatio) {
+ if (videoAspectRatio <= 0) {
+ // We don't have video's width or height information, use window's aspect ratio.
+ videoAspectRatio = mWindowAspectRatio;
+ }
if (Math.abs(mAppliedAspectRatio - videoAspectRatio) < DISPLAY_ASPECT_RATIO_EPSILON) {
// No need to change
return;
}
- if (videoAspectRatio < mWindowAspectRatio) {
+ if (Math.abs(mWindowAspectRatio - videoAspectRatio) < DISPLAY_ASPECT_RATIO_EPSILON) {
+ ((ViewGroup) mTvView.getParent()).setPadding(0, 0, 0, 0);
+ } else if (videoAspectRatio < mWindowAspectRatio) {
int newPadding = (mWindowWidth - Math.round(mWindowHeight * videoAspectRatio)) / 2;
((ViewGroup) mTvView.getParent()).setPadding(newPadding, 0, newPadding, 0);
} else {
@@ -230,6 +345,7 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
private void preparePlayback(Intent intent) {
mMediaSessionHelper.setupPlayback(mProgram, getSeekTimeFromIntent(intent));
+ mPlaybackControlHelper.updateSecondaryRow(false, false);
getActivity().getMediaController().getTransportControls().prepare();
updateRelatedRecordingsRow();
}
@@ -239,24 +355,35 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
mRelatedRecordingsRowAdapter.clear();
long programId = mProgram.getId();
String seriesId = mProgram.getSeriesId();
- if (!TextUtils.isEmpty(seriesId)) {
+ SeriesRecording seriesRecording = mDvrDataManager.getSeriesRecording(seriesId);
+ if (seriesRecording != null) {
if (DEBUG) Log.d(TAG, "Update related recordings with:" + seriesId);
- for (RecordedProgram program : mDvrDataManager.getRecordedPrograms()) {
- if (seriesId.equals(program.getSeriesId()) && programId != program.getId()) {
+ List<RecordedProgram> relatedPrograms =
+ mDvrDataManager.getRecordedPrograms(seriesRecording.getId());
+ for (RecordedProgram program : relatedPrograms) {
+ if (programId != program.getId()) {
mRelatedRecordingsRowAdapter.add(program);
}
}
}
- View view = getView();
if (mRelatedRecordingsRowAdapter.size() == 0) {
mRowsAdapter.remove(mRelatedRecordingsRow);
- view.setPadding(view.getPaddingLeft(), mExtraPaddingNoRelatedRow,
- view.getPaddingRight(), view.getPaddingBottom());
} else if (wasEmpty){
mRowsAdapter.add(mRelatedRecordingsRow);
- view.setPadding(view.getPaddingLeft(), 0,
- view.getPaddingRight(), view.getPaddingBottom());
}
+ onMediaControllerUpdated();
+ }
+
+ private void updateVerticalPosition() {
+ int verticalPadding = 0;
+ verticalPadding +=
+ mRelatedRecordingsRowAdapter.size() == 0 ? mPaddingWithoutRelatedRow : 0;
+ verticalPadding +=
+ mPlaybackControlHelper.hasSecondaryRow() ? 0 : mPaddingWithoutSecondaryRow;
+ if (DEBUG) Log.d(TAG, "New controls padding: " + verticalPadding);
+ View view = getView();
+ view.setPadding(view.getPaddingLeft(), verticalPadding,
+ view.getPaddingRight(), view.getPaddingBottom());
}
private void setUpRows() {
@@ -265,7 +392,7 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
ClassPresenterSelector selector = new ClassPresenterSelector();
selector.addClassPresenter(PlaybackControlsRow.class, controlsRowPresenter);
- selector.addClassPresenter(ListRow.class, new ListRowPresenter());
+ selector.addClassPresenter(ListRow.class, new DvrListRowPresenter(getContext()));
mRowsAdapter = new ArrayObjectAdapter(selector);
mRowsAdapter.add(mPlaybackControlHelper.getControlsRow());
@@ -274,7 +401,7 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
}
private ListRow getRelatedRecordingsRow() {
- mRelatedRecordingCardPresenter = new DvrPlaybackCardPresenter(getActivity());
+ mRelatedRecordingCardPresenter = new DvrPlaybackCardPresenter(getActivity(), this);
mRelatedRecordingsRowAdapter = new RelatedRecordingsAdapter(mRelatedRecordingCardPresenter);
HeaderItem header = new HeaderItem(0,
getActivity().getString(R.string.dvr_playback_related_recordings));
@@ -297,7 +424,7 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
}
@Override
- long getId(BaseProgram item) {
+ public long getId(BaseProgram item) {
return item.getId();
}
}
diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackSideFragment.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackSideFragment.java
new file mode 100644
index 00000000..e49870f1
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackSideFragment.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tv.dvr.ui.playback;
+
+import android.media.tv.TvTrackInfo;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v17.leanback.app.GuidedStepFragment;
+import android.support.v17.leanback.widget.GuidedAction;
+import android.text.TextUtils;
+import android.transition.Transition;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.tv.R;
+import com.android.tv.util.TvSettings;
+
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Fragment for DVR playback closed-caption/multi-audio settings.
+ */
+public class DvrPlaybackSideFragment extends GuidedStepFragment {
+ /**
+ * The tag for passing track infos to side fragments.
+ */
+ public static final String TRACK_INFOS = "dvr_key_track_infos";
+ /**
+ * The tag for passing selected track's ID to side fragments.
+ */
+ public static final String SELECTED_TRACK_ID = "dvr_key_selected_track_id";
+
+ private static final int ACTION_ID_NO_SUBTITLE = -1;
+ private static final int CHECK_SET_ID = 1;
+
+ private List<TvTrackInfo> mTrackInfos;
+ private String mSelectedTrackId;
+ private TvTrackInfo mSelectedTrack;
+ private int mTrackType;
+ private DvrPlaybackOverlayFragment mOverlayFragment;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ mTrackInfos = getArguments().getParcelableArrayList(TRACK_INFOS);
+ mTrackType = mTrackInfos.get(0).getType();
+ mSelectedTrackId = getArguments().getString(SELECTED_TRACK_ID);
+ mOverlayFragment = ((DvrPlaybackOverlayFragment) getFragmentManager()
+ .findFragmentById(R.id.dvr_playback_controls_fragment));
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ public View onCreateBackgroundView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View backgroundView = super.onCreateBackgroundView(inflater, container, savedInstanceState);
+ backgroundView.setBackgroundColor(getResources()
+ .getColor(R.color.lb_playback_controls_background_light));
+ return backgroundView;
+ }
+
+ @Override
+ public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
+ if (mTrackType == TvTrackInfo.TYPE_SUBTITLE) {
+ actions.add(new GuidedAction.Builder(getActivity())
+ .id(ACTION_ID_NO_SUBTITLE)
+ .title(getString(R.string.closed_caption_option_item_off))
+ .checkSetId(CHECK_SET_ID)
+ .checked(mSelectedTrackId == null)
+ .build());
+ }
+ for (int i = 0; i < mTrackInfos.size(); i++) {
+ TvTrackInfo info = mTrackInfos.get(i);
+ boolean checked = TextUtils.equals(info.getId(), mSelectedTrackId);
+ GuidedAction action = new GuidedAction.Builder(getActivity())
+ .id(i)
+ .title(getTrackLabel(info, i))
+ .checkSetId(CHECK_SET_ID)
+ .checked(checked)
+ .build();
+ actions.add(action);
+ if (checked) {
+ mSelectedTrack = info;
+ }
+ }
+ }
+
+ @Override
+ public void onGuidedActionFocused(GuidedAction action) {
+ int actionId = (int) action.getId();
+ mOverlayFragment.selectTrack(mTrackType, actionId < 0 ? null : mTrackInfos.get(actionId));
+ }
+
+ @Override
+ public void onGuidedActionClicked(GuidedAction action) {
+ int actionId = (int) action.getId();
+ mSelectedTrack = actionId < 0 ? null : mTrackInfos.get(actionId);
+ TvSettings.setDvrPlaybackTrackSettings(getContext(), mTrackType, mSelectedTrack);
+ getFragmentManager().popBackStack();
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ // Workaround: when overlay fragment is faded out, any focus will lost due to overlay
+ // fragment's implementation. So we disable overlay fragment's fading here to prevent
+ // losing focus while users are interacting with the side fragment.
+ mOverlayFragment.setFadingEnabled(false);
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ // We disable fading of overlay fragment to prevent side fragment from losing focus,
+ // therefore we should resume it here.
+ mOverlayFragment.setFadingEnabled(true);
+ mOverlayFragment.selectTrack(mTrackType, mSelectedTrack);
+ }
+
+ private String getTrackLabel(TvTrackInfo track, int trackIndex) {
+ if (track.getLanguage() != null) {
+ return new Locale(track.getLanguage()).getDisplayName();
+ }
+ return track.getType() == TvTrackInfo.TYPE_SUBTITLE ?
+ getString(R.string.closed_caption_unknown_language, trackIndex + 1)
+ : getString(R.string.multi_audio_unknown_language);
+ }
+
+ @Override
+ protected void onProvideFragmentTransitions() {
+ super.onProvideFragmentTransitions();
+ // Excludes the background scrim from transition to prevent the blinking caused by
+ // hiding the overlay fragment and sliding in the side fragment at the same time.
+ Transition t = getEnterTransition();
+ if (t != null) {
+ t.excludeTarget(R.id.guidedstep_background, true);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlayer.java b/src/com/android/tv/dvr/ui/playback/DvrPlayer.java
new file mode 100644
index 00000000..780bfb2f
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/playback/DvrPlayer.java
@@ -0,0 +1,583 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.tv.dvr.ui.playback;
+
+import android.media.PlaybackParams;
+import android.media.tv.TvContentRating;
+import android.media.tv.TvInputManager;
+import android.media.tv.TvTrackInfo;
+import android.media.tv.TvView;
+import android.media.session.PlaybackState;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.tv.dvr.data.RecordedProgram;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+class DvrPlayer {
+ private static final String TAG = "DvrPlayer";
+ private static final boolean DEBUG = false;
+
+ /**
+ * The max rewinding speed supported by DVR player.
+ */
+ public static final int MAX_REWIND_SPEED = 256;
+ /**
+ * The max fast-forwarding speed supported by DVR player.
+ */
+ public static final int MAX_FAST_FORWARD_SPEED = 256;
+
+ private static final long SEEK_POSITION_MARGIN_MS = TimeUnit.SECONDS.toMillis(2);
+ private static final long REWIND_POSITION_MARGIN_MS = 32; // Workaround value. b/29994826
+
+ private RecordedProgram mProgram;
+ private long mInitialSeekPositionMs;
+ private final TvView mTvView;
+ private DvrPlayerCallback mCallback;
+ private OnAspectRatioChangedListener mOnAspectRatioChangedListener;
+ private OnContentBlockedListener mOnContentBlockedListener;
+ private OnTracksAvailabilityChangedListener mOnTracksAvailabilityChangedListener;
+ private OnTrackSelectedListener mOnAudioTrackSelectedListener;
+ private OnTrackSelectedListener mOnSubtitleTrackSelectedListener;
+ private String mSelectedAudioTrackId;
+ private String mSelectedSubtitleTrackId;
+ private float mAspectRatio = Float.NaN;
+ private int mPlaybackState = PlaybackState.STATE_NONE;
+ private long mTimeShiftCurrentPositionMs;
+ private boolean mPauseOnPrepared;
+ private boolean mHasClosedCaption;
+ private boolean mHasMultiAudio;
+ private final PlaybackParams mPlaybackParams = new PlaybackParams();
+ private final DvrPlayerCallback mEmptyCallback = new DvrPlayerCallback();
+ private long mStartPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
+ private boolean mTimeShiftPlayAvailable;
+
+ public static class DvrPlayerCallback {
+ /**
+ * Called when the playback position is changed. The normal updating frequency is
+ * around 1 sec., which is restricted to the implementation of
+ * {@link android.media.tv.TvInputService}.
+ */
+ public void onPlaybackPositionChanged(long positionMs) { }
+ /**
+ * Called when the playback state or the playback speed is changed.
+ */
+ public void onPlaybackStateChanged(int playbackState, int playbackSpeed) { }
+ /**
+ * Called when the playback toward the end.
+ */
+ public void onPlaybackEnded() { }
+ }
+
+ public interface OnAspectRatioChangedListener {
+ /**
+ * Called when the Video's aspect ratio is changed.
+ *
+ * @param videoAspectRatio The aspect ratio of video. 0 stands for unknown ratios.
+ * Listeners should handle it carefully.
+ */
+ void onAspectRatioChanged(float videoAspectRatio);
+ }
+
+ public interface OnContentBlockedListener {
+ /**
+ * Called when the Video's aspect ratio is changed.
+ */
+ void onContentBlocked(TvContentRating rating);
+ }
+
+ public interface OnTracksAvailabilityChangedListener {
+ /**
+ * Called when the Video's subtitle or audio tracks are changed.
+ */
+ void onTracksAvailabilityChanged(boolean hasClosedCaption, boolean hasMultiAudio);
+ }
+
+ public interface OnTrackSelectedListener {
+ /**
+ * Called when certain subtitle or audio track is selected.
+ */
+ void onTrackSelected(String selectedTrackId);
+ }
+
+ public DvrPlayer(TvView tvView) {
+ mTvView = tvView;
+ mTvView.setCaptionEnabled(true);
+ mPlaybackParams.setSpeed(1.0f);
+ setTvViewCallbacks();
+ setCallback(null);
+ }
+
+ /**
+ * Prepares playback.
+ *
+ * @param doPlay indicates DVR player do or do not start playback after media is prepared.
+ */
+ public void prepare(boolean doPlay) throws IllegalStateException {
+ if (DEBUG) Log.d(TAG, "prepare()");
+ if (mProgram == null) {
+ throw new IllegalStateException("Recorded program not set");
+ } else if (mPlaybackState != PlaybackState.STATE_NONE) {
+ throw new IllegalStateException("Playback is already prepared");
+ }
+ mTvView.timeShiftPlay(mProgram.getInputId(), mProgram.getUri());
+ mPlaybackState = PlaybackState.STATE_CONNECTING;
+ mPauseOnPrepared = !doPlay;
+ mCallback.onPlaybackStateChanged(mPlaybackState, 1);
+ }
+
+ /**
+ * Resumes playback.
+ */
+ public void play() throws IllegalStateException {
+ if (DEBUG) Log.d(TAG, "play()");
+ if (!isPlaybackPrepared()) {
+ throw new IllegalStateException("Recorded program not set or video not ready yet");
+ }
+ switch (mPlaybackState) {
+ case PlaybackState.STATE_FAST_FORWARDING:
+ case PlaybackState.STATE_REWINDING:
+ setPlaybackSpeed(1);
+ break;
+ default:
+ mTvView.timeShiftResume();
+ }
+ mPlaybackState = PlaybackState.STATE_PLAYING;
+ mCallback.onPlaybackStateChanged(mPlaybackState, 1);
+ }
+
+ /**
+ * Pauses playback.
+ */
+ public void pause() throws IllegalStateException {
+ if (DEBUG) Log.d(TAG, "pause()");
+ if (!isPlaybackPrepared()) {
+ throw new IllegalStateException("Recorded program not set or playback not started yet");
+ }
+ switch (mPlaybackState) {
+ case PlaybackState.STATE_FAST_FORWARDING:
+ case PlaybackState.STATE_REWINDING:
+ setPlaybackSpeed(1);
+ // falls through
+ case PlaybackState.STATE_PLAYING:
+ mTvView.timeShiftPause();
+ mPlaybackState = PlaybackState.STATE_PAUSED;
+ break;
+ default:
+ break;
+ }
+ mCallback.onPlaybackStateChanged(mPlaybackState, 1);
+ }
+
+ /**
+ * Fast-forwards playback with the given speed. If the given speed is larger than
+ * {@value #MAX_FAST_FORWARD_SPEED}, uses {@value #MAX_FAST_FORWARD_SPEED}.
+ */
+ public void fastForward(int speed) throws IllegalStateException {
+ if (DEBUG) Log.d(TAG, "fastForward()");
+ if (!isPlaybackPrepared()) {
+ throw new IllegalStateException("Recorded program not set or playback not started yet");
+ }
+ if (speed <= 0) {
+ throw new IllegalArgumentException("Speed cannot be negative or 0");
+ }
+ if (mTimeShiftCurrentPositionMs >= mProgram.getDurationMillis() - SEEK_POSITION_MARGIN_MS) {
+ return;
+ }
+ speed = Math.min(speed, MAX_FAST_FORWARD_SPEED);
+ if (DEBUG) Log.d(TAG, "Let's play with speed: " + speed);
+ setPlaybackSpeed(speed);
+ mPlaybackState = PlaybackState.STATE_FAST_FORWARDING;
+ mCallback.onPlaybackStateChanged(mPlaybackState, speed);
+ }
+
+ /**
+ * Rewinds playback with the given speed. If the given speed is larger than
+ * {@value #MAX_REWIND_SPEED}, uses {@value #MAX_REWIND_SPEED}.
+ */
+ public void rewind(int speed) throws IllegalStateException {
+ if (DEBUG) Log.d(TAG, "rewind()");
+ if (!isPlaybackPrepared()) {
+ throw new IllegalStateException("Recorded program not set or playback not started yet");
+ }
+ if (speed <= 0) {
+ throw new IllegalArgumentException("Speed cannot be negative or 0");
+ }
+ if (mTimeShiftCurrentPositionMs <= REWIND_POSITION_MARGIN_MS) {
+ return;
+ }
+ speed = Math.min(speed, MAX_REWIND_SPEED);
+ if (DEBUG) Log.d(TAG, "Let's play with speed: " + speed);
+ setPlaybackSpeed(-speed);
+ mPlaybackState = PlaybackState.STATE_REWINDING;
+ mCallback.onPlaybackStateChanged(mPlaybackState, speed);
+ }
+
+ /**
+ * Seeks playback to the specified position.
+ */
+ public void seekTo(long positionMs) throws IllegalStateException {
+ if (DEBUG) Log.d(TAG, "seekTo()");
+ if (!isPlaybackPrepared()) {
+ throw new IllegalStateException("Recorded program not set or playback not started yet");
+ }
+ if (mProgram == null || mPlaybackState == PlaybackState.STATE_NONE) {
+ return;
+ }
+ positionMs = getRealSeekPosition(positionMs, SEEK_POSITION_MARGIN_MS);
+ if (DEBUG) Log.d(TAG, "Now: " + getPlaybackPosition() + ", shift to: " + positionMs);
+ mTvView.timeShiftSeekTo(positionMs + mStartPositionMs);
+ if (mPlaybackState == PlaybackState.STATE_FAST_FORWARDING ||
+ mPlaybackState == PlaybackState.STATE_REWINDING) {
+ mPlaybackState = PlaybackState.STATE_PLAYING;
+ mTvView.timeShiftResume();
+ mCallback.onPlaybackStateChanged(mPlaybackState, 1);
+ }
+ }
+
+ /**
+ * Resets playback.
+ */
+ public void reset() {
+ if (DEBUG) Log.d(TAG, "reset()");
+ mCallback.onPlaybackStateChanged(PlaybackState.STATE_NONE, 1);
+ mPlaybackState = PlaybackState.STATE_NONE;
+ mTvView.reset();
+ mTimeShiftPlayAvailable = false;
+ mStartPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
+ mTimeShiftCurrentPositionMs = 0;
+ mPlaybackParams.setSpeed(1.0f);
+ mProgram = null;
+ mSelectedAudioTrackId = null;
+ mSelectedSubtitleTrackId = null;
+ }
+
+ /**
+ * Sets callbacks for playback.
+ */
+ public void setCallback(DvrPlayerCallback callback) {
+ if (callback != null) {
+ mCallback = callback;
+ } else {
+ mCallback = mEmptyCallback;
+ }
+ }
+
+ /**
+ * Sets the listener to aspect ratio changing.
+ */
+ public void setOnAspectRatioChangedListener(OnAspectRatioChangedListener listener) {
+ mOnAspectRatioChangedListener = listener;
+ }
+
+ /**
+ * Sets the listener to content blocking.
+ */
+ public void setOnContentBlockedListener(OnContentBlockedListener listener) {
+ mOnContentBlockedListener = listener;
+ }
+
+ /**
+ * Sets the listener to tracks changing.
+ */
+ public void setOnTracksAvailabilityChangedListener(
+ OnTracksAvailabilityChangedListener listener) {
+ mOnTracksAvailabilityChangedListener = listener;
+ }
+
+ /**
+ * Sets the listener to tracks of the given type being selected.
+ *
+ * @param trackType should be either {@link TvTrackInfo#TYPE_AUDIO}
+ * or {@link TvTrackInfo#TYPE_SUBTITLE}.
+ */
+ public void setOnTrackSelectedListener(int trackType, OnTrackSelectedListener listener) {
+ if (trackType == TvTrackInfo.TYPE_AUDIO) {
+ mOnAudioTrackSelectedListener = listener;
+ } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
+ mOnSubtitleTrackSelectedListener = listener;
+ }
+ }
+
+ /**
+ * Gets the listener to tracks of the given type being selected.
+ */
+ public OnTrackSelectedListener getOnTrackSelectedListener(int trackType) {
+ if (trackType == TvTrackInfo.TYPE_AUDIO) {
+ return mOnAudioTrackSelectedListener;
+ } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
+ return mOnSubtitleTrackSelectedListener;
+ }
+ return null;
+ }
+
+ /**
+ * Sets recorded programs for playback. If the player is playing another program, stops it.
+ */
+ public void setProgram(RecordedProgram program, long initialSeekPositionMs) {
+ if (mProgram != null && mProgram.equals(program)) {
+ return;
+ }
+ if (mPlaybackState != PlaybackState.STATE_NONE) {
+ reset();
+ }
+ mInitialSeekPositionMs = initialSeekPositionMs;
+ mProgram = program;
+ }
+
+ /**
+ * Returns the recorded program now playing.
+ */
+ public RecordedProgram getProgram() {
+ return mProgram;
+ }
+
+ /**
+ * Returns the currrent playback posistion in msecs.
+ */
+ public long getPlaybackPosition() {
+ return mTimeShiftCurrentPositionMs;
+ }
+
+ /**
+ * Returns the playback speed currently used.
+ */
+ public int getPlaybackSpeed() {
+ return (int) mPlaybackParams.getSpeed();
+ }
+
+ /**
+ * Returns the playback state defined in {@link android.media.session.PlaybackState}.
+ */
+ public int getPlaybackState() {
+ return mPlaybackState;
+ }
+
+ /**
+ * Returns the subtitle tracks of the current playback.
+ */
+ public ArrayList<TvTrackInfo> getSubtitleTracks() {
+ return new ArrayList<>(mTvView.getTracks(TvTrackInfo.TYPE_SUBTITLE));
+ }
+
+ /**
+ * Returns the audio tracks of the current playback.
+ */
+ public ArrayList<TvTrackInfo> getAudioTracks() {
+ return new ArrayList<>(mTvView.getTracks(TvTrackInfo.TYPE_AUDIO));
+ }
+
+ /**
+ * Returns the ID of the selected track of the given type.
+ */
+ public String getSelectedTrackId(int trackType) {
+ if (trackType == TvTrackInfo.TYPE_AUDIO) {
+ return mSelectedAudioTrackId;
+ } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
+ return mSelectedSubtitleTrackId;
+ }
+ return null;
+ }
+
+ /**
+ * Returns if playback of the recorded program is started.
+ */
+ public boolean isPlaybackPrepared() {
+ return mPlaybackState != PlaybackState.STATE_NONE
+ && mPlaybackState != PlaybackState.STATE_CONNECTING;
+ }
+
+ /**
+ * Selects the given track.
+ *
+ * @return ID of the selected track.
+ */
+ String selectTrack(int trackType, TvTrackInfo selectedTrack) {
+ String oldSelectedTrackId = getSelectedTrackId(trackType);
+ String newSelectedTrackId = selectedTrack == null ? null : selectedTrack.getId();
+ if (!TextUtils.equals(oldSelectedTrackId, newSelectedTrackId)) {
+ if (selectedTrack == null) {
+ mTvView.selectTrack(trackType, null);
+ return null;
+ } else {
+ List<TvTrackInfo> tracks = mTvView.getTracks(trackType);
+ if (tracks != null && tracks.contains(selectedTrack)) {
+ mTvView.selectTrack(trackType, newSelectedTrackId);
+ return newSelectedTrackId;
+ } else if (trackType == TvTrackInfo.TYPE_SUBTITLE && oldSelectedTrackId != null) {
+ // Track not found, disabled closed caption.
+ mTvView.selectTrack(trackType, null);
+ return null;
+ }
+ }
+ }
+ return oldSelectedTrackId;
+ }
+
+ private void setSelectedTrackId(int trackType, String trackId) {
+ if (trackType == TvTrackInfo.TYPE_AUDIO) {
+ mSelectedAudioTrackId = trackId;
+ } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
+ mSelectedSubtitleTrackId = trackId;
+ }
+ }
+
+ private void setPlaybackSpeed(int speed) {
+ mPlaybackParams.setSpeed(speed);
+ mTvView.timeShiftSetPlaybackParams(mPlaybackParams);
+ }
+
+ private long getRealSeekPosition(long seekPositionMs, long endMarginMs) {
+ return Math.max(0, Math.min(seekPositionMs, mProgram.getDurationMillis() - endMarginMs));
+ }
+
+ private void setTvViewCallbacks() {
+ mTvView.setTimeShiftPositionCallback(new TvView.TimeShiftPositionCallback() {
+ @Override
+ public void onTimeShiftStartPositionChanged(String inputId, long timeMs) {
+ if (DEBUG) Log.d(TAG, "onTimeShiftStartPositionChanged:" + timeMs);
+ mStartPositionMs = timeMs;
+ if (mTimeShiftPlayAvailable) {
+ resumeToWatchedPositionIfNeeded();
+ }
+ }
+
+ @Override
+ public void onTimeShiftCurrentPositionChanged(String inputId, long timeMs) {
+ if (DEBUG) Log.d(TAG, "onTimeShiftCurrentPositionChanged: " + timeMs);
+ if (!mTimeShiftPlayAvailable) {
+ // Workaround of b/31436263
+ return;
+ }
+ // Workaround of b/32211561, TIF won't report start position when TIS report
+ // its start position as 0. In that case, we have to do the prework of playback
+ // on the first time we get current position, and the start position should be 0
+ // at that time.
+ if (mStartPositionMs == TvInputManager.TIME_SHIFT_INVALID_TIME) {
+ mStartPositionMs = 0;
+ resumeToWatchedPositionIfNeeded();
+ }
+ timeMs -= mStartPositionMs;
+ if (mPlaybackState == PlaybackState.STATE_REWINDING
+ && timeMs <= REWIND_POSITION_MARGIN_MS) {
+ play();
+ } else {
+ mTimeShiftCurrentPositionMs = getRealSeekPosition(timeMs, 0);
+ mCallback.onPlaybackPositionChanged(mTimeShiftCurrentPositionMs);
+ if (timeMs >= mProgram.getDurationMillis()) {
+ pause();
+ mCallback.onPlaybackEnded();
+ }
+ }
+ }
+ });
+ mTvView.setCallback(new TvView.TvInputCallback() {
+ @Override
+ public void onTimeShiftStatusChanged(String inputId, int status) {
+ if (DEBUG) Log.d(TAG, "onTimeShiftStatusChanged:" + status);
+ if (status == TvInputManager.TIME_SHIFT_STATUS_AVAILABLE
+ && mPlaybackState == PlaybackState.STATE_CONNECTING) {
+ mTimeShiftPlayAvailable = true;
+ if (mStartPositionMs != TvInputManager.TIME_SHIFT_INVALID_TIME) {
+ // onTimeShiftStatusChanged is sometimes called after
+ // onTimeShiftStartPositionChanged is called. In this case,
+ // resumeToWatchedPositionIfNeeded needs to be called here.
+ resumeToWatchedPositionIfNeeded();
+ }
+ }
+ }
+
+ @Override
+ public void onTracksChanged(String inputId, List<TvTrackInfo> tracks) {
+ boolean hasClosedCaption =
+ !mTvView.getTracks(TvTrackInfo.TYPE_SUBTITLE).isEmpty();
+ boolean hasMultiAudio = mTvView.getTracks(TvTrackInfo.TYPE_AUDIO).size() > 1;
+ if ((hasClosedCaption != mHasClosedCaption || hasMultiAudio != mHasMultiAudio)
+ && mOnTracksAvailabilityChangedListener != null) {
+ mOnTracksAvailabilityChangedListener
+ .onTracksAvailabilityChanged(hasClosedCaption, hasMultiAudio);
+ }
+ mHasClosedCaption = hasClosedCaption;
+ mHasMultiAudio = hasMultiAudio;
+ }
+
+ @Override
+ public void onTrackSelected(String inputId, int type, String trackId) {
+ if (type == TvTrackInfo.TYPE_AUDIO || type == TvTrackInfo.TYPE_SUBTITLE) {
+ setSelectedTrackId(type, trackId);
+ OnTrackSelectedListener listener = getOnTrackSelectedListener(type);
+ if (listener != null) {
+ listener.onTrackSelected(trackId);
+ }
+ } else if (type == TvTrackInfo.TYPE_VIDEO && trackId != null
+ && mOnAspectRatioChangedListener != null) {
+ List<TvTrackInfo> trackInfos = mTvView.getTracks(TvTrackInfo.TYPE_VIDEO);
+ if (trackInfos != null) {
+ for (TvTrackInfo trackInfo : trackInfos) {
+ if (trackInfo.getId().equals(trackId)) {
+ float videoAspectRatio;
+ int videoWidth = trackInfo.getVideoWidth();
+ int videoHeight = trackInfo.getVideoHeight();
+ if (videoWidth > 0 && videoHeight > 0) {
+ videoAspectRatio = trackInfo.getVideoPixelAspectRatio()
+ * trackInfo.getVideoWidth() / trackInfo.getVideoHeight();
+ } else {
+ // Aspect ratio is unknown. Pass the message to listeners.
+ videoAspectRatio = 0;
+ }
+ if (DEBUG) Log.d(TAG, "Aspect Ratio: " + videoAspectRatio);
+ if (mAspectRatio != videoAspectRatio || videoAspectRatio == 0) {
+ mOnAspectRatioChangedListener
+ .onAspectRatioChanged(videoAspectRatio);
+ mAspectRatio = videoAspectRatio;
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onContentBlocked(String inputId, TvContentRating rating) {
+ if (mOnContentBlockedListener != null) {
+ mOnContentBlockedListener.onContentBlocked(rating);
+ }
+ }
+ });
+ }
+
+ private void resumeToWatchedPositionIfNeeded() {
+ if (mInitialSeekPositionMs != TvInputManager.TIME_SHIFT_INVALID_TIME) {
+ mTvView.timeShiftSeekTo(getRealSeekPosition(mInitialSeekPositionMs,
+ SEEK_POSITION_MARGIN_MS) + mStartPositionMs);
+ mInitialSeekPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
+ }
+ if (mPauseOnPrepared) {
+ mTvView.timeShiftPause();
+ mPlaybackState = PlaybackState.STATE_PAUSED;
+ mPauseOnPrepared = false;
+ } else {
+ mTvView.timeShiftResume();
+ mPlaybackState = PlaybackState.STATE_PLAYING;
+ }
+ mCallback.onPlaybackStateChanged(mPlaybackState, 1);
+ }
+} \ No newline at end of file