aboutsummaryrefslogtreecommitdiff
path: root/src/com/android/tv/dvr
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/tv/dvr')
-rw-r--r--src/com/android/tv/dvr/BaseDvrDataManager.java41
-rw-r--r--src/com/android/tv/dvr/DvrDataManager.java12
-rw-r--r--src/com/android/tv/dvr/DvrDataManagerImpl.java143
-rw-r--r--src/com/android/tv/dvr/DvrManager.java81
-rw-r--r--src/com/android/tv/dvr/DvrScheduleManager.java139
-rw-r--r--src/com/android/tv/dvr/DvrStorageStatusManager.java35
-rw-r--r--src/com/android/tv/dvr/DvrWatchedPositionManager.java1
-rw-r--r--src/com/android/tv/dvr/WritableDvrDataManager.java6
-rw-r--r--src/com/android/tv/dvr/data/IdGenerator.java (renamed from src/com/android/tv/dvr/IdGenerator.java)2
-rw-r--r--src/com/android/tv/dvr/data/RecordedProgram.java (renamed from src/com/android/tv/dvr/RecordedProgram.java)2
-rw-r--r--src/com/android/tv/dvr/data/ScheduledRecording.java (renamed from src/com/android/tv/dvr/ScheduledRecording.java)17
-rw-r--r--src/com/android/tv/dvr/data/SeasonEpisodeNumber.java72
-rw-r--r--src/com/android/tv/dvr/data/SeriesInfo.java (renamed from src/com/android/tv/dvr/SeriesInfo.java)2
-rw-r--r--src/com/android/tv/dvr/data/SeriesRecording.java (renamed from src/com/android/tv/dvr/SeriesRecording.java)3
-rw-r--r--src/com/android/tv/dvr/provider/AsyncDvrDbTask.java4
-rw-r--r--src/com/android/tv/dvr/provider/DvrDatabaseHelper.java4
-rw-r--r--src/com/android/tv/dvr/provider/DvrDbSync.java (renamed from src/com/android/tv/dvr/DvrDbSync.java)34
-rw-r--r--src/com/android/tv/dvr/provider/EpisodicProgramLoadTask.java (renamed from src/com/android/tv/dvr/EpisodicProgramLoadTask.java)73
-rw-r--r--src/com/android/tv/dvr/recorder/ConflictChecker.java (renamed from src/com/android/tv/dvr/ConflictChecker.java)5
-rw-r--r--src/com/android/tv/dvr/recorder/DvrRecordingService.java (renamed from src/com/android/tv/dvr/DvrRecordingService.java)36
-rw-r--r--src/com/android/tv/dvr/recorder/DvrStartRecordingReceiver.java (renamed from src/com/android/tv/dvr/DvrStartRecordingReceiver.java)2
-rw-r--r--src/com/android/tv/dvr/recorder/InputTaskScheduler.java (renamed from src/com/android/tv/dvr/InputTaskScheduler.java)6
-rw-r--r--src/com/android/tv/dvr/recorder/RecordingTask.java (renamed from src/com/android/tv/dvr/RecordingTask.java)23
-rw-r--r--src/com/android/tv/dvr/recorder/ScheduledProgramReaper.java (renamed from src/com/android/tv/dvr/ScheduledProgramReaper.java)5
-rw-r--r--src/com/android/tv/dvr/recorder/Scheduler.java (renamed from src/com/android/tv/dvr/Scheduler.java)6
-rw-r--r--src/com/android/tv/dvr/recorder/SeriesRecordingScheduler.java (renamed from src/com/android/tv/dvr/SeriesRecordingScheduler.java)81
-rw-r--r--src/com/android/tv/dvr/ui/BigArguments.java54
-rw-r--r--src/com/android/tv/dvr/ui/ChangeImageTransformWithScaledParent.java79
-rw-r--r--src/com/android/tv/dvr/ui/CurrentRecordingDetailsFragment.java59
-rw-r--r--src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java4
-rw-r--r--src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java5
-rw-r--r--src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java2
-rw-r--r--src/com/android/tv/dvr/ui/DvrConflictFragment.java7
-rw-r--r--src/com/android/tv/dvr/ui/DvrForgetStorageErrorFragment.java87
-rw-r--r--src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java50
-rw-r--r--src/com/android/tv/dvr/ui/DvrHalfSizedDialogFragment.java12
-rw-r--r--src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java84
-rw-r--r--src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java50
-rw-r--r--src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java (renamed from src/com/android/tv/dvr/ui/PrioritySettingsFragment.java)7
-rw-r--r--src/com/android/tv/dvr/ui/DvrScheduleFragment.java17
-rw-r--r--src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java5
-rw-r--r--src/com/android/tv/dvr/ui/DvrSeriesDeletionFragment.java (renamed from src/com/android/tv/dvr/ui/SeriesDeletionFragment.java)11
-rw-r--r--src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java45
-rw-r--r--src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java20
-rw-r--r--src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java (renamed from src/com/android/tv/dvr/ui/SeriesSettingsFragment.java)209
-rw-r--r--src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java11
-rw-r--r--src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java4
-rw-r--r--src/com/android/tv/dvr/ui/DvrUiHelper.java (renamed from src/com/android/tv/dvr/DvrUiHelper.java)311
-rw-r--r--src/com/android/tv/dvr/ui/FadeBackground.java70
-rw-r--r--src/com/android/tv/dvr/ui/HalfSizedDialogFragment.java117
-rw-r--r--src/com/android/tv/dvr/ui/SortedArrayAdapter.java90
-rw-r--r--src/com/android/tv/dvr/ui/browse/ActionPresenterSelector.java (renamed from src/com/android/tv/dvr/ui/ActionPresenterSelector.java)10
-rw-r--r--src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java120
-rw-r--r--src/com/android/tv/dvr/ui/browse/DetailsContent.java (renamed from src/com/android/tv/dvr/ui/DetailsContent.java)4
-rw-r--r--src/com/android/tv/dvr/ui/browse/DetailsContentPresenter.java (renamed from src/com/android/tv/dvr/ui/DetailsContentPresenter.java)37
-rw-r--r--src/com/android/tv/dvr/ui/browse/DetailsViewBackgroundHelper.java (renamed from src/com/android/tv/dvr/ui/DetailsViewBackgroundHelper.java)4
-rw-r--r--src/com/android/tv/dvr/ui/browse/DvrBrowseActivity.java (renamed from src/com/android/tv/dvr/ui/DvrActivity.java)6
-rw-r--r--src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java (renamed from src/com/android/tv/dvr/ui/DvrBrowseFragment.java)159
-rw-r--r--src/com/android/tv/dvr/ui/browse/DvrDetailsActivity.java (renamed from src/com/android/tv/dvr/ui/DvrDetailsActivity.java)2
-rw-r--r--src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java (renamed from src/com/android/tv/dvr/ui/DvrDetailsFragment.java)6
-rw-r--r--src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java (renamed from src/com/android/tv/dvr/ui/DvrItemPresenter.java)13
-rw-r--r--src/com/android/tv/dvr/ui/browse/DvrListRowPresenter.java34
-rw-r--r--src/com/android/tv/dvr/ui/browse/FullScheduleCardHolder.java (renamed from src/com/android/tv/dvr/ui/FullScheduleCardHolder.java)2
-rw-r--r--src/com/android/tv/dvr/ui/browse/FullSchedulesCardPresenter.java (renamed from src/com/android/tv/dvr/ui/FullSchedulesCardPresenter.java)66
-rw-r--r--src/com/android/tv/dvr/ui/browse/RecordedProgramDetailsFragment.java (renamed from src/com/android/tv/dvr/ui/RecordedProgramDetailsFragment.java)6
-rw-r--r--src/com/android/tv/dvr/ui/browse/RecordedProgramPresenter.java (renamed from src/com/android/tv/dvr/ui/RecordedProgramPresenter.java)31
-rw-r--r--src/com/android/tv/dvr/ui/browse/RecordingCardView.java (renamed from src/com/android/tv/dvr/ui/RecordingCardView.java)113
-rw-r--r--src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java (renamed from src/com/android/tv/dvr/ui/RecordingDetailsFragment.java)4
-rw-r--r--src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java (renamed from src/com/android/tv/dvr/ui/ScheduledRecordingDetailsFragment.java)4
-rw-r--r--src/com/android/tv/dvr/ui/browse/ScheduledRecordingPresenter.java (renamed from src/com/android/tv/dvr/ui/ScheduledRecordingPresenter.java)21
-rw-r--r--src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java (renamed from src/com/android/tv/dvr/ui/SeriesRecordingDetailsFragment.java)24
-rw-r--r--src/com/android/tv/dvr/ui/browse/SeriesRecordingPresenter.java (renamed from src/com/android/tv/dvr/ui/SeriesRecordingPresenter.java)11
-rw-r--r--src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java2
-rw-r--r--src/com/android/tv/dvr/ui/list/DvrSchedulesActivity.java (renamed from src/com/android/tv/dvr/ui/DvrSchedulesActivity.java)76
-rw-r--r--src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java5
-rw-r--r--src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java72
-rw-r--r--src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java6
-rw-r--r--src/com/android/tv/dvr/ui/list/ScheduleRow.java4
-rw-r--r--src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java4
-rw-r--r--src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java50
-rw-r--r--src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java20
-rw-r--r--src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java26
-rw-r--r--src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java24
-rw-r--r--src/com/android/tv/dvr/ui/list/SeriesScheduleRowPresenter.java17
-rw-r--r--src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java (renamed from src/com/android/tv/dvr/DvrPlaybackActivity.java)4
-rw-r--r--src/com/android/tv/dvr/ui/playback/DvrPlaybackCardPresenter.java (renamed from src/com/android/tv/dvr/ui/DvrPlaybackCardPresenter.java)31
-rw-r--r--src/com/android/tv/dvr/ui/playback/DvrPlaybackControlHelper.java (renamed from src/com/android/tv/dvr/ui/DvrPlaybackControlHelper.java)106
-rw-r--r--src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java (renamed from src/com/android/tv/dvr/DvrPlaybackMediaSessionHelper.java)104
-rw-r--r--src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java (renamed from src/com/android/tv/dvr/ui/DvrPlaybackOverlayFragment.java)181
-rw-r--r--src/com/android/tv/dvr/ui/playback/DvrPlaybackSideFragment.java154
-rw-r--r--src/com/android/tv/dvr/ui/playback/DvrPlayer.java (renamed from src/com/android/tv/dvr/DvrPlayer.java)220
91 files changed, 2680 insertions, 1348 deletions
diff --git a/src/com/android/tv/dvr/BaseDvrDataManager.java b/src/com/android/tv/dvr/BaseDvrDataManager.java
index 89661df3..a8637449 100644
--- a/src/com/android/tv/dvr/BaseDvrDataManager.java
+++ b/src/com/android/tv/dvr/BaseDvrDataManager.java
@@ -26,7 +26,10 @@ import android.util.Log;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.feature.CommonFeatures;
-import com.android.tv.dvr.ScheduledRecording.RecordingState;
+import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.data.ScheduledRecording.RecordingState;
+import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.util.Clock;
import java.util.ArrayList;
@@ -318,5 +321,41 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager {
}
@Override
+ public void checkAndRemoveEmptySeriesRecording(long... seriesRecordingIds) {
+ List<SeriesRecording> toRemove = new ArrayList<>();
+ for (long rId : seriesRecordingIds) {
+ SeriesRecording seriesRecording = getSeriesRecording(rId);
+ if (seriesRecording != null && isEmptySeriesRecording(seriesRecording)) {
+ toRemove.add(seriesRecording);
+ }
+ }
+ removeSeriesRecording(SeriesRecording.toArray(toRemove));
+ }
+
+ /**
+ * Returns {@code true}, if the series recording is empty and can be removed. If a series
+ * recording is in NORMAL state or has recordings or schedules, it is not empty and cannot be
+ * removed.
+ */
+ protected final boolean isEmptySeriesRecording(@NonNull SeriesRecording seriesRecording) {
+ if (!seriesRecording.isStopped()) {
+ return false;
+ }
+ long seriesRecordingId = seriesRecording.getId();
+ for (ScheduledRecording r : getAvailableScheduledRecordings()) {
+ if (r.getSeriesRecordingId() == seriesRecordingId) {
+ return false;
+ }
+ }
+ String seriesId = seriesRecording.getSeriesId();
+ for (RecordedProgram r : getRecordedPrograms()) {
+ if (seriesId.equals(r.getSeriesId())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
public void forgetStorage(String inputId) { }
}
diff --git a/src/com/android/tv/dvr/DvrDataManager.java b/src/com/android/tv/dvr/DvrDataManager.java
index 06613667..6d400b82 100644
--- a/src/com/android/tv/dvr/DvrDataManager.java
+++ b/src/com/android/tv/dvr/DvrDataManager.java
@@ -21,7 +21,10 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Range;
-import com.android.tv.dvr.ScheduledRecording.RecordingState;
+import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.data.ScheduledRecording.RecordingState;
+import com.android.tv.dvr.data.SeriesRecording;
import java.util.Collection;
import java.util.List;
@@ -211,6 +214,13 @@ public interface DvrDataManager {
Collection<Long> getDisallowedProgramIds();
/**
+ * Checks each of the give series recordings to see if it's empty, i.e., it doesn't contains
+ * any available schedules or recorded programs, and it's status is
+ * {@link SeriesRecording#STATE_SERIES_STOPPED}; and removes those empty series recordings.
+ */
+ void checkAndRemoveEmptySeriesRecording(long... seriesRecordingIds);
+
+ /**
* Listens for the DVR schedules loading finished.
*/
interface OnDvrScheduleLoadFinishedListener {
diff --git a/src/com/android/tv/dvr/DvrDataManagerImpl.java b/src/com/android/tv/dvr/DvrDataManagerImpl.java
index 46682a48..6d0a9959 100644
--- a/src/com/android/tv/dvr/DvrDataManagerImpl.java
+++ b/src/com/android/tv/dvr/DvrDataManagerImpl.java
@@ -42,7 +42,11 @@ import android.util.Range;
import com.android.tv.TvApplication;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.dvr.DvrStorageStatusManager.OnStorageMountChangedListener;
-import com.android.tv.dvr.ScheduledRecording.RecordingState;
+import com.android.tv.dvr.data.IdGenerator;
+import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.data.ScheduledRecording.RecordingState;
+import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.dvr.provider.AsyncDvrDbTask.AsyncAddScheduleTask;
import com.android.tv.dvr.provider.AsyncDvrDbTask.AsyncAddSeriesRecordingTask;
import com.android.tv.dvr.provider.AsyncDvrDbTask.AsyncDeleteScheduleTask;
@@ -51,6 +55,8 @@ import com.android.tv.dvr.provider.AsyncDvrDbTask.AsyncDvrQueryScheduleTask;
import com.android.tv.dvr.provider.AsyncDvrDbTask.AsyncDvrQuerySeriesRecordingTask;
import com.android.tv.dvr.provider.AsyncDvrDbTask.AsyncUpdateScheduleTask;
import com.android.tv.dvr.provider.AsyncDvrDbTask.AsyncUpdateSeriesRecordingTask;
+import com.android.tv.dvr.provider.DvrDbSync;
+import com.android.tv.dvr.recorder.SeriesRecordingScheduler;
import com.android.tv.util.AsyncDbTask;
import com.android.tv.util.AsyncDbTask.AsyncRecordedProgramQueryTask;
import com.android.tv.util.Clock;
@@ -267,11 +273,14 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
removeScheduledRecording(ScheduledRecording.toArray(toDelete));
}
IdGenerator.SCHEDULED_RECORDING.setMaxId(maxId);
+ if (mRecordedProgramLoadFinished) {
+ validateSeriesRecordings();
+ }
mDvrLoadFinished = true;
notifyDvrScheduleLoadFinished();
- mDbSync = new DvrDbSync(mContext, DvrDataManagerImpl.this);
- mDbSync.start();
if (isInitialized()) {
+ mDbSync = new DvrDbSync(mContext, DvrDataManagerImpl.this);
+ mDbSync.start();
SeriesRecordingScheduler.getInstance(mContext).start();
}
}
@@ -306,6 +315,9 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
if (uri == null) {
uri = RecordedPrograms.CONTENT_URI;
}
+ if (recordedPrograms == null) {
+ recordedPrograms = Collections.emptyList();
+ }
int match = TvProviderUriMatcher.match(uri);
if (match == TvProviderUriMatcher.MATCH_RECORDED_PROGRAM) {
if (!mRecordedProgramLoadFinished) {
@@ -318,7 +330,11 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
}
mRecordedProgramLoadFinished = true;
notifyRecordedProgramLoadFinished();
- } else if (recordedPrograms == null || recordedPrograms.isEmpty()) {
+ if (isInitialized()) {
+ mDbSync = new DvrDbSync(mContext, DvrDataManagerImpl.this);
+ mDbSync.start();
+ }
+ } else if (recordedPrograms.isEmpty()) {
List<RecordedProgram> oldRecordedPrograms =
new ArrayList<>(mRecordedPrograms.values());
mRecordedPrograms.clear();
@@ -355,6 +371,7 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
}
}
if (isInitialized()) {
+ validateSeriesRecordings();
SeriesRecordingScheduler.getInstance(mContext).start();
}
} else if (match == TvProviderUriMatcher.MATCH_RECORDED_PROGRAM_ID) {
@@ -363,11 +380,15 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
}
long id = ContentUris.parseId(uri);
if (DEBUG) Log.d(TAG, "changed recorded program #" + id + " to " + recordedPrograms);
- if (recordedPrograms == null || recordedPrograms.isEmpty()) {
+ if (recordedPrograms.isEmpty()) {
mRecordedProgramsForRemovedInput.remove(id);
RecordedProgram old = mRecordedPrograms.remove(id);
if (old != null) {
notifyRecordedProgramsRemoved(old);
+ SeriesRecording r = mSeriesId2SeriesRecordings.get(old.getSeriesId());
+ if (r != null && isEmptySeriesRecording(r)) {
+ removeSeriesRecording(r);
+ }
}
} else {
RecordedProgram recordedProgram = recordedPrograms.get(0);
@@ -592,10 +613,16 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
public void removeScheduledRecording(boolean forceRemove, ScheduledRecording... schedules) {
List<ScheduledRecording> schedulesToDelete = new ArrayList<>();
List<ScheduledRecording> schedulesNotToDelete = new ArrayList<>();
+ Set<Long> seriesRecordingIdsToCheck = new HashSet<>();
for (ScheduledRecording r : schedules) {
mScheduledRecordings.remove(r.getId());
- getDeletedScheduleMap().remove(r.getId());
+ getDeletedScheduleMap().remove(r.getProgramId());
mProgramId2ScheduledRecordings.remove(r.getProgramId());
+ if (r.getSeriesRecordingId() != SeriesRecording.ID_NOT_SET
+ && (r.getState() == ScheduledRecording.STATE_RECORDING_NOT_STARTED
+ || r.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS)) {
+ seriesRecordingIdsToCheck.add(r.getSeriesRecordingId());
+ }
boolean isScheduleForRemovedInput =
mScheduledRecordingsForRemovedInput.remove(r.getProgramId()) != null;
// If it belongs to the series recording and it's not started yet, just mark delete
@@ -614,8 +641,19 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
}
}
if (mDvrLoadFinished) {
+ if (mRecordedProgramLoadFinished) {
+ checkAndRemoveEmptySeriesRecording(seriesRecordingIdsToCheck);
+ }
notifyScheduledRecordingRemoved(schedules);
}
+ Iterator<ScheduledRecording> iterator = schedulesNotToDelete.iterator();
+ while (iterator.hasNext()) {
+ ScheduledRecording r = iterator.next();
+ if (!mSeriesRecordings.containsKey(r.getSeriesRecordingId())) {
+ iterator.remove();
+ schedulesToDelete.add(r);
+ }
+ }
if (!schedulesToDelete.isEmpty()) {
new AsyncDeleteScheduleTask(mContext).executeOnDbThread(
ScheduledRecording.toArray(schedulesToDelete));
@@ -669,6 +707,7 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
private void updateScheduledRecording(boolean updateDb, final ScheduledRecording... schedules) {
List<ScheduledRecording> toUpdate = new ArrayList<>();
+ Set<Long> seriesRecordingIdsToCheck = new HashSet<>();
for (ScheduledRecording r : schedules) {
if (!SoftPreconditions.checkState(mScheduledRecordings.containsKey(r.getId()), TAG,
"Recording not found for: " + r)) {
@@ -691,6 +730,13 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
if (programId != ScheduledRecording.ID_NOT_SET) {
mProgramId2ScheduledRecordings.put(programId, r);
}
+ if (r.getState() == ScheduledRecording.STATE_RECORDING_FAILED
+ && r.getSeriesRecordingId() != SeriesRecording.ID_NOT_SET) {
+ // If the scheduled recording is failed, it may cause the automatically generated
+ // series recording for this schedule becomes invalid (with no future schedules and
+ // past recordings.) We should check and remove these series recordings.
+ seriesRecordingIdsToCheck.add(r.getSeriesRecordingId());
+ }
}
if (toUpdate.isEmpty()) {
return;
@@ -702,12 +748,17 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
if (updateDb) {
new AsyncUpdateScheduleTask(mContext).executeOnDbThread(scheduleArray);
}
+ checkAndRemoveEmptySeriesRecording(seriesRecordingIdsToCheck);
removeDeletedSchedules(schedules);
}
@Override
public void updateSeriesRecording(final SeriesRecording... seriesRecordings) {
for (SeriesRecording r : seriesRecordings) {
+ if (!SoftPreconditions.checkArgument(mSeriesRecordings.containsKey(r.getId()), TAG,
+ "Non Existing Series ID: " + r)) {
+ continue;
+ }
SeriesRecording old1 = mSeriesRecordings.put(r.getId(), r);
SeriesRecording old2 = mSeriesId2SeriesRecordings.put(r.getSeriesId(), r);
SoftPreconditions.checkArgument(old1.equals(old2), TAG, "Series ID cannot be"
@@ -769,14 +820,6 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
return r.getInputId().equals(inputId);
}
});
- List<SeriesRecording> movedSeriesRecordings =
- moveElements(mSeriesRecordingsForRemovedInput, mSeriesRecordings,
- new Filter<SeriesRecording>() {
- @Override
- public boolean filter(SeriesRecording r) {
- return r.getInputId().equals(inputId);
- }
- });
List<RecordedProgram> movedRecordedPrograms =
moveElements(mRecordedProgramsForRemovedInput, mRecordedPrograms,
new Filter<RecordedProgram>() {
@@ -785,6 +828,21 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
return r.getInputId().equals(inputId);
}
});
+ List<SeriesRecording> removedSeriesRecordings = new ArrayList<>();
+ List<SeriesRecording> movedSeriesRecordings =
+ moveElements(mSeriesRecordingsForRemovedInput, mSeriesRecordings,
+ new Filter<SeriesRecording>() {
+ @Override
+ public boolean filter(SeriesRecording r) {
+ if (r.getInputId().equals(inputId)) {
+ if (!isEmptySeriesRecording(r)) {
+ return true;
+ }
+ removedSeriesRecordings.add(r);
+ }
+ return false;
+ }
+ });
if (!movedSchedules.isEmpty()) {
for (ScheduledRecording schedule : movedSchedules) {
mProgramId2ScheduledRecordings.put(schedule.getProgramId(), schedule);
@@ -795,6 +853,11 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
mSeriesId2SeriesRecordings.put(seriesRecording.getSeriesId(), seriesRecording);
}
}
+ for (SeriesRecording r : removedSeriesRecordings) {
+ mSeriesRecordingsForRemovedInput.remove(r.getId());
+ }
+ new AsyncDeleteSeriesRecordingTask(mContext).executeOnDbThread(
+ SeriesRecording.toArray(removedSeriesRecordings));
// Notify after all the data are moved.
if (!movedSchedules.isEmpty()) {
notifyScheduledRecordingAdded(ScheduledRecording.toArray(movedSchedules));
@@ -811,20 +874,20 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
if (DEBUG) Log.d(TAG, "hideInput " + inputId);
List<ScheduledRecording> movedSchedules =
moveElements(mScheduledRecordings, mScheduledRecordingsForRemovedInput,
- new Filter<ScheduledRecording>() {
- @Override
- public boolean filter(ScheduledRecording r) {
- return r.getInputId().equals(inputId);
- }
- });
+ new Filter<ScheduledRecording>() {
+ @Override
+ public boolean filter(ScheduledRecording r) {
+ return r.getInputId().equals(inputId);
+ }
+ });
List<SeriesRecording> movedSeriesRecordings =
moveElements(mSeriesRecordings, mSeriesRecordingsForRemovedInput,
- new Filter<SeriesRecording>() {
- @Override
- public boolean filter(SeriesRecording r) {
- return r.getInputId().equals(inputId);
- }
- });
+ new Filter<SeriesRecording>() {
+ @Override
+ public boolean filter(SeriesRecording r) {
+ return r.getInputId().equals(inputId);
+ }
+ });
List<RecordedProgram> movedRecordedPrograms =
moveElements(mRecordedPrograms, mRecordedProgramsForRemovedInput,
new Filter<RecordedProgram>() {
@@ -855,6 +918,15 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
}
}
+ private void checkAndRemoveEmptySeriesRecording(Set<Long> seriesRecordingIds) {
+ int i = 0;
+ long[] rIds = new long[seriesRecordingIds.size()];
+ for (long rId : seriesRecordingIds) {
+ rIds[i++] = rId;
+ }
+ checkAndRemoveEmptySeriesRecording(rIds);
+ }
+
@Override
public void forgetStorage(String inputId) {
List<ScheduledRecording> schedulesToDelete = new ArrayList<>();
@@ -901,6 +973,25 @@ public class DvrDataManagerImpl extends BaseDvrDataManager {
}.executeOnDbThread();
}
+ private void validateSeriesRecordings() {
+ Iterator<SeriesRecording> iter = mSeriesRecordings.values().iterator();
+ List<SeriesRecording> removedSeriesRecordings = new ArrayList<>();
+ while (iter.hasNext()) {
+ SeriesRecording r = iter.next();
+ if (isEmptySeriesRecording(r)) {
+ iter.remove();
+ removedSeriesRecordings.add(r);
+ }
+ }
+ if (!removedSeriesRecordings.isEmpty()) {
+ SeriesRecording[] removed = SeriesRecording.toArray(removedSeriesRecordings);
+ new AsyncDeleteSeriesRecordingTask(mContext).executeOnDbThread(removed);
+ if (mDvrLoadFinished) {
+ notifySeriesRecordingRemoved(removed);
+ }
+ }
+ }
+
private final class RecordedProgramsQueryTask extends AsyncRecordedProgramQueryTask {
private final Uri mUri;
diff --git a/src/com/android/tv/dvr/DvrManager.java b/src/com/android/tv/dvr/DvrManager.java
index 5fa6f90f..2effe9cf 100644
--- a/src/com/android/tv/dvr/DvrManager.java
+++ b/src/com/android/tv/dvr/DvrManager.java
@@ -46,7 +46,9 @@ import com.android.tv.data.Program;
import com.android.tv.dvr.DvrDataManager.OnRecordedProgramLoadFinishedListener;
import com.android.tv.dvr.DvrDataManager.RecordedProgramListener;
import com.android.tv.dvr.DvrScheduleManager.OnInitializeListener;
-import com.android.tv.dvr.SeriesRecording.SeriesState;
+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.util.AsyncDbTask;
import com.android.tv.util.Utils;
@@ -234,7 +236,7 @@ public class DvrManager {
* Adds a new series recording and schedules for the programs with the initial state.
*/
public SeriesRecording addSeriesRecording(Program selectedProgram,
- List<Program> programsToSchedule, @SeriesState int initialState) {
+ List<Program> programsToSchedule, @SeriesRecording.SeriesState int initialState) {
Log.i(TAG, "Adding series recording for program " + selectedProgram + ", and schedules: "
+ programsToSchedule);
if (!SoftPreconditions.checkState(mDataManager.isInitialized())) {
@@ -308,8 +310,7 @@ public class DvrManager {
ScheduledRecording scheduleWithSameProgram =
mDataManager.getScheduledRecordingForProgramId(program.getId());
if (scheduleWithSameProgram != null) {
- if (scheduleWithSameProgram.getState()
- == ScheduledRecording.STATE_RECORDING_NOT_STARTED) {
+ if (scheduleWithSameProgram.isNotStarted()) {
ScheduledRecording r = ScheduledRecording.buildFrom(scheduleWithSameProgram)
.setSeriesRecordingId(series.getId())
.build();
@@ -337,10 +338,10 @@ public class DvrManager {
*/
public void updateSeriesRecording(SeriesRecording series) {
if (SoftPreconditions.checkState(mDataManager.isDvrScheduleLoadFinished())) {
- SeriesRecordingScheduler scheduler = SeriesRecordingScheduler.getInstance(mAppContext);
- scheduler.pauseUpdate();
SeriesRecording previousSeries = mDataManager.getSeriesRecording(series.getId());
if (previousSeries != null) {
+ // If the channel option of series changed, remove the existing schedules. The new
+ // schedules will be added by SeriesRecordingScheduler or by SeriesSettingsFragment.
if (previousSeries.getChannelOption() != series.getChannelOption()
|| (previousSeries.getChannelOption() == SeriesRecording.OPTION_CHANNEL_ONE
&& previousSeries.getChannelId() != series.getChannelId())) {
@@ -350,6 +351,18 @@ public class DvrManager {
for (ScheduledRecording schedule : schedules) {
if (schedule.isNotStarted()) {
schedulesToRemove.add(schedule);
+ } else if (schedule.isInProgress()
+ && series.getChannelOption() == SeriesRecording.OPTION_CHANNEL_ONE
+ && schedule.getChannelId() != series.getChannelId()) {
+ stopRecording(schedule);
+ }
+ }
+ List<ScheduledRecording> deletedSchedules =
+ new ArrayList<>(mDataManager.getDeletedSchedules());
+ for (ScheduledRecording deletedSchedule : deletedSchedules) {
+ if (deletedSchedule.getSeriesRecordingId() == series.getId()
+ && deletedSchedule.getEndTimeMs() > System.currentTimeMillis()) {
+ schedulesToRemove.add(deletedSchedule);
}
}
mDataManager.removeScheduledRecording(true,
@@ -363,7 +376,7 @@ public class DvrManager {
List<ScheduledRecording> schedulesToUpdate = new ArrayList<>();
for (ScheduledRecording schedule
: mDataManager.getScheduledRecordings(series.getId())) {
- if (schedule.isNotStarted()) {
+ if (schedule.isNotStarted() || schedule.isInProgress()) {
schedulesToUpdate.add(ScheduledRecording.buildFrom(schedule)
.setPriority(priority).build());
}
@@ -373,7 +386,6 @@ public class DvrManager {
ScheduledRecording.toArray(schedulesToUpdate));
}
}
- scheduler.resumeUpdate();
}
}
@@ -400,33 +412,6 @@ public class DvrManager {
}
/**
- * Returns true, if the series recording can be removed. If a series recording is NORMAL state
- * or has recordings or schedules, it cannot be removed.
- */
- public boolean canRemoveSeriesRecording(long seriesRecordingId) {
- SeriesRecording seriesRecording = mDataManager.getSeriesRecording(seriesRecordingId);
- if (seriesRecording == null) {
- return false;
- }
- if (!seriesRecording.isStopped()) {
- return false;
- }
- for (ScheduledRecording r : mDataManager.getAvailableScheduledRecordings()) {
- if (r.getSeriesRecordingId() == seriesRecordingId) {
- return false;
- }
- }
- String seriesId = seriesRecording.getSeriesId();
- SoftPreconditions.checkNotNull(seriesId);
- for (RecordedProgram r : mDataManager.getRecordedPrograms()) {
- if (seriesId.equals(r.getSeriesId())) {
- return false;
- }
- }
- return true;
- }
-
- /**
* Stops the currently recorded program
*/
public void stopRecording(final ScheduledRecording recording) {
@@ -657,6 +642,9 @@ public class DvrManager {
if (!mDataManager.isDvrScheduleLoadFinished() || channel == null) {
return false;
}
+ if (channel.isRecordingProhibited()) {
+ return false;
+ }
TvInputInfo info = Utils.getTvInputInfoForChannelId(mAppContext, channel.getId());
if (info == null) {
Log.w(TAG, "Could not find TvInputInfo for " + channel);
@@ -680,7 +668,12 @@ public class DvrManager {
if (!mDataManager.isInitialized()) {
return false;
}
- TvInputInfo info = Utils.getTvInputInfoForProgram(mAppContext, program);
+ Channel channel = TvApplication.getSingletons(mAppContext).getChannelDataManager()
+ .getChannel(program.getChannelId());
+ if (channel == null || channel.isRecordingProhibited()) {
+ return false;
+ }
+ TvInputInfo info = Utils.getTvInputInfoForChannelId(mAppContext, channel.getId());
if (info == null) {
Log.w(TAG, "Could not find TvInputInfo for " + program);
return false;
@@ -733,6 +726,17 @@ public class DvrManager {
return mDataManager.getSeriesRecording(program.getSeriesId());
}
+ /**
+ * Returns if there are valid items. Valid item contains {@link RecordedProgram},
+ * available {@link ScheduledRecording} and {@link SeriesRecording}.
+ */
+ public boolean hasValidItems() {
+ return !(mDataManager.getRecordedPrograms().isEmpty()
+ && mDataManager.getStartedRecordings().isEmpty()
+ && mDataManager.getNonStartedScheduledRecordings().isEmpty()
+ && mDataManager.getSeriesRecordings().isEmpty());
+ }
+
@WorkerThread
@VisibleForTesting
// Should be public to use mock DvrManager object.
@@ -840,9 +844,10 @@ public class DvrManager {
}
/**
- * Listener internally used inside dvr package.
+ * Listener to stop recording request. Should only be internally used inside dvr and its
+ * sub-package.
*/
- interface Listener {
+ public interface Listener {
void onStopRecordingRequested(ScheduledRecording scheduledRecording);
}
}
diff --git a/src/com/android/tv/dvr/DvrScheduleManager.java b/src/com/android/tv/dvr/DvrScheduleManager.java
index a5851a75..b72117aa 100644
--- a/src/com/android/tv/dvr/DvrScheduleManager.java
+++ b/src/com/android/tv/dvr/DvrScheduleManager.java
@@ -24,7 +24,6 @@ import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.util.ArraySet;
-import android.util.LongSparseArray;
import android.util.Range;
import com.android.tv.ApplicationSingletons;
@@ -35,7 +34,10 @@ import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.Program;
import com.android.tv.dvr.DvrDataManager.OnDvrScheduleLoadFinishedListener;
import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
+import com.android.tv.dvr.recorder.InputTaskScheduler;
import com.android.tv.util.CompositeComparator;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.util.Utils;
import java.util.ArrayList;
@@ -88,9 +90,8 @@ public class DvrScheduleManager {
private final Map<String, List<ScheduledRecording>> mInputScheduleMap = new HashMap<>();
// The inner map is a hash map from scheduled recording to its conflicting status, i.e.,
// the boolean value true denotes the schedule is just partially conflicting, which means
- // although there's conflictit, it might still be recorded partially.
- private final Map<String, Map<ScheduledRecording, Boolean>> mInputConflictInfoMap =
- new HashMap<>();
+ // although there's conflict, it might still be recorded partially.
+ private final Map<String, Map<Long, ConflictInfo>> mInputConflictInfoMap = new HashMap<>();
private boolean mInitialized;
@@ -171,10 +172,9 @@ public class DvrScheduleManager {
mInputScheduleMap.remove(inputId);
}
}
- Map<ScheduledRecording, Boolean> conflictInfo =
- mInputConflictInfoMap.get(inputId);
+ Map<Long, ConflictInfo> conflictInfo = mInputConflictInfoMap.get(inputId);
if (conflictInfo != null) {
- conflictInfo.remove(schedule);
+ conflictInfo.remove(schedule.getId());
if (conflictInfo.isEmpty()) {
mInputConflictInfoMap.remove(inputId);
}
@@ -221,21 +221,11 @@ public class DvrScheduleManager {
mInputScheduleMap.remove(inputId);
}
// Update conflict list as well
- Map<ScheduledRecording, Boolean> conflictInfo =
- mInputConflictInfoMap.get(inputId);
+ Map<Long, ConflictInfo> conflictInfo = mInputConflictInfoMap.get(inputId);
if (conflictInfo != null) {
- // Compare ID because ScheduledRecording.equals() doesn't work if the state
- // is changed.
- ScheduledRecording oldSchedule = null;
- for (ScheduledRecording s : conflictInfo.keySet()) {
- if (s.getId() == schedule.getId()) {
- oldSchedule = s;
- break;
- }
- }
- if (oldSchedule != null) {
- conflictInfo.put(schedule, conflictInfo.get(oldSchedule));
- conflictInfo.remove(oldSchedule);
+ ConflictInfo oldConflictInfo = conflictInfo.get(schedule.getId());
+ if (oldConflictInfo != null) {
+ oldConflictInfo.schedule = schedule;
}
}
}
@@ -317,24 +307,25 @@ public class DvrScheduleManager {
List<ScheduledRecording> addedConflicts = new ArrayList<>();
List<ScheduledRecording> removedConflicts = new ArrayList<>();
for (String inputId : mInputScheduleMap.keySet()) {
- Map<ScheduledRecording, Boolean> oldConflictsInfo = mInputConflictInfoMap.get(inputId);
+ Map<Long, ConflictInfo> oldConflictInfo = mInputConflictInfoMap.get(inputId);
Map<Long, ScheduledRecording> oldConflictMap = new HashMap<>();
- if (oldConflictsInfo != null) {
- for (ScheduledRecording r : oldConflictsInfo.keySet()) {
- oldConflictMap.put(r.getId(), r);
+ if (oldConflictInfo != null) {
+ for (ConflictInfo conflictInfo : oldConflictInfo.values()) {
+ oldConflictMap.put(conflictInfo.schedule.getId(), conflictInfo.schedule);
}
}
- Map<ScheduledRecording, Boolean> conflictInfo = getConflictingSchedulesInfo(inputId);
- if (conflictInfo.isEmpty()) {
+ List<ConflictInfo> conflicts = getConflictingSchedulesInfo(inputId);
+ if (conflicts.isEmpty()) {
mInputConflictInfoMap.remove(inputId);
} else {
- mInputConflictInfoMap.put(inputId, conflictInfo);
- List<ScheduledRecording> conflicts = new ArrayList<>(conflictInfo.keySet());
- for (ScheduledRecording r : conflicts) {
- if (oldConflictMap.remove(r.getId()) == null) {
- addedConflicts.add(r);
+ Map<Long, ConflictInfo> conflictInfos = new HashMap<>();
+ for (ConflictInfo conflictInfo : conflicts) {
+ conflictInfos.put(conflictInfo.schedule.getId(), conflictInfo);
+ if (oldConflictMap.remove(conflictInfo.schedule.getId()) == null) {
+ addedConflicts.add(conflictInfo.schedule);
}
}
+ mInputConflictInfoMap.put(inputId, conflictInfos);
}
removedConflicts.addAll(oldConflictMap.values());
}
@@ -565,8 +556,7 @@ public class DvrScheduleManager {
}
/**
- * Returns list of all conflicting scheduled recordings with schedules belonging to {@code
- * seriesRecording}
+ * Returns list of all conflicting scheduled recordings for the given {@code seriesRecording}
* recording.
* <p>
* Any empty list means there is no conflicts.
@@ -581,9 +571,18 @@ public class DvrScheduleManager {
if (input == null || !input.canRecord() || input.getTunerCount() <= 0) {
return Collections.emptyList();
}
- List<ScheduledRecording> schedulesForSeries = mDataManager.getScheduledRecordings(
+ List<ScheduledRecording> scheduledRecordingForSeries = mDataManager.getScheduledRecordings(
seriesRecording.getId());
- return getConflictingSchedules(input, schedulesForSeries);
+ List<ScheduledRecording> availableScheduledRecordingForSeries = new ArrayList<>();
+ for (ScheduledRecording scheduledRecording : scheduledRecordingForSeries) {
+ if (scheduledRecording.isNotStarted() || scheduledRecording.isInProgress()) {
+ availableScheduledRecordingForSeries.add(scheduledRecording);
+ }
+ }
+ if (availableScheduledRecordingForSeries.isEmpty()) {
+ return Collections.emptyList();
+ }
+ return getConflictingSchedules(input, availableScheduledRecordingForSeries);
}
/**
@@ -617,16 +616,16 @@ public class DvrScheduleManager {
* the given input.
*/
@NonNull
- private Map<ScheduledRecording, Boolean> getConflictingSchedulesInfo(String inputId) {
+ private List<ConflictInfo> getConflictingSchedulesInfo(String inputId) {
SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet");
TvInputInfo input = Utils.getTvInputInfoForInputId(mContext, inputId);
SoftPreconditions.checkState(input != null, TAG, "Can't find input for : " + inputId);
if (!mInitialized || input == null) {
- return Collections.emptyMap();
+ return Collections.emptyList();
}
List<ScheduledRecording> schedules = mInputScheduleMap.get(input.getId());
if (schedules == null || schedules.isEmpty()) {
- return Collections.emptyMap();
+ return Collections.emptyList();
}
return getConflictingSchedulesInfo(schedules, input.getTunerCount());
}
@@ -645,8 +644,8 @@ public class DvrScheduleManager {
if (!mInitialized || input == null) {
return false;
}
- Map<ScheduledRecording, Boolean> conflicts = mInputConflictInfoMap.get(input.getId());
- return conflicts != null && conflicts.containsKey(schedule);
+ Map<Long, ConflictInfo> conflicts = mInputConflictInfoMap.get(input.getId());
+ return conflicts != null && conflicts.containsKey(schedule.getId());
}
/**
@@ -664,8 +663,12 @@ public class DvrScheduleManager {
if (!mInitialized || input == null) {
return false;
}
- Map<ScheduledRecording, Boolean> conflicts = mInputConflictInfoMap.get(input.getId());
- return conflicts != null && conflicts.getOrDefault(schedule, false);
+ Map<Long, ConflictInfo> conflicts = mInputConflictInfoMap.get(input.getId());
+ if (conflicts != null) {
+ ConflictInfo conflictInfo = conflicts.get(schedule.getId());
+ return conflictInfo != null && conflictInfo.partialConflict;
+ }
+ return false;
}
/**
@@ -813,15 +816,17 @@ public class DvrScheduleManager {
@VisibleForTesting
static List<ScheduledRecording> getConflictingSchedules(
List<ScheduledRecording> schedules, int tunerCount, List<Range<Long>> periods) {
- List<ScheduledRecording> result = new ArrayList<>(
- getConflictingSchedulesInfo(schedules, tunerCount, periods).keySet());
- Collections.sort(result, RESULT_COMPARATOR);
+ List<ScheduledRecording> result = new ArrayList<>();
+ for (ConflictInfo conflictInfo :
+ getConflictingSchedulesInfo(schedules, tunerCount, periods)) {
+ result.add(conflictInfo.schedule);
+ }
return result;
}
@VisibleForTesting
- static Map<ScheduledRecording, Boolean> getConflictingSchedulesInfo(
- List<ScheduledRecording> schedules, int tunerCount) {
+ static List<ConflictInfo> getConflictingSchedulesInfo(List<ScheduledRecording> schedules,
+ int tunerCount) {
return getConflictingSchedulesInfo(schedules, tunerCount, null);
}
@@ -836,13 +841,13 @@ public class DvrScheduleManager {
* to be partially recorded under the given schedules and tuner count {@code true},
* or not {@code false}.
*/
- private static Map<ScheduledRecording, Boolean> getConflictingSchedulesInfo(
+ private static List<ConflictInfo> getConflictingSchedulesInfo(
List<ScheduledRecording> schedules, int tunerCount, List<Range<Long>> periods) {
List<ScheduledRecording> schedulesToCheck = new ArrayList<>(schedules);
// Sort by the same order as that in InputTaskScheduler.
Collections.sort(schedulesToCheck, InputTaskScheduler.getRecordingOrderComparator());
List<ScheduledRecording> recordings = new ArrayList<>();
- Map<ScheduledRecording, Boolean> conflicts = new HashMap<>();
+ Map<ScheduledRecording, ConflictInfo> conflicts = new HashMap<>();
Map<ScheduledRecording, ScheduledRecording> modified2OriginalSchedules = new HashMap<>();
// Simulate InputTaskScheduler.
while (!schedulesToCheck.isEmpty()) {
@@ -853,26 +858,29 @@ public class DvrScheduleManager {
if (modified2OriginalSchedules.containsKey(schedule)) {
// Schedule has been modified, which means it's already conflicted.
// Modify its state to partially conflicted.
- conflicts.put(modified2OriginalSchedules.get(schedule), true);
+ ScheduledRecording originalSchedule = modified2OriginalSchedules.get(schedule);
+ conflicts.put(originalSchedule, new ConflictInfo(originalSchedule, true));
}
} else {
ScheduledRecording candidate = findReplaceableRecording(recordings, schedule);
if (candidate != null) {
if (!modified2OriginalSchedules.containsKey(candidate)) {
- conflicts.put(candidate, true);
+ conflicts.put(candidate, new ConflictInfo(candidate, true));
}
recordings.remove(candidate);
recordings.add(schedule);
if (modified2OriginalSchedules.containsKey(schedule)) {
// Schedule has been modified, which means it's already conflicted.
// Modify its state to partially conflicted.
- conflicts.put(modified2OriginalSchedules.get(schedule), true);
+ ScheduledRecording originalSchedule =
+ modified2OriginalSchedules.get(schedule);
+ conflicts.put(originalSchedule, new ConflictInfo(originalSchedule, true));
}
} else {
if (!modified2OriginalSchedules.containsKey(schedule)) {
// if schedule has been modified, it's already conflicted.
// No need to add it again.
- conflicts.put(schedule, false);
+ conflicts.put(schedule, new ConflictInfo(schedule, false));
}
long earliestEndTime = getEarliestEndTime(recordings);
if (earliestEndTime < schedule.getEndTimeMs()) {
@@ -912,7 +920,14 @@ public class DvrScheduleManager {
}
}
}
- return conflicts;
+ List<ConflictInfo> result = new ArrayList<>(conflicts.values());
+ Collections.sort(result, new Comparator<ConflictInfo>() {
+ @Override
+ public int compare(ConflictInfo lhs, ConflictInfo rhs) {
+ return RESULT_COMPARATOR.compare(lhs.schedule, rhs.schedule);
+ }
+ });
+ return result;
}
private static void removeFinishedRecordings(List<ScheduledRecording> recordings,
@@ -954,6 +969,17 @@ public class DvrScheduleManager {
return earliest;
}
+ @VisibleForTesting
+ static class ConflictInfo {
+ public ScheduledRecording schedule;
+ public boolean partialConflict;
+
+ ConflictInfo(ScheduledRecording schedule, boolean partialConflict) {
+ this.schedule = schedule;
+ this.partialConflict = partialConflict;
+ }
+ }
+
/**
* A listener which is notified the initialization of schedule manager.
*/
@@ -970,6 +996,9 @@ public class DvrScheduleManager {
public interface OnConflictStateChangeListener {
/**
* Called when the conflicting schedules change.
+ * <p>
+ * Note that this can be called before
+ * {@link ScheduledRecordingListener#onScheduledRecordingAdded} is called.
*
* @param conflict {@code true} if the {@code schedules} are the new conflicts, otherwise
* {@code false}.
diff --git a/src/com/android/tv/dvr/DvrStorageStatusManager.java b/src/com/android/tv/dvr/DvrStorageStatusManager.java
index a653b5f4..2d41d732 100644
--- a/src/com/android/tv/dvr/DvrStorageStatusManager.java
+++ b/src/com/android/tv/dvr/DvrStorageStatusManager.java
@@ -25,6 +25,7 @@ import android.content.IntentFilter;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.media.tv.TvContract;
+import android.media.tv.TvInputInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Environment;
@@ -36,8 +37,11 @@ import android.support.annotation.IntDef;
import android.support.annotation.WorkerThread;
import android.util.Log;
+import com.android.tv.TvApplication;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.feature.CommonFeatures;
+import com.android.tv.tuner.tvinput.TunerTvInputService;
+import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
import java.io.File;
@@ -294,7 +298,7 @@ public class DvrStorageStatusManager {
storageMounted, storageMountedDir, storageMountedCapacity);
}
- private class CleanUpDbTask extends AsyncTask<Void, Void, Void> {
+ private class CleanUpDbTask extends AsyncTask<Void, Void, Boolean> {
private final ContentResolver mContentResolver;
private CleanUpDbTask() {
@@ -302,13 +306,15 @@ public class DvrStorageStatusManager {
}
@Override
- protected Void doInBackground(Void... params) {
+ protected Boolean doInBackground(Void... params) {
@DvrStorageStatusManager.StorageStatus int storageStatus = getDvrStorageStatus();
if (storageStatus == DvrStorageStatusManager.STORAGE_STATUS_MISSING) {
return null;
}
- List<ContentProviderOperation> ops = getDeleteOps(storageStatus
- == DvrStorageStatusManager.STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL);
+ if (storageStatus == DvrStorageStatusManager.STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL) {
+ return true;
+ }
+ List<ContentProviderOperation> ops = getDeleteOps();
if (ops == null || ops.isEmpty()) {
return null;
}
@@ -329,13 +335,28 @@ public class DvrStorageStatusManager {
}
@Override
- protected void onPostExecute(Void result) {
+ protected void onPostExecute(Boolean forgetStorage) {
+ if (forgetStorage != null && forgetStorage == true) {
+ DvrManager dvrManager = TvApplication.getSingletons(mContext).getDvrManager();
+ TvInputManagerHelper tvInputManagerHelper =
+ TvApplication.getSingletons(mContext).getTvInputManagerHelper();
+ List<TvInputInfo> tvInputInfoList =
+ tvInputManagerHelper.getTvInputInfos(true, false);
+ if (tvInputInfoList == null || tvInputInfoList.isEmpty()) {
+ return;
+ }
+ for (TvInputInfo info : tvInputInfoList) {
+ if (Utils.isBundledInput(info.getId())) {
+ dvrManager.forgetStorage(info.getId());
+ }
+ }
+ }
if (mCleanUpDbTask == this) {
mCleanUpDbTask = null;
}
}
- private List<ContentProviderOperation> getDeleteOps(boolean deleteAll) {
+ private List<ContentProviderOperation> getDeleteOps() {
List<ContentProviderOperation> ops = new ArrayList<>();
try (Cursor c = mContentResolver.query(
@@ -364,7 +385,7 @@ public class DvrStorageStatusManager {
continue;
}
File recordedProgramDir = new File(dataUri.getPath());
- if (deleteAll || !recordedProgramDir.exists()) {
+ if (!recordedProgramDir.exists()) {
ops.add(ContentProviderOperation.newDelete(
TvContract.buildRecordedProgramUri(Long.parseLong(id))).build());
}
diff --git a/src/com/android/tv/dvr/DvrWatchedPositionManager.java b/src/com/android/tv/dvr/DvrWatchedPositionManager.java
index 4eada742..dc05ed06 100644
--- a/src/com/android/tv/dvr/DvrWatchedPositionManager.java
+++ b/src/com/android/tv/dvr/DvrWatchedPositionManager.java
@@ -22,6 +22,7 @@ import android.media.tv.TvInputManager;
import android.support.annotation.IntDef;
import com.android.tv.common.SharedPreferencesUtils;
+import com.android.tv.dvr.data.RecordedProgram;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
diff --git a/src/com/android/tv/dvr/WritableDvrDataManager.java b/src/com/android/tv/dvr/WritableDvrDataManager.java
index bf72d912..129ba153 100644
--- a/src/com/android/tv/dvr/WritableDvrDataManager.java
+++ b/src/com/android/tv/dvr/WritableDvrDataManager.java
@@ -18,7 +18,9 @@ package com.android.tv.dvr;
import android.support.annotation.MainThread;
-import com.android.tv.dvr.ScheduledRecording.RecordingState;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.data.ScheduledRecording.RecordingState;
+import com.android.tv.dvr.data.SeriesRecording;
/**
* Full data manager.
@@ -27,7 +29,7 @@ import com.android.tv.dvr.ScheduledRecording.RecordingState;
* for internal use only. Do not call them from UI directly.
*/
@MainThread
-interface WritableDvrDataManager extends DvrDataManager {
+public interface WritableDvrDataManager extends DvrDataManager {
/**
* Adds new recordings.
*/
diff --git a/src/com/android/tv/dvr/IdGenerator.java b/src/com/android/tv/dvr/data/IdGenerator.java
index 0ed6362c..2ade1dad 100644
--- a/src/com/android/tv/dvr/IdGenerator.java
+++ b/src/com/android/tv/dvr/data/IdGenerator.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.dvr;
+package com.android.tv.dvr.data;
import java.util.concurrent.atomic.AtomicLong;
diff --git a/src/com/android/tv/dvr/RecordedProgram.java b/src/com/android/tv/dvr/data/RecordedProgram.java
index dd744f80..18e1c769 100644
--- a/src/com/android/tv/dvr/RecordedProgram.java
+++ b/src/com/android/tv/dvr/data/RecordedProgram.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr;
+package com.android.tv.dvr.data;
import static android.media.tv.TvContract.RecordedPrograms;
diff --git a/src/com/android/tv/dvr/ScheduledRecording.java b/src/com/android/tv/dvr/data/ScheduledRecording.java
index 2bda10ea..88849a2c 100644
--- a/src/com/android/tv/dvr/ScheduledRecording.java
+++ b/src/com/android/tv/dvr/data/ScheduledRecording.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.dvr;
+package com.android.tv.dvr.data;
import android.content.ContentValues;
import android.content.Context;
@@ -27,9 +27,11 @@ import android.text.TextUtils;
import android.util.Range;
import com.android.tv.R;
+import com.android.tv.TvApplication;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.Channel;
import com.android.tv.data.Program;
+import com.android.tv.dvr.DvrScheduleManager;
import com.android.tv.dvr.provider.DvrContract.Schedules;
import com.android.tv.util.CompositeComparator;
import com.android.tv.util.Utils;
@@ -683,6 +685,19 @@ public final class ScheduledRecording implements Parcelable {
}
}
+ /**
+ * Returns the program's display title, if the program title is not null, returns program title.
+ * Otherwise returns the channel name.
+ */
+ public String getProgramDisplayTitle(Context context) {
+ if (!TextUtils.isEmpty(mProgramTitle)) {
+ return mProgramTitle;
+ }
+ Channel channel = TvApplication.getSingletons(context).getChannelDataManager()
+ .getChannel(mChannelId);
+ return channel != null ? channel.getDisplayName()
+ : context.getString(R.string.no_program_information);
+ }
/**
* Converts a string to a @RecordingType int, defaulting to {@link #TYPE_TIMED}.
diff --git a/src/com/android/tv/dvr/data/SeasonEpisodeNumber.java b/src/com/android/tv/dvr/data/SeasonEpisodeNumber.java
new file mode 100644
index 00000000..89533dbb
--- /dev/null
+++ b/src/com/android/tv/dvr/data/SeasonEpisodeNumber.java
@@ -0,0 +1,72 @@
+/*
+ * 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.data;
+
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * A plain java object which includes the season/episode number for the series recording.
+ */
+public class SeasonEpisodeNumber {
+ public final long seriesRecordingId;
+ public final String seasonNumber;
+ public final String episodeNumber;
+
+ /**
+ * Creates a new Builder with the values set from an existing {@link ScheduledRecording}.
+ */
+ public SeasonEpisodeNumber(ScheduledRecording r) {
+ this(r.getSeriesRecordingId(), r.getSeasonNumber(), r.getEpisodeNumber());
+ }
+
+ public SeasonEpisodeNumber(long seriesRecordingId, String seasonNumber, String episodeNumber) {
+ this.seriesRecordingId = seriesRecordingId;
+ this.seasonNumber = seasonNumber;
+ this.episodeNumber = episodeNumber;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof SeasonEpisodeNumber)
+ || TextUtils.isEmpty(seasonNumber) || TextUtils.isEmpty(episodeNumber)) {
+ return false;
+ }
+ SeasonEpisodeNumber that = (SeasonEpisodeNumber) o;
+ return seriesRecordingId == that.seriesRecordingId
+ && Objects.equals(seasonNumber, that.seasonNumber)
+ && Objects.equals(episodeNumber, that.episodeNumber);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(seriesRecordingId, seasonNumber, episodeNumber);
+ }
+
+ @Override
+ public String toString() {
+ return "SeasonEpisodeNumber{" +
+ "seriesRecordingId=" + seriesRecordingId +
+ ", seasonNumber='" + seasonNumber +
+ ", episodeNumber=" + episodeNumber +
+ '}';
+ }
+} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/SeriesInfo.java b/src/com/android/tv/dvr/data/SeriesInfo.java
index 30256dc5..a0dec4a4 100644
--- a/src/com/android/tv/dvr/SeriesInfo.java
+++ b/src/com/android/tv/dvr/data/SeriesInfo.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr;
+package com.android.tv.dvr.data;
/**
* Series information.
diff --git a/src/com/android/tv/dvr/SeriesRecording.java b/src/com/android/tv/dvr/data/SeriesRecording.java
index f0690f5f..b7cf0f66 100644
--- a/src/com/android/tv/dvr/SeriesRecording.java
+++ b/src/com/android/tv/dvr/data/SeriesRecording.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.dvr;
+package com.android.tv.dvr.data;
import android.content.ContentValues;
import android.database.Cursor;
@@ -26,6 +26,7 @@ import android.text.TextUtils;
import com.android.tv.data.BaseProgram;
import com.android.tv.data.Program;
+import com.android.tv.dvr.DvrScheduleManager;
import com.android.tv.dvr.provider.DvrContract.SeriesRecordings;
import com.android.tv.util.Utils;
diff --git a/src/com/android/tv/dvr/provider/AsyncDvrDbTask.java b/src/com/android/tv/dvr/provider/AsyncDvrDbTask.java
index 1a12fb23..c5383d02 100644
--- a/src/com/android/tv/dvr/provider/AsyncDvrDbTask.java
+++ b/src/com/android/tv/dvr/provider/AsyncDvrDbTask.java
@@ -21,8 +21,8 @@ import android.database.Cursor;
import android.os.AsyncTask;
import android.support.annotation.Nullable;
-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.provider.DvrContract.Schedules;
import com.android.tv.dvr.provider.DvrContract.SeriesRecordings;
import com.android.tv.util.NamedThreadFactory;
diff --git a/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java b/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java
index 2f16ba5d..8b9481a9 100644
--- a/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java
+++ b/src/com/android/tv/dvr/provider/DvrDatabaseHelper.java
@@ -27,8 +27,8 @@ import android.provider.BaseColumns;
import android.text.TextUtils;
import android.util.Log;
-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.provider.DvrContract.Schedules;
import com.android.tv.dvr.provider.DvrContract.SeriesRecordings;
diff --git a/src/com/android/tv/dvr/DvrDbSync.java b/src/com/android/tv/dvr/provider/DvrDbSync.java
index df181455..8a0c2d19 100644
--- a/src/com/android/tv/dvr/DvrDbSync.java
+++ b/src/com/android/tv/dvr/provider/DvrDbSync.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.dvr;
+package com.android.tv.dvr.provider;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
@@ -34,6 +34,11 @@ import com.android.tv.TvApplication;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.Program;
import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
+import com.android.tv.dvr.DvrDataManagerImpl;
+import com.android.tv.dvr.DvrManager;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.data.SeriesRecording;
+import com.android.tv.dvr.recorder.SeriesRecordingScheduler;
import com.android.tv.util.AsyncDbTask.AsyncQueryProgramTask;
import com.android.tv.util.TvProviderUriMatcher;
@@ -57,11 +62,12 @@ import java.util.Set;
*/
@MainThread
@TargetApi(Build.VERSION_CODES.N)
-class DvrDbSync {
+public class DvrDbSync {
private static final String TAG = "DvrDbSync";
private static final boolean DEBUG = false;
private final Context mContext;
+ private final DvrManager mDvrManager;
private final DvrDataManagerImpl mDataManager;
private final ChannelDataManager mChannelDataManager;
private final Queue<Long> mProgramIdQueue = new LinkedList<>();
@@ -129,17 +135,21 @@ class DvrDbSync {
}
};
- DvrDbSync(Context context, DvrDataManagerImpl dataManager) {
- this(context, dataManager, TvApplication.getSingletons(context).getChannelDataManager());
+ public DvrDbSync(Context context, DvrDataManagerImpl dataManager) {
+ this(context, dataManager, TvApplication.getSingletons(context).getChannelDataManager(),
+ TvApplication.getSingletons(context).getDvrManager(),
+ SeriesRecordingScheduler.getInstance(context));
}
@VisibleForTesting
DvrDbSync(Context context, DvrDataManagerImpl dataManager,
- ChannelDataManager channelDataManager) {
+ ChannelDataManager channelDataManager, DvrManager dvrManager,
+ SeriesRecordingScheduler seriesRecordingScheduler) {
mContext = context;
+ mDvrManager = dvrManager;
mDataManager = dataManager;
mChannelDataManager = channelDataManager;
- mSeriesRecordingScheduler = SeriesRecordingScheduler.getInstance(context);
+ mSeriesRecordingScheduler = seriesRecordingScheduler;
}
/**
@@ -279,10 +289,9 @@ class DvrDbSync {
mDataManager.getSeriesRecording(program.getSeriesId());
if (seriesRecording == null) {
// The new program is episodic while the previous one isn't.
- SeriesRecording newSeriesRecording = TvApplication.getSingletons(mContext)
- .getDvrManager().addSeriesRecording(program,
- Collections.singletonList(program),
- SeriesRecording.STATE_SERIES_STOPPED);
+ SeriesRecording newSeriesRecording = mDvrManager.addSeriesRecording(
+ program, Collections.singletonList(program),
+ SeriesRecording.STATE_SERIES_STOPPED);
builder.setSeriesRecordingId(newSeriesRecording.getId());
needUpdate = true;
} else if (seriesRecording.getId() != schedule.getSeriesRecordingId()) {
@@ -306,8 +315,9 @@ class DvrDbSync {
// Old program belongs to a series but the new one doesn't.
seriesRecordingsToUpdate.add(seriesRecordingForOldSchedule);
}
- // Change start time only when the recording start time has not passed.
- boolean needToChangeStartTime = schedule.getStartTimeMs() > currentTimeMs
+ // Change start time only when the recording is not started yet.
+ boolean needToChangeStartTime =
+ schedule.getState() != ScheduledRecording.STATE_RECORDING_IN_PROGRESS
&& program.getStartTimeUtcMillis() != schedule.getStartTimeMs();
if (needToChangeStartTime) {
builder.setStartTimeMs(program.getStartTimeUtcMillis());
diff --git a/src/com/android/tv/dvr/EpisodicProgramLoadTask.java b/src/com/android/tv/dvr/provider/EpisodicProgramLoadTask.java
index 15ca2700..ba0aca51 100644
--- a/src/com/android/tv/dvr/EpisodicProgramLoadTask.java
+++ b/src/com/android/tv/dvr/provider/EpisodicProgramLoadTask.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.dvr;
+package com.android.tv.dvr.provider;
import android.annotation.TargetApi;
import android.content.Context;
@@ -24,13 +24,15 @@ import android.media.tv.TvContract.Programs;
import android.net.Uri;
import android.os.Build;
import android.support.annotation.Nullable;
-import android.support.annotation.VisibleForTesting;
import android.support.annotation.WorkerThread;
-import android.text.TextUtils;
import com.android.tv.TvApplication;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.Program;
+import com.android.tv.dvr.DvrDataManager;
+import com.android.tv.dvr.data.SeasonEpisodeNumber;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.util.AsyncDbTask.AsyncProgramQueryTask;
import com.android.tv.util.AsyncDbTask.CursorFilter;
import com.android.tv.util.PermissionUtils;
@@ -40,7 +42,6 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
-import java.util.Objects;
import java.util.Set;
/**
@@ -253,21 +254,13 @@ abstract public class EpisodicProgramLoadTask {
return sqlParams;
}
- @VisibleForTesting
- static boolean isEpisodeScheduled(Collection<ScheduledEpisode> scheduledEpisodes,
- ScheduledEpisode episode) {
- // The episode whose season number or episode number is null will always be scheduled.
- return scheduledEpisodes.contains(episode) && !TextUtils.isEmpty(episode.seasonNumber)
- && !TextUtils.isEmpty(episode.episodeNumber);
- }
-
/**
* Filter the programs which match the series recording. The episodes which the schedules are
* already created for are filtered out too.
*/
private class SeriesRecordingCursorFilter implements CursorFilter {
private final Set<Long> mDisallowedProgramIds = new HashSet<>();
- private final Set<ScheduledEpisode> mScheduledEpisodes = new HashSet<>();
+ private final Set<SeasonEpisodeNumber> mSeasonEpisodeNumbers = new HashSet<>();
SeriesRecordingCursorFilter(List<SeriesRecording> seriesRecordings) {
if (!mLoadDisallowedProgram) {
@@ -282,7 +275,7 @@ abstract public class EpisodicProgramLoadTask {
if (seriesRecordingIds.contains(r.getSeriesRecordingId())
&& r.getState() != ScheduledRecording.STATE_RECORDING_FAILED
&& r.getState() != ScheduledRecording.STATE_RECORDING_CLIPPED) {
- mScheduledEpisodes.add(new ScheduledEpisode(r));
+ mSeasonEpisodeNumbers.add(new SeasonEpisodeNumber(r));
}
}
}
@@ -306,9 +299,9 @@ abstract public class EpisodicProgramLoadTask {
}
if (programMatches) {
return mLoadScheduledEpisode
- || !isEpisodeScheduled(mScheduledEpisodes, new ScheduledEpisode(
- seriesRecording.getId(), program.getSeasonNumber(),
- program.getEpisodeNumber()));
+ || !mSeasonEpisodeNumbers.contains(new SeasonEpisodeNumber(
+ seriesRecording.getId(), program.getSeasonNumber(),
+ program.getEpisodeNumber()));
}
}
return false;
@@ -333,50 +326,4 @@ abstract public class EpisodicProgramLoadTask {
public String[] selectionArgs;
public CursorFilter filter;
}
-
- /**
- * A plain java object which includes the season/episode number for the series recording.
- */
- public static class ScheduledEpisode {
- public final long seriesRecordingId;
- public final String seasonNumber;
- public final String episodeNumber;
-
- /**
- * Create a new Builder with the values set from an existing {@link ScheduledRecording}.
- */
- ScheduledEpisode(ScheduledRecording r) {
- this(r.getSeriesRecordingId(), r.getSeasonNumber(), r.getEpisodeNumber());
- }
-
- public ScheduledEpisode(long seriesRecordingId, String seasonNumber, String episodeNumber) {
- this.seriesRecordingId = seriesRecordingId;
- this.seasonNumber = seasonNumber;
- this.episodeNumber = episodeNumber;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof ScheduledEpisode)) return false;
- ScheduledEpisode that = (ScheduledEpisode) o;
- return seriesRecordingId == that.seriesRecordingId
- && Objects.equals(seasonNumber, that.seasonNumber)
- && Objects.equals(episodeNumber, that.episodeNumber);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(seriesRecordingId, seasonNumber, episodeNumber);
- }
-
- @Override
- public String toString() {
- return "ScheduledEpisode{" +
- "seriesRecordingId=" + seriesRecordingId +
- ", seasonNumber='" + seasonNumber +
- ", episodeNumber=" + episodeNumber +
- '}';
- }
- }
}
diff --git a/src/com/android/tv/dvr/ConflictChecker.java b/src/com/android/tv/dvr/recorder/ConflictChecker.java
index 201e379e..8aa90116 100644
--- a/src/com/android/tv/dvr/ConflictChecker.java
+++ b/src/com/android/tv/dvr/recorder/ConflictChecker.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.dvr;
+package com.android.tv.dvr.recorder;
import android.annotation.TargetApi;
import android.content.ContentUris;
@@ -37,6 +37,9 @@ import com.android.tv.common.WeakHandler;
import com.android.tv.data.Channel;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
+import com.android.tv.dvr.DvrScheduleManager;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.ui.DvrUiHelper;
import java.util.ArrayList;
import java.util.HashMap;
diff --git a/src/com/android/tv/dvr/DvrRecordingService.java b/src/com/android/tv/dvr/recorder/DvrRecordingService.java
index 8c40aaa8..08ffaf86 100644
--- a/src/com/android/tv/dvr/DvrRecordingService.java
+++ b/src/com/android/tv/dvr/recorder/DvrRecordingService.java
@@ -14,9 +14,10 @@
* limitations under the License
*/
-package com.android.tv.dvr;
+package com.android.tv.dvr.recorder;
import android.app.AlarmManager;
+import android.app.Notification;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
@@ -27,9 +28,13 @@ import android.support.annotation.VisibleForTesting;
import android.util.Log;
import com.android.tv.ApplicationSingletons;
+import com.android.tv.InputSessionManager;
+import com.android.tv.InputSessionManager.OnRecordingSessionChangeListener;
+import com.android.tv.R;
import com.android.tv.TvApplication;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.feature.CommonFeatures;
+import com.android.tv.dvr.WritableDvrDataManager;
import com.android.tv.util.Clock;
import com.android.tv.util.RecurringRunner;
@@ -52,6 +57,8 @@ public class DvrRecordingService extends Service {
private static final boolean DEBUG = false;
public static final String HANDLER_THREAD_NAME = "DvrRecordingService-handler";
+ private static final int ONGOING_NOTIFICATION_ID = 1;
+
public static void startService(Context context) {
Intent dvrSchedulerIntent = new Intent(context, DvrRecordingService.class);
context.startService(dvrSchedulerIntent);
@@ -62,6 +69,27 @@ public class DvrRecordingService extends Service {
private Scheduler mScheduler;
private HandlerThread mHandlerThread;
+ private InputSessionManager mSessionManager;
+ private boolean mForeground;
+
+ private final OnRecordingSessionChangeListener mOnRecordingSessionChangeListener =
+ new OnRecordingSessionChangeListener() {
+ @Override
+ public void onRecordingSessionChange(final boolean create, final int count) {
+ if (create && !mForeground) {
+ Notification notification =
+ new Notification.Builder(getApplicationContext())
+ .setContentTitle(TAG)
+ .setSmallIcon(R.drawable.ic_dvr)
+ .build();
+ startForeground(ONGOING_NOTIFICATION_ID, notification);
+ mForeground = true;
+ } else if (!create && mForeground && count == 0) {
+ stopForeground(STOP_FOREGROUND_REMOVE);
+ mForeground = false;
+ }
+ }
+ };
@Override
public void onCreate() {
@@ -70,8 +98,11 @@ public class DvrRecordingService extends Service {
super.onCreate();
SoftPreconditions.checkFeatureEnabled(this, CommonFeatures.DVR, TAG);
ApplicationSingletons singletons = TvApplication.getSingletons(this);
- WritableDvrDataManager dataManager = (WritableDvrDataManager) singletons.getDvrDataManager();
+ WritableDvrDataManager dataManager =
+ (WritableDvrDataManager) singletons.getDvrDataManager();
+ mSessionManager = singletons.getInputSessionManager();
+ mSessionManager.addOnRecordingSessionChangeListener(mOnRecordingSessionChangeListener);
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
// mScheduler may have been set for testing.
if (mScheduler == null) {
@@ -105,6 +136,7 @@ public class DvrRecordingService extends Service {
mHandlerThread.quitSafely();
mHandlerThread = null;
}
+ mSessionManager.removeRecordingSessionChangeListener(mOnRecordingSessionChangeListener);
super.onDestroy();
}
diff --git a/src/com/android/tv/dvr/DvrStartRecordingReceiver.java b/src/com/android/tv/dvr/recorder/DvrStartRecordingReceiver.java
index 6d2f0d43..8c6ee145 100644
--- a/src/com/android/tv/dvr/DvrStartRecordingReceiver.java
+++ b/src/com/android/tv/dvr/recorder/DvrStartRecordingReceiver.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr;
+package com.android.tv.dvr.recorder;
import com.android.tv.TvApplication;
diff --git a/src/com/android/tv/dvr/InputTaskScheduler.java b/src/com/android/tv/dvr/recorder/InputTaskScheduler.java
index 53c89ebc..46546a76 100644
--- a/src/com/android/tv/dvr/InputTaskScheduler.java
+++ b/src/com/android/tv/dvr/recorder/InputTaskScheduler.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.dvr;
+package com.android.tv.dvr.recorder;
import android.content.Context;
import android.media.tv.TvInputInfo;
@@ -30,6 +30,10 @@ import android.util.LongSparseArray;
import com.android.tv.InputSessionManager;
import com.android.tv.data.Channel;
import com.android.tv.data.ChannelDataManager;
+import com.android.tv.dvr.DvrDataManager;
+import com.android.tv.dvr.DvrManager;
+import com.android.tv.dvr.WritableDvrDataManager;
+import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.util.Clock;
import com.android.tv.util.CompositeComparator;
diff --git a/src/com/android/tv/dvr/RecordingTask.java b/src/com/android/tv/dvr/recorder/RecordingTask.java
index c3d236b0..c3314dde 100644
--- a/src/com/android/tv/dvr/RecordingTask.java
+++ b/src/com/android/tv/dvr/recorder/RecordingTask.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr;
+package com.android.tv.dvr.recorder;
import android.annotation.TargetApi;
import android.content.Context;
@@ -37,7 +37,10 @@ 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.dvr.InputTaskScheduler.HandlerWrapper;
+import com.android.tv.dvr.DvrManager;
+import com.android.tv.dvr.WritableDvrDataManager;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.recorder.InputTaskScheduler.HandlerWrapper;
import com.android.tv.util.Clock;
import com.android.tv.util.Utils;
@@ -256,13 +259,21 @@ public class RecordingTask extends RecordingCallback implements Handler.Callback
public void run() {
if (TvApplication.getSingletons(mContext).getMainActivityWrapper()
.isResumed()) {
- Toast.makeText(mContext.getApplicationContext(),
- R.string.dvr_error_insufficient_space_description,
- Toast.LENGTH_LONG)
- .show();
+ ScheduledRecording scheduledRecording = mDataManager
+ .getScheduledRecording(mScheduledRecording.getId());
+ if (scheduledRecording != null) {
+ Toast.makeText(mContext.getApplicationContext(),
+ mContext.getString(R.string
+ .dvr_error_insufficient_space_description_one_recording,
+ scheduledRecording.getProgramDisplayTitle(mContext)),
+ Toast.LENGTH_LONG)
+ .show();
+ }
} else {
Utils.setRecordingFailedReason(mContext.getApplicationContext(),
TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE);
+ Utils.addFailedScheduledRecordingInfo(mContext.getApplicationContext(),
+ mScheduledRecording.getProgramDisplayTitle(mContext));
}
}
});
diff --git a/src/com/android/tv/dvr/ScheduledProgramReaper.java b/src/com/android/tv/dvr/recorder/ScheduledProgramReaper.java
index cd79a631..d958c4a1 100644
--- a/src/com/android/tv/dvr/ScheduledProgramReaper.java
+++ b/src/com/android/tv/dvr/recorder/ScheduledProgramReaper.java
@@ -14,11 +14,14 @@
* limitations under the License.
*/
-package com.android.tv.dvr;
+package com.android.tv.dvr.recorder;
import android.support.annotation.MainThread;
import android.support.annotation.VisibleForTesting;
+import com.android.tv.dvr.WritableDvrDataManager;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.util.Clock;
import java.util.ArrayList;
diff --git a/src/com/android/tv/dvr/Scheduler.java b/src/com/android/tv/dvr/recorder/Scheduler.java
index ce78e1be..19e73342 100644
--- a/src/com/android/tv/dvr/Scheduler.java
+++ b/src/com/android/tv/dvr/recorder/Scheduler.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.dvr;
+package com.android.tv.dvr.recorder;
import android.app.AlarmManager;
import android.app.PendingIntent;
@@ -32,8 +32,12 @@ import android.util.Range;
import com.android.tv.InputSessionManager;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.ChannelDataManager.Listener;
+import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrDataManager.OnDvrScheduleLoadFinishedListener;
import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
+import com.android.tv.dvr.DvrManager;
+import com.android.tv.dvr.WritableDvrDataManager;
+import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.util.Clock;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
diff --git a/src/com/android/tv/dvr/SeriesRecordingScheduler.java b/src/com/android/tv/dvr/recorder/SeriesRecordingScheduler.java
index 5ed12ce8..8a211f66 100644
--- a/src/com/android/tv/dvr/SeriesRecordingScheduler.java
+++ b/src/com/android/tv/dvr/recorder/SeriesRecordingScheduler.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.dvr;
+package com.android.tv.dvr.recorder;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
@@ -23,7 +23,6 @@ import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Build;
import android.support.annotation.MainThread;
-import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
@@ -36,9 +35,16 @@ import com.android.tv.common.SharedPreferencesUtils;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.Program;
import com.android.tv.data.epg.EpgFetcher;
+import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
import com.android.tv.dvr.DvrDataManager.SeriesRecordingListener;
-import com.android.tv.dvr.EpisodicProgramLoadTask.ScheduledEpisode;
+import com.android.tv.dvr.DvrManager;
+import com.android.tv.dvr.WritableDvrDataManager;
+import com.android.tv.dvr.data.SeasonEpisodeNumber;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.data.SeriesInfo;
+import com.android.tv.dvr.data.SeriesRecording;
+import com.android.tv.dvr.provider.EpisodicProgramLoadTask;
import com.android.tv.experiments.Experiments;
import java.util.ArrayList;
@@ -52,11 +58,11 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
-import java.util.concurrent.CopyOnWriteArraySet;
import java.util.Set;
/**
- * Creates the {@link ScheduledRecording}s for the {@link SeriesRecording}.
+ * Creates the {@link com.android.tv.dvr.data.ScheduledRecording}s for
+ * the {@link com.android.tv.dvr.data.SeriesRecording}.
* <p>
* The current implementation assumes that the series recordings are scheduled only for one channel.
*/
@@ -85,15 +91,13 @@ public class SeriesRecordingScheduler {
private final DvrManager mDvrManager;
private final WritableDvrDataManager mDataManager;
private final List<SeriesRecordingUpdateTask> mScheduleTasks = new ArrayList<>();
- private final List<FetchSeriesInfoTask> mFetchSeriesInfoTasks = new ArrayList<>();
+ private final LongSparseArray<FetchSeriesInfoTask> mFetchSeriesInfoTasks =
+ new LongSparseArray<>();
private final Set<String> mFetchedSeriesIds = new ArraySet<>();
private final SharedPreferences mSharedPreferences;
private boolean mStarted;
private boolean mPaused;
private final Set<Long> mPendingSeriesRecordings = new ArraySet<>();
- private final Set<OnSeriesRecordingUpdatedListener> mOnSeriesRecordingUpdatedListeners =
- new CopyOnWriteArraySet<>();
-
private final SeriesRecordingListener mSeriesRecordingListener = new SeriesRecordingListener() {
@Override
@@ -107,7 +111,7 @@ public class SeriesRecordingScheduler {
public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) {
// Cancel the update.
for (Iterator<SeriesRecordingUpdateTask> iter = mScheduleTasks.iterator();
- iter.hasNext(); ) {
+ iter.hasNext(); ) {
SeriesRecordingUpdateTask task = iter.next();
if (CollectionUtils.subtract(task.getSeriesRecordings(), seriesRecordings,
SeriesRecording.ID_COMPARATOR).isEmpty()) {
@@ -115,6 +119,13 @@ public class SeriesRecordingScheduler {
iter.remove();
}
}
+ for (SeriesRecording seriesRecording : seriesRecordings) {
+ FetchSeriesInfoTask task = mFetchSeriesInfoTasks.get(seriesRecording.getId());
+ if (task != null) {
+ task.cancel(true);
+ mFetchSeriesInfoTasks.remove(seriesRecording.getId());
+ }
+ }
}
@Override
@@ -226,7 +237,8 @@ public class SeriesRecordingScheduler {
}
if (DEBUG) Log.d(TAG, "stop");
mStarted = false;
- for (FetchSeriesInfoTask task : mFetchSeriesInfoTasks) {
+ for (int i = 0; i < mFetchSeriesInfoTasks.size(); i++) {
+ FetchSeriesInfoTask task = mFetchSeriesInfoTasks.get(mFetchSeriesInfoTasks.keyAt(i));
task.cancel(true);
}
mFetchSeriesInfoTasks.clear();
@@ -250,7 +262,7 @@ public class SeriesRecordingScheduler {
if (Experiments.CLOUD_EPG.get()) {
FetchSeriesInfoTask task = new FetchSeriesInfoTask(seriesRecording);
task.execute();
- mFetchSeriesInfoTasks.add(task);
+ mFetchSeriesInfoTasks.put(seriesRecording.getId(), task);
}
}
@@ -363,20 +375,6 @@ public class SeriesRecordingScheduler {
}
}
- /**
- * Adds {@link OnSeriesRecordingUpdatedListener}.
- */
- public void addOnSeriesRecordingUpdatedListener(OnSeriesRecordingUpdatedListener listener) {
- mOnSeriesRecordingUpdatedListeners.add(listener);
- }
-
- /**
- * Removes {@link OnSeriesRecordingUpdatedListener}.
- */
- public void removeOnSeriesRecordingUpdatedListener(OnSeriesRecordingUpdatedListener listener) {
- mOnSeriesRecordingUpdatedListeners.remove(listener);
- }
-
private boolean needToReadAllChannels(List<SeriesRecording> seriesRecordingsToUpdate) {
for (SeriesRecording seriesRecording : seriesRecordingsToUpdate) {
if (seriesRecording.getChannelOption() == SeriesRecording.OPTION_CHANNEL_ALL) {
@@ -403,8 +401,7 @@ public class SeriesRecordingScheduler {
/**
* @see #pickOneProgramPerEpisode(List, List)
*/
- @VisibleForTesting
- static LongSparseArray<List<Program>> pickOneProgramPerEpisode(
+ public static LongSparseArray<List<Program>> pickOneProgramPerEpisode(
DvrDataManager dataManager, List<SeriesRecording> seriesRecordings,
List<Program> programs) {
// Initialize.
@@ -415,7 +412,7 @@ public class SeriesRecordingScheduler {
seriesRecordingIds.put(seriesRecording.getSeriesId(), seriesRecording.getId());
}
// Group programs by the episode.
- Map<ScheduledEpisode, List<Program>> programsForEpisodeMap = new HashMap<>();
+ Map<SeasonEpisodeNumber, List<Program>> programsForEpisodeMap = new HashMap<>();
for (Program program : programs) {
long seriesRecordingId = seriesRecordingIds.get(program.getSeriesId());
if (TextUtils.isEmpty(program.getSeasonNumber())
@@ -424,17 +421,17 @@ public class SeriesRecordingScheduler {
result.get(seriesRecordingId).add(program);
continue;
}
- ScheduledEpisode episode = new ScheduledEpisode(seriesRecordingId,
+ SeasonEpisodeNumber seasonEpisodeNumber = new SeasonEpisodeNumber(seriesRecordingId,
program.getSeasonNumber(), program.getEpisodeNumber());
- List<Program> programsForEpisode = programsForEpisodeMap.get(episode);
+ List<Program> programsForEpisode = programsForEpisodeMap.get(seasonEpisodeNumber);
if (programsForEpisode == null) {
programsForEpisode = new ArrayList<>();
- programsForEpisodeMap.put(episode, programsForEpisode);
+ programsForEpisodeMap.put(seasonEpisodeNumber, programsForEpisode);
}
programsForEpisode.add(program);
}
// Pick one program.
- for (Entry<ScheduledEpisode, List<Program>> entry : programsForEpisodeMap.entrySet()) {
+ for (Entry<SeasonEpisodeNumber, List<Program>> entry : programsForEpisodeMap.entrySet()) {
List<Program> programsForEpisode = entry.getValue();
Collections.sort(programsForEpisode, new Comparator<Program>() {
@Override
@@ -512,13 +509,6 @@ public class SeriesRecordingScheduler {
mDvrManager.addScheduleToSeriesRecording(seriesRecording, programsToSchedule);
}
}
- if (!mOnSeriesRecordingUpdatedListeners.isEmpty()) {
- for (OnSeriesRecordingUpdatedListener listener
- : mOnSeriesRecordingUpdatedListeners) {
- listener.onSeriesRecordingUpdated(
- SeriesRecording.toArray(getSeriesRecordings()));
- }
- }
}
@Override
@@ -561,19 +551,12 @@ public class SeriesRecordingScheduler {
mFetchedSeriesIds.add(seriesInfo.getId());
updateFetchedSeries();
}
- mFetchSeriesInfoTasks.remove(this);
+ mFetchSeriesInfoTasks.remove(mSeriesRecording.getId());
}
@Override
protected void onCancelled(SeriesInfo seriesInfo) {
- mFetchSeriesInfoTasks.remove(this);
+ mFetchSeriesInfoTasks.remove(mSeriesRecording.getId());
}
}
-
- /**
- * A listener to notify when series recording are updated.
- */
- public interface OnSeriesRecordingUpdatedListener {
- void onSeriesRecordingUpdated(SeriesRecording... seriesRecordings);
- }
}
diff --git a/src/com/android/tv/dvr/ui/BigArguments.java b/src/com/android/tv/dvr/ui/BigArguments.java
new file mode 100644
index 00000000..ec3b5065
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/BigArguments.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tv.dvr.ui;
+
+import android.support.annotation.NonNull;
+
+import com.android.tv.common.SoftPreconditions;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Stores the object to pass through activities/fragments.
+ */
+public class BigArguments {
+ private final static String TAG = "BigArguments";
+ private static Map<String, Object> sBigArgumentMap = new HashMap<>();
+
+ /**
+ * Sets the argument.
+ */
+ public static void setArgument(String name, @NonNull Object value) {
+ SoftPreconditions.checkState(value != null, TAG, "Set argument, but value is null");
+ sBigArgumentMap.put(name, value);
+ }
+
+ /**
+ * Returns the argument which is associated to the name.
+ */
+ public static Object getArgument(String name) {
+ return sBigArgumentMap.get(name);
+ }
+
+ /**
+ * Resets the arguments.
+ */
+ public static void reset() {
+ sBigArgumentMap.clear();
+ }
+}
diff --git a/src/com/android/tv/dvr/ui/ChangeImageTransformWithScaledParent.java b/src/com/android/tv/dvr/ui/ChangeImageTransformWithScaledParent.java
new file mode 100644
index 00000000..cddece73
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/ChangeImageTransformWithScaledParent.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tv.dvr.ui;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.graphics.drawable.BitmapDrawable;
+import android.transition.ChangeImageTransform;
+import android.transition.TransitionValues;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
+
+import com.android.tv.R;
+
+import java.util.Map;
+
+/**
+ * TODO: Remove this class once b/32405620 is fixed.
+ * This class is for the workaround of b/32405620 and only for the shared element transition between
+ * {@link com.android.tv.dvr.ui.browse.RecordingCardView} and
+ * {@link com.android.tv.dvr.ui.browse.DvrDetailsActivity}.
+ */
+public class ChangeImageTransformWithScaledParent extends ChangeImageTransform {
+ private static final String PROPNAME_MATRIX = "android:changeImageTransform:matrix";
+
+ public ChangeImageTransformWithScaledParent(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void captureStartValues(TransitionValues transitionValues) {
+ super.captureStartValues(transitionValues);
+ applyParentScale(transitionValues);
+ }
+
+ @Override
+ public void captureEndValues(TransitionValues transitionValues) {
+ super.captureEndValues(transitionValues);
+ applyParentScale(transitionValues);
+ }
+
+ private void applyParentScale(TransitionValues transitionValues) {
+ View view = transitionValues.view;
+ Map<String, Object> values = transitionValues.values;
+ Matrix matrix = (Matrix) values.get(PROPNAME_MATRIX);
+ if (matrix != null && view.getId() == R.id.details_overview_image
+ && view instanceof ImageView) {
+ ImageView imageView = (ImageView) view;
+ if (imageView.getScaleType() == ScaleType.CENTER_INSIDE
+ && imageView.getDrawable() instanceof BitmapDrawable) {
+ Bitmap bitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
+ if (bitmap.getWidth() < imageView.getWidth()
+ && bitmap.getHeight() < imageView.getHeight()) {
+ float scale = imageView.getContext().getResources().getFraction(
+ R.fraction.lb_focus_zoom_factor_medium, 1, 1);
+ matrix.postScale(scale, scale, imageView.getWidth() / 2,
+ imageView.getHeight() / 2);
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/android/tv/dvr/ui/CurrentRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/CurrentRecordingDetailsFragment.java
deleted file mode 100644
index 5d8e20ff..00000000
--- a/src/com/android/tv/dvr/ui/CurrentRecordingDetailsFragment.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.tv.dvr.ui;
-
-import android.content.res.Resources;
-import android.support.v17.leanback.widget.Action;
-import android.support.v17.leanback.widget.OnActionClickedListener;
-import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
-
-import com.android.tv.R;
-import com.android.tv.TvApplication;
-import com.android.tv.dvr.DvrManager;
-
-/**
- * {@link RecordingDetailsFragment} for current recording in DVR.
- */
-public class CurrentRecordingDetailsFragment extends RecordingDetailsFragment {
- private static final int ACTION_STOP_RECORDING = 1;
-
- @Override
- protected SparseArrayObjectAdapter onCreateActionsAdapter() {
- SparseArrayObjectAdapter adapter =
- new SparseArrayObjectAdapter(new ActionPresenterSelector());
- Resources res = getResources();
- adapter.set(ACTION_STOP_RECORDING, new Action(ACTION_STOP_RECORDING,
- res.getString(R.string.epg_dvr_dialog_message_stop_recording), null,
- res.getDrawable(R.drawable.lb_ic_stop)));
- return adapter;
- }
-
- @Override
- protected OnActionClickedListener onCreateOnActionClickedListener() {
- return new OnActionClickedListener() {
- @Override
- public void onActionClicked(Action action) {
- if (action.getId() == ACTION_STOP_RECORDING) {
- DvrManager dvrManager = TvApplication.getSingletons(getActivity())
- .getDvrManager();
- dvrManager.stopRecording(getRecording());
- }
- getActivity().finish();
- }
- };
- }
-}
diff --git a/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java b/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java
index 9df228d1..936e9c31 100644
--- a/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrAlreadyRecordedFragment.java
@@ -28,11 +28,9 @@ import android.widget.Toast;
import com.android.tv.R;
import com.android.tv.TvApplication;
-import com.android.tv.dvr.RecordedProgram;
import com.android.tv.data.Program;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.DvrUiHelper;
-import com.android.tv.util.Utils;
+import com.android.tv.dvr.data.RecordedProgram;
import java.util.List;
diff --git a/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java b/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java
index 78f21784..3c73cb47 100644
--- a/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrAlreadyScheduledFragment.java
@@ -25,15 +25,12 @@ import android.support.annotation.NonNull;
import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
import android.support.v17.leanback.widget.GuidedAction;
import android.text.format.DateUtils;
-import android.widget.Toast;
import com.android.tv.R;
import com.android.tv.TvApplication;
import com.android.tv.data.Program;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.DvrUiHelper;
-import com.android.tv.dvr.ScheduledRecording;
-import com.android.tv.util.Utils;
+import com.android.tv.dvr.data.ScheduledRecording;
import java.util.List;
diff --git a/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java b/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java
index 837d8ab2..880dc8ac 100644
--- a/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java
@@ -27,7 +27,7 @@ import com.android.tv.TvApplication;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.Channel;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.ScheduledRecording;
+import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.ui.DvrConflictFragment.DvrChannelRecordConflictFragment;
import java.util.ArrayList;
diff --git a/src/com/android/tv/dvr/ui/DvrConflictFragment.java b/src/com/android/tv/dvr/ui/DvrConflictFragment.java
index e7be4d0a..5985f56f 100644
--- a/src/com/android/tv/dvr/ui/DvrConflictFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrConflictFragment.java
@@ -34,10 +34,9 @@ import com.android.tv.TvApplication;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.Channel;
import com.android.tv.data.Program;
-import com.android.tv.dvr.ConflictChecker;
-import com.android.tv.dvr.ConflictChecker.OnUpcomingConflictChangeListener;
-import com.android.tv.dvr.DvrUiHelper;
-import com.android.tv.dvr.ScheduledRecording;
+import com.android.tv.dvr.recorder.ConflictChecker;
+import com.android.tv.dvr.recorder.ConflictChecker.OnUpcomingConflictChangeListener;
+import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.util.Utils;
import java.util.ArrayList;
diff --git a/src/com/android/tv/dvr/ui/DvrForgetStorageErrorFragment.java b/src/com/android/tv/dvr/ui/DvrForgetStorageErrorFragment.java
deleted file mode 100644
index 73ddcdd0..00000000
--- a/src/com/android/tv/dvr/ui/DvrForgetStorageErrorFragment.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.dvr.ui;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
-import android.text.TextUtils;
-
-import com.android.tv.R;
-import com.android.tv.TvApplication;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.dvr.DvrDataManager;
-import com.android.tv.dvr.DvrManager;
-
-import java.util.List;
-
-public class DvrForgetStorageErrorFragment extends DvrGuidedStepFragment {
- private static final int ACTION_CANCEL = 1;
- private static final int ACTION_FORGET_STORAGE = 2;
- private String mInputId;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- Bundle args = getArguments();
- if (args != null) {
- mInputId = args.getString(DvrHalfSizedDialogFragment.KEY_INPUT_ID);
- }
- SoftPreconditions.checkArgument(!TextUtils.isEmpty(mInputId));
- super.onCreate(savedInstanceState);
- }
-
- @NonNull
- @Override
- public Guidance onCreateGuidance(Bundle savedInstanceState) {
- String title = getResources().getString(R.string.dvr_error_forget_storage_title);
- String description = getResources().getString(
- R.string.dvr_error_forget_storage_description);
- return new Guidance(title, description, null, null);
- }
-
- @Override
- public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
- Activity activity = getActivity();
- actions.add(new GuidedAction.Builder(activity)
- .id(ACTION_CANCEL)
- .title(getResources().getString(R.string.dvr_action_error_cancel))
- .build());
- actions.add(new GuidedAction.Builder(activity)
- .id(ACTION_FORGET_STORAGE)
- .title(getResources().getString(R.string.dvr_action_error_forget_storage))
- .build());
- }
-
- @Override
- public void onGuidedActionClicked(GuidedAction action) {
- if (action.getId() != ACTION_FORGET_STORAGE) {
- dismissDialog();
- return;
- }
- DvrManager dvrManager = TvApplication.getSingletons(getContext()).getDvrManager();
- dvrManager.forgetStorage(mInputId);
- Activity activity = getActivity();
- if (activity instanceof DvrDetailsActivity) {
- // Since we removed everything, just finish the activity.
- activity.finish();
- } else {
- dismissDialog();
- }
- }
-}
diff --git a/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java b/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java
index d26e6836..433588da 100644
--- a/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrGuidedStepFragment.java
@@ -16,10 +16,12 @@
package com.android.tv.dvr.ui;
+import android.app.Activity;
import android.app.DialogFragment;
import android.content.Context;
import android.os.Bundle;
import android.support.v17.leanback.app.GuidedStepFragment;
+import android.support.v17.leanback.widget.GuidanceStylist;
import android.support.v17.leanback.widget.GuidedAction;
import android.support.v17.leanback.widget.VerticalGridView;
import android.view.LayoutInflater;
@@ -29,11 +31,26 @@ import android.view.ViewGroup;
import com.android.tv.MainActivity;
import com.android.tv.R;
import com.android.tv.TvApplication;
+import com.android.tv.dialog.HalfSizedDialogFragment.OnActionClickListener;
import com.android.tv.dialog.SafeDismissDialogFragment;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.ui.HalfSizedDialogFragment.OnActionClickListener;
+
+import java.util.List;
public class DvrGuidedStepFragment extends GuidedStepFragment {
+ /**
+ * Action ID for "recording/scheduling the program anyway".
+ */
+ public static final int ACTION_RECORD_ANYWAY = 1;
+ /**
+ * Action ID for "deleting existed recordings".
+ */
+ public static final int ACTION_DELETE_RECORDINGS = 2;
+ /**
+ * Action ID for "cancelling current recording request".
+ */
+ public static final int ACTION_CANCEL_RECORDING = 3;
+
private DvrManager mDvrManager;
private OnActionClickListener mOnActionClickListener;
@@ -86,4 +103,35 @@ public class DvrGuidedStepFragment extends GuidedStepFragment {
protected void setOnActionClickListener(OnActionClickListener listener) {
mOnActionClickListener = listener;
}
+
+ /**
+ * The inner guided step fragment for
+ * {@link com.android.tv.dvr.ui.DvrHalfSizedDialogFragment
+ * .DvrNoFreeSpaceErrorDialogFragment}.
+ */
+ public static class DvrNoFreeSpaceErrorFragment
+ extends DvrGuidedStepFragment {
+ @Override
+ public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
+ return new GuidanceStylist.Guidance(getString(R.string.dvr_error_no_free_space_title),
+ getString(R.string.dvr_error_no_free_space_description), null, null);
+ }
+
+ @Override
+ public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
+ Activity activity = getActivity();
+ actions.add(new GuidedAction.Builder(activity)
+ .id(ACTION_RECORD_ANYWAY)
+ .title(R.string.dvr_action_record_anyway)
+ .build());
+ actions.add(new GuidedAction.Builder(activity)
+ .id(ACTION_DELETE_RECORDINGS)
+ .title(R.string.dvr_action_delete_recordings)
+ .build());
+ actions.add(new GuidedAction.Builder(activity)
+ .id(ACTION_CANCEL_RECORDING)
+ .title(R.string.dvr_action_record_cancel)
+ .build());
+ }
+ }
} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/DvrHalfSizedDialogFragment.java b/src/com/android/tv/dvr/ui/DvrHalfSizedDialogFragment.java
index 2b132db8..9054dd03 100644
--- a/src/com/android/tv/dvr/ui/DvrHalfSizedDialogFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrHalfSizedDialogFragment.java
@@ -29,6 +29,7 @@ import android.view.ViewGroup;
import com.android.tv.MainActivity;
import com.android.tv.R;
import com.android.tv.dvr.DvrStorageStatusManager;
+import com.android.tv.dialog.HalfSizedDialogFragment;
import com.android.tv.dvr.ui.DvrConflictFragment.DvrChannelWatchConflictFragment;
import com.android.tv.dvr.ui.DvrConflictFragment.DvrProgramConflictFragment;
import com.android.tv.guide.ProgramGuide;
@@ -166,6 +167,17 @@ public class DvrHalfSizedDialogFragment extends HalfSizedDialogFragment {
}
/**
+ * A dialog fragment to show error message when there is no enough free space to record.
+ */
+ public static class DvrNoFreeSpaceErrorDialogFragment
+ extends DvrGuidedStepDialogFragment {
+ @Override
+ protected DvrGuidedStepFragment onCreateGuidedStepFragment() {
+ return new DvrGuidedStepFragment.DvrNoFreeSpaceErrorFragment();
+ }
+ }
+
+ /**
* A dialog fragment to show error message when the current storage is too small to
* support DVR
*/
diff --git a/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java b/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java
index 3b1dbfa0..3c5df1a6 100644
--- a/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrInsufficientSpaceErrorFragment.java
@@ -17,6 +17,7 @@
package com.android.tv.dvr.ui;
import android.app.Activity;
+import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
@@ -24,19 +25,67 @@ import android.support.v17.leanback.widget.GuidedAction;
import com.android.tv.R;
import com.android.tv.TvApplication;
-import com.android.tv.dvr.DvrDataManager;
+import com.android.tv.common.SoftPreconditions;
+import com.android.tv.dvr.ui.browse.DvrBrowseActivity;
+import java.util.ArrayList;
import java.util.List;
public class DvrInsufficientSpaceErrorFragment extends DvrGuidedStepFragment {
- private static final int ACTION_DONE = 1;
- private static final int ACTION_OPEN_DVR = 2;
+ /**
+ * Key for the failed scheduled recordings information.
+ */
+ public static final String FAILED_SCHEDULED_RECORDING_INFOS =
+ "failed_scheduled_recording_infos";
+
+ private static final String TAG = "DvrInsufficientSpaceErrorFragment";
+
+ private static final int ACTION_VIEW_RECENT_RECORDINGS = 1;
+
+ private ArrayList<String> mFailedScheduledRecordingInfos;
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ Bundle args = getArguments();
+ if (args != null) {
+ mFailedScheduledRecordingInfos =
+ args.getStringArrayList(FAILED_SCHEDULED_RECORDING_INFOS);
+ }
+ SoftPreconditions.checkState(
+ mFailedScheduledRecordingInfos != null && !mFailedScheduledRecordingInfos.isEmpty(),
+ TAG, "failed scheduled recording is null");
+ }
@Override
public Guidance onCreateGuidance(Bundle savedInstanceState) {
- String title = getResources().getString(R.string.dvr_error_insufficient_space_title);
- String description = getResources()
- .getString(R.string.dvr_error_insufficient_space_description);
+ String title;
+ String description;
+ int failedScheduledRecordingSize = mFailedScheduledRecordingInfos.size();
+ if (failedScheduledRecordingSize == 1) {
+ title = getString(
+ R.string.dvr_error_insufficient_space_title_one_recording,
+ mFailedScheduledRecordingInfos.get(0));
+ description = getString(
+ R.string.dvr_error_insufficient_space_description_one_recording,
+ mFailedScheduledRecordingInfos.get(0));
+ } else if (failedScheduledRecordingSize == 2) {
+ title = getString(
+ R.string.dvr_error_insufficient_space_title_two_recordings,
+ mFailedScheduledRecordingInfos.get(0), mFailedScheduledRecordingInfos.get(1));
+ description = getString(
+ R.string.dvr_error_insufficient_space_description_two_recordings,
+ mFailedScheduledRecordingInfos.get(0), mFailedScheduledRecordingInfos.get(1));
+ } else {
+ title = getString(
+ R.string.dvr_error_insufficient_space_title_three_or_more_recordings,
+ mFailedScheduledRecordingInfos.get(0), mFailedScheduledRecordingInfos.get(1),
+ mFailedScheduledRecordingInfos.get(2));
+ description = getString(
+ R.string.dvr_error_insufficient_space_description_three_or_more_recordings,
+ mFailedScheduledRecordingInfos.get(0), mFailedScheduledRecordingInfos.get(1),
+ mFailedScheduledRecordingInfos.get(2));
+ }
return new Guidance(title, description, null, null);
}
@@ -44,26 +93,21 @@ public class DvrInsufficientSpaceErrorFragment extends DvrGuidedStepFragment {
public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
Activity activity = getActivity();
actions.add(new GuidedAction.Builder(activity)
- .id(ACTION_DONE)
- .title(getResources().getString(R.string.dvr_action_error_done))
+ .clickAction(GuidedAction.ACTION_ID_OK)
.build());
- DvrDataManager dvrDataManager = TvApplication.getSingletons(getContext())
- .getDvrDataManager();
- if (!(dvrDataManager.getRecordedPrograms().isEmpty()
- && dvrDataManager.getStartedRecordings().isEmpty()
- && dvrDataManager.getNonStartedScheduledRecordings().isEmpty()
- && dvrDataManager.getSeriesRecordings().isEmpty())) {
- actions.add(new GuidedAction.Builder(activity)
- .id(ACTION_OPEN_DVR)
- .title(getResources().getString(R.string.dvr_action_error_open_dvr))
- .build());
+ if (TvApplication.getSingletons(getContext()).getDvrManager().hasValidItems()) {
+ actions.add(new GuidedAction.Builder(activity)
+ .id(ACTION_VIEW_RECENT_RECORDINGS)
+ .title(getResources().getString(
+ R.string.dvr_error_insufficient_space_action_view_recent_recordings))
+ .build());
}
}
@Override
public void onGuidedActionClicked(GuidedAction action) {
- if (action.getId() == ACTION_OPEN_DVR) {
- Intent intent = new Intent(getActivity(), DvrActivity.class);
+ if (action.getId() == ACTION_VIEW_RECENT_RECORDINGS) {
+ Intent intent = new Intent(getActivity(), DvrBrowseActivity.class);
getActivity().startActivity(intent);
}
dismissDialog();
diff --git a/src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java b/src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java
index 2e2c2849..8dc9eb4e 100644
--- a/src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrMissingStorageErrorFragment.java
@@ -17,29 +17,27 @@
package com.android.tv.dvr.ui;
import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
import android.os.Bundle;
-import android.support.v17.leanback.app.GuidedStepFragment;
+import android.provider.Settings;
import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
import android.support.v17.leanback.widget.GuidedAction;
-import android.text.TextUtils;
+import android.util.Log;
import com.android.tv.R;
-import com.android.tv.common.SoftPreconditions;
+import com.android.tv.dvr.ui.browse.DvrDetailsActivity;
import java.util.List;
public class DvrMissingStorageErrorFragment extends DvrGuidedStepFragment {
- private static final int ACTION_CANCEL = 1;
- private static final int ACTION_FORGET_STORAGE = 2;
- private String mInputId;
+ private static String TAG = "DvrMissingStorageErrorFragment";
+
+ private static final int ACTION_OK = 1;
+ private static final int ACTION_OPEN_STORAGE_SETTINGS = 2;
@Override
public void onCreate(Bundle savedInstanceState) {
- Bundle args = getArguments();
- if (args != null) {
- mInputId = args.getString(DvrHalfSizedDialogFragment.KEY_INPUT_ID);
- }
- SoftPreconditions.checkArgument(!TextUtils.isEmpty(mInputId));
super.onCreate(savedInstanceState);
}
@@ -55,25 +53,31 @@ public class DvrMissingStorageErrorFragment extends DvrGuidedStepFragment {
public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
Activity activity = getActivity();
actions.add(new GuidedAction.Builder(activity)
- .id(ACTION_CANCEL)
- .title(getResources().getString(R.string.dvr_action_error_cancel))
+ .id(ACTION_OK)
+ .title(android.R.string.ok)
.build());
actions.add(new GuidedAction.Builder(activity)
- .id(ACTION_FORGET_STORAGE)
- .title(getResources().getString(R.string.dvr_action_error_forget_storage))
+ .id(ACTION_OPEN_STORAGE_SETTINGS)
+ .title(getResources().getString(R.string.dvr_action_error_storage_settings))
.build());
}
@Override
public void onGuidedActionClicked(GuidedAction action) {
- if (action.getId() == ACTION_FORGET_STORAGE) {
- DvrForgetStorageErrorFragment fragment = new DvrForgetStorageErrorFragment();
- Bundle args = new Bundle();
- args.putString(DvrHalfSizedDialogFragment.KEY_INPUT_ID, mInputId);
- fragment.setArguments(args);
- GuidedStepFragment.add(getFragmentManager(), fragment, R.id.halfsized_dialog_host);
+ Activity activity = getActivity();
+ if (activity instanceof DvrDetailsActivity) {
+ activity.finish();
+ } else {
+ dismissDialog();
+ }
+ if (action.getId() != ACTION_OPEN_STORAGE_SETTINGS) {
return;
}
- dismissDialog();
+ final Intent intent = new Intent(Settings.ACTION_INTERNAL_STORAGE_SETTINGS);
+ try {
+ getContext().startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ Log.e(TAG, "Can't start internal storage settings activity", e);
+ }
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/PrioritySettingsFragment.java b/src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java
index 158bd824..562898a3 100644
--- a/src/com/android/tv/dvr/ui/PrioritySettingsFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrPrioritySettingsFragment.java
@@ -33,7 +33,7 @@ import com.android.tv.TvApplication;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.DvrScheduleManager;
-import com.android.tv.dvr.SeriesRecording;
+import com.android.tv.dvr.data.SeriesRecording;
import java.util.ArrayList;
import java.util.List;
@@ -41,7 +41,7 @@ import java.util.List;
/**
* Fragment for DVR series recording settings.
*/
-public class PrioritySettingsFragment extends GuidedStepFragment {
+public class DvrPrioritySettingsFragment extends GuidedStepFragment {
/**
* Name of series recording id starting the fragment.
* Type: Long
@@ -162,7 +162,6 @@ public class PrioritySettingsFragment extends GuidedStepFragment {
return;
}
if (action.getId() < 0) {
- int selectedPosition = mSeriesRecordings.indexOf(mSelectedRecording);
mSelectedRecording = null;
for (int i = 0; i < mSeriesRecordings.size(); ++i) {
updateItem(i);
@@ -248,4 +247,4 @@ public class PrioritySettingsFragment extends GuidedStepFragment {
titleView.setTypeface(titleView.getTypeface(), Typeface.NORMAL);
}
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/DvrScheduleFragment.java b/src/com/android/tv/dvr/ui/DvrScheduleFragment.java
index da6d1637..d6008315 100644
--- a/src/com/android/tv/dvr/ui/DvrScheduleFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrScheduleFragment.java
@@ -32,9 +32,8 @@ import com.android.tv.TvApplication;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.Program;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.DvrUiHelper;
-import com.android.tv.dvr.ScheduledRecording;
-import com.android.tv.dvr.SeriesRecording;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.dvr.ui.DvrConflictFragment.DvrProgramConflictFragment;
import com.android.tv.util.Utils;
@@ -48,18 +47,26 @@ import java.util.List;
*/
@TargetApi(Build.VERSION_CODES.N)
public class DvrScheduleFragment extends DvrGuidedStepFragment {
+ /**
+ * Key for the whether to add the current program to series.
+ * Type: boolean
+ */
+ public static final String KEY_ADD_CURRENT_PROGRAM_TO_SERIES = "add_current_program_to_series";
+
private static final String TAG = "DvrScheduleFragment";
private static final int ACTION_RECORD_EPISODE = 1;
private static final int ACTION_RECORD_SERIES = 2;
private Program mProgram;
+ private boolean mAddCurrentProgramToSeries;
@Override
public void onCreate(Bundle savedInstanceState) {
Bundle args = getArguments();
if (args != null) {
mProgram = args.getParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM);
+ mAddCurrentProgramToSeries = args.getBoolean(KEY_ADD_CURRENT_PROGRAM_TO_SERIES, false);
}
DvrManager dvrManager = TvApplication.getSingletons(getContext()).getDvrManager();
SoftPreconditions.checkArgument(mProgram != null && mProgram.isEpisodic(), TAG,
@@ -139,8 +146,10 @@ public class DvrScheduleFragment extends DvrGuidedStepFragment {
.build();
getDvrManager().updateSeriesRecording(seriesRecording);
}
+
DvrUiHelper.startSeriesSettingsActivity(getContext(),
- seriesRecording.getId(), null, true, true, true);
+ seriesRecording.getId(), null, true, true, true,
+ mAddCurrentProgramToSeries ? mProgram : null);
dismissDialog();
}
}
diff --git a/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java b/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java
index f57e4b05..667af34a 100644
--- a/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java
+++ b/src/com/android/tv/dvr/ui/DvrSeriesDeletionActivity.java
@@ -22,9 +22,6 @@ import android.support.v17.leanback.app.GuidedStepFragment;
import com.android.tv.R;
import com.android.tv.TvApplication;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.dvr.ui.SeriesDeletionFragment;
-import com.android.tv.ui.sidepanel.SettingsFragment;
/**
* Activity to show details view in DVR.
@@ -42,7 +39,7 @@ public class DvrSeriesDeletionActivity extends Activity {
setContentView(R.layout.activity_dvr_series_settings);
// Check savedInstanceState to prevent that activity is being showed with animation.
if (savedInstanceState == null) {
- SeriesDeletionFragment deletionFragment = new SeriesDeletionFragment();
+ DvrSeriesDeletionFragment deletionFragment = new DvrSeriesDeletionFragment();
deletionFragment.setArguments(getIntent().getExtras());
GuidedStepFragment.addAsRoot(this, deletionFragment, R.id.dvr_settings_view_frame);
}
diff --git a/src/com/android/tv/dvr/ui/SeriesDeletionFragment.java b/src/com/android/tv/dvr/ui/DvrSeriesDeletionFragment.java
index 36e3cfc1..8bf8560f 100644
--- a/src/com/android/tv/dvr/ui/SeriesDeletionFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrSeriesDeletionFragment.java
@@ -33,9 +33,10 @@ import com.android.tv.common.SoftPreconditions;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.DvrWatchedPositionManager;
-import com.android.tv.dvr.RecordedProgram;
-import com.android.tv.dvr.SeriesRecording;
+import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.ui.GuidedActionsStylistWithDivider;
+import com.android.tv.util.Utils;
import java.util.ArrayList;
import java.util.Collections;
@@ -47,7 +48,7 @@ import java.util.concurrent.TimeUnit;
/**
* Fragment for DVR series recording settings.
*/
-public class SeriesDeletionFragment extends GuidedStepFragment {
+public class DvrSeriesDeletionFragment extends GuidedStepFragment {
private static final long WATCHED_TIME_UNIT_THRESHOLD = TimeUnit.MINUTES.toMillis(2);
// Since recordings' IDs are used as its check actions' IDs, which are random positive numbers,
@@ -218,8 +219,8 @@ public class SeriesDeletionFragment extends GuidedStepFragment {
private String getWatchedString(long watchedPositionMs, long durationMs) {
if (durationMs > WATCHED_TIME_UNIT_THRESHOLD) {
return getResources().getString(R.string.dvr_series_watched_info_minutes,
- Math.max(1, TimeUnit.MILLISECONDS.toMinutes(watchedPositionMs)),
- TimeUnit.MILLISECONDS.toMinutes(durationMs));
+ Math.max(1, Utils.getRoundOffMinsFromMs(watchedPositionMs)),
+ Utils.getRoundOffMinsFromMs(durationMs));
} else {
return getResources().getString(R.string.dvr_series_watched_info_seconds,
Math.max(1, TimeUnit.MILLISECONDS.toSeconds(watchedPositionMs)),
diff --git a/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java b/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java
index 1173df46..8f880f16 100644
--- a/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrSeriesScheduledFragment.java
@@ -25,22 +25,29 @@ import android.support.v17.leanback.widget.GuidedAction;
import com.android.tv.R;
import com.android.tv.TvApplication;
-import com.android.tv.dvr.DvrDataManager;
+import com.android.tv.data.Program;
import com.android.tv.dvr.DvrScheduleManager;
-import com.android.tv.dvr.ScheduledRecording;
-import com.android.tv.dvr.SeriesRecording;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.data.SeriesRecording;
+import com.android.tv.dvr.ui.list.DvrSchedulesActivity;
import com.android.tv.dvr.ui.list.DvrSeriesSchedulesFragment;
import java.util.List;
public class DvrSeriesScheduledFragment extends DvrGuidedStepFragment {
+ /**
+ * The key for program list which will be passed to {@link DvrSeriesSchedulesFragment}.
+ * Type: List<{@link Program}>
+ */
+ public static final String SERIES_SCHEDULED_KEY_PROGRAMS = "series_scheduled_key_programs";
+
private final static long SERIES_RECORDING_ID_NOT_SET = -1;
private final static int ACTION_VIEW_SCHEDULES = 1;
- private DvrScheduleManager mDvrScheduleManager;
private SeriesRecording mSeriesRecording;
private boolean mShowViewScheduleOption;
+ private List<Program> mPrograms;
private int mSchedulesAddedCount = 0;
private boolean mHasConflict = false;
@@ -58,22 +65,25 @@ public class DvrSeriesScheduledFragment extends DvrGuidedStepFragment {
}
mShowViewScheduleOption = getArguments().getBoolean(
DvrSeriesScheduledDialogActivity.SHOW_VIEW_SCHEDULE_OPTION);
- mDvrScheduleManager = TvApplication.getSingletons(context).getDvrScheduleManager();
mSeriesRecording = TvApplication.getSingletons(context).getDvrDataManager()
.getSeriesRecording(seriesRecordingId);
if (mSeriesRecording == null) {
getActivity().finish();
return;
}
+ mPrograms = (List<Program>) BigArguments.getArgument(SERIES_SCHEDULED_KEY_PROGRAMS);
+ BigArguments.reset();
mSchedulesAddedCount = TvApplication.getSingletons(getContext()).getDvrManager()
.getAvailableScheduledRecording(mSeriesRecording.getId()).size();
+ DvrScheduleManager dvrScheduleManager =
+ TvApplication.getSingletons(context).getDvrScheduleManager();
List<ScheduledRecording> conflictingRecordings =
- mDvrScheduleManager.getConflictingSchedules(mSeriesRecording);
+ dvrScheduleManager.getConflictingSchedules(mSeriesRecording);
mHasConflict = !conflictingRecordings.isEmpty();
for (ScheduledRecording recording : conflictingRecordings) {
if (recording.getSeriesRecordingId() == mSeriesRecording.getId()) {
++mInThisSeriesConflictCount;
- } else {
+ } else if (recording.getPriority() < mSeriesRecording.getPriority()) {
++mOutThisSeriesConflictCount;
}
}
@@ -113,6 +123,9 @@ public class DvrSeriesScheduledFragment extends DvrGuidedStepFragment {
.TYPE_SERIES_SCHEDULE);
intent.putExtra(DvrSeriesSchedulesFragment.SERIES_SCHEDULES_KEY_SERIES_RECORDING,
mSeriesRecording);
+ BigArguments.reset();
+ BigArguments.setArgument(DvrSeriesSchedulesFragment
+ .SERIES_SCHEDULES_KEY_SERIES_PROGRAMS, mPrograms);
startActivity(intent);
}
getActivity().finish();
@@ -121,30 +134,30 @@ public class DvrSeriesScheduledFragment extends DvrGuidedStepFragment {
private String getDescription() {
if (!mHasConflict) {
return getResources().getQuantityString(
- R.plurals.dvr_series_recording_scheduled_no_conflict, mSchedulesAddedCount,
+ R.plurals.dvr_series_scheduled_no_conflict, mSchedulesAddedCount,
mSchedulesAddedCount, mSeriesRecording.getTitle());
} else {
// mInThisSeriesConflictCount equals 0 and mOutThisSeriesConflictCount equals 0 means
// mHasConflict is false. So we don't need to check that case.
if (mInThisSeriesConflictCount != 0 && mOutThisSeriesConflictCount != 0) {
- return getResources().getQuantityString(R.plurals
- .dvr_series_recording_scheduled_this_and_other_series_conflict,
+ return getResources().getQuantityString(
+ R.plurals.dvr_series_scheduled_this_and_other_series_conflict,
mSchedulesAddedCount, mSchedulesAddedCount, mSeriesRecording.getTitle(),
mInThisSeriesConflictCount + mOutThisSeriesConflictCount);
} else if (mInThisSeriesConflictCount != 0) {
- return getResources().getQuantityString(R.plurals
- .dvr_series_recording_scheduled_only_this_series_conflict,
+ return getResources().getQuantityString(
+ R.plurals.dvr_series_recording_scheduled_only_this_series_conflict,
mSchedulesAddedCount, mSchedulesAddedCount, mSeriesRecording.getTitle(),
mInThisSeriesConflictCount);
} else {
if (mOutThisSeriesConflictCount == 1) {
- return getResources().getQuantityString(R.plurals
- .dvr_series_recording_scheduled_only_other_series_one_conflict,
+ return getResources().getQuantityString(
+ R.plurals.dvr_series_scheduled_only_other_series_one_conflict,
mSchedulesAddedCount, mSchedulesAddedCount,
mSeriesRecording.getTitle());
} else {
- return getResources().getQuantityString(R.plurals
- .dvr_series_recording_scheduled_only_other_series_conflict,
+ return getResources().getQuantityString(
+ R.plurals.dvr_series_scheduled_only_other_series_many_conflicts,
mSchedulesAddedCount, mSchedulesAddedCount, mSeriesRecording.getTitle(),
mOutThisSeriesConflictCount);
}
diff --git a/src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java b/src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java
index 3f7671b3..6dd20b3a 100644
--- a/src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java
+++ b/src/com/android/tv/dvr/ui/DvrSeriesSettingsActivity.java
@@ -17,7 +17,6 @@
package com.android.tv.dvr.ui;
import android.app.Activity;
-import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.support.v17.leanback.app.GuidedStepFragment;
@@ -38,25 +37,34 @@ public class DvrSeriesSettingsActivity extends Activity {
/**
* Name of the boolean flag to decide if the series recording with empty schedule and recording
* will be removed.
+ * Type: boolean
*/
public static final String REMOVE_EMPTY_SERIES_RECORDING = "remove_empty_series_recording";
/**
* Name of the boolean flag to decide if the setting fragment should be translucent.
+ * Type: boolean
*/
public static final String IS_WINDOW_TRANSLUCENT = "windows_translucent";
/**
- * Name of the channel id list. If the channel list is given, we show the channels
- * from the values in channel option.
- * Type: Long array
+ * Name of the program list. The list contains the programs which belong to the series.
+ * Type: List<{@link com.android.tv.data.Program}>
*/
- public static final String CHANNEL_ID_LIST = "channel_id_list";
+ public static final String PROGRAM_LIST = "program_list";
/**
* Name of the boolean flag to check if the confirm dialog should show view schedule option.
+ * Type: boolean
*/
public static final String SHOW_VIEW_SCHEDULE_OPTION_IN_DIALOG =
"show_view_schedule_option_in_dialog";
+ /**
+ * Name of the current program added to series. The current program will be recorded only when
+ * the series recording is initialized from media controller. But for other case, the current
+ * program won't be recorded.
+ */
+ public static final String CURRENT_PROGRAM = "current_program";
+
@Override
public void onCreate(Bundle savedInstanceState) {
TvApplication.setCurrentRunningProcess(this, true);
@@ -66,7 +74,7 @@ public class DvrSeriesSettingsActivity extends Activity {
SoftPreconditions.checkArgument(seriesRecordingId != -1);
if (savedInstanceState == null) {
- SeriesSettingsFragment settingFragment = new SeriesSettingsFragment();
+ DvrSeriesSettingsFragment settingFragment = new DvrSeriesSettingsFragment();
settingFragment.setArguments(getIntent().getExtras());
GuidedStepFragment.addAsRoot(this, settingFragment, R.id.dvr_settings_view_frame);
}
diff --git a/src/com/android/tv/dvr/ui/SeriesSettingsFragment.java b/src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java
index 6c05c9c6..f28382da 100644
--- a/src/com/android/tv/dvr/ui/SeriesSettingsFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java
@@ -17,19 +17,13 @@
package com.android.tv.dvr.ui;
import android.app.FragmentManager;
-import android.app.ProgressDialog;
import android.content.Context;
import android.os.Bundle;
import android.support.v17.leanback.app.GuidedStepFragment;
import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
import android.support.v17.leanback.widget.GuidedAction;
import android.support.v17.leanback.widget.GuidedActionsStylist;
-import android.util.Log;
import android.util.LongSparseArray;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import android.widget.ProgressBar;
import com.android.tv.R;
import com.android.tv.TvApplication;
@@ -38,14 +32,14 @@ import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.Program;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.DvrUiHelper;
-import com.android.tv.dvr.EpisodicProgramLoadTask;
-import com.android.tv.dvr.SeriesRecording;
-import com.android.tv.dvr.SeriesRecording.ChannelOption;
-import com.android.tv.dvr.SeriesRecordingScheduler;
-import com.android.tv.dvr.SeriesRecordingScheduler.OnSeriesRecordingUpdatedListener;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.data.SeasonEpisodeNumber;
+import com.android.tv.dvr.data.SeriesRecording;
+import com.android.tv.dvr.data.SeriesRecording.ChannelOption;
+import com.android.tv.dvr.recorder.SeriesRecordingScheduler;
+
import java.util.ArrayList;
-import java.util.Comparator;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -53,7 +47,7 @@ import java.util.Set;
/**
* Fragment for DVR series recording settings.
*/
-public class SeriesSettingsFragment extends GuidedStepFragment
+public class DvrSeriesSettingsFragment extends GuidedStepFragment
implements DvrDataManager.SeriesRecordingListener {
private static final String TAG = "SeriesSettingsFragment";
private static final boolean DEBUG = false;
@@ -66,15 +60,13 @@ public class SeriesSettingsFragment extends GuidedStepFragment
private static final long SUB_ACTION_ID_CHANNEL_ONE_BASE = 500;
private DvrDataManager mDvrDataManager;
- private ChannelDataManager mChannelDataManager;
- private DvrManager mDvrManager;
private SeriesRecording mSeriesRecording;
private long mSeriesRecordingId;
@ChannelOption int mChannelOption;
- private Comparator<Channel> mChannelComparator;
private long mSelectedChannelId;
private int mBackStackCount;
private boolean mShowViewScheduleOptionInDialog;
+ private Program mCurrentProgram;
private String mFragmentTitle;
private String mProrityActionTitle;
@@ -84,7 +76,7 @@ public class SeriesSettingsFragment extends GuidedStepFragment
private String mChannelsActionAllText;
private LongSparseArray<Channel> mId2Channel = new LongSparseArray<>();
private List<Channel> mChannels = new ArrayList<>();
- private EpisodicProgramLoadTask mEpisodicProgramLoadTask;
+ private List<Program> mPrograms;
private GuidedAction mPriorityGuidedAction;
private GuidedAction mChannelsGuidedAction;
@@ -100,22 +92,24 @@ public class SeriesSettingsFragment extends GuidedStepFragment
getActivity().finish();
return;
}
- mDvrManager = TvApplication.getSingletons(context).getDvrManager();
mShowViewScheduleOptionInDialog = getArguments().getBoolean(
DvrSeriesSettingsActivity.SHOW_VIEW_SCHEDULE_OPTION_IN_DIALOG);
+ mCurrentProgram = getArguments().getParcelable(DvrSeriesSettingsActivity.CURRENT_PROGRAM);
mDvrDataManager.addSeriesRecordingListener(this);
- long[] channelIds = getArguments().getLongArray(DvrSeriesSettingsActivity.CHANNEL_ID_LIST);
- mChannelDataManager = TvApplication.getSingletons(context).getChannelDataManager();
- if (channelIds == null) {
- Channel channel = mChannelDataManager.getChannel(mSeriesRecording.getChannelId());
- if (channel != null) {
- mId2Channel.put(channel.getId(), channel);
- mChannels.add(channel);
- }
- collectChannelsInBackground();
- } else {
- for (long channelId : channelIds) {
- Channel channel = mChannelDataManager.getChannel(channelId);
+ mPrograms = (List<Program>) BigArguments.getArgument(
+ DvrSeriesSettingsActivity.PROGRAM_LIST);
+ BigArguments.reset();
+ if (mPrograms == null) {
+ getActivity().finish();
+ return;
+ }
+ Set<Long> channelIds = new HashSet<>();
+ ChannelDataManager channelDataManager =
+ TvApplication.getSingletons(context).getChannelDataManager();
+ for (Program program : mPrograms) {
+ long channelId = program.getChannelId();
+ if (channelIds.add(channelId)) {
+ Channel channel = channelDataManager.getChannel(channelId);
if (channel != null) {
mId2Channel.put(channel.getId(), channel);
mChannels.add(channel);
@@ -125,16 +119,14 @@ public class SeriesSettingsFragment extends GuidedStepFragment
mChannelOption = mSeriesRecording.getChannelOption();
mSelectedChannelId = Channel.INVALID_ID;
if (mChannelOption == SeriesRecording.OPTION_CHANNEL_ONE) {
- Channel channel = mChannelDataManager.getChannel(mSeriesRecording.getChannelId());
+ Channel channel = channelDataManager.getChannel(mSeriesRecording.getChannelId());
if (channel != null) {
mSelectedChannelId = channel.getId();
} else {
mChannelOption = SeriesRecording.OPTION_CHANNEL_ALL;
}
}
- mChannelComparator = new Channel.DefaultComparator(context,
- TvApplication.getSingletons(context).getTvInputManagerHelper());
- mChannels.sort(mChannelComparator);
+ mChannels.sort(Channel.CHANNEL_NUMBER_COMPARATOR);
mFragmentTitle = getString(R.string.dvr_series_settings_title);
mProrityActionTitle = getString(R.string.dvr_series_settings_priority);
mProrityActionHighestText = getString(R.string.dvr_series_settings_priority_highest);
@@ -144,23 +136,23 @@ public class SeriesSettingsFragment extends GuidedStepFragment
}
@Override
+ public void onResume() {
+ super.onResume();
+ // To avoid the order of series's priority has changed, but series doesn't get update.
+ updatePriorityGuidedAction();
+ }
+
+ @Override
public void onDetach() {
super.onDetach();
mDvrDataManager.removeSeriesRecordingListener(this);
- if (mEpisodicProgramLoadTask != null) {
- mEpisodicProgramLoadTask.cancel(true);
- mEpisodicProgramLoadTask = null;
- }
}
@Override
public void onDestroy() {
- DvrManager dvrManager = TvApplication.getSingletons(getActivity()).getDvrManager();
- if (getFragmentManager().getBackStackEntryCount() == mBackStackCount
- && getArguments()
- .getBoolean(DvrSeriesSettingsActivity.REMOVE_EMPTY_SERIES_RECORDING)
- && dvrManager.canRemoveSeriesRecording(mSeriesRecordingId)) {
- dvrManager.removeSeriesRecording(mSeriesRecordingId);
+ if (getFragmentManager().getBackStackEntryCount() == mBackStackCount && getArguments()
+ .getBoolean(DvrSeriesSettingsActivity.REMOVE_EMPTY_SERIES_RECORDING)) {
+ mDvrDataManager.checkAndRemoveEmptySeriesRecording(mSeriesRecordingId);
}
super.onDestroy();
}
@@ -178,7 +170,6 @@ public class SeriesSettingsFragment extends GuidedStepFragment
.id(ACTION_ID_PRIORITY)
.title(mProrityActionTitle)
.build();
- updatePriorityGuidedAction(false);
actions.add(mPriorityGuidedAction);
mChannelsGuidedAction = new GuidedAction.Builder(getActivity())
@@ -204,10 +195,6 @@ public class SeriesSettingsFragment extends GuidedStepFragment
public void onGuidedActionClicked(GuidedAction action) {
long actionId = action.getId();
if (actionId == GuidedAction.ACTION_ID_OK) {
- if (mEpisodicProgramLoadTask != null) {
- mEpisodicProgramLoadTask.cancel(true);
- mEpisodicProgramLoadTask = null;
- }
if (mChannelOption != mSeriesRecording.getChannelOption()
|| mSeriesRecording.isStopped()
|| (mChannelOption == SeriesRecording.OPTION_CHANNEL_ONE
@@ -218,28 +205,14 @@ public class SeriesSettingsFragment extends GuidedStepFragment
if (mSelectedChannelId != Channel.INVALID_ID) {
builder.setChannelId(mSelectedChannelId);
}
- TvApplication.getSingletons(getContext()).getDvrManager()
- .updateSeriesRecording(builder.build());
- SeriesRecordingScheduler scheduler =
- SeriesRecordingScheduler.getInstance(getContext());
- // Since dialog is used even after the fragment is closed, we should
- // use application context.
- ProgressDialog dialog = ProgressDialog.show(getContext(), null, getString(
- R.string.dvr_series_schedules_progress_message_updating_programs));
- scheduler.addOnSeriesRecordingUpdatedListener(
- new OnSeriesRecordingUpdatedListener() {
- @Override
- public void onSeriesRecordingUpdated(SeriesRecording... seriesRecordings) {
- for (SeriesRecording seriesRecording : seriesRecordings) {
- if (seriesRecording.getId() == mSeriesRecordingId) {
- dialog.dismiss();
- scheduler.removeOnSeriesRecordingUpdatedListener(this);
- showConfirmDialog();
- return;
- }
- }
- }
- });
+ DvrManager dvrManager = TvApplication.getSingletons(getContext()).getDvrManager();
+ dvrManager.updateSeriesRecording(builder.build());
+ if (mCurrentProgram != null && (mChannelOption == SeriesRecording.OPTION_CHANNEL_ALL
+ || mSelectedChannelId == mCurrentProgram.getChannelId())) {
+ dvrManager.addSchedule(mCurrentProgram);
+ }
+ updateSchedulesToSeries();
+ showConfirmDialog();
} else {
showConfirmDialog();
}
@@ -247,9 +220,9 @@ public class SeriesSettingsFragment extends GuidedStepFragment
finishGuidedStepFragments();
} else if (actionId == ACTION_ID_PRIORITY) {
FragmentManager fragmentManager = getFragmentManager();
- PrioritySettingsFragment fragment = new PrioritySettingsFragment();
+ DvrPrioritySettingsFragment fragment = new DvrPrioritySettingsFragment();
Bundle args = new Bundle();
- args.putLong(PrioritySettingsFragment.COME_FROM_SERIES_RECORDING_ID,
+ args.putLong(DvrPrioritySettingsFragment.COME_FROM_SERIES_RECORDING_ID,
mSeriesRecording.getId());
fragment.setArguments(args);
GuidedStepFragment.add(fragmentManager, fragment, R.id.dvr_settings_view_frame);
@@ -281,7 +254,7 @@ public class SeriesSettingsFragment extends GuidedStepFragment
private void updateChannelsGuidedAction(boolean notifyActionChanged) {
if (mChannelOption == SeriesRecording.OPTION_CHANNEL_ALL) {
mChannelsGuidedAction.setDescription(mChannelsActionAllText);
- } else {
+ } else if (mId2Channel.get(mSelectedChannelId) != null){
mChannelsGuidedAction.setDescription(mId2Channel.get(mSelectedChannelId)
.getDisplayText());
}
@@ -290,7 +263,7 @@ public class SeriesSettingsFragment extends GuidedStepFragment
}
}
- private void updatePriorityGuidedAction(boolean notifyActionChanged) {
+ private void updatePriorityGuidedAction() {
int totalSeriesCount = 0;
int priorityOrder = 0;
for (SeriesRecording seriesRecording : mDvrDataManager.getSeriesRecordings()) {
@@ -312,49 +285,38 @@ public class SeriesSettingsFragment extends GuidedStepFragment
mPriorityGuidedAction.setDescription(getString(
R.string.dvr_series_settings_priority_rank, priorityOrder + 1));
}
- if (notifyActionChanged) {
- notifyActionChanged(findActionPositionById(ACTION_ID_PRIORITY));
- }
+ notifyActionChanged(findActionPositionById(ACTION_ID_PRIORITY));
}
- private void collectChannelsInBackground() {
- if (mEpisodicProgramLoadTask != null) {
- mEpisodicProgramLoadTask.cancel(true);
+ private void updateSchedulesToSeries() {
+ List<Program> recordingCandidates = new ArrayList<>();
+ Set<SeasonEpisodeNumber> scheduledEpisodes = new HashSet<>();
+ for (ScheduledRecording r : mDvrDataManager.getScheduledRecordings(mSeriesRecordingId)) {
+ if (r.getState() != ScheduledRecording.STATE_RECORDING_FAILED
+ && r.getState() != ScheduledRecording.STATE_RECORDING_CLIPPED) {
+ scheduledEpisodes.add(new SeasonEpisodeNumber(
+ r.getSeriesRecordingId(), r.getSeasonNumber(), r.getEpisodeNumber()));
+ }
}
- mEpisodicProgramLoadTask = new EpisodicProgramLoadTask(getContext(), mSeriesRecording) {
- @Override
- protected void onPostExecute(List<Program> programs) {
- mEpisodicProgramLoadTask = null;
- Set<Long> channelIds = new HashSet<>();
- for (Program program : programs) {
- channelIds.add(program.getChannelId());
- }
- boolean channelAdded = false;
- for (Long channelId : channelIds) {
- if (mId2Channel.get(channelId) != null) {
- continue;
- }
- Channel channel = mChannelDataManager.getChannel(channelId);
- if (channel != null) {
- channelAdded = true;
- mId2Channel.put(channelId, channel);
- mChannels.add(channel);
- if (DEBUG) Log.d(TAG, "Added channel: " + channel);
- }
- }
- if (!channelAdded) {
- return;
- }
- mChannels.sort(mChannelComparator);
- mChannelsGuidedAction.setSubActions(buildChannelSubAction());
- notifyActionChanged(findActionPositionById(ACTION_ID_CHANNEL));
- if (DEBUG) Log.d(TAG, "Complete EpisodicProgramLoadTask");
+ for (Program program : mPrograms) {
+ // Removes current programs and scheduled episodes out, matches the channel option.
+ if (program.getStartTimeUtcMillis() >= System.currentTimeMillis()
+ && mSeriesRecording.matchProgram(program)
+ && !scheduledEpisodes.contains(new SeasonEpisodeNumber(
+ mSeriesRecordingId, program.getSeasonNumber(), program.getEpisodeNumber()))) {
+ recordingCandidates.add(program);
}
- }.setLoadCurrentProgram(true)
- .setLoadDisallowedProgram(true)
- .setLoadScheduledEpisode(true)
- .setIgnoreChannelOption(true);
- mEpisodicProgramLoadTask.execute();
+ }
+ if (recordingCandidates.isEmpty()) {
+ return;
+ }
+ List<Program> programsToSchedule = SeriesRecordingScheduler.pickOneProgramPerEpisode(
+ mDvrDataManager, Collections.singletonList(mSeriesRecording), recordingCandidates)
+ .get(mSeriesRecordingId);
+ if (!programsToSchedule.isEmpty()) {
+ TvApplication.getSingletons(getContext()).getDvrManager()
+ .addScheduleToSeriesRecording(mSeriesRecording, programsToSchedule);
+ }
}
private List<GuidedAction> buildChannelSubAction() {
@@ -373,8 +335,8 @@ public class SeriesSettingsFragment extends GuidedStepFragment
}
private void showConfirmDialog() {
- DvrUiHelper.StartSeriesScheduledDialogActivity(
- getContext(), mSeriesRecording, mShowViewScheduleOptionInDialog);
+ DvrUiHelper.StartSeriesScheduledDialogActivity(getContext(), mSeriesRecording,
+ mShowViewScheduleOptionInDialog, mPrograms);
finishGuidedStepFragments();
}
@@ -382,16 +344,23 @@ public class SeriesSettingsFragment extends GuidedStepFragment
public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) { }
@Override
- public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) { }
+ public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) {
+ for (SeriesRecording series : seriesRecordings) {
+ if (series.getId() == mSeriesRecording.getId()) {
+ finishGuidedStepFragments();
+ return;
+ }
+ }
+ }
@Override
public void onSeriesRecordingChanged(SeriesRecording... seriesRecordings) {
for (SeriesRecording seriesRecording : seriesRecordings) {
if (seriesRecording.getId() == mSeriesRecordingId) {
mSeriesRecording = seriesRecording;
- updatePriorityGuidedAction(true);
+ updatePriorityGuidedAction();
return;
}
}
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java b/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java
index c3867886..b476fff7 100644
--- a/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrStopRecordingFragment.java
@@ -33,7 +33,7 @@ import com.android.tv.data.Channel;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
-import com.android.tv.dvr.ScheduledRecording;
+import com.android.tv.dvr.data.ScheduledRecording;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -131,15 +131,8 @@ public class DvrStopRecordingFragment extends DvrGuidedStepFragment {
String title = getString(R.string.dvr_stop_recording_dialog_title);
String description;
if (mStopReason == REASON_ON_CONFLICT) {
- String programTitle = mSchedule.getProgramTitle();
- if (TextUtils.isEmpty(programTitle)) {
- ChannelDataManager channelDataManager =
- TvApplication.getSingletons(getActivity()).getChannelDataManager();
- Channel channel = channelDataManager.getChannel(mSchedule.getChannelId());
- programTitle = channel.getDisplayName();
- }
description = getString(R.string.dvr_stop_recording_dialog_description_on_conflict,
- mSchedule.getProgramTitle());
+ mSchedule.getProgramDisplayTitle(getContext()));
} else {
description = getString(R.string.dvr_stop_recording_dialog_description);
}
diff --git a/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java b/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java
index feaa2357..fe3a4a60 100644
--- a/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java
+++ b/src/com/android/tv/dvr/ui/DvrStopSeriesRecordingFragment.java
@@ -31,8 +31,8 @@ import com.android.tv.R;
import com.android.tv.TvApplication;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.ScheduledRecording;
-import com.android.tv.dvr.SeriesRecording;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.data.SeriesRecording;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/com/android/tv/dvr/DvrUiHelper.java b/src/com/android/tv/dvr/ui/DvrUiHelper.java
index c0d3b0c5..507db6e7 100644
--- a/src/com/android/tv/dvr/DvrUiHelper.java
+++ b/src/com/android/tv/dvr/ui/DvrUiHelper.java
@@ -14,19 +14,21 @@
* limitations under the License.
*/
-package com.android.tv.dvr;
+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.TextUtils;
import android.widget.ImageView;
import android.widget.Toast;
@@ -36,32 +38,36 @@ 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.ui.DvrDetailsActivity;
-import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment;
+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.DvrSchedulesActivity;
-import com.android.tv.dvr.ui.DvrSeriesDeletionActivity;
-import com.android.tv.dvr.ui.DvrSeriesScheduledDialogActivity;
-import com.android.tv.dvr.ui.DvrSeriesSettingsActivity;
-import com.android.tv.dvr.ui.DvrStopRecordingFragment;
-import com.android.tv.dvr.ui.DvrStopSeriesRecordingDialogFragment;
-import com.android.tv.dvr.ui.DvrStopSeriesRecordingFragment;
-import com.android.tv.dvr.ui.HalfSizedDialogFragment;
+import com.android.tv.dvr.ui.browse.DvrBrowseActivity;
+import com.android.tv.dvr.ui.browse.DvrDetailsActivity;
+import com.android.tv.dvr.ui.list.DvrSchedulesActivity;
import com.android.tv.dvr.ui.list.DvrSchedulesFragment;
import com.android.tv.dvr.ui.list.DvrSeriesSchedulesFragment;
+import com.android.tv.util.ToastUtils;
import com.android.tv.util.Utils;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Set;
/**
* A helper class for DVR UI.
@@ -69,94 +75,54 @@ import java.util.List;
@MainThread
@TargetApi(Build.VERSION_CODES.N)
public class DvrUiHelper {
- /**
- * 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}.
- */
- public static boolean handleCreateSchedule(MainActivity activity, Program program) {
- 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 {
- SeriesRecording seriesRecording = dvrManager.getSeriesRecording(program);
- if (seriesRecording == null || seriesRecording.isStopped()) {
- DvrUiHelper.showScheduleDialog(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;
- }
- // Just add the schedule.
- dvrManager.addSchedule(program);
- }
- }
- return true;
+ private static String TAG = "DvrUiHelper";
- }
+ private static ProgressDialog sProgressDialog = null;
/**
* Checks if the storage status is good for recording and shows error messages if needed.
*
- * @return true if the storage status is fine to be recorded for {@code inputId}.
+ * @param recordingRequestRunnable if the storage status is OK to record or users choose to
+ * perform the operation anyway, this Runnable will run.
*/
- public static boolean checkStorageStatusAndShowErrorMessage(Activity activity, String inputId) {
- if (!Utils.isBundledInput(inputId)) {
- return true;
- }
- DvrStorageStatusManager dvrStorageStatusManager =
- TvApplication.getSingletons(activity).getDvrStorageStatusManager();
- int status = dvrStorageStatusManager.getDvrStorageStatus();
- if (status == DvrStorageStatusManager.STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL) {
- showDvrSmallSizedStorageErrorDialog(activity);
- return false;
- } else if (status == DvrStorageStatusManager.STORAGE_STATUS_MISSING) {
- showDvrMissingStorageErrorDialog(activity, inputId);
- return false;
- } else if (status == DvrStorageStatusManager.STORAGE_STATUS_FREE_SPACE_INSUFFICIENT) {
- // TODO: handle insufficient storage case.
- return true;
- } else {
- return true;
+ 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(MainActivity activity, Program program) {
+ 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(MainActivity activity, Channel channel) {
+ public static void showChannelRecordDurationOptions(Activity activity, Channel channel) {
if (SoftPreconditions.checkNotNull(channel) == null) {
return;
}
@@ -168,7 +134,7 @@ public class DvrUiHelper {
/**
* Shows the dialog which says that the new schedule conflicts with others.
*/
- public static void showScheduleConflictDialog(MainActivity activity, Program program) {
+ public static void showScheduleConflictDialog(Activity activity, Program program) {
if (program == null) {
return;
}
@@ -192,20 +158,47 @@ public class DvrUiHelper {
/**
* Shows DVR insufficient space error dialog.
*/
- public static void showDvrInsufficientSpaceErrorDialog(MainActivity activity) {
- showDialogFragment(activity, new DvrInsufficientSpaceErrorDialogFragment(), null);
+ 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, String inputId) {
- SoftPreconditions.checkArgument(!TextUtils.isEmpty(inputId));
- Bundle args = new Bundle();
- args.putString(DvrHalfSizedDialogFragment.KEY_INPUT_ID, inputId);
- showDialogFragment(activity, new DvrMissingStorageErrorDialogFragment(), args);
+ private static void showDvrMissingStorageErrorDialog(Activity activity) {
+ showDialogFragment(activity, new DvrMissingStorageErrorDialogFragment(), null);
}
/**
@@ -231,7 +224,7 @@ public class DvrUiHelper {
/**
* Shows "already scheduled" dialog.
*/
- public static void showAlreadyScheduleDialog(MainActivity activity, Program program) {
+ public static void showAlreadyScheduleDialog(Activity activity, Program program) {
if (program == null) {
return;
}
@@ -243,7 +236,7 @@ public class DvrUiHelper {
/**
* Shows "already recorded" dialog.
*/
- public static void showAlreadyRecordedDialog(MainActivity activity, Program program) {
+ public static void showAlreadyRecordedDialog(Activity activity, Program program) {
if (program == null) {
return;
}
@@ -252,6 +245,87 @@ public class DvrUiHelper {
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);
@@ -341,19 +415,66 @@ public class DvrUiHelper {
/**
* Shows the series settings activity.
*
- * @param channelIds Channel ID list which has programs belonging to the series.
+ * @param programs list of programs which belong to the series.
*/
public static void startSeriesSettingsActivity(Context context, long seriesRecordingId,
- @Nullable long[] channelIds, boolean removeEmptySeriesSchedule,
- boolean isWindowTranslucent, boolean showViewScheduleOptionInDialog) {
+ @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);
- intent.putExtra(DvrSeriesSettingsActivity.CHANNEL_ID_LIST, channelIds);
+ 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);
}
@@ -361,7 +482,8 @@ public class DvrUiHelper {
* Shows "series recording scheduled" dialog activity.
*/
public static void StartSeriesScheduledDialogActivity(Context context,
- SeriesRecording seriesRecording, boolean showViewScheduleOptionInDialog) {
+ SeriesRecording seriesRecording, boolean showViewScheduleOptionInDialog,
+ List<Program> programs) {
if (seriesRecording == null) {
return;
}
@@ -370,6 +492,9 @@ public class DvrUiHelper {
seriesRecording.getId());
intent.putExtra(DvrSeriesScheduledDialogActivity.SHOW_VIEW_SCHEDULE_OPTION,
showViewScheduleOptionInDialog);
+ BigArguments.reset();
+ BigArguments.setArgument(DvrSeriesScheduledFragment.SERIES_SCHEDULED_KEY_PROGRAMS,
+ programs);
context.startActivity(intent);
}
@@ -447,4 +572,4 @@ public class DvrUiHelper {
Utils.toTimeString(endTimeMs, false));
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/FadeBackground.java b/src/com/android/tv/dvr/ui/FadeBackground.java
new file mode 100644
index 00000000..4f06ebcf
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/FadeBackground.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tv.dvr.ui;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.transition.Transition;
+import android.transition.TransitionValues;
+import android.transition.Visibility;
+import android.util.AttributeSet;
+import android.view.ViewGroup;
+
+import com.android.tv.R;
+
+/**
+ * This transition fades in/out of the background of the view by changing the background color.
+ */
+public class FadeBackground extends Transition {
+ private final int mMode;
+
+ public FadeBackground(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FadeBackground);
+ mMode = a.getInt(R.styleable.FadeBackground_fadingMode, Visibility.MODE_IN);
+ a.recycle();
+ }
+
+ @Override
+ public void captureStartValues(TransitionValues transitionValues) { }
+
+ @Override
+ public void captureEndValues(TransitionValues transitionValues) { }
+
+ @Override
+ public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
+ TransitionValues endValues) {
+ if (startValues == null || endValues == null) {
+ return null;
+ }
+ Drawable background = endValues.view.getBackground();
+ if (background instanceof ColorDrawable) {
+ int color = ((ColorDrawable) background).getColor();
+ int transparentColor = Color.argb(0, Color.red(color), Color.green(color),
+ Color.blue(color));
+ return mMode == Visibility.MODE_OUT
+ ? ObjectAnimator.ofArgb(background, "color", transparentColor)
+ : ObjectAnimator.ofArgb(background, "color", transparentColor, color);
+ }
+ return null;
+ }
+}
diff --git a/src/com/android/tv/dvr/ui/HalfSizedDialogFragment.java b/src/com/android/tv/dvr/ui/HalfSizedDialogFragment.java
deleted file mode 100644
index d320816e..00000000
--- a/src/com/android/tv/dvr/ui/HalfSizedDialogFragment.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.dvr.ui;
-
-import android.content.DialogInterface;
-import android.os.Bundle;
-import android.os.Handler;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.tv.R;
-import com.android.tv.dialog.SafeDismissDialogFragment;
-
-import java.util.concurrent.TimeUnit;
-
-public class HalfSizedDialogFragment extends SafeDismissDialogFragment {
- public static final String DIALOG_TAG = HalfSizedDialogFragment.class.getSimpleName();
- public static final String TRACKER_LABEL = "Half sized dialog";
-
- private static final long AUTO_DISMISS_TIME_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
-
- private OnActionClickListener mOnActionClickListener;
-
- private Handler mHandler = new Handler();
- private Runnable mAutoDismisser = new Runnable() {
- @Override
- public void run() {
- dismiss();
- }
- };
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- return inflater.inflate(R.layout.halfsized_dialog, container, false);
- }
-
- @Override
- public void onStart() {
- super.onStart();
- getDialog().setOnKeyListener(new DialogInterface.OnKeyListener() {
- public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent keyEvent) {
- mHandler.removeCallbacks(mAutoDismisser);
- mHandler.postDelayed(mAutoDismisser, AUTO_DISMISS_TIME_THRESHOLD_MS);
- return false;
- }
- });
- mHandler.postDelayed(mAutoDismisser, AUTO_DISMISS_TIME_THRESHOLD_MS);
- }
-
- @Override
- public void onPause() {
- super.onPause();
- if (mOnActionClickListener != null) {
- // Dismisses the dialog to prevent the callback being forgotten during
- // fragment re-creating.
- dismiss();
- }
- }
-
- @Override
- public void onStop() {
- super.onStop();
- mHandler.removeCallbacks(mAutoDismisser);
- }
-
- @Override
- public int getTheme() {
- return R.style.Theme_TV_dialog_HalfSizedDialog;
- }
-
- @Override
- public String getTrackerLabel() {
- return TRACKER_LABEL;
- }
-
- /**
- * Sets {@link OnActionClickListener} for the dialog fragment. If listener is set, the dialog
- * will be automatically closed when it's paused to prevent the fragment being re-created by
- * the framework, which will result the listener being forgotten.
- */
- public void setOnActionClickListener(OnActionClickListener listener) {
- mOnActionClickListener = listener;
- }
-
- /**
- * Returns {@link OnActionClickListener} for sub-classes or any inner fragments.
- */
- protected OnActionClickListener getOnActionClickListener() {
- return mOnActionClickListener;
- }
-
- /**
- * An interface to provide callbacks for half-sized dialogs. Subclasses or inner fragments
- * should invoke {@link OnActionClickListener#onActionClick(long)} and provide the identifier
- * of the action user clicked.
- */
- public interface OnActionClickListener {
- void onActionClick(long actionId);
- }
-} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/SortedArrayAdapter.java b/src/com/android/tv/dvr/ui/SortedArrayAdapter.java
index 393a5ff3..8c0af9ed 100644
--- a/src/com/android/tv/dvr/ui/SortedArrayAdapter.java
+++ b/src/com/android/tv/dvr/ui/SortedArrayAdapter.java
@@ -20,11 +20,15 @@ import android.support.annotation.VisibleForTesting;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.PresenterSelector;
+import com.android.tv.common.SoftPreconditions;
+
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* Keeps a set of items sorted
@@ -35,16 +39,18 @@ public abstract class SortedArrayAdapter<T> extends ArrayObjectAdapter {
private final Comparator<T> mComparator;
private final int mMaxItemCount;
private int mExtraItemCount;
+ private final Set<Long> mIds = new HashSet<>();
- SortedArrayAdapter(PresenterSelector presenterSelector, Comparator<T> comparator) {
+ public SortedArrayAdapter(PresenterSelector presenterSelector, Comparator<T> comparator) {
this(presenterSelector, comparator, Integer.MAX_VALUE);
}
- SortedArrayAdapter(PresenterSelector presenterSelector, Comparator<T> comparator,
+ public SortedArrayAdapter(PresenterSelector presenterSelector, Comparator<T> comparator,
int maxItemCount) {
super(presenterSelector);
mComparator = comparator;
mMaxItemCount = maxItemCount;
+ setHasStableIds(true);
}
/**
@@ -56,7 +62,12 @@ public abstract class SortedArrayAdapter<T> extends ArrayObjectAdapter {
final void setInitialItems(List<T> items) {
List<T> itemsCopy = new ArrayList<>(items);
Collections.sort(itemsCopy, mComparator);
- addAll(0, itemsCopy.subList(0, Math.min(mMaxItemCount, itemsCopy.size())));
+ for (T item : itemsCopy) {
+ add(item, true);
+ if (size() == mMaxItemCount) {
+ break;
+ }
+ }
}
/**
@@ -82,6 +93,9 @@ public abstract class SortedArrayAdapter<T> extends ArrayObjectAdapter {
* the end to save search time.
*/
public final void add(T item, boolean insertToEnd) {
+ long newItemId = getId(item);
+ SoftPreconditions.checkState(!mIds.contains(newItemId));
+ mIds.add(newItemId);
int i;
if (insertToEnd) {
i = findInsertPosition(item);
@@ -89,8 +103,9 @@ public abstract class SortedArrayAdapter<T> extends ArrayObjectAdapter {
i = findInsertPositionBinary(item);
}
super.add(i, item);
- if (size() > mMaxItemCount + mExtraItemCount) {
- removeItems(mMaxItemCount, size() - mMaxItemCount - mExtraItemCount);
+ if (mMaxItemCount < Integer.MAX_VALUE && size() > mMaxItemCount + mExtraItemCount) {
+ Object removedItem = get(mMaxItemCount);
+ remove(removedItem);
}
}
@@ -100,48 +115,97 @@ public abstract class SortedArrayAdapter<T> extends ArrayObjectAdapter {
* They will be presented in their insertion order.
*/
public int addExtraItem(T item) {
+ long newItemId = getId(item);
+ SoftPreconditions.checkState(!mIds.contains(newItemId));
+ mIds.add(newItemId);
super.add(item);
return ++mExtraItemCount;
}
+ @Override
+ public boolean remove(Object item) {
+ return removeWithId((T) item);
+ }
+
/**
* Removes an item which has the same ID as {@code item}.
*/
public boolean removeWithId(T item) {
- int index = indexWithTypeAndId(item);
- return index >= 0 && index < size() && remove(get(index));
+ int index = indexWithId(item);
+ return index >= 0 && index < size() && removeItems(index, 1) == 1;
+ }
+
+ @Override
+ public int removeItems(int position, int count) {
+ int upperBound = Math.min(position + count, size());
+ for (int i = position; i < upperBound; i++) {
+ mIds.remove(getId((T) get(i)));
+ }
+ if (upperBound > size() - mExtraItemCount) {
+ mExtraItemCount -= upperBound - Math.max(size() - mExtraItemCount, position);
+ }
+ return super.removeItems(position, count);
+ }
+
+ @Override
+ public void replace(int position, Object item) {
+ boolean wasExtra = position >= size() - mExtraItemCount;
+ removeItems(position, 1);
+ if (!wasExtra) {
+ add(item);
+ } else {
+ addExtraItem((T) item);
+ }
+ }
+
+ @Override
+ public void clear() {
+ mIds.clear();
+ super.clear();
}
/**
- * Change an item in the list.
+ * Changes an item in the list.
* @param item The item to change.
*/
public final void change(T item) {
- int oldIndex = indexWithTypeAndId(item);
+ int oldIndex = indexWithId(item);
if (oldIndex != -1) {
T old = (T) get(oldIndex);
if (mComparator.compare(old, item) == 0) {
replace(oldIndex, item);
return;
}
- removeItems(oldIndex, 1);
+ remove(old);
}
add(item);
}
/**
+ * Checks whether the item is in the list.
+ */
+ public final boolean contains(T item) {
+ return indexWithId(item) != -1;
+ }
+
+ @Override
+ public long getId(int position) {
+ return getId((T) get(position));
+ }
+
+ /**
* Returns the id of the the given {@code item}, which will be used in {@link #change} to
* decide if the given item is already existed in the adapter.
*
* The id must be stable.
*/
- abstract long getId(T item);
+ protected abstract long getId(T item);
- private int indexWithTypeAndId(T item) {
+ private int indexWithId(T item) {
long id = getId(item);
for (int i = 0; i < size() - mExtraItemCount; i++) {
T r = (T) get(i);
- if (r.getClass() == item.getClass() && getId(r) == id) {
+ if (getId(r) == id) {
return i;
}
}
diff --git a/src/com/android/tv/dvr/ui/ActionPresenterSelector.java b/src/com/android/tv/dvr/ui/browse/ActionPresenterSelector.java
index 8b8cd5c5..38a78f5d 100644
--- a/src/com/android/tv/dvr/ui/ActionPresenterSelector.java
+++ b/src/com/android/tv/dvr/ui/browse/ActionPresenterSelector.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
import android.graphics.drawable.Drawable;
import android.support.v17.leanback.R;
@@ -110,11 +110,7 @@ class ActionPresenterSelector extends PresenterSelector {
.getDimensionPixelSize(R.dimen.lb_action_padding_horizontal);
vh.view.setPaddingRelative(padding, 0, padding, 0);
}
- if (vh.mLayoutDirection == View.LAYOUT_DIRECTION_RTL) {
- vh.mButton.setCompoundDrawablesWithIntrinsicBounds(null, null, icon, null);
- } else {
- vh.mButton.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
- }
+ vh.mButton.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null);
CharSequence line1 = action.getLabel1();
CharSequence line2 = action.getLabel2();
@@ -130,7 +126,7 @@ class ActionPresenterSelector extends PresenterSelector {
@Override
public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
ActionViewHolder vh = (ActionViewHolder) viewHolder;
- vh.mButton.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
+ vh.mButton.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, null, null);
vh.view.setPadding(0, 0, 0, 0);
vh.mAction = null;
}
diff --git a/src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java
new file mode 100644
index 00000000..c8f6a03f
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/browse/CurrentRecordingDetailsFragment.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.tv.dvr.ui.browse;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.OnActionClickedListener;
+import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+
+import com.android.tv.R;
+import com.android.tv.TvApplication;
+import com.android.tv.dialog.HalfSizedDialogFragment;
+import com.android.tv.dvr.DvrDataManager;
+import com.android.tv.dvr.DvrManager;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.ui.DvrStopRecordingFragment;
+import com.android.tv.dvr.ui.DvrUiHelper;
+
+/**
+ * {@link RecordingDetailsFragment} for current recording in DVR.
+ */
+public class CurrentRecordingDetailsFragment extends RecordingDetailsFragment {
+ private static final int ACTION_STOP_RECORDING = 1;
+
+ private DvrDataManager mDvrDataManger;
+ private final DvrDataManager.ScheduledRecordingListener mScheduledRecordingListener =
+ new DvrDataManager.ScheduledRecordingListener() {
+ @Override
+ public void onScheduledRecordingAdded(ScheduledRecording... schedules) { }
+
+ @Override
+ public void onScheduledRecordingRemoved(ScheduledRecording... schedules) {
+ for (ScheduledRecording schedule : schedules) {
+ if (schedule.getId() == getRecording().getId()) {
+ getActivity().finish();
+ return;
+ }
+ }
+ }
+
+ @Override
+ public void onScheduledRecordingStatusChanged(ScheduledRecording... schedules) {
+ for (ScheduledRecording schedule : schedules) {
+ if (schedule.getId() == getRecording().getId()
+ && schedule.getState()
+ != ScheduledRecording.STATE_RECORDING_IN_PROGRESS) {
+ getActivity().finish();
+ return;
+ }
+ }
+ }
+ };
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ mDvrDataManger = TvApplication.getSingletons(context).getDvrDataManager();
+ mDvrDataManger.addScheduledRecordingListener(mScheduledRecordingListener);
+ }
+
+ @Override
+ protected SparseArrayObjectAdapter onCreateActionsAdapter() {
+ SparseArrayObjectAdapter adapter =
+ new SparseArrayObjectAdapter(new ActionPresenterSelector());
+ Resources res = getResources();
+ adapter.set(ACTION_STOP_RECORDING, new Action(ACTION_STOP_RECORDING,
+ res.getString(R.string.epg_dvr_dialog_message_stop_recording), null,
+ res.getDrawable(R.drawable.lb_ic_stop)));
+ return adapter;
+ }
+
+ @Override
+ protected OnActionClickedListener onCreateOnActionClickedListener() {
+ return new OnActionClickedListener() {
+ @Override
+ public void onActionClicked(Action action) {
+ if (action.getId() == ACTION_STOP_RECORDING) {
+ DvrUiHelper.showStopRecordingDialog(getActivity(),
+ getRecording().getChannelId(),
+ DvrStopRecordingFragment.REASON_USER_STOP,
+ new HalfSizedDialogFragment.OnActionClickListener() {
+ @Override
+ public void onActionClick(long actionId) {
+ if (actionId == DvrStopRecordingFragment.ACTION_STOP) {
+ DvrManager dvrManager =
+ TvApplication.getSingletons(getContext())
+ .getDvrManager();
+ dvrManager.stopRecording(getRecording());
+ getActivity().finish();
+ }
+ }
+ });
+ }
+ }
+ };
+ }
+
+ @Override
+ public void onDetach() {
+ if (mDvrDataManger != null) {
+ mDvrDataManger.removeScheduledRecordingListener(mScheduledRecordingListener);
+ }
+ super.onDetach();
+ }
+}
diff --git a/src/com/android/tv/dvr/ui/DetailsContent.java b/src/com/android/tv/dvr/ui/browse/DetailsContent.java
index 19521fca..b43d1f12 100644
--- a/src/com/android/tv/dvr/ui/DetailsContent.java
+++ b/src/com/android/tv/dvr/ui/browse/DetailsContent.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
import android.media.tv.TvContract;
import android.support.annotation.Nullable;
@@ -26,7 +26,7 @@ import com.android.tv.data.Channel;
/**
* A class for details content.
*/
-public class DetailsContent {
+class DetailsContent {
/** Constant for invalid time. */
public static final long INVALID_TIME = -1;
diff --git a/src/com/android/tv/dvr/ui/DetailsContentPresenter.java b/src/com/android/tv/dvr/ui/browse/DetailsContentPresenter.java
index 175f05bc..a2e3fe16 100644
--- a/src/com/android/tv/dvr/ui/DetailsContentPresenter.java
+++ b/src/com/android/tv/dvr/ui/browse/DetailsContentPresenter.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
import android.app.Activity;
import android.animation.Animator;
@@ -38,13 +38,14 @@ import com.android.tv.util.Utils;
/**
* An {@link Presenter} for rendering a detailed description of an DVR item.
- * Typically this Presenter will be used in a {@link DetailsOverviewRowPresenter}.
+ * Typically this Presenter will be used in a
+ * {@link android.support.v17.leanback.widget.DetailsOverviewRowPresenter}.
* Most codes of this class is originated from
* {@link android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter}.
* The latter class are re-used to provide a customized version of
* {@link android.support.v17.leanback.widget.DetailsOverviewRow}.
*/
-public class DetailsContentPresenter extends Presenter {
+class DetailsContentPresenter extends Presenter {
/**
* The ViewHolder for the {@link DetailsContentPresenter}.
*/
@@ -113,6 +114,20 @@ public class DetailsContentPresenter extends Presenter {
public ViewHolder(final View view) {
super(view);
+ view.addOnAttachStateChangeListener(
+ new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ // In case predraw listener was removed in detach, make sure
+ // we have the proper layout.
+ addPreDrawListener();
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ removePreDrawListener();
+ }
+ });
mTitle = (TextView) view.findViewById(R.id.dvr_details_description_title);
mSubtitle = (TextView) view.findViewById(R.id.dvr_details_description_subtitle);
mBody = (TextView) view.findViewById(R.id.dvr_details_description_body);
@@ -276,22 +291,6 @@ public class DetailsContentPresenter extends Presenter {
@Override
public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { }
- @Override
- public void onViewAttachedToWindow(Presenter.ViewHolder holder) {
- // In case predraw listener was removed in detach, make sure
- // we have the proper layout.
- ViewHolder vh = (ViewHolder) holder;
- vh.addPreDrawListener();
- super.onViewAttachedToWindow(holder);
- }
-
- @Override
- public void onViewDetachedFromWindow(Presenter.ViewHolder holder) {
- ViewHolder vh = (ViewHolder) holder;
- vh.removePreDrawListener();
- super.onViewDetachedFromWindow(holder);
- }
-
private void setTopMargin(View view, int topMargin) {
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
lp.topMargin = topMargin;
diff --git a/src/com/android/tv/dvr/ui/DetailsViewBackgroundHelper.java b/src/com/android/tv/dvr/ui/browse/DetailsViewBackgroundHelper.java
index 6714ecd3..82fe9ce3 100644
--- a/src/com/android/tv/dvr/ui/DetailsViewBackgroundHelper.java
+++ b/src/com/android/tv/dvr/ui/browse/DetailsViewBackgroundHelper.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
import android.app.Activity;
import android.graphics.drawable.BitmapDrawable;
@@ -26,7 +26,7 @@ import android.support.v17.leanback.app.BackgroundManager;
/**
* The Background Helper.
*/
-public class DetailsViewBackgroundHelper {
+class DetailsViewBackgroundHelper {
// Background delay serves to avoid kicking off expensive bitmap loading
// in case multiple backgrounds are set in quick succession.
private static final int SET_BACKGROUND_DELAY_MS = 100;
diff --git a/src/com/android/tv/dvr/ui/DvrActivity.java b/src/com/android/tv/dvr/ui/browse/DvrBrowseActivity.java
index 45fb1cf1..2b3dcb25 100644
--- a/src/com/android/tv/dvr/ui/DvrActivity.java
+++ b/src/com/android/tv/dvr/ui/browse/DvrBrowseActivity.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
import android.app.Activity;
import android.os.Bundle;
@@ -25,11 +25,11 @@ import com.android.tv.TvApplication;
/**
* {@link android.app.Activity} for DVR UI.
*/
-public class DvrActivity extends Activity {
+public class DvrBrowseActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
TvApplication.setCurrentRunningProcess(this, true);
super.onCreate(savedInstanceState);
setContentView(R.layout.dvr_main);
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/DvrBrowseFragment.java b/src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java
index a6dd31d1..803d1017 100644
--- a/src/com/android/tv/dvr/ui/DvrBrowseFragment.java
+++ b/src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java
@@ -14,10 +14,9 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
import android.content.Context;
-import android.media.tv.TvInputManager.TvInputCallback;
import android.os.Bundle;
import android.os.Handler;
import android.support.v17.leanback.app.BrowseFragment;
@@ -25,11 +24,11 @@ import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.ClassPresenterSelector;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.ListRowPresenter;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.TitleViewAdapter;
-import android.text.TextUtils;
import android.util.Log;
+import android.view.View;
+import android.view.ViewTreeObserver.OnGlobalFocusChangeListener;
import com.android.tv.ApplicationSingletons;
import com.android.tv.R;
@@ -42,9 +41,10 @@ import com.android.tv.dvr.DvrDataManager.RecordedProgramListener;
import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
import com.android.tv.dvr.DvrDataManager.SeriesRecordingListener;
import com.android.tv.dvr.DvrScheduleManager;
-import com.android.tv.dvr.RecordedProgram;
-import com.android.tv.dvr.ScheduledRecording;
-import com.android.tv.dvr.SeriesRecording;
+import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.data.SeriesRecording;
+import com.android.tv.dvr.ui.SortedArrayAdapter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -79,6 +79,20 @@ public class DvrBrowseFragment extends BrowseFragment implements
private ClassPresenterSelector mPresenterSelector;
private final HashMap<String, RecordedProgram> mSeriesId2LatestProgram = new HashMap<>();
private final Handler mHandler = new Handler();
+ private final OnGlobalFocusChangeListener mOnGlobalFocusChangeListener =
+ new OnGlobalFocusChangeListener() {
+ @Override
+ public void onGlobalFocusChanged(View oldFocus, View newFocus) {
+ if (oldFocus instanceof RecordingCardView) {
+ ((RecordingCardView) oldFocus).expandTitle(false, true);
+ }
+ if (newFocus instanceof RecordingCardView) {
+ // If the header transition is ongoing, expand cards immediately without
+ // animation to make a smooth transition.
+ ((RecordingCardView) newFocus).expandTitle(true, !isInHeadersTransition());
+ }
+ }
+ };
private final Comparator<Object> RECORDED_PROGRAM_COMPARATOR = new Comparator<Object>() {
@Override
@@ -128,7 +142,7 @@ public class DvrBrowseFragment extends BrowseFragment implements
public void onConflictStateChange(boolean conflict, ScheduledRecording... schedules) {
if (mScheduleAdapter != null) {
for (ScheduledRecording schedule : schedules) {
- onScheduledRecordingStatusChanged(schedule);
+ onScheduledRecordingConflictStatusChanged(schedule);
}
}
}
@@ -154,16 +168,12 @@ public class DvrBrowseFragment extends BrowseFragment implements
new ScheduledRecordingPresenter(context))
.addClassPresenter(RecordedProgram.class, new RecordedProgramPresenter(context))
.addClassPresenter(SeriesRecording.class, new SeriesRecordingPresenter(context))
- .addClassPresenter(FullScheduleCardHolder.class, new FullSchedulesCardPresenter());
+ .addClassPresenter(FullScheduleCardHolder.class,
+ new FullSchedulesCardPresenter(context));
mGenreLabels = new ArrayList<>(Arrays.asList(GenreItems.getLabels(context)));
mGenreLabels.add(getString(R.string.dvr_main_others));
- setupUiElements();
- setupAdapters();
- mDvrScheudleManager.addOnConflictStateChangeListener(mOnConflictStateChangeListener);
- prepareEntranceTransition();
- if (mDvrDataManager.isInitialized()) {
- startEntranceTransition();
- } else {
+ prepareUiElements();
+ if (!startBrowseIfDvrInitialized()) {
if (!mDvrDataManager.isDvrScheduleLoadFinished()) {
mDvrDataManager.addDvrScheduleLoadFinishedListener(this);
}
@@ -174,6 +184,19 @@ public class DvrBrowseFragment extends BrowseFragment implements
}
@Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ view.getViewTreeObserver().addOnGlobalFocusChangeListener(mOnGlobalFocusChangeListener);
+ }
+
+ @Override
+ public void onDestroyView() {
+ getView().getViewTreeObserver()
+ .removeOnGlobalFocusChangeListener(mOnGlobalFocusChangeListener);
+ super.onDestroyView();
+ }
+
+ @Override
public void onDestroy() {
if (DEBUG) Log.d(TAG, "onDestroy");
mHandler.removeCallbacks(mUpdateRowsRunnable);
@@ -195,25 +218,13 @@ public class DvrBrowseFragment extends BrowseFragment implements
@Override
public void onDvrScheduleLoadFinished() {
- List<ScheduledRecording> scheduledRecordings = mDvrDataManager.getAllScheduledRecordings();
- onScheduledRecordingAdded(ScheduledRecording.toArray(scheduledRecordings));
- List<SeriesRecording> seriesRecordings = mDvrDataManager.getSeriesRecordings();
- onSeriesRecordingAdded(SeriesRecording.toArray(seriesRecordings));
- if (mDvrDataManager.isInitialized()) {
- startEntranceTransition();
- }
+ startBrowseIfDvrInitialized();
mDvrDataManager.removeDvrScheduleLoadFinishedListener(this);
}
@Override
public void onRecordedProgramLoadFinished() {
- for (RecordedProgram recordedProgram : mDvrDataManager.getRecordedPrograms()) {
- handleRecordedProgramAdded(recordedProgram, true);
- }
- updateRows();
- if (mDvrDataManager.isInitialized()) {
- startEntranceTransition();
- }
+ startBrowseIfDvrInitialized();
mDvrDataManager.removeRecordedProgramLoadFinishedListener(this);
}
@@ -270,6 +281,18 @@ public class DvrBrowseFragment extends BrowseFragment implements
}
}
+ private void onScheduledRecordingConflictStatusChanged(ScheduledRecording... schedules) {
+ for (ScheduledRecording schedule : schedules) {
+ if (needToShowScheduledRecording(schedule)) {
+ if (mScheduleAdapter.contains(schedule)) {
+ mScheduleAdapter.change(schedule);
+ }
+ } else {
+ mScheduleAdapter.removeWithId(schedule);
+ }
+ }
+ }
+
@Override
public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) {
handleSeriesRecordingsAdded(Arrays.asList(seriesRecordings));
@@ -295,44 +318,53 @@ public class DvrBrowseFragment extends BrowseFragment implements
super.showTitle(flags);
}
- private void setupUiElements() {
+ private void prepareUiElements() {
setBadgeDrawable(getActivity().getDrawable(R.drawable.ic_dvr_badge));
setHeadersState(HEADERS_ENABLED);
setHeadersTransitionOnBackEnabled(false);
setBrandColor(getResources().getColor(R.color.program_guide_side_panel_background, null));
+ mRowsAdapter = new ArrayObjectAdapter(new DvrListRowPresenter(getContext()));
+ setAdapter(mRowsAdapter);
+ prepareEntranceTransition();
}
- private void setupAdapters() {
- mRecentAdapter = new RecordedProgramAdapter(MAX_RECENT_ITEM_COUNT);
- mScheduleAdapter = new ScheduleAdapter(MAX_SCHEDULED_ITEM_COUNT);
- mSeriesAdapter = new SeriesAdapter();
- for (int i = 0; i < mGenreAdapters.length; i++) {
- mGenreAdapters[i] = new RecordedProgramAdapter();
- }
- // Schedule Recordings.
- List<ScheduledRecording> schedules = mDvrDataManager.getAllScheduledRecordings();
- onScheduledRecordingAdded(ScheduledRecording.toArray(schedules));
- mScheduleAdapter.addExtraItem(FullScheduleCardHolder.FULL_SCHEDULE_CARD_HOLDER);
- // Recorded Programs.
- for (RecordedProgram recordedProgram : mDvrDataManager.getRecordedPrograms()) {
- handleRecordedProgramAdded(recordedProgram, false);
- }
- // Series Recordings. Series recordings should be added after recorded programs, because
- // we build series recordings' latest program information while adding recorded programs.
- List<SeriesRecording> recordings = mDvrDataManager.getSeriesRecordings();
- handleSeriesRecordingsAdded(recordings);
- mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
- mRecentRow = new ListRow(new HeaderItem(
- getString(R.string.dvr_main_recent)), mRecentAdapter);
- mRowsAdapter.add(new ListRow(new HeaderItem(
- getString(R.string.dvr_main_scheduled)), mScheduleAdapter));
- mSeriesRow = new ListRow(new HeaderItem(
- getString(R.string.dvr_main_series)), mSeriesAdapter);
- updateRows();
- mDvrDataManager.addRecordedProgramListener(this);
- mDvrDataManager.addScheduledRecordingListener(this);
- mDvrDataManager.addSeriesRecordingListener(this);
- setAdapter(mRowsAdapter);
+ private boolean startBrowseIfDvrInitialized() {
+ if (mDvrDataManager.isInitialized()) {
+ // Setup rows
+ mRecentAdapter = new RecordedProgramAdapter(MAX_RECENT_ITEM_COUNT);
+ mScheduleAdapter = new ScheduleAdapter(MAX_SCHEDULED_ITEM_COUNT);
+ mSeriesAdapter = new SeriesAdapter();
+ for (int i = 0; i < mGenreAdapters.length; i++) {
+ mGenreAdapters[i] = new RecordedProgramAdapter();
+ }
+ // Schedule Recordings.
+ List<ScheduledRecording> schedules = mDvrDataManager.getAllScheduledRecordings();
+ onScheduledRecordingAdded(ScheduledRecording.toArray(schedules));
+ mScheduleAdapter.addExtraItem(FullScheduleCardHolder.FULL_SCHEDULE_CARD_HOLDER);
+ // Recorded Programs.
+ for (RecordedProgram recordedProgram : mDvrDataManager.getRecordedPrograms()) {
+ handleRecordedProgramAdded(recordedProgram, false);
+ }
+ // Series Recordings. Series recordings should be added after recorded programs, because
+ // we build series recordings' latest program information while adding recorded programs.
+ List<SeriesRecording> recordings = mDvrDataManager.getSeriesRecordings();
+ handleSeriesRecordingsAdded(recordings);
+ mRecentRow = new ListRow(new HeaderItem(
+ getString(R.string.dvr_main_recent)), mRecentAdapter);
+ mRowsAdapter.add(new ListRow(new HeaderItem(
+ getString(R.string.dvr_main_scheduled)), mScheduleAdapter));
+ mSeriesRow = new ListRow(new HeaderItem(
+ getString(R.string.dvr_main_series)), mSeriesAdapter);
+ updateRows();
+ // Initialize listeners
+ mDvrDataManager.addRecordedProgramListener(this);
+ mDvrDataManager.addScheduledRecordingListener(this);
+ mDvrDataManager.addSeriesRecordingListener(this);
+ mDvrScheudleManager.addOnConflictStateChangeListener(mOnConflictStateChangeListener);
+ startEntranceTransition();
+ return true;
+ }
+ return false;
}
private void handleRecordedProgramAdded(RecordedProgram recordedProgram,
@@ -589,10 +621,11 @@ public class DvrBrowseFragment extends BrowseFragment implements
@Override
public long getId(Object item) {
+ // We takes the inverse number for the ID of recorded programs to make the ID stable.
if (item instanceof SeriesRecording) {
return ((SeriesRecording) item).getId();
} else if (item instanceof RecordedProgram) {
- return ((RecordedProgram) item).getId();
+ return -((RecordedProgram) item).getId() - 1;
} else {
return -1;
}
diff --git a/src/com/android/tv/dvr/ui/DvrDetailsActivity.java b/src/com/android/tv/dvr/ui/browse/DvrDetailsActivity.java
index 806c775c..30c81e83 100644
--- a/src/com/android/tv/dvr/ui/DvrDetailsActivity.java
+++ b/src/com/android/tv/dvr/ui/browse/DvrDetailsActivity.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
import android.app.Activity;
import android.os.Bundle;
diff --git a/src/com/android/tv/dvr/ui/DvrDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java
index 21f9c4b4..4d3698ef 100644
--- a/src/com/android/tv/dvr/ui/DvrDetailsFragment.java
+++ b/src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
import android.content.Context;
import android.content.Intent;
@@ -48,8 +48,8 @@ import com.android.tv.data.BaseProgram;
import com.android.tv.data.Channel;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.dialog.PinDialogFragment;
-import com.android.tv.dvr.DvrPlaybackActivity;
-import com.android.tv.dvr.RecordedProgram;
+import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.dvr.ui.playback.DvrPlaybackActivity;
import com.android.tv.parental.ParentalControlSettings;
import com.android.tv.util.ImageLoader;
import com.android.tv.util.ToastUtils;
diff --git a/src/com/android/tv/dvr/ui/DvrItemPresenter.java b/src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java
index 339e5d2f..317b6af3 100644
--- a/src/com/android/tv/dvr/ui/DvrItemPresenter.java
+++ b/src/com/android/tv/dvr/ui/browse/DvrItemPresenter.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
import android.app.Activity;
import android.support.annotation.CallSuper;
@@ -22,16 +22,17 @@ import android.support.v17.leanback.widget.Presenter;
import android.view.View;
import android.view.View.OnClickListener;
-import com.android.tv.dvr.DvrUiHelper;
+import com.android.tv.dvr.ui.DvrUiHelper;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.Set;
/**
* An abstract class to present DVR items in {@link RecordingCardView}, which is mainly used in
- * {@link DvrBrowseFragment}. DVR items might include: {@link ScheduledRecording},
- * {@link RecordedProgram}, and {@link SeriesRecording}.
+ * {@link DvrBrowseFragment}. DVR items might include:
+ * {@link com.android.tv.dvr.data.ScheduledRecording},
+ * {@link com.android.tv.dvr.data.RecordedProgram}, and
+ * {@link com.android.tv.dvr.data.SeriesRecording}.
*/
public abstract class DvrItemPresenter extends Presenter {
private final Set<ViewHolder> mBoundViewHolders = new HashSet<>();
@@ -49,6 +50,8 @@ public abstract class DvrItemPresenter extends Presenter {
@CallSuper
public void onUnbindViewHolder(ViewHolder viewHolder) {
mBoundViewHolders.remove(viewHolder);
+ viewHolder.view.setTag(null);
+ viewHolder.view.setOnClickListener(null);
}
/**
diff --git a/src/com/android/tv/dvr/ui/browse/DvrListRowPresenter.java b/src/com/android/tv/dvr/ui/browse/DvrListRowPresenter.java
new file mode 100644
index 00000000..37a72eaf
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/browse/DvrListRowPresenter.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tv.dvr.ui.browse;
+
+import android.content.Context;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.view.ViewGroup;
+
+import com.android.tv.R;
+
+/** A list row presenter to display expand/fold card views list. */
+public class DvrListRowPresenter extends ListRowPresenter {
+ public DvrListRowPresenter(Context context) {
+ super();
+ setRowHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
+ setExpandedRowHeight(
+ context.getResources()
+ .getDimensionPixelSize(R.dimen.dvr_library_expanded_row_height));
+ }
+}
diff --git a/src/com/android/tv/dvr/ui/FullScheduleCardHolder.java b/src/com/android/tv/dvr/ui/browse/FullScheduleCardHolder.java
index d4d4d8ab..311137a9 100644
--- a/src/com/android/tv/dvr/ui/FullScheduleCardHolder.java
+++ b/src/com/android/tv/dvr/ui/browse/FullScheduleCardHolder.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
/**
* Special object for schedule preview;
diff --git a/src/com/android/tv/dvr/ui/FullSchedulesCardPresenter.java b/src/com/android/tv/dvr/ui/browse/FullSchedulesCardPresenter.java
index 7dd85f45..6d4763d4 100644
--- a/src/com/android/tv/dvr/ui/FullSchedulesCardPresenter.java
+++ b/src/com/android/tv/dvr/ui/browse/FullSchedulesCardPresenter.java
@@ -14,17 +14,17 @@
* limitations under the License.
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
import android.content.Context;
-import android.support.v17.leanback.widget.Presenter;
+import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.ViewGroup;
import com.android.tv.R;
import com.android.tv.TvApplication;
-import com.android.tv.dvr.DvrUiHelper;
-import com.android.tv.dvr.ScheduledRecording;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.ui.DvrUiHelper;
import com.android.tv.util.Utils;
import java.util.Collections;
@@ -33,23 +33,31 @@ import java.util.List;
/**
* Presents a {@link ScheduledRecording} in the {@link DvrBrowseFragment}.
*/
-public class FullSchedulesCardPresenter extends Presenter {
+class FullSchedulesCardPresenter extends DvrItemPresenter {
+ private Context mContext;
+ private final Drawable mIconDrawable;
+ private final String mCardTitleText;
+
+ public FullSchedulesCardPresenter(Context context) {
+ mContext = context;
+ mIconDrawable = mContext.getDrawable(R.drawable.dvr_full_schedule);
+ mCardTitleText = mContext.getString(R.string.dvr_full_schedule_card_view_title);
+ }
+
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
Context context = parent.getContext();
RecordingCardView view = new RecordingCardView(context);
- return new ScheduledRecordingViewHolder(view);
+ return new ViewHolder(view);
}
@Override
- public void onBindViewHolder(ViewHolder baseHolder, Object o) {
- final ScheduledRecordingViewHolder viewHolder = (ScheduledRecordingViewHolder) baseHolder;
- final RecordingCardView cardView = (RecordingCardView) viewHolder.view;
- final Context context = viewHolder.view.getContext();
+ public void onBindViewHolder(ViewHolder vh, Object o) {
+ final RecordingCardView cardView = (RecordingCardView) vh.view;
- cardView.setImage(context.getDrawable(R.drawable.dvr_full_schedule));
- cardView.setTitle(context.getString(R.string.dvr_full_schedule_card_view_title));
- List<ScheduledRecording> scheduledRecordings = TvApplication.getSingletons(context)
+ cardView.setImage(mIconDrawable);
+ cardView.setTitle(mCardTitleText);
+ List<ScheduledRecording> scheduledRecordings = TvApplication.getSingletons(mContext)
.getDvrDataManager().getAvailableScheduledRecordings();
int fullDays = 0;
if (!scheduledRecordings.isEmpty()) {
@@ -57,28 +65,24 @@ public class FullSchedulesCardPresenter extends Presenter {
Collections.max(scheduledRecordings, ScheduledRecording.START_TIME_COMPARATOR)
.getStartTimeMs()) + 1;
}
- cardView.setContent(context.getResources().getQuantityString(
+ cardView.setContent(mContext.getResources().getQuantityString(
R.plurals.dvr_full_schedule_card_view_content, fullDays, fullDays), null);
-
- View.OnClickListener clickListener = new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- DvrUiHelper.startSchedulesActivity(context, null);
- }
- };
- baseHolder.view.setOnClickListener(clickListener);
+ super.onBindViewHolder(vh, o);
}
@Override
- public void onUnbindViewHolder(ViewHolder baseHolder) {
- ScheduledRecordingViewHolder viewHolder = (ScheduledRecordingViewHolder) baseHolder;
- final RecordingCardView cardView = (RecordingCardView) viewHolder.view;
- cardView.reset();
+ public void onUnbindViewHolder(ViewHolder vh) {
+ ((RecordingCardView) vh.view).reset();
+ super.onUnbindViewHolder(vh);
}
- private static final class ScheduledRecordingViewHolder extends ViewHolder {
- ScheduledRecordingViewHolder(RecordingCardView view) {
- super(view);
- }
+ @Override
+ protected View.OnClickListener onCreateOnClickListener() {
+ return new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ DvrUiHelper.startSchedulesActivity(mContext, null);
+ }
+ };
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/RecordedProgramDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/RecordedProgramDetailsFragment.java
index e698b8a2..fe9b9de5 100644
--- a/src/com/android/tv/dvr/ui/RecordedProgramDetailsFragment.java
+++ b/src/com/android/tv/dvr/ui/browse/RecordedProgramDetailsFragment.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
import android.content.res.Resources;
import android.media.tv.TvInputManager;
@@ -30,10 +30,10 @@ import com.android.tv.data.Channel;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.DvrWatchedPositionManager;
-import com.android.tv.dvr.RecordedProgram;
+import com.android.tv.dvr.data.RecordedProgram;
/**
- * {@link DetailsFragment} for recorded program in DVR.
+ * {@link android.support.v17.leanback.app.DetailsFragment} for recorded program in DVR.
*/
public class RecordedProgramDetailsFragment extends DvrDetailsFragment
implements DvrDataManager.RecordedProgramListener {
diff --git a/src/com/android/tv/dvr/ui/RecordedProgramPresenter.java b/src/com/android/tv/dvr/ui/browse/RecordedProgramPresenter.java
index 1bf34310..ee978797 100644
--- a/src/com/android/tv/dvr/ui/RecordedProgramPresenter.java
+++ b/src/com/android/tv/dvr/ui/browse/RecordedProgramPresenter.java
@@ -14,31 +14,26 @@
* limitations under the License.
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
-import android.app.Activity;
import android.content.Context;
import android.media.tv.TvContract;
import android.media.tv.TvInputManager;
-import android.net.Uri;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.TextAppearanceSpan;
-import android.view.View;
import android.view.ViewGroup;
import com.android.tv.R;
import com.android.tv.TvApplication;
-import com.android.tv.dvr.RecordedProgram;
import com.android.tv.data.Channel;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.dvr.DvrWatchedPositionManager;
import com.android.tv.dvr.DvrWatchedPositionManager.WatchedPositionChangedListener;
+import com.android.tv.dvr.data.RecordedProgram;
import com.android.tv.util.Utils;
-import java.util.concurrent.TimeUnit;
-
/**
* Presents a {@link RecordedProgram} in the {@link DvrBrowseFragment}.
*/
@@ -50,6 +45,7 @@ public class RecordedProgramPresenter extends DvrItemPresenter {
private String mYesterdayString;
private final int mProgressBarColor;
private final boolean mShowEpisodeTitle;
+ private final boolean mExpandTitleWhenFocused;
private static final class RecordedProgramViewHolder extends ViewHolder
implements WatchedPositionChangedListener {
@@ -79,25 +75,27 @@ public class RecordedProgramPresenter extends DvrItemPresenter {
}
}
- public RecordedProgramPresenter(Context context, boolean showEpisodeTitle) {
+ public RecordedProgramPresenter(Context context, boolean showEpisodeTitle,
+ boolean expandTitleWhenFocused) {
mContext = context;
- mChannelDataManager = TvApplication.getSingletons(context).getChannelDataManager();
- mTodayString = context.getString(R.string.dvr_date_today);
- mYesterdayString = context.getString(R.string.dvr_date_yesterday);
+ mChannelDataManager = TvApplication.getSingletons(mContext).getChannelDataManager();
+ mTodayString = mContext.getString(R.string.dvr_date_today);
+ mYesterdayString = mContext.getString(R.string.dvr_date_yesterday);
mDvrWatchedPositionManager =
- TvApplication.getSingletons(context).getDvrWatchedPositionManager();
- mProgressBarColor = context.getResources()
+ TvApplication.getSingletons(mContext).getDvrWatchedPositionManager();
+ mProgressBarColor = mContext.getResources()
.getColor(R.color.play_controls_progress_bar_watched);
mShowEpisodeTitle = showEpisodeTitle;
+ mExpandTitleWhenFocused = expandTitleWhenFocused;
}
public RecordedProgramPresenter(Context context) {
- this(context, false);
+ this(context, false, false);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
- RecordingCardView view = new RecordingCardView(mContext);
+ RecordingCardView view = new RecordingCardView(mContext, mExpandTitleWhenFocused);
return new RecordedProgramViewHolder(view, mProgressBarColor);
}
@@ -132,8 +130,7 @@ public class RecordedProgramPresenter extends DvrItemPresenter {
isChannelLogo = true;
}
cardView.setImageUri(imageUri, isChannelLogo);
- int durationMinutes =
- Math.max(1, (int) TimeUnit.MILLISECONDS.toMinutes(program.getDurationMillis()));
+ int durationMinutes = Math.max(1, Utils.getRoundOffMinsFromMs(program.getDurationMillis()));
String durationString = getContext().getResources().getQuantityString(
R.plurals.dvr_program_duration, durationMinutes, durationMinutes);
cardView.setContent(getDescription(program), durationString);
diff --git a/src/com/android/tv/dvr/ui/RecordingCardView.java b/src/com/android/tv/dvr/ui/browse/RecordingCardView.java
index 51c3b03b..7b0a8cb9 100644
--- a/src/com/android/tv/dvr/ui/RecordingCardView.java
+++ b/src/com/android/tv/dvr/ui/browse/RecordingCardView.java
@@ -14,51 +14,69 @@
* limitations under the License.
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
+import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
+import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
-import android.net.Uri;
import android.support.annotation.Nullable;
import android.support.v17.leanback.widget.BaseCardView;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
+import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.android.tv.R;
-import com.android.tv.dvr.RecordedProgram;
+import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.ui.ViewUtils;
import com.android.tv.util.ImageLoader;
/**
- * A CardView for displaying info about a {@link com.android.tv.dvr.ScheduledRecording} or
- * {@link RecordedProgram} or
- * {@link com.android.tv.dvr.SeriesRecording}.
+ * A CardView for displaying info about a {@link com.android.tv.dvr.data.ScheduledRecording}
+ * or {@link RecordedProgram} or {@link com.android.tv.dvr.data.SeriesRecording}.
*/
-class RecordingCardView extends BaseCardView {
+public class RecordingCardView extends BaseCardView {
+ // This value should be the same with
+ // android.support.v17.leanback.widget.FocusHighlightHelper.BrowseItemFocusHighlight.DURATION_MS
+ private final static int ANIMATION_DURATION = 150;
private final ImageView mImageView;
private final int mImageWidth;
private final int mImageHeight;
private String mImageUri;
- private final TextView mTitleView;
private final TextView mMajorContentView;
private final TextView mMinorContentView;
private final ProgressBar mProgressBar;
private final View mAffiliatedIconContainer;
private final ImageView mAffiliatedIcon;
private final Drawable mDefaultImage;
+ private final FrameLayout mTitleArea;
+ private final TextView mFoldedTitleView;
+ private final TextView mExpandedTitleView;
+ private final ValueAnimator mExpandTitleAnimator;
+ private final int mFoldedTitleHeight;
+ private final int mExpandedTitleHeight;
+ private final boolean mExpandTitleWhenFocused;
+ private boolean mExpanded;
- RecordingCardView(Context context) {
- this(context,
- context.getResources().getDimensionPixelSize(R.dimen.dvr_card_image_layout_width),
- context.getResources().getDimensionPixelSize(R.dimen.dvr_card_image_layout_height));
+ public RecordingCardView(Context context) {
+ this(context, false);
}
- RecordingCardView(Context context, int imageWidth, int imageHeight) {
+ public RecordingCardView(Context context, boolean expandTitleWhenFocused) {
+ this(context, context.getResources().getDimensionPixelSize(
+ R.dimen.dvr_library_card_image_layout_width), context.getResources()
+ .getDimensionPixelSize(R.dimen.dvr_library_card_image_layout_height),
+ expandTitleWhenFocused);
+ }
+
+ public RecordingCardView(Context context, int imageWidth, int imageHeight,
+ boolean expandTitleWhenFocused) {
super(context);
//TODO(dvr): move these to the layout XML.
setCardType(BaseCardView.CARD_TYPE_INFO_UNDER_WITH_EXTRA);
@@ -75,13 +93,73 @@ class RecordingCardView extends BaseCardView {
mProgressBar = (ProgressBar) findViewById(R.id.recording_progress);
mAffiliatedIconContainer = findViewById(R.id.affiliated_icon_container);
mAffiliatedIcon = (ImageView) findViewById(R.id.affiliated_icon);
- mTitleView = (TextView) findViewById(R.id.title);
mMajorContentView = (TextView) findViewById(R.id.content_major);
mMinorContentView = (TextView) findViewById(R.id.content_minor);
+ mTitleArea = (FrameLayout) findViewById(R.id.title_area);
+ mFoldedTitleView = (TextView) findViewById(R.id.title_one_line);
+ mExpandedTitleView = (TextView) findViewById(R.id.title_two_lines);
+ mFoldedTitleHeight = getResources()
+ .getDimensionPixelSize(R.dimen.dvr_library_card_folded_title_height);
+ mExpandedTitleHeight = getResources()
+ .getDimensionPixelSize(R.dimen.dvr_library_card_expanded_title_height);
+ mExpandTitleAnimator = ValueAnimator.ofFloat(0.0f, 1.0f).setDuration(ANIMATION_DURATION);
+ mExpandTitleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ float value = (Float) valueAnimator.getAnimatedValue();
+ mExpandedTitleView.setAlpha(value);
+ mFoldedTitleView.setAlpha(1.0f - value);
+ ViewUtils.setLayoutHeight(mTitleArea, (int) (mFoldedTitleHeight
+ + (mExpandedTitleHeight - mFoldedTitleHeight) * value));
+ }
+ });
+ mExpandTitleWhenFocused = expandTitleWhenFocused;
+ }
+
+ @Override
+ protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
+ super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+ if (mExpandTitleWhenFocused) {
+ if (gainFocus) {
+ expandTitle(true, true);
+ } else {
+ expandTitle(false, true);
+ }
+ }
+ }
+
+ /**
+ * Expands/folds the title area to show program title with two/one lines.
+ *
+ * @param expand {@code true} to expand the title area, or {@code false} to fold it.
+ * @param withAnimation {@code true} to expand/fold with animation.
+ */
+ public void expandTitle(boolean expand, boolean withAnimation) {
+ if (expand != mExpanded && mFoldedTitleView.getLayout().getEllipsisCount(0) > 0) {
+ if (withAnimation) {
+ if (expand) {
+ mExpandTitleAnimator.start();
+ } else {
+ mExpandTitleAnimator.reverse();
+ }
+ } else {
+ if (expand) {
+ mFoldedTitleView.setAlpha(0.0f);
+ mExpandedTitleView.setAlpha(1.0f);
+ ViewUtils.setLayoutHeight(mTitleArea, mExpandedTitleHeight);
+ } else {
+ mFoldedTitleView.setAlpha(1.0f);
+ mExpandedTitleView.setAlpha(0.0f);
+ ViewUtils.setLayoutHeight(mTitleArea, mFoldedTitleHeight);
+ }
+ }
+ mExpanded = expand;
+ }
}
void setTitle(CharSequence title) {
- mTitleView.setText(title);
+ mFoldedTitleView.setText(title);
+ mExpandedTitleView.setText(title);
}
void setContent(CharSequence majorContent, CharSequence minorContent) {
@@ -178,8 +256,9 @@ class RecordingCardView extends BaseCardView {
}
public void reset() {
- mTitleView.setText(null);
+ mFoldedTitleView.setText(null);
+ mExpandedTitleView.setText(null);
setContent(null, null);
mImageView.setImageDrawable(mDefaultImage);
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/RecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java
index 4e19ec3f..a877e05f 100644
--- a/src/com/android/tv/dvr/ui/RecordingDetailsFragment.java
+++ b/src/com/android/tv/dvr/ui/browse/RecordingDetailsFragment.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
import android.os.Bundle;
import android.support.v17.leanback.app.DetailsFragment;
@@ -26,7 +26,7 @@ import android.text.style.TextAppearanceSpan;
import com.android.tv.R;
import com.android.tv.TvApplication;
import com.android.tv.data.Channel;
-import com.android.tv.dvr.ScheduledRecording;
+import com.android.tv.dvr.data.ScheduledRecording;
/**
* {@link DetailsFragment} for recordings in DVR.
diff --git a/src/com/android/tv/dvr/ui/ScheduledRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java
index 60816bb5..eb0f4f0d 100644
--- a/src/com/android/tv/dvr/ui/ScheduledRecordingDetailsFragment.java
+++ b/src/com/android/tv/dvr/ui/browse/ScheduledRecordingDetailsFragment.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
import android.content.res.Resources;
import android.os.Bundle;
@@ -26,7 +26,7 @@ import android.text.TextUtils;
import com.android.tv.R;
import com.android.tv.TvApplication;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.DvrUiHelper;
+import com.android.tv.dvr.ui.DvrUiHelper;
/**
* {@link RecordingDetailsFragment} for scheduled recording in DVR.
diff --git a/src/com/android/tv/dvr/ui/ScheduledRecordingPresenter.java b/src/com/android/tv/dvr/ui/browse/ScheduledRecordingPresenter.java
index 5f447f13..efc8785a 100644
--- a/src/com/android/tv/dvr/ui/ScheduledRecordingPresenter.java
+++ b/src/com/android/tv/dvr/ui/browse/ScheduledRecordingPresenter.java
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
-import android.app.Activity;
import android.content.Context;
import android.media.tv.TvContract;
import android.os.Handler;
@@ -32,7 +31,7 @@ import com.android.tv.TvApplication;
import com.android.tv.data.Channel;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.ScheduledRecording;
+import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.util.Utils;
import java.util.concurrent.TimeUnit;
@@ -40,7 +39,7 @@ import java.util.concurrent.TimeUnit;
/**
* Presents a {@link ScheduledRecording} in the {@link DvrBrowseFragment}.
*/
-public class ScheduledRecordingPresenter extends DvrItemPresenter {
+class ScheduledRecordingPresenter extends DvrItemPresenter {
private static final long PROGRESS_UPDATE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(5);
private final ChannelDataManager mChannelDataManager;
@@ -92,18 +91,17 @@ public class ScheduledRecordingPresenter extends DvrItemPresenter {
}
public ScheduledRecordingPresenter(Context context) {
- ApplicationSingletons singletons = TvApplication.getSingletons(context);
+ mContext = context;
+ ApplicationSingletons singletons = TvApplication.getSingletons(mContext);
mChannelDataManager = singletons.getChannelDataManager();
mDvrManager = singletons.getDvrManager();
- mContext = context;
- mProgressBarColor = context.getResources()
+ mProgressBarColor = mContext.getResources()
.getColor(R.color.play_controls_recording_icon_color_on_focus);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
- Context context = parent.getContext();
- RecordingCardView view = new RecordingCardView(context);
+ RecordingCardView view = new RecordingCardView(mContext);
return new ScheduledRecordingViewHolder(view, mProgressBarColor);
}
@@ -144,9 +142,8 @@ public class ScheduledRecordingPresenter extends DvrItemPresenter {
public void onUnbindViewHolder(ViewHolder baseHolder) {
ScheduledRecordingViewHolder viewHolder = (ScheduledRecordingViewHolder) baseHolder;
viewHolder.stopUpdateProgressBar();
- final RecordingCardView cardView = (RecordingCardView) viewHolder.view;
viewHolder.mScheduledRecording = null;
- cardView.reset();
+ ((RecordingCardView) viewHolder.view).reset();
super.onUnbindViewHolder(viewHolder);
}
@@ -174,4 +171,4 @@ public class ScheduledRecordingPresenter extends DvrItemPresenter {
cardView.setTitle(title);
cardView.setImageUri(imageUri, isChannelLogo);
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/SeriesRecordingDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java
index e9e391d4..f7b60b50 100644
--- a/src/com/android/tv/dvr/ui/SeriesRecordingDetailsFragment.java
+++ b/src/com/android/tv/dvr/ui/browse/SeriesRecordingDetailsFragment.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
@@ -28,7 +28,6 @@ import android.support.v17.leanback.widget.DetailsOverviewRow;
import android.support.v17.leanback.widget.DetailsOverviewRowPresenter;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.ListRowPresenter;
import android.support.v17.leanback.widget.OnActionClickedListener;
import android.support.v17.leanback.widget.PresenterSelector;
import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
@@ -39,12 +38,11 @@ import com.android.tv.TvApplication;
import com.android.tv.data.BaseProgram;
import com.android.tv.data.Channel;
import com.android.tv.dvr.DvrDataManager;
-import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.DvrUiHelper;
import com.android.tv.dvr.DvrWatchedPositionManager;
-import com.android.tv.dvr.RecordedProgram;
-import com.android.tv.dvr.ScheduledRecording;
-import com.android.tv.dvr.SeriesRecording;
+import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.dvr.data.SeriesRecording;
+import com.android.tv.dvr.ui.DvrUiHelper;
+import com.android.tv.dvr.ui.SortedArrayAdapter;
import java.util.Collections;
import java.util.Comparator;
@@ -85,7 +83,7 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement
mWatchLabel = getString(R.string.dvr_detail_watch);
mResumeLabel = getString(R.string.dvr_detail_series_resume);
mWatchDrawable = getResources().getDrawable(R.drawable.lb_ic_play, null);
- mRecordedProgramPresenter = new RecordedProgramPresenter(getContext(), true);
+ mRecordedProgramPresenter = new RecordedProgramPresenter(getContext(), true, true);
super.onCreate(savedInstanceState);
}
@@ -158,7 +156,7 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement
DetailsOverviewRowPresenter rowPresenter) {
ClassPresenterSelector presenterSelector = new ClassPresenterSelector();
presenterSelector.addClassPresenter(DetailsOverviewRow.class, rowPresenter);
- presenterSelector.addClassPresenter(ListRow.class, new ListRowPresenter());
+ presenterSelector.addClassPresenter(ListRow.class, new DvrListRowPresenter(getContext()));
return presenterSelector;
}
@@ -203,10 +201,7 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement
mDvrDataManager.removeSeriesRecordingListener(this);
mDvrDataManager.removeRecordedProgramListener(this);
if (mSeries != null) {
- DvrManager dvrManager = TvApplication.getSingletons(getActivity()).getDvrManager();
- if (dvrManager.canRemoveSeriesRecording(mSeries.getId())) {
- dvrManager.removeSeriesRecording(mSeries.getId());
- }
+ mDvrDataManager.checkAndRemoveEmptySeriesRecording(mSeries.getId());
}
mRecordedProgramPresenter.unbindAllViewHolders();
}
@@ -265,7 +260,6 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement
public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) {
for (SeriesRecording series : seriesRecordings) {
if (series.getId() == mSeries.getId()) {
- mSeries = null;
getActivity().finish();
return;
}
@@ -372,4 +366,4 @@ public class SeriesRecordingDetailsFragment extends DvrDetailsFragment implement
return program.getId();
}
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/SeriesRecordingPresenter.java b/src/com/android/tv/dvr/ui/browse/SeriesRecordingPresenter.java
index c2c0f596..af6ecc19 100644
--- a/src/com/android/tv/dvr/ui/SeriesRecordingPresenter.java
+++ b/src/com/android/tv/dvr/ui/browse/SeriesRecordingPresenter.java
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.browse;
-import android.app.Activity;
import android.content.Context;
import android.media.tv.TvContract;
import android.media.tv.TvInputManager;
@@ -34,16 +33,16 @@ import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.DvrWatchedPositionManager;
import com.android.tv.dvr.DvrWatchedPositionManager.WatchedPositionChangedListener;
-import com.android.tv.dvr.RecordedProgram;
-import com.android.tv.dvr.ScheduledRecording;
-import com.android.tv.dvr.SeriesRecording;
+import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.data.SeriesRecording;
import java.util.List;
/**
* Presents a {@link SeriesRecording} in {@link DvrBrowseFragment}.
*/
-public class SeriesRecordingPresenter extends DvrItemPresenter {
+class SeriesRecordingPresenter extends DvrItemPresenter {
private final ChannelDataManager mChannelDataManager;
private final DvrDataManager mDvrDataManager;
private final DvrManager mDvrManager;
diff --git a/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java b/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java
index d28f026c..5abd52a1 100644
--- a/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java
+++ b/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java
@@ -29,7 +29,7 @@ import com.android.tv.R;
import com.android.tv.TvApplication;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrScheduleManager;
-import com.android.tv.dvr.ScheduledRecording;
+import com.android.tv.dvr.data.ScheduledRecording;
/**
* A base fragment to show the list of schedule recordings.
diff --git a/src/com/android/tv/dvr/ui/DvrSchedulesActivity.java b/src/com/android/tv/dvr/ui/list/DvrSchedulesActivity.java
index f6e6ac26..a0410bb3 100644
--- a/src/com/android/tv/dvr/ui/DvrSchedulesActivity.java
+++ b/src/com/android/tv/dvr/ui/list/DvrSchedulesActivity.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.list;
import android.app.Activity;
import android.app.ProgressDialog;
@@ -24,15 +24,13 @@ import android.support.annotation.IntDef;
import com.android.tv.R;
import com.android.tv.TvApplication;
import com.android.tv.data.Program;
-import com.android.tv.dvr.EpisodicProgramLoadTask;
-import com.android.tv.dvr.SeriesRecording;
-import com.android.tv.dvr.SeriesRecordingScheduler;
-import com.android.tv.dvr.ui.list.DvrSchedulesFragment;
-import com.android.tv.dvr.ui.list.DvrSeriesSchedulesFragment;
+import com.android.tv.dvr.data.SeriesRecording;
+import com.android.tv.dvr.provider.EpisodicProgramLoadTask;
+import com.android.tv.dvr.recorder.SeriesRecordingScheduler;
+import com.android.tv.dvr.ui.BigArguments;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -72,33 +70,47 @@ public class DvrSchedulesActivity extends Activity {
getFragmentManager().beginTransaction().add(
R.id.fragment_container, schedulesFragment).commit();
} else if (scheduleType == TYPE_SERIES_SCHEDULE) {
- final ProgressDialog dialog = ProgressDialog.show(this, null, getString(
- R.string.dvr_series_schedules_progress_message_reading_programs));
- SeriesRecording seriesRecording = getIntent().getExtras()
- .getParcelable(DvrSeriesSchedulesFragment
- .SERIES_SCHEDULES_KEY_SERIES_RECORDING);
- // To get programs faster, hold the update of the series schedules.
- SeriesRecordingScheduler.getInstance(this).pauseUpdate();
- new EpisodicProgramLoadTask(this, Collections.singletonList(seriesRecording)) {
- @Override
- protected void onPostExecute(List<Program> programs) {
- SeriesRecordingScheduler.getInstance(DvrSchedulesActivity.this).resumeUpdate();
- dialog.dismiss();
- Bundle args = getIntent().getExtras();
- args.putParcelableArrayList(DvrSeriesSchedulesFragment
- .SERIES_SCHEDULES_KEY_SERIES_PROGRAMS, new ArrayList<>(programs));
- DvrSeriesSchedulesFragment schedulesFragment = new DvrSeriesSchedulesFragment();
- schedulesFragment.setArguments(args);
- getFragmentManager().beginTransaction().add(
- R.id.fragment_container, schedulesFragment).commit();
- }
- }.setLoadCurrentProgram(true)
- .setLoadDisallowedProgram(true)
- .setLoadScheduledEpisode(true)
- .setIgnoreChannelOption(true)
- .execute();
+ if (BigArguments.getArgument(DvrSeriesSchedulesFragment
+ .SERIES_SCHEDULES_KEY_SERIES_PROGRAMS) != null) {
+ // The programs will be passed to the DvrSeriesSchedulesFragment, so don't need
+ // to reset the BigArguments.
+ showDvrSeriesSchedulesFragment(getIntent().getExtras());
+ } else {
+ final ProgressDialog dialog = ProgressDialog.show(this, null, getString(
+ R.string.dvr_series_progress_message_reading_programs));
+ SeriesRecording seriesRecording = getIntent().getExtras()
+ .getParcelable(DvrSeriesSchedulesFragment
+ .SERIES_SCHEDULES_KEY_SERIES_RECORDING);
+ // To get programs faster, hold the update of the series schedules.
+ SeriesRecordingScheduler.getInstance(this).pauseUpdate();
+ new EpisodicProgramLoadTask(this, Collections.singletonList(seriesRecording)) {
+ @Override
+ protected void onPostExecute(List<Program> programs) {
+ SeriesRecordingScheduler.getInstance(DvrSchedulesActivity.this)
+ .resumeUpdate();
+ dialog.dismiss();
+ Bundle args = getIntent().getExtras();
+ BigArguments.reset();
+ BigArguments.setArgument(
+ DvrSeriesSchedulesFragment.SERIES_SCHEDULES_KEY_SERIES_PROGRAMS,
+ programs == null ? Collections.EMPTY_LIST : programs);
+ showDvrSeriesSchedulesFragment(args);
+ }
+ }.setLoadCurrentProgram(true)
+ .setLoadDisallowedProgram(true)
+ .setLoadScheduledEpisode(true)
+ .setIgnoreChannelOption(true)
+ .execute();
+ }
} else {
finish();
}
}
+
+ private void showDvrSeriesSchedulesFragment(Bundle args) {
+ DvrSeriesSchedulesFragment schedulesFragment = new DvrSeriesSchedulesFragment();
+ schedulesFragment.setArguments(args);
+ getFragmentManager().beginTransaction().add(
+ R.id.fragment_container, schedulesFragment).commit();
+ }
}
diff --git a/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java b/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java
index 722c9b6e..3cbb500a 100644
--- a/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java
+++ b/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java
@@ -18,12 +18,9 @@ package com.android.tv.dvr.ui.list;
import android.os.Bundle;
import android.support.v17.leanback.widget.ClassPresenterSelector;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
import com.android.tv.R;
-import com.android.tv.dvr.ScheduledRecording;
+import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.ui.list.SchedulesHeaderRowPresenter.DateHeaderRowPresenter;
/**
diff --git a/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java b/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java
index 42a1e72b..57e7a88f 100644
--- a/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java
+++ b/src/com/android/tv/dvr/ui/list/DvrSeriesSchedulesFragment.java
@@ -17,6 +17,7 @@
package com.android.tv.dvr.ui.list;
import android.annotation.TargetApi;
+import android.content.Context;
import android.database.ContentObserver;
import android.media.tv.TvContract.Programs;
import android.net.Uri;
@@ -35,11 +36,13 @@ import com.android.tv.R;
import com.android.tv.TvApplication;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.Program;
+import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrDataManager.SeriesRecordingListener;
-import com.android.tv.dvr.EpisodicProgramLoadTask;
-import com.android.tv.dvr.SeriesRecording;
-import com.android.tv.dvr.ui.list.SchedulesHeaderRowPresenter.SeriesRecordingHeaderRowPresenter;
+import com.android.tv.dvr.data.SeriesRecording;
+import com.android.tv.dvr.provider.EpisodicProgramLoadTask;
+import com.android.tv.dvr.ui.BigArguments;
+import java.util.Collections;
import java.util.List;
/**
@@ -47,20 +50,22 @@ import java.util.List;
*/
@TargetApi(Build.VERSION_CODES.N)
public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment {
- private static final String TAG = "DvrSeriesSchedulesFragment";
/**
* The key for series recording whose scheduled recording list will be displayed.
+ * Type: {@link SeriesRecording}
*/
public static final String SERIES_SCHEDULES_KEY_SERIES_RECORDING =
"series_schedules_key_series_recording";
/**
- * The key for programs belong to the series recording whose scheduled recording
- * list will be displayed.
+ * The key for programs which belong to the series recording whose scheduled recording list
+ * will be displayed.
+ * Type: List<{@link Program}>
*/
public static final String SERIES_SCHEDULES_KEY_SERIES_PROGRAMS =
"series_schedules_key_series_programs";
private ChannelDataManager mChannelDataManager;
+ private DvrDataManager mDvrDataManager;
private SeriesRecording mSeriesRecording;
private List<Program> mPrograms;
private EpisodicProgramLoadTask mProgramLoadTask;
@@ -87,20 +92,22 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment {
&& getRowsAdapter() instanceof SeriesScheduleRowAdapter) {
((SeriesScheduleRowAdapter) getRowsAdapter())
.onSeriesRecordingUpdated(r);
+ mSeriesRecording = r;
+ updateEmptyMessage();
return;
}
}
}
};
- private final ContentObserver mContentObserver =
- new ContentObserver(new Handler(Looper.getMainLooper())) {
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- super.onChange(selfChange, uri);
- executeProgramLoadingTask();
- }
- };
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final ContentObserver mContentObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ super.onChange(selfChange, uri);
+ executeProgramLoadingTask();
+ }
+ };
private final ChannelDataManager.Listener mChannelListener = new ChannelDataManager.Listener() {
@Override
@@ -120,17 +127,28 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment {
}
@Override
- public void onCreate(Bundle savedInstanceState) {
+ public void onAttach(Context context) {
+ super.onAttach(context);
Bundle args = getArguments();
if (args != null) {
mSeriesRecording = args.getParcelable(SERIES_SCHEDULES_KEY_SERIES_RECORDING);
- mPrograms = args.getParcelableArrayList(SERIES_SCHEDULES_KEY_SERIES_PROGRAMS);
+ mPrograms = (List<Program>) BigArguments.getArgument(
+ SERIES_SCHEDULES_KEY_SERIES_PROGRAMS);
+ BigArguments.reset();
}
+ if (args == null || mPrograms == null) {
+ getActivity().finish();
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ApplicationSingletons singletons = TvApplication.getSingletons(getContext());
- singletons.getDvrDataManager().addSeriesRecordingListener(mSeriesRecordingListener);
mChannelDataManager = singletons.getChannelDataManager();
mChannelDataManager.addListener(mChannelListener);
+ mDvrDataManager = singletons.getDvrDataManager();
+ mDvrDataManager.addSeriesRecordingListener(mSeriesRecordingListener);
getContext().getContentResolver().registerContentObserver(Programs.CONTENT_URI, true,
mContentObserver);
}
@@ -144,8 +162,16 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment {
private void onProgramsUpdated() {
((SeriesScheduleRowAdapter) getRowsAdapter()).setPrograms(mPrograms);
+ updateEmptyMessage();
+ }
+
+ private void updateEmptyMessage() {
if (mPrograms == null || mPrograms.isEmpty()) {
- showEmptyMessage(R.string.dvr_series_schedules_empty_state);
+ if (mSeriesRecording.getState() == SeriesRecording.STATE_SERIES_STOPPED) {
+ showEmptyMessage(R.string.dvr_series_schedules_stopped_empty_state);
+ } else {
+ showEmptyMessage(R.string.dvr_series_schedules_empty_state);
+ }
} else {
hideEmptyMessage();
}
@@ -158,15 +184,15 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment {
mProgramLoadTask = null;
}
getContext().getContentResolver().unregisterContentObserver(mContentObserver);
+ mHandler.removeCallbacksAndMessages(null);
mChannelDataManager.removeListener(mChannelListener);
- TvApplication.getSingletons(getContext()).getDvrDataManager()
- .removeSeriesRecordingListener(mSeriesRecordingListener);
+ mDvrDataManager.removeSeriesRecordingListener(mSeriesRecordingListener);
super.onDestroy();
}
@Override
public SchedulesHeaderRowPresenter onCreateHeaderRowPresenter() {
- return new SeriesRecordingHeaderRowPresenter(getContext());
+ return new SchedulesHeaderRowPresenter.SeriesRecordingHeaderRowPresenter(getContext());
}
@Override
@@ -195,7 +221,7 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment {
mProgramLoadTask = new EpisodicProgramLoadTask(getContext(), mSeriesRecording) {
@Override
protected void onPostExecute(List<Program> programs) {
- mPrograms = programs;
+ mPrograms = programs == null ? Collections.EMPTY_LIST : programs;
onProgramsUpdated();
}
};
@@ -205,4 +231,4 @@ public class DvrSeriesSchedulesFragment extends BaseDvrSchedulesFragment {
.setIgnoreChannelOption(true)
.execute();
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java b/src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java
index 23aebf59..9b0ad105 100644
--- a/src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java
+++ b/src/com/android/tv/dvr/ui/list/EpisodicProgramRow.java
@@ -19,13 +19,13 @@ package com.android.tv.dvr.ui.list;
import android.content.Context;
import com.android.tv.data.Program;
-import com.android.tv.dvr.ScheduledRecording;
-import com.android.tv.dvr.ScheduledRecording.Builder;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.data.ScheduledRecording.Builder;
/**
* A class for the episodic program.
*/
-public class EpisodicProgramRow extends ScheduleRow {
+class EpisodicProgramRow extends ScheduleRow {
private final String mInputId;
private final Program mProgram;
diff --git a/src/com/android/tv/dvr/ui/list/ScheduleRow.java b/src/com/android/tv/dvr/ui/list/ScheduleRow.java
index 3fc92e8a..66e96f94 100644
--- a/src/com/android/tv/dvr/ui/list/ScheduleRow.java
+++ b/src/com/android/tv/dvr/ui/list/ScheduleRow.java
@@ -20,12 +20,12 @@ import android.content.Context;
import android.support.annotation.Nullable;
import com.android.tv.common.SoftPreconditions;
-import com.android.tv.dvr.ScheduledRecording;
+import com.android.tv.dvr.data.ScheduledRecording;
/**
* A class for schedule recording row.
*/
-public class ScheduleRow {
+class ScheduleRow {
private final SchedulesHeaderRow mHeaderRow;
@Nullable private ScheduledRecording mSchedule;
private boolean mStopRecordingRequested;
diff --git a/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java b/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java
index 9cc82653..97d60473 100644
--- a/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java
+++ b/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java
@@ -30,8 +30,8 @@ import com.android.tv.R;
import com.android.tv.TvApplication;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.ScheduledRecording;
import com.android.tv.dvr.ui.list.SchedulesHeaderRow.DateHeaderRow;
+import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.util.Utils;
import java.util.ArrayList;
@@ -43,7 +43,7 @@ import java.util.concurrent.TimeUnit;
/**
* An adapter for {@link ScheduleRow}.
*/
-public class ScheduleRowAdapter extends ArrayObjectAdapter {
+class ScheduleRowAdapter extends ArrayObjectAdapter {
private static final String TAG = "ScheduleRowAdapter";
private static final boolean DEBUG = false;
diff --git a/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java b/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java
index 1257e725..dc4e3c41 100644
--- a/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java
+++ b/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java
@@ -42,25 +42,24 @@ import com.android.tv.R;
import com.android.tv.TvApplication;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.Channel;
+import com.android.tv.dialog.HalfSizedDialogFragment;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.DvrScheduleManager;
-import com.android.tv.dvr.DvrUiHelper;
-import com.android.tv.dvr.ScheduledRecording;
+import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.dvr.ui.DvrStopRecordingFragment;
-import com.android.tv.dvr.ui.HalfSizedDialogFragment;
+import com.android.tv.dvr.ui.DvrUiHelper;
import com.android.tv.util.ToastUtils;
import com.android.tv.util.Utils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
-import java.util.concurrent.TimeUnit;
/**
* A RowPresenter for {@link ScheduleRow}.
*/
@TargetApi(Build.VERSION_CODES.N)
-public class ScheduleRowPresenter extends RowPresenter {
+class ScheduleRowPresenter extends RowPresenter {
private static final String TAG = "ScheduleRowPresenter";
@Retention(RetentionPolicy.SOURCE)
@@ -345,7 +344,9 @@ public class ScheduleRowPresenter extends RowPresenter {
viewHolder.mInfoContainer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
- onInfoClicked(row);
+ if (isInfoClickable(row)) {
+ onInfoClicked(row);
+ }
}
});
@@ -366,8 +367,7 @@ public class ScheduleRowPresenter extends RowPresenter {
viewHolder.mTimeView.setText(onGetRecordingTimeText(row));
String programInfoText = onGetProgramInfoText(row);
if (TextUtils.isEmpty(programInfoText)) {
- int durationMins =
- Math.max((int) TimeUnit.MILLISECONDS.toMinutes(row.getDuration()), 1);
+ int durationMins = Math.max(1, Utils.getRoundOffMinsFromMs(row.getDuration()));
programInfoText = mContext.getResources().getQuantityString(
R.plurals.dvr_schedules_recording_duration, durationMins, durationMins);
}
@@ -403,6 +403,7 @@ public class ScheduleRowPresenter extends RowPresenter {
} else {
viewHolder.whiteBackInfo();
}
+ viewHolder.mInfoContainer.setFocusable(isInfoClickable(row));
updateActionContainer(viewHolder, viewHolder.isSelected());
}
@@ -454,11 +455,13 @@ public class ScheduleRowPresenter extends RowPresenter {
/**
* Called when user click Info in {@link ScheduleRow}.
*/
- protected void onInfoClicked(ScheduleRow scheduleRow) {
- ScheduledRecording schedule = scheduleRow.getSchedule();
- if (schedule != null) {
- DvrUiHelper.startDetailsActivity((Activity) mContext, schedule, null, true);
- }
+ protected void onInfoClicked(ScheduleRow row) {
+ DvrUiHelper.startDetailsActivity((Activity) mContext, row.getSchedule(), null, true);
+ }
+
+ private boolean isInfoClickable(ScheduleRow row) {
+ return row.getSchedule() != null
+ && (row.getSchedule().isNotStarted() || row.getSchedule().isInProgress());
}
/**
@@ -545,7 +548,7 @@ public class ScheduleRowPresenter extends RowPresenter {
// This row has been deleted.
return;
}
- if (row.isOnAir() && row.isRecordingInProgress() && !row.isStopRecordingRequested()) {
+ if (row.isRecordingInProgress() && !row.isStopRecordingRequested()) {
row.setStopRecordingRequested(true);
mDvrManager.stopRecording(row.getSchedule());
CharSequence deletedInfo = onGetProgramInfoText(row);
@@ -670,10 +673,9 @@ public class ScheduleRowPresenter extends RowPresenter {
hideActionView(viewHolder.mFirstActionContainer, View.GONE);
}
};
- if (mLastFocusedViewId == R.id.action_first_container
- || mLastFocusedViewId == R.id.action_second_container) {
- mLastFocusedViewId = R.id.info_container;
- }
+ mLastFocusedViewId = R.id.info_container;
+ SoftPreconditions.checkState(viewHolder.mInfoContainer.isFocusable(), TAG,
+ "No focusable view in this row: " + viewHolder);
break;
}
View view = viewHolder.view.findViewById(mLastFocusedViewId);
@@ -683,8 +685,10 @@ public class ScheduleRowPresenter extends RowPresenter {
// requestFocus() explicitly.
if (view.hasFocus()) {
viewHolder.mPendingAnimationRunnable.run();
- } else {
+ } else if (view.isFocusable()){
view.requestFocus();
+ } else {
+ viewHolder.view.requestFocus();
}
}
} else {
@@ -737,10 +741,10 @@ public class ScheduleRowPresenter extends RowPresenter {
@ScheduleRowAction
protected int[] getAvailableActions(ScheduleRow row) {
if (row.getSchedule() != null) {
- if (row.isOnAir()) {
- if (row.isRecordingInProgress()) {
- return new int[] {ACTION_STOP_RECORDING};
- } else if (row.isRecordingNotStarted()) {
+ if (row.isRecordingInProgress()) {
+ return new int[]{ACTION_STOP_RECORDING};
+ } else if (row.isOnAir()) {
+ if (row.isRecordingNotStarted()) {
if (canResolveConflict()) {
// The "START" action can change the conflict states.
return new int[] {ACTION_REMOVE_SCHEDULE, ACTION_START_RECORDING};
diff --git a/src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java b/src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java
index 0fb0924d..715ecb8c 100644
--- a/src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java
+++ b/src/com/android/tv/dvr/ui/list/SchedulesHeaderRow.java
@@ -16,12 +16,15 @@
package com.android.tv.dvr.ui.list;
-import com.android.tv.dvr.SeriesRecording;
+import com.android.tv.data.Program;
+import com.android.tv.dvr.data.SeriesRecording;
+
+import java.util.List;
/**
* A base class for the rows for schedules' header.
*/
-public abstract class SchedulesHeaderRow {
+abstract class SchedulesHeaderRow {
private String mTitle;
private String mDescription;
private int mItemCount;
@@ -98,11 +101,20 @@ public abstract class SchedulesHeaderRow {
*/
public static class SeriesRecordingHeaderRow extends SchedulesHeaderRow {
private SeriesRecording mSeriesRecording;
+ private List<Program> mPrograms;
public SeriesRecordingHeaderRow(String title, String description, int itemCount,
- SeriesRecording series) {
+ SeriesRecording series, List<Program> programs) {
super(title, description, itemCount);
mSeriesRecording = series;
+ mPrograms = programs;
+ }
+
+ /**
+ * Returns the list of programs which belong to the series.
+ */
+ public List<Program> getPrograms() {
+ return mPrograms;
}
/**
@@ -119,4 +131,4 @@ public abstract class SchedulesHeaderRow {
mSeriesRecording = seriesRecording;
}
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java b/src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java
index 69c33a96..fe2033ba 100644
--- a/src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java
+++ b/src/com/android/tv/dvr/ui/list/SchedulesHeaderRowPresenter.java
@@ -30,15 +30,14 @@ import android.widget.TextView;
import com.android.tv.R;
import com.android.tv.TvApplication;
-import com.android.tv.dvr.DvrUiHelper;
-import com.android.tv.dvr.SeriesRecording;
-import com.android.tv.dvr.ui.DvrSchedulesActivity;
+import com.android.tv.dvr.data.SeriesRecording;
+import com.android.tv.dvr.ui.DvrUiHelper;
import com.android.tv.dvr.ui.list.SchedulesHeaderRow.SeriesRecordingHeaderRow;
/**
* A base class for RowPresenter for {@link SchedulesHeaderRow}
*/
-public abstract class SchedulesHeaderRowPresenter extends RowPresenter {
+abstract class SchedulesHeaderRowPresenter extends RowPresenter {
private Context mContext;
public SchedulesHeaderRowPresenter(Context context) {
@@ -79,7 +78,7 @@ public abstract class SchedulesHeaderRowPresenter extends RowPresenter {
}
/**
- * A presenter for {@link com.android.tv.dvr.ui.list.SchedulesHeaderRow.DateHeaderRow}.
+ * A presenter for {@link SchedulesHeaderRow.DateHeaderRow}.
*/
public static class DateHeaderRowPresenter extends SchedulesHeaderRowPresenter {
public DateHeaderRowPresenter(Context context) {
@@ -93,7 +92,7 @@ public abstract class SchedulesHeaderRowPresenter extends RowPresenter {
/**
* A ViewHolder for
- * {@link com.android.tv.dvr.ui.list.SchedulesHeaderRow.DateHeaderRow}.
+ * {@link SchedulesHeaderRow.DateHeaderRow}.
*/
public static class DateHeaderRowViewHolder extends SchedulesHeaderRowViewHolder {
public DateHeaderRowViewHolder(Context context, ViewGroup parent) {
@@ -152,9 +151,9 @@ public abstract class SchedulesHeaderRowPresenter extends RowPresenter {
headerViewHolder.mSeriesSettingsButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
- // TODO: pass channel list for settings.
DvrUiHelper.startSeriesSettingsActivity(getContext(),
- header.getSeriesRecording().getId(), null, false, false, false);
+ header.getSeriesRecording().getId(),
+ header.getPrograms(), false, false, false, null);
}
});
headerViewHolder.mToggleStartStopButton.setOnClickListener(new OnClickListener() {
@@ -169,9 +168,9 @@ public abstract class SchedulesHeaderRowPresenter extends RowPresenter {
.build();
TvApplication.getSingletons(getContext()).getDvrManager()
.updateSeriesRecording(seriesRecording);
- // TODO: pass channel list for settings.
DvrUiHelper.startSeriesSettingsActivity(getContext(),
- header.getSeriesRecording().getId(), null, false, false, false);
+ header.getSeriesRecording().getId(),
+ header.getPrograms(), false, false, false, null);
} else {
DvrUiHelper.showCancelAllSeriesRecordingDialog(
(DvrSchedulesActivity) view.getContext(),
@@ -182,11 +181,8 @@ public abstract class SchedulesHeaderRowPresenter extends RowPresenter {
}
private void setTextDrawable(TextView textView, Drawable drawableStart) {
- if (mLtr) {
- textView.setCompoundDrawablesWithIntrinsicBounds(drawableStart, null, null, null);
- } else {
- textView.setCompoundDrawablesWithIntrinsicBounds(null, null, drawableStart, null);
- }
+ textView.setCompoundDrawablesRelativeWithIntrinsicBounds(drawableStart, null, null,
+ null);
}
/**
diff --git a/src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java b/src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java
index 3b493774..6b6de8b8 100644
--- a/src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java
+++ b/src/com/android/tv/dvr/ui/list/SeriesScheduleRowAdapter.java
@@ -31,8 +31,8 @@ import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.Program;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.ScheduledRecording;
-import com.android.tv.dvr.SeriesRecording;
+import com.android.tv.dvr.data.ScheduledRecording;
+import com.android.tv.dvr.data.SeriesRecording;
import com.android.tv.dvr.ui.list.SchedulesHeaderRow.SeriesRecordingHeaderRow;
import com.android.tv.util.Utils;
@@ -46,7 +46,7 @@ import java.util.Map;
* An adapter for series schedule row.
*/
@TargetApi(Build.VERSION_CODES.N)
-public class SeriesScheduleRowAdapter extends ScheduleRowAdapter {
+class SeriesScheduleRowAdapter extends ScheduleRowAdapter {
private static final String TAG = "SeriesRowAdapter";
private static final boolean DEBUG = false;
@@ -96,7 +96,7 @@ public class SeriesScheduleRowAdapter extends ScheduleRowAdapter {
Collections.sort(sortedPrograms);
List<EpisodicProgramRow> rows = new ArrayList<>();
mHeaderRow = new SeriesRecordingHeaderRow(mSeriesRecording.getTitle(),
- null, sortedPrograms.size(), mSeriesRecording);
+ null, sortedPrograms.size(), mSeriesRecording, programs);
for (Program program : sortedPrograms) {
ScheduledRecording schedule =
mDataManager.getScheduledRecordingForProgramId(program.getId());
@@ -145,7 +145,7 @@ public class SeriesScheduleRowAdapter extends ScheduleRowAdapter {
if (index != -1) {
EpisodicProgramRow row = (EpisodicProgramRow) get(index);
if (!row.isStartRecordingRequested()) {
- row.setSchedule(schedule);
+ setScheduleToRow(row, schedule);
notifyArrayItemRangeChanged(index, 1);
}
}
@@ -195,12 +195,10 @@ public class SeriesScheduleRowAdapter extends ScheduleRowAdapter {
if (!isStartOrStopRequested()) {
executePendingUpdate();
}
- row.setSchedule(schedule);
+ setScheduleToRow(row, schedule);
}
- } else if (willBeKept(schedule)) {
- row.setSchedule(schedule);
} else {
- row.setSchedule(null);
+ setScheduleToRow(row, schedule);
}
notifyArrayItemRangeChanged(index, 1);
}
@@ -213,6 +211,14 @@ public class SeriesScheduleRowAdapter extends ScheduleRowAdapter {
}
}
+ private void setScheduleToRow(ScheduleRow row, ScheduledRecording schedule) {
+ if (schedule != null && willBeKept(schedule)) {
+ row.setSchedule(schedule);
+ } else {
+ row.setSchedule(null);
+ }
+ }
+
private int findRowIndexByProgramId(long programId) {
for (int i = 0; i < size(); i++) {
Object item = get(i);
diff --git a/src/com/android/tv/dvr/ui/list/SeriesScheduleRowPresenter.java b/src/com/android/tv/dvr/ui/list/SeriesScheduleRowPresenter.java
index 5d88579a..c8503e0d 100644
--- a/src/com/android/tv/dvr/ui/list/SeriesScheduleRowPresenter.java
+++ b/src/com/android/tv/dvr/ui/list/SeriesScheduleRowPresenter.java
@@ -22,13 +22,13 @@ import android.view.ViewGroup;
import com.android.tv.R;
import com.android.tv.common.SoftPreconditions;
-import com.android.tv.dvr.DvrUiHelper;
+import com.android.tv.dvr.ui.DvrUiHelper;
import com.android.tv.util.Utils;
/**
* A RowPresenter for series schedule row.
*/
-public class SeriesScheduleRowPresenter extends ScheduleRowPresenter {
+class SeriesScheduleRowPresenter extends ScheduleRowPresenter {
private static final String TAG = "SeriesRowPresenter";
private boolean mLtr;
@@ -74,13 +74,8 @@ public class SeriesScheduleRowPresenter extends ScheduleRowPresenter {
viewHolder.getProgramTitleView().setCompoundDrawablePadding(getContext()
.getResources().getDimensionPixelOffset(
R.dimen.dvr_schedules_warning_icon_padding));
- if (mLtr) {
- viewHolder.getProgramTitleView().setCompoundDrawablesWithIntrinsicBounds(
- R.drawable.ic_warning_gray600_36dp, 0, 0, 0);
- } else {
- viewHolder.getProgramTitleView().setCompoundDrawablesWithIntrinsicBounds(
- 0, 0, R.drawable.ic_warning_gray600_36dp, 0);
- }
+ viewHolder.getProgramTitleView().setCompoundDrawablesRelativeWithIntrinsicBounds(
+ R.drawable.ic_warning_gray600_36dp, 0, 0, 0);
} else {
viewHolder.getProgramTitleView().setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
}
@@ -88,9 +83,7 @@ public class SeriesScheduleRowPresenter extends ScheduleRowPresenter {
@Override
protected void onInfoClicked(ScheduleRow row) {
- if (row.getSchedule() != null) {
- DvrUiHelper.startSchedulesActivity(getContext(), row.getSchedule());
- }
+ DvrUiHelper.startSchedulesActivity(getContext(), row.getSchedule());
}
@Override
diff --git a/src/com/android/tv/dvr/DvrPlaybackActivity.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java
index 5deda44a..2437d1f5 100644
--- a/src/com/android/tv/dvr/DvrPlaybackActivity.java
+++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackActivity.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr;
+package com.android.tv.dvr.ui.playback;
import android.app.Activity;
import android.content.Intent;
@@ -24,7 +24,7 @@ import android.util.Log;
import com.android.tv.R;
import com.android.tv.TvApplication;
-import com.android.tv.dvr.ui.DvrPlaybackOverlayFragment;
+import com.android.tv.dvr.data.RecordedProgram;
/**
* Activity to play a {@link RecordedProgram}.
diff --git a/src/com/android/tv/dvr/ui/DvrPlaybackCardPresenter.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackCardPresenter.java
index 8c4c856c..4bd121b1 100644
--- a/src/com/android/tv/dvr/ui/DvrPlaybackCardPresenter.java
+++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackCardPresenter.java
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.playback;
import android.content.Context;
import android.content.Intent;
-import android.content.res.Resources;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
@@ -26,22 +25,25 @@ import android.view.View.OnClickListener;
import android.view.ViewGroup;
import com.android.tv.R;
-import com.android.tv.dvr.RecordedProgram;
-import com.android.tv.dvr.DvrPlaybackActivity;
+import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.dvr.ui.browse.RecordedProgramPresenter;
+import com.android.tv.dvr.ui.browse.RecordingCardView;
import com.android.tv.util.Utils;
/**
* This class is used to generate Views and bind Objects for related recordings in DVR playback.
*/
-public class DvrPlaybackCardPresenter extends RecordedProgramPresenter {
+class DvrPlaybackCardPresenter extends RecordedProgramPresenter {
private static final String TAG = "DvrPlaybackCardPresenter";
private static final boolean DEBUG = false;
private final int mRelatedRecordingCardWidth;
private final int mRelatedRecordingCardHeight;
+ private final DvrPlaybackOverlayFragment mFragment;
- DvrPlaybackCardPresenter(Context context) {
+ DvrPlaybackCardPresenter(Context context, DvrPlaybackOverlayFragment fragment) {
super(context);
+ mFragment = fragment;
mRelatedRecordingCardWidth =
context.getResources().getDimensionPixelSize(R.dimen.dvr_related_recordings_width);
mRelatedRecordingCardHeight =
@@ -50,9 +52,8 @@ public class DvrPlaybackCardPresenter extends RecordedProgramPresenter {
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
- Resources res = parent.getResources();
RecordingCardView view = new RecordingCardView(
- getContext(), mRelatedRecordingCardWidth, mRelatedRecordingCardHeight);
+ getContext(), mRelatedRecordingCardWidth, mRelatedRecordingCardHeight, true);
return new ViewHolder(view);
}
@@ -61,6 +62,10 @@ public class DvrPlaybackCardPresenter extends RecordedProgramPresenter {
return new OnClickListener() {
@Override
public void onClick(View v) {
+ // Disable fading of overlay fragment to prevent the layout blinking while updating
+ // new playback states and info. The fading enabled status will be reset during
+ // playback state changing, in DvrPlaybackControlHelper.onStateChanged().
+ mFragment.setFadingEnabled(false);
long programId = ((RecordedProgram) v.getTag()).getId();
if (DEBUG) Log.d(TAG, "Play Related Recording:" + programId);
Intent intent = new Intent(getContext(), DvrPlaybackActivity.class);
@@ -69,14 +74,4 @@ public class DvrPlaybackCardPresenter extends RecordedProgramPresenter {
}
};
}
-
- @Override
- protected String getDescription(RecordedProgram program) {
- String description = program.getDescription();
- if (TextUtils.isEmpty(description)) {
- description =
- getContext().getResources().getString(R.string.dvr_msg_no_program_description);
- }
- return description;
- }
} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/ui/DvrPlaybackControlHelper.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackControlHelper.java
index 0bc4ecb1..4658a328 100644
--- a/src/com/android/tv/dvr/ui/DvrPlaybackControlHelper.java
+++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackControlHelper.java
@@ -14,19 +14,26 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.playback;
import android.app.Activity;
+import android.content.Context;
import android.graphics.drawable.Drawable;
import android.media.MediaMetadata;
import android.media.session.MediaController;
import android.media.session.MediaController.TransportControls;
import android.media.session.PlaybackState;
+import android.media.tv.TvTrackInfo;
+import android.os.Bundle;
import android.support.v17.leanback.app.PlaybackControlGlue;
import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter;
import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
import android.support.v17.leanback.widget.OnActionClickedListener;
import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.PlaybackControlsRow.ClosedCaptioningAction;
+import android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction;
import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
import android.support.v17.leanback.widget.RowPresenter;
import android.text.TextUtils;
@@ -37,19 +44,18 @@ import android.view.View;
import com.android.tv.R;
import com.android.tv.util.TimeShiftUtils;
+import java.util.ArrayList;
+
/**
* A helper class to assist {@link DvrPlaybackOverlayFragment} to manage its controls row and
* send command to the media controller. It also helps to update playback states displayed in the
* fragment according to information the media session provides.
*/
-public class DvrPlaybackControlHelper extends PlaybackControlGlue {
+class DvrPlaybackControlHelper extends PlaybackControlGlue {
private static final String TAG = "DvrPlaybackControlHelper";
private static final boolean DEBUG = false;
- /**
- * Indicates the ID of the media under playback is unknown.
- */
- public static int UNKNOWN_MEDIA_ID = -1;
+ private static final int AUDIO_ACTION_ID = 1001;
private int mPlaybackState = PlaybackState.STATE_NONE;
private int mPlaybackSpeedLevel;
@@ -60,6 +66,9 @@ public class DvrPlaybackControlHelper extends PlaybackControlGlue {
private final MediaController.Callback mMediaControllerCallback = new MediaControllerCallback();
private final TransportControls mTransportControls;
private final int mExtraPaddingTopForNoDescription;
+ private final ArrayObjectAdapter mSecondaryActionsAdapter;
+ private final MultiAction mClosedCaptioningAction;
+ private final MultiAction mMultiAudioAction;
public DvrPlaybackControlHelper(Activity activity, DvrPlaybackOverlayFragment overlayFragment) {
super(activity, overlayFragment, new int[TimeShiftUtils.MAX_SPEED_LEVEL + 1]);
@@ -68,11 +77,15 @@ public class DvrPlaybackControlHelper extends PlaybackControlGlue {
mTransportControls = mMediaController.getTransportControls();
mExtraPaddingTopForNoDescription = activity.getResources()
.getDimensionPixelOffset(R.dimen.dvr_playback_controls_extra_padding_top);
+ mSecondaryActionsAdapter = new ArrayObjectAdapter(new ControlButtonPresenterSelector());
+ mClosedCaptioningAction = new ClosedCaptioningAction(activity);
+ mMultiAudioAction = new MultiAudioAction(activity);
}
@Override
public PlaybackControlsRowPresenter createControlsRowAndPresenter() {
PlaybackControlsRow controlsRow = new PlaybackControlsRow(this);
+ controlsRow.setSecondaryActionsAdapter(mSecondaryActionsAdapter);
setControlsRow(controlsRow);
AbstractDetailsDescriptionPresenter detailsPresenter =
new AbstractDetailsDescriptionPresenter() {
@@ -116,7 +129,21 @@ public class DvrPlaybackControlHelper extends PlaybackControlGlue {
@Override
public void onActionClicked(Action action) {
if (mReadyToControl) {
- DvrPlaybackControlHelper.super.onActionClicked(action);
+ int trackType;
+ if (action.getId() == mClosedCaptioningAction.getId()) {
+ trackType = TvTrackInfo.TYPE_SUBTITLE;
+ } else if (action.getId() == AUDIO_ACTION_ID) {
+ trackType = TvTrackInfo.TYPE_AUDIO;
+ } else {
+ DvrPlaybackControlHelper.super.onActionClicked(action);
+ return;
+ }
+ ArrayList<TvTrackInfo> trackInfos =
+ ((DvrPlaybackOverlayFragment) getFragment()).getTracks(trackType);
+ if (!trackInfos.isEmpty()) {
+ showSideFragment(trackInfos, ((DvrPlaybackOverlayFragment)
+ getFragment()).getSelectedTrackId(trackType));
+ }
}
}
});
@@ -158,10 +185,10 @@ public class DvrPlaybackControlHelper extends PlaybackControlGlue {
/**
* Returns the ID of the media under playback.
*/
- public long getMediaId() {
+ public String getMediaId() {
MediaMetadata mediaMetadata = mMediaController.getMetadata();
- return mediaMetadata == null ? UNKNOWN_MEDIA_ID
- : mediaMetadata.getLong(MediaMetadata.METADATA_KEY_MEDIA_ID);
+ return mediaMetadata == null ? null
+ : mediaMetadata.getString(MediaMetadata.METADATA_KEY_MEDIA_ID);
}
@Override
@@ -217,6 +244,37 @@ public class DvrPlaybackControlHelper extends PlaybackControlGlue {
mMediaController.unregisterCallback(mMediaControllerCallback);
}
+ /**
+ * Update the secondary controls row.
+ * @param hasClosedCaption {@code true} to show the closed caption selection button,
+ * {@code false} to hide it.
+ * @param hasMultiAudio {@code true} to show the audio track selection button,
+ * {@code false} to hide it.
+ */
+ public void updateSecondaryRow(boolean hasClosedCaption, boolean hasMultiAudio) {
+ if (hasClosedCaption) {
+ if (mSecondaryActionsAdapter.indexOf(mClosedCaptioningAction) < 0) {
+ mSecondaryActionsAdapter.add(0, mClosedCaptioningAction);
+ }
+ } else {
+ mSecondaryActionsAdapter.remove(mClosedCaptioningAction);
+ }
+ if (hasMultiAudio) {
+ if (mSecondaryActionsAdapter.indexOf(mMultiAudioAction) < 0) {
+ mSecondaryActionsAdapter.add(mMultiAudioAction);
+ }
+ } else {
+ mSecondaryActionsAdapter.remove(mMultiAudioAction);
+ }
+ }
+
+ /**
+ * Returns if the secondary controls row has any buttons and thus should be shown.
+ */
+ public boolean hasSecondaryRow() {
+ return mSecondaryActionsAdapter.size() != 0;
+ }
+
@Override
protected void startPlayback(int speedId) {
if (getCurrentSpeedId() == speedId) {
@@ -251,6 +309,14 @@ public class DvrPlaybackControlHelper extends PlaybackControlGlue {
// Do nothing.
}
+ /**
+ * Notifies closed caption being enabled/disabled to update related UI.
+ */
+ void onSubtitleTrackStateChanged(boolean enabled) {
+ mClosedCaptioningAction.setIndex(enabled ?
+ ClosedCaptioningAction.ON : ClosedCaptioningAction.OFF);
+ }
+
private void onStateChanged(int state, long positionMs, int speedLevel) {
if (DEBUG) Log.d(TAG, "onStateChanged");
getControlsRow().setCurrentTime((int) positionMs);
@@ -297,6 +363,19 @@ public class DvrPlaybackControlHelper extends PlaybackControlGlue {
onStateChanged();
}
+ private void showSideFragment(ArrayList<TvTrackInfo> trackInfos, String selectedTrackId) {
+ Bundle args = new Bundle();
+ args.putParcelableArrayList(DvrPlaybackSideFragment.TRACK_INFOS, trackInfos);
+ args.putString(DvrPlaybackSideFragment.SELECTED_TRACK_ID, selectedTrackId);
+ DvrPlaybackSideFragment sideFragment = new DvrPlaybackSideFragment();
+ sideFragment.setArguments(args);
+ getFragment().getFragmentManager().beginTransaction()
+ .hide(getFragment())
+ .replace(R.id.dvr_playback_side_fragment, sideFragment)
+ .addToBackStack(null)
+ .commit();
+ }
+
private class MediaControllerCallback extends MediaController.Callback {
@Override
public void onPlaybackStateChanged(PlaybackState state) {
@@ -310,4 +389,11 @@ public class DvrPlaybackControlHelper extends PlaybackControlGlue {
((DvrPlaybackOverlayFragment) getFragment()).onMediaControllerUpdated();
}
}
+
+ private static class MultiAudioAction extends MultiAction {
+ MultiAudioAction(Context context) {
+ super(AUDIO_ACTION_ID);
+ setDrawables(new Drawable[]{context.getDrawable(R.drawable.ic_tvoption_multi_track)});
+ }
+ }
} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/DvrPlaybackMediaSessionHelper.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java
index 9759a856..843d2dbe 100644
--- a/src/com/android/tv/dvr/DvrPlaybackMediaSessionHelper.java
+++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr;
+package com.android.tv.dvr.ui.playback;
import android.app.Activity;
import android.content.Intent;
@@ -31,14 +31,16 @@ 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.ui.DvrPlaybackOverlayFragment;
+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;
-public class DvrPlaybackMediaSessionHelper {
+class DvrPlaybackMediaSessionHelper {
private static final String TAG = "DvrPlaybackMediaSessionHelper";
private static final boolean DEBUG = false;
@@ -102,6 +104,7 @@ public class DvrPlaybackMediaSessionHelper {
}
if (mMediaSession != null) {
mMediaSession.release();
+ mMediaSession = null;
}
}
@@ -179,83 +182,88 @@ public class DvrPlaybackMediaSessionHelper {
cardTitleText = (channel != null) ? channel.getDisplayName()
: mActivity.getString(R.string.no_program_information);
}
- updateMediaMetadata(program.getId(), cardTitleText, program.getDescription(),
- mProgramDurationMs, null, 0);
+ 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, cardTitleText, program.getDescription(),
- mProgramDurationMs, null, posterArtUri);
+ updatePosterArt(program, currentMetadata, null, posterArtUri);
mMediaSession.setActive(true);
}
- private void updatePosterArt(RecordedProgram program, String cardTitleText,
- String cardSubtitleText, long duration,
+ private void updatePosterArt(RecordedProgram program, MediaMetadata currentMetadata,
@Nullable Bitmap posterArt, @Nullable String posterArtUri) {
if (posterArt != null) {
- updateMediaMetadata(program.getId(), cardTitleText,
- cardSubtitleText, duration, posterArt, 0);
+ updateMetadataImageInfo(program, currentMetadata, posterArt, 0);
} else if (posterArtUri != null) {
ImageLoader.loadBitmap(mActivity, posterArtUri, mNowPlayingCardWidth,
- mNowPlayingCardHeight, new ProgramPosterArtCallback(
- mActivity, program, cardTitleText, cardSubtitleText, duration));
+ mNowPlayingCardHeight,
+ new ProgramPosterArtCallback(mActivity, program, currentMetadata));
} else {
- updateMediaMetadata(program.getId(), cardTitleText,
- cardSubtitleText, duration, null, R.drawable.default_now_card);
+ updateMetadataImageInfo(program, currentMetadata, null, R.drawable.default_now_card);
}
}
private class ProgramPosterArtCallback extends
ImageLoader.ImageLoaderCallback<Activity> {
- private RecordedProgram mRecordedProgram;
- private String mCardTitleText;
- private String mCardSubtitleText;
- private long mDuration;
+ private final RecordedProgram mRecordedProgram;
+ private final MediaMetadata mCurrentMetadata;
public ProgramPosterArtCallback(Activity activity, RecordedProgram program,
- String cardTitleText, String cardSubtitleText, long duration) {
+ MediaMetadata metadata) {
super(activity);
mRecordedProgram = program;
- mCardTitleText = cardTitleText;
- mCardSubtitleText = cardSubtitleText;
- mDuration = duration;
+ mCurrentMetadata = metadata;
}
@Override
public void onBitmapLoaded(Activity activity, @Nullable Bitmap posterArt) {
if (isCurrentProgram(mRecordedProgram)) {
- updatePosterArt(mRecordedProgram, mCardTitleText,
- mCardSubtitleText, mDuration, posterArt, null);
+ updatePosterArt(mRecordedProgram, mCurrentMetadata, posterArt, null);
}
}
}
- private void updateMediaMetadata(final long programId, final String title,
- final String subtitle, final long duration,
- final Bitmap posterArt, final int imageResId) {
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... arg0) {
- MediaMetadata.Builder builder = new MediaMetadata.Builder();
- builder.putLong(MediaMetadata.METADATA_KEY_MEDIA_ID, programId)
- .putString(MediaMetadata.METADATA_KEY_TITLE, title)
- .putLong(MediaMetadata.METADATA_KEY_DURATION, duration);
- if (subtitle != null) {
- builder.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, subtitle);
- }
- Bitmap programPosterArt = posterArt;
- if (programPosterArt == null && imageResId != 0) {
- programPosterArt =
- BitmapFactory.decodeResource(mActivity.getResources(), imageResId);
- }
- if (programPosterArt != null) {
- builder.putBitmap(MediaMetadata.METADATA_KEY_ART, programPosterArt);
- }
+ 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());
- return null;
+ } 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();
}
- }.execute();
+ }
}
// An event was triggered by MediaController.TransportControls and must be handled here.
diff --git a/src/com/android/tv/dvr/ui/DvrPlaybackOverlayFragment.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java
index 51ec93b8..ff907182 100644
--- a/src/com/android/tv/dvr/ui/DvrPlaybackOverlayFragment.java
+++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java
@@ -14,13 +14,14 @@
* limitations under the License
*/
-package com.android.tv.dvr.ui;
+package com.android.tv.dvr.ui.playback;
import android.content.Context;
import android.content.Intent;
import android.graphics.Point;
import android.hardware.display.DisplayManager;
import android.media.tv.TvContentRating;
+import android.media.tv.TvTrackInfo;
import android.os.Bundle;
import android.media.session.PlaybackState;
import android.media.tv.TvInputManager;
@@ -30,7 +31,6 @@ import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.ClassPresenterSelector;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.ListRowPresenter;
import android.support.v17.leanback.widget.PlaybackControlsRow;
import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
import android.support.v17.leanback.widget.SinglePresenterSelector;
@@ -38,20 +38,25 @@ import android.view.Display;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
-import android.text.TextUtils;
import android.util.Log;
import com.android.tv.R;
import com.android.tv.TvApplication;
import com.android.tv.data.BaseProgram;
-import com.android.tv.dvr.RecordedProgram;
import com.android.tv.dialog.PinDialogFragment;
import com.android.tv.dvr.DvrDataManager;
-import com.android.tv.dvr.DvrPlayer;
-import com.android.tv.dvr.DvrPlaybackMediaSessionHelper;
+import com.android.tv.dvr.data.RecordedProgram;
+import com.android.tv.dvr.data.SeriesRecording;
+import com.android.tv.dvr.ui.SortedArrayAdapter;
+import com.android.tv.dvr.ui.browse.DvrListRowPresenter;
import com.android.tv.parental.ContentRatingsManager;
+import com.android.tv.util.TvSettings;
+import com.android.tv.util.TvTrackInfoUtils;
import com.android.tv.util.Utils;
+import java.util.List;
+import java.util.ArrayList;
+
public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
// TODO: Handles audio focus. Deals with block and ratings.
private static final String TAG = "DvrPlaybackOverlayFragment";
@@ -62,6 +67,7 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
// mProgram is only used to store program from intent. Don't use it elsewhere.
private RecordedProgram mProgram;
+ private DvrPlayer mDvrPlayer;
private DvrPlaybackMediaSessionHelper mMediaSessionHelper;
private DvrPlaybackControlHelper mPlaybackControlHelper;
private ArrayObjectAdapter mRowsAdapter;
@@ -72,19 +78,30 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
private TvView mTvView;
private View mBlockScreenView;
private ListRow mRelatedRecordingsRow;
- private int mExtraPaddingNoRelatedRow;
+ private int mPaddingWithoutRelatedRow;
+ private int mPaddingWithoutSecondaryRow;
private int mWindowWidth;
private int mWindowHeight;
private float mAppliedAspectRatio;
private float mWindowAspectRatio;
private boolean mPinChecked;
+ private DvrPlayer.OnTrackSelectedListener mOnSubtitleTrackSelectedListener =
+ new DvrPlayer.OnTrackSelectedListener() {
+ @Override
+ public void onTrackSelected(String selectedTrackId) {
+ mPlaybackControlHelper.onSubtitleTrackStateChanged(selectedTrackId != null);
+ mRowsAdapter.notifyArrayItemRangeChanged(0, 1);
+ }
+ };
@Override
public void onCreate(Bundle savedInstanceState) {
if (DEBUG) Log.d(TAG, "onCreate");
super.onCreate(savedInstanceState);
- mExtraPaddingNoRelatedRow = getActivity().getResources()
- .getDimensionPixelOffset(R.dimen.dvr_playback_fragment_extra_padding_top);
+ mPaddingWithoutRelatedRow = getActivity().getResources()
+ .getDimensionPixelOffset(R.dimen.dvr_playback_overlay_padding_top_no_related_row);
+ mPaddingWithoutSecondaryRow = getActivity().getResources()
+ .getDimensionPixelOffset(R.dimen.dvr_playback_overlay_padding_top_no_secondary_row);
mDvrDataManager = TvApplication.getSingletons(getActivity()).getDvrDataManager();
mContentRatingsManager = TvApplication.getSingletons(getContext())
.getTvInputManagerHelper().getContentRatingsManager();
@@ -110,13 +127,31 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
super.onActivityCreated(savedInstanceState);
mTvView = (TvView) getActivity().findViewById(R.id.dvr_tv_view);
mBlockScreenView = getActivity().findViewById(R.id.block_screen);
+ mDvrPlayer = new DvrPlayer(mTvView);
mMediaSessionHelper = new DvrPlaybackMediaSessionHelper(
- getActivity(), MEDIA_SESSION_TAG, new DvrPlayer(mTvView), this);
+ getActivity(), MEDIA_SESSION_TAG, mDvrPlayer, this);
mPlaybackControlHelper = new DvrPlaybackControlHelper(getActivity(), this);
setUpRows();
- preparePlayback(getActivity().getIntent());
- DvrPlayer dvrPlayer = mMediaSessionHelper.getDvrPlayer();
- dvrPlayer.setAspectRatioChangedListener(new DvrPlayer.AspectRatioChangedListener() {
+ mDvrPlayer.setOnTracksAvailabilityChangedListener(
+ new DvrPlayer.OnTracksAvailabilityChangedListener() {
+ @Override
+ public void onTracksAvailabilityChanged(boolean hasClosedCaption,
+ boolean hasMultiAudio) {
+ mPlaybackControlHelper.updateSecondaryRow(hasClosedCaption, hasMultiAudio);
+ if (hasClosedCaption) {
+ mDvrPlayer.setOnTrackSelectedListener(TvTrackInfo.TYPE_SUBTITLE,
+ mOnSubtitleTrackSelectedListener);
+ selectBestMatchedTrack(TvTrackInfo.TYPE_SUBTITLE);
+ } else {
+ mDvrPlayer.setOnTrackSelectedListener(TvTrackInfo.TYPE_SUBTITLE, null);
+ }
+ if (hasMultiAudio) {
+ selectBestMatchedTrack(TvTrackInfo.TYPE_AUDIO);
+ }
+ onMediaControllerUpdated();
+ }
+ });
+ mDvrPlayer.setOnAspectRatioChangedListener(new DvrPlayer.OnAspectRatioChangedListener() {
@Override
public void onAspectRatioChanged(float videoAspectRatio) {
updateAspectRatio(videoAspectRatio);
@@ -124,7 +159,7 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
});
mPinChecked = getActivity().getIntent()
.getBooleanExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_PIN_CHECKED, false);
- dvrPlayer.setContentBlockedListener(new DvrPlayer.ContentBlockedListener() {
+ mDvrPlayer.setOnContentBlockedListener(new DvrPlayer.OnContentBlockedListener() {
@Override
public void onContentBlocked(TvContentRating rating) {
if (mPinChecked) {
@@ -149,6 +184,7 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
.show(getActivity().getFragmentManager(), PinDialogFragment.DIALOG_TAG);
}
});
+ preparePlayback(getActivity().getIntent());
}
@Override
@@ -200,6 +236,9 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
updateAspectRatio(mAppliedAspectRatio);
}
+ /**
+ * Returns next recorded episode in the same series as now playing program.
+ */
public RecordedProgram getNextEpisode(RecordedProgram program) {
int position = mRelatedRecordingsRowAdapter.findInsertPosition(program);
if (position == mRelatedRecordingsRowAdapter.size()) {
@@ -209,16 +248,92 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
}
}
+ /**
+ * Returns the tracks of the give type of the current playback.
+
+ * @param trackType Should be {@link TvTrackInfo#TYPE_SUBTITLE}
+ * or {@link TvTrackInfo#TYPE_AUDIO}. Or returns {@code null}.
+ */
+ public ArrayList<TvTrackInfo> getTracks(int trackType) {
+ if (trackType == TvTrackInfo.TYPE_AUDIO) {
+ return mDvrPlayer.getAudioTracks();
+ } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
+ return mDvrPlayer.getSubtitleTracks();
+ }
+ return null;
+ }
+
+ /**
+ * Returns the ID of the selected track of the given type.
+ */
+ public String getSelectedTrackId(int trackType) {
+ return mDvrPlayer.getSelectedTrackId(trackType);
+ }
+
+ /**
+ * Returns the language setting of the given track type.
+
+ * @param trackType Should be {@link TvTrackInfo#TYPE_SUBTITLE}
+ * or {@link TvTrackInfo#TYPE_AUDIO}.
+ * @return {@code null} if no language has been set for the given track type.
+ */
+ TvTrackInfo getTrackSetting(int trackType) {
+ return TvSettings.getDvrPlaybackTrackSettings(getContext(), trackType);
+ }
+
+ /**
+ * Selects the given audio or subtitle track for DVR playback.
+ * @param trackType Should be {@link TvTrackInfo#TYPE_SUBTITLE}
+ * or {@link TvTrackInfo#TYPE_AUDIO}.
+ * @param selectedTrack {@code null} to disable the audio or subtitle track according to
+ * trackType.
+ */
+ void selectTrack(int trackType, TvTrackInfo selectedTrack) {
+ if (mDvrPlayer.isPlaybackPrepared()) {
+ mDvrPlayer.selectTrack(trackType, selectedTrack);
+ }
+ }
+
+ /**
+ * Notifies the content of controls row or related recordings row is changed and the UI should
+ * be updated according to the change.
+ */
void onMediaControllerUpdated() {
- mRowsAdapter.notifyArrayItemRangeChanged(0, 1);
+ updateVerticalPosition();
+ mRowsAdapter.notifyArrayItemRangeChanged(0, 2);
+ }
+
+ private void selectBestMatchedTrack(int trackType) {
+ TvTrackInfo selectedTrack = getTrackSetting(trackType);
+ if (selectedTrack != null) {
+ TvTrackInfo bestMatchedTrack = TvTrackInfoUtils.getBestTrackInfo(getTracks(trackType),
+ selectedTrack.getId(), selectedTrack.getLanguage(),
+ trackType == TvTrackInfo.TYPE_AUDIO ? selectedTrack.getAudioChannelCount() : 0);
+ if (bestMatchedTrack != null && (trackType == TvTrackInfo.TYPE_AUDIO || Utils
+ .isEqualLanguage(bestMatchedTrack.getLanguage(),
+ selectedTrack.getLanguage()))) {
+ selectTrack(trackType, bestMatchedTrack);
+ return;
+ }
+ }
+ if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
+ // Disables closed captioning if there's no matched language.
+ selectTrack(TvTrackInfo.TYPE_SUBTITLE, null);
+ }
}
private void updateAspectRatio(float videoAspectRatio) {
+ if (videoAspectRatio <= 0) {
+ // We don't have video's width or height information, use window's aspect ratio.
+ videoAspectRatio = mWindowAspectRatio;
+ }
if (Math.abs(mAppliedAspectRatio - videoAspectRatio) < DISPLAY_ASPECT_RATIO_EPSILON) {
// No need to change
return;
}
- if (videoAspectRatio < mWindowAspectRatio) {
+ if (Math.abs(mWindowAspectRatio - videoAspectRatio) < DISPLAY_ASPECT_RATIO_EPSILON) {
+ ((ViewGroup) mTvView.getParent()).setPadding(0, 0, 0, 0);
+ } else if (videoAspectRatio < mWindowAspectRatio) {
int newPadding = (mWindowWidth - Math.round(mWindowHeight * videoAspectRatio)) / 2;
((ViewGroup) mTvView.getParent()).setPadding(newPadding, 0, newPadding, 0);
} else {
@@ -230,6 +345,7 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
private void preparePlayback(Intent intent) {
mMediaSessionHelper.setupPlayback(mProgram, getSeekTimeFromIntent(intent));
+ mPlaybackControlHelper.updateSecondaryRow(false, false);
getActivity().getMediaController().getTransportControls().prepare();
updateRelatedRecordingsRow();
}
@@ -239,24 +355,35 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
mRelatedRecordingsRowAdapter.clear();
long programId = mProgram.getId();
String seriesId = mProgram.getSeriesId();
- if (!TextUtils.isEmpty(seriesId)) {
+ SeriesRecording seriesRecording = mDvrDataManager.getSeriesRecording(seriesId);
+ if (seriesRecording != null) {
if (DEBUG) Log.d(TAG, "Update related recordings with:" + seriesId);
- for (RecordedProgram program : mDvrDataManager.getRecordedPrograms()) {
- if (seriesId.equals(program.getSeriesId()) && programId != program.getId()) {
+ List<RecordedProgram> relatedPrograms =
+ mDvrDataManager.getRecordedPrograms(seriesRecording.getId());
+ for (RecordedProgram program : relatedPrograms) {
+ if (programId != program.getId()) {
mRelatedRecordingsRowAdapter.add(program);
}
}
}
- View view = getView();
if (mRelatedRecordingsRowAdapter.size() == 0) {
mRowsAdapter.remove(mRelatedRecordingsRow);
- view.setPadding(view.getPaddingLeft(), mExtraPaddingNoRelatedRow,
- view.getPaddingRight(), view.getPaddingBottom());
} else if (wasEmpty){
mRowsAdapter.add(mRelatedRecordingsRow);
- view.setPadding(view.getPaddingLeft(), 0,
- view.getPaddingRight(), view.getPaddingBottom());
}
+ onMediaControllerUpdated();
+ }
+
+ private void updateVerticalPosition() {
+ int verticalPadding = 0;
+ verticalPadding +=
+ mRelatedRecordingsRowAdapter.size() == 0 ? mPaddingWithoutRelatedRow : 0;
+ verticalPadding +=
+ mPlaybackControlHelper.hasSecondaryRow() ? 0 : mPaddingWithoutSecondaryRow;
+ if (DEBUG) Log.d(TAG, "New controls padding: " + verticalPadding);
+ View view = getView();
+ view.setPadding(view.getPaddingLeft(), verticalPadding,
+ view.getPaddingRight(), view.getPaddingBottom());
}
private void setUpRows() {
@@ -265,7 +392,7 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
ClassPresenterSelector selector = new ClassPresenterSelector();
selector.addClassPresenter(PlaybackControlsRow.class, controlsRowPresenter);
- selector.addClassPresenter(ListRow.class, new ListRowPresenter());
+ selector.addClassPresenter(ListRow.class, new DvrListRowPresenter(getContext()));
mRowsAdapter = new ArrayObjectAdapter(selector);
mRowsAdapter.add(mPlaybackControlHelper.getControlsRow());
@@ -274,7 +401,7 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
}
private ListRow getRelatedRecordingsRow() {
- mRelatedRecordingCardPresenter = new DvrPlaybackCardPresenter(getActivity());
+ mRelatedRecordingCardPresenter = new DvrPlaybackCardPresenter(getActivity(), this);
mRelatedRecordingsRowAdapter = new RelatedRecordingsAdapter(mRelatedRecordingCardPresenter);
HeaderItem header = new HeaderItem(0,
getActivity().getString(R.string.dvr_playback_related_recordings));
@@ -297,7 +424,7 @@ public class DvrPlaybackOverlayFragment extends PlaybackOverlayFragment {
}
@Override
- long getId(BaseProgram item) {
+ public long getId(BaseProgram item) {
return item.getId();
}
}
diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackSideFragment.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackSideFragment.java
new file mode 100644
index 00000000..e49870f1
--- /dev/null
+++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackSideFragment.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tv.dvr.ui.playback;
+
+import android.media.tv.TvTrackInfo;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v17.leanback.app.GuidedStepFragment;
+import android.support.v17.leanback.widget.GuidedAction;
+import android.text.TextUtils;
+import android.transition.Transition;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.tv.R;
+import com.android.tv.util.TvSettings;
+
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Fragment for DVR playback closed-caption/multi-audio settings.
+ */
+public class DvrPlaybackSideFragment extends GuidedStepFragment {
+ /**
+ * The tag for passing track infos to side fragments.
+ */
+ public static final String TRACK_INFOS = "dvr_key_track_infos";
+ /**
+ * The tag for passing selected track's ID to side fragments.
+ */
+ public static final String SELECTED_TRACK_ID = "dvr_key_selected_track_id";
+
+ private static final int ACTION_ID_NO_SUBTITLE = -1;
+ private static final int CHECK_SET_ID = 1;
+
+ private List<TvTrackInfo> mTrackInfos;
+ private String mSelectedTrackId;
+ private TvTrackInfo mSelectedTrack;
+ private int mTrackType;
+ private DvrPlaybackOverlayFragment mOverlayFragment;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ mTrackInfos = getArguments().getParcelableArrayList(TRACK_INFOS);
+ mTrackType = mTrackInfos.get(0).getType();
+ mSelectedTrackId = getArguments().getString(SELECTED_TRACK_ID);
+ mOverlayFragment = ((DvrPlaybackOverlayFragment) getFragmentManager()
+ .findFragmentById(R.id.dvr_playback_controls_fragment));
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ public View onCreateBackgroundView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View backgroundView = super.onCreateBackgroundView(inflater, container, savedInstanceState);
+ backgroundView.setBackgroundColor(getResources()
+ .getColor(R.color.lb_playback_controls_background_light));
+ return backgroundView;
+ }
+
+ @Override
+ public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
+ if (mTrackType == TvTrackInfo.TYPE_SUBTITLE) {
+ actions.add(new GuidedAction.Builder(getActivity())
+ .id(ACTION_ID_NO_SUBTITLE)
+ .title(getString(R.string.closed_caption_option_item_off))
+ .checkSetId(CHECK_SET_ID)
+ .checked(mSelectedTrackId == null)
+ .build());
+ }
+ for (int i = 0; i < mTrackInfos.size(); i++) {
+ TvTrackInfo info = mTrackInfos.get(i);
+ boolean checked = TextUtils.equals(info.getId(), mSelectedTrackId);
+ GuidedAction action = new GuidedAction.Builder(getActivity())
+ .id(i)
+ .title(getTrackLabel(info, i))
+ .checkSetId(CHECK_SET_ID)
+ .checked(checked)
+ .build();
+ actions.add(action);
+ if (checked) {
+ mSelectedTrack = info;
+ }
+ }
+ }
+
+ @Override
+ public void onGuidedActionFocused(GuidedAction action) {
+ int actionId = (int) action.getId();
+ mOverlayFragment.selectTrack(mTrackType, actionId < 0 ? null : mTrackInfos.get(actionId));
+ }
+
+ @Override
+ public void onGuidedActionClicked(GuidedAction action) {
+ int actionId = (int) action.getId();
+ mSelectedTrack = actionId < 0 ? null : mTrackInfos.get(actionId);
+ TvSettings.setDvrPlaybackTrackSettings(getContext(), mTrackType, mSelectedTrack);
+ getFragmentManager().popBackStack();
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ // Workaround: when overlay fragment is faded out, any focus will lost due to overlay
+ // fragment's implementation. So we disable overlay fragment's fading here to prevent
+ // losing focus while users are interacting with the side fragment.
+ mOverlayFragment.setFadingEnabled(false);
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ // We disable fading of overlay fragment to prevent side fragment from losing focus,
+ // therefore we should resume it here.
+ mOverlayFragment.setFadingEnabled(true);
+ mOverlayFragment.selectTrack(mTrackType, mSelectedTrack);
+ }
+
+ private String getTrackLabel(TvTrackInfo track, int trackIndex) {
+ if (track.getLanguage() != null) {
+ return new Locale(track.getLanguage()).getDisplayName();
+ }
+ return track.getType() == TvTrackInfo.TYPE_SUBTITLE ?
+ getString(R.string.closed_caption_unknown_language, trackIndex + 1)
+ : getString(R.string.multi_audio_unknown_language);
+ }
+
+ @Override
+ protected void onProvideFragmentTransitions() {
+ super.onProvideFragmentTransitions();
+ // Excludes the background scrim from transition to prevent the blinking caused by
+ // hiding the overlay fragment and sliding in the side fragment at the same time.
+ Transition t = getEnterTransition();
+ if (t != null) {
+ t.excludeTarget(R.id.guidedstep_background, true);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/tv/dvr/DvrPlayer.java b/src/com/android/tv/dvr/ui/playback/DvrPlayer.java
index 5656655c..780bfb2f 100644
--- a/src/com/android/tv/dvr/DvrPlayer.java
+++ b/src/com/android/tv/dvr/ui/playback/DvrPlayer.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.tv.dvr;
+package com.android.tv.dvr.ui.playback;
import android.media.PlaybackParams;
import android.media.tv.TvContentRating;
@@ -22,12 +22,16 @@ import android.media.tv.TvInputManager;
import android.media.tv.TvTrackInfo;
import android.media.tv.TvView;
import android.media.session.PlaybackState;
+import android.text.TextUtils;
import android.util.Log;
+import com.android.tv.dvr.data.RecordedProgram;
+
+import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
-public class DvrPlayer {
+class DvrPlayer {
private static final String TAG = "DvrPlayer";
private static final boolean DEBUG = false;
@@ -47,12 +51,19 @@ public class DvrPlayer {
private long mInitialSeekPositionMs;
private final TvView mTvView;
private DvrPlayerCallback mCallback;
- private AspectRatioChangedListener mAspectRatioChangedListener;
- private ContentBlockedListener mContentBlockedListener;
+ 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;
@@ -75,22 +86,40 @@ public class DvrPlayer {
public void onPlaybackEnded() { }
}
- public interface AspectRatioChangedListener {
+ 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 ContentBlockedListener {
+ 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);
@@ -236,6 +265,8 @@ public class DvrPlayer {
mTimeShiftCurrentPositionMs = 0;
mPlaybackParams.setSpeed(1.0f);
mProgram = null;
+ mSelectedAudioTrackId = null;
+ mSelectedSubtitleTrackId = null;
}
/**
@@ -250,17 +281,51 @@ public class DvrPlayer {
}
/**
- * Sets listener to aspect ratio changing.
+ * 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 setAspectRatioChangedListener(AspectRatioChangedListener listener) {
- mAspectRatioChangedListener = listener;
+ public void setOnTrackSelectedListener(int trackType, OnTrackSelectedListener listener) {
+ if (trackType == TvTrackInfo.TYPE_AUDIO) {
+ mOnAudioTrackSelectedListener = listener;
+ } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
+ mOnSubtitleTrackSelectedListener = listener;
+ }
}
/**
- * Sets listener to content blocking.
+ * Gets the listener to tracks of the given type being selected.
*/
- public void setContentBlockedListener(ContentBlockedListener listener) {
- mContentBlockedListener = listener;
+ public OnTrackSelectedListener getOnTrackSelectedListener(int trackType) {
+ if (trackType == TvTrackInfo.TYPE_AUDIO) {
+ return mOnAudioTrackSelectedListener;
+ } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
+ return mOnSubtitleTrackSelectedListener;
+ }
+ return null;
}
/**
@@ -306,6 +371,32 @@ public class DvrPlayer {
}
/**
+ * 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() {
@@ -313,6 +404,41 @@ public class DvrPlayer {
&& 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);
@@ -369,28 +495,60 @@ public class DvrPlayer {
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 onTrackSelected(String inputId, int type, String trackId) {
- if (trackId == null || type != TvTrackInfo.TYPE_VIDEO
- || mAspectRatioChangedListener == null) {
- return;
+ 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);
}
- List<TvTrackInfo> trackInfos = mTvView.getTracks(TvTrackInfo.TYPE_VIDEO);
- if (trackInfos != null) {
- for (TvTrackInfo trackInfo : trackInfos) {
- if (trackInfo.getId().equals(trackId)) {
- float videoAspectRatio = trackInfo.getVideoPixelAspectRatio()
- * trackInfo.getVideoWidth() / trackInfo.getVideoHeight();
- if (DEBUG) Log.d(TAG, "Aspect Ratio: " + videoAspectRatio);
- if (!Float.isNaN(videoAspectRatio)
- && mAspectRatio != videoAspectRatio) {
- mAspectRatioChangedListener
- .onAspectRatioChanged(videoAspectRatio);
- mAspectRatio = videoAspectRatio;
- return;
+ 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;
+ }
}
}
}
@@ -399,8 +557,8 @@ public class DvrPlayer {
@Override
public void onContentBlocked(String inputId, TvContentRating rating) {
- if (mContentBlockedListener != null) {
- mContentBlockedListener.onContentBlocked(rating);
+ if (mOnContentBlockedListener != null) {
+ mOnContentBlockedListener.onContentBlocked(rating);
}
}
});