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/DetailsContent.java207
-rw-r--r--src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java26
-rw-r--r--src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java26
-rw-r--r--src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java25
-rw-r--r--src/com/android/tv/dvr/ui/DvrConflictFragment.java48
-rw-r--r--src/com/android/tv/dvr/ui/DvrForgetStorageErrorFragment.java87
-rw-r--r--src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java116
-rw-r--r--src/com/android/tv/dvr/ui/DvrHalfSizedDialogFragment.java39
-rw-r--r--src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java101
-rw-r--r--src/com/android/tv/dvr/ui/DvrItemPresenter.java80
-rw-r--r--src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java67
-rw-r--r--src/com/android/tv/dvr/ui/DvrPlaybackCardPresenter.java82
-rw-r--r--src/com/android/tv/dvr/ui/DvrPlaybackOverlayFragment.java304
-rw-r--r--src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java (renamed from src/com/android/tv/dvr/ui/PrioritySettingsFragment.java)29
-rw-r--r--src/com/android/tv/dvr/ui/DvrScheduleFragment.java36
-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.java62
-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.java29
-rw-r--r--src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java20
-rw-r--r--src/com/android/tv/dvr/ui/DvrUiHelper.java649
-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/RecordedProgramPresenter.java182
-rw-r--r--src/com/android/tv/dvr/ui/RecordingDetailsFragment.java87
-rw-r--r--src/com/android/tv/dvr/ui/ScheduledRecordingPresenter.java177
-rw-r--r--src/com/android/tv/dvr/ui/SortedArrayAdapter.java90
-rw-r--r--src/com/android/tv/dvr/ui/TrackedGuidedStepFragment.java82
-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.java317
-rw-r--r--src/com/android/tv/dvr/ui/browse/DetailsContentPresenter.java (renamed from src/com/android/tv/dvr/ui/DetailsContentPresenter.java)65
-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)23
-rw-r--r--src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java (renamed from src/com/android/tv/dvr/ui/DvrBrowseFragment.java)192
-rw-r--r--src/com/android/tv/dvr/ui/browse/DvrDetailsActivity.java (renamed from src/com/android/tv/dvr/ui/DvrDetailsActivity.java)60
-rw-r--r--src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java (renamed from src/com/android/tv/dvr/ui/DvrDetailsFragment.java)83
-rw-r--r--src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java140
-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)68
-rw-r--r--src/com/android/tv/dvr/ui/browse/RecordedProgramDetailsFragment.java (renamed from src/com/android/tv/dvr/ui/RecordedProgramDetailsFragment.java)33
-rw-r--r--src/com/android/tv/dvr/ui/browse/RecordedProgramPresenter.java142
-rw-r--r--src/com/android/tv/dvr/ui/browse/RecordingCardView.java (renamed from src/com/android/tv/dvr/ui/RecordingCardView.java)141
-rw-r--r--src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java51
-rw-r--r--src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java (renamed from src/com/android/tv/dvr/ui/ScheduledRecordingDetailsFragment.java)6
-rw-r--r--src/com/android/tv/dvr/ui/browse/ScheduledRecordingPresenter.java138
-rw-r--r--src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java (renamed from src/com/android/tv/dvr/ui/SeriesRecordingDetailsFragment.java)41
-rw-r--r--src/com/android/tv/dvr/ui/browse/SeriesRecordingPresenter.java (renamed from src/com/android/tv/dvr/ui/SeriesRecordingPresenter.java)62
-rw-r--r--src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java5
-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.java9
-rw-r--r--src/com/android/tv/dvr/ui/list/ScheduleRow.java8
-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.java94
-rw-r--r--src/com/android/tv/dvr/ui/playback/DvrPlaybackCardPresenter.java45
-rw-r--r--src/com/android/tv/dvr/ui/playback/DvrPlaybackControlHelper.java (renamed from src/com/android/tv/dvr/ui/DvrPlaybackControlHelper.java)164
-rw-r--r--src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java335
-rw-r--r--src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java494
-rw-r--r--src/com/android/tv/dvr/ui/playback/DvrPlaybackSideFragment.java154
-rw-r--r--src/com/android/tv/dvr/ui/playback/DvrPlayer.java583
73 files changed, 5022 insertions, 2170 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/DetailsContent.java b/src/com/android/tv/dvr/ui/DetailsContent.java
deleted file mode 100644
index 19521fca..00000000
--- a/src/com/android/tv/dvr/ui/DetailsContent.java
+++ /dev/null
@@ -1,207 +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.media.tv.TvContract;
-import android.support.annotation.Nullable;
-import android.text.TextUtils;
-
-import com.android.tv.data.BaseProgram;
-import com.android.tv.data.Channel;
-
-/**
- * A class for details content.
- */
-public class DetailsContent {
- /** Constant for invalid time. */
- public static final long INVALID_TIME = -1;
-
- private CharSequence mTitle;
- private long mStartTimeUtcMillis;
- private long mEndTimeUtcMillis;
- private String mDescription;
- private String mLogoImageUri;
- private String mBackgroundImageUri;
-
- private DetailsContent() { }
-
- /**
- * Returns title.
- */
- public CharSequence getTitle() {
- return mTitle;
- }
-
- /**
- * Returns start time.
- */
- public long getStartTimeUtcMillis() {
- return mStartTimeUtcMillis;
- }
-
- /**
- * Returns end time.
- */
- public long getEndTimeUtcMillis() {
- return mEndTimeUtcMillis;
- }
-
- /**
- * Returns description.
- */
- public String getDescription() {
- return mDescription;
- }
-
- /**
- * Returns Logo image URI as a String.
- */
- public String getLogoImageUri() {
- return mLogoImageUri;
- }
-
- /**
- * Returns background image URI as a String.
- */
- public String getBackgroundImageUri() {
- return mBackgroundImageUri;
- }
-
- /**
- * Copies other details content.
- */
- public void copyFrom(DetailsContent other) {
- if (this == other) {
- return;
- }
- mTitle = other.mTitle;
- mStartTimeUtcMillis = other.mStartTimeUtcMillis;
- mEndTimeUtcMillis = other.mEndTimeUtcMillis;
- mDescription = other.mDescription;
- mLogoImageUri = other.mLogoImageUri;
- mBackgroundImageUri = other.mBackgroundImageUri;
- }
-
- /**
- * A class for building details content.
- */
- public static final class Builder {
- private final DetailsContent mDetailsContent;
-
- public Builder() {
- mDetailsContent = new DetailsContent();
- mDetailsContent.mStartTimeUtcMillis = INVALID_TIME;
- mDetailsContent.mEndTimeUtcMillis = INVALID_TIME;
- }
-
- /**
- * Sets title.
- */
- public Builder setTitle(CharSequence title) {
- mDetailsContent.mTitle = title;
- return this;
- }
-
- /**
- * Sets start time.
- */
- public Builder setStartTimeUtcMillis(long startTimeUtcMillis) {
- mDetailsContent.mStartTimeUtcMillis = startTimeUtcMillis;
- return this;
- }
-
- /**
- * Sets end time.
- */
- public Builder setEndTimeUtcMillis(long endTimeUtcMillis) {
- mDetailsContent.mEndTimeUtcMillis = endTimeUtcMillis;
- return this;
- }
-
- /**
- * Sets description.
- */
- public Builder setDescription(String description) {
- mDetailsContent.mDescription = description;
- return this;
- }
-
- /**
- * Sets logo image URI as a String.
- */
- public Builder setLogoImageUri(String logoImageUri) {
- mDetailsContent.mLogoImageUri = logoImageUri;
- return this;
- }
-
- /**
- * Sets background image URI as a String.
- */
- public Builder setBackgroundImageUri(String backgroundImageUri) {
- mDetailsContent.mBackgroundImageUri = backgroundImageUri;
- return this;
- }
-
- /**
- * Sets background image and logo image URI from program and channel.
- */
- public Builder setImageUris(@Nullable BaseProgram program, @Nullable Channel channel) {
- if (program != null) {
- return setImageUris(program.getPosterArtUri(), program.getThumbnailUri(), channel);
- } else {
- return setImageUris(null, null, channel);
- }
- }
-
- /**
- * Sets background image and logo image URI and channel is used for fallback images.
- */
- public Builder setImageUris(@Nullable String posterArtUri,
- @Nullable String thumbnailUri, @Nullable Channel channel) {
- mDetailsContent.mLogoImageUri = null;
- mDetailsContent.mBackgroundImageUri = null;
- if (!TextUtils.isEmpty(posterArtUri) && !TextUtils.isEmpty(thumbnailUri)) {
- mDetailsContent.mLogoImageUri = posterArtUri;
- mDetailsContent.mBackgroundImageUri = thumbnailUri;
- } else if (!TextUtils.isEmpty(posterArtUri)) {
- // thumbnailUri is empty
- mDetailsContent.mLogoImageUri = posterArtUri;
- mDetailsContent.mBackgroundImageUri = posterArtUri;
- } else if (!TextUtils.isEmpty(thumbnailUri)) {
- // posterArtUri is empty
- mDetailsContent.mLogoImageUri = thumbnailUri;
- mDetailsContent.mBackgroundImageUri = thumbnailUri;
- }
- if (TextUtils.isEmpty(mDetailsContent.mLogoImageUri) && channel != null) {
- String channelLogoUri = TvContract.buildChannelLogoUri(channel.getId())
- .toString();
- mDetailsContent.mLogoImageUri = channelLogoUri;
- mDetailsContent.mBackgroundImageUri = channelLogoUri;
- }
- return this;
- }
-
- /**
- * Builds details content.
- */
- public DetailsContent build() {
- DetailsContent detailsContent = new DetailsContent();
- detailsContent.copyFrom(mDetailsContent);
- return detailsContent;
- }
- }
-} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java b/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java
index 9df228d1..62327870 100644
--- a/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java
@@ -24,15 +24,12 @@ 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.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;
@@ -92,7 +89,7 @@ public class DvrAlreadyRecordedFragment extends DvrGuidedStepFragment {
}
@Override
- public void onGuidedActionClicked(GuidedAction action) {
+ public void onTrackedGuidedActionClicked(GuidedAction action) {
if (action.getId() == ACTION_RECORD_ANYWAY) {
getDvrManager().addSchedule(mProgram);
} else if (action.getId() == ACTION_WATCH) {
@@ -100,4 +97,23 @@ public class DvrAlreadyRecordedFragment extends DvrGuidedStepFragment {
}
dismissDialog();
}
+
+ @Override
+ public String getTrackerPrefix() {
+ return "onTrackedGuidedActionClicked";
+ }
+
+ @Override
+ public String getTrackerLabelForGuidedAction(GuidedAction action) {
+ long actionId = action.getId();
+ if (actionId == ACTION_RECORD_ANYWAY) {
+ return "record-anyway";
+ } else if (actionId == ACTION_WATCH) {
+ return "watch-now";
+ } else if (actionId == ACTION_CANCEL) {
+ return "cancel-recording";
+ } else {
+ return super.getTrackerLabelForGuidedAction(action);
+ }
+ }
}
diff --git a/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java b/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java
index 78f21784..6da75e55 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;
@@ -95,7 +92,7 @@ public class DvrAlreadyScheduledFragment extends DvrGuidedStepFragment {
}
@Override
- public void onGuidedActionClicked(GuidedAction action) {
+ public void onTrackedGuidedActionClicked(GuidedAction action) {
if (action.getId() == ACTION_RECORD_ANYWAY) {
getDvrManager().addSchedule(mProgram);
} else if (action.getId() == ACTION_RECORD_INSTEAD) {
@@ -104,4 +101,23 @@ public class DvrAlreadyScheduledFragment extends DvrGuidedStepFragment {
}
dismissDialog();
}
+
+ @Override
+ public String getTrackerPrefix() {
+ return "DvrAlreadyScheduledFragment";
+ }
+
+ @Override
+ public String getTrackerLabelForGuidedAction(GuidedAction action) {
+ long actionId = action.getId();
+ if (actionId == ACTION_RECORD_ANYWAY) {
+ return "record-anyway";
+ } else if (actionId == ACTION_RECORD_INSTEAD) {
+ return "record-instead";
+ } else if (actionId == ACTION_CANCEL) {
+ return "cancel-recording";
+ } else {
+ return super.getTrackerLabelForGuidedAction(action);
+ }
+ }
}
diff --git a/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java b/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java
index 837d8ab2..36659412 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;
@@ -85,7 +85,7 @@ public class DvrChannelRecordDurationOptionFragment extends DvrGuidedStepFragmen
}
@Override
- public void onGuidedActionClicked(GuidedAction action) {
+ public void onTrackedGuidedActionClicked(GuidedAction action) {
DvrManager dvrManager = TvApplication.getSingletons(getContext()).getDvrManager();
long duration = mDurations.get((int) action.getId());
long startTimeMs = System.currentTimeMillis();
@@ -106,4 +106,25 @@ public class DvrChannelRecordDurationOptionFragment extends DvrGuidedStepFragmen
R.id.halfsized_dialog_host);
}
}
+
+ @Override
+ public String getTrackerPrefix() {
+ return "DvrChannelRecordDurationOptionFragment";
+ }
+
+ @Override
+ public String getTrackerLabelForGuidedAction(GuidedAction action) {
+ long actionId = action.getId();
+ if (actionId == 0) {
+ return "record-10-minutes";
+ } else if (actionId == 1) {
+ return "record-30-minutes";
+ } else if (actionId == 2) {
+ return "record-1-hour";
+ } else if (actionId == 3) {
+ return "record-3-hour";
+ } else {
+ return super.getTrackerLabelForGuidedAction(action);
+ }
+ }
}
diff --git a/src/com/android/tv/dvr/ui/DvrConflictFragment.java b/src/com/android/tv/dvr/ui/DvrConflictFragment.java
index e7be4d0a..6f362e68 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;
@@ -85,7 +84,7 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment {
}
@Override
- public void onGuidedActionClicked(GuidedAction action) {
+ public void onTrackedGuidedActionClicked(GuidedAction action) {
if (action.getId() == ACTION_VIEW_SCHEDULES) {
DvrUiHelper.startSchedulesActivityForOneTimeRecordingConflict(
getContext(), getConflicts());
@@ -93,6 +92,16 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment {
dismissDialog();
}
+ @Override
+ public String getTrackerLabelForGuidedAction(GuidedAction action) {
+ long actionId = getId();
+ if (actionId == ACTION_VIEW_SCHEDULES) {
+ return "view-schedules";
+ } else {
+ return super.getTrackerLabelForGuidedAction(action);
+ }
+ }
+
String getConflictDescription() {
List<String> titles = new ArrayList<>();
HashSet<String> titleSet = new HashSet<>();
@@ -185,6 +194,11 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment {
Drawable icon = getResources().getDrawable(R.drawable.ic_error_white_48dp, null);
return new Guidance(title, descriptionPrefix + " " + description, null, icon);
}
+
+ @Override
+ public String getTrackerPrefix() {
+ return "DvrProgramConflictFragment";
+ }
}
/**
@@ -236,6 +250,11 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment {
Drawable icon = getResources().getDrawable(R.drawable.ic_error_white_48dp, null);
return new Guidance(title, descriptionPrefix + " " + description, null, icon);
}
+
+ @Override
+ public String getTrackerPrefix() {
+ return "DvrChannelRecordConflictFragment";
+ }
}
/**
@@ -300,7 +319,7 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment {
}
@Override
- public void onGuidedActionClicked(GuidedAction action) {
+ public void onTrackedGuidedActionClicked(GuidedAction action) {
if (action.getId() == ACTION_CANCEL) {
ConflictChecker checker = ((MainActivity) getContext()).getDvrConflictChecker();
if (checker != null) {
@@ -319,6 +338,23 @@ public abstract class DvrConflictFragment extends DvrGuidedStepFragment {
}
@Override
+ public String getTrackerPrefix() {
+ return "DvrChannelWatchConflictFragment";
+ }
+
+ @Override
+ public String getTrackerLabelForGuidedAction(GuidedAction action) {
+ long actionId = action.getId();
+ if (actionId == ACTION_CANCEL) {
+ return "cancel";
+ } else if (actionId == ACTION_DELETE_CONFLICT) {
+ return "delete";
+ } else {
+ return super.getTrackerLabelForGuidedAction(action);
+ }
+ }
+
+ @Override
public void onDetach() {
ConflictChecker checker = ((MainActivity) getContext()).getDvrConflictChecker();
if (checker != null) {
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..ab852e10 100644
--- a/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java
@@ -16,24 +16,43 @@
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;
import android.view.View;
import android.view.ViewGroup;
+import com.android.tv.ApplicationSingletons;
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 com.android.tv.dvr.DvrStorageStatusManager;
+
+import java.util.List;
+
+public abstract class DvrGuidedStepFragment extends TrackedGuidedStepFragment {
+ /**
+ * 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;
+ public static final String UNKNOWN_DVR_ACTION = "Unknown DVR Action";
-public class DvrGuidedStepFragment extends GuidedStepFragment {
private DvrManager mDvrManager;
private OnActionClickListener mOnActionClickListener;
@@ -44,7 +63,8 @@ public class DvrGuidedStepFragment extends GuidedStepFragment {
@Override
public void onAttach(Context context) {
super.onAttach(context);
- mDvrManager = TvApplication.getSingletons(context).getDvrManager();
+ ApplicationSingletons singletons = TvApplication.getSingletons(context);
+ mDvrManager = singletons.getDvrManager();
}
@Override
@@ -64,13 +84,27 @@ public class DvrGuidedStepFragment extends GuidedStepFragment {
}
@Override
- public void onGuidedActionClicked(GuidedAction action) {
+ public void onTrackedGuidedActionClicked(GuidedAction action) {
if (mOnActionClickListener != null) {
mOnActionClickListener.onActionClick(action.getId());
}
dismissDialog();
}
+ @Override
+ public String getTrackerLabelForGuidedAction(GuidedAction action) {
+ long actionId = action.getId();
+ if (actionId == ACTION_RECORD_ANYWAY) {
+ return "record-anyway";
+ } else if (actionId == ACTION_DELETE_RECORDINGS) {
+ return "delete-recordings";
+ } else if (actionId == ACTION_CANCEL_RECORDING) {
+ return "cancel-recording";
+ } else {
+ return super.getTrackerLabelForGuidedAction(action);
+ }
+ }
+
protected void dismissDialog() {
if (getActivity() instanceof MainActivity) {
SafeDismissDialogFragment currentDialog =
@@ -86,4 +120,76 @@ 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());
+ }
+
+ @Override
+ public String getTrackerPrefix() {
+ return "DvrNoFreeSpaceErrorFragment";
+ }
+ }
+
+ /**
+ * The inner guided step fragment for
+ * {@link com.android.tv.dvr.ui.DvrHalfSizedDialogFragment
+ * .DvrSmallSizedStorageErrorDialogFragment}.
+ */
+ public static class DvrSmallSizedStorageErrorFragment extends DvrGuidedStepFragment {
+ @Override
+ public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
+ String title = getResources().getString(
+ R.string.dvr_error_small_sized_storage_title);
+ String description = getResources().getString(
+ R.string.dvr_error_small_sized_storage_description,
+ DvrStorageStatusManager.MIN_STORAGE_SIZE_FOR_DVR_IN_BYTES / 1024
+ / 1024 / 1024);
+ return new GuidanceStylist.Guidance(title, description, null, null);
+ }
+
+ @Override
+ public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
+ Activity activity = getActivity();
+ actions.add(new GuidedAction.Builder(activity)
+ .id(GuidedAction.ACTION_ID_OK)
+ .title(android.R.string.ok)
+ .build());
+ }
+
+ @Override
+ public void onTrackedGuidedActionClicked(GuidedAction action) {
+ dismissDialog();
+ }
+
+ @Override
+ public String getTrackerPrefix() {
+ return "DvrSmallSizedStorageErrorFragment";
+ }
+ }
} \ 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..f8ef3850 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
*/
@@ -173,32 +185,7 @@ public class DvrHalfSizedDialogFragment extends HalfSizedDialogFragment {
extends DvrGuidedStepDialogFragment {
@Override
protected DvrGuidedStepFragment onCreateGuidedStepFragment() {
- return new DvrGuidedStepFragment() {
- @Override
- public Guidance onCreateGuidance(Bundle savedInstanceState) {
- String title = getResources().getString(
- R.string.dvr_error_small_sized_storage_title);
- String description = getResources().getString(
- R.string.dvr_error_small_sized_storage_description,
- DvrStorageStatusManager.MIN_STORAGE_SIZE_FOR_DVR_IN_BYTES / 1024
- / 1024 / 1024);
- return new Guidance(title, description, null, null);
- }
-
- @Override
- public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
- Activity activity = getActivity();
- actions.add(new GuidedAction.Builder(activity)
- .id(GuidedAction.ACTION_ID_OK)
- .title(android.R.string.ok)
- .build());
- }
-
- @Override
- public void onGuidedActionClicked(GuidedAction action) {
- dismissDialog();
- }
- };
+ return new DvrGuidedStepFragment.DvrSmallSizedStorageErrorFragment();
}
}
diff --git a/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java b/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java
index 3b1dbfa0..182416b6 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,28 +93,38 @@ 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);
+ public void onTrackedGuidedActionClicked(GuidedAction action) {
+ if (action.getId() == ACTION_VIEW_RECENT_RECORDINGS) {
+ Intent intent = new Intent(getActivity(), DvrBrowseActivity.class);
getActivity().startActivity(intent);
}
dismissDialog();
}
+
+ @Override
+ public String getTrackerPrefix() {
+ return "DvrInsufficientSpaceErrorFragment";
+ }
+
+ @Override
+ public String getTrackerLabelForGuidedAction(GuidedAction action) {
+ long actionId = action.getId();
+ if (actionId == ACTION_VIEW_RECENT_RECORDINGS) {
+ return "view-recent";
+ } else {
+ return super.getTrackerLabelForGuidedAction(action);
+ }
+ }
}
diff --git a/src/com/android/tv/dvr/ui/DvrItemPresenter.java b/src/com/android/tv/dvr/ui/DvrItemPresenter.java
deleted file mode 100644
index 339e5d2f..00000000
--- a/src/com/android/tv/dvr/ui/DvrItemPresenter.java
+++ /dev/null
@@ -1,80 +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.support.annotation.CallSuper;
-import android.support.v17.leanback.widget.Presenter;
-import android.view.View;
-import android.view.View.OnClickListener;
-
-import com.android.tv.dvr.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}.
- */
-public abstract class DvrItemPresenter extends Presenter {
- private final Set<ViewHolder> mBoundViewHolders = new HashSet<>();
- private final OnClickListener mOnClickListener = onCreateOnClickListener();
-
- @Override
- @CallSuper
- public void onBindViewHolder(ViewHolder viewHolder, Object o) {
- viewHolder.view.setTag(o);
- viewHolder.view.setOnClickListener(mOnClickListener);
- mBoundViewHolders.add(viewHolder);
- }
-
- @Override
- @CallSuper
- public void onUnbindViewHolder(ViewHolder viewHolder) {
- mBoundViewHolders.remove(viewHolder);
- }
-
- /**
- * Unbinds all bound view holders.
- */
- public void unbindAllViewHolders() {
- // When browse fragments are destroyed, RecyclerView would not call presenters'
- // onUnbindViewHolder(). We should handle it by ourselves to prevent resources leaks.
- for (ViewHolder viewHolder : new HashSet<>(mBoundViewHolders)) {
- onUnbindViewHolder(viewHolder);
- }
- }
-
- /**
- * Creates {@link OnClickListener} for DVR library's card views.
- */
- protected OnClickListener onCreateOnClickListener() {
- return new OnClickListener() {
- @Override
- public void onClick(View view) {
- if (view instanceof RecordingCardView) {
- RecordingCardView v = (RecordingCardView) view;
- DvrUiHelper.startDetailsActivity((Activity) v.getContext(),
- v.getTag(), v.getImageView(), false);
- }
- }
- };
- }
-} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java b/src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java
index 2e2c2849..e726995f 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 final String TAG = "DvrMissingStorageError";
+
+ 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,46 @@ 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);
+ public void onTrackedGuidedActionClicked(GuidedAction action) {
+ 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);
+ }
+ }
+
+ @Override
+ public String getTrackerPrefix() {
+ return "DvrMissingStorageErrorFragment";
+ }
+
+ @Override
+ public String getTrackerLabelForGuidedAction(GuidedAction action) {
+ long actionId = action.getId();
+ if (actionId == ACTION_OPEN_STORAGE_SETTINGS) {
+ return "open-storage-settings";
+ } else {
+ return super.getTrackerLabelForGuidedAction(action);
+ }
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/DvrPlaybackCardPresenter.java b/src/com/android/tv/dvr/ui/DvrPlaybackCardPresenter.java
deleted file mode 100644
index 8c4c856c..00000000
--- a/src/com/android/tv/dvr/ui/DvrPlaybackCardPresenter.java
+++ /dev/null
@@ -1,82 +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.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.View;
-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.util.Utils;
-
-/**
- * This class is used to generate Views and bind Objects for related recordings in DVR playback.
- */
-public class DvrPlaybackCardPresenter extends RecordedProgramPresenter {
- private static final String TAG = "DvrPlaybackCardPresenter";
- private static final boolean DEBUG = false;
-
- private final int mRelatedRecordingCardWidth;
- private final int mRelatedRecordingCardHeight;
-
- DvrPlaybackCardPresenter(Context context) {
- super(context);
- mRelatedRecordingCardWidth =
- context.getResources().getDimensionPixelSize(R.dimen.dvr_related_recordings_width);
- mRelatedRecordingCardHeight =
- context.getResources().getDimensionPixelSize(R.dimen.dvr_related_recordings_height);
- }
-
- @Override
- public ViewHolder onCreateViewHolder(ViewGroup parent) {
- Resources res = parent.getResources();
- RecordingCardView view = new RecordingCardView(
- getContext(), mRelatedRecordingCardWidth, mRelatedRecordingCardHeight);
- return new ViewHolder(view);
- }
-
- @Override
- protected OnClickListener onCreateOnClickListener() {
- return new OnClickListener() {
- @Override
- public void onClick(View v) {
- long programId = ((RecordedProgram) v.getTag()).getId();
- if (DEBUG) Log.d(TAG, "Play Related Recording:" + programId);
- Intent intent = new Intent(getContext(), DvrPlaybackActivity.class);
- intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, programId);
- getContext().startActivity(intent);
- }
- };
- }
-
- @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/DvrPlaybackOverlayFragment.java b/src/com/android/tv/dvr/ui/DvrPlaybackOverlayFragment.java
deleted file mode 100644
index 51ec93b8..00000000
--- a/src/com/android/tv/dvr/ui/DvrPlaybackOverlayFragment.java
+++ /dev/null
@@ -1,304 +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.Context;
-import android.content.Intent;
-import android.graphics.Point;
-import android.hardware.display.DisplayManager;
-import android.media.tv.TvContentRating;
-import android.os.Bundle;
-import android.media.session.PlaybackState;
-import android.media.tv.TvInputManager;
-import android.media.tv.TvView;
-import android.support.v17.leanback.app.PlaybackOverlayFragment;
-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;
-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.parental.ContentRatingsManager;
-import com.android.tv.util.Utils;
-
-public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
- // TODO: Handles audio focus. Deals with block and ratings.
- private static final String TAG = "DvrPlaybackOverlayFragment";
- private static final boolean DEBUG = false;
-
- private static final String MEDIA_SESSION_TAG = "com.android.tv.dvr.mediasession";
- private static final float DISPLAY_ASPECT_RATIO_EPSILON = 0.01f;
-
- // mProgram is only used to store program from intent. Don't use it elsewhere.
- private RecordedProgram mProgram;
- private DvrPlaybackMediaSessionHelper mMediaSessionHelper;
- private DvrPlaybackControlHelper mPlaybackControlHelper;
- private ArrayObjectAdapter mRowsAdapter;
- private SortedArrayAdapter<BaseProgram> mRelatedRecordingsRowAdapter;
- private DvrPlaybackCardPresenter mRelatedRecordingCardPresenter;
- private DvrDataManager mDvrDataManager;
- private ContentRatingsManager mContentRatingsManager;
- private TvView mTvView;
- private View mBlockScreenView;
- private ListRow mRelatedRecordingsRow;
- private int mExtraPaddingNoRelatedRow;
- private int mWindowWidth;
- private int mWindowHeight;
- private float mAppliedAspectRatio;
- private float mWindowAspectRatio;
- private boolean mPinChecked;
-
- @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);
- mDvrDataManager = TvApplication.getSingletons(getActivity()).getDvrDataManager();
- mContentRatingsManager = TvApplication.getSingletons(getContext())
- .getTvInputManagerHelper().getContentRatingsManager();
- mProgram = getProgramFromIntent(getActivity().getIntent());
- if (mProgram == null) {
- Toast.makeText(getActivity(), getString(R.string.dvr_program_not_found),
- Toast.LENGTH_SHORT).show();
- getActivity().finish();
- return;
- }
- Point size = new Point();
- ((DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE))
- .getDisplay(Display.DEFAULT_DISPLAY).getSize(size);
- mWindowWidth = size.x;
- mWindowHeight = size.y;
- mWindowAspectRatio = mAppliedAspectRatio = (float) mWindowWidth / mWindowHeight;
- setBackgroundType(PlaybackOverlayFragment.BG_LIGHT);
- setFadingEnabled(true);
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- mTvView = (TvView) getActivity().findViewById(R.id.dvr_tv_view);
- mBlockScreenView = getActivity().findViewById(R.id.block_screen);
- mMediaSessionHelper = new DvrPlaybackMediaSessionHelper(
- getActivity(), MEDIA_SESSION_TAG, new DvrPlayer(mTvView), this);
- mPlaybackControlHelper = new DvrPlaybackControlHelper(getActivity(), this);
- setUpRows();
- preparePlayback(getActivity().getIntent());
- DvrPlayer dvrPlayer = mMediaSessionHelper.getDvrPlayer();
- dvrPlayer.setAspectRatioChangedListener(new DvrPlayer.AspectRatioChangedListener() {
- @Override
- public void onAspectRatioChanged(float videoAspectRatio) {
- updateAspectRatio(videoAspectRatio);
- }
- });
- mPinChecked = getActivity().getIntent()
- .getBooleanExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_PIN_CHECKED, false);
- dvrPlayer.setContentBlockedListener(new DvrPlayer.ContentBlockedListener() {
- @Override
- public void onContentBlocked(TvContentRating rating) {
- if (mPinChecked) {
- mTvView.unblockContent(rating);
- return;
- }
- mBlockScreenView.setVisibility(View.VISIBLE);
- getActivity().getMediaController().getTransportControls().pause();
- new PinDialogFragment(PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_DVR,
- new PinDialogFragment.ResultListener() {
- @Override
- public void done(boolean success) {
- if (success) {
- mPinChecked = true;
- mTvView.unblockContent(rating);
- mBlockScreenView.setVisibility(View.GONE);
- getActivity().getMediaController()
- .getTransportControls().play();
- }
- }
- }, mContentRatingsManager.getDisplayNameForRating(rating))
- .show(getActivity().getFragmentManager(), PinDialogFragment.DIALOG_TAG);
- }
- });
- }
-
- @Override
- public void onPause() {
- if (DEBUG) Log.d(TAG, "onPause");
- super.onPause();
- if (mMediaSessionHelper.getPlaybackState() == PlaybackState.STATE_FAST_FORWARDING
- || mMediaSessionHelper.getPlaybackState() == PlaybackState.STATE_REWINDING) {
- getActivity().getMediaController().getTransportControls().pause();
- }
- if (mMediaSessionHelper.getPlaybackState() == PlaybackState.STATE_NONE) {
- getActivity().requestVisibleBehind(false);
- } else {
- getActivity().requestVisibleBehind(true);
- }
- }
-
- @Override
- public void onDestroy() {
- if (DEBUG) Log.d(TAG, "onDestroy");
- mPlaybackControlHelper.unregisterCallback();
- mMediaSessionHelper.release();
- mRelatedRecordingCardPresenter.unbindAllViewHolders();
- super.onDestroy();
- }
-
- /**
- * Passes the intent to the fragment.
- */
- public void onNewIntent(Intent intent) {
- mProgram = getProgramFromIntent(intent);
- if (mProgram == null) {
- Toast.makeText(getActivity(), getString(R.string.dvr_program_not_found),
- Toast.LENGTH_SHORT).show();
- // Continue playing the original program
- return;
- }
- preparePlayback(intent);
- }
-
- /**
- * Should be called when windows' size is changed in order to notify DVR player
- * to update it's view width/height and position.
- */
- public void onWindowSizeChanged(final int windowWidth, final int windowHeight) {
- mWindowWidth = windowWidth;
- mWindowHeight = windowHeight;
- mWindowAspectRatio = (float) mWindowWidth / mWindowHeight;
- updateAspectRatio(mAppliedAspectRatio);
- }
-
- public RecordedProgram getNextEpisode(RecordedProgram program) {
- int position = mRelatedRecordingsRowAdapter.findInsertPosition(program);
- if (position == mRelatedRecordingsRowAdapter.size()) {
- return null;
- } else {
- return (RecordedProgram) mRelatedRecordingsRowAdapter.get(position);
- }
- }
-
- void onMediaControllerUpdated() {
- mRowsAdapter.notifyArrayItemRangeChanged(0, 1);
- }
-
- private void updateAspectRatio(float videoAspectRatio) {
- if (Math.abs(mAppliedAspectRatio - videoAspectRatio) < DISPLAY_ASPECT_RATIO_EPSILON) {
- // No need to change
- return;
- }
- if (videoAspectRatio < mWindowAspectRatio) {
- int newPadding = (mWindowWidth - Math.round(mWindowHeight * videoAspectRatio)) / 2;
- ((ViewGroup) mTvView.getParent()).setPadding(newPadding, 0, newPadding, 0);
- } else {
- int newPadding = (mWindowHeight - Math.round(mWindowWidth / videoAspectRatio)) / 2;
- ((ViewGroup) mTvView.getParent()).setPadding(0, newPadding, 0, newPadding);
- }
- mAppliedAspectRatio = videoAspectRatio;
- }
-
- private void preparePlayback(Intent intent) {
- mMediaSessionHelper.setupPlayback(mProgram, getSeekTimeFromIntent(intent));
- getActivity().getMediaController().getTransportControls().prepare();
- updateRelatedRecordingsRow();
- }
-
- private void updateRelatedRecordingsRow() {
- boolean wasEmpty = (mRelatedRecordingsRowAdapter.size() == 0);
- mRelatedRecordingsRowAdapter.clear();
- long programId = mProgram.getId();
- String seriesId = mProgram.getSeriesId();
- if (!TextUtils.isEmpty(seriesId)) {
- if (DEBUG) Log.d(TAG, "Update related recordings with:" + seriesId);
- for (RecordedProgram program : mDvrDataManager.getRecordedPrograms()) {
- if (seriesId.equals(program.getSeriesId()) && 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());
- }
- }
-
- private void setUpRows() {
- PlaybackControlsRowPresenter controlsRowPresenter =
- mPlaybackControlHelper.createControlsRowAndPresenter();
-
- ClassPresenterSelector selector = new ClassPresenterSelector();
- selector.addClassPresenter(PlaybackControlsRow.class, controlsRowPresenter);
- selector.addClassPresenter(ListRow.class, new ListRowPresenter());
-
- mRowsAdapter = new ArrayObjectAdapter(selector);
- mRowsAdapter.add(mPlaybackControlHelper.getControlsRow());
- mRelatedRecordingsRow = getRelatedRecordingsRow();
- setAdapter(mRowsAdapter);
- }
-
- private ListRow getRelatedRecordingsRow() {
- mRelatedRecordingCardPresenter = new DvrPlaybackCardPresenter(getActivity());
- mRelatedRecordingsRowAdapter = new RelatedRecordingsAdapter(mRelatedRecordingCardPresenter);
- HeaderItem header = new HeaderItem(0,
- getActivity().getString(R.string.dvr_playback_related_recordings));
- return new ListRow(header, mRelatedRecordingsRowAdapter);
- }
-
- private RecordedProgram getProgramFromIntent(Intent intent) {
- long programId = intent.getLongExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, -1);
- return mDvrDataManager.getRecordedProgram(programId);
- }
-
- private long getSeekTimeFromIntent(Intent intent) {
- return intent.getLongExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_SEEK_TIME,
- TvInputManager.TIME_SHIFT_INVALID_TIME);
- }
-
- private class RelatedRecordingsAdapter extends SortedArrayAdapter<BaseProgram> {
- RelatedRecordingsAdapter(DvrPlaybackCardPresenter presenter) {
- super(new SinglePresenterSelector(presenter), BaseProgram.EPISODE_COMPARATOR);
- }
-
- @Override
- long getId(BaseProgram item) {
- return item.getId();
- }
- }
-} \ 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..e4cb7243 100644
--- a/src/com/android/tv/dvr/ui/PrioritySettingsFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java
@@ -20,7 +20,6 @@ import android.app.FragmentManager;
import android.content.Context;
import android.graphics.Typeface;
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;
@@ -33,15 +32,13 @@ 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;
-/**
- * Fragment for DVR series recording settings.
- */
-public class PrioritySettingsFragment extends GuidedStepFragment {
+/** Fragment for DVR series recording settings. */
+public class DvrPrioritySettingsFragment extends TrackedGuidedStepFragment {
/**
* Name of series recording id starting the fragment.
* Type: Long
@@ -124,7 +121,7 @@ public class PrioritySettingsFragment extends GuidedStepFragment {
}
@Override
- public void onGuidedActionClicked(GuidedAction action) {
+ public void onTrackedGuidedActionClicked(GuidedAction action) {
long actionId = action.getId();
if (actionId == ACTION_ID_SAVE) {
DvrManager dvrManager = TvApplication.getSingletons(getContext()).getDvrManager();
@@ -156,13 +153,27 @@ public class PrioritySettingsFragment extends GuidedStepFragment {
}
@Override
+ public String getTrackerPrefix() {
+ return "DvrPrioritySettingsFragment";
+ }
+
+ @Override
+ public String getTrackerLabelForGuidedAction(GuidedAction action) {
+ long actionId = action.getId();
+ if (actionId == ACTION_ID_SAVE) {
+ return "save";
+ } else {
+ return super.getTrackerLabelForGuidedAction(action);
+ }
+ }
+
+ @Override
public void onGuidedActionFocused(GuidedAction action) {
super.onGuidedActionFocused(action);
if (mSelectedRecording == null) {
return;
}
if (action.getId() < 0) {
- int selectedPosition = mSeriesRecordings.indexOf(mSelectedRecording);
mSelectedRecording = null;
for (int i = 0; i < mSeriesRecordings.size(); ++i) {
updateItem(i);
@@ -248,4 +259,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..390e0928 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,
@@ -109,7 +116,7 @@ public class DvrScheduleFragment extends DvrGuidedStepFragment {
}
@Override
- public void onGuidedActionClicked(GuidedAction action) {
+ public void onTrackedGuidedActionClicked(GuidedAction action) {
if (action.getId() == ACTION_RECORD_EPISODE) {
getDvrManager().addSchedule(mProgram);
List<ScheduledRecording> conflicts = getDvrManager().getConflictingSchedules(mProgram);
@@ -139,9 +146,28 @@ 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();
}
}
+
+ @Override
+ public String getTrackerPrefix() {
+ return "DvrSmallSizedStorageErrorFragment";
+ }
+
+ @Override
+ public String getTrackerLabelForGuidedAction(GuidedAction action) {
+ long actionId = action.getId();
+ if (actionId == ACTION_RECORD_EPISODE) {
+ return "record-episode";
+ } else if (actionId == ACTION_RECORD_SERIES) {
+ return "record-series";
+ } else {
+ return super.getTrackerLabelForGuidedAction(action);
+ }
+ }
}
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..2c4bb3ea 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;
}
}
@@ -106,45 +116,63 @@ public class DvrSeriesScheduledFragment extends DvrGuidedStepFragment {
}
@Override
- public void onGuidedActionClicked(GuidedAction action) {
+ public void onTrackedGuidedActionClicked(GuidedAction action) {
if (action.getId() == ACTION_VIEW_SCHEDULES) {
Intent intent = new Intent(getActivity(), DvrSchedulesActivity.class);
intent.putExtra(DvrSchedulesActivity.KEY_SCHEDULES_TYPE, DvrSchedulesActivity
.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();
}
+ @Override
+ public String getTrackerPrefix() {
+ return "DvrMissingStorageErrorFragment";
+ }
+
+ @Override
+ public String getTrackerLabelForGuidedAction(GuidedAction action) {
+ long actionId = action.getId();
+ if (actionId == ACTION_VIEW_SCHEDULES) {
+ return "view-schedules";
+ } else {
+ return super.getTrackerLabelForGuidedAction(action);
+ }
+ }
+
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..baa45793 100644
--- a/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java
@@ -25,15 +25,12 @@ import android.support.annotation.IntDef;
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.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 +128,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);
}
@@ -158,4 +148,19 @@ public class DvrStopRecordingFragment extends DvrGuidedStepFragment {
.clickAction(GuidedAction.ACTION_ID_CANCEL)
.build());
}
+
+ @Override
+ public String getTrackerPrefix() {
+ return "DvrStopRecordingFragment";
+ }
+
+ @Override
+ public String getTrackerLabelForGuidedAction(GuidedAction action) {
+ long actionId = action.getId();
+ if (actionId == ACTION_STOP) {
+ return "stop";
+ } else {
+ return super.getTrackerLabelForGuidedAction(action);
+ }
+ }
} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java b/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java
index feaa2357..7b56cfc1 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;
@@ -78,7 +78,7 @@ public class DvrStopSeriesRecordingFragment extends DvrGuidedStepFragment {
}
@Override
- public void onGuidedActionClicked(GuidedAction action) {
+ public void onTrackedGuidedActionClicked(GuidedAction action) {
if (action.getId() == ACTION_STOP_SERIES_RECORDING) {
ApplicationSingletons singletons = TvApplication.getSingletons(getContext());
DvrManager dvrManager = singletons.getDvrManager();
@@ -101,4 +101,18 @@ public class DvrStopSeriesRecordingFragment extends DvrGuidedStepFragment {
}
dismissDialog();
}
+
+ @Override
+ public String getTrackerPrefix() {
+ return "DvrStopSeriesRecordingFragment";
+ }
+
+ @Override
+ public String getTrackerLabelForGuidedAction(GuidedAction action) {
+ if (action.getId() == ACTION_STOP_SERIES_RECORDING) {
+ return "stop";
+ } else {
+ return super.getTrackerLabelForGuidedAction(action);
+ }
+ }
}
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..302fd6cd
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/DvrUiHelper.java
@@ -0,0 +1,649 @@
+/*
+ * 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.text.Html;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.text.style.TextAppearanceSpan;
+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.BaseProgram;
+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.dvr.ui.playback.DvrPlaybackActivity;
+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 final 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;
+ }
+
+ /**
+ * Launches DVR playback activity for the give recorded program.
+ *
+ * @param programId the ID of the recorded program going to be played.
+ * @param seekTimeMs the seek position to initial playback.
+ * @param pinChecked {@code true} if the pin code for parental controls has already been
+ * verified, otherwise {@code false}.
+ */
+ public static void startPlaybackActivity(Context context, long programId,
+ long seekTimeMs, boolean pinChecked) {
+ Intent intent = new Intent(context, DvrPlaybackActivity.class);
+ intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, programId);
+ if (seekTimeMs != TvInputManager.TIME_SHIFT_INVALID_TIME) {
+ intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_SEEK_TIME, seekTimeMs);
+ }
+ intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_PIN_CHECKED, pinChecked);
+ context.startActivity(intent);
+ }
+
+ /**
+ * 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();
+ }
+
+ /**
+ * Returns the styled schedule's title with its season and episode number.
+ */
+ public static CharSequence getStyledTitleWithEpisodeNumber(Context context,
+ ScheduledRecording schedule, int episodeNumberStyleResId) {
+ return getStyledTitleWithEpisodeNumber(context, schedule.getProgramTitle(),
+ schedule.getSeasonNumber(), schedule.getEpisodeNumber(), episodeNumberStyleResId);
+ }
+
+ /**
+ * Returns the styled program's title with its season and episode number.
+ */
+ public static CharSequence getStyledTitleWithEpisodeNumber(Context context,
+ BaseProgram program, int episodeNumberStyleResId) {
+ return getStyledTitleWithEpisodeNumber(context, program.getTitle(),
+ program.getSeasonNumber(), program.getEpisodeNumber(), episodeNumberStyleResId);
+ }
+
+ @NonNull
+ public static CharSequence getStyledTitleWithEpisodeNumber(Context context, String title,
+ String seasonNumber, String episodeNumber, int episodeNumberStyleResId) {
+ if (TextUtils.isEmpty(title)) {
+ return "";
+ }
+ SpannableStringBuilder builder;
+ if (TextUtils.isEmpty(seasonNumber) || seasonNumber.equals("0")) {
+ builder = TextUtils.isEmpty(episodeNumber) ? new SpannableStringBuilder(title) :
+ new SpannableStringBuilder(Html.fromHtml(
+ context.getString(R.string.program_title_with_episode_number_no_season,
+ title, episodeNumber)));
+ } else {
+ builder = new SpannableStringBuilder(Html.fromHtml(
+ context.getString(R.string.program_title_with_episode_number,
+ title, seasonNumber, episodeNumber)));
+ }
+ Object[] spans = builder.getSpans(0, builder.length(), Object.class);
+ if (spans.length > 0) {
+ if (episodeNumberStyleResId != 0) {
+ builder.setSpan(new TextAppearanceSpan(context, episodeNumberStyleResId),
+ builder.getSpanStart(spans[0]), builder.getSpanEnd(spans[0]),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ builder.removeSpan(spans[0]);
+ }
+ return new SpannableString(builder);
+ }
+} \ 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/RecordedProgramPresenter.java b/src/com/android/tv/dvr/ui/RecordedProgramPresenter.java
deleted file mode 100644
index 1bf34310..00000000
--- a/src/com/android/tv/dvr/ui/RecordedProgramPresenter.java
+++ /dev/null
@@ -1,182 +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.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.util.Utils;
-
-import java.util.concurrent.TimeUnit;
-
-/**
- * Presents a {@link RecordedProgram} in the {@link DvrBrowseFragment}.
- */
-public class RecordedProgramPresenter extends DvrItemPresenter {
- private final ChannelDataManager mChannelDataManager;
- private final DvrWatchedPositionManager mDvrWatchedPositionManager;
- private final Context mContext;
- private String mTodayString;
- private String mYesterdayString;
- private final int mProgressBarColor;
- private final boolean mShowEpisodeTitle;
-
- private static final class RecordedProgramViewHolder extends ViewHolder
- implements WatchedPositionChangedListener {
- private RecordedProgram mProgram;
-
- RecordedProgramViewHolder(RecordingCardView view, int progressColor) {
- super(view);
- view.setProgressBarColor(progressColor);
- }
-
- private void setProgram(RecordedProgram program) {
- mProgram = program;
- }
-
- private void setProgressBar(long watchedPositionMs) {
- ((RecordingCardView) view).setProgressBar(
- (watchedPositionMs == TvInputManager.TIME_SHIFT_INVALID_TIME) ? null
- : Math.min(100, (int) (100.0f * watchedPositionMs
- / mProgram.getDurationMillis())));
- }
-
- @Override
- public void onWatchedPositionChanged(long programId, long positionMs) {
- if (programId == mProgram.getId()) {
- setProgressBar(positionMs);
- }
- }
- }
-
- public RecordedProgramPresenter(Context context, boolean showEpisodeTitle) {
- mContext = context;
- mChannelDataManager = TvApplication.getSingletons(context).getChannelDataManager();
- mTodayString = context.getString(R.string.dvr_date_today);
- mYesterdayString = context.getString(R.string.dvr_date_yesterday);
- mDvrWatchedPositionManager =
- TvApplication.getSingletons(context).getDvrWatchedPositionManager();
- mProgressBarColor = context.getResources()
- .getColor(R.color.play_controls_progress_bar_watched);
- mShowEpisodeTitle = showEpisodeTitle;
- }
-
- public RecordedProgramPresenter(Context context) {
- this(context, false);
- }
-
- @Override
- public ViewHolder onCreateViewHolder(ViewGroup parent) {
- RecordingCardView view = new RecordingCardView(mContext);
- return new RecordedProgramViewHolder(view, mProgressBarColor);
- }
-
- @Override
- public void onBindViewHolder(ViewHolder viewHolder, Object o) {
- final RecordedProgram program = (RecordedProgram) o;
- final RecordingCardView cardView = (RecordingCardView) viewHolder.view;
- Channel channel = mChannelDataManager.getChannel(program.getChannelId());
- String titleString = mShowEpisodeTitle ? program.getEpisodeDisplayTitle(mContext)
- : program.getTitleWithEpisodeNumber(mContext);
- SpannableString title = titleString == null ? null : new SpannableString(titleString);
- if (TextUtils.isEmpty(title)) {
- title = new SpannableString(channel != null ? channel.getDisplayName()
- : mContext.getResources().getString(R.string.no_program_information));
- } else if (!mShowEpisodeTitle) {
- // TODO: Some translation may add delimiters in-between program titles, we should use
- // a more robust way to get the span range.
- String programTitle = program.getTitle();
- title.setSpan(new TextAppearanceSpan(mContext,
- R.style.text_appearance_card_view_episode_number), programTitle == null ? 0
- : programTitle.length(), title.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- cardView.setTitle(title);
- String imageUri = null;
- boolean isChannelLogo = false;
- if (program.getPosterArtUri() != null) {
- imageUri = program.getPosterArtUri();
- } else if (program.getThumbnailUri() != null) {
- imageUri = program.getThumbnailUri();
- } else if (channel != null) {
- imageUri = TvContract.buildChannelLogoUri(channel.getId()).toString();
- isChannelLogo = true;
- }
- cardView.setImageUri(imageUri, isChannelLogo);
- int durationMinutes =
- Math.max(1, (int) TimeUnit.MILLISECONDS.toMinutes(program.getDurationMillis()));
- String durationString = getContext().getResources().getQuantityString(
- R.plurals.dvr_program_duration, durationMinutes, durationMinutes);
- cardView.setContent(getDescription(program), durationString);
- if (viewHolder instanceof RecordedProgramViewHolder) {
- RecordedProgramViewHolder cardViewHolder = (RecordedProgramViewHolder) viewHolder;
- cardViewHolder.setProgram(program);
- mDvrWatchedPositionManager.addListener(cardViewHolder, program.getId());
- cardViewHolder
- .setProgressBar(mDvrWatchedPositionManager.getWatchedPosition(program.getId()));
- }
- super.onBindViewHolder(viewHolder, o);
- }
-
- @Override
- public void onUnbindViewHolder(ViewHolder viewHolder) {
- if (viewHolder instanceof RecordedProgramViewHolder) {
- mDvrWatchedPositionManager.removeListener((RecordedProgramViewHolder) viewHolder,
- ((RecordedProgramViewHolder) viewHolder).mProgram.getId());
- }
- ((RecordingCardView) viewHolder.view).reset();
- super.onUnbindViewHolder(viewHolder);
- }
-
- /**
- * Returns description would be used in its card view.
- */
- protected String getDescription(RecordedProgram recording) {
- int dateDifference = Utils.computeDateDifference(recording.getStartTimeUtcMillis(),
- System.currentTimeMillis());
- if (dateDifference == 0) {
- return mTodayString;
- } else if (dateDifference == 1) {
- return mYesterdayString;
- } else {
- return Utils.getDurationString(mContext, recording.getStartTimeUtcMillis(),
- recording.getStartTimeUtcMillis(), false, true, false, 0);
- }
- }
-
- /**
- * Returns context.
- */
- protected Context getContext() {
- return mContext;
- }
-}
diff --git a/src/com/android/tv/dvr/ui/RecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/RecordingDetailsFragment.java
deleted file mode 100644
index 4e19ec3f..00000000
--- a/src/com/android/tv/dvr/ui/RecordingDetailsFragment.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.os.Bundle;
-import android.support.v17.leanback.app.DetailsFragment;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.TextUtils;
-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;
-
-/**
- * {@link DetailsFragment} for recordings in DVR.
- */
-abstract class RecordingDetailsFragment extends DvrDetailsFragment {
- private ScheduledRecording mRecording;
-
- @Override
- protected void onCreateInternal() {
- setDetailsOverviewRow(createDetailsContent());
- }
-
- @Override
- protected boolean onLoadRecordingDetails(Bundle args) {
- long scheduledRecordingId = args.getLong(DvrDetailsActivity.RECORDING_ID);
- mRecording = TvApplication.getSingletons(getContext()).getDvrDataManager()
- .getScheduledRecording(scheduledRecordingId);
- return mRecording != null;
- }
-
- /**
- * Returns {@link ScheduledRecording} for the current fragment.
- */
- public ScheduledRecording getRecording() {
- return mRecording;
- }
-
- private DetailsContent createDetailsContent() {
- Channel channel = TvApplication.getSingletons(getContext()).getChannelDataManager()
- .getChannel(mRecording.getChannelId());
- SpannableString title = mRecording.getProgramTitleWithEpisodeNumber(getContext()) == null ?
- null : new SpannableString(mRecording
- .getProgramTitleWithEpisodeNumber(getContext()));
- if (TextUtils.isEmpty(title)) {
- title = new SpannableString(channel != null ? channel.getDisplayName()
- : getContext().getResources().getString(
- R.string.no_program_information));
- } else {
- String programTitle = mRecording.getProgramTitle();
- title.setSpan(new TextAppearanceSpan(getContext(),
- R.style.text_appearance_card_view_episode_number), programTitle == null ? 0
- : programTitle.length(), title.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- String description = !TextUtils.isEmpty(mRecording.getProgramDescription()) ?
- mRecording.getProgramDescription() : mRecording.getProgramLongDescription();
- if (TextUtils.isEmpty(description)) {
- description = channel != null ? channel.getDescription() : null;
- }
- return new DetailsContent.Builder()
- .setTitle(title)
- .setStartTimeUtcMillis(mRecording.getStartTimeMs())
- .setEndTimeUtcMillis(mRecording.getEndTimeMs())
- .setDescription(description)
- .setImageUris(mRecording.getProgramPosterArtUri(),
- mRecording.getProgramThumbnailUri(), channel)
- .build();
- }
-}
diff --git a/src/com/android/tv/dvr/ui/ScheduledRecordingPresenter.java b/src/com/android/tv/dvr/ui/ScheduledRecordingPresenter.java
deleted file mode 100644
index 5f447f13..00000000
--- a/src/com/android/tv/dvr/ui/ScheduledRecordingPresenter.java
+++ /dev/null
@@ -1,177 +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.content.Context;
-import android.media.tv.TvContract;
-import android.os.Handler;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.TextUtils;
-import android.text.style.TextAppearanceSpan;
-import android.view.ViewGroup;
-
-import com.android.tv.ApplicationSingletons;
-import com.android.tv.R;
-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.util.Utils;
-
-import java.util.concurrent.TimeUnit;
-
-/**
- * Presents a {@link ScheduledRecording} in the {@link DvrBrowseFragment}.
- */
-public class ScheduledRecordingPresenter extends DvrItemPresenter {
- private static final long PROGRESS_UPDATE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(5);
-
- private final ChannelDataManager mChannelDataManager;
- private final DvrManager mDvrManager;
- private final Context mContext;
- private final int mProgressBarColor;
-
- private static final class ScheduledRecordingViewHolder extends ViewHolder {
- private final Handler mHandler = new Handler();
- private ScheduledRecording mScheduledRecording;
- private final Runnable mProgressBarUpdater = new Runnable() {
- @Override
- public void run() {
- updateProgressBar();
- mHandler.postDelayed(this, PROGRESS_UPDATE_INTERVAL_MS);
- }
- };
-
- ScheduledRecordingViewHolder(RecordingCardView view, int progressBarColor) {
- super(view);
- view.setProgressBarColor(progressBarColor);
- }
-
- private void updateProgressBar() {
- if (mScheduledRecording == null) {
- return;
- }
- int recordingState = mScheduledRecording.getState();
- RecordingCardView cardView = (RecordingCardView) view;
- if (recordingState == ScheduledRecording.STATE_RECORDING_IN_PROGRESS) {
- cardView.setProgressBar(Math.max(0, Math.min((int) (100 *
- (System.currentTimeMillis() - mScheduledRecording.getStartTimeMs())
- / mScheduledRecording.getDuration()), 100)));
- } else if (recordingState == ScheduledRecording.STATE_RECORDING_FINISHED) {
- cardView.setProgressBar(100);
- } else {
- // Hides progress bar.
- cardView.setProgressBar(null);
- }
- }
-
- private void startUpdateProgressBar() {
- mHandler.post(mProgressBarUpdater);
- }
-
- private void stopUpdateProgressBar() {
- mHandler.removeCallbacks(mProgressBarUpdater);
- }
- }
-
- public ScheduledRecordingPresenter(Context context) {
- ApplicationSingletons singletons = TvApplication.getSingletons(context);
- mChannelDataManager = singletons.getChannelDataManager();
- mDvrManager = singletons.getDvrManager();
- mContext = context;
- mProgressBarColor = context.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);
- return new ScheduledRecordingViewHolder(view, mProgressBarColor);
- }
-
- @Override
- public void onBindViewHolder(ViewHolder baseHolder, Object o) {
- final ScheduledRecordingViewHolder viewHolder = (ScheduledRecordingViewHolder) baseHolder;
- final ScheduledRecording recording = (ScheduledRecording) o;
- final RecordingCardView cardView = (RecordingCardView) viewHolder.view;
- final Context context = viewHolder.view.getContext();
-
- setTitleAndImage(cardView, recording);
- int dateDifference = Utils.computeDateDifference(System.currentTimeMillis(),
- recording.getStartTimeMs());
- if (dateDifference <= 0) {
- cardView.setContent(mContext.getString(R.string.dvr_date_today_time,
- Utils.getDurationString(context, recording.getStartTimeMs(),
- recording.getEndTimeMs(), false, false, true, 0)), null);
- } else if (dateDifference == 1) {
- cardView.setContent(mContext.getString(R.string.dvr_date_tomorrow_time,
- Utils.getDurationString(context, recording.getStartTimeMs(),
- recording.getEndTimeMs(), false, false, true, 0)), null);
- } else {
- cardView.setContent(Utils.getDurationString(context, recording.getStartTimeMs(),
- recording.getStartTimeMs(), false, true, false, 0), null);
- }
- if (mDvrManager.isConflicting(recording)) {
- cardView.setAffiliatedIcon(R.drawable.ic_warning_white_32dp);
- } else {
- cardView.setAffiliatedIcon(0);
- }
- viewHolder.updateProgressBar();
- viewHolder.mScheduledRecording = recording;
- viewHolder.startUpdateProgressBar();
- super.onBindViewHolder(viewHolder, o);
- }
-
- @Override
- public void onUnbindViewHolder(ViewHolder baseHolder) {
- ScheduledRecordingViewHolder viewHolder = (ScheduledRecordingViewHolder) baseHolder;
- viewHolder.stopUpdateProgressBar();
- final RecordingCardView cardView = (RecordingCardView) viewHolder.view;
- viewHolder.mScheduledRecording = null;
- cardView.reset();
- super.onUnbindViewHolder(viewHolder);
- }
-
- private void setTitleAndImage(RecordingCardView cardView, ScheduledRecording recording) {
- Channel channel = mChannelDataManager.getChannel(recording.getChannelId());
- SpannableString title = recording.getProgramTitleWithEpisodeNumber(mContext) == null ?
- null : new SpannableString(recording.getProgramTitleWithEpisodeNumber(mContext));
- if (TextUtils.isEmpty(title)) {
- title = new SpannableString(channel != null ? channel.getDisplayName()
- : mContext.getResources().getString(R.string.no_program_information));
- } else {
- String programTitle = recording.getProgramTitle();
- title.setSpan(new TextAppearanceSpan(mContext,
- R.style.text_appearance_card_view_episode_number),
- programTitle == null ? 0 : programTitle.length(), title.length(),
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- String imageUri = recording.getProgramPosterArtUri();
- boolean isChannelLogo = false;
- if (TextUtils.isEmpty(imageUri)) {
- imageUri = channel != null ?
- TvContract.buildChannelLogoUri(channel.getId()).toString() : null;
- isChannelLogo = true;
- }
- cardView.setTitle(title);
- cardView.setImageUri(imageUri, isChannelLogo);
- }
-}
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/TrackedGuidedStepFragment.java b/src/com/android/tv/dvr/ui/TrackedGuidedStepFragment.java
new file mode 100644
index 00000000..5fe9c478
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/TrackedGuidedStepFragment.java
@@ -0,0 +1,82 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.support.v17.leanback.app.GuidedStepFragment;
+import android.support.v17.leanback.widget.GuidedAction;
+
+import com.android.tv.TvApplication;
+import com.android.tv.analytics.Tracker;
+
+/** A {@link GuidedStepFragment} with {@link Tracker} for analytics. */
+public abstract class TrackedGuidedStepFragment extends GuidedStepFragment {
+ private Tracker mTracker;
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ mTracker = TvApplication.getSingletons(context).getAnalytics().getDefaultTracker();
+ }
+
+ @Override
+ public void onDetach() {
+ mTracker = null;
+ super.onDetach();
+ }
+
+ @Override
+ public final void onGuidedActionClicked(GuidedAction action) {
+ super.onGuidedActionClicked(action);
+ if (mTracker != null) {
+ mTracker.sendMenuClicked(
+ getTrackerPrefix() + "-action-" + getTrackerLabelForGuidedAction(action));
+ }
+ onTrackedGuidedActionClicked(action);
+ }
+
+ public String getTrackerLabelForGuidedAction(GuidedAction action) {
+ long actionId = action.getId();
+ if (actionId == GuidedAction.ACTION_ID_CANCEL) {
+ return "cancel";
+ } else if (actionId == GuidedAction.ACTION_ID_NEXT) {
+ return "next";
+ } else if (actionId == GuidedAction.ACTION_ID_CURRENT) {
+ return "current";
+ } else if (actionId == GuidedAction.ACTION_ID_OK) {
+ return "ok";
+ } else if (actionId == GuidedAction.ACTION_ID_CANCEL) {
+ return "cancel";
+ } else if (actionId == GuidedAction.ACTION_ID_FINISH) {
+ return "finish";
+ } else if (actionId == GuidedAction.ACTION_ID_CONTINUE) {
+ return "continue";
+ } else if (actionId == GuidedAction.ACTION_ID_YES) {
+ return "yes";
+ } else if (actionId == GuidedAction.ACTION_ID_NO) {
+ return "no";
+ } else {
+ return "unknown-" + actionId;
+ }
+ }
+
+ /** Delegated from {@link #onGuidedActionClicked(GuidedAction)} */
+ public abstract void onTrackedGuidedActionClicked(GuidedAction action);
+
+ /** The prefix used for analytics tracking, Usually the class name. */
+ public abstract String getTrackerPrefix();
+}
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..bf18ddc0
--- /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.dvr_detail_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/browse/DetailsContent.java b/src/com/android/tv/dvr/ui/browse/DetailsContent.java
new file mode 100644
index 00000000..c1fa05d7
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/browse/DetailsContent.java
@@ -0,0 +1,317 @@
+/*
+ * 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.media.tv.TvContract;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+
+import com.android.tv.R;
+import com.android.tv.TvApplication;
+import com.android.tv.data.Channel;
+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.DvrUiHelper;
+
+/**
+ * A class for details content.
+ */
+class DetailsContent {
+ /** Constant for invalid time. */
+ public static final long INVALID_TIME = -1;
+
+ private CharSequence mTitle;
+ private long mStartTimeUtcMillis;
+ private long mEndTimeUtcMillis;
+ private String mDescription;
+ private String mLogoImageUri;
+ private String mBackgroundImageUri;
+ private boolean mUsingChannelLogo;
+
+ static DetailsContent createFromRecordedProgram(Context context,
+ RecordedProgram recordedProgram) {
+ return new DetailsContent.Builder()
+ .setChannelId(recordedProgram.getChannelId())
+ .setProgramTitle(recordedProgram.getTitle())
+ .setSeasonNumber(recordedProgram.getSeasonNumber())
+ .setEpisodeNumber(recordedProgram.getEpisodeNumber())
+ .setStartTimeUtcMillis(recordedProgram.getStartTimeUtcMillis())
+ .setEndTimeUtcMillis(recordedProgram.getEndTimeUtcMillis())
+ .setDescription(TextUtils.isEmpty(recordedProgram.getLongDescription())
+ ? recordedProgram.getDescription() : recordedProgram.getLongDescription())
+ .setPosterArtUri(recordedProgram.getPosterArtUri())
+ .setThumbnailUri(recordedProgram.getThumbnailUri())
+ .build(context);
+ }
+
+ static DetailsContent createFromSeriesRecording(Context context,
+ SeriesRecording seriesRecording) {
+ return new DetailsContent.Builder()
+ .setChannelId(seriesRecording.getChannelId())
+ .setTitle(seriesRecording.getTitle())
+ .setDescription(TextUtils.isEmpty(seriesRecording.getLongDescription())
+ ? seriesRecording.getDescription() : seriesRecording.getLongDescription())
+ .setPosterArtUri(seriesRecording.getPosterUri())
+ .setThumbnailUri(seriesRecording.getPhotoUri())
+ .build(context);
+ }
+
+ static DetailsContent createFromScheduledRecording(Context context,
+ ScheduledRecording scheduledRecording) {
+ Channel channel = TvApplication.getSingletons(context).getChannelDataManager()
+ .getChannel(scheduledRecording.getChannelId());
+ String description = !TextUtils.isEmpty(scheduledRecording.getProgramDescription()) ?
+ scheduledRecording.getProgramDescription()
+ : scheduledRecording.getProgramLongDescription();
+ if (TextUtils.isEmpty(description)) {
+ description = channel != null ? channel.getDescription() : null;
+ }
+ return new DetailsContent.Builder()
+ .setChannelId(scheduledRecording.getChannelId())
+ .setProgramTitle(scheduledRecording.getProgramTitle())
+ .setSeasonNumber(scheduledRecording.getSeasonNumber())
+ .setEpisodeNumber(scheduledRecording.getEpisodeNumber())
+ .setStartTimeUtcMillis(scheduledRecording.getStartTimeMs())
+ .setEndTimeUtcMillis(scheduledRecording.getEndTimeMs())
+ .setDescription(description)
+ .setPosterArtUri(scheduledRecording.getProgramPosterArtUri())
+ .setThumbnailUri(scheduledRecording.getProgramThumbnailUri())
+ .build(context);
+ }
+
+ private DetailsContent() { }
+
+ /**
+ * Returns title.
+ */
+ public CharSequence getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Returns start time.
+ */
+ public long getStartTimeUtcMillis() {
+ return mStartTimeUtcMillis;
+ }
+
+ /**
+ * Returns end time.
+ */
+ public long getEndTimeUtcMillis() {
+ return mEndTimeUtcMillis;
+ }
+
+ /**
+ * Returns description.
+ */
+ public String getDescription() {
+ return mDescription;
+ }
+
+ /**
+ * Returns Logo image URI as a String.
+ */
+ public String getLogoImageUri() {
+ return mLogoImageUri;
+ }
+
+ /**
+ * Returns background image URI as a String.
+ */
+ public String getBackgroundImageUri() {
+ return mBackgroundImageUri;
+ }
+
+ /**
+ * Returns if image URIs are from its channels' logo.
+ */
+ public boolean isUsingChannelLogo() {
+ return mUsingChannelLogo;
+ }
+
+ /**
+ * Copies other details content.
+ */
+ public void copyFrom(DetailsContent other) {
+ if (this == other) {
+ return;
+ }
+ mTitle = other.mTitle;
+ mStartTimeUtcMillis = other.mStartTimeUtcMillis;
+ mEndTimeUtcMillis = other.mEndTimeUtcMillis;
+ mDescription = other.mDescription;
+ mLogoImageUri = other.mLogoImageUri;
+ mBackgroundImageUri = other.mBackgroundImageUri;
+ mUsingChannelLogo = other.mUsingChannelLogo;
+ }
+
+ /**
+ * A class for building details content.
+ */
+ public static final class Builder {
+ private final DetailsContent mDetailsContent;
+
+ private long mChannelId;
+ private String mProgramTitle;
+ private String mSeasonNumber;
+ private String mEpisodeNumber;
+ private String mPosterArtUri;
+ private String mThumbnailUri;
+
+ public Builder() {
+ mDetailsContent = new DetailsContent();
+ mDetailsContent.mStartTimeUtcMillis = INVALID_TIME;
+ mDetailsContent.mEndTimeUtcMillis = INVALID_TIME;
+ }
+
+ /**
+ * Sets title.
+ */
+ public Builder setTitle(CharSequence title) {
+ mDetailsContent.mTitle = title;
+ return this;
+ }
+
+ /**
+ * Sets start time.
+ */
+ public Builder setStartTimeUtcMillis(long startTimeUtcMillis) {
+ mDetailsContent.mStartTimeUtcMillis = startTimeUtcMillis;
+ return this;
+ }
+
+ /**
+ * Sets end time.
+ */
+ public Builder setEndTimeUtcMillis(long endTimeUtcMillis) {
+ mDetailsContent.mEndTimeUtcMillis = endTimeUtcMillis;
+ return this;
+ }
+
+ /**
+ * Sets description.
+ */
+ public Builder setDescription(String description) {
+ mDetailsContent.mDescription = description;
+ return this;
+ }
+
+ /**
+ * Sets logo image URI as a String.
+ */
+ public Builder setLogoImageUri(String logoImageUri) {
+ mDetailsContent.mLogoImageUri = logoImageUri;
+ return this;
+ }
+
+ /**
+ * Sets background image URI as a String.
+ */
+ public Builder setBackgroundImageUri(String backgroundImageUri) {
+ mDetailsContent.mBackgroundImageUri = backgroundImageUri;
+ return this;
+ }
+
+ private Builder setProgramTitle(String programTitle) {
+ mProgramTitle = programTitle;
+ return this;
+ }
+
+ private Builder setSeasonNumber(String seasonNumber) {
+ mSeasonNumber = seasonNumber;
+ return this;
+ }
+
+ private Builder setEpisodeNumber(String episodeNumber) {
+ mEpisodeNumber = episodeNumber;
+ return this;
+ }
+
+ private Builder setChannelId(long channelId) {
+ mChannelId = channelId;
+ return this;
+ }
+
+ private Builder setPosterArtUri(String posterArtUri) {
+ mPosterArtUri = posterArtUri;
+ return this;
+ }
+
+ private Builder setThumbnailUri(String thumbnailUri) {
+ mThumbnailUri = thumbnailUri;
+ return this;
+ }
+
+ private void createStyledTitle(Context context, Channel channel) {
+ CharSequence title = DvrUiHelper.getStyledTitleWithEpisodeNumber(context,
+ mProgramTitle, mSeasonNumber, mEpisodeNumber,
+ R.style.text_appearance_card_view_episode_number);
+ if (TextUtils.isEmpty(title)) {
+ mDetailsContent.mTitle = channel != null ? channel.getDisplayName()
+ : context.getResources().getString(R.string.no_program_information);
+ } else {
+ mDetailsContent.mTitle = title;
+ }
+ }
+
+ private void createImageUris(@Nullable Channel channel) {
+ mDetailsContent.mLogoImageUri = null;
+ mDetailsContent.mBackgroundImageUri = null;
+ mDetailsContent.mUsingChannelLogo = false;
+ if (!TextUtils.isEmpty(mPosterArtUri) && !TextUtils.isEmpty(mThumbnailUri)) {
+ mDetailsContent.mLogoImageUri = mPosterArtUri;
+ mDetailsContent.mBackgroundImageUri = mThumbnailUri;
+ } else if (!TextUtils.isEmpty(mPosterArtUri)) {
+ // thumbnailUri is empty
+ mDetailsContent.mLogoImageUri = mPosterArtUri;
+ mDetailsContent.mBackgroundImageUri = mPosterArtUri;
+ } else if (!TextUtils.isEmpty(mThumbnailUri)) {
+ // posterArtUri is empty
+ mDetailsContent.mLogoImageUri = mThumbnailUri;
+ mDetailsContent.mBackgroundImageUri = mThumbnailUri;
+ }
+ if (TextUtils.isEmpty(mDetailsContent.mLogoImageUri) && channel != null) {
+ String channelLogoUri = TvContract.buildChannelLogoUri(channel.getId())
+ .toString();
+ mDetailsContent.mLogoImageUri = channelLogoUri;
+ mDetailsContent.mBackgroundImageUri = channelLogoUri;
+ mDetailsContent.mUsingChannelLogo = true;
+ }
+ }
+
+ /**
+ * Builds details content.
+ */
+ public DetailsContent build(Context context) {
+ Channel channel = TvApplication.getSingletons(context).getChannelDataManager()
+ .getChannel(mChannelId);
+ if (mDetailsContent.mTitle == null) {
+ createStyledTitle(context, channel);
+ }
+ if (mDetailsContent.mBackgroundImageUri == null
+ && mDetailsContent.mLogoImageUri == null) {
+ createImageUris(channel);
+ }
+ DetailsContent detailsContent = new DetailsContent();
+ detailsContent.copyFrom(mDetailsContent);
+ return detailsContent;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/DetailsContentPresenter.java b/src/com/android/tv/dvr/ui/browse/DetailsContentPresenter.java
index 175f05bc..09b57887 100644
--- a/src/com/android/tv/dvr/ui/DetailsContentPresenter.java
+++ b/src/com/android/tv/dvr/ui/browse/DetailsContentPresenter.java
@@ -14,13 +14,14 @@
* 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;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
+import android.content.Context;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
import android.support.v17.leanback.widget.Presenter;
@@ -29,6 +30,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
+import android.view.accessibility.AccessibilityManager;
import android.widget.LinearLayout;
import android.widget.TextView;
@@ -38,13 +40,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}.
*/
@@ -82,25 +85,38 @@ public class DetailsContentPresenter extends Presenter {
return false;
}
final int bodyLines = mBody.getLineCount();
- final int maxLines = mFullTextMode ? bodyLines :
+ int maxLines = mFullTextMode ? bodyLines :
(mTitle.getLineCount() > 1 ? mBodyMinLines : mBodyMaxLines);
if (bodyLines > maxLines) {
mReadMoreView.setVisibility(View.VISIBLE);
mDescriptionContainer.setFocusable(true);
+ mDescriptionContainer.setClickable(true);
mDescriptionContainer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mFullTextMode = true;
mReadMoreView.setVisibility(View.GONE);
- mDescriptionContainer.setFocusable(false);
+ mDescriptionContainer.setFocusable((
+ (AccessibilityManager) view.getContext()
+ .getSystemService(
+ Context.ACCESSIBILITY_SERVICE))
+ .isEnabled());
+ mDescriptionContainer.setClickable(false);
mDescriptionContainer.setOnClickListener(null);
+ int oldMaxLines = mBody.getMaxLines();
mBody.setMaxLines(bodyLines);
// Minus 1 from line difference to eliminate the space
// originally occupied by "READ MORE"
- showFullText((bodyLines - maxLines - 1) * mBodyLineSpacing);
+ showFullText((bodyLines - oldMaxLines - 1) * mBodyLineSpacing);
}
});
}
+ if (mReadMoreView.getVisibility() == View.VISIBLE
+ && mSubtitle.getVisibility() == View.VISIBLE) {
+ // If both "READ MORE" and subtitle is shown, the capable maximum lines
+ // will be one line less.
+ maxLines -= 1;
+ }
if (mBody.getMaxLines() != maxLines) {
mBody.setMaxLines(maxLines);
return false;
@@ -113,11 +129,30 @@ 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);
mDescriptionContainer =
(LinearLayout) view.findViewById(R.id.dvr_details_description_container);
+ // We have to explicitly set focusable to true here for accessibility, since we might
+ // set the view's focusable state when we need to show "READ MORE", which would remove
+ // the default focusable state for accessibility.
+ mDescriptionContainer.setFocusable(((AccessibilityManager) view.getContext()
+ .getSystemService(Context.ACCESSIBILITY_SERVICE)).isEnabled());
mReadMoreView = (TextView) view.findViewById(R.id.dvr_details_description_read_more);
FontMetricsInt titleFontMetricsInt = getFontMetricsInt(mTitle);
@@ -129,7 +164,7 @@ public class DetailsContentPresenter extends Presenter {
mUnderTitleBaselineMargin = view.getResources().getDimensionPixelSize(
R.dimen.lb_details_description_under_title_baseline_margin);
mUnderSubtitleBaselineMargin = view.getResources().getDimensionPixelSize(
- R.dimen.lb_details_description_under_subtitle_baseline_margin);
+ R.dimen.dvr_details_description_under_subtitle_baseline_margin);
mTitleLineSpacing = view.getResources().getDimensionPixelSize(
R.dimen.lb_details_description_title_line_spacing);
@@ -276,22 +311,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..07eec107 100644
--- a/src/com/android/tv/dvr/ui/DvrActivity.java
+++ b/src/com/android/tv/dvr/ui/browse/DvrBrowseActivity.java
@@ -14,9 +14,11 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
import android.app.Activity;
+import android.content.Intent;
+import android.media.tv.TvInputManager;
import android.os.Bundle;
import com.android.tv.R;
@@ -25,11 +27,26 @@ import com.android.tv.TvApplication;
/**
* {@link android.app.Activity} for DVR UI.
*/
-public class DvrActivity extends Activity {
+public class DvrBrowseActivity extends Activity {
+ private DvrBrowseFragment mFragment;
+
@Override
public void onCreate(Bundle savedInstanceState) {
TvApplication.setCurrentRunningProcess(this, true);
super.onCreate(savedInstanceState);
setContentView(R.layout.dvr_main);
+ mFragment = (DvrBrowseFragment) getFragmentManager().findFragmentById(R.id.dvr_frame);
+ handleIntent(getIntent());
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ handleIntent(intent);
+ }
+
+ private void handleIntent(Intent intent) {
+ if (TvInputManager.ACTION_VIEW_RECORDING_SCHEDULES.equals(intent.getAction())) {
+ mFragment.showScheduledRow();
+ }
}
-}
+} \ 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..cb3a5745 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;
@@ -64,12 +64,16 @@ public class DvrBrowseFragment extends BrowseFragment implements
private static final int MAX_RECENT_ITEM_COUNT = 10;
private static final int MAX_SCHEDULED_ITEM_COUNT = 4;
+ private boolean mShouldShowScheduleRow;
+ private boolean mEntranceTransitionEnded;
+
private RecordedProgramAdapter mRecentAdapter;
private ScheduleAdapter mScheduleAdapter;
private SeriesAdapter mSeriesAdapter;
private RecordedProgramAdapter[] mGenreAdapters =
new RecordedProgramAdapter[GenreItems.getGenreCount() + 1];
private ListRow mRecentRow;
+ private ListRow mScheduledRow;
private ListRow mSeriesRow;
private ListRow[] mGenreRows = new ListRow[GenreItems.getGenreCount() + 1];
private List<String> mGenreLabels;
@@ -79,6 +83,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
@@ -104,7 +122,7 @@ public class DvrBrowseFragment extends BrowseFragment implements
}
};
- private final Comparator<Object> SCHEDULE_COMPARATOR = new Comparator<Object>() {
+ private static final Comparator<Object> SCHEDULE_COMPARATOR = new Comparator<Object>() {
@Override
public int compare(Object lhs, Object rhs) {
if (lhs instanceof ScheduledRecording) {
@@ -128,7 +146,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 +172,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 +188,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 +222,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 +285,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 +322,80 @@ public class DvrBrowseFragment extends BrowseFragment implements
super.showTitle(flags);
}
- private void setupUiElements() {
+ @Override
+ protected void onEntranceTransitionEnd() {
+ super.onEntranceTransitionEnd();
+ if (mShouldShowScheduleRow) {
+ showScheduledRowInternal();
+ }
+ mEntranceTransitionEnded = true;
+ }
+
+ void showScheduledRow() {
+ if (!mEntranceTransitionEnded) {
+ setHeadersState(HEADERS_HIDDEN);
+ mShouldShowScheduleRow = true;
+ } else {
+ showScheduledRowInternal();
+ }
+ }
+
+ private void showScheduledRowInternal() {
+ setSelectedPosition(mRowsAdapter.indexOf(mScheduledRow), true, null);
+ if (getHeadersState() == HEADERS_ENABLED) {
+ startHeadersTransition(false);
+ }
+ mShouldShowScheduleRow = false;
+ }
+
+ 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);
+ mScheduledRow = new ListRow(new HeaderItem(
+ getString(R.string.dvr_main_scheduled)), mScheduleAdapter);
+ mSeriesRow = new ListRow(new HeaderItem(
+ getString(R.string.dvr_main_series)), mSeriesAdapter);
+ mRowsAdapter.add(mScheduledRow);
+ 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 +652,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..35d21db8 100644
--- a/src/com/android/tv/dvr/ui/DvrDetailsActivity.java
+++ b/src/com/android/tv/dvr/ui/browse/DvrDetailsActivity.java
@@ -14,19 +14,23 @@
* 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;
import android.support.v17.leanback.app.DetailsFragment;
+import android.transition.Transition;
+import android.transition.Transition.TransitionListener;
+import android.view.View;
import com.android.tv.R;
import com.android.tv.TvApplication;
+import com.android.tv.dialog.PinDialogFragment;
/**
* Activity to show details view in DVR.
*/
-public class DvrDetailsActivity extends Activity {
+public class DvrDetailsActivity extends Activity implements PinDialogFragment.OnPinCheckedListener {
/**
* Name of record id added to the Intent.
*/
@@ -68,6 +72,8 @@ public class DvrDetailsActivity extends Activity {
*/
public static final int SERIES_RECORDING_VIEW = 4;
+ private PinDialogFragment.OnPinCheckedListener mOnPinCheckedListener;
+
@Override
public void onCreate(Bundle savedInstanceState) {
TvApplication.setCurrentRunningProcess(this, true);
@@ -94,5 +100,55 @@ public class DvrDetailsActivity extends Activity {
getFragmentManager().beginTransaction()
.replace(R.id.dvr_details_view_frame, detailsFragment).commit();
}
+
+ // This is a workaround for the focus on O device
+ addTransitionListener();
+ }
+
+ @Override
+ public void onPinChecked(boolean checked, int type, String rating) {
+ if (mOnPinCheckedListener != null) {
+ mOnPinCheckedListener.onPinChecked(checked, type, rating);
+ }
+ }
+
+ void setOnPinCheckListener(PinDialogFragment.OnPinCheckedListener listener) {
+ mOnPinCheckedListener = listener;
+ }
+
+ private void addTransitionListener() {
+ getWindow()
+ .getSharedElementEnterTransition()
+ .addListener(
+ new TransitionListener() {
+ @Override
+ public void onTransitionStart(Transition transition) {
+ // Do nothing
+ }
+
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ View actions = findViewById(R.id.details_overview_actions);
+ if (actions != null) {
+ actions.requestFocus();
+ }
+ }
+
+ @Override
+ public void onTransitionCancel(Transition transition) {
+ // Do nothing
+
+ }
+
+ @Override
+ public void onTransitionPause(Transition transition) {
+ // Do nothing
+ }
+
+ @Override
+ public void onTransitionResume(Transition transition) {
+ // Do nothing
+ }
+ });
}
}
diff --git a/src/com/android/tv/dvr/ui/DvrDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java
index 21f9c4b4..19fb7117 100644
--- a/src/com/android/tv/dvr/ui/DvrDetailsFragment.java
+++ b/src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java
@@ -14,16 +14,14 @@
* 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;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.media.tv.TvContentRating;
-import android.media.tv.TvInputManager;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
@@ -36,20 +34,18 @@ import android.support.v17.leanback.widget.OnActionClickedListener;
import android.support.v17.leanback.widget.PresenterSelector;
import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
import android.support.v17.leanback.widget.VerticalGridView;
-import android.text.Spannable;
-import android.text.SpannableString;
import android.text.TextUtils;
-import android.text.style.TextAppearanceSpan;
import android.widget.Toast;
import com.android.tv.R;
import com.android.tv.TvApplication;
-import com.android.tv.data.BaseProgram;
+import com.android.tv.common.SoftPreconditions;
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.dialog.PinDialogFragment.OnPinCheckedListener;
+import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.dvr.ui.DvrUiHelper;
import com.android.tv.parental.ParentalControlSettings;
import com.android.tv.util.ImageLoader;
import com.android.tv.util.ToastUtils;
@@ -163,26 +159,6 @@ abstract class DvrDetailsFragment extends DetailsFragment {
abstract OnActionClickedListener onCreateOnActionClickedListener();
/**
- * Returns program title with episode number. If the program is null, returns channel name.
- */
- protected CharSequence getTitleFromProgram(BaseProgram program, Channel channel) {
- String titleWithEpisodeNumber = program.getTitleWithEpisodeNumber(getContext());
- SpannableString title = titleWithEpisodeNumber == null ? null
- : new SpannableString(titleWithEpisodeNumber);
- if (TextUtils.isEmpty(title)) {
- title = new SpannableString(channel != null ? channel.getDisplayName()
- : getContext().getResources().getString(
- R.string.no_program_information));
- } else {
- String programTitle = program.getTitle();
- title.setSpan(new TextAppearanceSpan(getContext(),
- R.style.text_appearance_card_view_episode_number), programTitle == null ? 0
- : programTitle.length(), title.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- return title;
- }
-
- /**
* Loads logo and background images for detail fragments.
*/
protected void onLoadLogoAndBackgroundImages(DetailsContent detailsContent) {
@@ -233,10 +209,11 @@ abstract class DvrDetailsFragment extends DetailsFragment {
Toast.LENGTH_SHORT);
return;
}
+ long programId = recordedProgram.getId();
ParentalControlSettings parental = TvApplication.getSingletons(getActivity())
.getTvInputManagerHelper().getParentalControlSettings();
if (!parental.isParentalControlsEnabled()) {
- launchPlaybackActivity(recordedProgram, seekTimeMs, false);
+ DvrUiHelper.startPlaybackActivity(getContext(), programId, seekTimeMs, false);
return;
}
ChannelDataManager channelDataManager =
@@ -246,21 +223,12 @@ abstract class DvrDetailsFragment extends DetailsFragment {
checkPinToPlay(recordedProgram, seekTimeMs);
return;
}
- String ratingString = recordedProgram.getContentRating();
- if (TextUtils.isEmpty(ratingString)) {
- launchPlaybackActivity(recordedProgram, seekTimeMs, false);
- return;
- }
- String[] ratingList = ratingString.split(",");
- TvContentRating[] programRatings = new TvContentRating[ratingList.length];
- for (int i = 0; i < ratingList.length; i++) {
- programRatings[i] = TvContentRating.unflattenFromString(ratingList[i]);
- }
- TvContentRating blockRatings = parental.getBlockedRating(programRatings);
+ TvContentRating[] ratings = recordedProgram.getContentRatings();
+ TvContentRating blockRatings = parental.getBlockedRating(ratings);
if (blockRatings != null) {
checkPinToPlay(recordedProgram, seekTimeMs);
} else {
- launchPlaybackActivity(recordedProgram, seekTimeMs, false);
+ DvrUiHelper.startPlaybackActivity(getContext(), programId, seekTimeMs, false);
}
}
@@ -279,26 +247,21 @@ abstract class DvrDetailsFragment extends DetailsFragment {
}
private void checkPinToPlay(RecordedProgram recordedProgram, long seekTimeMs) {
- new PinDialogFragment(PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM,
- new PinDialogFragment.ResultListener() {
- @Override
- public void done(boolean success) {
- if (success) {
- launchPlaybackActivity(recordedProgram, seekTimeMs, true);
- }
+ SoftPreconditions.checkState(getActivity() instanceof DvrDetailsActivity);
+ if (getActivity() instanceof DvrDetailsActivity) {
+ ((DvrDetailsActivity) getActivity()).setOnPinCheckListener(new OnPinCheckedListener() {
+ @Override
+ public void onPinChecked(boolean checked, int type, String rating) {
+ ((DvrDetailsActivity) getActivity()).setOnPinCheckListener(null);
+ if (checked && type == PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM) {
+ DvrUiHelper.startPlaybackActivity(getContext(), recordedProgram.getId(),
+ seekTimeMs, true);
}
- }).show(getActivity().getFragmentManager(), PinDialogFragment.DIALOG_TAG);
- }
-
- private void launchPlaybackActivity(RecordedProgram mRecordedProgram, long seekTimeMs,
- boolean pinChecked) {
- Intent intent = new Intent(getActivity(), DvrPlaybackActivity.class);
- intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, mRecordedProgram.getId());
- if (seekTimeMs != TvInputManager.TIME_SHIFT_INVALID_TIME) {
- intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_SEEK_TIME, seekTimeMs);
+ }
+ });
+ PinDialogFragment.create(PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM)
+ .show(getActivity().getFragmentManager(), PinDialogFragment.DIALOG_TAG);
}
- intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_PIN_CHECKED, pinChecked);
- getActivity().startActivity(intent);
}
private static class MyImageLoaderCallback extends
diff --git a/src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java b/src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java
new file mode 100644
index 00000000..df0e61c1
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java
@@ -0,0 +1,140 @@
+/*
+ * 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.app.Activity;
+import android.content.Context;
+import android.support.annotation.CallSuper;
+import android.support.v17.leanback.widget.Presenter;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+
+import com.android.tv.common.SoftPreconditions;
+import com.android.tv.dvr.ui.DvrUiHelper;
+
+import java.util.HashSet;
+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 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<T> extends Presenter {
+ protected final Context mContext;
+ private final Set<DvrItemViewHolder> mBoundViewHolders = new HashSet<>();
+ private final OnClickListener mOnClickListener = onCreateOnClickListener();
+
+ protected class DvrItemViewHolder extends ViewHolder {
+ DvrItemViewHolder(RecordingCardView view) {
+ super(view);
+ }
+
+ protected RecordingCardView getView() {
+ return (RecordingCardView) view;
+ }
+
+ protected void onBound(T item) { }
+
+ protected void onUnbound() { }
+ }
+
+ DvrItemPresenter(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public final ViewHolder onCreateViewHolder(ViewGroup parent) {
+ return onCreateDvrItemViewHolder();
+ }
+
+ @Override
+ public final void onBindViewHolder(ViewHolder baseHolder, Object item) {
+ DvrItemViewHolder viewHolder;
+ T dvrItem;
+ try {
+ viewHolder = (DvrItemViewHolder) baseHolder;
+ Class<T> itemType = (Class<T>) item.getClass();
+ dvrItem = itemType.cast(item);
+ } catch (ClassCastException e) {
+ SoftPreconditions.checkState(false);
+ return;
+ }
+ viewHolder.view.setTag(item);
+ viewHolder.view.setOnClickListener(mOnClickListener);
+ onBindDvrItemViewHolder(viewHolder, dvrItem);
+ viewHolder.onBound(dvrItem);
+ mBoundViewHolders.add(viewHolder);
+ }
+
+ @Override
+ @CallSuper
+ public void onUnbindViewHolder(ViewHolder baseHolder) {
+ DvrItemViewHolder viewHolder = (DvrItemViewHolder) baseHolder;
+ mBoundViewHolders.remove(viewHolder);
+ viewHolder.onUnbound();
+ viewHolder.view.setTag(null);
+ viewHolder.view.setOnClickListener(null);
+ }
+
+ /**
+ * Unbinds all bound view holders.
+ */
+ public void unbindAllViewHolders() {
+ // When browse fragments are destroyed, RecyclerView would not call presenters'
+ // onUnbindViewHolder(). We should handle it by ourselves to prevent resources leaks.
+ for (ViewHolder viewHolder : new HashSet<>(mBoundViewHolders)) {
+ onUnbindViewHolder(viewHolder);
+ }
+ }
+
+ /**
+ * This method will be called when a {@link DvrItemViewHolder} is needed to be created.
+ */
+ abstract protected DvrItemViewHolder onCreateDvrItemViewHolder();
+
+ /**
+ * This method will be called when a {@link DvrItemViewHolder} is bound to a DVR item.
+ */
+ abstract protected void onBindDvrItemViewHolder(DvrItemViewHolder viewHolder, T item);
+
+ /**
+ * Returns context.
+ */
+ protected Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Creates {@link OnClickListener} for DVR library's card views.
+ */
+ protected OnClickListener onCreateOnClickListener() {
+ return new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (view instanceof RecordingCardView) {
+ RecordingCardView v = (RecordingCardView) view;
+ DvrUiHelper.startDetailsActivity((Activity) v.getContext(),
+ v.getTag(), v.getImageView(), false);
+ }
+ }
+ };
+ }
+} \ No newline at end of file
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..94c67eec 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,28 @@ import java.util.List;
/**
* Presents a {@link ScheduledRecording} in the {@link DvrBrowseFragment}.
*/
-public class FullSchedulesCardPresenter extends Presenter {
+class FullSchedulesCardPresenter extends DvrItemPresenter<Object> {
+ private final Drawable mIconDrawable;
+ private final String mCardTitleText;
+
+ FullSchedulesCardPresenter(Context context) {
+ super(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);
+ public DvrItemViewHolder onCreateDvrItemViewHolder() {
+ return new DvrItemViewHolder(new RecordingCardView(mContext));
}
@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 onBindDvrItemViewHolder(DvrItemViewHolder 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.setTitle(mCardTitleText);
+ cardView.setImage(mIconDrawable);
+ List<ScheduledRecording> scheduledRecordings = TvApplication.getSingletons(mContext)
.getDvrDataManager().getAvailableScheduledRecordings();
int fullDays = 0;
if (!scheduledRecordings.isEmpty()) {
@@ -57,28 +62,23 @@ 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);
}
@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..eb9cb26c 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;
@@ -22,18 +22,16 @@ import android.os.Bundle;
import android.support.v17.leanback.widget.Action;
import android.support.v17.leanback.widget.OnActionClickedListener;
import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
-import android.text.TextUtils;
import com.android.tv.R;
import com.android.tv.TvApplication;
-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 {
@@ -44,7 +42,6 @@ public class RecordedProgramDetailsFragment extends DvrDetailsFragment
private DvrWatchedPositionManager mDvrWatchedPositionManager;
private RecordedProgram mRecordedProgram;
- private DetailsContent mDetailsContent;
private boolean mPaused;
private DvrDataManager mDvrDataManager;
@@ -59,7 +56,8 @@ public class RecordedProgramDetailsFragment extends DvrDetailsFragment
public void onCreateInternal() {
mDvrWatchedPositionManager = TvApplication.getSingletons(getActivity())
.getDvrWatchedPositionManager();
- setDetailsOverviewRow(mDetailsContent);
+ setDetailsOverviewRow(DetailsContent
+ .createFromRecordedProgram(getContext(), mRecordedProgram));
}
@Override
@@ -87,26 +85,7 @@ public class RecordedProgramDetailsFragment extends DvrDetailsFragment
protected boolean onLoadRecordingDetails(Bundle args) {
long recordedProgramId = args.getLong(DvrDetailsActivity.RECORDING_ID);
mRecordedProgram = mDvrDataManager.getRecordedProgram(recordedProgramId);
- if (mRecordedProgram == null) {
- // notify super class to end activity before initializing anything
- return false;
- }
- mDetailsContent = createDetailsContent();
- return true;
- }
-
- private DetailsContent createDetailsContent() {
- Channel channel = TvApplication.getSingletons(getContext()).getChannelDataManager()
- .getChannel(mRecordedProgram.getChannelId());
- String description = TextUtils.isEmpty(mRecordedProgram.getLongDescription())
- ? mRecordedProgram.getDescription() : mRecordedProgram.getLongDescription();
- return new DetailsContent.Builder()
- .setTitle(getTitleFromProgram(mRecordedProgram, channel))
- .setStartTimeUtcMillis(mRecordedProgram.getStartTimeUtcMillis())
- .setEndTimeUtcMillis(mRecordedProgram.getEndTimeUtcMillis())
- .setDescription(description)
- .setImageUris(mRecordedProgram, channel)
- .build();
+ return mRecordedProgram != null;
}
@Override
diff --git a/src/com/android/tv/dvr/ui/browse/RecordedProgramPresenter.java b/src/com/android/tv/dvr/ui/browse/RecordedProgramPresenter.java
new file mode 100644
index 00000000..5fe162b6
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/browse/RecordedProgramPresenter.java
@@ -0,0 +1,142 @@
+/*
+ * 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.media.tv.TvInputManager;
+
+import com.android.tv.R;
+import com.android.tv.TvApplication;
+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;
+
+/**
+ * Presents a {@link RecordedProgram} in the {@link DvrBrowseFragment}.
+ */
+public class RecordedProgramPresenter extends DvrItemPresenter<RecordedProgram> {
+ private final DvrWatchedPositionManager mDvrWatchedPositionManager;
+ private String mTodayString;
+ private String mYesterdayString;
+ private final int mProgressBarColor;
+ private final boolean mShowEpisodeTitle;
+ private final boolean mExpandTitleWhenFocused;
+
+ protected final class RecordedProgramViewHolder extends DvrItemViewHolder
+ implements WatchedPositionChangedListener {
+ private RecordedProgram mProgram;
+ private boolean mShowProgress;
+
+ public RecordedProgramViewHolder(RecordingCardView view, Integer progressColor) {
+ super(view);
+ if (progressColor == null) {
+ mShowProgress = false;
+ } else {
+ mShowProgress = true;
+ view.setProgressBarColor(progressColor);
+ }
+ }
+
+ private void setProgressBar(long watchedPositionMs) {
+ ((RecordingCardView) view).setProgressBar(
+ (watchedPositionMs == TvInputManager.TIME_SHIFT_INVALID_TIME) ? null
+ : Math.min(100, (int) (100.0f * watchedPositionMs
+ / mProgram.getDurationMillis())));
+ }
+
+ @Override
+ public void onWatchedPositionChanged(long programId, long positionMs) {
+ if (programId == mProgram.getId()) {
+ setProgressBar(positionMs);
+ }
+ }
+
+ @Override
+ protected void onBound(RecordedProgram program) {
+ mProgram = program;
+ if (mShowProgress) {
+ mDvrWatchedPositionManager.addListener(this, program.getId());
+ setProgressBar(mDvrWatchedPositionManager.getWatchedPosition(program.getId()));
+ } else {
+ getView().setProgressBar(null);
+ }
+ }
+
+ @Override
+ protected void onUnbound() {
+ if (mShowProgress) {
+ mDvrWatchedPositionManager.removeListener(this, mProgram.getId());
+ }
+ getView().reset();
+ }
+ }
+
+ RecordedProgramPresenter(Context context, boolean showEpisodeTitle,
+ boolean expandTitleWhenFocused) {
+ super(context);
+ mTodayString = mContext.getString(R.string.dvr_date_today);
+ mYesterdayString = mContext.getString(R.string.dvr_date_yesterday);
+ mDvrWatchedPositionManager =
+ 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, false);
+ }
+
+ @Override
+ public DvrItemViewHolder onCreateDvrItemViewHolder() {
+ return new RecordedProgramViewHolder(
+ new RecordingCardView(mContext, mExpandTitleWhenFocused), mProgressBarColor);
+ }
+
+ @Override
+ public void onBindDvrItemViewHolder(DvrItemViewHolder baseHolder, RecordedProgram program) {
+ final RecordedProgramViewHolder viewHolder = (RecordedProgramViewHolder) baseHolder;
+ final RecordingCardView cardView = viewHolder.getView();
+ DetailsContent details = DetailsContent.createFromRecordedProgram(mContext, program);
+ cardView.setTitle(mShowEpisodeTitle ?
+ program.getEpisodeDisplayTitle(mContext) : details.getTitle());
+ cardView.setImageUri(details.getLogoImageUri(), details.isUsingChannelLogo());
+ cardView.setContent(generateMajorContent(program), generateMinorContent(program));
+ cardView.setDetailBackgroundImageUri(details.getBackgroundImageUri());
+ }
+
+ private String generateMajorContent(RecordedProgram program) {
+ int dateDifference = Utils.computeDateDifference(program.getStartTimeUtcMillis(),
+ System.currentTimeMillis());
+ if (dateDifference == 0) {
+ return mTodayString;
+ } else if (dateDifference == 1) {
+ return mYesterdayString;
+ } else {
+ return Utils.getDurationString(mContext, program.getStartTimeUtcMillis(),
+ program.getStartTimeUtcMillis(), false, true, false, 0);
+ }
+ }
+
+ private String generateMinorContent(RecordedProgram program) {
+ int durationMinutes = Math.max(1, Utils.getRoundOffMinsFromMs(program.getDurationMillis()));
+ return mContext.getResources().getQuantityString(
+ R.plurals.dvr_program_duration, durationMinutes, durationMinutes);
+ }
+}
diff --git a/src/com/android/tv/dvr/ui/RecordingCardView.java b/src/com/android/tv/dvr/ui/browse/RecordingCardView.java
index 51c3b03b..767addc8 100644
--- a/src/com/android/tv/dvr/ui/RecordingCardView.java
+++ b/src/com/android/tv/dvr/ui/browse/RecordingCardView.java
@@ -14,51 +14,70 @@
* 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;
+ private String mDetailBackgroundImageUri;
- 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 +94,81 @@ 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);
+ // Preload the background image going to be used in detail fragments here to prevent
+ // loading and drawing background images during activity transitions.
+ if (gainFocus) {
+ if (!TextUtils.isEmpty(mDetailBackgroundImageUri)) {
+ ImageLoader.loadBitmap(getContext(), mDetailBackgroundImageUri,
+ Integer.MAX_VALUE, Integer.MAX_VALUE, null);
+ }
+ }
+ 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) {
@@ -118,6 +205,11 @@ class RecordingCardView extends BaseCardView {
mProgressBar.getProgressDrawable().setTint(color);
}
+ /**
+ * Sets the image URI of the poster should be shown on the card view.
+
+ * @param isChannelLogo {@code true} if the image is from channels' logo.
+ */
void setImageUri(String uri, boolean isChannelLogo) {
if (isChannelLogo) {
mImageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
@@ -134,7 +226,7 @@ class RecordingCardView extends BaseCardView {
}
/**
- * Set image to card view.
+ * Sets the {@link Drawable} of the poster should be shown on the card view.
*/
public void setImage(Drawable image) {
if (image != null) {
@@ -142,6 +234,10 @@ class RecordingCardView extends BaseCardView {
}
}
+ /**
+ * Sets the affiliated icon of the card view, which will be displayed at the lower-right corner
+ * of the poster.
+ */
public void setAffiliatedIcon(int imageResId) {
if (imageResId > 0) {
mAffiliatedIconContainer.setVisibility(View.VISIBLE);
@@ -152,6 +248,14 @@ class RecordingCardView extends BaseCardView {
}
/**
+ * Sets the background image URI of the card view, which will be displayed as background when
+ * the view is clicked and shows its details fragment.
+ */
+ public void setDetailBackgroundImageUri(String uri) {
+ mDetailBackgroundImageUri = uri;
+ }
+
+ /**
* Returns image view.
*/
public ImageView getImageView() {
@@ -178,8 +282,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/browse/RecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java
new file mode 100644
index 00000000..56ec357f
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java
@@ -0,0 +1,51 @@
+/*
+ * 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.os.Bundle;
+import android.support.v17.leanback.app.DetailsFragment;
+
+import com.android.tv.TvApplication;
+import com.android.tv.dvr.data.ScheduledRecording;
+
+/**
+ * {@link DetailsFragment} for recordings in DVR.
+ */
+abstract class RecordingDetailsFragment extends DvrDetailsFragment {
+ private ScheduledRecording mRecording;
+
+ @Override
+ protected void onCreateInternal() {
+ setDetailsOverviewRow(DetailsContent
+ .createFromScheduledRecording(getContext(), mRecording));
+ }
+
+ @Override
+ protected boolean onLoadRecordingDetails(Bundle args) {
+ long scheduledRecordingId = args.getLong(DvrDetailsActivity.RECORDING_ID);
+ mRecording = TvApplication.getSingletons(getContext()).getDvrDataManager()
+ .getScheduledRecording(scheduledRecordingId);
+ return mRecording != null;
+ }
+
+ /**
+ * Returns {@link ScheduledRecording} for the current fragment.
+ */
+ public ScheduledRecording getRecording() {
+ return mRecording;
+ }
+} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/ScheduledRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java
index 60816bb5..958f8bf8 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.
@@ -66,7 +66,7 @@ public class ScheduledRecordingDetailsFragment extends RecordingDetailsFragment
adapter.set(ACTION_VIEW_SCHEDULE, mScheduleAction);
}
adapter.set(ACTION_CANCEL, new Action(ACTION_CANCEL,
- res.getString(R.string.epg_dvr_dialog_message_remove_recording_schedule), null,
+ res.getString(R.string.dvr_detail_cancel_recording), null,
res.getDrawable(R.drawable.ic_dvr_cancel_32dp)));
return adapter;
}
diff --git a/src/com/android/tv/dvr/ui/browse/ScheduledRecordingPresenter.java b/src/com/android/tv/dvr/ui/browse/ScheduledRecordingPresenter.java
new file mode 100644
index 00000000..273d3d19
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/browse/ScheduledRecordingPresenter.java
@@ -0,0 +1,138 @@
+/*
+ * 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.os.Handler;
+
+import com.android.tv.R;
+import com.android.tv.TvApplication;
+import com.android.tv.dvr.DvrManager;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.util.Utils;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Presents a {@link ScheduledRecording} in the {@link DvrBrowseFragment}.
+ */
+class ScheduledRecordingPresenter extends DvrItemPresenter<ScheduledRecording> {
+ private static final long PROGRESS_UPDATE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(5);
+
+ private final DvrManager mDvrManager;
+ private final int mProgressBarColor;
+
+ private final class ScheduledRecordingViewHolder extends DvrItemViewHolder {
+ private final Handler mHandler = new Handler();
+ private ScheduledRecording mScheduledRecording;
+ private final Runnable mProgressBarUpdater = new Runnable() {
+ @Override
+ public void run() {
+ updateProgressBar();
+ mHandler.postDelayed(this, PROGRESS_UPDATE_INTERVAL_MS);
+ }
+ };
+
+ ScheduledRecordingViewHolder(RecordingCardView view, int progressBarColor) {
+ super(view);
+ view.setProgressBarColor(progressBarColor);
+ }
+
+ @Override
+ protected void onBound(ScheduledRecording recording) {
+ mScheduledRecording = recording;
+ updateProgressBar();
+ startUpdateProgressBar();
+ }
+
+ @Override
+ protected void onUnbound() {
+ stopUpdateProgressBar();
+ mScheduledRecording = null;
+ getView().reset();
+ }
+
+ private void updateProgressBar() {
+ if (mScheduledRecording == null) {
+ return;
+ }
+ int recordingState = mScheduledRecording.getState();
+ RecordingCardView cardView = (RecordingCardView) view;
+ if (recordingState == ScheduledRecording.STATE_RECORDING_IN_PROGRESS) {
+ cardView.setProgressBar(Math.max(0, Math.min((int) (100 *
+ (System.currentTimeMillis() - mScheduledRecording.getStartTimeMs())
+ / mScheduledRecording.getDuration()), 100)));
+ } else if (recordingState == ScheduledRecording.STATE_RECORDING_FINISHED) {
+ cardView.setProgressBar(100);
+ } else {
+ // Hides progress bar.
+ cardView.setProgressBar(null);
+ }
+ }
+
+ private void startUpdateProgressBar() {
+ mHandler.post(mProgressBarUpdater);
+ }
+
+ private void stopUpdateProgressBar() {
+ mHandler.removeCallbacks(mProgressBarUpdater);
+ }
+ }
+
+ public ScheduledRecordingPresenter(Context context) {
+ super(context);
+ mDvrManager = TvApplication.getSingletons(mContext).getDvrManager();
+ mProgressBarColor = mContext.getResources()
+ .getColor(R.color.play_controls_recording_icon_color_on_focus);
+ }
+
+ @Override
+ public DvrItemViewHolder onCreateDvrItemViewHolder() {
+ return new ScheduledRecordingViewHolder(new RecordingCardView(mContext), mProgressBarColor);
+ }
+
+ @Override
+ public void onBindDvrItemViewHolder(DvrItemViewHolder baseHolder,
+ ScheduledRecording recording) {
+ final ScheduledRecordingViewHolder viewHolder = (ScheduledRecordingViewHolder) baseHolder;
+ final RecordingCardView cardView = viewHolder.getView();
+ DetailsContent details = DetailsContent.createFromScheduledRecording(mContext, recording);
+ cardView.setTitle(details.getTitle());
+ cardView.setImageUri(details.getLogoImageUri(), details.isUsingChannelLogo());
+ cardView.setAffiliatedIcon(mDvrManager.isConflicting(recording) ?
+ R.drawable.ic_warning_white_32dp : 0);
+ cardView.setContent(generateMajorContent(recording), null);
+ cardView.setDetailBackgroundImageUri(details.getBackgroundImageUri());
+ }
+
+ private String generateMajorContent(ScheduledRecording recording) {
+ int dateDifference = Utils.computeDateDifference(System.currentTimeMillis(),
+ recording.getStartTimeMs());
+ if (dateDifference <= 0) {
+ return mContext.getString(R.string.dvr_date_today_time,
+ Utils.getDurationString(mContext, recording.getStartTimeMs(),
+ recording.getEndTimeMs(), false, false, true, 0));
+ } else if (dateDifference == 1) {
+ return mContext.getString(R.string.dvr_date_tomorrow_time,
+ Utils.getDurationString(mContext, recording.getStartTimeMs(),
+ recording.getEndTimeMs(), false, false, true, 0));
+ } else {
+ return Utils.getDurationString(mContext, recording.getStartTimeMs(),
+ recording.getStartTimeMs(), false, true, false, 0);
+ }
+ }
+} \ 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..c2aa8e98 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;
@@ -37,14 +36,12 @@ import android.text.TextUtils;
import com.android.tv.R;
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;
@@ -67,7 +64,6 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement
// After fragments are created, it should be cleared to save resources.
private List<RecordedProgram> mRecordedPrograms;
private RecordedProgram mRecommendRecordedProgram;
- private DetailsContent mDetailsContent;
private int mSeasonRowCount;
private SparseArrayObjectAdapter mActionsAdapter;
private Action mDeleteAction;
@@ -85,7 +81,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);
}
@@ -93,7 +89,7 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement
protected void onCreateInternal() {
mDvrWatchedPositionManager = TvApplication.getSingletons(getActivity())
.getDvrWatchedPositionManager();
- setDetailsOverviewRow(mDetailsContent);
+ setDetailsOverviewRow(DetailsContent.createFromSeriesRecording(getContext(), mSeries));
setupRecordedProgramsRow();
mDvrDataManager.addSeriesRecordingListener(this);
mDvrDataManager.addRecordedProgramListener(this);
@@ -149,7 +145,6 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement
}
mRecordedPrograms = mDvrDataManager.getRecordedPrograms(mSeries.getId());
Collections.sort(mRecordedPrograms, RecordedProgram.SEASON_REVERSED_EPISODE_COMPARATOR);
- mDetailsContent = createDetailsContent();
return true;
}
@@ -158,22 +153,10 @@ 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;
}
- private DetailsContent createDetailsContent() {
- Channel channel = TvApplication.getSingletons(getContext()).getChannelDataManager()
- .getChannel(mSeries.getChannelId());
- String description = TextUtils.isEmpty(mSeries.getLongDescription())
- ? mSeries.getDescription() : mSeries.getLongDescription();
- return new DetailsContent.Builder()
- .setTitle(mSeries.getTitle())
- .setDescription(description)
- .setImageUris(mSeries.getPosterUri(), mSeries.getPhotoUri(), channel)
- .build();
- }
-
@Override
protected SparseArrayObjectAdapter onCreateActionsAdapter() {
mActionsAdapter = new SparseArrayObjectAdapter(new ActionPresenterSelector());
@@ -203,10 +186,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 +245,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 +351,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..e508259d 100644
--- a/src/com/android/tv/dvr/ui/SeriesRecordingPresenter.java
+++ b/src/com/android/tv/dvr/ui/browse/SeriesRecordingPresenter.java
@@ -14,42 +14,36 @@
* 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.text.TextUtils;
-import android.view.ViewGroup;
import com.android.tv.ApplicationSingletons;
import com.android.tv.R;
import com.android.tv.TvApplication;
-import com.android.tv.data.Channel;
-import com.android.tv.data.ChannelDataManager;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrDataManager.RecordedProgramListener;
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 {
- private final ChannelDataManager mChannelDataManager;
+class SeriesRecordingPresenter extends DvrItemPresenter<SeriesRecording> {
private final DvrDataManager mDvrDataManager;
private final DvrManager mDvrManager;
private final DvrWatchedPositionManager mWatchedPositionManager;
- private static final class SeriesRecordingViewHolder extends ViewHolder implements
+ private final class SeriesRecordingViewHolder extends DvrItemViewHolder implements
WatchedPositionChangedListener, ScheduledRecordingListener, RecordedProgramListener {
private SeriesRecording mSeriesRecording;
private RecordingCardView mCardView;
@@ -138,7 +132,8 @@ public class SeriesRecordingPresenter extends DvrItemPresenter {
// Do nothing
}
- public void onBound(SeriesRecording seriesRecording) {
+ @Override
+ protected void onBound(SeriesRecording seriesRecording) {
mSeriesRecording = seriesRecording;
mDvrDataManager.addScheduledRecordingListener(this);
mDvrDataManager.addRecordedProgramListener(this);
@@ -152,10 +147,12 @@ public class SeriesRecordingPresenter extends DvrItemPresenter {
updateCardViewContent();
}
- public void onUnbound() {
+ @Override
+ protected void onUnbound() {
mDvrDataManager.removeScheduledRecordingListener(this);
mDvrDataManager.removeRecordedProgramListener(this);
mWatchedPositionManager.removeListener(this);
+ getView().reset();
}
private void updateCardViewContent() {
@@ -186,29 +183,28 @@ public class SeriesRecordingPresenter extends DvrItemPresenter {
}
public SeriesRecordingPresenter(Context context) {
+ super(context);
ApplicationSingletons singletons = TvApplication.getSingletons(context);
- mChannelDataManager = singletons.getChannelDataManager();
mDvrDataManager = singletons.getDvrDataManager();
mDvrManager = singletons.getDvrManager();
mWatchedPositionManager = singletons.getDvrWatchedPositionManager();
}
@Override
- public ViewHolder onCreateViewHolder(ViewGroup parent) {
- Context context = parent.getContext();
- RecordingCardView view = new RecordingCardView(context);
- return new SeriesRecordingViewHolder(view, mDvrDataManager, mDvrManager,
- mWatchedPositionManager);
+ public DvrItemViewHolder onCreateDvrItemViewHolder() {
+ return new SeriesRecordingViewHolder(new RecordingCardView(mContext), mDvrDataManager,
+ mDvrManager, mWatchedPositionManager);
}
@Override
- public void onBindViewHolder(ViewHolder baseHolder, Object o) {
+ public void onBindDvrItemViewHolder(DvrItemViewHolder baseHolder, SeriesRecording series) {
final SeriesRecordingViewHolder viewHolder = (SeriesRecordingViewHolder) baseHolder;
- final SeriesRecording seriesRecording = (SeriesRecording) o;
- final RecordingCardView cardView = (RecordingCardView) viewHolder.view;
- viewHolder.onBound(seriesRecording);
- setTitleAndImage(cardView, seriesRecording);
- super.onBindViewHolder(baseHolder, o);
+ final RecordingCardView cardView = viewHolder.getView();
+ viewHolder.onBound(series);
+ DetailsContent details = DetailsContent.createFromSeriesRecording(mContext, series);
+ cardView.setTitle(details.getTitle());
+ cardView.setImageUri(details.getLogoImageUri(), details.isUsingChannelLogo());
+ cardView.setDetailBackgroundImageUri(details.getBackgroundImageUri());
}
@Override
@@ -217,18 +213,4 @@ public class SeriesRecordingPresenter extends DvrItemPresenter {
((SeriesRecordingViewHolder) viewHolder).onUnbound();
super.onUnbindViewHolder(viewHolder);
}
-
- private void setTitleAndImage(RecordingCardView cardView, SeriesRecording recording) {
- cardView.setTitle(recording.getTitle());
- if (recording.getPosterUri() != null) {
- cardView.setImageUri(recording.getPosterUri(), false);
- } else {
- Channel channel = mChannelDataManager.getChannel(recording.getChannelId());
- String imageUri = null;
- if (channel != null) {
- imageUri = TvContract.buildChannelLogoUri(channel.getId()).toString();
- }
- cardView.setImageUri(imageUri, true);
- }
- }
}
diff --git a/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java b/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java
index d28f026c..b9407b15 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.
@@ -40,7 +40,8 @@ public abstract class BaseDvrSchedulesFragment extends DetailsFragment
/**
* The key for scheduled recording which has be selected in the list.
*/
- public static String SCHEDULES_KEY_SCHEDULED_RECORDING = "schedules_key_scheduled_recording";
+ public static final String SCHEDULES_KEY_SCHEDULED_RECORDING =
+ "schedules_key_scheduled_recording";
private ScheduleRowAdapter mRowsAdapter;
private TextView mEmptyInfoScreenView;
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..2af832ec 100644
--- a/src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java
+++ b/src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java
@@ -19,13 +19,14 @@ 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;
+import com.android.tv.dvr.ui.DvrUiHelper;
/**
* A class for the episodic program.
*/
-public class EpisodicProgramRow extends ScheduleRow {
+class EpisodicProgramRow extends ScheduleRow {
private final String mInputId;
private final Program mProgram;
@@ -65,7 +66,7 @@ public class EpisodicProgramRow extends ScheduleRow {
@Override
public String getProgramTitleWithEpisodeNumber(Context context) {
- return mProgram.getTitleWithEpisodeNumber(context);
+ return DvrUiHelper.getStyledTitleWithEpisodeNumber(context, mProgram, 0).toString();
}
@Override
diff --git a/src/com/android/tv/dvr/ui/list/ScheduleRow.java b/src/com/android/tv/dvr/ui/list/ScheduleRow.java
index 3fc92e8a..91ba393a 100644
--- a/src/com/android/tv/dvr/ui/list/ScheduleRow.java
+++ b/src/com/android/tv/dvr/ui/list/ScheduleRow.java
@@ -20,12 +20,13 @@ 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;
+import com.android.tv.dvr.ui.DvrUiHelper;
/**
* A class for schedule recording row.
*/
-public class ScheduleRow {
+class ScheduleRow {
private final SchedulesHeaderRow mHeaderRow;
@Nullable private ScheduledRecording mSchedule;
private boolean mStopRecordingRequested;
@@ -166,7 +167,8 @@ public class ScheduleRow {
* Returns the program title with episode number.
*/
public String getProgramTitleWithEpisodeNumber(Context context) {
- return mSchedule != null ? mSchedule.getProgramTitleWithEpisodeNumber(context) : null;
+ return mSchedule != null ? DvrUiHelper.getStyledTitleWithEpisodeNumber(context,
+ mSchedule, 0).toString() : null;
}
/**
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..6824cfe2
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java
@@ -0,0 +1,94 @@
+/*
+ * 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.ContentUris;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.tv.R;
+import com.android.tv.TvApplication;
+import com.android.tv.dialog.PinDialogFragment.OnPinCheckedListener;
+import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.util.Utils;
+
+/**
+ * Activity to play a {@link RecordedProgram}.
+ */
+public class DvrPlaybackActivity extends Activity implements OnPinCheckedListener {
+ private static final String TAG = "DvrPlaybackActivity";
+ private static final boolean DEBUG = false;
+
+ private DvrPlaybackOverlayFragment mOverlayFragment;
+ private OnPinCheckedListener mOnPinCheckedListener;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ TvApplication.setCurrentRunningProcess(this, true);
+ if (DEBUG) Log.d(TAG, "onCreate");
+ super.onCreate(savedInstanceState);
+ setIntent(createProgramIntent(getIntent()));
+ 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) {
+ setIntent(createProgramIntent(intent));
+ mOverlayFragment.onNewIntent(createProgramIntent(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));
+ }
+
+ private Intent createProgramIntent(Intent intent) {
+ if (Intent.ACTION_VIEW.equals(intent.getAction())) {
+ Uri uri = intent.getData();
+ long recordedProgramId = ContentUris.parseId(uri);
+ intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, recordedProgramId);
+ }
+ return intent;
+ }
+
+ @Override
+ public void onPinChecked(boolean checked, int type, String rating) {
+ if (mOnPinCheckedListener != null) {
+ mOnPinCheckedListener.onPinChecked(checked, type, rating);
+ }
+ }
+
+ void setOnPinCheckListener(OnPinCheckedListener listener) {
+ mOnPinCheckedListener = listener;
+ }
+} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackCardPresenter.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackCardPresenter.java
new file mode 100644
index 00000000..8ef0041d
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackCardPresenter.java
@@ -0,0 +1,45 @@
+/*
+ * 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.content.Context;
+
+import com.android.tv.R;
+import com.android.tv.dvr.ui.browse.RecordedProgramPresenter;
+import com.android.tv.dvr.ui.browse.RecordingCardView;
+
+/**
+ * This class is used to generate Views and bind Objects for related recordings in DVR playback.
+ */
+class DvrPlaybackCardPresenter extends RecordedProgramPresenter {
+ private final int mRelatedRecordingCardWidth;
+ private final int mRelatedRecordingCardHeight;
+
+ DvrPlaybackCardPresenter(Context context) {
+ super(context);
+ mRelatedRecordingCardWidth =
+ context.getResources().getDimensionPixelSize(R.dimen.dvr_related_recordings_width);
+ mRelatedRecordingCardHeight =
+ context.getResources().getDimensionPixelSize(R.dimen.dvr_related_recordings_height);
+ }
+
+ @Override
+ public DvrItemViewHolder onCreateDvrItemViewHolder() {
+ return new RecordedProgramViewHolder(new RecordingCardView(
+ getContext(), mRelatedRecordingCardWidth, mRelatedRecordingCardHeight, true), null);
+ }
+}
diff --git a/src/com/android/tv/dvr/ui/DvrPlaybackControlHelper.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackControlHelper.java
index 0bc4ecb1..1a6ae187 100644
--- a/src/com/android/tv/dvr/ui/DvrPlaybackControlHelper.java
+++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackControlHelper.java
@@ -14,66 +14,80 @@
* 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.support.v17.leanback.app.PlaybackControlGlue;
+import android.media.tv.TvTrackInfo;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v17.leanback.media.PlaybackControlGlue;
import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter;
import android.support.v17.leanback.widget.Action;
-import android.support.v17.leanback.widget.OnActionClickedListener;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
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;
import android.util.Log;
import android.view.KeyEvent;
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 {
- private static final String TAG = "DvrPlaybackControlHelper";
+class DvrPlaybackControlHelper extends PlaybackControlGlue {
+ private static final String TAG = "DvrPlaybackControlHelpr";
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;
private int mPlaybackSpeedId;
private boolean mReadyToControl;
+ private final DvrPlaybackOverlayFragment mFragment;
private final MediaController mMediaController;
private final MediaController.Callback mMediaControllerCallback = new MediaControllerCallback();
private final TransportControls mTransportControls;
private final int mExtraPaddingTopForNoDescription;
+ private final MultiAction mClosedCaptioningAction;
+ private final MultiAction mMultiAudioAction;
+ private ArrayObjectAdapter mSecondaryActionsAdapter;
- public DvrPlaybackControlHelper(Activity activity, DvrPlaybackOverlayFragment overlayFragment) {
- super(activity, overlayFragment, new int[TimeShiftUtils.MAX_SPEED_LEVEL + 1]);
+ DvrPlaybackControlHelper(Activity activity, DvrPlaybackOverlayFragment overlayFragment) {
+ super(activity, new int[TimeShiftUtils.MAX_SPEED_LEVEL + 1]);
+ mFragment = overlayFragment;
mMediaController = activity.getMediaController();
mMediaController.registerCallback(mMediaControllerCallback);
mTransportControls = mMediaController.getTransportControls();
mExtraPaddingTopForNoDescription = activity.getResources()
.getDimensionPixelOffset(R.dimen.dvr_playback_controls_extra_padding_top);
+ mClosedCaptioningAction = new ClosedCaptioningAction(activity);
+ mMultiAudioAction = new MultiAudioAction(activity);
+ createControlsRowPresenter();
}
- @Override
- public PlaybackControlsRowPresenter createControlsRowAndPresenter() {
+ void createControlsRow() {
PlaybackControlsRow controlsRow = new PlaybackControlsRow(this);
setControlsRow(controlsRow);
+ mSecondaryActionsAdapter = (ArrayObjectAdapter) controlsRow.getSecondaryActionsAdapter();
+ }
+
+ private void createControlsRowPresenter() {
AbstractDetailsDescriptionPresenter detailsPresenter =
new AbstractDetailsDescriptionPresenter() {
@Override
@@ -112,30 +126,31 @@ public class DvrPlaybackControlHelper extends PlaybackControlGlue {
.getColor(R.color.play_controls_progress_bar_watched));
presenter.setBackgroundColor(getContext().getResources()
.getColor(R.color.play_controls_body_background_enabled));
- presenter.setOnActionClickedListener(new OnActionClickedListener() {
- @Override
- public void onActionClicked(Action action) {
- if (mReadyToControl) {
- DvrPlaybackControlHelper.super.onActionClicked(action);
- }
- }
- });
- return presenter;
+ setControlsRowPresenter(presenter);
}
@Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
+ public void onActionClicked(Action action) {
if (mReadyToControl) {
- if (keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE && event.getAction() == KeyEvent.ACTION_DOWN
- && (mPlaybackState == PlaybackState.STATE_FAST_FORWARDING
- || mPlaybackState == PlaybackState.STATE_REWINDING)) {
- // Workaround of b/31489271. Clicks play/pause button first to reset play controls
- // to "play" state. Then we can pass MEDIA_PAUSE to let playback be paused.
- onActionClicked(getControlsRow().getActionForKeyCode(keyCode));
+ int trackType;
+ if (action.getId() == mClosedCaptioningAction.getId()) {
+ trackType = TvTrackInfo.TYPE_SUBTITLE;
+ } else if (action.getId() == AUDIO_ACTION_ID) {
+ trackType = TvTrackInfo.TYPE_AUDIO;
+ } else {
+ super.onActionClicked(action);
+ return;
+ }
+ ArrayList<TvTrackInfo> trackInfos = mFragment.getTracks(trackType);
+ if (!trackInfos.isEmpty()) {
+ showSideFragment(trackInfos, mFragment.getSelectedTrackId(trackType));
}
- return super.onKey(v, keyCode, event);
}
- return false;
+ }
+
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ return mReadyToControl && super.onKey(v, keyCode, event);
}
@Override
@@ -158,10 +173,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
@@ -213,12 +228,45 @@ public class DvrPlaybackControlHelper extends PlaybackControlGlue {
/**
* Unregister media controller's callback.
*/
- public void unregisterCallback() {
+ void unregisterCallback() {
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.
+ */
+ 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);
+ }
+ getHost().notifyPlaybackRowChanged();
+ }
+
+ @Nullable
+ Boolean hasSecondaryRow() {
+ if (mSecondaryActionsAdapter == null) {
+ return null;
+ }
+ return mSecondaryActionsAdapter.size() != 0;
+ }
+
@Override
- protected void startPlayback(int speedId) {
+ public void play(int speedId) {
if (getCurrentSpeedId() == speedId) {
return;
}
@@ -232,23 +280,16 @@ public class DvrPlaybackControlHelper extends PlaybackControlGlue {
}
@Override
- protected void pausePlayback() {
+ public void pause() {
mTransportControls.pause();
}
- @Override
- protected void skipToNext() {
- // Do nothing.
- }
-
- @Override
- protected void skipToPrevious() {
- // Do nothing.
- }
-
- @Override
- protected void onRowChanged(PlaybackControlsRow row) {
- // 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) {
@@ -297,6 +338,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);
+ mFragment.getFragmentManager().beginTransaction()
+ .hide(mFragment)
+ .replace(R.id.dvr_playback_side_fragment, sideFragment)
+ .addToBackStack(null)
+ .commit();
+ }
+
private class MediaControllerCallback extends MediaController.Callback {
@Override
public void onPlaybackStateChanged(PlaybackState state) {
@@ -307,7 +361,13 @@ public class DvrPlaybackControlHelper extends PlaybackControlGlue {
@Override
public void onMetadataChanged(MediaMetadata metadata) {
DvrPlaybackControlHelper.this.onMetadataChanged();
- ((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/playback/DvrPlaybackOverlayFragment.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java
new file mode 100644
index 00000000..783ae682
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java
@@ -0,0 +1,494 @@
+/*
+ * 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.Fragment;
+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;
+import android.media.tv.TvView;
+import android.support.v17.leanback.app.PlaybackFragment;
+import android.support.v17.leanback.app.PlaybackFragmentGlueHost;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
+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.Presenter;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.SinglePresenterSelector;
+import android.view.Display;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Toast;
+import android.util.Log;
+
+import com.android.tv.R;
+import com.android.tv.TvApplication;
+import com.android.tv.data.BaseProgram;
+import com.android.tv.dialog.PinDialogFragment;
+import com.android.tv.dvr.DvrDataManager;
+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.dvr.ui.browse.RecordingCardView;
+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 PlaybackFragment {
+ // TODO: Handles audio focus. Deals with block and ratings.
+ private static final String TAG = "DvrPlaybackOverlayFrag";
+ private static final boolean DEBUG = false;
+
+ private static final String MEDIA_SESSION_TAG = "com.android.tv.dvr.mediasession";
+ private static final float DISPLAY_ASPECT_RATIO_EPSILON = 0.01f;
+
+ // 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;
+ private SortedArrayAdapter<BaseProgram> mRelatedRecordingsRowAdapter;
+ private DvrPlaybackCardPresenter mRelatedRecordingCardPresenter;
+ private DvrDataManager mDvrDataManager;
+ private ContentRatingsManager mContentRatingsManager;
+ private TvView mTvView;
+ private View mBlockScreenView;
+ private ListRow mRelatedRecordingsRow;
+ private int mVerticalPaddingBase;
+ private int mPaddingWithoutRelatedRow;
+ private int mPaddingWithoutSecondaryRow;
+ private int mWindowWidth;
+ private int mWindowHeight;
+ private float mAppliedAspectRatio;
+ private float mWindowAspectRatio;
+ private boolean mPinChecked;
+ private boolean mStarted;
+ 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);
+ mVerticalPaddingBase = getActivity().getResources()
+ .getDimensionPixelOffset(R.dimen.dvr_playback_overlay_padding_top_base);
+ 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();
+ if (!mDvrDataManager.isRecordedProgramLoadFinished()) {
+ mDvrDataManager.addRecordedProgramLoadFinishedListener(
+ new DvrDataManager.OnRecordedProgramLoadFinishedListener() {
+ @Override
+ public void onRecordedProgramLoadFinished() {
+ mDvrDataManager.removeRecordedProgramLoadFinishedListener(this);
+ if (handleIntent(getActivity().getIntent(), true)) {
+ setUpRows();
+ preparePlayback(getActivity().getIntent());
+ }
+ }
+ }
+ );
+ } else if (!handleIntent(getActivity().getIntent(), true)) {
+ return;
+ }
+ Point size = new Point();
+ ((DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE))
+ .getDisplay(Display.DEFAULT_DISPLAY).getSize(size);
+ mWindowWidth = size.x;
+ mWindowHeight = size.y;
+ mWindowAspectRatio = mAppliedAspectRatio = (float) mWindowWidth / mWindowHeight;
+ setBackgroundType(PlaybackFragment.BG_LIGHT);
+ setFadingEnabled(true);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ mStarted = true;
+ updateVerticalPosition();
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ 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, mDvrPlayer, this);
+ mPlaybackControlHelper = new DvrPlaybackControlHelper(getActivity(), this);
+ mRelatedRecordingsRow = getRelatedRecordingsRow();
+ 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);
+ }
+ updateVerticalPosition();
+ mPlaybackControlHelper.getHost().notifyPlaybackRowChanged();
+ }
+ });
+ mDvrPlayer.setOnAspectRatioChangedListener(new DvrPlayer.OnAspectRatioChangedListener() {
+ @Override
+ public void onAspectRatioChanged(float videoAspectRatio) {
+ updateAspectRatio(videoAspectRatio);
+ }
+ });
+ mPinChecked = getActivity().getIntent()
+ .getBooleanExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_PIN_CHECKED, false);
+ mDvrPlayer.setOnContentBlockedListener(
+ new DvrPlayer.OnContentBlockedListener() {
+ @Override
+ public void onContentBlocked(TvContentRating contentRating) {
+ if (mPinChecked) {
+ mTvView.unblockContent(contentRating);
+ return;
+ }
+ mBlockScreenView.setVisibility(View.VISIBLE);
+ getActivity().getMediaController().getTransportControls().pause();
+ ((DvrPlaybackActivity) getActivity())
+ .setOnPinCheckListener(
+ new PinDialogFragment.OnPinCheckedListener() {
+ @Override
+ public void onPinChecked(
+ boolean checked, int type, String rating) {
+ ((DvrPlaybackActivity) getActivity())
+ .setOnPinCheckListener(null);
+ if (checked) {
+ mPinChecked = true;
+ mTvView.unblockContent(contentRating);
+ mBlockScreenView.setVisibility(View.GONE);
+ getActivity()
+ .getMediaController()
+ .getTransportControls()
+ .play();
+ }
+ }
+ });
+ PinDialogFragment.create(
+ PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_DVR,
+ contentRating.flattenToString())
+ .show(
+ getActivity().getFragmentManager(),
+ PinDialogFragment.DIALOG_TAG);
+ }
+ });
+ setOnItemViewClickedListener(new BaseOnItemViewClickedListener() {
+ @Override
+ public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+ RowPresenter.ViewHolder rowViewHolder, Object row) {
+ if (itemViewHolder.view instanceof RecordingCardView) {
+ setFadingEnabled(false);
+ long programId = ((RecordedProgram) itemViewHolder.view.getTag()).getId();
+ if (DEBUG) Log.d(TAG, "Play Related Recording:" + programId);
+ Intent intent = new Intent(getContext(), DvrPlaybackActivity.class);
+ intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, programId);
+ getContext().startActivity(intent);
+ }
+ }
+ });
+ if (mProgram != null) {
+ setUpRows();
+ preparePlayback(getActivity().getIntent());
+ }
+ }
+
+ @Override
+ public void onPause() {
+ if (DEBUG) Log.d(TAG, "onPause");
+ super.onPause();
+ if (mMediaSessionHelper.getPlaybackState() == PlaybackState.STATE_FAST_FORWARDING
+ || mMediaSessionHelper.getPlaybackState() == PlaybackState.STATE_REWINDING) {
+ getActivity().getMediaController().getTransportControls().pause();
+ }
+ if (mMediaSessionHelper.getPlaybackState() == PlaybackState.STATE_NONE) {
+ getActivity().requestVisibleBehind(false);
+ } else {
+ getActivity().requestVisibleBehind(true);
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ if (DEBUG) Log.d(TAG, "onDestroy");
+ mPlaybackControlHelper.unregisterCallback();
+ mMediaSessionHelper.release();
+ mRelatedRecordingCardPresenter.unbindAllViewHolders();
+ super.onDestroy();
+ }
+
+ /**
+ * Passes the intent to the fragment.
+ */
+ public void onNewIntent(Intent intent) {
+ if (mDvrDataManager.isRecordedProgramLoadFinished() && handleIntent(intent, false)) {
+ preparePlayback(intent);
+ }
+ }
+
+ /**
+ * Should be called when windows' size is changed in order to notify DVR player
+ * to update it's view width/height and position.
+ */
+ public void onWindowSizeChanged(final int windowWidth, final int windowHeight) {
+ mWindowWidth = windowWidth;
+ mWindowHeight = windowHeight;
+ mWindowAspectRatio = (float) mWindowWidth / mWindowHeight;
+ 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()) {
+ return null;
+ } else {
+ return (RecordedProgram) mRelatedRecordingsRowAdapter.get(position);
+ }
+ }
+
+ /**
+ * 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);
+ }
+ }
+
+ private boolean handleIntent(Intent intent, boolean finishActivity) {
+ mProgram = getProgramFromIntent(intent);
+ if (mProgram == null) {
+ Toast.makeText(getActivity(), getString(R.string.dvr_program_not_found),
+ Toast.LENGTH_SHORT).show();
+ if (finishActivity) {
+ getActivity().finish();
+ }
+ return false;
+ }
+ return true;
+ }
+
+ 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 (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 {
+ int newPadding = (mWindowHeight - Math.round(mWindowWidth / videoAspectRatio)) / 2;
+ ((ViewGroup) mTvView.getParent()).setPadding(0, newPadding, 0, newPadding);
+ }
+ mAppliedAspectRatio = videoAspectRatio;
+ }
+
+ private void preparePlayback(Intent intent) {
+ mMediaSessionHelper.setupPlayback(mProgram, getSeekTimeFromIntent(intent));
+ mPlaybackControlHelper.updateSecondaryRow(false, false);
+ getActivity().getMediaController().getTransportControls().prepare();
+ updateRelatedRecordingsRow();
+ }
+
+ private void updateRelatedRecordingsRow() {
+ boolean wasEmpty = (mRelatedRecordingsRowAdapter.size() == 0);
+ mRelatedRecordingsRowAdapter.clear();
+ long programId = mProgram.getId();
+ String seriesId = mProgram.getSeriesId();
+ SeriesRecording seriesRecording = mDvrDataManager.getSeriesRecording(seriesId);
+ if (seriesRecording != null) {
+ if (DEBUG) Log.d(TAG, "Update related recordings with:" + seriesId);
+ List<RecordedProgram> relatedPrograms =
+ mDvrDataManager.getRecordedPrograms(seriesRecording.getId());
+ for (RecordedProgram program : relatedPrograms) {
+ if (programId != program.getId()) {
+ mRelatedRecordingsRowAdapter.add(program);
+ }
+ }
+ }
+ if (mRelatedRecordingsRowAdapter.size() == 0) {
+ mRowsAdapter.remove(mRelatedRecordingsRow);
+ } else if (wasEmpty){
+ mRowsAdapter.add(mRelatedRecordingsRow);
+ }
+ updateVerticalPosition();
+ mRowsAdapter.notifyArrayItemRangeChanged(1, 1);
+ }
+
+ private void setUpRows() {
+ mPlaybackControlHelper.createControlsRow();
+ mPlaybackControlHelper.setHost(new PlaybackFragmentGlueHost(this));
+ mRowsAdapter = (ArrayObjectAdapter) getAdapter();
+ ClassPresenterSelector selector =
+ (ClassPresenterSelector) mRowsAdapter.getPresenterSelector();
+ selector.addClassPresenter(ListRow.class, new DvrListRowPresenter(getContext()));
+ mRowsAdapter.setPresenterSelector(selector);
+ if (mStarted) {
+ // If it's started before setting up rows, vertical position has not been updated and
+ // should be updated here.
+ updateVerticalPosition();
+ }
+ }
+
+ private ListRow getRelatedRecordingsRow() {
+ mRelatedRecordingCardPresenter = new DvrPlaybackCardPresenter(getActivity());
+ mRelatedRecordingsRowAdapter = new RelatedRecordingsAdapter(mRelatedRecordingCardPresenter);
+ HeaderItem header = new HeaderItem(0,
+ getActivity().getString(R.string.dvr_playback_related_recordings));
+ return new ListRow(header, mRelatedRecordingsRowAdapter);
+ }
+
+ private RecordedProgram getProgramFromIntent(Intent intent) {
+ long programId = intent.getLongExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, -1);
+ return mDvrDataManager.getRecordedProgram(programId);
+ }
+
+ private long getSeekTimeFromIntent(Intent intent) {
+ return intent.getLongExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_SEEK_TIME,
+ TvInputManager.TIME_SHIFT_INVALID_TIME);
+ }
+
+ private void updateVerticalPosition() {
+ Boolean hasSecondaryRow = mPlaybackControlHelper.hasSecondaryRow();
+ if (hasSecondaryRow == null) {
+ return;
+ }
+
+ int verticalPadding = mVerticalPaddingBase;
+ if (mRelatedRecordingsRowAdapter.size() == 0) {
+ verticalPadding += mPaddingWithoutRelatedRow;
+ }
+ if (!hasSecondaryRow) {
+ verticalPadding += mPaddingWithoutSecondaryRow;
+ }
+ Fragment fragment = getChildFragmentManager().findFragmentById(R.id.playback_controls_dock);
+ View view = fragment == null ? null : fragment.getView();
+ if (view != null) {
+ view.setTranslationY(verticalPadding);
+ }
+ }
+
+ private class RelatedRecordingsAdapter extends SortedArrayAdapter<BaseProgram> {
+ RelatedRecordingsAdapter(DvrPlaybackCardPresenter presenter) {
+ super(new SinglePresenterSelector(presenter), BaseProgram.EPISODE_COMPARATOR);
+ }
+
+ @Override
+ public long getId(BaseProgram item) {
+ return item.getId();
+ }
+ }
+} \ No newline at end of file
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..7226c666
--- /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.session.PlaybackState;
+import android.media.tv.TvContentRating;
+import android.media.tv.TvInputManager;
+import android.media.tv.TvTrackInfo;
+import android.media.tv.TvView;
+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